@swarmclawai/swarmclaw 0.4.0 → 0.5.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 +21 -4
- package/bin/server-cmd.js +28 -19
- package/next.config.ts +13 -0
- package/package.json +3 -1
- package/src/app/api/agents/[id]/route.ts +39 -22
- package/src/app/api/agents/[id]/thread/route.ts +2 -2
- package/src/app/api/agents/route.ts +3 -2
- package/src/app/api/agents/trash/route.ts +44 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/connectors/[id]/route.ts +17 -7
- package/src/app/api/connectors/[id]/webhook/route.ts +103 -0
- package/src/app/api/connectors/route.ts +6 -3
- package/src/app/api/credentials/[id]/route.ts +2 -1
- package/src/app/api/credentials/route.ts +2 -2
- package/src/app/api/documents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +8 -0
- package/src/app/api/knowledge/[id]/route.ts +5 -4
- package/src/app/api/knowledge/upload/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/route.ts +11 -14
- package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
- package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
- package/src/app/api/mcp-servers/route.ts +2 -2
- package/src/app/api/memory/[id]/route.ts +9 -8
- package/src/app/api/memory/route.ts +2 -2
- package/src/app/api/memory-images/[filename]/route.ts +2 -1
- package/src/app/api/openclaw/agent-files/route.ts +57 -0
- package/src/app/api/openclaw/approvals/route.ts +46 -0
- package/src/app/api/openclaw/config-sync/route.ts +33 -0
- package/src/app/api/openclaw/cron/route.ts +52 -0
- package/src/app/api/openclaw/directory/route.ts +27 -0
- package/src/app/api/openclaw/discover/route.ts +62 -0
- package/src/app/api/openclaw/dotenv-keys/route.ts +18 -0
- package/src/app/api/openclaw/exec-config/route.ts +41 -0
- package/src/app/api/openclaw/gateway/route.ts +72 -0
- package/src/app/api/openclaw/history/route.ts +109 -0
- package/src/app/api/openclaw/media/route.ts +53 -0
- package/src/app/api/openclaw/models/route.ts +12 -0
- package/src/app/api/openclaw/permissions/route.ts +39 -0
- package/src/app/api/openclaw/sandbox-env/route.ts +69 -0
- package/src/app/api/openclaw/skills/install/route.ts +32 -0
- package/src/app/api/openclaw/skills/remove/route.ts +24 -0
- package/src/app/api/openclaw/skills/route.ts +82 -0
- package/src/app/api/openclaw/sync/route.ts +31 -0
- package/src/app/api/orchestrator/run/route.ts +2 -2
- package/src/app/api/projects/[id]/route.ts +55 -0
- package/src/app/api/projects/route.ts +27 -0
- package/src/app/api/providers/[id]/models/route.ts +2 -1
- package/src/app/api/providers/[id]/route.ts +13 -15
- package/src/app/api/providers/route.ts +2 -2
- package/src/app/api/schedules/[id]/route.ts +16 -18
- package/src/app/api/schedules/[id]/run/route.ts +4 -3
- package/src/app/api/schedules/route.ts +2 -2
- package/src/app/api/secrets/[id]/route.ts +16 -17
- package/src/app/api/secrets/route.ts +2 -2
- package/src/app/api/sessions/[id]/clear/route.ts +2 -1
- package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
- package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
- package/src/app/api/sessions/[id]/edit-resend/route.ts +22 -0
- package/src/app/api/sessions/[id]/fork/route.ts +44 -0
- package/src/app/api/sessions/[id]/messages/route.ts +20 -2
- package/src/app/api/sessions/[id]/retry/route.ts +2 -1
- package/src/app/api/sessions/[id]/route.ts +14 -4
- package/src/app/api/sessions/route.ts +8 -4
- package/src/app/api/skills/[id]/route.ts +23 -21
- package/src/app/api/skills/import/route.ts +2 -2
- package/src/app/api/skills/route.ts +2 -2
- package/src/app/api/tasks/[id]/approve/route.ts +2 -1
- package/src/app/api/tasks/[id]/route.ts +6 -5
- package/src/app/api/tasks/route.ts +2 -2
- package/src/app/api/tts/stream/route.ts +48 -0
- package/src/app/api/upload/route.ts +2 -2
- package/src/app/api/uploads/[filename]/route.ts +4 -1
- package/src/app/api/webhooks/[id]/route.ts +29 -31
- package/src/app/api/webhooks/route.ts +2 -2
- package/src/app/globals.css +14 -0
- package/src/app/layout.tsx +5 -20
- package/src/app/page.tsx +3 -24
- package/src/cli/index.js +60 -0
- package/src/cli/index.ts +1 -1
- package/src/cli/spec.js +42 -0
- package/src/components/agents/agent-avatar.tsx +45 -0
- package/src/components/agents/agent-card.tsx +19 -5
- package/src/components/agents/agent-chat-list.tsx +31 -24
- package/src/components/agents/agent-files-editor.tsx +185 -0
- package/src/components/agents/agent-list.tsx +84 -3
- package/src/components/agents/agent-sheet.tsx +147 -14
- package/src/components/agents/cron-job-form.tsx +137 -0
- package/src/components/agents/exec-config-panel.tsx +147 -0
- package/src/components/agents/inspector-panel.tsx +310 -0
- package/src/components/agents/openclaw-skills-panel.tsx +230 -0
- package/src/components/agents/permission-preset-selector.tsx +79 -0
- package/src/components/agents/personality-builder.tsx +111 -0
- package/src/components/agents/sandbox-env-panel.tsx +72 -0
- package/src/components/agents/skill-install-dialog.tsx +102 -0
- package/src/components/agents/trash-list.tsx +109 -0
- package/src/components/chat/chat-area.tsx +41 -6
- package/src/components/chat/chat-header.tsx +305 -29
- package/src/components/chat/chat-preview-panel.tsx +113 -0
- package/src/components/chat/exec-approval-card.tsx +89 -0
- package/src/components/chat/message-bubble.tsx +218 -36
- package/src/components/chat/message-list.tsx +135 -31
- package/src/components/chat/streaming-bubble.tsx +59 -10
- package/src/components/chat/suggestions-bar.tsx +74 -0
- package/src/components/chat/thinking-indicator.tsx +20 -6
- package/src/components/chat/tool-call-bubble.tsx +98 -19
- package/src/components/chat/tool-request-banner.tsx +20 -2
- package/src/components/chat/trace-block.tsx +103 -0
- package/src/components/chat/voice-overlay.tsx +80 -0
- package/src/components/connectors/connector-list.tsx +6 -2
- package/src/components/connectors/connector-sheet.tsx +31 -7
- package/src/components/layout/app-layout.tsx +47 -25
- package/src/components/projects/project-list.tsx +123 -0
- package/src/components/projects/project-sheet.tsx +135 -0
- package/src/components/schedules/schedule-list.tsx +3 -1
- package/src/components/sessions/new-session-sheet.tsx +6 -6
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/sessions/session-list.tsx +7 -7
- package/src/components/settings/gateway-connection-panel.tsx +278 -0
- package/src/components/shared/avatar.tsx +13 -2
- package/src/components/shared/connector-platform-icon.tsx +4 -0
- package/src/components/shared/settings/section-heartbeat.tsx +1 -1
- package/src/components/shared/settings/section-orchestrator.tsx +1 -2
- package/src/components/shared/settings/section-web-search.tsx +56 -0
- package/src/components/shared/settings/settings-page.tsx +74 -0
- package/src/components/skills/skill-list.tsx +2 -1
- package/src/components/tasks/task-board.tsx +1 -1
- package/src/components/tasks/task-list.tsx +5 -2
- package/src/components/tasks/task-sheet.tsx +12 -12
- package/src/hooks/use-continuous-speech.ts +181 -0
- package/src/hooks/use-openclaw-gateway.ts +63 -0
- package/src/hooks/use-view-router.ts +52 -0
- package/src/hooks/use-voice-conversation.ts +80 -0
- package/src/lib/id.ts +6 -0
- package/src/lib/notification-sounds.ts +58 -0
- package/src/lib/personality-parser.ts +97 -0
- package/src/lib/projects.ts +13 -0
- package/src/lib/provider-sets.ts +5 -0
- package/src/lib/providers/anthropic.ts +14 -1
- package/src/lib/providers/index.ts +6 -0
- package/src/lib/providers/ollama.ts +9 -1
- package/src/lib/providers/openai.ts +9 -1
- package/src/lib/providers/openclaw.ts +28 -2
- package/src/lib/runtime-loop.ts +2 -2
- package/src/lib/server/api-routes.test.ts +5 -6
- package/src/lib/server/build-llm.ts +17 -4
- package/src/lib/server/chat-execution.ts +82 -6
- package/src/lib/server/collection-helpers.ts +54 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +217 -0
- package/src/lib/server/connectors/bluebubbles.ts +360 -0
- package/src/lib/server/connectors/connector-routing.test.ts +1 -1
- package/src/lib/server/connectors/googlechat.ts +51 -8
- package/src/lib/server/connectors/manager.ts +424 -13
- package/src/lib/server/connectors/media.ts +2 -2
- package/src/lib/server/connectors/openclaw.ts +65 -0
- package/src/lib/server/connectors/pairing.test.ts +99 -0
- package/src/lib/server/connectors/pairing.ts +256 -0
- package/src/lib/server/connectors/signal.ts +1 -0
- package/src/lib/server/connectors/teams.ts +5 -5
- package/src/lib/server/connectors/types.ts +10 -0
- package/src/lib/server/daemon-state.ts +11 -0
- package/src/lib/server/execution-log.ts +3 -3
- package/src/lib/server/heartbeat-service.ts +1 -1
- package/src/lib/server/knowledge-db.test.ts +2 -33
- package/src/lib/server/main-agent-loop.ts +8 -9
- package/src/lib/server/main-session.ts +21 -0
- package/src/lib/server/memory-db.ts +6 -6
- package/src/lib/server/openclaw-approvals.ts +105 -0
- package/src/lib/server/openclaw-config-sync.ts +107 -0
- package/src/lib/server/openclaw-exec-config.ts +52 -0
- package/src/lib/server/openclaw-gateway.ts +291 -0
- package/src/lib/server/openclaw-history-merge.ts +36 -0
- package/src/lib/server/openclaw-models.ts +56 -0
- package/src/lib/server/openclaw-permission-presets.ts +64 -0
- package/src/lib/server/openclaw-sync.ts +497 -0
- package/src/lib/server/orchestrator-lg.ts +30 -9
- package/src/lib/server/orchestrator.ts +4 -4
- package/src/lib/server/process-manager.ts +2 -2
- package/src/lib/server/queue.ts +24 -11
- package/src/lib/server/scheduler.ts +2 -2
- package/src/lib/server/session-mailbox.ts +2 -2
- package/src/lib/server/session-run-manager.ts +2 -2
- package/src/lib/server/session-tools/connector.ts +53 -6
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/delegate.ts +22 -6
- package/src/lib/server/session-tools/file.ts +192 -19
- package/src/lib/server/session-tools/index.ts +4 -2
- package/src/lib/server/session-tools/memory.ts +2 -2
- package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
- package/src/lib/server/session-tools/sandbox.ts +33 -0
- package/src/lib/server/session-tools/search-providers.ts +277 -0
- package/src/lib/server/session-tools/session-info.ts +2 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +2 -2
- package/src/lib/server/session-tools/shell.ts +1 -1
- package/src/lib/server/session-tools/web.ts +53 -72
- package/src/lib/server/storage.ts +74 -11
- package/src/lib/server/stream-agent-chat.ts +53 -4
- package/src/lib/server/suggestions.ts +20 -0
- package/src/lib/server/task-result.test.ts +44 -0
- package/src/lib/server/task-result.ts +14 -0
- package/src/lib/server/ws-hub.ts +14 -0
- package/src/lib/tool-definitions.ts +5 -3
- package/src/lib/tts-stream.ts +130 -0
- package/src/lib/view-routes.ts +28 -0
- package/src/proxy.ts +3 -0
- package/src/stores/use-app-store.ts +80 -1
- package/src/stores/use-approval-store.ts +78 -0
- package/src/stores/use-chat-store.ts +162 -6
- package/src/types/index.ts +154 -3
- package/tsconfig.json +13 -4
|
@@ -5,29 +5,9 @@ import path from 'path'
|
|
|
5
5
|
import * as cheerio from 'cheerio'
|
|
6
6
|
import { UPLOAD_DIR } from '../storage'
|
|
7
7
|
import type { ToolBuildContext } from './context'
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// DuckDuckGo redirect-URL decoder
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
function decodeDuckDuckGoUrl(rawUrl: string): string {
|
|
15
|
-
if (!rawUrl) return rawUrl
|
|
16
|
-
try {
|
|
17
|
-
const url = rawUrl.startsWith('http')
|
|
18
|
-
? new URL(rawUrl)
|
|
19
|
-
: new URL(rawUrl, 'https://duckduckgo.com')
|
|
20
|
-
const uddg = url.searchParams.get('uddg')
|
|
21
|
-
if (uddg) return decodeURIComponent(uddg)
|
|
22
|
-
return url.toString()
|
|
23
|
-
} catch {
|
|
24
|
-
const fromQuery = rawUrl.match(/[?&]uddg=([^&]+)/)?.[1]
|
|
25
|
-
if (fromQuery) {
|
|
26
|
-
try { return decodeURIComponent(fromQuery) } catch { /* noop */ }
|
|
27
|
-
}
|
|
28
|
-
return rawUrl
|
|
29
|
-
}
|
|
30
|
-
}
|
|
8
|
+
import { spawnSync } from 'child_process'
|
|
9
|
+
import { safePath, truncate, MAX_OUTPUT, findBinaryOnPath } from './context'
|
|
10
|
+
import { getSearchProvider } from './search-providers'
|
|
31
11
|
|
|
32
12
|
// ---------------------------------------------------------------------------
|
|
33
13
|
// Global registry of active browser instances for cleanup sweeps
|
|
@@ -86,58 +66,20 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
86
66
|
async ({ query, maxResults }) => {
|
|
87
67
|
try {
|
|
88
68
|
const limit = Math.min(maxResults || 5, 10)
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
})
|
|
94
|
-
if (!res.ok) {
|
|
95
|
-
return `Error searching web: HTTP ${res.status} ${res.statusText}`
|
|
96
|
-
}
|
|
97
|
-
const html = await res.text()
|
|
98
|
-
const $ = cheerio.load(html)
|
|
99
|
-
const results: { title: string; url: string; snippet: string }[] = []
|
|
100
|
-
|
|
101
|
-
// Primary parser: DuckDuckGo result cards
|
|
102
|
-
$('.result').each((_i, el) => {
|
|
103
|
-
if (results.length >= limit) return false
|
|
104
|
-
const link = $(el).find('a.result__a').first()
|
|
105
|
-
const rawHref = link.attr('href') || ''
|
|
106
|
-
const title = link.text().replace(/\s+/g, ' ').trim()
|
|
107
|
-
if (!rawHref || !title) return
|
|
108
|
-
const snippet = $(el).find('.result__snippet').first().text().replace(/\s+/g, ' ').trim()
|
|
109
|
-
results.push({
|
|
110
|
-
title,
|
|
111
|
-
url: decodeDuckDuckGoUrl(rawHref),
|
|
112
|
-
snippet,
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
// Fallback parser: any result__a anchors
|
|
117
|
-
if (results.length === 0) {
|
|
118
|
-
$('a.result__a').each((_i, el) => {
|
|
119
|
-
if (results.length >= limit) return false
|
|
120
|
-
const rawHref = $(el).attr('href') || ''
|
|
121
|
-
const title = $(el).text().replace(/\s+/g, ' ').trim()
|
|
122
|
-
if (!rawHref || !title) return
|
|
123
|
-
results.push({
|
|
124
|
-
title,
|
|
125
|
-
url: decodeDuckDuckGoUrl(rawHref),
|
|
126
|
-
snippet: '',
|
|
127
|
-
})
|
|
128
|
-
})
|
|
129
|
-
}
|
|
130
|
-
|
|
69
|
+
const { loadSettings } = await import('../storage')
|
|
70
|
+
const settings = loadSettings()
|
|
71
|
+
const provider = await getSearchProvider(settings)
|
|
72
|
+
const results = await provider.search(query, limit)
|
|
131
73
|
return results.length > 0
|
|
132
74
|
? JSON.stringify(results, null, 2)
|
|
133
75
|
: 'No results found.'
|
|
134
|
-
} catch (err:
|
|
135
|
-
return `Error searching web: ${err.message}`
|
|
76
|
+
} catch (err: unknown) {
|
|
77
|
+
return `Error searching web: ${err instanceof Error ? err.message : String(err)}`
|
|
136
78
|
}
|
|
137
79
|
},
|
|
138
80
|
{
|
|
139
81
|
name: 'web_search',
|
|
140
|
-
description: 'Search the web
|
|
82
|
+
description: 'Search the web. Returns an array of results with title, url, and snippet.',
|
|
141
83
|
schema: z.object({
|
|
142
84
|
query: z.string().describe('Search query'),
|
|
143
85
|
maxResults: z.number().optional().describe('Maximum results to return (default 5, max 10)'),
|
|
@@ -169,8 +111,8 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
169
111
|
.replace(/\s+/g, ' ')
|
|
170
112
|
.trim()
|
|
171
113
|
return truncate(text, MAX_OUTPUT)
|
|
172
|
-
} catch (err:
|
|
173
|
-
return `Error fetching URL: ${err.message}`
|
|
114
|
+
} catch (err: unknown) {
|
|
115
|
+
return `Error fetching URL: ${err instanceof Error ? err.message : String(err)}`
|
|
174
116
|
}
|
|
175
117
|
},
|
|
176
118
|
{
|
|
@@ -374,8 +316,8 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
374
316
|
? params.saveTo.trim()
|
|
375
317
|
: undefined
|
|
376
318
|
return await callMcpTool(mcpTool, args, { saveTo })
|
|
377
|
-
} catch (err:
|
|
378
|
-
return `Error: ${err.message}`
|
|
319
|
+
} catch (err: unknown) {
|
|
320
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
379
321
|
}
|
|
380
322
|
},
|
|
381
323
|
{
|
|
@@ -404,5 +346,44 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
404
346
|
)
|
|
405
347
|
}
|
|
406
348
|
|
|
349
|
+
// ---- openclaw_browser (CLI passthrough) -----------------------------------
|
|
350
|
+
|
|
351
|
+
if (bctx.hasTool('browser') || bctx.hasTool('openclaw_browser')) {
|
|
352
|
+
const openclawPath = findBinaryOnPath('openclaw') || findBinaryOnPath('clawdbot')
|
|
353
|
+
if (openclawPath) {
|
|
354
|
+
tools.push(
|
|
355
|
+
tool(
|
|
356
|
+
async ({ command, args: cmdArgs }) => {
|
|
357
|
+
try {
|
|
358
|
+
const spawnArgs = ['browser', command, '--json']
|
|
359
|
+
if (cmdArgs) spawnArgs.push(...cmdArgs.split(/\s+/).filter(Boolean))
|
|
360
|
+
const result = spawnSync(openclawPath, spawnArgs, {
|
|
361
|
+
encoding: 'utf-8',
|
|
362
|
+
timeout: 60_000,
|
|
363
|
+
maxBuffer: MAX_OUTPUT,
|
|
364
|
+
})
|
|
365
|
+
const stdout = (result.stdout || '').trim()
|
|
366
|
+
const stderr = (result.stderr || '').trim()
|
|
367
|
+
if (result.status !== 0) {
|
|
368
|
+
return `Error (exit ${result.status}): ${stderr || stdout || 'unknown error'}`
|
|
369
|
+
}
|
|
370
|
+
return truncate(stdout || '(no output)', MAX_OUTPUT)
|
|
371
|
+
} catch (err: unknown) {
|
|
372
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: 'openclaw_browser',
|
|
377
|
+
description: 'Control a browser through the OpenClaw CLI. Requires openclaw/clawdbot CLI on PATH. Passes through to `openclaw browser <command> --json`.',
|
|
378
|
+
schema: z.object({
|
|
379
|
+
command: z.string().describe('Browser command (navigate, screenshot, click, type, evaluate, etc.)'),
|
|
380
|
+
args: z.string().optional().describe('Additional arguments as a space-separated string'),
|
|
381
|
+
}),
|
|
382
|
+
},
|
|
383
|
+
),
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
407
388
|
return tools
|
|
408
389
|
}
|
|
@@ -5,6 +5,8 @@ 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 type { Message } from '@/types'
|
|
9
|
+
import { ensureMainSessionFlag } from './main-session'
|
|
8
10
|
export const UPLOAD_DIR = path.join(DATA_DIR, 'uploads')
|
|
9
11
|
|
|
10
12
|
// Ensure directories exist
|
|
@@ -13,9 +15,12 @@ for (const dir of [DATA_DIR, UPLOAD_DIR, WORKSPACE_DIR]) {
|
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
// --- SQLite Database ---
|
|
16
|
-
const
|
|
18
|
+
const IS_BUILD_BOOTSTRAP = process.env.SWARMCLAW_BUILD_MODE === '1'
|
|
19
|
+
const DB_PATH = IS_BUILD_BOOTSTRAP ? ':memory:' : path.join(DATA_DIR, 'swarmclaw.db')
|
|
17
20
|
const db = new Database(DB_PATH)
|
|
18
|
-
|
|
21
|
+
if (!IS_BUILD_BOOTSTRAP) {
|
|
22
|
+
db.pragma('journal_mode = WAL')
|
|
23
|
+
}
|
|
19
24
|
db.pragma('foreign_keys = ON')
|
|
20
25
|
|
|
21
26
|
const collectionCacheKey = '__swarmclaw_storage_collection_cache__' as const
|
|
@@ -43,6 +48,7 @@ const COLLECTIONS = [
|
|
|
43
48
|
'model_overrides',
|
|
44
49
|
'mcp_servers',
|
|
45
50
|
'webhook_logs',
|
|
51
|
+
'projects',
|
|
46
52
|
] as const
|
|
47
53
|
|
|
48
54
|
for (const table of COLLECTIONS) {
|
|
@@ -242,10 +248,12 @@ function migrateFromJson() {
|
|
|
242
248
|
console.log('[storage] Migration complete. JSON files preserved as backup.')
|
|
243
249
|
}
|
|
244
250
|
|
|
245
|
-
|
|
251
|
+
if (!IS_BUILD_BOOTSTRAP) {
|
|
252
|
+
migrateFromJson()
|
|
253
|
+
}
|
|
246
254
|
|
|
247
255
|
// Seed default agent if agents table is empty
|
|
248
|
-
{
|
|
256
|
+
if (!IS_BUILD_BOOTSTRAP) {
|
|
249
257
|
const defaultStarterTools = [
|
|
250
258
|
'memory',
|
|
251
259
|
'files',
|
|
@@ -304,6 +312,7 @@ Be concise and helpful. When users ask how to do something, guide them to the sp
|
|
|
304
312
|
soul: '',
|
|
305
313
|
isOrchestrator: false,
|
|
306
314
|
tools: defaultStarterTools,
|
|
315
|
+
heartbeatEnabled: true,
|
|
307
316
|
platformAssignScope: 'all',
|
|
308
317
|
skillIds: [],
|
|
309
318
|
subAgentIds: [],
|
|
@@ -340,10 +349,12 @@ function loadEnv() {
|
|
|
340
349
|
})
|
|
341
350
|
}
|
|
342
351
|
}
|
|
343
|
-
|
|
352
|
+
if (!IS_BUILD_BOOTSTRAP) {
|
|
353
|
+
loadEnv()
|
|
354
|
+
}
|
|
344
355
|
|
|
345
356
|
// Auto-generate CREDENTIAL_SECRET if missing
|
|
346
|
-
if (!process.env.CREDENTIAL_SECRET) {
|
|
357
|
+
if (!IS_BUILD_BOOTSTRAP && !process.env.CREDENTIAL_SECRET) {
|
|
347
358
|
const secret = crypto.randomBytes(32).toString('hex')
|
|
348
359
|
const envPath = path.join(process.cwd(), '.env.local')
|
|
349
360
|
fs.appendFileSync(envPath, `\nCREDENTIAL_SECRET=${secret}\n`)
|
|
@@ -353,7 +364,7 @@ if (!process.env.CREDENTIAL_SECRET) {
|
|
|
353
364
|
|
|
354
365
|
// Auto-generate ACCESS_KEY if missing (used for simple auth)
|
|
355
366
|
const SETUP_FLAG = path.join(DATA_DIR, '.setup_pending')
|
|
356
|
-
if (!process.env.ACCESS_KEY) {
|
|
367
|
+
if (!IS_BUILD_BOOTSTRAP && !process.env.ACCESS_KEY) {
|
|
357
368
|
const key = crypto.randomBytes(16).toString('hex')
|
|
358
369
|
const envPath = path.join(process.cwd(), '.env.local')
|
|
359
370
|
fs.appendFileSync(envPath, `\nACCESS_KEY=${key}\n`)
|
|
@@ -383,7 +394,31 @@ export function markSetupComplete(): void {
|
|
|
383
394
|
|
|
384
395
|
// --- Sessions ---
|
|
385
396
|
export function loadSessions(): Record<string, any> {
|
|
386
|
-
|
|
397
|
+
const sessions = loadCollection('sessions')
|
|
398
|
+
const agents = loadCollection('agents')
|
|
399
|
+
let changed = false
|
|
400
|
+
|
|
401
|
+
for (const [id, session] of Object.entries(sessions)) {
|
|
402
|
+
if (!session || typeof session !== 'object') continue
|
|
403
|
+
|
|
404
|
+
if (typeof session.id !== 'string' || !session.id.trim()) {
|
|
405
|
+
session.id = id
|
|
406
|
+
changed = true
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const beforeMainFlag = session.mainSession === true
|
|
410
|
+
ensureMainSessionFlag(session)
|
|
411
|
+
if (!beforeMainFlag && session.mainSession === true) changed = true
|
|
412
|
+
|
|
413
|
+
const agentId = typeof session.agentId === 'string' ? session.agentId.trim() : ''
|
|
414
|
+
if (agentId && !Object.prototype.hasOwnProperty.call(agents, agentId)) {
|
|
415
|
+
session.agentId = null
|
|
416
|
+
changed = true
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (changed) saveCollection('sessions', sessions)
|
|
421
|
+
return sessions
|
|
387
422
|
}
|
|
388
423
|
|
|
389
424
|
export function saveSessions(s: Record<string, any>) {
|
|
@@ -451,8 +486,23 @@ export function decryptKey(encrypted: string): string {
|
|
|
451
486
|
}
|
|
452
487
|
|
|
453
488
|
// --- Agents ---
|
|
454
|
-
export function loadAgents(): Record<string, any> {
|
|
455
|
-
|
|
489
|
+
export function loadAgents(opts?: { includeTrashed?: boolean }): Record<string, any> {
|
|
490
|
+
const all = loadCollection('agents')
|
|
491
|
+
if (opts?.includeTrashed) return all
|
|
492
|
+
const result: Record<string, any> = {}
|
|
493
|
+
for (const [id, agent] of Object.entries(all)) {
|
|
494
|
+
if (!agent.trashedAt) result[id] = agent
|
|
495
|
+
}
|
|
496
|
+
return result
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export function loadTrashedAgents(): Record<string, any> {
|
|
500
|
+
const all = loadCollection('agents')
|
|
501
|
+
const result: Record<string, any> = {}
|
|
502
|
+
for (const [id, agent] of Object.entries(all)) {
|
|
503
|
+
if (agent.trashedAt) result[id] = agent
|
|
504
|
+
}
|
|
505
|
+
return result
|
|
456
506
|
}
|
|
457
507
|
|
|
458
508
|
export function saveAgents(p: Record<string, any>) {
|
|
@@ -526,12 +576,14 @@ export async function getSecret(key: string): Promise<{
|
|
|
526
576
|
if (!needle) return null
|
|
527
577
|
|
|
528
578
|
const secrets = loadSecrets()
|
|
579
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
529
580
|
const matches = Object.values(secrets).find((secret: any) => {
|
|
530
581
|
if (!secret || typeof secret !== 'object') return false
|
|
531
582
|
const id = typeof secret.id === 'string' ? secret.id.toLowerCase() : ''
|
|
532
583
|
const name = typeof secret.name === 'string' ? secret.name.toLowerCase() : ''
|
|
533
584
|
const service = typeof secret.service === 'string' ? secret.service.toLowerCase() : ''
|
|
534
585
|
return id === needle || name === needle || service === needle
|
|
586
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
535
587
|
}) as any | undefined
|
|
536
588
|
|
|
537
589
|
if (!matches) return null
|
|
@@ -576,6 +628,17 @@ export function saveModelOverrides(m: Record<string, string[]>) {
|
|
|
576
628
|
saveCollection('model_overrides', m)
|
|
577
629
|
}
|
|
578
630
|
|
|
631
|
+
// --- Projects ---
|
|
632
|
+
export function loadProjects(): Record<string, any> {
|
|
633
|
+
return loadCollection('projects')
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
export function saveProjects(s: Record<string, any>) {
|
|
637
|
+
saveCollection('projects', s)
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export function deleteProject(id: string) { deleteCollectionItem('projects', id) }
|
|
641
|
+
|
|
579
642
|
// --- Skills ---
|
|
580
643
|
export function loadSkills(): Record<string, any> {
|
|
581
644
|
return loadCollection('skills')
|
|
@@ -678,7 +741,7 @@ export function appendWebhookLog(id: string, entry: any) {
|
|
|
678
741
|
upsertCollectionItem('webhook_logs', id, entry)
|
|
679
742
|
}
|
|
680
743
|
|
|
681
|
-
export function getSessionMessages(sessionId: string):
|
|
744
|
+
export function getSessionMessages(sessionId: string): Message[] {
|
|
682
745
|
const stmt = db.prepare('SELECT data FROM sessions WHERE id = ?')
|
|
683
746
|
const row = stmt.get(sessionId) as { data: string } | undefined
|
|
684
747
|
if (!row) return []
|
|
@@ -10,6 +10,7 @@ import { loadRuntimeSettings, getAgentLoopRecursionLimit } from './runtime-setti
|
|
|
10
10
|
import { getMemoryDb } from './memory-db'
|
|
11
11
|
import { logExecution } from './execution-log'
|
|
12
12
|
import type { Session, Message, UsageRecord } from '@/types'
|
|
13
|
+
import { extractSuggestions } from './suggestions'
|
|
13
14
|
|
|
14
15
|
interface StreamAgentChatOpts {
|
|
15
16
|
session: Session
|
|
@@ -26,7 +27,7 @@ interface StreamAgentChatOpts {
|
|
|
26
27
|
|
|
27
28
|
function buildToolCapabilityLines(enabledTools: string[]): string[] {
|
|
28
29
|
const lines: string[] = []
|
|
29
|
-
if (enabledTools.includes('shell')) lines.push('- Shell execution is available (`execute_command`). Use it for
|
|
30
|
+
if (enabledTools.includes('shell')) lines.push('- Shell execution is available (`execute_command`). Use it for running servers, installing deps, running scripts, git commands, build/test steps, and any single or chained shell commands. Supports background mode for long-running processes like dev servers.')
|
|
30
31
|
if (enabledTools.includes('process')) lines.push('- Process control is available (`process_tool`) for long-running commands (poll/log/write/kill).')
|
|
31
32
|
if (enabledTools.includes('files') || enabledTools.includes('copy_file') || enabledTools.includes('move_file') || enabledTools.includes('delete_file')) {
|
|
32
33
|
lines.push('- File operations are available (`read_file`, `write_file`, `list_files`, `copy_file`, `move_file`, `send_file`). `delete_file` is destructive and may be disabled unless explicitly enabled.')
|
|
@@ -111,10 +112,10 @@ function buildAgenticExecutionPolicy(opts: {
|
|
|
111
112
|
? 'When coordinating platform work, inspect existing sessions and avoid duplicating active efforts.'
|
|
112
113
|
: '',
|
|
113
114
|
hasDelegationTool
|
|
114
|
-
? `
|
|
115
|
+
? 'CRITICAL — tool selection: ALWAYS use `execute_command` for running servers, dev servers, HTTP servers, installing dependencies, running scripts, git operations, process management, starting/stopping services, or any command the user wants to "run". Delegation tools (Claude/Codex/OpenCode) CANNOT keep a server running — their session ends and the process dies. `execute_command` with background=true is the ONLY way to run persistent processes.'
|
|
115
116
|
: '',
|
|
116
117
|
hasDelegationTool
|
|
117
|
-
?
|
|
118
|
+
? `Only use CLI delegation (${delegationOrder.join(' -> ')}) for tasks that need deep code understanding across multiple files: large refactors, complex debugging, multi-file code generation, or test suites. Never delegate when the user says "run", "start", "serve", "execute", or "test it locally".`
|
|
118
119
|
: '',
|
|
119
120
|
opts.enabledTools.includes('memory')
|
|
120
121
|
? 'Memory is active and required for long-horizon work: before major tasks, run memory_tool search/list for relevant prior work; after each meaningful step, store concise reusable notes (what changed, where it lives, constraints, next step). Treat memory as shared context plus your own agent notes, not as user-owned personal profile data.'
|
|
@@ -168,11 +169,20 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
168
169
|
|
|
169
170
|
// fallbackCredentialIds is intentionally accepted for compatibility with caller signatures.
|
|
170
171
|
void fallbackCredentialIds
|
|
172
|
+
|
|
173
|
+
// Resolve agent's thinking level for provider-native params
|
|
174
|
+
let agentThinkingLevel: 'minimal' | 'low' | 'medium' | 'high' | undefined
|
|
175
|
+
if (session.agentId) {
|
|
176
|
+
const agentsForThinking = loadAgents()
|
|
177
|
+
agentThinkingLevel = agentsForThinking[session.agentId]?.thinkingLevel
|
|
178
|
+
}
|
|
179
|
+
|
|
171
180
|
const llm = buildChatModel({
|
|
172
181
|
provider: session.provider,
|
|
173
182
|
model: session.model,
|
|
174
183
|
apiKey,
|
|
175
184
|
apiEndpoint: session.apiEndpoint,
|
|
185
|
+
thinkingLevel: agentThinkingLevel,
|
|
176
186
|
})
|
|
177
187
|
|
|
178
188
|
// Build stateModifier
|
|
@@ -228,6 +238,17 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
228
238
|
stateModifierParts.push('You are a capable AI assistant with tool access. Be execution-oriented and outcome-focused.')
|
|
229
239
|
}
|
|
230
240
|
|
|
241
|
+
// Thinking level guidance (applies to all providers via system prompt)
|
|
242
|
+
if (agentThinkingLevel) {
|
|
243
|
+
const thinkingGuidance: Record<string, string> = {
|
|
244
|
+
minimal: 'Be direct and concise. Skip extended analysis.',
|
|
245
|
+
low: 'Keep reasoning brief. Focus on key conclusions.',
|
|
246
|
+
medium: 'Provide moderate depth of analysis and reasoning.',
|
|
247
|
+
high: 'Think deeply and thoroughly. Show detailed reasoning.',
|
|
248
|
+
}
|
|
249
|
+
stateModifierParts.push(`## Reasoning Depth\n${thinkingGuidance[agentThinkingLevel]}`)
|
|
250
|
+
}
|
|
251
|
+
|
|
231
252
|
if ((session.tools || []).includes('memory') && session.agentId) {
|
|
232
253
|
try {
|
|
233
254
|
const memDb = getMemoryDb()
|
|
@@ -326,6 +347,16 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
326
347
|
}
|
|
327
348
|
}
|
|
328
349
|
|
|
350
|
+
stateModifierParts.push(
|
|
351
|
+
[
|
|
352
|
+
'## Follow-up Suggestions',
|
|
353
|
+
'At the end of every response, include a <suggestions> block with exactly 3 short',
|
|
354
|
+
'follow-up prompts the user might want to send next, as a JSON array. Keep each under 60 chars.',
|
|
355
|
+
'Make them contextual to what you just said. Example:',
|
|
356
|
+
'<suggestions>["Set up a Discord connector", "Create a research agent", "Show the task board"]</suggestions>',
|
|
357
|
+
].join('\n'),
|
|
358
|
+
)
|
|
359
|
+
|
|
329
360
|
stateModifierParts.push(
|
|
330
361
|
buildAgenticExecutionPolicy({
|
|
331
362
|
enabledTools: session.tools || [],
|
|
@@ -586,6 +617,13 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
586
617
|
if (signal) signal.removeEventListener('abort', abortFromSignal)
|
|
587
618
|
}
|
|
588
619
|
|
|
620
|
+
// Extract LLM-generated suggestions from the response and strip the tag
|
|
621
|
+
const extracted = extractSuggestions(fullText)
|
|
622
|
+
fullText = extracted.clean
|
|
623
|
+
if (extracted.suggestions) {
|
|
624
|
+
write(`data: ${JSON.stringify({ t: 'md', text: JSON.stringify({ suggestions: extracted.suggestions }) })}\n\n`)
|
|
625
|
+
}
|
|
626
|
+
|
|
589
627
|
// Track cost
|
|
590
628
|
const totalTokens = totalInputTokens + totalOutputTokens
|
|
591
629
|
if (totalTokens > 0) {
|
|
@@ -612,14 +650,25 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
|
|
|
612
650
|
// Plugin hooks: afterAgentComplete
|
|
613
651
|
await pluginMgr.runHook('afterAgentComplete', { session, response: fullText })
|
|
614
652
|
|
|
653
|
+
// OpenClaw auto-sync: push memory if enabled
|
|
654
|
+
try {
|
|
655
|
+
const { loadSyncConfig, pushMemoryToOpenClaw } = await import('./openclaw-sync')
|
|
656
|
+
const syncConfig = loadSyncConfig()
|
|
657
|
+
if (syncConfig.autoSyncMemory) {
|
|
658
|
+
pushMemoryToOpenClaw(session.agentId || undefined)
|
|
659
|
+
}
|
|
660
|
+
} catch { /* OpenClaw sync not available — ignore */ }
|
|
661
|
+
|
|
615
662
|
// Clean up browser and other session resources
|
|
616
663
|
await cleanup()
|
|
617
664
|
|
|
618
665
|
// If tools were called, finalResponse is the text from the last LLM turn only.
|
|
619
666
|
// Fall back to fullText if the last segment is empty (e.g. agent ended on a tool call
|
|
620
667
|
// with no summary text).
|
|
668
|
+
// Strip suggestions tag from lastSegment too (connector delivery)
|
|
669
|
+
const cleanLastSegment = extractSuggestions(lastSegment).clean
|
|
621
670
|
const finalResponse = hasToolCalls
|
|
622
|
-
? (
|
|
671
|
+
? (cleanLastSegment.trim() || fullText)
|
|
623
672
|
: fullText
|
|
624
673
|
|
|
625
674
|
return { fullText, finalResponse }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
const suggestionsSchema = z.array(z.string().min(1).max(80)).length(3)
|
|
4
|
+
|
|
5
|
+
const SUGGESTIONS_RE = /<suggestions>\s*([\s\S]*?)\s*<\/suggestions>\s*$/
|
|
6
|
+
|
|
7
|
+
export function extractSuggestions(text: string): { clean: string; suggestions: string[] | null } {
|
|
8
|
+
const match = text.match(SUGGESTIONS_RE)
|
|
9
|
+
if (!match) return { clean: text, suggestions: null }
|
|
10
|
+
|
|
11
|
+
const clean = text.slice(0, match.index).trimEnd()
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(match[1])
|
|
15
|
+
const validated = suggestionsSchema.parse(parsed)
|
|
16
|
+
return { clean, suggestions: validated }
|
|
17
|
+
} catch {
|
|
18
|
+
return { clean, suggestions: null }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { extractTaskResult } from './task-result'
|
|
4
|
+
|
|
5
|
+
describe('extractTaskResult', () => {
|
|
6
|
+
it('limits artifact extraction to messages from the current run window', () => {
|
|
7
|
+
const session = {
|
|
8
|
+
messages: [
|
|
9
|
+
{
|
|
10
|
+
role: 'assistant',
|
|
11
|
+
time: 1_000,
|
|
12
|
+
text: 'old run artifact: /api/uploads/wiki-old.png',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
role: 'assistant',
|
|
16
|
+
time: 2_000,
|
|
17
|
+
text: 'new run artifact: /api/uploads/wiki-new.png',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = extractTaskResult(session, 'done', { sinceTime: 1_500 })
|
|
23
|
+
assert.deepEqual(result.artifacts.map((a) => a.url), ['/api/uploads/wiki-new.png'])
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('excludes messages without timestamps when sinceTime is provided', () => {
|
|
27
|
+
const session = {
|
|
28
|
+
messages: [
|
|
29
|
+
{
|
|
30
|
+
role: 'assistant',
|
|
31
|
+
text: 'undated artifact: /api/uploads/undated.png',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
role: 'assistant',
|
|
35
|
+
time: 5_000,
|
|
36
|
+
text: 'dated artifact: /api/uploads/dated.png',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const result = extractTaskResult(session, 'done', { sinceTime: 4_000 })
|
|
42
|
+
assert.deepEqual(result.artifacts.map((a) => a.url), ['/api/uploads/dated.png'])
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -43,6 +43,7 @@ function classifyArtifact(filename: string): Artifact['type'] {
|
|
|
43
43
|
interface MessageLike {
|
|
44
44
|
role?: string
|
|
45
45
|
text?: string
|
|
46
|
+
time?: number
|
|
46
47
|
imageUrl?: string
|
|
47
48
|
imagePath?: string
|
|
48
49
|
toolEvents?: Array<{ name?: string; output?: string }>
|
|
@@ -52,6 +53,10 @@ interface SessionLike {
|
|
|
52
53
|
messages?: MessageLike[]
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
interface ExtractTaskResultOptions {
|
|
57
|
+
sinceTime?: number | null
|
|
58
|
+
}
|
|
59
|
+
|
|
55
60
|
// ---------------------------------------------------------------------------
|
|
56
61
|
// Core extraction
|
|
57
62
|
// ---------------------------------------------------------------------------
|
|
@@ -64,9 +69,13 @@ interface SessionLike {
|
|
|
64
69
|
export function extractTaskResult(
|
|
65
70
|
session: SessionLike | null | undefined,
|
|
66
71
|
rawResultText: string | null | undefined,
|
|
72
|
+
options?: ExtractTaskResultOptions,
|
|
67
73
|
): TaskResult {
|
|
68
74
|
const seen = new Set<string>()
|
|
69
75
|
const artifacts: Artifact[] = []
|
|
76
|
+
const sinceTime = typeof options?.sinceTime === 'number' && Number.isFinite(options.sinceTime)
|
|
77
|
+
? options.sinceTime
|
|
78
|
+
: null
|
|
70
79
|
|
|
71
80
|
function addUrl(raw: string) {
|
|
72
81
|
const url = stripSandbox(raw)
|
|
@@ -79,6 +88,11 @@ export function extractTaskResult(
|
|
|
79
88
|
// Walk session messages to collect all artifact URLs
|
|
80
89
|
if (Array.isArray(session?.messages)) {
|
|
81
90
|
for (const msg of session.messages) {
|
|
91
|
+
if (sinceTime !== null) {
|
|
92
|
+
const msgTime = typeof msg.time === 'number' && Number.isFinite(msg.time) ? msg.time : null
|
|
93
|
+
if (msgTime === null || msgTime < sinceTime) continue
|
|
94
|
+
}
|
|
95
|
+
|
|
82
96
|
// Explicit image fields
|
|
83
97
|
if (msg.imageUrl) addUrl(msg.imageUrl)
|
|
84
98
|
if (msg.imagePath) {
|
package/src/lib/server/ws-hub.ts
CHANGED
|
@@ -83,3 +83,17 @@ export function notify(topic: string, action = 'update', id?: string) {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
/** Send an event with a data payload to subscribed browser clients. */
|
|
88
|
+
export function notifyWithPayload(topic: string, data: unknown) {
|
|
89
|
+
const hub = getHub()
|
|
90
|
+
if (!hub) return
|
|
91
|
+
|
|
92
|
+
const payload = JSON.stringify({ topic, action: 'event', data })
|
|
93
|
+
|
|
94
|
+
for (const client of hub.clients) {
|
|
95
|
+
if (client.topics.has(topic) && client.ws.readyState === WebSocket.OPEN) {
|
|
96
|
+
client.ws.send(payload)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -18,8 +18,10 @@ export const AVAILABLE_TOOLS: ToolDefinition[] = [
|
|
|
18
18
|
{ id: 'codex_cli', label: 'Codex CLI', description: 'Delegate complex tasks to OpenAI Codex CLI' },
|
|
19
19
|
{ id: 'opencode_cli', label: 'OpenCode CLI', description: 'Delegate complex tasks to OpenCode CLI' },
|
|
20
20
|
{ id: 'browser', label: 'Browser', description: 'Playwright — browse, scrape, interact with web pages' },
|
|
21
|
-
{ id: 'memory', label: 'Memory', description: 'Store and retrieve long-term memories across
|
|
21
|
+
{ id: 'memory', label: 'Memory', description: 'Store and retrieve long-term memories across conversations' },
|
|
22
22
|
{ id: 'sandbox', label: 'Sandbox', description: 'Run JS/TS/Python code in an isolated Deno sandbox' },
|
|
23
|
+
{ id: 'create_document', label: 'Create Document', description: 'Render markdown to PDF, HTML, or image' },
|
|
24
|
+
{ id: 'create_spreadsheet', label: 'Create Spreadsheet', description: 'Create Excel or CSV files from structured data' },
|
|
23
25
|
]
|
|
24
26
|
|
|
25
27
|
export const PLATFORM_TOOLS: ToolDefinition[] = [
|
|
@@ -28,9 +30,9 @@ export const PLATFORM_TOOLS: ToolDefinition[] = [
|
|
|
28
30
|
{ id: 'manage_schedules', label: 'Schedules', description: 'Create, edit, and delete schedules' },
|
|
29
31
|
{ id: 'manage_skills', label: 'Skills', description: 'Create, edit, and delete skills' },
|
|
30
32
|
{ id: 'manage_documents', label: 'Documents', description: 'Upload, search, and delete indexed documents' },
|
|
31
|
-
{ id: 'manage_webhooks', label: 'Webhooks', description: 'Register webhooks that trigger agent
|
|
33
|
+
{ id: 'manage_webhooks', label: 'Webhooks', description: 'Register webhooks that trigger agent workflows' },
|
|
32
34
|
{ id: 'manage_connectors', label: 'Connectors', description: 'Create, edit, and delete connectors' },
|
|
33
|
-
{ id: 'manage_sessions', label: '
|
|
35
|
+
{ id: 'manage_sessions', label: 'Chats', description: 'List chats, send messages, and spawn agent work' },
|
|
34
36
|
{ id: 'manage_secrets', label: 'Secrets', description: 'Store and retrieve encrypted service secrets' },
|
|
35
37
|
]
|
|
36
38
|
|