@shykaruu/jarvis-brain 0.4.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 +428 -0
- package/bin/jarvis.ts +449 -0
- package/package.json +79 -0
- package/roles/activity-observer.yaml +60 -0
- package/roles/ceo-founder.yaml +144 -0
- package/roles/chief-of-staff.yaml +158 -0
- package/roles/dev-lead.yaml +182 -0
- package/roles/executive-assistant.yaml +77 -0
- package/roles/marketing-director.yaml +168 -0
- package/roles/personal-assistant.yaml +266 -0
- package/roles/research-specialist.yaml +60 -0
- package/roles/specialists/content-writer.yaml +53 -0
- package/roles/specialists/customer-support.yaml +57 -0
- package/roles/specialists/data-analyst.yaml +57 -0
- package/roles/specialists/financial-analyst.yaml +56 -0
- package/roles/specialists/hr-specialist.yaml +55 -0
- package/roles/specialists/legal-advisor.yaml +58 -0
- package/roles/specialists/marketing-strategist.yaml +56 -0
- package/roles/specialists/project-coordinator.yaml +55 -0
- package/roles/specialists/research-analyst.yaml +58 -0
- package/roles/specialists/software-engineer.yaml +57 -0
- package/roles/specialists/system-administrator.yaml +57 -0
- package/roles/system-admin.yaml +76 -0
- package/scripts/ensure-bun.cjs +16 -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 +261 -0
- package/src/actions/browser/session.ts +506 -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 +363 -0
- package/src/actions/tools/builtin.ts +950 -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/documents.ts +169 -0
- package/src/actions/tools/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +31 -0
- package/src/actions/tools/registry.ts +173 -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 +592 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +309 -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 +301 -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 +417 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +238 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +827 -0
- package/src/cli/uninstall.test.ts +37 -0
- package/src/cli/uninstall.ts +202 -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/dashboard-auth.ts +75 -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 +149 -0
- package/src/comms/voice.test.ts +504 -0
- package/src/comms/voice.ts +341 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +669 -0
- package/src/config/README.md +389 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +183 -0
- package/src/config/loader.ts +148 -0
- package/src/config/types.ts +293 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +667 -0
- package/src/daemon/api-routes.ts +3067 -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/dashboard-auth.test.ts +170 -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/flock.c +7 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1070 -0
- package/src/daemon/llm-settings.test.ts +78 -0
- package/src/daemon/llm-settings.ts +450 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.test.ts +283 -0
- package/src/daemon/pid.ts +224 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +926 -0
- package/src/global.d.ts +4 -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 +407 -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 +400 -0
- package/src/llm/gemini.ts +380 -0
- package/src/llm/groq.ts +406 -0
- package/src/llm/history.ts +147 -0
- package/src/llm/index.ts +21 -0
- package/src/llm/manager.ts +226 -0
- package/src/llm/ollama.ts +316 -0
- package/src/llm/openai.ts +411 -0
- package/src/llm/openrouter.ts +390 -0
- package/src/llm/provider.test.ts +487 -0
- package/src/llm/provider.ts +61 -0
- package/src/llm/test.ts +88 -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 +120 -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 +218 -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 +195 -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/sites/builder-tools.ts +215 -0
- package/src/sites/dev-server-manager.ts +286 -0
- package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
- package/src/sites/fixtures/security-test-site/Makefile +15 -0
- package/src/sites/fixtures/security-test-site/README.md +18 -0
- package/src/sites/fixtures/security-test-site/index.html +12 -0
- package/src/sites/fixtures/security-test-site/index.ts +16 -0
- package/src/sites/fixtures/security-test-site/package.json +13 -0
- package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
- package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
- package/src/sites/git-manager.ts +240 -0
- package/src/sites/github-manager.ts +355 -0
- package/src/sites/index.ts +25 -0
- package/src/sites/project-manager.ts +389 -0
- package/src/sites/proxy.ts +133 -0
- package/src/sites/service.ts +136 -0
- package/src/sites/templates.ts +169 -0
- package/src/sites/types.ts +89 -0
- package/src/user/profile-followup.test.ts +84 -0
- package/src/user/profile-followup.ts +185 -0
- package/src/user/profile.ts +224 -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 +270 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/dashboard-sessions.ts +44 -0
- package/src/vault/documents.ts +130 -0
- package/src/vault/entities.ts +185 -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 +139 -0
- package/src/vault/retrieval.ts +258 -0
- package/src/vault/schema.ts +709 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/user-profile.test.ts +113 -0
- package/src/vault/user-profile.ts +176 -0
- package/src/vault/vectors.ts +92 -0
- package/src/vault/webapp-template-seeds.ts +116 -0
- package/src/vault/webapp-templates.ts +244 -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-3gr23jt9.js +112614 -0
- package/ui/dist/index-9vmj8127.css +14239 -0
- package/ui/dist/index-hy9pc1gm.js +112873 -0
- package/ui/dist/index-j2ep5d1w.js +112374 -0
- package/ui/dist/index-jt00vjqs.js +112858 -0
- package/ui/dist/index-k9ymx5qb.js +112374 -0
- package/ui/dist/index.html +16 -0
- package/ui/public/audio/pcm-capture-processor.js +11 -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,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manage Goals Tool — Chat-Driven Goal Pursuit
|
|
3
|
+
*
|
|
4
|
+
* Allows the agent to create, list, score, update, decompose, replan,
|
|
5
|
+
* and run daily rhythm from natural language chat.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ToolDefinition } from './registry.ts';
|
|
9
|
+
import type { GoalService } from '../../goals/service.ts';
|
|
10
|
+
import type { NLGoalBuilder } from '../../goals/nl-builder.ts';
|
|
11
|
+
import type { GoalEstimator } from '../../goals/estimator.ts';
|
|
12
|
+
import type { DailyRhythm } from '../../goals/rhythm.ts';
|
|
13
|
+
import type { AccountabilityEngine } from '../../goals/accountability.ts';
|
|
14
|
+
import * as vault from '../../vault/goals.ts';
|
|
15
|
+
|
|
16
|
+
export type GoalToolDeps = {
|
|
17
|
+
goalService: GoalService;
|
|
18
|
+
nlBuilder: NLGoalBuilder;
|
|
19
|
+
estimator: GoalEstimator;
|
|
20
|
+
rhythm: DailyRhythm;
|
|
21
|
+
accountability: AccountabilityEngine;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function createManageGoalsTool(deps: GoalToolDeps): ToolDefinition {
|
|
25
|
+
return {
|
|
26
|
+
name: 'manage_goals',
|
|
27
|
+
description: [
|
|
28
|
+
'Manage OKR-style goals with hierarchical structure (objective → key_result → milestone → task → daily_action).',
|
|
29
|
+
'Google-style 0.0-1.0 scoring (0.7 = good, 1.0 = aimed too low).',
|
|
30
|
+
'',
|
|
31
|
+
'Actions: create, list, get, score, update_status, update, decompose, replan, estimate,',
|
|
32
|
+
' morning_plan, evening_review, metrics, delete, tree, overdue, escalations',
|
|
33
|
+
].join('\n'),
|
|
34
|
+
category: 'goals',
|
|
35
|
+
parameters: {
|
|
36
|
+
action: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'The action to perform',
|
|
39
|
+
required: true,
|
|
40
|
+
},
|
|
41
|
+
text: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
description: 'Natural language goal description (for "create")',
|
|
44
|
+
required: false,
|
|
45
|
+
},
|
|
46
|
+
goal_id: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Target goal ID',
|
|
49
|
+
required: false,
|
|
50
|
+
},
|
|
51
|
+
score: {
|
|
52
|
+
type: 'number',
|
|
53
|
+
description: 'Score value 0.0-1.0 (for "score")',
|
|
54
|
+
required: false,
|
|
55
|
+
},
|
|
56
|
+
reason: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: 'Reason for score update',
|
|
59
|
+
required: false,
|
|
60
|
+
},
|
|
61
|
+
status: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
description: 'New status (for "update_status"): draft, active, paused, completed, failed, killed',
|
|
64
|
+
required: false,
|
|
65
|
+
},
|
|
66
|
+
title: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
description: 'Goal title (for quick "create" without NL)',
|
|
69
|
+
required: false,
|
|
70
|
+
},
|
|
71
|
+
level: {
|
|
72
|
+
type: 'string',
|
|
73
|
+
description: 'Goal level: objective, key_result, milestone, task, daily_action',
|
|
74
|
+
required: false,
|
|
75
|
+
},
|
|
76
|
+
parent_id: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: 'Parent goal ID (for "create")',
|
|
79
|
+
required: false,
|
|
80
|
+
},
|
|
81
|
+
filter_status: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'Filter by status (for "list")',
|
|
84
|
+
required: false,
|
|
85
|
+
},
|
|
86
|
+
filter_level: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
description: 'Filter by level (for "list")',
|
|
89
|
+
required: false,
|
|
90
|
+
},
|
|
91
|
+
limit: {
|
|
92
|
+
type: 'number',
|
|
93
|
+
description: 'Max results (for "list")',
|
|
94
|
+
required: false,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
execute: async (params) => {
|
|
98
|
+
const action = String(params.action ?? '').toLowerCase();
|
|
99
|
+
|
|
100
|
+
switch (action) {
|
|
101
|
+
case 'create': {
|
|
102
|
+
const text = params.text as string | undefined;
|
|
103
|
+
const title = params.title as string | undefined;
|
|
104
|
+
|
|
105
|
+
if (text) {
|
|
106
|
+
// NL goal creation
|
|
107
|
+
try {
|
|
108
|
+
const proposal = await deps.nlBuilder.parseGoal(text);
|
|
109
|
+
|
|
110
|
+
if (proposal.clarifying_questions?.length) {
|
|
111
|
+
return `Before creating this goal, I have some questions:\n${proposal.clarifying_questions.map((q, i) => `${i + 1}. ${q}`).join('\n')}\n\nPlease answer these and I'll create the full OKR breakdown.`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const goals = deps.nlBuilder.createFromProposal(
|
|
115
|
+
proposal,
|
|
116
|
+
params.parent_id as string | undefined,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const summary = goals.map(g =>
|
|
120
|
+
`${' '.repeat(levelDepth(g.level))}${g.level}: ${g.title}`
|
|
121
|
+
).join('\n');
|
|
122
|
+
|
|
123
|
+
return `Created ${goals.length} goals:\n${summary}`;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
return `Error creating goal from NL: ${err instanceof Error ? err.message : err}`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (title) {
|
|
130
|
+
// Quick creation
|
|
131
|
+
const level = (params.level as string) ?? 'task';
|
|
132
|
+
const goal = deps.goalService.createGoal(title, level as any, {
|
|
133
|
+
parent_id: params.parent_id as string | undefined,
|
|
134
|
+
});
|
|
135
|
+
return `Created ${level}: "${goal.title}" (${goal.id})`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return 'Error: Provide either "text" (NL description) or "title" (quick create).';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
case 'list': {
|
|
142
|
+
const goals = vault.findGoals({
|
|
143
|
+
status: params.filter_status as any,
|
|
144
|
+
level: params.filter_level as any,
|
|
145
|
+
limit: params.limit as number ?? 20,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (goals.length === 0) return 'No goals found matching filters.';
|
|
149
|
+
|
|
150
|
+
return goals.map(g =>
|
|
151
|
+
`[${g.status}] ${g.title} (${g.level}, score: ${g.score}, health: ${g.health})` +
|
|
152
|
+
(g.deadline ? ` — due ${new Date(g.deadline).toLocaleDateString()}` : '')
|
|
153
|
+
).join('\n');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case 'get': {
|
|
157
|
+
const goalId = params.goal_id as string;
|
|
158
|
+
if (!goalId) return 'Error: "goal_id" is required.';
|
|
159
|
+
|
|
160
|
+
const goal = vault.getGoal(goalId);
|
|
161
|
+
if (!goal) return `Goal "${goalId}" not found.`;
|
|
162
|
+
|
|
163
|
+
const children = vault.getGoalChildren(goalId);
|
|
164
|
+
const progress = vault.getProgressHistory(goalId, 5);
|
|
165
|
+
|
|
166
|
+
let result = `**${goal.title}**\n`;
|
|
167
|
+
result += `Level: ${goal.level} | Status: ${goal.status} | Health: ${goal.health}\n`;
|
|
168
|
+
result += `Score: ${goal.score}${goal.score_reason ? ` (${goal.score_reason})` : ''}\n`;
|
|
169
|
+
result += `Time horizon: ${goal.time_horizon}\n`;
|
|
170
|
+
if (goal.description) result += `Description: ${goal.description}\n`;
|
|
171
|
+
if (goal.success_criteria) result += `Success criteria: ${goal.success_criteria}\n`;
|
|
172
|
+
if (goal.deadline) result += `Deadline: ${new Date(goal.deadline).toLocaleDateString()}\n`;
|
|
173
|
+
if (goal.tags.length) result += `Tags: ${goal.tags.join(', ')}\n`;
|
|
174
|
+
if (goal.escalation_stage !== 'none') result += `Escalation: ${goal.escalation_stage}\n`;
|
|
175
|
+
|
|
176
|
+
if (children.length > 0) {
|
|
177
|
+
result += `\nChildren (${children.length}):\n`;
|
|
178
|
+
result += children.map(c => ` - ${c.title} (${c.level}, ${c.score})`).join('\n');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (progress.length > 0) {
|
|
182
|
+
result += `\nRecent progress:\n`;
|
|
183
|
+
result += progress.map(p =>
|
|
184
|
+
` ${new Date(p.created_at).toLocaleDateString()}: ${p.score_before} → ${p.score_after} (${p.note})`
|
|
185
|
+
).join('\n');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
case 'score': {
|
|
192
|
+
const goalId = params.goal_id as string;
|
|
193
|
+
const score = params.score as number;
|
|
194
|
+
const reason = (params.reason as string) ?? '';
|
|
195
|
+
if (!goalId) return 'Error: "goal_id" is required.';
|
|
196
|
+
if (score === undefined || score === null) return 'Error: "score" is required (0.0-1.0).';
|
|
197
|
+
|
|
198
|
+
const goal = deps.goalService.scoreGoal(goalId, score, reason);
|
|
199
|
+
if (!goal) return `Goal "${goalId}" not found.`;
|
|
200
|
+
return `Updated score for "${goal.title}": ${goal.score} — ${reason}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
case 'update_status': {
|
|
204
|
+
const goalId = params.goal_id as string;
|
|
205
|
+
const status = params.status as string;
|
|
206
|
+
if (!goalId) return 'Error: "goal_id" is required.';
|
|
207
|
+
if (!status) return 'Error: "status" is required.';
|
|
208
|
+
|
|
209
|
+
const goal = deps.goalService.updateStatus(goalId, status as any);
|
|
210
|
+
if (!goal) return `Goal "${goalId}" not found.`;
|
|
211
|
+
return `Updated "${goal.title}" status to: ${goal.status}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
case 'update': {
|
|
215
|
+
const goalId = params.goal_id as string;
|
|
216
|
+
if (!goalId) return 'Error: "goal_id" is required.';
|
|
217
|
+
|
|
218
|
+
const updates: Record<string, unknown> = {};
|
|
219
|
+
if (params.title) updates.title = params.title;
|
|
220
|
+
if (params.reason) updates.description = params.reason; // overloaded field
|
|
221
|
+
|
|
222
|
+
const goal = deps.goalService.updateGoal(goalId, updates as any);
|
|
223
|
+
if (!goal) return `Goal "${goalId}" not found.`;
|
|
224
|
+
return `Updated "${goal.title}".`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
case 'decompose': {
|
|
228
|
+
const goalId = params.goal_id as string;
|
|
229
|
+
if (!goalId) return 'Error: "goal_id" is required.';
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const proposal = await deps.nlBuilder.decompose(goalId);
|
|
233
|
+
if (!proposal) return 'Could not decompose — goal not found or already at lowest level.';
|
|
234
|
+
|
|
235
|
+
const goals = deps.nlBuilder.createFromProposal(proposal, goalId);
|
|
236
|
+
const summary = goals.map(g =>
|
|
237
|
+
`${' '.repeat(levelDepth(g.level))}${g.level}: ${g.title}`
|
|
238
|
+
).join('\n');
|
|
239
|
+
return `Decomposed into ${goals.length} sub-goals:\n${summary}`;
|
|
240
|
+
} catch (err) {
|
|
241
|
+
return `Decomposition error: ${err instanceof Error ? err.message : err}`;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
case 'replan': {
|
|
246
|
+
const goalId = params.goal_id as string;
|
|
247
|
+
if (!goalId) return 'Error: "goal_id" is required.';
|
|
248
|
+
|
|
249
|
+
const goal = vault.getGoal(goalId);
|
|
250
|
+
if (!goal) return `Goal "${goalId}" not found.`;
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const analysis = await deps.accountability.generateReplanOptions(goal);
|
|
254
|
+
let result = `**Replan Analysis: ${goal.title}**\n\n`;
|
|
255
|
+
result += `Analysis: ${analysis.analysis}\n\n`;
|
|
256
|
+
result += `Options:\n`;
|
|
257
|
+
for (const opt of analysis.options) {
|
|
258
|
+
result += ` [${opt.impact.toUpperCase()}] ${opt.label}: ${opt.description}\n`;
|
|
259
|
+
}
|
|
260
|
+
result += `\nRecommendation: ${analysis.recommendation}`;
|
|
261
|
+
return result;
|
|
262
|
+
} catch (err) {
|
|
263
|
+
return `Replan error: ${err instanceof Error ? err.message : err}`;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
case 'estimate': {
|
|
268
|
+
const goalId = params.goal_id as string;
|
|
269
|
+
if (!goalId) return 'Error: "goal_id" is required.';
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const estimate = await deps.estimator.estimate(goalId);
|
|
273
|
+
if (!estimate) return `Goal "${goalId}" not found.`;
|
|
274
|
+
|
|
275
|
+
return `**Estimate:** ${estimate.final_estimate_hours}h (confidence: ${(estimate.confidence * 100).toFixed(0)}%)\n` +
|
|
276
|
+
`LLM: ${estimate.llm_estimate_hours}h | Historical: ${estimate.historical_estimate_hours ?? 'N/A'}h\n` +
|
|
277
|
+
`${estimate.reasoning}` +
|
|
278
|
+
(estimate.similar_past_goals.length > 0
|
|
279
|
+
? `\nBased on ${estimate.similar_past_goals.length} similar past goal(s)`
|
|
280
|
+
: '');
|
|
281
|
+
} catch (err) {
|
|
282
|
+
return `Estimation error: ${err instanceof Error ? err.message : err}`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
case 'morning_plan': {
|
|
287
|
+
try {
|
|
288
|
+
const result = await deps.rhythm.runMorningPlan();
|
|
289
|
+
let output = `**Morning Plan**\n\n`;
|
|
290
|
+
output += `${result.message}\n\n`;
|
|
291
|
+
if (result.warnings.length) {
|
|
292
|
+
output += `Warnings:\n${result.warnings.map(w => ` ⚠ ${w}`).join('\n')}\n\n`;
|
|
293
|
+
}
|
|
294
|
+
output += `Focus areas:\n${result.focusAreas.map(f => ` → ${f}`).join('\n')}\n\n`;
|
|
295
|
+
output += `Today's actions:\n${result.dailyActions.map(a => ` □ ${a}`).join('\n')}`;
|
|
296
|
+
return output;
|
|
297
|
+
} catch (err) {
|
|
298
|
+
return `Morning plan error: ${err instanceof Error ? err.message : err}`;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
case 'evening_review': {
|
|
303
|
+
try {
|
|
304
|
+
const result = await deps.rhythm.runEveningReview();
|
|
305
|
+
let output = `**Evening Review**\n\n`;
|
|
306
|
+
output += `${result.message}\n\n`;
|
|
307
|
+
output += `Assessment: ${result.assessment}\n`;
|
|
308
|
+
if (result.scoreUpdates.length) {
|
|
309
|
+
output += `\nScore updates:\n${result.scoreUpdates.map(s => ` ${s.goalId}: ${s.newScore} — ${s.reason}`).join('\n')}`;
|
|
310
|
+
}
|
|
311
|
+
return output;
|
|
312
|
+
} catch (err) {
|
|
313
|
+
return `Evening review error: ${err instanceof Error ? err.message : err}`;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
case 'metrics': {
|
|
318
|
+
const m = deps.goalService.getMetrics();
|
|
319
|
+
return `**Goal Metrics**\n` +
|
|
320
|
+
`Total: ${m.total} | Active: ${m.active} | Completed: ${m.completed}\n` +
|
|
321
|
+
`Failed: ${m.failed} | Killed: ${m.killed}\n` +
|
|
322
|
+
`Avg OKR Score: ${m.avg_score.toFixed(2)}\n` +
|
|
323
|
+
`On Track: ${m.on_track} | At Risk: ${m.at_risk} | Behind: ${m.behind} | Critical: ${m.critical}\n` +
|
|
324
|
+
`Overdue: ${m.overdue}`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
case 'delete': {
|
|
328
|
+
const goalId = params.goal_id as string;
|
|
329
|
+
if (!goalId) return 'Error: "goal_id" is required.';
|
|
330
|
+
const deleted = deps.goalService.deleteGoal(goalId);
|
|
331
|
+
return deleted ? `Goal deleted (and all children).` : `Goal "${goalId}" not found.`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
case 'tree': {
|
|
335
|
+
const goalId = params.goal_id as string;
|
|
336
|
+
if (!goalId) return 'Error: "goal_id" is required.';
|
|
337
|
+
|
|
338
|
+
const tree = vault.getGoalTree(goalId);
|
|
339
|
+
if (tree.length === 0) return `Goal "${goalId}" not found.`;
|
|
340
|
+
|
|
341
|
+
return tree.map(g =>
|
|
342
|
+
`${' '.repeat(levelDepth(g.level))}[${g.status}] ${g.title} (${g.level}, ${g.score})`
|
|
343
|
+
).join('\n');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
case 'overdue': {
|
|
347
|
+
const overdue = vault.getOverdueGoals();
|
|
348
|
+
if (overdue.length === 0) return 'No overdue goals. Keep it up.';
|
|
349
|
+
return `**${overdue.length} Overdue Goal(s):**\n` +
|
|
350
|
+
overdue.map(g =>
|
|
351
|
+
` ${g.title} — due ${new Date(g.deadline!).toLocaleDateString()} (score: ${g.score})`
|
|
352
|
+
).join('\n');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
case 'escalations': {
|
|
356
|
+
const actions = deps.accountability.runEscalationCheck();
|
|
357
|
+
if (actions.length === 0) return 'No goals need escalation right now.';
|
|
358
|
+
return `**${actions.length} Escalation(s) Needed:**\n` +
|
|
359
|
+
actions.map(a =>
|
|
360
|
+
` ${a.goalTitle}: ${a.currentStage} → ${a.newStage} (${a.weeksBehind} weeks behind)\n ${a.message}`
|
|
361
|
+
).join('\n\n');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
default:
|
|
365
|
+
return `Unknown action "${action}". Available: create, list, get, score, update_status, update, decompose, replan, estimate, morning_plan, evening_review, metrics, delete, tree, overdue, escalations`;
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function levelDepth(level: string): number {
|
|
372
|
+
const depths: Record<string, number> = {
|
|
373
|
+
objective: 0, key_result: 1, milestone: 2, task: 3, daily_action: 4,
|
|
374
|
+
};
|
|
375
|
+
return depths[level] ?? 0;
|
|
376
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Tools Guard — Module-level flag for --no-local-tools mode.
|
|
3
|
+
*
|
|
4
|
+
* Separate file to avoid circular dependencies between builtin.ts and desktop.ts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
let _noLocalTools = false;
|
|
8
|
+
|
|
9
|
+
export function setNoLocalTools(enabled: boolean): void {
|
|
10
|
+
_noLocalTools = enabled;
|
|
11
|
+
if (enabled) {
|
|
12
|
+
console.log('[Tools] Local tool execution disabled (--no-local-tools). Tools require a target sidecar.');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isNoLocalTools(): boolean {
|
|
17
|
+
return _noLocalTools;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const LOCAL_DISABLED_MSG = 'Error: Local tool execution is disabled (--no-local-tools). Specify a "target" sidecar to route this command to a remote machine. Use list_sidecars to see available sidecars.';
|
|
21
|
+
|
|
22
|
+
/** Default working directory for tools — set by site builder context per conversation turn. */
|
|
23
|
+
let _defaultCwd: string | null = null;
|
|
24
|
+
|
|
25
|
+
export function setDefaultCwd(cwd: string | null): void {
|
|
26
|
+
_defaultCwd = cwd;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getDefaultCwd(): string | null {
|
|
30
|
+
return _defaultCwd;
|
|
31
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { ContentBlock } from '../../llm/provider.ts';
|
|
2
|
+
|
|
3
|
+
export type ToolParameter = {
|
|
4
|
+
type: string;
|
|
5
|
+
description: string;
|
|
6
|
+
required: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type ToolResult = {
|
|
10
|
+
content: ContentBlock[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function isToolResult(v: unknown): v is ToolResult {
|
|
14
|
+
return v !== null && typeof v === 'object' && 'content' in (v as object) && Array.isArray((v as ToolResult).content);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type ToolDefinition = {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
category: string;
|
|
21
|
+
parameters: Record<string, ToolParameter>;
|
|
22
|
+
execute: (params: Record<string, unknown>) => Promise<unknown>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export class ToolRegistry {
|
|
26
|
+
private tools: Map<string, ToolDefinition> = new Map();
|
|
27
|
+
/** Default working directory for tools like run_command. Set by site builder context. */
|
|
28
|
+
defaultCwd: string | null = null;
|
|
29
|
+
|
|
30
|
+
register(tool: ToolDefinition): void {
|
|
31
|
+
if (this.tools.has(tool.name)) {
|
|
32
|
+
throw new Error(`Tool '${tool.name}' is already registered`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.validateToolDefinition(tool);
|
|
36
|
+
this.tools.set(tool.name, tool);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get(name: string): ToolDefinition | undefined {
|
|
40
|
+
return this.tools.get(name);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
list(category?: string): ToolDefinition[] {
|
|
44
|
+
const allTools = Array.from(this.tools.values());
|
|
45
|
+
|
|
46
|
+
if (!category) {
|
|
47
|
+
return allTools;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return allTools.filter(tool => tool.category === category);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
has(name: string): boolean {
|
|
54
|
+
return this.tools.has(name);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async execute(name: string, params: Record<string, unknown>): Promise<unknown> {
|
|
58
|
+
const tool = this.tools.get(name);
|
|
59
|
+
|
|
60
|
+
if (!tool) {
|
|
61
|
+
throw new Error(`Tool '${name}' not found in registry`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.validateParameters(tool, params);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
return await tool.execute(params);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Tool '${name}' execution failed: ${error instanceof Error ? error.message : String(error)}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
unregister(name: string): boolean {
|
|
76
|
+
return this.tools.delete(name);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
clear(): void {
|
|
80
|
+
this.tools.clear();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getCategories(): string[] {
|
|
84
|
+
const categories = new Set<string>();
|
|
85
|
+
|
|
86
|
+
for (const tool of this.tools.values()) {
|
|
87
|
+
categories.add(tool.category);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return Array.from(categories).sort();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
count(): number {
|
|
94
|
+
return this.tools.size;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private validateToolDefinition(tool: ToolDefinition): void {
|
|
98
|
+
if (!tool.name || typeof tool.name !== 'string') {
|
|
99
|
+
throw new Error('Tool must have a valid name');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!tool.description || typeof tool.description !== 'string') {
|
|
103
|
+
throw new Error(`Tool '${tool.name}' must have a description`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!tool.category || typeof tool.category !== 'string') {
|
|
107
|
+
throw new Error(`Tool '${tool.name}' must have a category`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (typeof tool.execute !== 'function') {
|
|
111
|
+
throw new Error(`Tool '${tool.name}' must have an execute function`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (typeof tool.parameters !== 'object' || tool.parameters === null) {
|
|
115
|
+
throw new Error(`Tool '${tool.name}' must have a parameters object`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const [paramName, paramDef] of Object.entries(tool.parameters)) {
|
|
119
|
+
if (!paramDef.type || typeof paramDef.type !== 'string') {
|
|
120
|
+
throw new Error(`Parameter '${paramName}' in tool '${tool.name}' must have a type`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!paramDef.description || typeof paramDef.description !== 'string') {
|
|
124
|
+
throw new Error(`Parameter '${paramName}' in tool '${tool.name}' must have a description`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (typeof paramDef.required !== 'boolean') {
|
|
128
|
+
throw new Error(`Parameter '${paramName}' in tool '${tool.name}' must specify if it's required`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private validateParameters(tool: ToolDefinition, params: Record<string, unknown>): void {
|
|
134
|
+
for (const [paramName, paramDef] of Object.entries(tool.parameters)) {
|
|
135
|
+
const value = params[paramName];
|
|
136
|
+
|
|
137
|
+
if (paramDef.required && (value === undefined || value === null)) {
|
|
138
|
+
throw new Error(`Required parameter '${paramName}' missing for tool '${tool.name}'`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (value !== undefined && value !== null) {
|
|
142
|
+
const actualType = typeof value;
|
|
143
|
+
const expectedType = paramDef.type.toLowerCase();
|
|
144
|
+
|
|
145
|
+
if (expectedType === 'array' && !Array.isArray(value)) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Parameter '${paramName}' for tool '${tool.name}' must be an array, got ${actualType}`
|
|
148
|
+
);
|
|
149
|
+
} else if (expectedType === 'object' && (actualType !== 'object' || Array.isArray(value))) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`Parameter '${paramName}' for tool '${tool.name}' must be an object, got ${actualType}`
|
|
152
|
+
);
|
|
153
|
+
} else if (
|
|
154
|
+
expectedType !== 'array' &&
|
|
155
|
+
expectedType !== 'object' &&
|
|
156
|
+
actualType !== expectedType &&
|
|
157
|
+
!(expectedType === 'number' && actualType === 'bigint')
|
|
158
|
+
) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Parameter '${paramName}' for tool '${tool.name}' must be ${expectedType}, got ${actualType}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const unexpectedParams = Object.keys(params).filter(key => !tool.parameters[key]);
|
|
167
|
+
if (unexpectedParams.length > 0) {
|
|
168
|
+
console.warn(
|
|
169
|
+
`Unexpected parameters for tool '${tool.name}': ${unexpectedParams.join(', ')}`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Research Queue Tool
|
|
3
|
+
*
|
|
4
|
+
* Lets the agent (or user via chat) manage the background research queue.
|
|
5
|
+
* Actions: add, list, remove.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ToolDefinition } from './registry.ts';
|
|
9
|
+
import type { ResearchQueue, ResearchPriority } from '../../daemon/research-queue.ts';
|
|
10
|
+
|
|
11
|
+
let queueRef: ResearchQueue | null = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Wire the research queue instance. Must be called before tool use.
|
|
15
|
+
*/
|
|
16
|
+
export function setResearchQueueRef(queue: ResearchQueue): void {
|
|
17
|
+
queueRef = queue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const researchQueueTool: ToolDefinition = {
|
|
21
|
+
name: 'research_queue',
|
|
22
|
+
description: 'Manage the background research queue. Add topics for JARVIS to research during idle time, list current queue, or remove topics.',
|
|
23
|
+
category: 'productivity',
|
|
24
|
+
parameters: {
|
|
25
|
+
action: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Action to perform: "add", "list", or "remove"',
|
|
28
|
+
required: true,
|
|
29
|
+
},
|
|
30
|
+
topic: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'The research topic (required for "add")',
|
|
33
|
+
required: false,
|
|
34
|
+
},
|
|
35
|
+
reason: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'Why this topic should be researched (required for "add")',
|
|
38
|
+
required: false,
|
|
39
|
+
},
|
|
40
|
+
priority: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
description: 'Priority: "high", "normal", or "low" (default: "normal", for "add" only)',
|
|
43
|
+
required: false,
|
|
44
|
+
},
|
|
45
|
+
id: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
description: 'Topic ID to remove (required for "remove")',
|
|
48
|
+
required: false,
|
|
49
|
+
},
|
|
50
|
+
status: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'Filter by status for "list": "queued", "in_progress", "completed", "failed"',
|
|
53
|
+
required: false,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
execute: async (params) => {
|
|
57
|
+
if (!queueRef) {
|
|
58
|
+
return { error: 'Research queue not initialized' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const action = String(params.action ?? '');
|
|
62
|
+
|
|
63
|
+
switch (action) {
|
|
64
|
+
case 'add': {
|
|
65
|
+
const topic = String(params.topic ?? '');
|
|
66
|
+
const reason = String(params.reason ?? 'No reason provided');
|
|
67
|
+
const priority = (params.priority as ResearchPriority) ?? 'normal';
|
|
68
|
+
|
|
69
|
+
if (!topic) {
|
|
70
|
+
return { error: 'Missing "topic" parameter' };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const entry = queueRef.addTopic(topic, reason, 'agent', priority);
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
id: entry.id,
|
|
77
|
+
message: `Added to research queue: "${topic}" (${priority} priority)`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case 'list': {
|
|
82
|
+
const status = params.status as any;
|
|
83
|
+
const topics = queueRef.list(status || undefined);
|
|
84
|
+
return {
|
|
85
|
+
count: topics.length,
|
|
86
|
+
topics: topics.map((t) => ({
|
|
87
|
+
id: t.id,
|
|
88
|
+
topic: t.topic,
|
|
89
|
+
reason: t.reason,
|
|
90
|
+
priority: t.priority,
|
|
91
|
+
status: t.status,
|
|
92
|
+
source: t.source,
|
|
93
|
+
result: t.result ? (t.result.length > 200 ? t.result.slice(0, 197) + '...' : t.result) : undefined,
|
|
94
|
+
})),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
case 'remove': {
|
|
99
|
+
const id = String(params.id ?? '');
|
|
100
|
+
if (!id) {
|
|
101
|
+
return { error: 'Missing "id" parameter' };
|
|
102
|
+
}
|
|
103
|
+
const removed = queueRef.remove(id);
|
|
104
|
+
return { success: removed, message: removed ? 'Topic removed' : 'Topic not found' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
default:
|
|
108
|
+
return { error: `Unknown action: "${action}". Use "add", "list", or "remove".` };
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
};
|