@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,126 @@
|
|
|
1
|
+
import { TerminalExecutor, type CommandResult } from './executor.ts';
|
|
2
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
export class WSLBridge {
|
|
5
|
+
private executor: TerminalExecutor;
|
|
6
|
+
private windowsHome: string | null = null;
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
this.executor = new TerminalExecutor();
|
|
10
|
+
|
|
11
|
+
if (WSLBridge.isWSL()) {
|
|
12
|
+
this.detectWindowsHome();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static isWSL(): boolean {
|
|
17
|
+
try {
|
|
18
|
+
if (process.platform !== 'linux') {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (existsSync('/proc/version')) {
|
|
23
|
+
const version = readFileSync('/proc/version', 'utf-8').toLowerCase();
|
|
24
|
+
return version.includes('microsoft') || version.includes('wsl');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return false;
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async runWindowsCommand(command: string): Promise<CommandResult> {
|
|
38
|
+
if (!WSLBridge.isWSL()) {
|
|
39
|
+
throw new Error('Not running in WSL environment');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
return await this.executor.execute(`cmd.exe /C "${command.replace(/"/g, '\\"')}"`);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new Error(`Failed to run Windows command: ${error instanceof Error ? error.message : String(error)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async runPowerShell(script: string): Promise<CommandResult> {
|
|
50
|
+
if (!WSLBridge.isWSL()) {
|
|
51
|
+
throw new Error('Not running in WSL environment');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const escapedScript = script
|
|
56
|
+
.replace(/\\/g, '\\\\')
|
|
57
|
+
.replace(/"/g, '\\"')
|
|
58
|
+
.replace(/\$/g, '\\$')
|
|
59
|
+
.replace(/`/g, '\\`');
|
|
60
|
+
|
|
61
|
+
return await this.executor.execute(`powershell.exe -Command "${escapedScript}"`);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
throw new Error(`Failed to run PowerShell script: ${error instanceof Error ? error.message : String(error)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getWindowsHome(): string | null {
|
|
68
|
+
return this.windowsHome;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private detectWindowsHome(): void {
|
|
72
|
+
try {
|
|
73
|
+
const result = this.executor.execute('cmd.exe /C "echo %USERPROFILE%"');
|
|
74
|
+
result.then(res => {
|
|
75
|
+
const path = res.stdout.trim();
|
|
76
|
+
|
|
77
|
+
if (path && !path.includes('%')) {
|
|
78
|
+
this.windowsHome = this.convertWindowsPath(path);
|
|
79
|
+
}
|
|
80
|
+
}).catch(() => {
|
|
81
|
+
this.windowsHome = null;
|
|
82
|
+
});
|
|
83
|
+
} catch {
|
|
84
|
+
this.windowsHome = null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private convertWindowsPath(windowsPath: string): string {
|
|
89
|
+
const normalized = windowsPath.replace(/\\/g, '/');
|
|
90
|
+
|
|
91
|
+
const driveMatch = normalized.match(/^([A-Z]):/i);
|
|
92
|
+
if (driveMatch) {
|
|
93
|
+
const drive = driveMatch[1]?.toLowerCase();
|
|
94
|
+
const rest = normalized.slice(2);
|
|
95
|
+
return `/mnt/${drive}${rest}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async convertToWindowsPath(wslPath: string): Promise<string> {
|
|
102
|
+
if (!WSLBridge.isWSL()) {
|
|
103
|
+
throw new Error('Not running in WSL environment');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const result = await this.executor.execute(`wslpath -w "${wslPath}"`);
|
|
108
|
+
return result.stdout.trim();
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new Error(`Failed to convert WSL path: ${error instanceof Error ? error.message : String(error)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async convertToWSLPath(windowsPath: string): Promise<string> {
|
|
115
|
+
if (!WSLBridge.isWSL()) {
|
|
116
|
+
throw new Error('Not running in WSL environment');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const result = await this.executor.execute(`wslpath -u "${windowsPath}"`);
|
|
121
|
+
return result.stdout.trim();
|
|
122
|
+
} catch (error) {
|
|
123
|
+
throw new Error(`Failed to convert Windows path: ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getAppController,
|
|
5
|
+
TerminalExecutor,
|
|
6
|
+
WSLBridge,
|
|
7
|
+
BrowserSession,
|
|
8
|
+
CDPBrowser,
|
|
9
|
+
ToolRegistry,
|
|
10
|
+
type ToolDefinition,
|
|
11
|
+
} from './index.ts';
|
|
12
|
+
|
|
13
|
+
async function testActionLayer() {
|
|
14
|
+
console.log('Testing J.A.R.V.I.S. Action Layer\n');
|
|
15
|
+
|
|
16
|
+
console.log('1. App Controller');
|
|
17
|
+
console.log(` Platform: ${process.platform}`);
|
|
18
|
+
try {
|
|
19
|
+
const appController = getAppController();
|
|
20
|
+
console.log(` ✓ App controller initialized for ${process.platform}`);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.log(` ⚠ App controller not available: ${error instanceof Error ? error.message.split('\n')[0] : String(error)}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log('\n2. Terminal Executor');
|
|
26
|
+
const executor = new TerminalExecutor();
|
|
27
|
+
console.log(` Detected shell: ${executor.getShell()}`);
|
|
28
|
+
try {
|
|
29
|
+
const result = await executor.execute('echo "Hello from JARVIS"');
|
|
30
|
+
console.log(` ✓ Command executed: ${result.stdout.trim()}`);
|
|
31
|
+
console.log(` Duration: ${result.duration}ms`);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.log(` ✗ Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log('\n3. WSL Bridge');
|
|
37
|
+
const isWSL = WSLBridge.isWSL();
|
|
38
|
+
console.log(` Running in WSL: ${isWSL}`);
|
|
39
|
+
if (isWSL) {
|
|
40
|
+
const bridge = new WSLBridge();
|
|
41
|
+
console.log(` Windows home: ${bridge.getWindowsHome() ?? 'Not detected'}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log('\n4. Browser Session');
|
|
45
|
+
const browserSession = new BrowserSession(9222);
|
|
46
|
+
const isAvailable = await browserSession.isAvailable();
|
|
47
|
+
console.log(` Chrome DevTools available: ${isAvailable}`);
|
|
48
|
+
if (isAvailable) {
|
|
49
|
+
try {
|
|
50
|
+
await (browserSession as any).ensureConnected();
|
|
51
|
+
const browser = (browserSession as any).getBrowser();
|
|
52
|
+
const tabs = await browser.listTabs();
|
|
53
|
+
console.log(` ✓ Found ${tabs.length} browser tab(s)`);
|
|
54
|
+
await browserSession.disconnect();
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.log(` ⚠ Could not connect: ${error instanceof Error ? error.message.split('\n')[0] : String(error)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log('\n5. Tool Registry');
|
|
61
|
+
const registry = new ToolRegistry();
|
|
62
|
+
|
|
63
|
+
const exampleTool: ToolDefinition = {
|
|
64
|
+
name: 'echo',
|
|
65
|
+
description: 'Echo a message',
|
|
66
|
+
category: 'utility',
|
|
67
|
+
parameters: {
|
|
68
|
+
message: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: 'Message to echo',
|
|
71
|
+
required: true,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
execute: async (params) => {
|
|
75
|
+
return `Echo: ${params.message}`;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
registry.register(exampleTool);
|
|
80
|
+
console.log(` Registered tools: ${registry.count()}`);
|
|
81
|
+
console.log(` Categories: ${registry.getCategories().join(', ')}`);
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const result = await registry.execute('echo', { message: 'Hello JARVIS!' });
|
|
85
|
+
console.log(` ✓ Tool execution: ${result}`);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.log(` ✗ Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log('\n✓ Action Layer test complete!\n');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
testActionLayer().catch(console.error);
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manage Agents Tool — Multi-Agent Hierarchy
|
|
3
|
+
*
|
|
4
|
+
* Allows the primary agent to spawn persistent sub-agents, assign them tasks
|
|
5
|
+
* (async), check status, collect results, and terminate them.
|
|
6
|
+
*
|
|
7
|
+
* For quick sync delegation (spawn → run → return → terminate in one call),
|
|
8
|
+
* use delegate_task instead.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { AgentOrchestrator } from '../../agents/orchestrator.ts';
|
|
12
|
+
import type { LLMManager } from '../../llm/manager.ts';
|
|
13
|
+
import type { RoleDefinition } from '../../roles/types.ts';
|
|
14
|
+
import type { ToolDefinition } from './registry.ts';
|
|
15
|
+
import type { AgentTaskManager } from '../../agents/task-manager.ts';
|
|
16
|
+
import { createScopedToolRegistry, type ProgressCallback } from '../../agents/sub-agent-runner.ts';
|
|
17
|
+
|
|
18
|
+
export type AgentToolDeps = {
|
|
19
|
+
orchestrator: AgentOrchestrator;
|
|
20
|
+
llmManager: LLMManager;
|
|
21
|
+
specialists: Map<string, RoleDefinition>;
|
|
22
|
+
taskManager: AgentTaskManager;
|
|
23
|
+
onProgress?: ProgressCallback;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Track scoped registries for persistent agents so they can be reused across tasks
|
|
27
|
+
const agentRegistries = new Map<string, ReturnType<typeof createScopedToolRegistry>>();
|
|
28
|
+
|
|
29
|
+
export function createManageAgentsTool(deps: AgentToolDeps): ToolDefinition {
|
|
30
|
+
return {
|
|
31
|
+
name: 'manage_agents',
|
|
32
|
+
description: [
|
|
33
|
+
'Manage persistent sub-agents for complex or parallel work.',
|
|
34
|
+
'Use this when you need agents that stay alive across multiple tasks or run in parallel.',
|
|
35
|
+
'For quick one-shot tasks, use delegate_task instead.',
|
|
36
|
+
'',
|
|
37
|
+
'Actions:',
|
|
38
|
+
' spawn — Create a persistent specialist agent (returns agent_id)',
|
|
39
|
+
' assign — Send a task to an existing agent (async, returns task_id)',
|
|
40
|
+
' status — Check agent or task status',
|
|
41
|
+
' collect — Get full result of a completed task',
|
|
42
|
+
' list — Show all active agents and tasks',
|
|
43
|
+
' terminate — Shut down an agent',
|
|
44
|
+
'',
|
|
45
|
+
'Available specialists: ' + Array.from(deps.specialists.keys()).join(', '),
|
|
46
|
+
'',
|
|
47
|
+
'Workflow: spawn → assign → status/collect → terminate',
|
|
48
|
+
].join('\n'),
|
|
49
|
+
category: 'delegation',
|
|
50
|
+
parameters: {
|
|
51
|
+
action: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'The action: spawn, assign, status, collect, list, terminate',
|
|
54
|
+
required: true,
|
|
55
|
+
},
|
|
56
|
+
specialist: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: 'Specialist role ID (required for spawn)',
|
|
59
|
+
required: false,
|
|
60
|
+
},
|
|
61
|
+
agent_id: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
description: 'Agent ID (required for assign, terminate; optional for status)',
|
|
64
|
+
required: false,
|
|
65
|
+
},
|
|
66
|
+
task_id: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
description: 'Task ID (for status, collect)',
|
|
69
|
+
required: false,
|
|
70
|
+
},
|
|
71
|
+
task: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
description: 'Task description (required for assign)',
|
|
74
|
+
required: false,
|
|
75
|
+
},
|
|
76
|
+
context: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: 'Background context for the task (optional for assign)',
|
|
79
|
+
required: false,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
execute: async (params) => {
|
|
83
|
+
const action = params.action as string;
|
|
84
|
+
|
|
85
|
+
switch (action) {
|
|
86
|
+
case 'spawn':
|
|
87
|
+
return handleSpawn(deps, params);
|
|
88
|
+
case 'assign':
|
|
89
|
+
return handleAssign(deps, params);
|
|
90
|
+
case 'status':
|
|
91
|
+
return handleStatus(deps, params);
|
|
92
|
+
case 'collect':
|
|
93
|
+
return handleCollect(deps, params);
|
|
94
|
+
case 'list':
|
|
95
|
+
return handleList(deps);
|
|
96
|
+
case 'terminate':
|
|
97
|
+
return handleTerminate(deps, params);
|
|
98
|
+
default:
|
|
99
|
+
return `Error: Unknown action "${action}". Use: spawn, assign, status, collect, list, terminate`;
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function handleSpawn(deps: AgentToolDeps, params: Record<string, unknown>): string {
|
|
106
|
+
const specialistId = params.specialist as string;
|
|
107
|
+
if (!specialistId) {
|
|
108
|
+
return 'Error: "specialist" is required for spawn. Available: ' + Array.from(deps.specialists.keys()).join(', ');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const role = deps.specialists.get(specialistId);
|
|
112
|
+
if (!role) {
|
|
113
|
+
return `Error: Unknown specialist "${specialistId}". Available: ${Array.from(deps.specialists.keys()).join(', ')}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const primary = deps.orchestrator.getPrimary();
|
|
117
|
+
if (!primary) {
|
|
118
|
+
return 'Error: No primary agent exists';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const agent = deps.orchestrator.spawnSubAgent(primary.id, role);
|
|
122
|
+
const scopedRegistry = createScopedToolRegistry(agent.agent.authority.allowed_tools);
|
|
123
|
+
agentRegistries.set(agent.id, scopedRegistry);
|
|
124
|
+
|
|
125
|
+
console.log(`[ManageAgents] Spawned ${role.name} (${agent.id}) with ${scopedRegistry.count()} tools`);
|
|
126
|
+
|
|
127
|
+
return JSON.stringify({
|
|
128
|
+
agent_id: agent.id,
|
|
129
|
+
name: role.name,
|
|
130
|
+
specialist: specialistId,
|
|
131
|
+
status: 'idle',
|
|
132
|
+
tools_available: scopedRegistry.count(),
|
|
133
|
+
tool_categories: agent.agent.authority.allowed_tools,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function handleAssign(deps: AgentToolDeps, params: Record<string, unknown>): Promise<string> {
|
|
138
|
+
const agentId = params.agent_id as string;
|
|
139
|
+
const task = params.task as string;
|
|
140
|
+
const context = (params.context as string) || '';
|
|
141
|
+
|
|
142
|
+
if (!agentId) return 'Error: "agent_id" is required for assign';
|
|
143
|
+
if (!task) return 'Error: "task" is required for assign';
|
|
144
|
+
|
|
145
|
+
const agent = deps.orchestrator.getAgent(agentId);
|
|
146
|
+
if (!agent) return `Error: Agent "${agentId}" not found. Use list to see active agents.`;
|
|
147
|
+
|
|
148
|
+
if (deps.taskManager.isAgentBusy(agentId)) {
|
|
149
|
+
return `Error: Agent "${agent.agent.role.name}" is already running a task. Wait for it to finish or terminate it.`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const scopedRegistry = agentRegistries.get(agentId);
|
|
153
|
+
if (!scopedRegistry) {
|
|
154
|
+
return `Error: No tool registry for agent "${agentId}". Was it spawned via manage_agents?`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Notify progress
|
|
158
|
+
deps.onProgress?.({
|
|
159
|
+
type: 'text',
|
|
160
|
+
agentName: agent.agent.role.name,
|
|
161
|
+
agentId,
|
|
162
|
+
data: `[Assigning task to ${agent.agent.role.name}...]`,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const taskId = deps.taskManager.launch({
|
|
166
|
+
agent,
|
|
167
|
+
task,
|
|
168
|
+
context,
|
|
169
|
+
llmManager: deps.llmManager,
|
|
170
|
+
toolRegistry: scopedRegistry,
|
|
171
|
+
onProgress: deps.onProgress,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
console.log(`[ManageAgents] Assigned task ${taskId} to ${agent.agent.role.name}`);
|
|
175
|
+
|
|
176
|
+
return JSON.stringify({
|
|
177
|
+
task_id: taskId,
|
|
178
|
+
agent_id: agentId,
|
|
179
|
+
agent_name: agent.agent.role.name,
|
|
180
|
+
status: 'running',
|
|
181
|
+
message: `Task assigned to ${agent.agent.role.name}. Use status or collect to check progress.`,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleStatus(deps: AgentToolDeps, params: Record<string, unknown>): string {
|
|
186
|
+
const taskId = params.task_id as string;
|
|
187
|
+
const agentId = params.agent_id as string;
|
|
188
|
+
|
|
189
|
+
if (taskId) {
|
|
190
|
+
const task = deps.taskManager.getTask(taskId);
|
|
191
|
+
if (!task) return `Error: Task "${taskId}" not found`;
|
|
192
|
+
|
|
193
|
+
const elapsed = task.completedAt
|
|
194
|
+
? task.completedAt - task.startedAt
|
|
195
|
+
: Date.now() - task.startedAt;
|
|
196
|
+
|
|
197
|
+
return JSON.stringify({
|
|
198
|
+
task_id: task.id,
|
|
199
|
+
agent_name: task.agentName,
|
|
200
|
+
status: task.status,
|
|
201
|
+
task: task.task,
|
|
202
|
+
elapsed_ms: elapsed,
|
|
203
|
+
elapsed_seconds: Math.round(elapsed / 1000),
|
|
204
|
+
has_result: task.result !== null,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (agentId) {
|
|
209
|
+
const agent = deps.orchestrator.getAgent(agentId);
|
|
210
|
+
if (!agent) return `Error: Agent "${agentId}" not found`;
|
|
211
|
+
|
|
212
|
+
const agentTask = deps.taskManager.getAgentTask(agentId);
|
|
213
|
+
return JSON.stringify({
|
|
214
|
+
agent_id: agentId,
|
|
215
|
+
name: agent.agent.role.name,
|
|
216
|
+
status: agent.agent.status,
|
|
217
|
+
current_task: agent.agent.current_task,
|
|
218
|
+
busy: deps.taskManager.isAgentBusy(agentId),
|
|
219
|
+
latest_task: agentTask ? {
|
|
220
|
+
task_id: agentTask.id,
|
|
221
|
+
status: agentTask.status,
|
|
222
|
+
task: agentTask.task,
|
|
223
|
+
} : null,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// No ID given — return summary
|
|
228
|
+
return handleList(deps);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function handleCollect(deps: AgentToolDeps, params: Record<string, unknown>): string {
|
|
232
|
+
const taskId = params.task_id as string;
|
|
233
|
+
if (!taskId) return 'Error: "task_id" is required for collect';
|
|
234
|
+
|
|
235
|
+
const task = deps.taskManager.getTask(taskId);
|
|
236
|
+
if (!task) return `Error: Task "${taskId}" not found`;
|
|
237
|
+
|
|
238
|
+
if (task.status === 'running') {
|
|
239
|
+
const elapsed = Math.round((Date.now() - task.startedAt) / 1000);
|
|
240
|
+
return JSON.stringify({
|
|
241
|
+
task_id: task.id,
|
|
242
|
+
status: 'running',
|
|
243
|
+
agent_name: task.agentName,
|
|
244
|
+
elapsed_seconds: elapsed,
|
|
245
|
+
message: 'Task is still running. Check back later.',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const result = task.result!;
|
|
250
|
+
const toolsList = result.toolsUsed.length > 0
|
|
251
|
+
? result.toolsUsed.join(', ')
|
|
252
|
+
: 'none';
|
|
253
|
+
|
|
254
|
+
return JSON.stringify({
|
|
255
|
+
task_id: task.id,
|
|
256
|
+
status: task.status,
|
|
257
|
+
agent_name: task.agentName,
|
|
258
|
+
success: result.success,
|
|
259
|
+
response: result.response,
|
|
260
|
+
tools_used: toolsList,
|
|
261
|
+
tokens_used: result.tokensUsed.input + result.tokensUsed.output,
|
|
262
|
+
elapsed_seconds: Math.round(((task.completedAt ?? Date.now()) - task.startedAt) / 1000),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function handleList(deps: AgentToolDeps): string {
|
|
267
|
+
const allAgents = deps.orchestrator.getAllAgents();
|
|
268
|
+
const primary = deps.orchestrator.getPrimary();
|
|
269
|
+
|
|
270
|
+
// Filter to sub-agents only (not the primary)
|
|
271
|
+
const subAgents = allAgents.filter(a => a.id !== primary?.id);
|
|
272
|
+
|
|
273
|
+
const agents = subAgents.map(a => ({
|
|
274
|
+
agent_id: a.id,
|
|
275
|
+
name: a.agent.role.name,
|
|
276
|
+
specialist: a.agent.role.id,
|
|
277
|
+
status: a.agent.status,
|
|
278
|
+
current_task: a.agent.current_task,
|
|
279
|
+
busy: deps.taskManager.isAgentBusy(a.id),
|
|
280
|
+
}));
|
|
281
|
+
|
|
282
|
+
const tasks = deps.taskManager.listTasks().map(t => ({
|
|
283
|
+
task_id: t.id,
|
|
284
|
+
agent_name: t.agentName,
|
|
285
|
+
status: t.status,
|
|
286
|
+
task: t.task.slice(0, 100),
|
|
287
|
+
elapsed_seconds: Math.round(((t.completedAt ?? Date.now()) - t.startedAt) / 1000),
|
|
288
|
+
}));
|
|
289
|
+
|
|
290
|
+
return JSON.stringify({
|
|
291
|
+
active_agents: agents.length,
|
|
292
|
+
agents,
|
|
293
|
+
tasks_total: tasks.length,
|
|
294
|
+
tasks_running: tasks.filter(t => t.status === 'running').length,
|
|
295
|
+
tasks,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function handleTerminate(deps: AgentToolDeps, params: Record<string, unknown>): string {
|
|
300
|
+
const agentId = params.agent_id as string;
|
|
301
|
+
if (!agentId) return 'Error: "agent_id" is required for terminate';
|
|
302
|
+
|
|
303
|
+
const agent = deps.orchestrator.getAgent(agentId);
|
|
304
|
+
if (!agent) return `Error: Agent "${agentId}" not found`;
|
|
305
|
+
|
|
306
|
+
const name = agent.agent.role.name;
|
|
307
|
+
|
|
308
|
+
// Clean up registry
|
|
309
|
+
agentRegistries.delete(agentId);
|
|
310
|
+
|
|
311
|
+
// Terminate via orchestrator (cascades to children)
|
|
312
|
+
deps.orchestrator.terminateAgent(agentId);
|
|
313
|
+
|
|
314
|
+
console.log(`[ManageAgents] Terminated ${name} (${agentId})`);
|
|
315
|
+
|
|
316
|
+
return JSON.stringify({
|
|
317
|
+
terminated: agentId,
|
|
318
|
+
name,
|
|
319
|
+
message: `${name} terminated.`,
|
|
320
|
+
});
|
|
321
|
+
}
|