@usejarvis/brain 0.1.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/LICENSE +153 -0
- package/README.md +278 -0
- package/bin/jarvis.ts +413 -0
- package/package.json +74 -0
- package/scripts/ensure-bun.cjs +8 -0
- package/src/actions/README.md +421 -0
- package/src/actions/app-control/desktop-controller.test.ts +26 -0
- package/src/actions/app-control/desktop-controller.ts +438 -0
- package/src/actions/app-control/interface.ts +64 -0
- package/src/actions/app-control/linux.ts +273 -0
- package/src/actions/app-control/macos.ts +54 -0
- package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
- package/src/actions/app-control/sidecar-launcher.ts +286 -0
- package/src/actions/app-control/windows.ts +44 -0
- package/src/actions/browser/cdp.ts +138 -0
- package/src/actions/browser/chrome-launcher.ts +252 -0
- package/src/actions/browser/session.ts +437 -0
- package/src/actions/browser/stealth.ts +49 -0
- package/src/actions/index.ts +20 -0
- package/src/actions/terminal/executor.ts +157 -0
- package/src/actions/terminal/wsl-bridge.ts +126 -0
- package/src/actions/test.ts +93 -0
- package/src/actions/tools/agents.ts +321 -0
- package/src/actions/tools/builtin.ts +846 -0
- package/src/actions/tools/commitments.ts +192 -0
- package/src/actions/tools/content.ts +217 -0
- package/src/actions/tools/delegate.ts +147 -0
- package/src/actions/tools/desktop.test.ts +55 -0
- package/src/actions/tools/desktop.ts +305 -0
- package/src/actions/tools/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +20 -0
- package/src/actions/tools/registry.ts +171 -0
- package/src/actions/tools/research.ts +111 -0
- package/src/actions/tools/sidecar-list.ts +57 -0
- package/src/actions/tools/sidecar-route.ts +105 -0
- package/src/actions/tools/workflows.ts +216 -0
- package/src/agents/agent.ts +132 -0
- package/src/agents/delegation.ts +107 -0
- package/src/agents/hierarchy.ts +113 -0
- package/src/agents/index.ts +19 -0
- package/src/agents/messaging.ts +125 -0
- package/src/agents/orchestrator.ts +576 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +307 -0
- package/src/agents/task-manager.ts +151 -0
- package/src/authority/approval-delivery.ts +59 -0
- package/src/authority/approval.ts +196 -0
- package/src/authority/audit.ts +158 -0
- package/src/authority/authority.test.ts +519 -0
- package/src/authority/deferred-executor.ts +103 -0
- package/src/authority/emergency.ts +66 -0
- package/src/authority/engine.ts +297 -0
- package/src/authority/index.ts +12 -0
- package/src/authority/learning.ts +111 -0
- package/src/authority/tool-action-map.ts +74 -0
- package/src/awareness/analytics.ts +466 -0
- package/src/awareness/awareness.test.ts +332 -0
- package/src/awareness/capture-engine.ts +305 -0
- package/src/awareness/context-graph.ts +130 -0
- package/src/awareness/context-tracker.ts +349 -0
- package/src/awareness/index.ts +25 -0
- package/src/awareness/intelligence.ts +321 -0
- package/src/awareness/ocr-engine.ts +88 -0
- package/src/awareness/service.ts +528 -0
- package/src/awareness/struggle-detector.ts +342 -0
- package/src/awareness/suggestion-engine.ts +476 -0
- package/src/awareness/types.ts +201 -0
- package/src/cli/autostart.ts +241 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +230 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +580 -0
- package/src/comms/README.md +329 -0
- package/src/comms/auth-error.html +48 -0
- package/src/comms/channels/discord.ts +228 -0
- package/src/comms/channels/signal.ts +56 -0
- package/src/comms/channels/telegram.ts +316 -0
- package/src/comms/channels/whatsapp.ts +60 -0
- package/src/comms/channels.test.ts +173 -0
- package/src/comms/desktop-notify.ts +114 -0
- package/src/comms/example.ts +129 -0
- package/src/comms/index.ts +129 -0
- package/src/comms/streaming.ts +142 -0
- package/src/comms/voice.test.ts +152 -0
- package/src/comms/voice.ts +291 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +473 -0
- package/src/config/README.md +387 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +137 -0
- package/src/config/loader.ts +142 -0
- package/src/config/types.ts +260 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +600 -0
- package/src/daemon/api-routes.ts +2119 -0
- package/src/daemon/background-agent-service.ts +396 -0
- package/src/daemon/background-agent.test.ts +78 -0
- package/src/daemon/channel-service.ts +201 -0
- package/src/daemon/commitment-executor.ts +297 -0
- package/src/daemon/event-classifier.ts +239 -0
- package/src/daemon/event-coalescer.ts +123 -0
- package/src/daemon/event-reactor.ts +214 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1004 -0
- package/src/daemon/llm-settings.ts +316 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.ts +98 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +788 -0
- package/src/goals/accountability.ts +240 -0
- package/src/goals/awareness-bridge.ts +185 -0
- package/src/goals/estimator.ts +185 -0
- package/src/goals/events.ts +28 -0
- package/src/goals/goals.test.ts +400 -0
- package/src/goals/integration.test.ts +329 -0
- package/src/goals/nl-builder.test.ts +220 -0
- package/src/goals/nl-builder.ts +256 -0
- package/src/goals/rhythm.test.ts +177 -0
- package/src/goals/rhythm.ts +275 -0
- package/src/goals/service.test.ts +135 -0
- package/src/goals/service.ts +348 -0
- package/src/goals/types.ts +106 -0
- package/src/goals/workflow-bridge.ts +96 -0
- package/src/integrations/google-api.ts +134 -0
- package/src/integrations/google-auth.ts +175 -0
- package/src/llm/README.md +291 -0
- package/src/llm/anthropic.ts +386 -0
- package/src/llm/gemini.ts +371 -0
- package/src/llm/index.ts +19 -0
- package/src/llm/manager.ts +153 -0
- package/src/llm/ollama.ts +307 -0
- package/src/llm/openai.ts +350 -0
- package/src/llm/provider.test.ts +231 -0
- package/src/llm/provider.ts +60 -0
- package/src/llm/test.ts +87 -0
- package/src/observers/README.md +278 -0
- package/src/observers/calendar.ts +113 -0
- package/src/observers/clipboard.ts +136 -0
- package/src/observers/email.ts +109 -0
- package/src/observers/example.ts +58 -0
- package/src/observers/file-watcher.ts +124 -0
- package/src/observers/index.ts +159 -0
- package/src/observers/notifications.ts +197 -0
- package/src/observers/observers.test.ts +203 -0
- package/src/observers/processes.ts +225 -0
- package/src/personality/README.md +61 -0
- package/src/personality/adapter.ts +196 -0
- package/src/personality/index.ts +20 -0
- package/src/personality/learner.ts +209 -0
- package/src/personality/model.ts +132 -0
- package/src/personality/personality.test.ts +236 -0
- package/src/roles/README.md +252 -0
- package/src/roles/authority.ts +119 -0
- package/src/roles/example-usage.ts +198 -0
- package/src/roles/index.ts +42 -0
- package/src/roles/loader.ts +143 -0
- package/src/roles/prompt-builder.ts +194 -0
- package/src/roles/test-multi.ts +102 -0
- package/src/roles/test-role.yaml +77 -0
- package/src/roles/test-utils.ts +93 -0
- package/src/roles/test.ts +106 -0
- package/src/roles/tool-guide.ts +190 -0
- package/src/roles/types.ts +36 -0
- package/src/roles/utils.ts +200 -0
- package/src/scripts/google-setup.ts +168 -0
- package/src/sidecar/connection.ts +179 -0
- package/src/sidecar/index.ts +6 -0
- package/src/sidecar/manager.ts +542 -0
- package/src/sidecar/protocol.ts +85 -0
- package/src/sidecar/rpc.ts +161 -0
- package/src/sidecar/scheduler.ts +136 -0
- package/src/sidecar/types.ts +112 -0
- package/src/sidecar/validator.ts +144 -0
- package/src/vault/README.md +110 -0
- package/src/vault/awareness.ts +341 -0
- package/src/vault/commitments.ts +299 -0
- package/src/vault/content-pipeline.ts +260 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/entities.ts +180 -0
- package/src/vault/extractor.test.ts +356 -0
- package/src/vault/extractor.ts +345 -0
- package/src/vault/facts.ts +190 -0
- package/src/vault/goals.ts +477 -0
- package/src/vault/index.ts +87 -0
- package/src/vault/keychain.ts +99 -0
- package/src/vault/observations.ts +115 -0
- package/src/vault/relationships.ts +178 -0
- package/src/vault/retrieval.test.ts +126 -0
- package/src/vault/retrieval.ts +227 -0
- package/src/vault/schema.ts +658 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/vectors.ts +92 -0
- package/src/vault/workflows.ts +403 -0
- package/src/workflows/auto-suggest.ts +290 -0
- package/src/workflows/engine.ts +366 -0
- package/src/workflows/events.ts +24 -0
- package/src/workflows/executor.ts +207 -0
- package/src/workflows/nl-builder.ts +198 -0
- package/src/workflows/nodes/actions/agent-task.ts +73 -0
- package/src/workflows/nodes/actions/calendar-action.ts +85 -0
- package/src/workflows/nodes/actions/code-execution.ts +73 -0
- package/src/workflows/nodes/actions/discord.ts +77 -0
- package/src/workflows/nodes/actions/file-write.ts +73 -0
- package/src/workflows/nodes/actions/gmail.ts +69 -0
- package/src/workflows/nodes/actions/http-request.ts +117 -0
- package/src/workflows/nodes/actions/notification.ts +85 -0
- package/src/workflows/nodes/actions/run-tool.ts +55 -0
- package/src/workflows/nodes/actions/send-message.ts +82 -0
- package/src/workflows/nodes/actions/shell-command.ts +76 -0
- package/src/workflows/nodes/actions/telegram.ts +60 -0
- package/src/workflows/nodes/builtin.ts +119 -0
- package/src/workflows/nodes/error/error-handler.ts +37 -0
- package/src/workflows/nodes/error/fallback.ts +47 -0
- package/src/workflows/nodes/error/retry.ts +82 -0
- package/src/workflows/nodes/logic/delay.ts +42 -0
- package/src/workflows/nodes/logic/if-else.ts +41 -0
- package/src/workflows/nodes/logic/loop.ts +90 -0
- package/src/workflows/nodes/logic/merge.ts +38 -0
- package/src/workflows/nodes/logic/race.ts +40 -0
- package/src/workflows/nodes/logic/switch.ts +59 -0
- package/src/workflows/nodes/logic/template-render.ts +53 -0
- package/src/workflows/nodes/logic/variable-get.ts +37 -0
- package/src/workflows/nodes/logic/variable-set.ts +59 -0
- package/src/workflows/nodes/registry.ts +99 -0
- package/src/workflows/nodes/transform/aggregate.ts +99 -0
- package/src/workflows/nodes/transform/csv-parse.ts +70 -0
- package/src/workflows/nodes/transform/json-parse.ts +63 -0
- package/src/workflows/nodes/transform/map-filter.ts +84 -0
- package/src/workflows/nodes/transform/regex-match.ts +89 -0
- package/src/workflows/nodes/triggers/calendar.ts +33 -0
- package/src/workflows/nodes/triggers/clipboard.ts +32 -0
- package/src/workflows/nodes/triggers/cron.ts +40 -0
- package/src/workflows/nodes/triggers/email.ts +40 -0
- package/src/workflows/nodes/triggers/file-change.ts +45 -0
- package/src/workflows/nodes/triggers/git.ts +46 -0
- package/src/workflows/nodes/triggers/manual.ts +23 -0
- package/src/workflows/nodes/triggers/poll.ts +81 -0
- package/src/workflows/nodes/triggers/process.ts +44 -0
- package/src/workflows/nodes/triggers/screen-event.ts +37 -0
- package/src/workflows/nodes/triggers/webhook.ts +39 -0
- package/src/workflows/safe-eval.ts +139 -0
- package/src/workflows/template.ts +118 -0
- package/src/workflows/triggers/cron.ts +311 -0
- package/src/workflows/triggers/manager.ts +285 -0
- package/src/workflows/triggers/observer-bridge.ts +172 -0
- package/src/workflows/triggers/poller.ts +201 -0
- package/src/workflows/triggers/screen-condition.ts +218 -0
- package/src/workflows/triggers/triggers.test.ts +740 -0
- package/src/workflows/triggers/webhook.ts +191 -0
- package/src/workflows/types.ts +133 -0
- package/src/workflows/variables.ts +72 -0
- package/src/workflows/workflows.test.ts +383 -0
- package/src/workflows/yaml.ts +104 -0
- package/ui/dist/index-j75njzc1.css +1199 -0
- package/ui/dist/index-p2zh407q.js +80603 -0
- package/ui/dist/index.html +13 -0
- package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
- package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
- package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
- package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
- package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Tools — The Hands
|
|
3
|
+
*
|
|
4
|
+
* Concrete tool implementations that the agent can call:
|
|
5
|
+
* run_command, read_file, write_file, list_directory
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, unlinkSync } from 'node:fs';
|
|
9
|
+
import { resolve } from 'node:path';
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
11
|
+
import { hostname, platform, arch, cpus, version } from 'node:os';
|
|
12
|
+
import { TerminalExecutor } from '../terminal/executor.ts';
|
|
13
|
+
import { BrowserController, type PageSnapshot } from '../browser/session.ts';
|
|
14
|
+
import type { ToolDefinition, ToolResult } from './registry.ts';
|
|
15
|
+
import type { LLMTool } from '../../llm/provider.ts';
|
|
16
|
+
import { routeToSidecar } from './sidecar-route.ts';
|
|
17
|
+
import { listSidecarsTool } from './sidecar-list.ts';
|
|
18
|
+
import { DESKTOP_TOOLS } from './desktop.ts';
|
|
19
|
+
|
|
20
|
+
const terminal = new TerminalExecutor({ timeout: 30000 });
|
|
21
|
+
|
|
22
|
+
// Shared browser controller (lazy-connected on first browser tool use)
|
|
23
|
+
export const browser = new BrowserController();
|
|
24
|
+
|
|
25
|
+
import { isNoLocalTools, LOCAL_DISABLED_MSG } from './local-tools-guard.ts';
|
|
26
|
+
// Re-export for convenience
|
|
27
|
+
export { setNoLocalTools, isNoLocalTools } from './local-tools-guard.ts';
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Convert a ToolDefinition's parameters to JSON Schema for LLM tool use.
|
|
32
|
+
*/
|
|
33
|
+
export function toolDefToLLMTool(tool: ToolDefinition): LLMTool {
|
|
34
|
+
const properties: Record<string, unknown> = {};
|
|
35
|
+
const required: string[] = [];
|
|
36
|
+
|
|
37
|
+
for (const [name, param] of Object.entries(tool.parameters)) {
|
|
38
|
+
properties[name] = {
|
|
39
|
+
type: param.type,
|
|
40
|
+
description: param.description,
|
|
41
|
+
};
|
|
42
|
+
if (param.required) {
|
|
43
|
+
required.push(name);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
name: tool.name,
|
|
49
|
+
description: tool.description,
|
|
50
|
+
parameters: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties,
|
|
53
|
+
required,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// --- Tool Implementations ---
|
|
59
|
+
|
|
60
|
+
export const runCommandTool: ToolDefinition = {
|
|
61
|
+
name: 'run_command',
|
|
62
|
+
description: 'Execute a shell command and return the output. Use this to run terminal commands, scripts, or system utilities. Optionally specify a "target" sidecar name/ID to run the command on a remote machine instead of locally.',
|
|
63
|
+
category: 'terminal',
|
|
64
|
+
parameters: {
|
|
65
|
+
command: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'The shell command to execute',
|
|
68
|
+
required: true,
|
|
69
|
+
},
|
|
70
|
+
target: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
description: 'Sidecar name or ID to run on a remote machine (omit for local execution)',
|
|
73
|
+
required: false,
|
|
74
|
+
},
|
|
75
|
+
cwd: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
description: 'Working directory for the command (optional, defaults to home directory)',
|
|
78
|
+
required: false,
|
|
79
|
+
},
|
|
80
|
+
timeout: {
|
|
81
|
+
type: 'number',
|
|
82
|
+
description: 'Timeout in milliseconds (optional, defaults to 30000)',
|
|
83
|
+
required: false,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
execute: async (params) => {
|
|
87
|
+
const target = params.target as string | undefined;
|
|
88
|
+
if (target) {
|
|
89
|
+
return routeToSidecar(target, 'run_command', {
|
|
90
|
+
command: params.command,
|
|
91
|
+
cwd: params.cwd,
|
|
92
|
+
timeout: params.timeout,
|
|
93
|
+
}, 'terminal');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
97
|
+
|
|
98
|
+
const command = params.command as string;
|
|
99
|
+
const cwd = (params.cwd as string) || undefined;
|
|
100
|
+
const timeout = (params.timeout as number) || undefined;
|
|
101
|
+
|
|
102
|
+
const result = await terminal.execute(command, { cwd, timeout });
|
|
103
|
+
|
|
104
|
+
let output = '';
|
|
105
|
+
if (result.stdout) output += result.stdout;
|
|
106
|
+
if (result.stderr) output += (output ? '\n' : '') + `[stderr] ${result.stderr}`;
|
|
107
|
+
if (result.exitCode !== 0) output += `\n[exit code: ${result.exitCode}]`;
|
|
108
|
+
|
|
109
|
+
// Truncate very large outputs
|
|
110
|
+
if (output.length > 10000) {
|
|
111
|
+
output = output.slice(0, 10000) + '\n... [truncated, output was ' + output.length + ' chars]';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return output || '[no output]';
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const readFileTool: ToolDefinition = {
|
|
119
|
+
name: 'read_file',
|
|
120
|
+
description: 'Read the contents of a file from disk. Returns the file content as text. Optionally specify a "target" sidecar to read from a remote machine.',
|
|
121
|
+
category: 'file-ops',
|
|
122
|
+
parameters: {
|
|
123
|
+
path: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
description: 'The absolute or relative path to the file to read',
|
|
126
|
+
required: true,
|
|
127
|
+
},
|
|
128
|
+
target: {
|
|
129
|
+
type: 'string',
|
|
130
|
+
description: 'Sidecar name or ID to read from a remote machine (omit for local)',
|
|
131
|
+
required: false,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
execute: async (params) => {
|
|
135
|
+
const target = params.target as string | undefined;
|
|
136
|
+
if (target) {
|
|
137
|
+
return routeToSidecar(target, 'read_file', { path: params.path }, 'filesystem');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
141
|
+
|
|
142
|
+
const filePath = resolve(params.path as string);
|
|
143
|
+
|
|
144
|
+
if (!existsSync(filePath)) {
|
|
145
|
+
return `Error: File not found: ${filePath}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const stat = statSync(filePath);
|
|
149
|
+
if (stat.isDirectory()) {
|
|
150
|
+
return `Error: Path is a directory, not a file: ${filePath}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Limit file size to 100KB
|
|
154
|
+
if (stat.size > 100 * 1024) {
|
|
155
|
+
const content = readFileSync(filePath, 'utf-8').slice(0, 100 * 1024);
|
|
156
|
+
return content + '\n... [truncated, file is ' + stat.size + ' bytes]';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return readFileSync(filePath, 'utf-8');
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const writeFileTool: ToolDefinition = {
|
|
164
|
+
name: 'write_file',
|
|
165
|
+
description: 'Write content to a file on disk. Creates the file if it does not exist, overwrites if it does. Optionally specify a "target" sidecar to write on a remote machine.',
|
|
166
|
+
category: 'file-ops',
|
|
167
|
+
parameters: {
|
|
168
|
+
path: {
|
|
169
|
+
type: 'string',
|
|
170
|
+
description: 'The absolute or relative path to the file to write',
|
|
171
|
+
required: true,
|
|
172
|
+
},
|
|
173
|
+
content: {
|
|
174
|
+
type: 'string',
|
|
175
|
+
description: 'The content to write to the file',
|
|
176
|
+
required: true,
|
|
177
|
+
},
|
|
178
|
+
target: {
|
|
179
|
+
type: 'string',
|
|
180
|
+
description: 'Sidecar name or ID to write on a remote machine (omit for local)',
|
|
181
|
+
required: false,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
execute: async (params) => {
|
|
185
|
+
const target = params.target as string | undefined;
|
|
186
|
+
if (target) {
|
|
187
|
+
return routeToSidecar(target, 'write_file', { path: params.path, content: params.content }, 'filesystem');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
191
|
+
|
|
192
|
+
const filePath = resolve(params.path as string);
|
|
193
|
+
const content = params.content as string;
|
|
194
|
+
|
|
195
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
196
|
+
return `File written successfully: ${filePath} (${content.length} bytes)`;
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const listDirectoryTool: ToolDefinition = {
|
|
201
|
+
name: 'list_directory',
|
|
202
|
+
description: 'List the contents of a directory. Returns file and folder names with their types and sizes. Optionally specify a "target" sidecar to list on a remote machine.',
|
|
203
|
+
category: 'file-ops',
|
|
204
|
+
parameters: {
|
|
205
|
+
path: {
|
|
206
|
+
type: 'string',
|
|
207
|
+
description: 'The absolute or relative path to the directory to list',
|
|
208
|
+
required: true,
|
|
209
|
+
},
|
|
210
|
+
target: {
|
|
211
|
+
type: 'string',
|
|
212
|
+
description: 'Sidecar name or ID to list on a remote machine (omit for local)',
|
|
213
|
+
required: false,
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
execute: async (params) => {
|
|
217
|
+
const target = params.target as string | undefined;
|
|
218
|
+
if (target) {
|
|
219
|
+
return routeToSidecar(target, 'list_directory', { path: params.path }, 'filesystem');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
223
|
+
|
|
224
|
+
const dirPath = resolve(params.path as string);
|
|
225
|
+
|
|
226
|
+
if (!existsSync(dirPath)) {
|
|
227
|
+
return `Error: Directory not found: ${dirPath}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const stat = statSync(dirPath);
|
|
231
|
+
if (!stat.isDirectory()) {
|
|
232
|
+
return `Error: Path is a file, not a directory: ${dirPath}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const entries = readdirSync(dirPath);
|
|
236
|
+
const lines: string[] = [];
|
|
237
|
+
|
|
238
|
+
for (const entry of entries) {
|
|
239
|
+
try {
|
|
240
|
+
const entryPath = `${dirPath}/${entry}`;
|
|
241
|
+
const entryStat = statSync(entryPath);
|
|
242
|
+
const type = entryStat.isDirectory() ? 'dir' : 'file';
|
|
243
|
+
const size = entryStat.isDirectory() ? '' : ` (${entryStat.size} bytes)`;
|
|
244
|
+
lines.push(`${type} ${entry}${size}`);
|
|
245
|
+
} catch {
|
|
246
|
+
lines.push(`??? ${entry}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (lines.length === 0) {
|
|
251
|
+
return `[empty directory: ${dirPath}]`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return lines.join('\n');
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// --- Clipboard / Screenshot / System Info helpers ---
|
|
259
|
+
|
|
260
|
+
function localClipboardRead(): string {
|
|
261
|
+
const os = platform();
|
|
262
|
+
if (os === 'darwin') {
|
|
263
|
+
return execSync('pbpaste', { encoding: 'utf-8' });
|
|
264
|
+
} else if (os === 'win32') {
|
|
265
|
+
return execSync('powershell -command Get-Clipboard', { encoding: 'utf-8' }).trimEnd();
|
|
266
|
+
} else {
|
|
267
|
+
try {
|
|
268
|
+
return execSync('xclip -selection clipboard -o', { encoding: 'utf-8' });
|
|
269
|
+
} catch {
|
|
270
|
+
return execSync('xsel --clipboard --output', { encoding: 'utf-8' });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function localClipboardWrite(content: string): void {
|
|
276
|
+
const os = platform();
|
|
277
|
+
if (os === 'darwin') {
|
|
278
|
+
execSync('pbcopy', { input: content, encoding: 'utf-8' });
|
|
279
|
+
} else if (os === 'win32') {
|
|
280
|
+
execSync('powershell -command Set-Clipboard', { input: content, encoding: 'utf-8' });
|
|
281
|
+
} else {
|
|
282
|
+
try {
|
|
283
|
+
execSync('xclip -selection clipboard', { input: content, encoding: 'utf-8' });
|
|
284
|
+
} catch {
|
|
285
|
+
execSync('xsel --clipboard --input', { input: content, encoding: 'utf-8' });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function localCaptureScreen(): string {
|
|
291
|
+
const os = platform();
|
|
292
|
+
const tmp = `/tmp/jarvis-screenshot-${Date.now()}.png`;
|
|
293
|
+
if (os === 'darwin') {
|
|
294
|
+
execSync(`screencapture -x ${tmp}`);
|
|
295
|
+
} else if (os === 'win32') {
|
|
296
|
+
execSync(`powershell -command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::PrimaryScreen | ForEach-Object { $bmp = New-Object System.Drawing.Bitmap($_.Bounds.Width, $_.Bounds.Height); $g = [System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen($_.Bounds.Location, [System.Drawing.Point]::Empty, $_.Bounds.Size); $bmp.Save('${tmp}') }"`);
|
|
297
|
+
} else {
|
|
298
|
+
try {
|
|
299
|
+
execSync(`scrot ${tmp}`);
|
|
300
|
+
} catch {
|
|
301
|
+
execSync(`import -window root ${tmp}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const data = readFileSync(tmp);
|
|
305
|
+
unlinkSync(tmp);
|
|
306
|
+
return data.toString('base64');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function localSystemInfo(): Record<string, unknown> {
|
|
310
|
+
return {
|
|
311
|
+
hostname: hostname(),
|
|
312
|
+
os: platform(),
|
|
313
|
+
arch: arch(),
|
|
314
|
+
cpus: cpus().length,
|
|
315
|
+
node_version: version(),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// --- Clipboard / Screenshot / System Info tools ---
|
|
320
|
+
|
|
321
|
+
export const getClipboardTool: ToolDefinition = {
|
|
322
|
+
name: 'get_clipboard',
|
|
323
|
+
description: 'Read the clipboard contents. Optionally specify a "target" sidecar name/ID to read from a remote machine instead of locally.',
|
|
324
|
+
category: 'general',
|
|
325
|
+
parameters: {
|
|
326
|
+
target: {
|
|
327
|
+
type: 'string',
|
|
328
|
+
description: 'Sidecar name or ID for remote execution (omit for local)',
|
|
329
|
+
required: false,
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
execute: async (params) => {
|
|
333
|
+
const target = params.target as string | undefined;
|
|
334
|
+
if (target) return routeToSidecar(target, 'get_clipboard', {}, 'clipboard');
|
|
335
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
336
|
+
try {
|
|
337
|
+
const content = localClipboardRead();
|
|
338
|
+
return content || '[clipboard is empty]';
|
|
339
|
+
} catch (err) {
|
|
340
|
+
return `Error reading clipboard: ${err instanceof Error ? err.message : err}`;
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
export const setClipboardTool: ToolDefinition = {
|
|
346
|
+
name: 'set_clipboard',
|
|
347
|
+
description: 'Write text to the clipboard. Optionally specify a "target" sidecar name/ID to write to a remote machine instead of locally.',
|
|
348
|
+
category: 'general',
|
|
349
|
+
parameters: {
|
|
350
|
+
content: {
|
|
351
|
+
type: 'string',
|
|
352
|
+
description: 'The text to write to the clipboard',
|
|
353
|
+
required: true,
|
|
354
|
+
},
|
|
355
|
+
target: {
|
|
356
|
+
type: 'string',
|
|
357
|
+
description: 'Sidecar name or ID for remote execution (omit for local)',
|
|
358
|
+
required: false,
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
execute: async (params) => {
|
|
362
|
+
const target = params.target as string | undefined;
|
|
363
|
+
if (target) return routeToSidecar(target, 'set_clipboard', { content: params.content }, 'clipboard');
|
|
364
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
365
|
+
try {
|
|
366
|
+
localClipboardWrite(params.content as string);
|
|
367
|
+
return 'Clipboard updated.';
|
|
368
|
+
} catch (err) {
|
|
369
|
+
return `Error writing clipboard: ${err instanceof Error ? err.message : err}`;
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
export const captureScreenTool: ToolDefinition = {
|
|
375
|
+
name: 'capture_screen',
|
|
376
|
+
description: 'Take a screenshot of the screen. Optionally specify a "target" sidecar name/ID to capture a remote machine instead of locally.',
|
|
377
|
+
category: 'general',
|
|
378
|
+
parameters: {
|
|
379
|
+
target: {
|
|
380
|
+
type: 'string',
|
|
381
|
+
description: 'Sidecar name or ID for remote execution (omit for local)',
|
|
382
|
+
required: false,
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
execute: async (params) => {
|
|
386
|
+
const target = params.target as string | undefined;
|
|
387
|
+
if (target) return routeToSidecar(target, 'capture_screen', {}, 'screenshot');
|
|
388
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
389
|
+
try {
|
|
390
|
+
const base64 = localCaptureScreen();
|
|
391
|
+
return JSON.stringify({ type: 'inline', mime_type: 'image/png', data: base64 });
|
|
392
|
+
} catch (err) {
|
|
393
|
+
return `Error capturing screen: ${err instanceof Error ? err.message : err}`;
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
export const getSystemInfoTool: ToolDefinition = {
|
|
399
|
+
name: 'get_system_info',
|
|
400
|
+
description: 'Get system information (hostname, OS, architecture, CPU count). Optionally specify a "target" sidecar name/ID to query a remote machine instead of locally.',
|
|
401
|
+
category: 'general',
|
|
402
|
+
parameters: {
|
|
403
|
+
target: {
|
|
404
|
+
type: 'string',
|
|
405
|
+
description: 'Sidecar name or ID for remote execution (omit for local)',
|
|
406
|
+
required: false,
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
execute: async (params) => {
|
|
410
|
+
const target = params.target as string | undefined;
|
|
411
|
+
if (target) return routeToSidecar(target, 'get_system_info', {}, 'system_info');
|
|
412
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
413
|
+
return JSON.stringify(localSystemInfo(), null, 2);
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// --- Browser Tool Helpers ---
|
|
418
|
+
|
|
419
|
+
const MAX_PAGE_TEXT = 2000; // chars of visible page text
|
|
420
|
+
const MAX_ELEMENTS = 50; // interactive elements shown to LLM
|
|
421
|
+
|
|
422
|
+
function formatSnapshot(snap: PageSnapshot): string {
|
|
423
|
+
const lines: string[] = [];
|
|
424
|
+
lines.push(`Page: ${snap.title}`);
|
|
425
|
+
lines.push(`URL: ${snap.url}`);
|
|
426
|
+
lines.push('');
|
|
427
|
+
lines.push('--- Page Text ---');
|
|
428
|
+
lines.push(snap.text.slice(0, MAX_PAGE_TEXT));
|
|
429
|
+
if (snap.text.length > MAX_PAGE_TEXT) {
|
|
430
|
+
lines.push(`... (${snap.text.length - MAX_PAGE_TEXT} chars truncated)`);
|
|
431
|
+
}
|
|
432
|
+
lines.push('');
|
|
433
|
+
|
|
434
|
+
if (snap.elements.length > 0) {
|
|
435
|
+
const shown = snap.elements.slice(0, MAX_ELEMENTS);
|
|
436
|
+
lines.push(`--- Interactive Elements (${shown.length}/${snap.elements.length}) ---`);
|
|
437
|
+
for (const el of shown) {
|
|
438
|
+
const attrParts: string[] = [];
|
|
439
|
+
if (el.attrs.name) attrParts.push(`name="${el.attrs.name}"`);
|
|
440
|
+
if (el.attrs.placeholder) attrParts.push(`placeholder="${el.attrs.placeholder}"`);
|
|
441
|
+
if (el.attrs.type) attrParts.push(`type="${el.attrs.type}"`);
|
|
442
|
+
if (el.attrs.href) attrParts.push(`href="${el.attrs.href.slice(0, 80)}"`);
|
|
443
|
+
if (el.attrs['aria-label']) attrParts.push(`aria-label="${el.attrs['aria-label']}"`);
|
|
444
|
+
if (el.attrs.role) attrParts.push(`role="${el.attrs.role}"`);
|
|
445
|
+
|
|
446
|
+
const textStr = el.text ? ` "${el.text.slice(0, 50)}"` : '';
|
|
447
|
+
const attrStr = attrParts.length > 0 ? ' ' + attrParts.join(' ') : '';
|
|
448
|
+
lines.push(`[${el.id}] ${el.tag}${textStr}${attrStr}`);
|
|
449
|
+
}
|
|
450
|
+
if (snap.elements.length > MAX_ELEMENTS) {
|
|
451
|
+
lines.push(`... (${snap.elements.length - MAX_ELEMENTS} more elements not shown)`);
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
lines.push('(no interactive elements found)');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return lines.join('\n');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// --- Browser Tool Implementations ---
|
|
461
|
+
|
|
462
|
+
export const browserNavigateTool: ToolDefinition = {
|
|
463
|
+
name: 'browser_navigate',
|
|
464
|
+
description: 'Navigate the browser to a URL. Returns page text content and a list of interactive elements with [id] numbers you can reference in browser_click and browser_type. Optionally specify a "target" sidecar to use a remote browser.',
|
|
465
|
+
category: 'browser',
|
|
466
|
+
parameters: {
|
|
467
|
+
url: {
|
|
468
|
+
type: 'string',
|
|
469
|
+
description: 'The URL to navigate to',
|
|
470
|
+
required: true,
|
|
471
|
+
},
|
|
472
|
+
target: {
|
|
473
|
+
type: 'string',
|
|
474
|
+
description: 'Sidecar name or ID to use a remote browser (omit for local)',
|
|
475
|
+
required: false,
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
execute: async (params) => {
|
|
479
|
+
const target = params.target as string | undefined;
|
|
480
|
+
if (target) {
|
|
481
|
+
return routeToSidecar(target, 'browser_navigate', { url: params.url }, 'browser');
|
|
482
|
+
}
|
|
483
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
484
|
+
try {
|
|
485
|
+
const snap = await browser.navigate(params.url as string);
|
|
486
|
+
return formatSnapshot(snap);
|
|
487
|
+
} catch (err) {
|
|
488
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
export const browserSnapshotTool: ToolDefinition = {
|
|
494
|
+
name: 'browser_snapshot',
|
|
495
|
+
description: 'Get the current page content and interactive elements. Each element has an [id] you can use with browser_click and browser_type. Use this after clicking or typing to see what changed.',
|
|
496
|
+
category: 'browser',
|
|
497
|
+
parameters: {
|
|
498
|
+
target: {
|
|
499
|
+
type: 'string',
|
|
500
|
+
description: 'Sidecar name or ID to use a remote browser (omit for local)',
|
|
501
|
+
required: false,
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
execute: async (params) => {
|
|
505
|
+
const target = params.target as string | undefined;
|
|
506
|
+
if (target) {
|
|
507
|
+
return routeToSidecar(target, 'browser_snapshot', {}, 'browser');
|
|
508
|
+
}
|
|
509
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
510
|
+
try {
|
|
511
|
+
const snap = await browser.snapshot();
|
|
512
|
+
return formatSnapshot(snap);
|
|
513
|
+
} catch (err) {
|
|
514
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
export const browserClickTool: ToolDefinition = {
|
|
520
|
+
name: 'browser_click',
|
|
521
|
+
description: 'Click an interactive element on the page by its [id] from the last browser_navigate or browser_snapshot.',
|
|
522
|
+
category: 'browser',
|
|
523
|
+
parameters: {
|
|
524
|
+
element_id: {
|
|
525
|
+
type: 'number',
|
|
526
|
+
description: 'The [id] of the element to click (from browser_snapshot)',
|
|
527
|
+
required: true,
|
|
528
|
+
},
|
|
529
|
+
target: {
|
|
530
|
+
type: 'string',
|
|
531
|
+
description: 'Sidecar name or ID to use a remote browser (omit for local)',
|
|
532
|
+
required: false,
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
execute: async (params) => {
|
|
536
|
+
const target = params.target as string | undefined;
|
|
537
|
+
if (target) {
|
|
538
|
+
return routeToSidecar(target, 'browser_click', { element_id: params.element_id }, 'browser');
|
|
539
|
+
}
|
|
540
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
541
|
+
try {
|
|
542
|
+
return await browser.click(params.element_id as number);
|
|
543
|
+
} catch (err) {
|
|
544
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
export const browserTypeTool: ToolDefinition = {
|
|
550
|
+
name: 'browser_type',
|
|
551
|
+
description: 'Type text into an input element by its [id]. Set submit to true to press Enter after typing (useful for search forms).',
|
|
552
|
+
category: 'browser',
|
|
553
|
+
parameters: {
|
|
554
|
+
element_id: {
|
|
555
|
+
type: 'number',
|
|
556
|
+
description: 'The [id] of the input element to type into (from browser_snapshot)',
|
|
557
|
+
required: true,
|
|
558
|
+
},
|
|
559
|
+
text: {
|
|
560
|
+
type: 'string',
|
|
561
|
+
description: 'The text to type',
|
|
562
|
+
required: true,
|
|
563
|
+
},
|
|
564
|
+
submit: {
|
|
565
|
+
type: 'boolean',
|
|
566
|
+
description: 'Press Enter after typing (default: false)',
|
|
567
|
+
required: false,
|
|
568
|
+
},
|
|
569
|
+
target: {
|
|
570
|
+
type: 'string',
|
|
571
|
+
description: 'Sidecar name or ID to use a remote browser (omit for local)',
|
|
572
|
+
required: false,
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
execute: async (params) => {
|
|
576
|
+
const target = params.target as string | undefined;
|
|
577
|
+
if (target) {
|
|
578
|
+
return routeToSidecar(target, 'browser_type', {
|
|
579
|
+
element_id: params.element_id,
|
|
580
|
+
text: params.text,
|
|
581
|
+
submit: params.submit,
|
|
582
|
+
}, 'browser');
|
|
583
|
+
}
|
|
584
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
585
|
+
try {
|
|
586
|
+
return await browser.type(
|
|
587
|
+
params.element_id as number,
|
|
588
|
+
params.text as string,
|
|
589
|
+
(params.submit as boolean) ?? false,
|
|
590
|
+
);
|
|
591
|
+
} catch (err) {
|
|
592
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
export const browserScreenshotTool: ToolDefinition = {
|
|
598
|
+
name: 'browser_screenshot',
|
|
599
|
+
description: 'Take a screenshot of the current browser page. The image is sent directly to the AI for visual analysis.',
|
|
600
|
+
category: 'browser',
|
|
601
|
+
parameters: {
|
|
602
|
+
target: {
|
|
603
|
+
type: 'string',
|
|
604
|
+
description: 'Sidecar name or ID to use a remote browser (omit for local)',
|
|
605
|
+
required: false,
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
execute: async (params) => {
|
|
609
|
+
const target = params.target as string | undefined;
|
|
610
|
+
if (target) {
|
|
611
|
+
return routeToSidecar(target, 'browser_screenshot', {}, 'browser');
|
|
612
|
+
}
|
|
613
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
614
|
+
try {
|
|
615
|
+
const { base64, mimeType } = await browser.screenshotBuffer();
|
|
616
|
+
return {
|
|
617
|
+
content: [
|
|
618
|
+
{ type: 'text' as const, text: 'Browser screenshot captured.' },
|
|
619
|
+
{ type: 'image' as const, source: { type: 'base64' as const, media_type: mimeType, data: base64 } },
|
|
620
|
+
],
|
|
621
|
+
} satisfies ToolResult;
|
|
622
|
+
} catch (err) {
|
|
623
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
export const browserScrollTool: ToolDefinition = {
|
|
629
|
+
name: 'browser_scroll',
|
|
630
|
+
description: 'Scroll the page up or down. Use this when you need to see content below the fold. After scrolling, use browser_snapshot to see the new content.',
|
|
631
|
+
category: 'browser',
|
|
632
|
+
parameters: {
|
|
633
|
+
direction: {
|
|
634
|
+
type: 'string',
|
|
635
|
+
description: 'Scroll direction: "down" or "up" (default: "down")',
|
|
636
|
+
required: false,
|
|
637
|
+
},
|
|
638
|
+
amount: {
|
|
639
|
+
type: 'number',
|
|
640
|
+
description: 'Pixels to scroll (default: one viewport height). Use larger values like 2000 to jump further.',
|
|
641
|
+
required: false,
|
|
642
|
+
},
|
|
643
|
+
target: {
|
|
644
|
+
type: 'string',
|
|
645
|
+
description: 'Sidecar name or ID to use a remote browser (omit for local)',
|
|
646
|
+
required: false,
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
execute: async (params) => {
|
|
650
|
+
const target = params.target as string | undefined;
|
|
651
|
+
if (target) {
|
|
652
|
+
return routeToSidecar(target, 'browser_scroll', {
|
|
653
|
+
direction: params.direction,
|
|
654
|
+
amount: params.amount,
|
|
655
|
+
}, 'browser');
|
|
656
|
+
}
|
|
657
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
658
|
+
try {
|
|
659
|
+
const direction = (params.direction as string) === 'up' ? 'up' : 'down';
|
|
660
|
+
const amount = params.amount as number | undefined;
|
|
661
|
+
return await browser.scroll(direction, amount);
|
|
662
|
+
} catch (err) {
|
|
663
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
export const browserEvaluateTool: ToolDefinition = {
|
|
669
|
+
name: 'browser_evaluate',
|
|
670
|
+
description: 'Execute JavaScript in the browser page context. Use this for advanced interactions when the standard tools are not enough.',
|
|
671
|
+
category: 'browser',
|
|
672
|
+
parameters: {
|
|
673
|
+
expression: {
|
|
674
|
+
type: 'string',
|
|
675
|
+
description: 'JavaScript expression to evaluate in the page. For complex operations, wrap in an IIFE: (() => { ... })()',
|
|
676
|
+
required: true,
|
|
677
|
+
},
|
|
678
|
+
target: {
|
|
679
|
+
type: 'string',
|
|
680
|
+
description: 'Sidecar name or ID to use a remote browser (omit for local)',
|
|
681
|
+
required: false,
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
execute: async (params) => {
|
|
685
|
+
const target = params.target as string | undefined;
|
|
686
|
+
if (target) {
|
|
687
|
+
return routeToSidecar(target, 'browser_evaluate', { expression: params.expression }, 'browser');
|
|
688
|
+
}
|
|
689
|
+
if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
|
|
690
|
+
try {
|
|
691
|
+
const result = await browser.evaluate(params.expression as string);
|
|
692
|
+
if (result === undefined || result === null) return '(no return value)';
|
|
693
|
+
return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
694
|
+
} catch (err) {
|
|
695
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Non-browser tools (terminal, file operations).
|
|
702
|
+
* Safe to share across multiple agent services — they are stateless.
|
|
703
|
+
*/
|
|
704
|
+
export const NON_BROWSER_TOOLS: ToolDefinition[] = [
|
|
705
|
+
runCommandTool,
|
|
706
|
+
readFileTool,
|
|
707
|
+
writeFileTool,
|
|
708
|
+
listDirectoryTool,
|
|
709
|
+
getClipboardTool,
|
|
710
|
+
setClipboardTool,
|
|
711
|
+
captureScreenTool,
|
|
712
|
+
getSystemInfoTool,
|
|
713
|
+
listSidecarsTool,
|
|
714
|
+
];
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* All built-in tools.
|
|
718
|
+
*/
|
|
719
|
+
export const BUILTIN_TOOLS: ToolDefinition[] = [
|
|
720
|
+
...NON_BROWSER_TOOLS,
|
|
721
|
+
browserNavigateTool,
|
|
722
|
+
browserSnapshotTool,
|
|
723
|
+
browserClickTool,
|
|
724
|
+
browserTypeTool,
|
|
725
|
+
browserScrollTool,
|
|
726
|
+
browserEvaluateTool,
|
|
727
|
+
browserScreenshotTool,
|
|
728
|
+
...DESKTOP_TOOLS,
|
|
729
|
+
];
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Create browser tools bound to a specific BrowserController.
|
|
733
|
+
* Used to give the background agent its own browser instance
|
|
734
|
+
* while keeping tool definitions identical to the main agent's.
|
|
735
|
+
*/
|
|
736
|
+
export function createBrowserTools(ctrl: BrowserController): ToolDefinition[] {
|
|
737
|
+
return [
|
|
738
|
+
{
|
|
739
|
+
name: 'browser_navigate',
|
|
740
|
+
description: browserNavigateTool.description,
|
|
741
|
+
category: 'browser',
|
|
742
|
+
parameters: browserNavigateTool.parameters,
|
|
743
|
+
execute: async (params) => {
|
|
744
|
+
try {
|
|
745
|
+
const snap = await ctrl.navigate(params.url as string);
|
|
746
|
+
return formatSnapshot(snap);
|
|
747
|
+
} catch (err) {
|
|
748
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
name: 'browser_snapshot',
|
|
754
|
+
description: browserSnapshotTool.description,
|
|
755
|
+
category: 'browser',
|
|
756
|
+
parameters: browserSnapshotTool.parameters,
|
|
757
|
+
execute: async () => {
|
|
758
|
+
try {
|
|
759
|
+
const snap = await ctrl.snapshot();
|
|
760
|
+
return formatSnapshot(snap);
|
|
761
|
+
} catch (err) {
|
|
762
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
763
|
+
}
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
name: 'browser_click',
|
|
768
|
+
description: browserClickTool.description,
|
|
769
|
+
category: 'browser',
|
|
770
|
+
parameters: browserClickTool.parameters,
|
|
771
|
+
execute: async (params) => {
|
|
772
|
+
try {
|
|
773
|
+
return await ctrl.click(params.element_id as number);
|
|
774
|
+
} catch (err) {
|
|
775
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
776
|
+
}
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
name: 'browser_type',
|
|
781
|
+
description: browserTypeTool.description,
|
|
782
|
+
category: 'browser',
|
|
783
|
+
parameters: browserTypeTool.parameters,
|
|
784
|
+
execute: async (params) => {
|
|
785
|
+
try {
|
|
786
|
+
return await ctrl.type(
|
|
787
|
+
params.element_id as number,
|
|
788
|
+
params.text as string,
|
|
789
|
+
(params.submit as boolean) ?? false,
|
|
790
|
+
);
|
|
791
|
+
} catch (err) {
|
|
792
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
name: 'browser_scroll',
|
|
798
|
+
description: browserScrollTool.description,
|
|
799
|
+
category: 'browser',
|
|
800
|
+
parameters: browserScrollTool.parameters,
|
|
801
|
+
execute: async (params) => {
|
|
802
|
+
try {
|
|
803
|
+
const direction = (params.direction as string) === 'up' ? 'up' : 'down';
|
|
804
|
+
const amount = params.amount as number | undefined;
|
|
805
|
+
return await ctrl.scroll(direction, amount);
|
|
806
|
+
} catch (err) {
|
|
807
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
name: 'browser_evaluate',
|
|
813
|
+
description: browserEvaluateTool.description,
|
|
814
|
+
category: 'browser',
|
|
815
|
+
parameters: browserEvaluateTool.parameters,
|
|
816
|
+
execute: async (params) => {
|
|
817
|
+
try {
|
|
818
|
+
const result = await ctrl.evaluate(params.expression as string);
|
|
819
|
+
if (result === undefined || result === null) return '(no return value)';
|
|
820
|
+
return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
821
|
+
} catch (err) {
|
|
822
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
name: 'browser_screenshot',
|
|
828
|
+
description: browserScreenshotTool.description,
|
|
829
|
+
category: 'browser',
|
|
830
|
+
parameters: browserScreenshotTool.parameters,
|
|
831
|
+
execute: async () => {
|
|
832
|
+
try {
|
|
833
|
+
const { base64, mimeType } = await ctrl.screenshotBuffer();
|
|
834
|
+
return {
|
|
835
|
+
content: [
|
|
836
|
+
{ type: 'text' as const, text: 'Browser screenshot captured.' },
|
|
837
|
+
{ type: 'image' as const, source: { type: 'base64' as const, media_type: mimeType, data: base64 } },
|
|
838
|
+
],
|
|
839
|
+
} satisfies ToolResult;
|
|
840
|
+
} catch (err) {
|
|
841
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
842
|
+
}
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
];
|
|
846
|
+
}
|