@swarmclawai/swarmclaw 0.7.3 → 0.7.5
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 +47 -40
- 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 +17 -0
- package/src/app/api/agents/[id]/thread/route.ts +4 -87
- package/src/app/api/agents/route.ts +23 -1
- package/src/app/api/auth/route.ts +1 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +16 -5
- 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]/route.ts +12 -0
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +7 -1
- 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/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/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +1 -1
- 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 +6 -10
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +2 -1
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/page.tsx +126 -15
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +34 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +20 -4
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-sheet.tsx +249 -7
- package/src/components/agents/inspector-panel.tsx +3 -2
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +41 -14
- package/src/components/chat/chat-card.tsx +2 -1
- package/src/components/chat/chat-header.tsx +8 -13
- package/src/components/chat/chat-list.tsx +58 -20
- package/src/components/chat/message-list.tsx +142 -18
- 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 +157 -86
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +2 -0
- 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/projects/project-detail.tsx +7 -2
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/section-heartbeat.tsx +11 -6
- package/src/components/shared/settings/section-orchestrator.tsx +3 -0
- package/src/components/shared/settings/settings-page.tsx +5 -3
- package/src/components/tasks/approvals-panel.tsx +7 -1
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -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-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/agent-thread-session.test.ts +85 -0
- package/src/lib/server/agent-thread-session.ts +123 -0
- package/src/lib/server/approvals-auto-approve.test.ts +59 -0
- package/src/lib/server/build-llm.test.ts +13 -5
- package/src/lib/server/chat-execution-tool-events.test.ts +87 -2
- package/src/lib/server/chat-execution.ts +159 -71
- package/src/lib/server/chatroom-helpers.test.ts +7 -0
- package/src/lib/server/chatroom-helpers.ts +99 -6
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/manager.ts +89 -61
- package/src/lib/server/connectors/slack.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -2
- package/src/lib/server/data-dir.test.ts +56 -0
- package/src/lib/server/data-dir.ts +15 -9
- 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 -8
- package/src/lib/server/heartbeat-wake.ts +6 -2
- package/src/lib/server/main-agent-loop.ts +13 -6
- package/src/lib/server/openclaw-exec-config.ts +4 -2
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/orchestrator-lg.ts +1 -2
- package/src/lib/server/orchestrator.ts +3 -2
- package/src/lib/server/plugins.test.ts +9 -1
- package/src/lib/server/plugins.ts +12 -2
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +1 -1
- 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/session-tools/autonomy-tools.test.ts +23 -0
- package/src/lib/server/session-tools/crud.ts +27 -3
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +18 -8
- package/src/lib/server/session-tools/file-normalize.test.ts +5 -0
- package/src/lib/server/session-tools/file.ts +8 -2
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/index.ts +31 -1
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/monitor.ts +14 -7
- 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.ts +1 -1
- package/src/lib/server/session-tools/plugin-creator.ts +9 -2
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/session-info.ts +22 -1
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +23 -0
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +3 -1
- package/src/lib/server/session-tools/web.ts +73 -30
- package/src/lib/server/storage.ts +29 -3
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +139 -4
- package/src/lib/server/structured-extract.ts +1 -1
- package/src/lib/server/task-mention.ts +0 -1
- package/src/lib/server/tool-aliases.ts +37 -6
- package/src/lib/server/tool-capability-policy.ts +1 -1
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.ts +55 -1
- package/src/stores/use-app-store.ts +43 -1
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +189 -6
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -13
|
@@ -14,22 +14,20 @@ function getDenoPath(): string | null {
|
|
|
14
14
|
return findBinaryOnPath('deno')
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function getNodePath(): string | null {
|
|
18
|
-
return findBinaryOnPath('node')
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function getTsxPath(): string | null {
|
|
22
|
-
return findBinaryOnPath('tsx')
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function getPythonPath(): string | null {
|
|
26
|
-
return findBinaryOnPath('python3') ?? findBinaryOnPath('python')
|
|
27
|
-
}
|
|
28
|
-
|
|
29
17
|
const EXT_MAP: Record<string, string> = {
|
|
30
18
|
javascript: 'js',
|
|
31
19
|
typescript: 'ts',
|
|
32
|
-
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sandboxUnavailableError(reason: string): string {
|
|
23
|
+
return JSON.stringify({
|
|
24
|
+
error: reason,
|
|
25
|
+
guidance: [
|
|
26
|
+
'Install Deno or run `npm run setup:easy` to enable sandbox_exec.',
|
|
27
|
+
'Use http_request for straightforward API calls.',
|
|
28
|
+
'Use plugin_creator plus manage_schedules for recurring automations.',
|
|
29
|
+
],
|
|
30
|
+
})
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
/**
|
|
@@ -45,18 +43,13 @@ async function executeSandboxExec(args: any, context: { sessionId?: string; cwd?
|
|
|
45
43
|
const sessionId = context.sessionId ?? 'unknown'
|
|
46
44
|
const sandboxDir = path.join('/tmp', `swarmclaw-sandbox-${sessionId}-${Date.now()}`)
|
|
47
45
|
const denoPath = getDenoPath()
|
|
48
|
-
const nodePath = getNodePath()
|
|
49
|
-
const tsxPath = getTsxPath()
|
|
50
|
-
const pythonPath = getPythonPath()
|
|
51
46
|
|
|
52
|
-
if (language
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
if (language === 'typescript' && !denoPath && !tsxPath) {
|
|
56
|
-
return JSON.stringify({ error: 'No TypeScript runtime available. Install Deno or tsx.' })
|
|
47
|
+
if (language !== 'javascript' && language !== 'typescript') {
|
|
48
|
+
return sandboxUnavailableError('sandbox_exec currently supports only JavaScript and TypeScript via Deno.')
|
|
57
49
|
}
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
|
|
51
|
+
if (!denoPath) {
|
|
52
|
+
return sandboxUnavailableError('Deno is required for sandbox_exec. Unsafe Node/Python fallbacks are disabled.')
|
|
60
53
|
}
|
|
61
54
|
|
|
62
55
|
try {
|
|
@@ -65,36 +58,15 @@ async function executeSandboxExec(args: any, context: { sessionId?: string; cwd?
|
|
|
65
58
|
const scriptPath = path.join(sandboxDir, scriptFile)
|
|
66
59
|
fs.writeFileSync(scriptPath, code, 'utf-8')
|
|
67
60
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
cwd: sandboxDir, encoding: 'utf-8', timeout, maxBuffer: MAX_OUTPUT,
|
|
78
|
-
env: { PATH: process.env.PATH || '/usr/bin:/bin' } as any,
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
} else if (language === 'typescript') {
|
|
82
|
-
if (denoPath) {
|
|
83
|
-
result = spawnSync(denoPath, [
|
|
84
|
-
'run', '--allow-read=.', '--allow-write=.', '--allow-net', '--deny-env', '--no-prompt', scriptFile,
|
|
85
|
-
], { cwd: sandboxDir, encoding: 'utf-8', timeout, maxBuffer: MAX_OUTPUT })
|
|
86
|
-
} else {
|
|
87
|
-
result = spawnSync(tsxPath!, [scriptPath], {
|
|
88
|
-
cwd: sandboxDir, encoding: 'utf-8', timeout, maxBuffer: MAX_OUTPUT,
|
|
89
|
-
env: { PATH: process.env.PATH || '/usr/bin:/bin' } as any,
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
} else {
|
|
93
|
-
result = spawnSync(pythonPath!, [scriptPath], {
|
|
94
|
-
cwd: sandboxDir, encoding: 'utf-8', timeout, maxBuffer: MAX_OUTPUT,
|
|
95
|
-
env: { PATH: process.env.PATH || '/usr/bin:/bin' } as any,
|
|
96
|
-
})
|
|
97
|
-
}
|
|
61
|
+
const result = spawnSync(denoPath, [
|
|
62
|
+
'run',
|
|
63
|
+
'--allow-read=.',
|
|
64
|
+
'--allow-write=.',
|
|
65
|
+
'--allow-net',
|
|
66
|
+
'--deny-env',
|
|
67
|
+
'--no-prompt',
|
|
68
|
+
scriptFile,
|
|
69
|
+
], { cwd: sandboxDir, encoding: 'utf-8', timeout, maxBuffer: MAX_OUTPUT })
|
|
98
70
|
|
|
99
71
|
const stdout = truncate((result.stdout || '').toString(), MAX_OUTPUT)
|
|
100
72
|
const stderr = truncate((result.stderr || '').toString(), MAX_OUTPUT)
|
|
@@ -125,16 +97,18 @@ async function executeSandboxExec(args: any, context: { sessionId?: string; cwd?
|
|
|
125
97
|
}
|
|
126
98
|
|
|
127
99
|
async function executeListRuntimes() {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const ver = spawnSync(bin, ['--version'], { encoding: 'utf-8', timeout: 3000 })
|
|
132
|
-
runtimes[name] = { available: true, version: (ver.stdout || '').split('\n')[0]?.trim() || null }
|
|
133
|
-
} else {
|
|
134
|
-
runtimes[name] = { available: false }
|
|
135
|
-
}
|
|
100
|
+
const denoPath = getDenoPath()
|
|
101
|
+
if (!denoPath) {
|
|
102
|
+
return sandboxUnavailableError('Deno is not available for sandbox_exec.')
|
|
136
103
|
}
|
|
137
|
-
|
|
104
|
+
const ver = spawnSync(denoPath, ['--version'], { encoding: 'utf-8', timeout: 3000 })
|
|
105
|
+
return JSON.stringify({
|
|
106
|
+
deno: {
|
|
107
|
+
available: true,
|
|
108
|
+
version: (ver.stdout || '').split('\n')[0]?.trim() || null,
|
|
109
|
+
},
|
|
110
|
+
sandboxReady: true,
|
|
111
|
+
})
|
|
138
112
|
}
|
|
139
113
|
|
|
140
114
|
/**
|
|
@@ -142,18 +116,23 @@ async function executeListRuntimes() {
|
|
|
142
116
|
*/
|
|
143
117
|
const SandboxPlugin: Plugin = {
|
|
144
118
|
name: 'Core Sandbox',
|
|
145
|
-
description: '
|
|
119
|
+
description: 'Deno-based isolated code execution for JavaScript and TypeScript when custom code is necessary.',
|
|
146
120
|
hooks: {
|
|
147
|
-
getCapabilityDescription: () => 'I can run
|
|
121
|
+
getCapabilityDescription: () => 'I can run JavaScript or TypeScript in a Deno sandbox (`sandbox_exec`) when custom code is necessary. For straightforward API calls, use `http_request` instead.',
|
|
122
|
+
getOperatingGuidance: () => [
|
|
123
|
+
'Use `http_request` for straightforward REST/JSON API calls instead of writing code in `sandbox_exec`.',
|
|
124
|
+
'Use `sandbox_exec` only when custom parsing or transformation code is actually needed.',
|
|
125
|
+
'For recurring automations, prefer `plugin_creator` plus `manage_schedules` over repeated sandbox runs.',
|
|
126
|
+
],
|
|
148
127
|
} as PluginHooks,
|
|
149
128
|
tools: [
|
|
150
129
|
{
|
|
151
130
|
name: 'sandbox_exec',
|
|
152
|
-
description: 'Execute
|
|
131
|
+
description: 'Execute JavaScript or TypeScript in a Deno sandbox when custom code is necessary.',
|
|
153
132
|
parameters: {
|
|
154
133
|
type: 'object',
|
|
155
134
|
properties: {
|
|
156
|
-
language: { type: 'string', enum: ['javascript', 'typescript'
|
|
135
|
+
language: { type: 'string', enum: ['javascript', 'typescript'] },
|
|
157
136
|
code: { type: 'string' },
|
|
158
137
|
timeoutSec: { type: 'number' }
|
|
159
138
|
},
|
|
@@ -163,7 +142,7 @@ const SandboxPlugin: Plugin = {
|
|
|
163
142
|
},
|
|
164
143
|
{
|
|
165
144
|
name: 'sandbox_list_runtimes',
|
|
166
|
-
description: '
|
|
145
|
+
description: 'Report whether the Deno sandbox runtime is available.',
|
|
167
146
|
parameters: { type: 'object', properties: {} },
|
|
168
147
|
execute: async () => executeListRuntimes()
|
|
169
148
|
}
|
|
@@ -185,7 +164,11 @@ export function buildSandboxTools(bctx: ToolBuildContext): StructuredToolInterfa
|
|
|
185
164
|
{
|
|
186
165
|
name: 'sandbox_exec',
|
|
187
166
|
description: SandboxPlugin.tools![0].description,
|
|
188
|
-
schema: z.object({
|
|
167
|
+
schema: z.object({
|
|
168
|
+
language: z.enum(['javascript', 'typescript']),
|
|
169
|
+
code: z.string(),
|
|
170
|
+
timeoutSec: z.number().optional(),
|
|
171
|
+
})
|
|
189
172
|
}
|
|
190
173
|
),
|
|
191
174
|
tool(
|
|
@@ -198,29 +181,5 @@ export function buildSandboxTools(bctx: ToolBuildContext): StructuredToolInterfa
|
|
|
198
181
|
)
|
|
199
182
|
)
|
|
200
183
|
|
|
201
|
-
const openclawPath = findBinaryOnPath('openclaw') || findBinaryOnPath('clawdbot')
|
|
202
|
-
if (openclawPath) {
|
|
203
|
-
tools.push(
|
|
204
|
-
tool(
|
|
205
|
-
async (rawArgs) => {
|
|
206
|
-
const normalized = normalizeToolInputArgs((rawArgs ?? {}) as Record<string, unknown>)
|
|
207
|
-
const code = normalized.code as string | undefined
|
|
208
|
-
const explain = normalized.explain as boolean | undefined
|
|
209
|
-
try {
|
|
210
|
-
if (!code) return JSON.stringify({ error: 'code is required' })
|
|
211
|
-
const args = explain ? ['sandbox', 'explain', code] : ['sandbox', 'run', code]
|
|
212
|
-
const result = spawnSync(openclawPath, args, { encoding: 'utf-8', timeout: 60_000, maxBuffer: MAX_OUTPUT })
|
|
213
|
-
return JSON.stringify({ exitCode: result.status ?? 0, stdout: truncate(result.stdout || '', MAX_OUTPUT), stderr: truncate(result.stderr || '', MAX_OUTPUT) })
|
|
214
|
-
} catch (err: any) { return JSON.stringify({ error: err.message }) }
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
name: 'openclaw_sandbox',
|
|
218
|
-
description: 'Execute or explain code through OpenClaw CLI.',
|
|
219
|
-
schema: z.object({ code: z.string(), explain: z.boolean().optional() }),
|
|
220
|
-
}
|
|
221
|
-
)
|
|
222
|
-
)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
184
|
return tools
|
|
226
185
|
}
|
|
@@ -25,9 +25,30 @@ async function executeWhoAmI(context: { sessionId?: string; agentId?: string })
|
|
|
25
25
|
} catch (err: any) { return `Error: ${err.message}` }
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function inferSessionsAction(
|
|
29
|
+
normalized: Record<string, unknown>,
|
|
30
|
+
context: { sessionId?: string; agentId?: string },
|
|
31
|
+
): string | undefined {
|
|
32
|
+
const explicit = typeof normalized.action === 'string' ? normalized.action.trim() : ''
|
|
33
|
+
if (explicit) return explicit
|
|
34
|
+
|
|
35
|
+
const hasUpdates = !!normalized.updates && typeof normalized.updates === 'object'
|
|
36
|
+
const hasSpawnTarget = typeof normalized.agentId === 'string' || typeof normalized.agent_id === 'string'
|
|
37
|
+
const hasHistoryTarget =
|
|
38
|
+
typeof normalized.sessionId === 'string'
|
|
39
|
+
|| typeof normalized.session_id === 'string'
|
|
40
|
+
|| typeof normalized.limit === 'number'
|
|
41
|
+
|| !!context.sessionId
|
|
42
|
+
|
|
43
|
+
if (hasUpdates) return 'update'
|
|
44
|
+
if (hasSpawnTarget) return 'spawn'
|
|
45
|
+
if (hasHistoryTarget) return 'history'
|
|
46
|
+
return 'list'
|
|
47
|
+
}
|
|
48
|
+
|
|
28
49
|
async function executeSessionsAction(args: any, context: { sessionId?: string; agentId?: string; cwd: string }) {
|
|
29
50
|
const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
|
|
30
|
-
const action = normalized
|
|
51
|
+
const action = inferSessionsAction(normalized, context)
|
|
31
52
|
const sessionId = (normalized.sessionId ?? normalized.session_id) as string | undefined
|
|
32
53
|
const message = normalized.message as string | undefined
|
|
33
54
|
const limit = normalized.limit as number | undefined
|
|
@@ -32,12 +32,14 @@ describe('module exports', () => {
|
|
|
32
32
|
const crawl = await import('./crawl')
|
|
33
33
|
const mailbox = await import('./mailbox')
|
|
34
34
|
const humanLoop = await import('./human-loop')
|
|
35
|
+
const sandbox = await import('./sandbox')
|
|
35
36
|
assert.equal(typeof document.buildDocumentTools, 'function')
|
|
36
37
|
assert.equal(typeof extract.buildExtractTools, 'function')
|
|
37
38
|
assert.equal(typeof table.buildTableTools, 'function')
|
|
38
39
|
assert.equal(typeof crawl.buildCrawlTools, 'function')
|
|
39
40
|
assert.equal(typeof mailbox.buildMailboxTools, 'function')
|
|
40
41
|
assert.equal(typeof humanLoop.buildHumanLoopTools, 'function')
|
|
42
|
+
assert.equal(typeof sandbox.buildSandboxTools, 'function')
|
|
41
43
|
})
|
|
42
44
|
})
|
|
43
45
|
|
|
@@ -72,6 +74,27 @@ describe('buildSessionTools signature', () => {
|
|
|
72
74
|
// Verify the function has arity of at least 2
|
|
73
75
|
assert.ok(buildSessionTools.length >= 2, 'buildSessionTools should accept at least 2 params')
|
|
74
76
|
})
|
|
77
|
+
|
|
78
|
+
it('sandbox builder exposes only the local Deno sandbox tools', async () => {
|
|
79
|
+
const { buildSandboxTools } = await import('./sandbox')
|
|
80
|
+
const bctx: import('./context').ToolBuildContext = {
|
|
81
|
+
cwd: process.cwd(),
|
|
82
|
+
ctx: { sessionId: 'sandbox-test' },
|
|
83
|
+
hasPlugin: (name) => name === 'sandbox',
|
|
84
|
+
hasTool: (name) => name === 'sandbox',
|
|
85
|
+
cleanupFns: [],
|
|
86
|
+
commandTimeoutMs: 1_000,
|
|
87
|
+
claudeTimeoutMs: 1_000,
|
|
88
|
+
cliProcessTimeoutMs: 1_000,
|
|
89
|
+
persistDelegateResumeId: () => {},
|
|
90
|
+
readStoredDelegateResumeId: () => null,
|
|
91
|
+
resolveCurrentSession: () => null,
|
|
92
|
+
activePlugins: ['sandbox'],
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const tools = buildSandboxTools(bctx).map((tool) => tool.name).sort()
|
|
96
|
+
assert.deepEqual(tools, ['sandbox_exec', 'sandbox_list_runtimes'])
|
|
97
|
+
})
|
|
75
98
|
})
|
|
76
99
|
|
|
77
100
|
// ---------------------------------------------------------------------------
|
|
@@ -163,8 +163,8 @@ const ShellPlugin: Plugin = {
|
|
|
163
163
|
name: 'Core Shell',
|
|
164
164
|
description: 'Execute shell commands and manage background processes.',
|
|
165
165
|
hooks: {
|
|
166
|
-
getCapabilityDescription: () => 'I can run shell commands
|
|
167
|
-
getOperatingGuidance: () => ['Shell: use `
|
|
166
|
+
getCapabilityDescription: () => 'I can run shell commands with the unified `shell` tool. Use action `execute` for commands, and `list` / `status` / `poll` / `log` for long-lived processes.',
|
|
167
|
+
getOperatingGuidance: () => ['Shell: use `shell` with `{"action":"execute","command":"..."}` for servers, installs, scripts, and git. Use `background=true` for long-lived processes.', 'Verify servers with `shell` status/log actions and liveness probes before claiming success.', 'Resolve IPs/URLs via shell — never use placeholders. Retry path errors without workdir override.'],
|
|
168
168
|
} as PluginHooks,
|
|
169
169
|
tools: [
|
|
170
170
|
{
|
|
@@ -9,6 +9,7 @@ import type { ToolBuildContext } from './context'
|
|
|
9
9
|
import type { Plugin, PluginHooks } from '@/types'
|
|
10
10
|
import { getPluginManager } from '../plugins'
|
|
11
11
|
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
12
|
+
import { applyResolvedRoute, resolvePrimaryAgentRoute } from '../agent-runtime-config'
|
|
12
13
|
import {
|
|
13
14
|
appendDelegationCheckpoint,
|
|
14
15
|
cancelDelegationJob,
|
|
@@ -74,7 +75,7 @@ async function startSubagentJob(jobId: string, args: {
|
|
|
74
75
|
const sessions = loadSessions()
|
|
75
76
|
const parent = context.sessionId ? sessions[context.sessionId] : null
|
|
76
77
|
const browserProfileId = resolveSubagentBrowserProfileId(parent, sid, args.shareBrowserProfile === true)
|
|
77
|
-
|
|
78
|
+
const nextSession = {
|
|
78
79
|
id: sid,
|
|
79
80
|
name: `subagent-${agent.name}`,
|
|
80
81
|
cwd: args.cwd || context.cwd,
|
|
@@ -91,6 +92,7 @@ async function startSubagentJob(jobId: string, args: {
|
|
|
91
92
|
plugins: agent.plugins || agent.tools || [],
|
|
92
93
|
browserProfileId,
|
|
93
94
|
}
|
|
95
|
+
sessions[sid] = applyResolvedRoute(nextSession, resolvePrimaryAgentRoute(agent))
|
|
94
96
|
saveSessions(sessions)
|
|
95
97
|
|
|
96
98
|
startDelegationJob(jobId, {
|
|
@@ -222,7 +222,7 @@ const WebPlugin: Plugin = {
|
|
|
222
222
|
name: 'Core Web',
|
|
223
223
|
description: 'Search the web and fetch content from URLs.',
|
|
224
224
|
hooks: {
|
|
225
|
-
getCapabilityDescription: () => 'I can
|
|
225
|
+
getCapabilityDescription: () => 'I can use the unified `web` tool with action `search` for research and action `fetch` for reading a URL.',
|
|
226
226
|
} as PluginHooks,
|
|
227
227
|
tools: [
|
|
228
228
|
{
|
|
@@ -400,9 +400,12 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
const stringifyStructured = (value: unknown): string => truncate(JSON.stringify(value, null, 2), MAX_OUTPUT)
|
|
403
|
+
const callBrowserEvaluate = (fn: string) => callMcpTool('browser_evaluate', {
|
|
404
|
+
function: fn,
|
|
405
|
+
})
|
|
403
406
|
|
|
404
407
|
const captureStructuredObservation = async () => {
|
|
405
|
-
const expression = `(
|
|
408
|
+
const expression = `() => {
|
|
406
409
|
const normalize = (value) => String(value || '').replace(/\\s+/g, ' ').trim();
|
|
407
410
|
const visible = (el) => {
|
|
408
411
|
if (!el) return false;
|
|
@@ -445,17 +448,27 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
445
448
|
.slice(0, 10)
|
|
446
449
|
.map((el) => normalize(el.innerText || el.textContent))
|
|
447
450
|
.filter(Boolean);
|
|
448
|
-
|
|
451
|
+
const textPreview = normalize(document.body?.innerText || document.body?.textContent || '').slice(0, 1200);
|
|
452
|
+
const lowerPreview = textPreview.toLowerCase();
|
|
453
|
+
const notices = [];
|
|
454
|
+
if (/ask the human|out-of-band|do not guess|verification code required/.test(lowerPreview)) {
|
|
455
|
+
notices.push({
|
|
456
|
+
type: 'human_input_required',
|
|
457
|
+
message: 'This page requires human-provided input. Ask the human instead of guessing or repeatedly submitting blank values.',
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
449
461
|
url: window.location.href,
|
|
450
462
|
title: document.title || null,
|
|
451
|
-
textPreview
|
|
463
|
+
textPreview,
|
|
452
464
|
links,
|
|
453
465
|
forms,
|
|
454
466
|
tables,
|
|
455
467
|
errors,
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
468
|
+
notices,
|
|
469
|
+
};
|
|
470
|
+
}`
|
|
471
|
+
const raw = await callBrowserEvaluate(expression)
|
|
459
472
|
const parsed = extractJsonPayload(raw)
|
|
460
473
|
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
461
474
|
const observation = {
|
|
@@ -638,18 +651,39 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
638
651
|
|
|
639
652
|
const dismissCookieBanners = async (mcpCall: (toolName: string, args: Record<string, unknown>) => Promise<string>) => {
|
|
640
653
|
await new Promise((r) => setTimeout(r, 1500))
|
|
641
|
-
const js = `(
|
|
654
|
+
const js = `() => {
|
|
642
655
|
const sel = ['button[id*="reject" i]', 'button[class*="reject" i]', 'a[id*="reject" i]', 'a[class*="reject" i]', '#onetrust-reject-all-handler', '#CybotCookiebotDialogBodyButtonDecline', '#didomi-notice-disagree-button', '.qc-cmp2-summary-buttons button:first-child', 'button.sp_choice_type_12'];
|
|
643
656
|
for (const s of sel) { const el = document.querySelector(s); if (el && el.offsetParent !== null) { el.click(); return 'dismissed:' + s; } }
|
|
644
657
|
const btns = [...document.querySelectorAll('button, a[role="button"]')]; const rejectRe = /^(reject|reject all|decline|deny|refuse|no,? thanks|only necessary|necessary only)$/i;
|
|
645
658
|
for (const b of btns) { const txt = (b.textContent || '').trim(); if (rejectRe.test(txt) && b.offsetParent !== null) { b.click(); return 'dismissed:text=' + txt; } }
|
|
646
659
|
return 'none';
|
|
647
|
-
}
|
|
648
|
-
await mcpCall('browser_evaluate', {
|
|
660
|
+
}`
|
|
661
|
+
await mcpCall('browser_evaluate', { function: js })
|
|
649
662
|
}
|
|
650
663
|
|
|
651
664
|
const performFillForm = async (params: Record<string, unknown>) => {
|
|
652
|
-
const fields = Array.isArray(params.fields)
|
|
665
|
+
const fields = Array.isArray(params.fields)
|
|
666
|
+
? params.fields
|
|
667
|
+
: (() => {
|
|
668
|
+
const form = params.form
|
|
669
|
+
if (!form || typeof form !== 'object' || Array.isArray(form)) return []
|
|
670
|
+
return Object.entries(form as Record<string, unknown>).map(([key, value]) => {
|
|
671
|
+
const escapedId = String(key).replace(/[^a-zA-Z0-9_-]/g, '')
|
|
672
|
+
const escapedAttr = String(key).replace(/["\\]/g, '\\$&')
|
|
673
|
+
const inferredType = typeof value === 'boolean'
|
|
674
|
+
? 'checkbox'
|
|
675
|
+
: /password/i.test(key)
|
|
676
|
+
? 'password'
|
|
677
|
+
: 'text'
|
|
678
|
+
return {
|
|
679
|
+
element: escapedId
|
|
680
|
+
? `#${escapedId}, [name="${escapedAttr}"]`
|
|
681
|
+
: `[name="${escapedAttr}"]`,
|
|
682
|
+
type: inferredType,
|
|
683
|
+
value,
|
|
684
|
+
}
|
|
685
|
+
})
|
|
686
|
+
})()
|
|
653
687
|
if (fields.length === 0) return { ok: false, error: 'fields is required for fill_form.' }
|
|
654
688
|
const filled: Array<Record<string, unknown>> = []
|
|
655
689
|
for (const field of fields) {
|
|
@@ -692,14 +726,29 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
692
726
|
element: typeof params.submitElement === 'string' ? params.submitElement : undefined,
|
|
693
727
|
})
|
|
694
728
|
} else {
|
|
695
|
-
await
|
|
729
|
+
await callBrowserEvaluate(`() => {
|
|
730
|
+
const form = document.forms[0];
|
|
731
|
+
if (!form) return { submitted: false, reason: 'no-form' };
|
|
732
|
+
const submitButton = form.querySelector('button[type="submit"], input[type="submit"], button');
|
|
733
|
+
if (submitButton && typeof submitButton.click === 'function') {
|
|
734
|
+
submitButton.click();
|
|
735
|
+
return { submitted: true, method: 'click' };
|
|
736
|
+
}
|
|
737
|
+
if (typeof form.requestSubmit === 'function') {
|
|
738
|
+
form.requestSubmit();
|
|
739
|
+
return { submitted: true, method: 'requestSubmit' };
|
|
740
|
+
}
|
|
741
|
+
if (typeof form.submit === 'function') {
|
|
742
|
+
form.submit();
|
|
743
|
+
return { submitted: true, method: 'submit' };
|
|
744
|
+
}
|
|
745
|
+
return { submitted: false, reason: 'no-submit-method' };
|
|
746
|
+
}`)
|
|
696
747
|
}
|
|
697
748
|
|
|
698
749
|
const waitMs = typeof params.waitMs === 'number' ? Math.max(250, params.waitMs) : 1000
|
|
699
750
|
try {
|
|
700
|
-
await
|
|
701
|
-
expression: `await new Promise(resolve => setTimeout(resolve, ${Math.min(waitMs, 5000)}))`,
|
|
702
|
-
})
|
|
751
|
+
await callBrowserEvaluate(`async () => { await new Promise(resolve => setTimeout(resolve, ${Math.min(waitMs, 5000)})); }`)
|
|
703
752
|
} catch {
|
|
704
753
|
await new Promise((resolve) => setTimeout(resolve, waitMs))
|
|
705
754
|
}
|
|
@@ -723,18 +772,16 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
723
772
|
const maxScrolls = typeof params.maxScrolls === 'number' ? Math.max(1, Math.min(20, params.maxScrolls)) : 8
|
|
724
773
|
let matchedAtStep = -1
|
|
725
774
|
for (let index = 0; index < maxScrolls; index += 1) {
|
|
726
|
-
const result = await
|
|
727
|
-
expression: `(() => {
|
|
775
|
+
const result = await callBrowserEvaluate(`() => {
|
|
728
776
|
const bodyText = String(document.body?.innerText || document.body?.textContent || '');
|
|
729
777
|
const selector = ${JSON.stringify(selector)};
|
|
730
778
|
const containsText = ${JSON.stringify(containsText)};
|
|
731
779
|
const match = (selector && !!document.querySelector(selector))
|
|
732
780
|
|| (containsText && bodyText.includes(containsText));
|
|
733
|
-
if (match) return
|
|
781
|
+
if (match) return { found: true, scrollY: window.scrollY, step: ${index} };
|
|
734
782
|
window.scrollBy({ top: Math.max(window.innerHeight * 0.85, 600), behavior: 'instant' });
|
|
735
|
-
return
|
|
736
|
-
})
|
|
737
|
-
})
|
|
783
|
+
return { found: false, scrollY: window.scrollY, step: ${index} };
|
|
784
|
+
}`)
|
|
738
785
|
const payload = extractJsonPayload(result)
|
|
739
786
|
if (payload && typeof payload === 'object' && !Array.isArray(payload) && (payload as Record<string, unknown>).found === true) {
|
|
740
787
|
matchedAtStep = index
|
|
@@ -756,8 +803,7 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
756
803
|
const linkText = typeof params.linkText === 'string' ? params.linkText.trim() : ''
|
|
757
804
|
const hrefContains = typeof params.hrefContains === 'string' ? params.hrefContains.trim() : ''
|
|
758
805
|
if (!linkText && !hrefContains) return null
|
|
759
|
-
const result = await
|
|
760
|
-
expression: `(() => {
|
|
806
|
+
const result = await callBrowserEvaluate(`() => {
|
|
761
807
|
const linkText = ${JSON.stringify(linkText)};
|
|
762
808
|
const hrefContains = ${JSON.stringify(hrefContains)};
|
|
763
809
|
const links = Array.from(document.querySelectorAll('a[href]'));
|
|
@@ -769,9 +815,8 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
769
815
|
if (hrefContains && href.toLowerCase().includes(hrefContains.toLowerCase())) return true;
|
|
770
816
|
return false;
|
|
771
817
|
});
|
|
772
|
-
return
|
|
773
|
-
})
|
|
774
|
-
})
|
|
818
|
+
return { href: match ? (match.href || match.getAttribute('href') || '') : null };
|
|
819
|
+
}`)
|
|
775
820
|
const payload = extractJsonPayload(result)
|
|
776
821
|
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
777
822
|
const href = (payload as Record<string, unknown>).href
|
|
@@ -1083,16 +1128,14 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
|
|
|
1083
1128
|
|
|
1084
1129
|
if (action === 'screenshot' || action === 'snapshot') {
|
|
1085
1130
|
try {
|
|
1086
|
-
await
|
|
1087
|
-
expression: `await new Promise(resolve => {
|
|
1131
|
+
await callBrowserEvaluate(`async () => { await new Promise(resolve => {
|
|
1088
1132
|
if (document.readyState === 'complete') {
|
|
1089
1133
|
setTimeout(resolve, 1200);
|
|
1090
1134
|
} else {
|
|
1091
1135
|
window.addEventListener('load', () => setTimeout(resolve, 1200), { once: true });
|
|
1092
1136
|
setTimeout(resolve, 5000);
|
|
1093
1137
|
}
|
|
1094
|
-
})
|
|
1095
|
-
})
|
|
1138
|
+
}); }`)
|
|
1096
1139
|
} catch {
|
|
1097
1140
|
await new Promise((r) => setTimeout(r, 1200))
|
|
1098
1141
|
}
|
|
@@ -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',
|
|
@@ -159,6 +162,7 @@ const COLLECTIONS = [
|
|
|
159
162
|
'browser_sessions',
|
|
160
163
|
'watch_jobs',
|
|
161
164
|
'delegation_jobs',
|
|
165
|
+
'external_agents',
|
|
162
166
|
] as const
|
|
163
167
|
|
|
164
168
|
export type StorageCollection = (typeof COLLECTIONS)[number]
|
|
@@ -352,10 +356,12 @@ const JSON_FILES: Record<string, string> = {
|
|
|
352
356
|
tasks: path.join(DATA_DIR, 'tasks.json'),
|
|
353
357
|
secrets: path.join(DATA_DIR, 'secrets.json'),
|
|
354
358
|
provider_configs: path.join(DATA_DIR, 'providers.json'),
|
|
359
|
+
gateway_profiles: path.join(DATA_DIR, 'gateways.json'),
|
|
355
360
|
skills: path.join(DATA_DIR, 'skills.json'),
|
|
356
361
|
connectors: path.join(DATA_DIR, 'connectors.json'),
|
|
357
362
|
documents: path.join(DATA_DIR, 'documents.json'),
|
|
358
363
|
webhooks: path.join(DATA_DIR, 'webhooks.json'),
|
|
364
|
+
external_agents: path.join(DATA_DIR, 'external-agents.json'),
|
|
359
365
|
}
|
|
360
366
|
|
|
361
367
|
const MIGRATION_FLAG = path.join(DATA_DIR, '.sqlite_migrated')
|
|
@@ -803,6 +809,8 @@ function isProvidedSecretValue(value: unknown): value is string {
|
|
|
803
809
|
|
|
804
810
|
function buildPersistedSettings(input: Record<string, any>, existing?: PersistedSettingsRecord): PersistedSettingsRecord {
|
|
805
811
|
const next = cloneRecord(input) as PersistedSettingsRecord
|
|
812
|
+
Object.assign(next, normalizeRuntimeSettingFields(next))
|
|
813
|
+
Object.assign(next, normalizeHeartbeatSettingFields(next))
|
|
806
814
|
const encrypted = {
|
|
807
815
|
...(existing ? getEncryptedAppSettings(existing) : {}),
|
|
808
816
|
...getEncryptedAppSettings(next),
|
|
@@ -935,6 +943,15 @@ export function saveProviderConfigs(p: Record<string, any>) {
|
|
|
935
943
|
saveCollection('provider_configs', p)
|
|
936
944
|
}
|
|
937
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
|
+
|
|
938
955
|
// --- Model Overrides (user-added models for built-in providers) ---
|
|
939
956
|
export function loadModelOverrides(): Record<string, string[]> {
|
|
940
957
|
return loadCollection('model_overrides') as Record<string, string[]>
|
|
@@ -964,6 +981,15 @@ export function saveSkills(s: Record<string, any>) {
|
|
|
964
981
|
saveCollection('skills', s)
|
|
965
982
|
}
|
|
966
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
|
+
|
|
967
993
|
// --- Usage ---
|
|
968
994
|
export function loadUsage(): Record<string, any[]> {
|
|
969
995
|
const stmt = db.prepare('SELECT session_id, data FROM usage')
|
|
@@ -1067,11 +1093,11 @@ export function saveIntegrityBaselines(entries: Record<string, any>) {
|
|
|
1067
1093
|
}
|
|
1068
1094
|
|
|
1069
1095
|
// --- Webhook Logs ---
|
|
1070
|
-
export function loadWebhookLogs(): Record<string,
|
|
1096
|
+
export function loadWebhookLogs(): Record<string, unknown> {
|
|
1071
1097
|
return loadCollection('webhook_logs')
|
|
1072
1098
|
}
|
|
1073
1099
|
|
|
1074
|
-
export function appendWebhookLog(id: string, entry:
|
|
1100
|
+
export function appendWebhookLog(id: string, entry: unknown) {
|
|
1075
1101
|
upsertCollectionItem('webhook_logs', id, entry)
|
|
1076
1102
|
}
|
|
1077
1103
|
|