@swarmclawai/swarmclaw 0.7.2 → 0.7.4
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 +116 -50
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +43 -0
- package/src/app/api/agents/[id]/thread/route.ts +39 -8
- package/src/app/api/agents/route.ts +35 -2
- package/src/app/api/auth/route.ts +77 -8
- package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +30 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +23 -1
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +12 -4
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +55 -17
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +16 -6
- package/src/app/api/tasks/bulk/route.ts +3 -3
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +135 -17
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +38 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +21 -12
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +456 -23
- package/src/components/agents/inspector-panel.tsx +110 -49
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +70 -27
- package/src/components/chat/chat-card.tsx +6 -21
- package/src/components/chat/chat-header.tsx +263 -366
- package/src/components/chat/chat-list.tsx +62 -26
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +145 -19
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +422 -209
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +385 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +189 -1
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +88 -6
- package/src/components/shared/settings/section-orchestrator.tsx +6 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +248 -47
- package/src/components/tasks/approvals-panel.tsx +211 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +264 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +44 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
- package/src/lib/server/chat-execution.ts +402 -125
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +74 -2
- package/src/lib/server/chatroom-helpers.ts +144 -11
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +994 -130
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +189 -10
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/daemon-state.ts +62 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +23 -43
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +31 -964
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +6 -5
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +18 -8
- package/src/lib/server/orchestrator.ts +5 -4
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +215 -0
- package/src/lib/server/plugins.ts +832 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +4 -21
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +96 -34
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +40 -12
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +243 -24
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +87 -2
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +162 -12
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +95 -25
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +58 -4
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +195 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +947 -108
- package/src/lib/server/storage.ts +255 -10
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +185 -25
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -11
- package/src/lib/server/tool-aliases.ts +80 -12
- package/src/lib/server/tool-capability-policy.ts +7 -1
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +62 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +43 -7
- package/src/stores/use-chat-store.ts +31 -2
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +470 -44
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- package/src/lib/server/session-run-manager.test.ts +0 -26
|
@@ -5,7 +5,9 @@ import os from 'os'
|
|
|
5
5
|
import Database from 'better-sqlite3'
|
|
6
6
|
|
|
7
7
|
import { DATA_DIR, WORKSPACE_DIR } from './data-dir'
|
|
8
|
-
import
|
|
8
|
+
import { normalizeHeartbeatSettingFields } from '@/lib/heartbeat-defaults'
|
|
9
|
+
import { normalizeRuntimeSettingFields } from '@/lib/runtime-loop'
|
|
10
|
+
import type { ExternalAgentRuntime, GatewayProfile, Message } from '@/types'
|
|
9
11
|
export const UPLOAD_DIR = path.join(DATA_DIR, 'uploads')
|
|
10
12
|
|
|
11
13
|
// --- LRU Cache ---
|
|
@@ -135,6 +137,7 @@ const COLLECTIONS = [
|
|
|
135
137
|
'tasks',
|
|
136
138
|
'secrets',
|
|
137
139
|
'provider_configs',
|
|
140
|
+
'gateway_profiles',
|
|
138
141
|
'skills',
|
|
139
142
|
'connectors',
|
|
140
143
|
'documents',
|
|
@@ -156,8 +159,14 @@ const COLLECTIONS = [
|
|
|
156
159
|
'souls',
|
|
157
160
|
'benchmarks',
|
|
158
161
|
'approvals',
|
|
162
|
+
'browser_sessions',
|
|
163
|
+
'watch_jobs',
|
|
164
|
+
'delegation_jobs',
|
|
165
|
+
'external_agents',
|
|
159
166
|
] as const
|
|
160
167
|
|
|
168
|
+
export type StorageCollection = (typeof COLLECTIONS)[number]
|
|
169
|
+
|
|
161
170
|
for (const table of COLLECTIONS) {
|
|
162
171
|
db.exec(`CREATE TABLE IF NOT EXISTS ${table} (id TEXT PRIMARY KEY, data TEXT NOT NULL)`)
|
|
163
172
|
}
|
|
@@ -185,12 +194,34 @@ function getCollectionRawCache(table: string): LRUMap<string, string> {
|
|
|
185
194
|
return loaded
|
|
186
195
|
}
|
|
187
196
|
|
|
197
|
+
function normalizeStoredRecord(table: string, value: any): any {
|
|
198
|
+
if (table !== 'sessions') return value
|
|
199
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return value
|
|
200
|
+
|
|
201
|
+
const session = value as Record<string, any>
|
|
202
|
+
if (session.sessionType !== 'human') session.sessionType = 'human'
|
|
203
|
+
const isLegacyShortcut = (
|
|
204
|
+
(typeof session.id === 'string' && session.id.startsWith('agent-thread-'))
|
|
205
|
+
|| (typeof session.name === 'string' && session.name.startsWith('agent-thread:'))
|
|
206
|
+
)
|
|
207
|
+
if (
|
|
208
|
+
isLegacyShortcut
|
|
209
|
+
&& typeof session.agentId === 'string'
|
|
210
|
+
&& session.agentId.trim()
|
|
211
|
+
&& (!session.shortcutForAgentId || session.shortcutForAgentId !== session.agentId)
|
|
212
|
+
) {
|
|
213
|
+
session.shortcutForAgentId = session.agentId
|
|
214
|
+
}
|
|
215
|
+
if ('mainLoopState' in session) delete session.mainLoopState
|
|
216
|
+
return session
|
|
217
|
+
}
|
|
218
|
+
|
|
188
219
|
function loadCollection(table: string): Record<string, any> {
|
|
189
220
|
const raw = getCollectionRawCache(table)
|
|
190
221
|
const result: Record<string, any> = {}
|
|
191
222
|
for (const [id, data] of raw.entries()) {
|
|
192
223
|
try {
|
|
193
|
-
result[id] = JSON.parse(data)
|
|
224
|
+
result[id] = normalizeStoredRecord(table, JSON.parse(data))
|
|
194
225
|
} catch {
|
|
195
226
|
// Ignore malformed records instead of crashing list endpoints.
|
|
196
227
|
}
|
|
@@ -205,7 +236,8 @@ function saveCollection(table: string, data: Record<string, any>) {
|
|
|
205
236
|
const toDelete: string[] = []
|
|
206
237
|
|
|
207
238
|
for (const [id, val] of Object.entries(data)) {
|
|
208
|
-
const
|
|
239
|
+
const normalized = normalizeStoredRecord(table, val)
|
|
240
|
+
const serialized = JSON.stringify(normalized)
|
|
209
241
|
if (typeof serialized !== 'string') continue
|
|
210
242
|
next.set(id, serialized)
|
|
211
243
|
if (current.get(id) !== serialized) {
|
|
@@ -251,7 +283,7 @@ function deleteCollectionItem(table: string, id: string) {
|
|
|
251
283
|
* concurrent processes are modifying different items.
|
|
252
284
|
*/
|
|
253
285
|
function upsertCollectionItem(table: string, id: string, value: any) {
|
|
254
|
-
const serialized = JSON.stringify(value)
|
|
286
|
+
const serialized = JSON.stringify(normalizeStoredRecord(table, value))
|
|
255
287
|
db.prepare(`INSERT OR REPLACE INTO ${table} (id, data) VALUES (?, ?)`).run(id, serialized)
|
|
256
288
|
// Update the in-memory cache
|
|
257
289
|
const cached = collectionCache.get(table)
|
|
@@ -260,6 +292,51 @@ function upsertCollectionItem(table: string, id: string, value: any) {
|
|
|
260
292
|
}
|
|
261
293
|
}
|
|
262
294
|
|
|
295
|
+
function loadCollectionItem(table: string, id: string): any | null {
|
|
296
|
+
const row = db.prepare(`SELECT data FROM ${table} WHERE id = ?`).get(id) as { data: string } | undefined
|
|
297
|
+
if (!row) return null
|
|
298
|
+
try {
|
|
299
|
+
return normalizeStoredRecord(table, JSON.parse(row.data))
|
|
300
|
+
} catch {
|
|
301
|
+
return null
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function upsertCollectionItems(table: string, entries: Array<[string, any]>): void {
|
|
306
|
+
if (!entries.length) return
|
|
307
|
+
const prepared = entries
|
|
308
|
+
.map(([id, value]) => [id, JSON.stringify(normalizeStoredRecord(table, value))] as const)
|
|
309
|
+
.filter(([, serialized]) => typeof serialized === 'string')
|
|
310
|
+
if (!prepared.length) return
|
|
311
|
+
|
|
312
|
+
const transaction = db.transaction(() => {
|
|
313
|
+
const upsert = db.prepare(`INSERT OR REPLACE INTO ${table} (id, data) VALUES (?, ?)`)
|
|
314
|
+
for (const [id, serialized] of prepared) {
|
|
315
|
+
upsert.run(id, serialized)
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
transaction()
|
|
319
|
+
|
|
320
|
+
const cached = collectionCache.get(table)
|
|
321
|
+
if (cached) {
|
|
322
|
+
for (const [id, serialized] of prepared) {
|
|
323
|
+
cached.set(id, serialized)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function loadStoredItem(table: StorageCollection, id: string): any | null {
|
|
329
|
+
return loadCollectionItem(table, id)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function upsertStoredItem(table: StorageCollection, id: string, value: any): void {
|
|
333
|
+
upsertCollectionItem(table, id, value)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function upsertStoredItems(table: StorageCollection, entries: Array<[string, any]>): void {
|
|
337
|
+
upsertCollectionItems(table, entries)
|
|
338
|
+
}
|
|
339
|
+
|
|
263
340
|
function loadSingleton(table: string, fallback: any): any {
|
|
264
341
|
const row = db.prepare(`SELECT data FROM ${table} WHERE id = 1`).get() as { data: string } | undefined
|
|
265
342
|
return row ? JSON.parse(row.data) : fallback
|
|
@@ -279,10 +356,12 @@ const JSON_FILES: Record<string, string> = {
|
|
|
279
356
|
tasks: path.join(DATA_DIR, 'tasks.json'),
|
|
280
357
|
secrets: path.join(DATA_DIR, 'secrets.json'),
|
|
281
358
|
provider_configs: path.join(DATA_DIR, 'providers.json'),
|
|
359
|
+
gateway_profiles: path.join(DATA_DIR, 'gateways.json'),
|
|
282
360
|
skills: path.join(DATA_DIR, 'skills.json'),
|
|
283
361
|
connectors: path.join(DATA_DIR, 'connectors.json'),
|
|
284
362
|
documents: path.join(DATA_DIR, 'documents.json'),
|
|
285
363
|
webhooks: path.join(DATA_DIR, 'webhooks.json'),
|
|
364
|
+
external_agents: path.join(DATA_DIR, 'external-agents.json'),
|
|
286
365
|
}
|
|
287
366
|
|
|
288
367
|
const MIGRATION_FLAG = path.join(DATA_DIR, '.sqlite_migrated')
|
|
@@ -392,7 +471,7 @@ if (!IS_BUILD_BOOTSTRAP) {
|
|
|
392
471
|
|
|
393
472
|
## Platform
|
|
394
473
|
|
|
395
|
-
- **Agents** — Create specialized AI agents (Agents tab → "+") with a provider, model, system prompt, and tools. "Generate with AI" scaffolds agents from a description.
|
|
474
|
+
- **Agents** — Create specialized AI agents (Agents tab → "+") with a provider, model, system prompt, and tools. "Generate with AI" scaffolds agents from a description. Enable cross-agent delegation when an agent should assign work to others.
|
|
396
475
|
- **Providers** — Configure LLM backends in Settings → Providers: Claude Code CLI, OpenAI Codex CLI, OpenCode CLI, Anthropic, OpenAI, Google Gemini, DeepSeek, Groq, Together AI, Mistral AI, xAI (Grok), Fireworks AI, Ollama, OpenClaw, or custom OpenAI-compatible endpoints.
|
|
397
476
|
- **Tasks** — The Task Board tracks work items. Assign agents and they'll execute autonomously.
|
|
398
477
|
- **Schedules** — Cron-based recurring jobs that run agents or tasks automatically.
|
|
@@ -419,7 +498,7 @@ Use your platform management tools proactively:
|
|
|
419
498
|
You have opinions about good agent design. You suggest creative approaches, warn about common pitfalls, and get excited when someone gets something cool working. You're not a manual — you're a collaborator.
|
|
420
499
|
|
|
421
500
|
Be concise but not curt. Warmth doesn't require verbosity. When someone asks "how do I...?", give them the direct steps. Offer to do things rather than just explaining — if someone wants an agent created, create it. Use your tools when actions speak louder than words. If you don't know something, say so honestly.`,
|
|
422
|
-
isOrchestrator:
|
|
501
|
+
isOrchestrator: true,
|
|
423
502
|
plugins: defaultStarterTools,
|
|
424
503
|
heartbeatEnabled: true,
|
|
425
504
|
platformAssignScope: 'all',
|
|
@@ -440,6 +519,15 @@ Be concise but not curt. Warmth doesn't require verbosity. When someone asks "ho
|
|
|
440
519
|
existing.plugins = mergedPlugins
|
|
441
520
|
delete existing.tools
|
|
442
521
|
existing.updatedAt = Date.now()
|
|
522
|
+
}
|
|
523
|
+
if (existing.platformAssignScope === 'all' || existing.platformAssignScope === 'self') {
|
|
524
|
+
const derivedIsOrchestrator = existing.platformAssignScope === 'all'
|
|
525
|
+
if (existing.isOrchestrator !== derivedIsOrchestrator) {
|
|
526
|
+
existing.isOrchestrator = derivedIsOrchestrator
|
|
527
|
+
existing.updatedAt = Date.now()
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (JSON.stringify(JSON.parse(row.data)) !== JSON.stringify(existing)) {
|
|
443
531
|
db.prepare('UPDATE agents SET data = ? WHERE id = ?').run(JSON.stringify(existing), 'default')
|
|
444
532
|
}
|
|
445
533
|
} catch {
|
|
@@ -685,12 +773,108 @@ export function saveQueue(q: string[]) {
|
|
|
685
773
|
}
|
|
686
774
|
|
|
687
775
|
// --- Settings ---
|
|
776
|
+
const APP_SETTINGS_SECRET_FIELDS = [
|
|
777
|
+
'elevenLabsApiKey',
|
|
778
|
+
'tavilyApiKey',
|
|
779
|
+
'braveApiKey',
|
|
780
|
+
] as const
|
|
781
|
+
|
|
782
|
+
const ENCRYPTED_APP_SETTINGS_KEY = '__encryptedAppSettings'
|
|
783
|
+
|
|
784
|
+
type PersistedSettingsRecord = Record<string, any> & {
|
|
785
|
+
[ENCRYPTED_APP_SETTINGS_KEY]?: Record<string, string>
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function cloneRecord<T extends Record<string, any>>(value: T): T {
|
|
789
|
+
return JSON.parse(JSON.stringify(value || {})) as T
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function isPlainRecord(value: unknown): value is Record<string, any> {
|
|
793
|
+
return !!value && typeof value === 'object' && !Array.isArray(value)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function getEncryptedAppSettings(settings: PersistedSettingsRecord): Record<string, string> {
|
|
797
|
+
return isPlainRecord(settings[ENCRYPTED_APP_SETTINGS_KEY])
|
|
798
|
+
? { ...(settings[ENCRYPTED_APP_SETTINGS_KEY] as Record<string, string>) }
|
|
799
|
+
: {}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function isClearedSecretValue(value: unknown): boolean {
|
|
803
|
+
return value === null || (typeof value === 'string' && value.trim() === '')
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function isProvidedSecretValue(value: unknown): value is string {
|
|
807
|
+
return typeof value === 'string' && value.trim().length > 0
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function buildPersistedSettings(input: Record<string, any>, existing?: PersistedSettingsRecord): PersistedSettingsRecord {
|
|
811
|
+
const next = cloneRecord(input) as PersistedSettingsRecord
|
|
812
|
+
Object.assign(next, normalizeRuntimeSettingFields(next))
|
|
813
|
+
Object.assign(next, normalizeHeartbeatSettingFields(next))
|
|
814
|
+
const encrypted = {
|
|
815
|
+
...(existing ? getEncryptedAppSettings(existing) : {}),
|
|
816
|
+
...getEncryptedAppSettings(next),
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
delete next[ENCRYPTED_APP_SETTINGS_KEY]
|
|
820
|
+
|
|
821
|
+
for (const field of APP_SETTINGS_SECRET_FIELDS) {
|
|
822
|
+
const raw = next[field]
|
|
823
|
+
if (isClearedSecretValue(raw)) {
|
|
824
|
+
delete encrypted[field]
|
|
825
|
+
delete next[field]
|
|
826
|
+
continue
|
|
827
|
+
}
|
|
828
|
+
if (isProvidedSecretValue(raw)) {
|
|
829
|
+
encrypted[field] = encryptKey(raw)
|
|
830
|
+
delete next[field]
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (Object.keys(encrypted).length > 0) next[ENCRYPTED_APP_SETTINGS_KEY] = encrypted
|
|
835
|
+
return next
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function resolveSettingsSecrets(settings: PersistedSettingsRecord): Record<string, any> {
|
|
839
|
+
const resolved = cloneRecord(settings)
|
|
840
|
+
delete resolved[ENCRYPTED_APP_SETTINGS_KEY]
|
|
841
|
+
|
|
842
|
+
const encrypted = getEncryptedAppSettings(settings)
|
|
843
|
+
for (const field of APP_SETTINGS_SECRET_FIELDS) {
|
|
844
|
+
if (isProvidedSecretValue(resolved[field])) continue
|
|
845
|
+
const value = encrypted[field]
|
|
846
|
+
if (typeof value !== 'string' || !value) continue
|
|
847
|
+
try {
|
|
848
|
+
resolved[field] = decryptKey(value)
|
|
849
|
+
} catch {
|
|
850
|
+
// Ignore malformed encrypted settings instead of breaking all settings reads.
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
return resolved
|
|
855
|
+
}
|
|
856
|
+
|
|
688
857
|
export function loadSettings(): Record<string, any> {
|
|
689
|
-
|
|
858
|
+
const persisted = loadSingleton('settings', {}) as PersistedSettingsRecord
|
|
859
|
+
const normalized = buildPersistedSettings(persisted, persisted)
|
|
860
|
+
if (JSON.stringify(persisted) !== JSON.stringify(normalized)) {
|
|
861
|
+
saveSingleton('settings', normalized)
|
|
862
|
+
}
|
|
863
|
+
return resolveSettingsSecrets(normalized)
|
|
690
864
|
}
|
|
691
865
|
|
|
692
866
|
export function saveSettings(s: Record<string, any>) {
|
|
693
|
-
|
|
867
|
+
const existing = loadSingleton('settings', {}) as PersistedSettingsRecord
|
|
868
|
+
saveSingleton('settings', buildPersistedSettings(s, existing))
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
export function loadPublicSettings(): Record<string, any> {
|
|
872
|
+
const settings = cloneRecord(loadSettings())
|
|
873
|
+
for (const field of APP_SETTINGS_SECRET_FIELDS) {
|
|
874
|
+
settings[`${field}Configured`] = isProvidedSecretValue(settings[field])
|
|
875
|
+
settings[field] = null
|
|
876
|
+
}
|
|
877
|
+
return settings
|
|
694
878
|
}
|
|
695
879
|
|
|
696
880
|
// --- Secrets (service keys for orchestrators) ---
|
|
@@ -759,6 +943,15 @@ export function saveProviderConfigs(p: Record<string, any>) {
|
|
|
759
943
|
saveCollection('provider_configs', p)
|
|
760
944
|
}
|
|
761
945
|
|
|
946
|
+
// --- Gateway Profiles ---
|
|
947
|
+
export function loadGatewayProfiles(): Record<string, any> {
|
|
948
|
+
return loadCollection('gateway_profiles') as Record<string, GatewayProfile>
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
export function saveGatewayProfiles(g: Record<string, GatewayProfile>) {
|
|
952
|
+
saveCollection('gateway_profiles', g)
|
|
953
|
+
}
|
|
954
|
+
|
|
762
955
|
// --- Model Overrides (user-added models for built-in providers) ---
|
|
763
956
|
export function loadModelOverrides(): Record<string, string[]> {
|
|
764
957
|
return loadCollection('model_overrides') as Record<string, string[]>
|
|
@@ -788,6 +981,15 @@ export function saveSkills(s: Record<string, any>) {
|
|
|
788
981
|
saveCollection('skills', s)
|
|
789
982
|
}
|
|
790
983
|
|
|
984
|
+
// --- External Agent Runtimes ---
|
|
985
|
+
export function loadExternalAgents(): Record<string, ExternalAgentRuntime> {
|
|
986
|
+
return loadCollection('external_agents') as Record<string, ExternalAgentRuntime>
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
export function saveExternalAgents(items: Record<string, ExternalAgentRuntime>) {
|
|
990
|
+
saveCollection('external_agents', items)
|
|
991
|
+
}
|
|
992
|
+
|
|
791
993
|
// --- Usage ---
|
|
792
994
|
export function loadUsage(): Record<string, any[]> {
|
|
793
995
|
const stmt = db.prepare('SELECT session_id, data FROM usage')
|
|
@@ -891,11 +1093,11 @@ export function saveIntegrityBaselines(entries: Record<string, any>) {
|
|
|
891
1093
|
}
|
|
892
1094
|
|
|
893
1095
|
// --- Webhook Logs ---
|
|
894
|
-
export function loadWebhookLogs(): Record<string,
|
|
1096
|
+
export function loadWebhookLogs(): Record<string, unknown> {
|
|
895
1097
|
return loadCollection('webhook_logs')
|
|
896
1098
|
}
|
|
897
1099
|
|
|
898
|
-
export function appendWebhookLog(id: string, entry:
|
|
1100
|
+
export function appendWebhookLog(id: string, entry: unknown) {
|
|
899
1101
|
upsertCollectionItem('webhook_logs', id, entry)
|
|
900
1102
|
}
|
|
901
1103
|
|
|
@@ -1026,6 +1228,49 @@ export function loadApprovals(): Record<string, unknown> {
|
|
|
1026
1228
|
return loadCollection('approvals')
|
|
1027
1229
|
}
|
|
1028
1230
|
|
|
1231
|
+
// --- Browser Sessions ---
|
|
1232
|
+
export function loadBrowserSessions(): Record<string, unknown> {
|
|
1233
|
+
return loadCollection('browser_sessions')
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
export function upsertBrowserSession(id: string, data: unknown) {
|
|
1237
|
+
upsertCollectionItem('browser_sessions', id, data)
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
export function deleteBrowserSession(id: string) {
|
|
1241
|
+
deleteCollectionItem('browser_sessions', id)
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// --- Watch Jobs ---
|
|
1245
|
+
export function loadWatchJobs(): Record<string, unknown> {
|
|
1246
|
+
return loadCollection('watch_jobs')
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
export function upsertWatchJob(id: string, data: unknown) {
|
|
1250
|
+
upsertCollectionItem('watch_jobs', id, data)
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
export function upsertWatchJobs(entries: Array<[string, unknown]>) {
|
|
1254
|
+
upsertCollectionItems('watch_jobs', entries)
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
export function deleteWatchJob(id: string) {
|
|
1258
|
+
deleteCollectionItem('watch_jobs', id)
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// --- Delegation Jobs ---
|
|
1262
|
+
export function loadDelegationJobs(): Record<string, unknown> {
|
|
1263
|
+
return loadCollection('delegation_jobs')
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
export function upsertDelegationJob(id: string, data: unknown) {
|
|
1267
|
+
upsertCollectionItem('delegation_jobs', id, data)
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
export function deleteDelegationJob(id: string) {
|
|
1271
|
+
deleteCollectionItem('delegation_jobs', id)
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1029
1274
|
export function upsertApproval(id: string, approval: unknown) {
|
|
1030
1275
|
upsertCollectionItem('approvals', id, approval)
|
|
1031
1276
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { describe, it } from 'node:test'
|
|
5
|
+
import { buildToolDisciplineLines, looksLikeOpenEndedDeliverableTask } from './stream-agent-chat'
|
|
6
|
+
|
|
7
|
+
const streamAgentChatSource = fs.readFileSync(path.join(path.dirname(new URL(import.meta.url).pathname), 'stream-agent-chat.ts'), 'utf-8')
|
|
8
|
+
|
|
9
|
+
describe('buildToolDisciplineLines', () => {
|
|
10
|
+
it('tells the agent to use direct platform tools when manage_platform is absent', () => {
|
|
11
|
+
const lines = buildToolDisciplineLines(['files', 'manage_schedules'])
|
|
12
|
+
|
|
13
|
+
assert.equal(lines[0], 'Enabled tools in this session: `files`, `manage_schedules`.')
|
|
14
|
+
assert.ok(lines.some((line) => line.includes('Do not substitute `manage_platform`')))
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('omits the manage_platform warning when the umbrella tool is enabled', () => {
|
|
18
|
+
const lines = buildToolDisciplineLines(['manage_platform', 'manage_schedules'])
|
|
19
|
+
|
|
20
|
+
assert.ok(lines.every((line) => !line.includes('Do not substitute `manage_platform`')))
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('includes concrete files-tool examples for revision work', () => {
|
|
24
|
+
const lines = buildToolDisciplineLines(['files'])
|
|
25
|
+
|
|
26
|
+
assert.ok(lines.some((line) => line.includes('{"action":"read","filePath":"path/to/file.md"}')))
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('warns browser tasks to use literal urls and the supported form schema', () => {
|
|
30
|
+
const lines = buildToolDisciplineLines(['browser', 'http_request', 'email', 'ask_human'])
|
|
31
|
+
|
|
32
|
+
assert.ok(lines.some((line) => line.includes('Do not invent placeholder URLs')))
|
|
33
|
+
assert.ok(lines.some((line) => line.includes('A shorthand `form` object keyed by input id/name also works')))
|
|
34
|
+
assert.ok(lines.some((line) => line.includes('Keep JSON request bodies as raw JSON strings')))
|
|
35
|
+
assert.ok(lines.some((line) => line.includes('{"action":"send","to":"user@example.com","subject":"...","body":"..."}')))
|
|
36
|
+
assert.ok(lines.some((line) => line.includes('do not guess or keep re-submitting blank forms')))
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('tells the agent that named enabled tools are completion requirements', () => {
|
|
40
|
+
assert.ok(streamAgentChatSource.includes('If a task explicitly names an enabled tool, use that tool before declaring success.'))
|
|
41
|
+
assert.ok(streamAgentChatSource.includes('collect required human input through the tool'))
|
|
42
|
+
assert.ok(streamAgentChatSource.includes('You have not yet completed the required explicit tool step(s):'))
|
|
43
|
+
assert.ok(streamAgentChatSource.includes('[Loop Budget Reached]'))
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('looksLikeOpenEndedDeliverableTask', () => {
|
|
48
|
+
it('detects open-ended deliverable prompts', () => {
|
|
49
|
+
assert.equal(
|
|
50
|
+
looksLikeOpenEndedDeliverableTask('Revise the landing copy and update the proposal draft with a stronger second pass.'),
|
|
51
|
+
true,
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('does not misclassify explicit coding tasks', () => {
|
|
56
|
+
assert.equal(
|
|
57
|
+
looksLikeOpenEndedDeliverableTask('Fix the React bug in src/components/chat/chat-area.tsx and run npm run build.'),
|
|
58
|
+
false,
|
|
59
|
+
)
|
|
60
|
+
})
|
|
61
|
+
})
|