@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,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commitments Tool
|
|
3
|
+
*
|
|
4
|
+
* Allows the agent to manage tasks/commitments:
|
|
5
|
+
* list, get, create, update status, set due date.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ToolDefinition } from './registry.ts';
|
|
9
|
+
import type { CommitmentPriority, CommitmentStatus } from '../../vault/commitments.ts';
|
|
10
|
+
import {
|
|
11
|
+
createCommitment, getCommitment, findCommitments,
|
|
12
|
+
updateCommitmentStatus, updateCommitmentDue, getUpcoming,
|
|
13
|
+
} from '../../vault/commitments.ts';
|
|
14
|
+
|
|
15
|
+
const VALID_STATUSES = ['pending', 'active', 'completed', 'failed', 'escalated'];
|
|
16
|
+
const VALID_PRIORITIES = ['low', 'normal', 'high', 'critical'];
|
|
17
|
+
|
|
18
|
+
export const commitmentsTool: ToolDefinition = {
|
|
19
|
+
name: 'commitments',
|
|
20
|
+
description: [
|
|
21
|
+
'Manage tasks and commitments. Use this to create, update, and track scheduled tasks.',
|
|
22
|
+
'',
|
|
23
|
+
'Actions:',
|
|
24
|
+
' list — List commitments, filtered by status/priority/assigned_to/overdue',
|
|
25
|
+
' get — Get a single commitment by ID',
|
|
26
|
+
' create — Create a new task (required: what. optional: when_due, priority, context, assigned_to)',
|
|
27
|
+
' update_status — Update a commitment\'s status (pending, active, completed, failed)',
|
|
28
|
+
' set_due — Set or clear a commitment\'s due date',
|
|
29
|
+
'',
|
|
30
|
+
'For when_due, use ISO 8601 format (e.g., "2026-02-28T14:00:00") or "null" to clear.',
|
|
31
|
+
'Priorities: low, normal, high, critical',
|
|
32
|
+
'Statuses: pending, active, completed, failed, escalated',
|
|
33
|
+
].join('\n'),
|
|
34
|
+
category: 'tasks',
|
|
35
|
+
parameters: {
|
|
36
|
+
action: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'The action: list, get, create, update_status, set_due',
|
|
39
|
+
required: true,
|
|
40
|
+
},
|
|
41
|
+
id: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
description: 'Commitment ID (required for get, update_status, set_due)',
|
|
44
|
+
required: false,
|
|
45
|
+
},
|
|
46
|
+
what: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Task description (required for create)',
|
|
49
|
+
required: false,
|
|
50
|
+
},
|
|
51
|
+
when_due: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Due date in ISO 8601 format (e.g., "2026-02-28T14:00:00"). Use "null" to clear.',
|
|
54
|
+
required: false,
|
|
55
|
+
},
|
|
56
|
+
priority: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: 'Priority: low, normal, high, critical',
|
|
59
|
+
required: false,
|
|
60
|
+
},
|
|
61
|
+
context: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
description: 'Additional context for the task',
|
|
64
|
+
required: false,
|
|
65
|
+
},
|
|
66
|
+
assigned_to: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
description: 'Who the task is assigned to (e.g., "jarvis", "user")',
|
|
69
|
+
required: false,
|
|
70
|
+
},
|
|
71
|
+
status: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
description: 'New status for update_status: pending, active, completed, failed',
|
|
74
|
+
required: false,
|
|
75
|
+
},
|
|
76
|
+
result: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: 'Result text (optional, for update_status when completing/failing)',
|
|
79
|
+
required: false,
|
|
80
|
+
},
|
|
81
|
+
filter_status: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'Filter by status (for list action)',
|
|
84
|
+
required: false,
|
|
85
|
+
},
|
|
86
|
+
filter_overdue: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
description: 'Set to "true" to list only overdue commitments',
|
|
89
|
+
required: false,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
execute: async (params) => {
|
|
93
|
+
const action = params.action as string;
|
|
94
|
+
|
|
95
|
+
switch (action) {
|
|
96
|
+
case 'list': {
|
|
97
|
+
const query: { status?: CommitmentStatus; priority?: CommitmentPriority; assigned_to?: string; overdue?: boolean } = {};
|
|
98
|
+
if (params.filter_status) query.status = params.filter_status as CommitmentStatus;
|
|
99
|
+
if (params.priority) query.priority = params.priority as CommitmentPriority;
|
|
100
|
+
if (params.assigned_to) query.assigned_to = params.assigned_to as string;
|
|
101
|
+
if (params.filter_overdue === 'true') query.overdue = true;
|
|
102
|
+
|
|
103
|
+
const items = findCommitments(query);
|
|
104
|
+
if (items.length === 0) return 'No commitments found matching the criteria.';
|
|
105
|
+
return items.map(c => {
|
|
106
|
+
const due = c.when_due ? ` (due: ${new Date(c.when_due).toLocaleString()})` : '';
|
|
107
|
+
const assignee = c.assigned_to ? ` [${c.assigned_to}]` : '';
|
|
108
|
+
return `[${c.id}] [${c.priority}] ${c.what}${due} — ${c.status}${assignee}`;
|
|
109
|
+
}).join('\n');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
case 'get': {
|
|
113
|
+
if (!params.id) return 'Error: "id" is required for get action';
|
|
114
|
+
const item = getCommitment(params.id as string);
|
|
115
|
+
if (!item) return `Commitment not found: ${params.id}`;
|
|
116
|
+
return [
|
|
117
|
+
`ID: ${item.id}`,
|
|
118
|
+
`Task: ${item.what}`,
|
|
119
|
+
`Status: ${item.status}`,
|
|
120
|
+
`Priority: ${item.priority}`,
|
|
121
|
+
`Due: ${item.when_due ? new Date(item.when_due).toLocaleString() : 'none'}`,
|
|
122
|
+
`Assigned to: ${item.assigned_to || 'unassigned'}`,
|
|
123
|
+
`Context: ${item.context || 'none'}`,
|
|
124
|
+
`Created: ${new Date(item.created_at).toLocaleString()}`,
|
|
125
|
+
item.completed_at ? `Completed: ${new Date(item.completed_at).toLocaleString()}` : null,
|
|
126
|
+
item.result ? `Result: ${item.result}` : null,
|
|
127
|
+
].filter(Boolean).join('\n');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
case 'create': {
|
|
131
|
+
if (!params.what) return 'Error: "what" is required for create action';
|
|
132
|
+
|
|
133
|
+
let whenDue: number | undefined;
|
|
134
|
+
if (params.when_due && params.when_due !== 'null') {
|
|
135
|
+
const parsed = new Date(params.when_due as string).getTime();
|
|
136
|
+
if (isNaN(parsed)) return `Error: Invalid date format for when_due: "${params.when_due}". Use ISO 8601 (e.g., "2026-02-28T14:00:00")`;
|
|
137
|
+
whenDue = parsed;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (params.priority && !VALID_PRIORITIES.includes(params.priority as string)) {
|
|
141
|
+
return `Error: Invalid priority "${params.priority}". Must be: ${VALID_PRIORITIES.join(', ')}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const item = createCommitment(params.what as string, {
|
|
145
|
+
when_due: whenDue,
|
|
146
|
+
priority: (params.priority as CommitmentPriority) || undefined,
|
|
147
|
+
context: params.context as string | undefined,
|
|
148
|
+
assigned_to: params.assigned_to as string | undefined,
|
|
149
|
+
created_from: 'jarvis',
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const due = item.when_due ? ` — due: ${new Date(item.when_due).toLocaleString()}` : '';
|
|
153
|
+
return `Created commitment: [${item.id}] "${item.what}" (${item.priority})${due}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case 'update_status': {
|
|
157
|
+
if (!params.id) return 'Error: "id" is required for update_status action';
|
|
158
|
+
if (!params.status) return 'Error: "status" is required for update_status action';
|
|
159
|
+
if (!VALID_STATUSES.includes(params.status as string)) {
|
|
160
|
+
return `Error: Invalid status "${params.status}". Must be: ${VALID_STATUSES.join(', ')}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const updated = updateCommitmentStatus(
|
|
164
|
+
params.id as string,
|
|
165
|
+
params.status as CommitmentStatus,
|
|
166
|
+
params.result as string | undefined,
|
|
167
|
+
);
|
|
168
|
+
if (!updated) return `Commitment not found: ${params.id}`;
|
|
169
|
+
return `Updated: [${updated.id}] "${updated.what}" — now ${updated.status}${updated.result ? ` (result: ${updated.result})` : ''}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
case 'set_due': {
|
|
173
|
+
if (!params.id) return 'Error: "id" is required for set_due action';
|
|
174
|
+
|
|
175
|
+
let whenDue: number | null = null;
|
|
176
|
+
if (params.when_due && params.when_due !== 'null') {
|
|
177
|
+
const parsed = new Date(params.when_due as string).getTime();
|
|
178
|
+
if (isNaN(parsed)) return `Error: Invalid date format: "${params.when_due}". Use ISO 8601 (e.g., "2026-02-28T14:00:00")`;
|
|
179
|
+
whenDue = parsed;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const updated = updateCommitmentDue(params.id as string, whenDue);
|
|
183
|
+
if (!updated) return `Commitment not found: ${params.id}`;
|
|
184
|
+
const due = updated.when_due ? new Date(updated.when_due).toLocaleString() : 'cleared';
|
|
185
|
+
return `Due date updated: [${updated.id}] "${updated.what}" — due: ${due}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
default:
|
|
189
|
+
return `Unknown action: "${action}". Valid actions: list, get, create, update_status, set_due`;
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Pipeline Tool
|
|
3
|
+
*
|
|
4
|
+
* Allows the agent to manage the content pipeline:
|
|
5
|
+
* list, get, create, update, advance stage, add notes, get notes.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: For long content (drafts, scripts, articles), use append_body
|
|
8
|
+
* to write in chunks rather than sending the entire text in a single update.
|
|
9
|
+
* This avoids token limit truncation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ToolDefinition } from './registry.ts';
|
|
13
|
+
import type { ContentStage, ContentType } from '../../vault/content-pipeline.ts';
|
|
14
|
+
import {
|
|
15
|
+
createContent, getContent, findContent, updateContent,
|
|
16
|
+
advanceStage, regressStage,
|
|
17
|
+
addStageNote, getStageNotes,
|
|
18
|
+
CONTENT_STAGES, CONTENT_TYPES,
|
|
19
|
+
} from '../../vault/content-pipeline.ts';
|
|
20
|
+
|
|
21
|
+
export const contentPipelineTool: ToolDefinition = {
|
|
22
|
+
name: 'content_pipeline',
|
|
23
|
+
description: [
|
|
24
|
+
'Manage the content creation pipeline. Use this tool to create, update, and track content items',
|
|
25
|
+
'through their lifecycle stages: idea → research → outline → draft → assets → review → scheduled → published.',
|
|
26
|
+
'',
|
|
27
|
+
'Actions:',
|
|
28
|
+
' list — List content items, optionally filtered by stage, type, or tag',
|
|
29
|
+
' get — Get a single content item by ID',
|
|
30
|
+
' create — Create a new content item',
|
|
31
|
+
' update — Update a content item (title, stage, tags, etc.)',
|
|
32
|
+
' set_body — Replace the entire body text (use for SHORT content only)',
|
|
33
|
+
' append_body — Append text to the body (use for LONG content like drafts, scripts, articles)',
|
|
34
|
+
' advance — Move content to the next stage',
|
|
35
|
+
' regress — Move content to the previous stage',
|
|
36
|
+
' add_note — Add a note to a specific stage of content',
|
|
37
|
+
' get_notes — Get notes for a content item (optionally for a specific stage)',
|
|
38
|
+
'',
|
|
39
|
+
'IMPORTANT: For writing drafts, scripts, or any content longer than a few paragraphs,',
|
|
40
|
+
'use append_body in multiple calls (e.g., intro, then body sections, then conclusion).',
|
|
41
|
+
'Use set_body with body="" first to clear the body if you need to rewrite from scratch.',
|
|
42
|
+
'This prevents data loss from output token limits.',
|
|
43
|
+
'',
|
|
44
|
+
'Content types: ' + CONTENT_TYPES.join(', '),
|
|
45
|
+
'Stages: ' + CONTENT_STAGES.join(' → '),
|
|
46
|
+
].join('\n'),
|
|
47
|
+
category: 'content',
|
|
48
|
+
parameters: {
|
|
49
|
+
action: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
description: 'The action: list, get, create, update, set_body, append_body, advance, regress, add_note, get_notes',
|
|
52
|
+
required: true,
|
|
53
|
+
},
|
|
54
|
+
id: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description: 'Content item ID (required for get, update, set_body, append_body, advance, regress, add_note, get_notes)',
|
|
57
|
+
required: false,
|
|
58
|
+
},
|
|
59
|
+
title: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Content title (required for create, optional for update)',
|
|
62
|
+
required: false,
|
|
63
|
+
},
|
|
64
|
+
body: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
description: 'Content text. For set_body: replaces entire body. For append_body: appended to existing body. Keep under 1000 words per call.',
|
|
67
|
+
required: false,
|
|
68
|
+
},
|
|
69
|
+
content_type: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: 'Content type: youtube, blog, twitter, instagram, tiktok, linkedin, podcast, newsletter, short_form, other',
|
|
72
|
+
required: false,
|
|
73
|
+
},
|
|
74
|
+
stage: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
description: 'Pipeline stage (for filtering in list, or setting in update, or specifying in add_note/get_notes)',
|
|
77
|
+
required: false,
|
|
78
|
+
},
|
|
79
|
+
tags: {
|
|
80
|
+
type: 'string',
|
|
81
|
+
description: 'Comma-separated tags (for create/update/filter)',
|
|
82
|
+
required: false,
|
|
83
|
+
},
|
|
84
|
+
note: {
|
|
85
|
+
type: 'string',
|
|
86
|
+
description: 'Note text (required for add_note)',
|
|
87
|
+
required: false,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
execute: async (params) => {
|
|
91
|
+
const action = params.action as string;
|
|
92
|
+
|
|
93
|
+
switch (action) {
|
|
94
|
+
case 'list': {
|
|
95
|
+
const query: { stage?: ContentStage; content_type?: ContentType; tag?: string } = {};
|
|
96
|
+
if (params.stage) query.stage = params.stage as ContentStage;
|
|
97
|
+
if (params.content_type) query.content_type = params.content_type as ContentType;
|
|
98
|
+
if (params.tags) query.tag = params.tags as string;
|
|
99
|
+
const items = findContent(query);
|
|
100
|
+
if (items.length === 0) return 'No content items found matching the criteria.';
|
|
101
|
+
return items.map(i =>
|
|
102
|
+
`[${i.id}] "${i.title}" (${i.content_type}) — stage: ${i.stage}, tags: ${i.tags.join(', ') || 'none'}, updated: ${new Date(i.updated_at).toLocaleString()}`
|
|
103
|
+
).join('\n');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
case 'get': {
|
|
107
|
+
if (!params.id) return 'Error: "id" is required for get action';
|
|
108
|
+
const item = getContent(params.id as string);
|
|
109
|
+
if (!item) return `Content item not found: ${params.id}`;
|
|
110
|
+
return [
|
|
111
|
+
`Title: ${item.title}`,
|
|
112
|
+
`Type: ${item.content_type}`,
|
|
113
|
+
`Stage: ${item.stage}`,
|
|
114
|
+
`Tags: ${item.tags.join(', ') || 'none'}`,
|
|
115
|
+
`Created by: ${item.created_by}`,
|
|
116
|
+
`Created: ${new Date(item.created_at).toLocaleString()}`,
|
|
117
|
+
`Updated: ${new Date(item.updated_at).toLocaleString()}`,
|
|
118
|
+
`Body length: ${item.body.length} chars`,
|
|
119
|
+
item.scheduled_at ? `Scheduled: ${new Date(item.scheduled_at).toLocaleString()}` : null,
|
|
120
|
+
item.published_url ? `Published URL: ${item.published_url}` : null,
|
|
121
|
+
'',
|
|
122
|
+
'--- Body ---',
|
|
123
|
+
item.body || '(empty)',
|
|
124
|
+
].filter(Boolean).join('\n');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
case 'create': {
|
|
128
|
+
if (!params.title) return 'Error: "title" is required for create action';
|
|
129
|
+
const tags = params.tags ? (params.tags as string).split(',').map(t => t.trim()) : undefined;
|
|
130
|
+
const item = createContent(params.title as string, {
|
|
131
|
+
body: params.body as string | undefined,
|
|
132
|
+
content_type: params.content_type as ContentType | undefined,
|
|
133
|
+
stage: params.stage as ContentStage | undefined,
|
|
134
|
+
tags,
|
|
135
|
+
created_by: 'jarvis',
|
|
136
|
+
});
|
|
137
|
+
return `Created content item: [${item.id}] "${item.title}" (${item.content_type}, stage: ${item.stage})`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
case 'update': {
|
|
141
|
+
if (!params.id) return 'Error: "id" is required for update action';
|
|
142
|
+
const updates: Record<string, unknown> = {};
|
|
143
|
+
if (params.title !== undefined) updates.title = params.title;
|
|
144
|
+
if (params.content_type !== undefined) updates.content_type = params.content_type;
|
|
145
|
+
if (params.stage !== undefined) updates.stage = params.stage;
|
|
146
|
+
if (params.tags !== undefined) {
|
|
147
|
+
updates.tags = (params.tags as string).split(',').map(t => t.trim());
|
|
148
|
+
}
|
|
149
|
+
// Note: body is NOT handled here. Use set_body or append_body instead.
|
|
150
|
+
const updated = updateContent(params.id as string, updates);
|
|
151
|
+
if (!updated) return `Content item not found: ${params.id}`;
|
|
152
|
+
return `Updated: [${updated.id}] "${updated.title}" — stage: ${updated.stage}, body: ${updated.body.length} chars`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
case 'set_body': {
|
|
156
|
+
if (!params.id) return 'Error: "id" is required for set_body action';
|
|
157
|
+
if (params.body === undefined) return 'Error: "body" is required for set_body action';
|
|
158
|
+
const updated = updateContent(params.id as string, { body: params.body as string });
|
|
159
|
+
if (!updated) return `Content item not found: ${params.id}`;
|
|
160
|
+
return `Body set: ${updated.body.length} chars saved for "${updated.title}"`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case 'append_body': {
|
|
164
|
+
if (!params.id) return 'Error: "id" is required for append_body action';
|
|
165
|
+
if (!params.body) return 'Error: "body" is required for append_body action (the text to append)';
|
|
166
|
+
const existing = getContent(params.id as string);
|
|
167
|
+
if (!existing) return `Content item not found: ${params.id}`;
|
|
168
|
+
const newBody = existing.body + (existing.body ? '\n\n' : '') + (params.body as string);
|
|
169
|
+
const updated = updateContent(params.id as string, { body: newBody });
|
|
170
|
+
if (!updated) return `Failed to append body`;
|
|
171
|
+
return `Appended ${(params.body as string).length} chars. Total body: ${updated.body.length} chars for "${updated.title}"`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
case 'advance': {
|
|
175
|
+
if (!params.id) return 'Error: "id" is required for advance action';
|
|
176
|
+
const advanced = advanceStage(params.id as string);
|
|
177
|
+
if (!advanced) return 'Cannot advance: item not found or already at last stage (published).';
|
|
178
|
+
return `Advanced to stage: ${advanced.stage} — "${advanced.title}"`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
case 'regress': {
|
|
182
|
+
if (!params.id) return 'Error: "id" is required for regress action';
|
|
183
|
+
const regressed = regressStage(params.id as string);
|
|
184
|
+
if (!regressed) return 'Cannot regress: item not found or already at first stage (idea).';
|
|
185
|
+
return `Regressed to stage: ${regressed.stage} — "${regressed.title}"`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
case 'add_note': {
|
|
189
|
+
if (!params.id) return 'Error: "id" is required for add_note action';
|
|
190
|
+
if (!params.stage) return 'Error: "stage" is required for add_note action';
|
|
191
|
+
if (!params.note) return 'Error: "note" is required for add_note action';
|
|
192
|
+
const stageNote = addStageNote(
|
|
193
|
+
params.id as string,
|
|
194
|
+
params.stage as ContentStage,
|
|
195
|
+
params.note as string,
|
|
196
|
+
'jarvis',
|
|
197
|
+
);
|
|
198
|
+
return `Note added to ${stageNote.stage} stage: "${stageNote.note.slice(0, 100)}${stageNote.note.length > 100 ? '...' : ''}"`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
case 'get_notes': {
|
|
202
|
+
if (!params.id) return 'Error: "id" is required for get_notes action';
|
|
203
|
+
const notes = getStageNotes(
|
|
204
|
+
params.id as string,
|
|
205
|
+
params.stage as ContentStage | undefined,
|
|
206
|
+
);
|
|
207
|
+
if (notes.length === 0) return 'No notes found.';
|
|
208
|
+
return notes.map(n =>
|
|
209
|
+
`[${n.stage}] (${n.author}, ${new Date(n.created_at).toLocaleString()}): ${n.note}`
|
|
210
|
+
).join('\n');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
default:
|
|
214
|
+
return `Unknown action: "${action}". Valid actions: list, get, create, update, set_body, append_body, advance, regress, add_note, get_notes`;
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegate Task Tool — Multi-Agent Delegation
|
|
3
|
+
*
|
|
4
|
+
* Allows the primary agent (PA) to delegate tasks to specialist sub-agents.
|
|
5
|
+
* The tool spawns a specialist, runs it through a full LLM+tool loop,
|
|
6
|
+
* and returns the result to the PA.
|
|
7
|
+
*
|
|
8
|
+
* Supports sync mode (tool blocks until done) and async mode (background).
|
|
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 { runSubAgent, createScopedToolRegistry, type ProgressCallback } from '../../agents/sub-agent-runner.ts';
|
|
16
|
+
|
|
17
|
+
export type DelegateToolDeps = {
|
|
18
|
+
orchestrator: AgentOrchestrator;
|
|
19
|
+
llmManager: LLMManager;
|
|
20
|
+
specialists: Map<string, RoleDefinition>;
|
|
21
|
+
onProgress?: ProgressCallback;
|
|
22
|
+
onDelegation?: (specialistName: string, task: string) => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create the delegate_task tool definition.
|
|
27
|
+
* The deps are captured in the closure so the tool has access at execution time.
|
|
28
|
+
*/
|
|
29
|
+
export function createDelegateTool(deps: DelegateToolDeps): ToolDefinition {
|
|
30
|
+
return {
|
|
31
|
+
name: 'delegate_task',
|
|
32
|
+
description: [
|
|
33
|
+
'Quick sync delegation: spawns a specialist, runs the task to completion, returns the result.',
|
|
34
|
+
'The specialist has its own LLM and tools. Blocks until done — use for focused, quick tasks.',
|
|
35
|
+
'For persistent agents or parallel work, use manage_agents instead.',
|
|
36
|
+
'',
|
|
37
|
+
'Available specialists: ' + Array.from(deps.specialists.keys()).join(', '),
|
|
38
|
+
].join('\n'),
|
|
39
|
+
category: 'delegation',
|
|
40
|
+
parameters: {
|
|
41
|
+
specialist: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
description: 'The specialist role ID to delegate to (e.g., "research-analyst", "software-engineer")',
|
|
44
|
+
required: true,
|
|
45
|
+
},
|
|
46
|
+
task: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Clear description of what the specialist should do',
|
|
49
|
+
required: true,
|
|
50
|
+
},
|
|
51
|
+
context: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Background information, relevant details, or constraints for the task',
|
|
54
|
+
required: true,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
execute: async (params) => {
|
|
58
|
+
const specialistId = params.specialist as string;
|
|
59
|
+
const task = params.task as string;
|
|
60
|
+
const context = params.context as string;
|
|
61
|
+
|
|
62
|
+
// Validate specialist exists
|
|
63
|
+
const specialistRole = deps.specialists.get(specialistId);
|
|
64
|
+
if (!specialistRole) {
|
|
65
|
+
const available = Array.from(deps.specialists.keys()).join(', ');
|
|
66
|
+
return `Error: Unknown specialist "${specialistId}". Available: ${available}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Get the primary agent as parent
|
|
70
|
+
const primary = deps.orchestrator.getPrimary();
|
|
71
|
+
if (!primary) {
|
|
72
|
+
return 'Error: No primary agent exists to delegate from';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(`[DelegateTool] Delegating to ${specialistRole.name}: ${task.slice(0, 100)}...`);
|
|
76
|
+
|
|
77
|
+
// Notify task board: ownership is transferring to specialist
|
|
78
|
+
deps.onDelegation?.(specialistRole.name, task);
|
|
79
|
+
|
|
80
|
+
// Notify progress: delegation starting
|
|
81
|
+
if (deps.onProgress) {
|
|
82
|
+
deps.onProgress({
|
|
83
|
+
type: 'text',
|
|
84
|
+
agentName: specialistRole.name,
|
|
85
|
+
agentId: 'pending',
|
|
86
|
+
data: `[Delegating to ${specialistRole.name}...]`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Spawn sub-agent
|
|
91
|
+
const subAgent = deps.orchestrator.spawnSubAgent(primary.id, specialistRole);
|
|
92
|
+
|
|
93
|
+
// Create scoped tool registry based on specialist's allowed tools
|
|
94
|
+
const scopedRegistry = createScopedToolRegistry(subAgent.agent.authority.allowed_tools);
|
|
95
|
+
|
|
96
|
+
console.log(`[DelegateTool] Spawned ${specialistRole.name} (${subAgent.id}) with ${scopedRegistry.count()} tools`);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Run the sub-agent (sync — blocks until complete)
|
|
100
|
+
const result = await runSubAgent({
|
|
101
|
+
agent: subAgent,
|
|
102
|
+
task,
|
|
103
|
+
context,
|
|
104
|
+
llmManager: deps.llmManager,
|
|
105
|
+
toolRegistry: scopedRegistry,
|
|
106
|
+
onProgress: deps.onProgress,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Terminate sub-agent after completion
|
|
110
|
+
deps.orchestrator.terminateAgent(subAgent.id);
|
|
111
|
+
|
|
112
|
+
// Format result for the PA
|
|
113
|
+
const toolsList = result.toolsUsed.length > 0
|
|
114
|
+
? `\nTools used: ${result.toolsUsed.join(', ')}`
|
|
115
|
+
: '';
|
|
116
|
+
const tokens = `\nTokens: ${result.tokensUsed.input + result.tokensUsed.output}`;
|
|
117
|
+
|
|
118
|
+
if (result.success) {
|
|
119
|
+
return [
|
|
120
|
+
`[${specialistRole.name} completed]`,
|
|
121
|
+
'',
|
|
122
|
+
result.response,
|
|
123
|
+
toolsList,
|
|
124
|
+
tokens,
|
|
125
|
+
].join('\n');
|
|
126
|
+
} else {
|
|
127
|
+
return [
|
|
128
|
+
`[${specialistRole.name} failed]`,
|
|
129
|
+
'',
|
|
130
|
+
result.response,
|
|
131
|
+
toolsList,
|
|
132
|
+
tokens,
|
|
133
|
+
].join('\n');
|
|
134
|
+
}
|
|
135
|
+
} catch (err) {
|
|
136
|
+
// Clean up on error
|
|
137
|
+
try {
|
|
138
|
+
deps.orchestrator.terminateAgent(subAgent.id);
|
|
139
|
+
} catch { /* ignore cleanup errors */ }
|
|
140
|
+
|
|
141
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
142
|
+
console.error(`[DelegateTool] Error:`, errorMsg);
|
|
143
|
+
return `Error: Delegation to ${specialistRole.name} failed: ${errorMsg}`;
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { test, expect, describe } from 'bun:test';
|
|
2
|
+
import { DESKTOP_TOOLS } from './desktop.ts';
|
|
3
|
+
|
|
4
|
+
describe('DESKTOP_TOOLS', () => {
|
|
5
|
+
test('contains 9 desktop tools', () => {
|
|
6
|
+
expect(DESKTOP_TOOLS).toHaveLength(9);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('all have desktop category', () => {
|
|
10
|
+
for (const tool of DESKTOP_TOOLS) {
|
|
11
|
+
expect(tool.category).toBe('desktop');
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('tool names match expected desktop tools', () => {
|
|
16
|
+
const names = DESKTOP_TOOLS.map((t: any) => t.name).sort();
|
|
17
|
+
expect(names).toEqual([
|
|
18
|
+
'desktop_click',
|
|
19
|
+
'desktop_find_element',
|
|
20
|
+
'desktop_focus_window',
|
|
21
|
+
'desktop_launch_app',
|
|
22
|
+
'desktop_list_windows',
|
|
23
|
+
'desktop_press_keys',
|
|
24
|
+
'desktop_screenshot',
|
|
25
|
+
'desktop_snapshot',
|
|
26
|
+
'desktop_type',
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('all tools have execute functions', () => {
|
|
31
|
+
for (const tool of DESKTOP_TOOLS) {
|
|
32
|
+
expect(typeof tool.execute).toBe('function');
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('all tools have descriptions', () => {
|
|
37
|
+
for (const tool of DESKTOP_TOOLS) {
|
|
38
|
+
expect(tool.description.length).toBeGreaterThan(10);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('all tools have target parameter', () => {
|
|
43
|
+
for (const tool of DESKTOP_TOOLS) {
|
|
44
|
+
expect(tool.parameters.target).toBeDefined();
|
|
45
|
+
expect(tool.parameters.target!.type).toBe('string');
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('returns not-implemented error without target (local execution stub)', async () => {
|
|
50
|
+
for (const tool of DESKTOP_TOOLS) {
|
|
51
|
+
const result = await tool.execute({});
|
|
52
|
+
expect(String(result)).toContain('not yet implemented');
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|