cowork-os 0.3.21
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 +21 -0
- package/README.md +1638 -0
- package/bin/cowork.js +42 -0
- package/build/entitlements.mac.plist +16 -0
- package/build/icon.icns +0 -0
- package/build/icon.png +0 -0
- package/dist/electron/electron/activity/ActivityRepository.js +190 -0
- package/dist/electron/electron/agent/browser/browser-service.js +639 -0
- package/dist/electron/electron/agent/context-manager.js +225 -0
- package/dist/electron/electron/agent/custom-skill-loader.js +566 -0
- package/dist/electron/electron/agent/daemon.js +975 -0
- package/dist/electron/electron/agent/executor.js +3561 -0
- package/dist/electron/electron/agent/llm/anthropic-provider.js +155 -0
- package/dist/electron/electron/agent/llm/bedrock-provider.js +202 -0
- package/dist/electron/electron/agent/llm/gemini-provider.js +375 -0
- package/dist/electron/electron/agent/llm/index.js +34 -0
- package/dist/electron/electron/agent/llm/ollama-provider.js +263 -0
- package/dist/electron/electron/agent/llm/openai-oauth.js +101 -0
- package/dist/electron/electron/agent/llm/openai-provider.js +657 -0
- package/dist/electron/electron/agent/llm/openrouter-provider.js +232 -0
- package/dist/electron/electron/agent/llm/pricing.js +160 -0
- package/dist/electron/electron/agent/llm/provider-factory.js +880 -0
- package/dist/electron/electron/agent/llm/types.js +178 -0
- package/dist/electron/electron/agent/queue-manager.js +378 -0
- package/dist/electron/electron/agent/sandbox/docker-sandbox.js +402 -0
- package/dist/electron/electron/agent/sandbox/macos-sandbox.js +407 -0
- package/dist/electron/electron/agent/sandbox/runner.js +410 -0
- package/dist/electron/electron/agent/sandbox/sandbox-factory.js +228 -0
- package/dist/electron/electron/agent/sandbox/security-utils.js +258 -0
- package/dist/electron/electron/agent/search/brave-provider.js +119 -0
- package/dist/electron/electron/agent/search/google-provider.js +100 -0
- package/dist/electron/electron/agent/search/index.js +28 -0
- package/dist/electron/electron/agent/search/provider-factory.js +395 -0
- package/dist/electron/electron/agent/search/serpapi-provider.js +112 -0
- package/dist/electron/electron/agent/search/tavily-provider.js +90 -0
- package/dist/electron/electron/agent/search/types.js +40 -0
- package/dist/electron/electron/agent/security/index.js +12 -0
- package/dist/electron/electron/agent/security/input-sanitizer.js +303 -0
- package/dist/electron/electron/agent/security/output-filter.js +217 -0
- package/dist/electron/electron/agent/skill-eligibility.js +281 -0
- package/dist/electron/electron/agent/skill-registry.js +396 -0
- package/dist/electron/electron/agent/skills/document.js +878 -0
- package/dist/electron/electron/agent/skills/image-generator.js +225 -0
- package/dist/electron/electron/agent/skills/organizer.js +141 -0
- package/dist/electron/electron/agent/skills/presentation.js +367 -0
- package/dist/electron/electron/agent/skills/spreadsheet.js +165 -0
- package/dist/electron/electron/agent/tools/browser-tools.js +523 -0
- package/dist/electron/electron/agent/tools/builtin-settings.js +384 -0
- package/dist/electron/electron/agent/tools/canvas-tools.js +530 -0
- package/dist/electron/electron/agent/tools/cron-tools.js +577 -0
- package/dist/electron/electron/agent/tools/edit-tools.js +194 -0
- package/dist/electron/electron/agent/tools/file-tools.js +719 -0
- package/dist/electron/electron/agent/tools/glob-tools.js +283 -0
- package/dist/electron/electron/agent/tools/grep-tools.js +387 -0
- package/dist/electron/electron/agent/tools/image-tools.js +111 -0
- package/dist/electron/electron/agent/tools/mention-tools.js +282 -0
- package/dist/electron/electron/agent/tools/node-tools.js +476 -0
- package/dist/electron/electron/agent/tools/registry.js +2719 -0
- package/dist/electron/electron/agent/tools/search-tools.js +91 -0
- package/dist/electron/electron/agent/tools/shell-tools.js +574 -0
- package/dist/electron/electron/agent/tools/skill-tools.js +274 -0
- package/dist/electron/electron/agent/tools/system-tools.js +578 -0
- package/dist/electron/electron/agent/tools/web-fetch-tools.js +444 -0
- package/dist/electron/electron/agent/tools/x-tools.js +264 -0
- package/dist/electron/electron/agents/AgentRoleRepository.js +420 -0
- package/dist/electron/electron/agents/HeartbeatService.js +356 -0
- package/dist/electron/electron/agents/MentionRepository.js +197 -0
- package/dist/electron/electron/agents/TaskSubscriptionRepository.js +168 -0
- package/dist/electron/electron/agents/WorkingStateRepository.js +229 -0
- package/dist/electron/electron/canvas/canvas-manager.js +714 -0
- package/dist/electron/electron/canvas/canvas-preload.js +53 -0
- package/dist/electron/electron/canvas/canvas-protocol.js +195 -0
- package/dist/electron/electron/canvas/canvas-store.js +174 -0
- package/dist/electron/electron/canvas/index.js +13 -0
- package/dist/electron/electron/control-plane/client.js +364 -0
- package/dist/electron/electron/control-plane/handlers.js +572 -0
- package/dist/electron/electron/control-plane/index.js +41 -0
- package/dist/electron/electron/control-plane/node-manager.js +264 -0
- package/dist/electron/electron/control-plane/protocol.js +194 -0
- package/dist/electron/electron/control-plane/remote-client.js +437 -0
- package/dist/electron/electron/control-plane/server.js +640 -0
- package/dist/electron/electron/control-plane/settings.js +369 -0
- package/dist/electron/electron/control-plane/ssh-tunnel.js +549 -0
- package/dist/electron/electron/cron/index.js +30 -0
- package/dist/electron/electron/cron/schedule.js +190 -0
- package/dist/electron/electron/cron/service.js +614 -0
- package/dist/electron/electron/cron/store.js +155 -0
- package/dist/electron/electron/cron/types.js +82 -0
- package/dist/electron/electron/cron/webhook.js +258 -0
- package/dist/electron/electron/database/SecureSettingsRepository.js +444 -0
- package/dist/electron/electron/database/TaskLabelRepository.js +120 -0
- package/dist/electron/electron/database/repositories.js +1781 -0
- package/dist/electron/electron/database/schema.js +978 -0
- package/dist/electron/electron/extensions/index.js +33 -0
- package/dist/electron/electron/extensions/loader.js +313 -0
- package/dist/electron/electron/extensions/registry.js +485 -0
- package/dist/electron/electron/extensions/types.js +11 -0
- package/dist/electron/electron/gateway/channel-registry.js +1102 -0
- package/dist/electron/electron/gateway/channels/bluebubbles-client.js +479 -0
- package/dist/electron/electron/gateway/channels/bluebubbles.js +432 -0
- package/dist/electron/electron/gateway/channels/discord.js +975 -0
- package/dist/electron/electron/gateway/channels/email-client.js +593 -0
- package/dist/electron/electron/gateway/channels/email.js +443 -0
- package/dist/electron/electron/gateway/channels/google-chat.js +631 -0
- package/dist/electron/electron/gateway/channels/imessage-client.js +363 -0
- package/dist/electron/electron/gateway/channels/imessage.js +465 -0
- package/dist/electron/electron/gateway/channels/index.js +36 -0
- package/dist/electron/electron/gateway/channels/line-client.js +470 -0
- package/dist/electron/electron/gateway/channels/line.js +479 -0
- package/dist/electron/electron/gateway/channels/matrix-client.js +432 -0
- package/dist/electron/electron/gateway/channels/matrix.js +592 -0
- package/dist/electron/electron/gateway/channels/mattermost-client.js +394 -0
- package/dist/electron/electron/gateway/channels/mattermost.js +496 -0
- package/dist/electron/electron/gateway/channels/signal-client.js +500 -0
- package/dist/electron/electron/gateway/channels/signal.js +582 -0
- package/dist/electron/electron/gateway/channels/slack.js +415 -0
- package/dist/electron/electron/gateway/channels/teams.js +596 -0
- package/dist/electron/electron/gateway/channels/telegram.js +1390 -0
- package/dist/electron/electron/gateway/channels/twitch-client.js +502 -0
- package/dist/electron/electron/gateway/channels/twitch.js +396 -0
- package/dist/electron/electron/gateway/channels/types.js +8 -0
- package/dist/electron/electron/gateway/channels/whatsapp.js +953 -0
- package/dist/electron/electron/gateway/context-policy.js +268 -0
- package/dist/electron/electron/gateway/index.js +1063 -0
- package/dist/electron/electron/gateway/infrastructure.js +496 -0
- package/dist/electron/electron/gateway/router.js +2700 -0
- package/dist/electron/electron/gateway/security.js +375 -0
- package/dist/electron/electron/gateway/session.js +115 -0
- package/dist/electron/electron/gateway/tunnel.js +503 -0
- package/dist/electron/electron/guardrails/guardrail-manager.js +348 -0
- package/dist/electron/electron/hooks/gmail-watcher.js +300 -0
- package/dist/electron/electron/hooks/index.js +46 -0
- package/dist/electron/electron/hooks/mappings.js +381 -0
- package/dist/electron/electron/hooks/server.js +480 -0
- package/dist/electron/electron/hooks/settings.js +447 -0
- package/dist/electron/electron/hooks/types.js +41 -0
- package/dist/electron/electron/ipc/canvas-handlers.js +158 -0
- package/dist/electron/electron/ipc/handlers.js +3138 -0
- package/dist/electron/electron/ipc/mission-control-handlers.js +141 -0
- package/dist/electron/electron/main.js +448 -0
- package/dist/electron/electron/mcp/client/MCPClientManager.js +330 -0
- package/dist/electron/electron/mcp/client/MCPServerConnection.js +437 -0
- package/dist/electron/electron/mcp/client/transports/SSETransport.js +304 -0
- package/dist/electron/electron/mcp/client/transports/StdioTransport.js +307 -0
- package/dist/electron/electron/mcp/client/transports/WebSocketTransport.js +329 -0
- package/dist/electron/electron/mcp/host/MCPHostServer.js +354 -0
- package/dist/electron/electron/mcp/host/ToolAdapter.js +100 -0
- package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +497 -0
- package/dist/electron/electron/mcp/settings.js +446 -0
- package/dist/electron/electron/mcp/types.js +59 -0
- package/dist/electron/electron/memory/MemoryService.js +435 -0
- package/dist/electron/electron/notifications/index.js +17 -0
- package/dist/electron/electron/notifications/service.js +118 -0
- package/dist/electron/electron/notifications/store.js +144 -0
- package/dist/electron/electron/preload.js +842 -0
- package/dist/electron/electron/reports/StandupReportService.js +272 -0
- package/dist/electron/electron/security/concurrency.js +293 -0
- package/dist/electron/electron/security/index.js +15 -0
- package/dist/electron/electron/security/policy-manager.js +435 -0
- package/dist/electron/electron/settings/appearance-manager.js +193 -0
- package/dist/electron/electron/settings/personality-manager.js +724 -0
- package/dist/electron/electron/settings/x-manager.js +58 -0
- package/dist/electron/electron/tailscale/exposure.js +188 -0
- package/dist/electron/electron/tailscale/index.js +28 -0
- package/dist/electron/electron/tailscale/settings.js +205 -0
- package/dist/electron/electron/tailscale/tailscale.js +355 -0
- package/dist/electron/electron/tray/QuickInputWindow.js +568 -0
- package/dist/electron/electron/tray/TrayManager.js +895 -0
- package/dist/electron/electron/tray/index.js +9 -0
- package/dist/electron/electron/updater/index.js +6 -0
- package/dist/electron/electron/updater/update-manager.js +418 -0
- package/dist/electron/electron/utils/env-migration.js +209 -0
- package/dist/electron/electron/utils/process.js +102 -0
- package/dist/electron/electron/utils/rate-limiter.js +104 -0
- package/dist/electron/electron/utils/validation.js +419 -0
- package/dist/electron/electron/utils/x-cli.js +177 -0
- package/dist/electron/electron/voice/VoiceService.js +507 -0
- package/dist/electron/electron/voice/index.js +14 -0
- package/dist/electron/electron/voice/voice-settings-manager.js +359 -0
- package/dist/electron/shared/channelMessages.js +170 -0
- package/dist/electron/shared/types.js +1185 -0
- package/package.json +159 -0
- package/resources/skills/1password.json +10 -0
- package/resources/skills/add-documentation.json +31 -0
- package/resources/skills/analyze-csv.json +17 -0
- package/resources/skills/apple-notes.json +10 -0
- package/resources/skills/apple-reminders.json +10 -0
- package/resources/skills/auto-commenter.json +10 -0
- package/resources/skills/bear-notes.json +10 -0
- package/resources/skills/bird.json +35 -0
- package/resources/skills/blogwatcher.json +10 -0
- package/resources/skills/blucli.json +10 -0
- package/resources/skills/bluebubbles.json +10 -0
- package/resources/skills/camsnap.json +10 -0
- package/resources/skills/clean-imports.json +18 -0
- package/resources/skills/code-review.json +18 -0
- package/resources/skills/coding-agent.json +10 -0
- package/resources/skills/compare-files.json +23 -0
- package/resources/skills/convert-code.json +34 -0
- package/resources/skills/create-changelog.json +24 -0
- package/resources/skills/debug-error.json +17 -0
- package/resources/skills/dependency-check.json +10 -0
- package/resources/skills/discord.json +10 -0
- package/resources/skills/eightctl.json +10 -0
- package/resources/skills/explain-code.json +29 -0
- package/resources/skills/extract-todos.json +18 -0
- package/resources/skills/food-order.json +10 -0
- package/resources/skills/gemini.json +10 -0
- package/resources/skills/generate-readme.json +10 -0
- package/resources/skills/gifgrep.json +10 -0
- package/resources/skills/git-commit.json +10 -0
- package/resources/skills/github.json +10 -0
- package/resources/skills/gog.json +10 -0
- package/resources/skills/goplaces.json +10 -0
- package/resources/skills/himalaya.json +10 -0
- package/resources/skills/imsg.json +10 -0
- package/resources/skills/karpathy-guidelines.json +12 -0
- package/resources/skills/last30days.json +26 -0
- package/resources/skills/local-places.json +10 -0
- package/resources/skills/mcporter.json +10 -0
- package/resources/skills/model-usage.json +10 -0
- package/resources/skills/nano-banana-pro.json +10 -0
- package/resources/skills/nano-pdf.json +10 -0
- package/resources/skills/notion.json +10 -0
- package/resources/skills/obsidian.json +10 -0
- package/resources/skills/openai-image-gen.json +10 -0
- package/resources/skills/openai-whisper-api.json +10 -0
- package/resources/skills/openai-whisper.json +10 -0
- package/resources/skills/openhue.json +10 -0
- package/resources/skills/oracle.json +10 -0
- package/resources/skills/ordercli.json +10 -0
- package/resources/skills/peekaboo.json +10 -0
- package/resources/skills/project-structure.json +10 -0
- package/resources/skills/proofread.json +17 -0
- package/resources/skills/refactor-code.json +31 -0
- package/resources/skills/rename-symbol.json +23 -0
- package/resources/skills/sag.json +10 -0
- package/resources/skills/security-audit.json +18 -0
- package/resources/skills/session-logs.json +10 -0
- package/resources/skills/sherpa-onnx-tts.json +10 -0
- package/resources/skills/skill-creator.json +15 -0
- package/resources/skills/skill-hub.json +29 -0
- package/resources/skills/slack.json +10 -0
- package/resources/skills/songsee.json +10 -0
- package/resources/skills/sonoscli.json +10 -0
- package/resources/skills/spotify-player.json +10 -0
- package/resources/skills/startup-cfo.json +55 -0
- package/resources/skills/summarize-folder.json +18 -0
- package/resources/skills/summarize.json +10 -0
- package/resources/skills/things-mac.json +10 -0
- package/resources/skills/tmux.json +10 -0
- package/resources/skills/translate.json +36 -0
- package/resources/skills/trello.json +10 -0
- package/resources/skills/video-frames.json +10 -0
- package/resources/skills/voice-call.json +10 -0
- package/resources/skills/wacli.json +10 -0
- package/resources/skills/weather.json +10 -0
- package/resources/skills/write-tests.json +31 -0
- package/src/electron/activity/ActivityRepository.ts +238 -0
- package/src/electron/agent/browser/browser-service.ts +721 -0
- package/src/electron/agent/context-manager.ts +257 -0
- package/src/electron/agent/custom-skill-loader.ts +634 -0
- package/src/electron/agent/daemon.ts +1097 -0
- package/src/electron/agent/executor.ts +4017 -0
- package/src/electron/agent/llm/anthropic-provider.ts +175 -0
- package/src/electron/agent/llm/bedrock-provider.ts +236 -0
- package/src/electron/agent/llm/gemini-provider.ts +422 -0
- package/src/electron/agent/llm/index.ts +9 -0
- package/src/electron/agent/llm/ollama-provider.ts +347 -0
- package/src/electron/agent/llm/openai-oauth.ts +127 -0
- package/src/electron/agent/llm/openai-provider.ts +686 -0
- package/src/electron/agent/llm/openrouter-provider.ts +273 -0
- package/src/electron/agent/llm/pricing.ts +180 -0
- package/src/electron/agent/llm/provider-factory.ts +971 -0
- package/src/electron/agent/llm/types.ts +291 -0
- package/src/electron/agent/queue-manager.ts +408 -0
- package/src/electron/agent/sandbox/docker-sandbox.ts +453 -0
- package/src/electron/agent/sandbox/macos-sandbox.ts +426 -0
- package/src/electron/agent/sandbox/runner.ts +453 -0
- package/src/electron/agent/sandbox/sandbox-factory.ts +337 -0
- package/src/electron/agent/sandbox/security-utils.ts +251 -0
- package/src/electron/agent/search/brave-provider.ts +141 -0
- package/src/electron/agent/search/google-provider.ts +131 -0
- package/src/electron/agent/search/index.ts +6 -0
- package/src/electron/agent/search/provider-factory.ts +450 -0
- package/src/electron/agent/search/serpapi-provider.ts +138 -0
- package/src/electron/agent/search/tavily-provider.ts +108 -0
- package/src/electron/agent/search/types.ts +118 -0
- package/src/electron/agent/security/index.ts +20 -0
- package/src/electron/agent/security/input-sanitizer.ts +380 -0
- package/src/electron/agent/security/output-filter.ts +259 -0
- package/src/electron/agent/skill-eligibility.ts +334 -0
- package/src/electron/agent/skill-registry.ts +457 -0
- package/src/electron/agent/skills/document.ts +1070 -0
- package/src/electron/agent/skills/image-generator.ts +272 -0
- package/src/electron/agent/skills/organizer.ts +131 -0
- package/src/electron/agent/skills/presentation.ts +418 -0
- package/src/electron/agent/skills/spreadsheet.ts +166 -0
- package/src/electron/agent/tools/browser-tools.ts +546 -0
- package/src/electron/agent/tools/builtin-settings.ts +422 -0
- package/src/electron/agent/tools/canvas-tools.ts +572 -0
- package/src/electron/agent/tools/cron-tools.ts +723 -0
- package/src/electron/agent/tools/edit-tools.ts +196 -0
- package/src/electron/agent/tools/file-tools.ts +811 -0
- package/src/electron/agent/tools/glob-tools.ts +303 -0
- package/src/electron/agent/tools/grep-tools.ts +432 -0
- package/src/electron/agent/tools/image-tools.ts +126 -0
- package/src/electron/agent/tools/mention-tools.ts +371 -0
- package/src/electron/agent/tools/node-tools.ts +550 -0
- package/src/electron/agent/tools/registry.ts +3052 -0
- package/src/electron/agent/tools/search-tools.ts +111 -0
- package/src/electron/agent/tools/shell-tools.ts +651 -0
- package/src/electron/agent/tools/skill-tools.ts +340 -0
- package/src/electron/agent/tools/system-tools.ts +665 -0
- package/src/electron/agent/tools/web-fetch-tools.ts +528 -0
- package/src/electron/agent/tools/x-tools.ts +267 -0
- package/src/electron/agents/AgentRoleRepository.ts +557 -0
- package/src/electron/agents/HeartbeatService.ts +469 -0
- package/src/electron/agents/MentionRepository.ts +242 -0
- package/src/electron/agents/TaskSubscriptionRepository.ts +231 -0
- package/src/electron/agents/WorkingStateRepository.ts +278 -0
- package/src/electron/canvas/canvas-manager.ts +818 -0
- package/src/electron/canvas/canvas-preload.ts +102 -0
- package/src/electron/canvas/canvas-protocol.ts +174 -0
- package/src/electron/canvas/canvas-store.ts +200 -0
- package/src/electron/canvas/index.ts +8 -0
- package/src/electron/control-plane/client.ts +527 -0
- package/src/electron/control-plane/handlers.ts +723 -0
- package/src/electron/control-plane/index.ts +51 -0
- package/src/electron/control-plane/node-manager.ts +322 -0
- package/src/electron/control-plane/protocol.ts +269 -0
- package/src/electron/control-plane/remote-client.ts +517 -0
- package/src/electron/control-plane/server.ts +853 -0
- package/src/electron/control-plane/settings.ts +401 -0
- package/src/electron/control-plane/ssh-tunnel.ts +624 -0
- package/src/electron/cron/index.ts +9 -0
- package/src/electron/cron/schedule.ts +217 -0
- package/src/electron/cron/service.ts +743 -0
- package/src/electron/cron/store.ts +165 -0
- package/src/electron/cron/types.ts +291 -0
- package/src/electron/cron/webhook.ts +303 -0
- package/src/electron/database/SecureSettingsRepository.ts +514 -0
- package/src/electron/database/TaskLabelRepository.ts +148 -0
- package/src/electron/database/repositories.ts +2397 -0
- package/src/electron/database/schema.ts +1017 -0
- package/src/electron/extensions/index.ts +18 -0
- package/src/electron/extensions/loader.ts +336 -0
- package/src/electron/extensions/registry.ts +546 -0
- package/src/electron/extensions/types.ts +372 -0
- package/src/electron/gateway/channel-registry.ts +1267 -0
- package/src/electron/gateway/channels/bluebubbles-client.ts +641 -0
- package/src/electron/gateway/channels/bluebubbles.ts +509 -0
- package/src/electron/gateway/channels/discord.ts +1150 -0
- package/src/electron/gateway/channels/email-client.ts +708 -0
- package/src/electron/gateway/channels/email.ts +516 -0
- package/src/electron/gateway/channels/google-chat.ts +760 -0
- package/src/electron/gateway/channels/imessage-client.ts +473 -0
- package/src/electron/gateway/channels/imessage.ts +520 -0
- package/src/electron/gateway/channels/index.ts +21 -0
- package/src/electron/gateway/channels/line-client.ts +598 -0
- package/src/electron/gateway/channels/line.ts +559 -0
- package/src/electron/gateway/channels/matrix-client.ts +632 -0
- package/src/electron/gateway/channels/matrix.ts +655 -0
- package/src/electron/gateway/channels/mattermost-client.ts +526 -0
- package/src/electron/gateway/channels/mattermost.ts +550 -0
- package/src/electron/gateway/channels/signal-client.ts +722 -0
- package/src/electron/gateway/channels/signal.ts +666 -0
- package/src/electron/gateway/channels/slack.ts +458 -0
- package/src/electron/gateway/channels/teams.ts +681 -0
- package/src/electron/gateway/channels/telegram.ts +1727 -0
- package/src/electron/gateway/channels/twitch-client.ts +665 -0
- package/src/electron/gateway/channels/twitch.ts +468 -0
- package/src/electron/gateway/channels/types.ts +1002 -0
- package/src/electron/gateway/channels/whatsapp.ts +1101 -0
- package/src/electron/gateway/context-policy.ts +382 -0
- package/src/electron/gateway/index.ts +1274 -0
- package/src/electron/gateway/infrastructure.ts +645 -0
- package/src/electron/gateway/router.ts +3206 -0
- package/src/electron/gateway/security.ts +422 -0
- package/src/electron/gateway/session.ts +144 -0
- package/src/electron/gateway/tunnel.ts +626 -0
- package/src/electron/guardrails/guardrail-manager.ts +380 -0
- package/src/electron/hooks/gmail-watcher.ts +355 -0
- package/src/electron/hooks/index.ts +30 -0
- package/src/electron/hooks/mappings.ts +404 -0
- package/src/electron/hooks/server.ts +574 -0
- package/src/electron/hooks/settings.ts +466 -0
- package/src/electron/hooks/types.ts +245 -0
- package/src/electron/ipc/canvas-handlers.ts +223 -0
- package/src/electron/ipc/handlers.ts +3661 -0
- package/src/electron/ipc/mission-control-handlers.ts +182 -0
- package/src/electron/main.ts +496 -0
- package/src/electron/mcp/client/MCPClientManager.ts +406 -0
- package/src/electron/mcp/client/MCPServerConnection.ts +514 -0
- package/src/electron/mcp/client/transports/SSETransport.ts +360 -0
- package/src/electron/mcp/client/transports/StdioTransport.ts +355 -0
- package/src/electron/mcp/client/transports/WebSocketTransport.ts +384 -0
- package/src/electron/mcp/host/MCPHostServer.ts +388 -0
- package/src/electron/mcp/host/ToolAdapter.ts +140 -0
- package/src/electron/mcp/registry/MCPRegistryManager.ts +565 -0
- package/src/electron/mcp/settings.ts +468 -0
- package/src/electron/mcp/types.ts +371 -0
- package/src/electron/memory/MemoryService.ts +523 -0
- package/src/electron/notifications/index.ts +16 -0
- package/src/electron/notifications/service.ts +161 -0
- package/src/electron/notifications/store.ts +163 -0
- package/src/electron/preload.ts +2845 -0
- package/src/electron/reports/StandupReportService.ts +356 -0
- package/src/electron/security/concurrency.ts +333 -0
- package/src/electron/security/index.ts +17 -0
- package/src/electron/security/policy-manager.ts +539 -0
- package/src/electron/settings/appearance-manager.ts +182 -0
- package/src/electron/settings/personality-manager.ts +800 -0
- package/src/electron/settings/x-manager.ts +62 -0
- package/src/electron/tailscale/exposure.ts +262 -0
- package/src/electron/tailscale/index.ts +34 -0
- package/src/electron/tailscale/settings.ts +218 -0
- package/src/electron/tailscale/tailscale.ts +379 -0
- package/src/electron/tray/QuickInputWindow.ts +609 -0
- package/src/electron/tray/TrayManager.ts +1005 -0
- package/src/electron/tray/index.ts +6 -0
- package/src/electron/updater/index.ts +1 -0
- package/src/electron/updater/update-manager.ts +447 -0
- package/src/electron/utils/env-migration.ts +203 -0
- package/src/electron/utils/process.ts +124 -0
- package/src/electron/utils/rate-limiter.ts +130 -0
- package/src/electron/utils/validation.ts +493 -0
- package/src/electron/utils/x-cli.ts +198 -0
- package/src/electron/voice/VoiceService.ts +583 -0
- package/src/electron/voice/index.ts +9 -0
- package/src/electron/voice/voice-settings-manager.ts +403 -0
- package/src/renderer/App.tsx +775 -0
- package/src/renderer/components/ActivityFeed.tsx +407 -0
- package/src/renderer/components/ActivityFeedItem.tsx +285 -0
- package/src/renderer/components/AgentRoleCard.tsx +343 -0
- package/src/renderer/components/AgentRoleEditor.tsx +805 -0
- package/src/renderer/components/AgentSquadSettings.tsx +295 -0
- package/src/renderer/components/AgentWorkingStatePanel.tsx +411 -0
- package/src/renderer/components/AppearanceSettings.tsx +122 -0
- package/src/renderer/components/ApprovalDialog.tsx +100 -0
- package/src/renderer/components/BlueBubblesSettings.tsx +505 -0
- package/src/renderer/components/BuiltinToolsSettings.tsx +307 -0
- package/src/renderer/components/CanvasPreview.tsx +1189 -0
- package/src/renderer/components/CommandOutput.tsx +202 -0
- package/src/renderer/components/ContextPolicySettings.tsx +523 -0
- package/src/renderer/components/ControlPlaneSettings.tsx +1134 -0
- package/src/renderer/components/DisclaimerModal.tsx +124 -0
- package/src/renderer/components/DiscordSettings.tsx +436 -0
- package/src/renderer/components/EmailSettings.tsx +606 -0
- package/src/renderer/components/ExtensionsSettings.tsx +542 -0
- package/src/renderer/components/FileViewer.tsx +224 -0
- package/src/renderer/components/GoogleChatSettings.tsx +535 -0
- package/src/renderer/components/GuardrailSettings.tsx +487 -0
- package/src/renderer/components/HooksSettings.tsx +581 -0
- package/src/renderer/components/ImessageSettings.tsx +484 -0
- package/src/renderer/components/LineSettings.tsx +483 -0
- package/src/renderer/components/MCPRegistryBrowser.tsx +386 -0
- package/src/renderer/components/MCPSettings.tsx +943 -0
- package/src/renderer/components/MainContent.tsx +2433 -0
- package/src/renderer/components/MatrixSettings.tsx +510 -0
- package/src/renderer/components/MattermostSettings.tsx +473 -0
- package/src/renderer/components/MemorySettings.tsx +247 -0
- package/src/renderer/components/MentionBadge.tsx +87 -0
- package/src/renderer/components/MentionInput.tsx +409 -0
- package/src/renderer/components/MentionList.tsx +476 -0
- package/src/renderer/components/MissionControlPanel.tsx +1995 -0
- package/src/renderer/components/NodesSettings.tsx +316 -0
- package/src/renderer/components/NotificationPanel.tsx +481 -0
- package/src/renderer/components/Onboarding/AwakeningOrb.tsx +44 -0
- package/src/renderer/components/Onboarding/Onboarding.tsx +443 -0
- package/src/renderer/components/Onboarding/TypewriterText.tsx +102 -0
- package/src/renderer/components/Onboarding/index.ts +3 -0
- package/src/renderer/components/OnboardingModal.tsx +698 -0
- package/src/renderer/components/PairingCodeDisplay.tsx +324 -0
- package/src/renderer/components/PersonalitySettings.tsx +597 -0
- package/src/renderer/components/QueueSettings.tsx +119 -0
- package/src/renderer/components/QuickTaskFAB.tsx +71 -0
- package/src/renderer/components/RightPanel.tsx +413 -0
- package/src/renderer/components/ScheduledTasksSettings.tsx +1328 -0
- package/src/renderer/components/SearchSettings.tsx +328 -0
- package/src/renderer/components/Settings.tsx +1504 -0
- package/src/renderer/components/Sidebar.tsx +344 -0
- package/src/renderer/components/SignalSettings.tsx +673 -0
- package/src/renderer/components/SkillHubBrowser.tsx +458 -0
- package/src/renderer/components/SkillParameterModal.tsx +185 -0
- package/src/renderer/components/SkillsSettings.tsx +451 -0
- package/src/renderer/components/SlackSettings.tsx +442 -0
- package/src/renderer/components/StandupReportViewer.tsx +614 -0
- package/src/renderer/components/TaskBoard.tsx +498 -0
- package/src/renderer/components/TaskBoardCard.tsx +357 -0
- package/src/renderer/components/TaskBoardColumn.tsx +211 -0
- package/src/renderer/components/TaskLabelManager.tsx +472 -0
- package/src/renderer/components/TaskQueuePanel.tsx +144 -0
- package/src/renderer/components/TaskQuickActions.tsx +492 -0
- package/src/renderer/components/TaskTimeline.tsx +216 -0
- package/src/renderer/components/TaskView.tsx +162 -0
- package/src/renderer/components/TeamsSettings.tsx +518 -0
- package/src/renderer/components/TelegramSettings.tsx +421 -0
- package/src/renderer/components/Toast.tsx +76 -0
- package/src/renderer/components/TraySettings.tsx +189 -0
- package/src/renderer/components/TwitchSettings.tsx +511 -0
- package/src/renderer/components/UpdateSettings.tsx +295 -0
- package/src/renderer/components/VoiceIndicator.tsx +270 -0
- package/src/renderer/components/VoiceSettings.tsx +867 -0
- package/src/renderer/components/WhatsAppSettings.tsx +721 -0
- package/src/renderer/components/WorkingStateEditor.tsx +309 -0
- package/src/renderer/components/WorkingStateHistory.tsx +481 -0
- package/src/renderer/components/WorkspaceSelector.tsx +150 -0
- package/src/renderer/components/XSettings.tsx +311 -0
- package/src/renderer/global.d.ts +9 -0
- package/src/renderer/hooks/useAgentContext.ts +153 -0
- package/src/renderer/hooks/useOnboardingFlow.ts +548 -0
- package/src/renderer/hooks/useVoiceInput.ts +268 -0
- package/src/renderer/index.html +12 -0
- package/src/renderer/main.tsx +10 -0
- package/src/renderer/public/cowork-os-logo.png +0 -0
- package/src/renderer/quick-input.html +164 -0
- package/src/renderer/styles/index.css +14504 -0
- package/src/renderer/utils/agentMessages.ts +749 -0
- package/src/renderer/utils/voice-directives.ts +169 -0
- package/src/shared/channelMessages.ts +213 -0
- package/src/shared/types.ts +3608 -0
- package/tsconfig.electron.json +26 -0
- package/tsconfig.json +26 -0
- package/tsconfig.node.json +10 -0
- package/vite.config.ts +23 -0
|
@@ -0,0 +1,2719 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ToolRegistry = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const fsPromises = __importStar(require("fs/promises"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const file_tools_1 = require("./file-tools");
|
|
41
|
+
const skill_tools_1 = require("./skill-tools");
|
|
42
|
+
const search_tools_1 = require("./search-tools");
|
|
43
|
+
const web_fetch_tools_1 = require("./web-fetch-tools");
|
|
44
|
+
const glob_tools_1 = require("./glob-tools");
|
|
45
|
+
const grep_tools_1 = require("./grep-tools");
|
|
46
|
+
const edit_tools_1 = require("./edit-tools");
|
|
47
|
+
const browser_tools_1 = require("./browser-tools");
|
|
48
|
+
const shell_tools_1 = require("./shell-tools");
|
|
49
|
+
const image_tools_1 = require("./image-tools");
|
|
50
|
+
const system_tools_1 = require("./system-tools");
|
|
51
|
+
const cron_tools_1 = require("./cron-tools");
|
|
52
|
+
const canvas_tools_1 = require("./canvas-tools");
|
|
53
|
+
const mention_tools_1 = require("./mention-tools");
|
|
54
|
+
const x_tools_1 = require("./x-tools");
|
|
55
|
+
const search_1 = require("../search");
|
|
56
|
+
const MCPClientManager_1 = require("../../mcp/client/MCPClientManager");
|
|
57
|
+
const settings_1 = require("../../mcp/settings");
|
|
58
|
+
const policy_manager_1 = require("../../security/policy-manager");
|
|
59
|
+
const builtin_settings_1 = require("./builtin-settings");
|
|
60
|
+
const custom_skill_loader_1 = require("../custom-skill-loader");
|
|
61
|
+
const personality_manager_1 = require("../../settings/personality-manager");
|
|
62
|
+
const types_1 = require("../../../shared/types");
|
|
63
|
+
/**
|
|
64
|
+
* ToolRegistry manages all available tools and their execution
|
|
65
|
+
* Integrates with SecurityPolicyManager for context-aware tool filtering
|
|
66
|
+
*/
|
|
67
|
+
class ToolRegistry {
|
|
68
|
+
constructor(workspace, daemon, taskId, gatewayContext) {
|
|
69
|
+
this.workspace = workspace;
|
|
70
|
+
this.daemon = daemon;
|
|
71
|
+
this.taskId = taskId;
|
|
72
|
+
this.shadowedToolsLogged = false;
|
|
73
|
+
this.fileTools = new file_tools_1.FileTools(workspace, daemon, taskId);
|
|
74
|
+
this.skillTools = new skill_tools_1.SkillTools(workspace, daemon, taskId);
|
|
75
|
+
this.searchTools = new search_tools_1.SearchTools(workspace, daemon, taskId);
|
|
76
|
+
this.webFetchTools = new web_fetch_tools_1.WebFetchTools(workspace, daemon, taskId);
|
|
77
|
+
this.globTools = new glob_tools_1.GlobTools(workspace, daemon, taskId);
|
|
78
|
+
this.grepTools = new grep_tools_1.GrepTools(workspace, daemon, taskId);
|
|
79
|
+
this.editTools = new edit_tools_1.EditTools(workspace, daemon, taskId);
|
|
80
|
+
this.browserTools = new browser_tools_1.BrowserTools(workspace, daemon, taskId);
|
|
81
|
+
this.shellTools = new shell_tools_1.ShellTools(workspace, daemon, taskId);
|
|
82
|
+
this.imageTools = new image_tools_1.ImageTools(workspace, daemon, taskId);
|
|
83
|
+
this.systemTools = new system_tools_1.SystemTools(workspace, daemon, taskId);
|
|
84
|
+
this.cronTools = new cron_tools_1.CronTools(workspace, daemon, taskId);
|
|
85
|
+
this.canvasTools = new canvas_tools_1.CanvasTools(workspace, daemon, taskId);
|
|
86
|
+
this.mentionTools = new mention_tools_1.MentionTools(workspace.id, taskId, daemon);
|
|
87
|
+
this.xTools = new x_tools_1.XTools(workspace, daemon, taskId);
|
|
88
|
+
this.gatewayContext = gatewayContext;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get the current workspace
|
|
92
|
+
*/
|
|
93
|
+
getWorkspace() {
|
|
94
|
+
return this.workspace;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Update the workspace for all tools
|
|
98
|
+
* Used when switching workspaces mid-task
|
|
99
|
+
*/
|
|
100
|
+
setWorkspace(workspace) {
|
|
101
|
+
this.workspace = workspace;
|
|
102
|
+
this.fileTools.setWorkspace(workspace);
|
|
103
|
+
this.skillTools.setWorkspace(workspace);
|
|
104
|
+
this.searchTools.setWorkspace(workspace);
|
|
105
|
+
this.webFetchTools.setWorkspace(workspace);
|
|
106
|
+
this.globTools.setWorkspace(workspace);
|
|
107
|
+
this.grepTools.setWorkspace(workspace);
|
|
108
|
+
this.editTools.setWorkspace(workspace);
|
|
109
|
+
this.browserTools.setWorkspace(workspace);
|
|
110
|
+
this.shellTools.setWorkspace(workspace);
|
|
111
|
+
this.imageTools.setWorkspace(workspace);
|
|
112
|
+
this.systemTools.setWorkspace(workspace);
|
|
113
|
+
this.cronTools.setWorkspace(workspace);
|
|
114
|
+
this.canvasTools.setWorkspace(workspace);
|
|
115
|
+
this.xTools.setWorkspace(workspace);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Enforce new canvas sessions for follow-up messages by setting a cutoff timestamp.
|
|
119
|
+
* Sessions created before the cutoff will be rejected for canvas_push/open_url.
|
|
120
|
+
*/
|
|
121
|
+
setCanvasSessionCutoff(cutoff) {
|
|
122
|
+
this.canvasTools.setSessionCutoff(cutoff);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Set the gateway context for tool filtering
|
|
126
|
+
* Used when task originates from Telegram/Discord/etc.
|
|
127
|
+
*/
|
|
128
|
+
setGatewayContext(context) {
|
|
129
|
+
this.gatewayContext = context;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Send stdin input to the currently running shell command
|
|
133
|
+
*/
|
|
134
|
+
sendStdin(input) {
|
|
135
|
+
return this.shellTools.sendStdin(input);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check if a shell command is currently running
|
|
139
|
+
*/
|
|
140
|
+
hasActiveShellProcess() {
|
|
141
|
+
return this.shellTools.hasActiveProcess();
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Kill the currently running shell command (send SIGINT)
|
|
145
|
+
* @param force - If true, send SIGKILL immediately instead of graceful escalation
|
|
146
|
+
*/
|
|
147
|
+
killShellProcess(force) {
|
|
148
|
+
return this.shellTools.killProcess(force);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Check if a tool is allowed based on security policy
|
|
152
|
+
*/
|
|
153
|
+
isToolAllowed(toolName) {
|
|
154
|
+
return (0, policy_manager_1.isToolAllowedQuick)(toolName, this.workspace, this.gatewayContext);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get all available tools in provider-agnostic format
|
|
158
|
+
* Filters tools based on workspace permissions, gateway context, and user settings
|
|
159
|
+
* Sorts tools by priority (high priority tools first)
|
|
160
|
+
*/
|
|
161
|
+
getTools() {
|
|
162
|
+
const allTools = [
|
|
163
|
+
...this.getFileToolDefinitions(),
|
|
164
|
+
...this.getSkillToolDefinitions(),
|
|
165
|
+
...glob_tools_1.GlobTools.getToolDefinitions(),
|
|
166
|
+
...grep_tools_1.GrepTools.getToolDefinitions(),
|
|
167
|
+
...edit_tools_1.EditTools.getToolDefinitions(),
|
|
168
|
+
...web_fetch_tools_1.WebFetchTools.getToolDefinitions(),
|
|
169
|
+
...browser_tools_1.BrowserTools.getToolDefinitions(),
|
|
170
|
+
];
|
|
171
|
+
// Only add search tool if a provider is configured
|
|
172
|
+
if (search_1.SearchProviderFactory.isAnyProviderConfigured()) {
|
|
173
|
+
allTools.push(...this.getSearchToolDefinitions());
|
|
174
|
+
}
|
|
175
|
+
// Only add X/Twitter tool if integration is enabled
|
|
176
|
+
if (x_tools_1.XTools.isEnabled()) {
|
|
177
|
+
allTools.push(...this.getXToolDefinitions());
|
|
178
|
+
}
|
|
179
|
+
// Only add shell tool if workspace has shell permission
|
|
180
|
+
if (this.workspace.permissions.shell) {
|
|
181
|
+
allTools.push(...this.getShellToolDefinitions());
|
|
182
|
+
}
|
|
183
|
+
// Only add image tools if Gemini API is configured
|
|
184
|
+
if (image_tools_1.ImageTools.isAvailable()) {
|
|
185
|
+
allTools.push(...image_tools_1.ImageTools.getToolDefinitions());
|
|
186
|
+
}
|
|
187
|
+
// Always add system tools (they enable broader system interaction)
|
|
188
|
+
allTools.push(...system_tools_1.SystemTools.getToolDefinitions());
|
|
189
|
+
// Always add cron/scheduling tools (enables task scheduling)
|
|
190
|
+
allTools.push(...cron_tools_1.CronTools.getToolDefinitions());
|
|
191
|
+
// Always add canvas tools (enables visual workspace)
|
|
192
|
+
allTools.push(...canvas_tools_1.CanvasTools.getToolDefinitions());
|
|
193
|
+
// Always add mention tools (enables multi-agent collaboration)
|
|
194
|
+
allTools.push(...mention_tools_1.MentionTools.getToolDefinitions());
|
|
195
|
+
// Add meta tools for execution control
|
|
196
|
+
allTools.push(...this.getMetaToolDefinitions());
|
|
197
|
+
// Collect built-in tool names before adding MCP tools
|
|
198
|
+
const builtinToolNames = new Set(allTools.map(t => t.name));
|
|
199
|
+
// Add MCP tools from connected servers, filtering out those that shadow built-in tools
|
|
200
|
+
const settings = settings_1.MCPSettingsManager.loadSettings();
|
|
201
|
+
const prefix = settings.toolNamePrefix || 'mcp_';
|
|
202
|
+
const mcpTools = this.getMCPToolDefinitions();
|
|
203
|
+
const shadowedTools = [];
|
|
204
|
+
for (const mcpTool of mcpTools) {
|
|
205
|
+
const baseName = mcpTool.name.slice(prefix.length);
|
|
206
|
+
if (builtinToolNames.has(baseName)) {
|
|
207
|
+
// Skip MCP tools that shadow built-in tools - prefer built-in versions
|
|
208
|
+
shadowedTools.push(mcpTool.name);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
allTools.push(mcpTool);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (shadowedTools.length > 0 && !this.shadowedToolsLogged) {
|
|
215
|
+
console.log(`[ToolRegistry] Skipped ${shadowedTools.length} MCP tools that shadow built-in tools:`, shadowedTools.join(', '));
|
|
216
|
+
this.shadowedToolsLogged = true;
|
|
217
|
+
}
|
|
218
|
+
// Filter tools based on security policy (workspace + gateway context)
|
|
219
|
+
let filteredTools = allTools.filter(tool => this.isToolAllowed(tool.name));
|
|
220
|
+
// Filter tools based on user's built-in tool settings
|
|
221
|
+
const disabledBySettings = [];
|
|
222
|
+
filteredTools = filteredTools.filter(tool => {
|
|
223
|
+
// MCP tools are not affected by built-in settings
|
|
224
|
+
if (tool.name.startsWith(prefix)) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
// Meta tools are always enabled
|
|
228
|
+
if (['revise_plan', 'set_personality', 'set_persona', 'set_agent_name', 'set_user_name', 'set_response_style', 'set_quirks', 'spawn_agent', 'wait_for_agent', 'get_agent_status', 'list_agents'].includes(tool.name)) {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
// Check built-in tool settings
|
|
232
|
+
const isEnabled = builtin_settings_1.BuiltinToolsSettingsManager.isToolEnabled(tool.name);
|
|
233
|
+
if (!isEnabled) {
|
|
234
|
+
disabledBySettings.push(tool.name);
|
|
235
|
+
}
|
|
236
|
+
return isEnabled;
|
|
237
|
+
});
|
|
238
|
+
// Log filtered tools for debugging
|
|
239
|
+
const blockedTools = allTools.filter(tool => !this.isToolAllowed(tool.name));
|
|
240
|
+
if (blockedTools.length > 0 && this.gatewayContext) {
|
|
241
|
+
console.log(`[ToolRegistry] Blocked ${blockedTools.length} tools for ${this.gatewayContext} context:`, blockedTools.map(t => t.name).join(', '));
|
|
242
|
+
}
|
|
243
|
+
if (disabledBySettings.length > 0) {
|
|
244
|
+
console.log(`[ToolRegistry] Disabled ${disabledBySettings.length} tools by user settings:`, disabledBySettings.join(', '));
|
|
245
|
+
}
|
|
246
|
+
// Sort tools by priority (high first, then normal, then low)
|
|
247
|
+
// This helps influence which tools the LLM is more likely to choose
|
|
248
|
+
const priorityOrder = { high: 0, normal: 1, low: 2 };
|
|
249
|
+
filteredTools.sort((a, b) => {
|
|
250
|
+
// MCP tools always come after built-in tools at the same priority
|
|
251
|
+
const aIsMcp = a.name.startsWith(prefix);
|
|
252
|
+
const bIsMcp = b.name.startsWith(prefix);
|
|
253
|
+
const aPriority = aIsMcp ? 'normal' : builtin_settings_1.BuiltinToolsSettingsManager.getToolPriority(a.name);
|
|
254
|
+
const bPriority = bIsMcp ? 'normal' : builtin_settings_1.BuiltinToolsSettingsManager.getToolPriority(b.name);
|
|
255
|
+
const diff = priorityOrder[aPriority] - priorityOrder[bPriority];
|
|
256
|
+
if (diff !== 0)
|
|
257
|
+
return diff;
|
|
258
|
+
// Within same priority, put built-in tools first
|
|
259
|
+
if (aIsMcp && !bIsMcp)
|
|
260
|
+
return 1;
|
|
261
|
+
if (!aIsMcp && bIsMcp)
|
|
262
|
+
return -1;
|
|
263
|
+
return 0;
|
|
264
|
+
});
|
|
265
|
+
return filteredTools;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get MCP tools from connected servers
|
|
269
|
+
*/
|
|
270
|
+
getMCPToolDefinitions() {
|
|
271
|
+
try {
|
|
272
|
+
const mcpManager = MCPClientManager_1.MCPClientManager.getInstance();
|
|
273
|
+
const mcpTools = mcpManager.getAllTools();
|
|
274
|
+
const settings = settings_1.MCPSettingsManager.loadSettings();
|
|
275
|
+
const prefix = settings.toolNamePrefix || 'mcp_';
|
|
276
|
+
return mcpTools.map((tool) => ({
|
|
277
|
+
name: `${prefix}${tool.name}`,
|
|
278
|
+
description: tool.description || `MCP tool: ${tool.name}`,
|
|
279
|
+
input_schema: tool.inputSchema,
|
|
280
|
+
}));
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
// MCP not initialized yet, return empty array
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Set the callback for handling plan revisions
|
|
289
|
+
*/
|
|
290
|
+
setPlanRevisionHandler(handler) {
|
|
291
|
+
this.planRevisionHandler = handler;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Set the callback for handling workspace switches
|
|
295
|
+
*/
|
|
296
|
+
setWorkspaceSwitchHandler(handler) {
|
|
297
|
+
this.workspaceSwitchHandler = handler;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Switch to a different workspace
|
|
301
|
+
* Used internally by switch_workspace tool
|
|
302
|
+
*/
|
|
303
|
+
async switchWorkspace(input) {
|
|
304
|
+
const { path: workspacePath, workspace_id } = input;
|
|
305
|
+
if (!workspacePath && !workspace_id) {
|
|
306
|
+
return {
|
|
307
|
+
success: false,
|
|
308
|
+
error: 'Either path or workspace_id must be provided',
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (!this.workspaceSwitchHandler) {
|
|
312
|
+
return {
|
|
313
|
+
success: false,
|
|
314
|
+
error: 'Workspace switching is not available in this context',
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
// Look up the workspace
|
|
319
|
+
let newWorkspace;
|
|
320
|
+
if (workspace_id) {
|
|
321
|
+
newWorkspace = this.daemon.getWorkspaceById(workspace_id);
|
|
322
|
+
if (!newWorkspace) {
|
|
323
|
+
return {
|
|
324
|
+
success: false,
|
|
325
|
+
error: `Workspace not found with id: ${workspace_id}`,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else if (workspacePath) {
|
|
330
|
+
newWorkspace = this.daemon.getWorkspaceByPath(workspacePath);
|
|
331
|
+
if (!newWorkspace) {
|
|
332
|
+
// Try to create a new workspace for this path
|
|
333
|
+
const pathModule = await Promise.resolve().then(() => __importStar(require('path')));
|
|
334
|
+
const fsModule = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
335
|
+
// Check if path exists and is a directory
|
|
336
|
+
if (!fsModule.existsSync(workspacePath)) {
|
|
337
|
+
return {
|
|
338
|
+
success: false,
|
|
339
|
+
error: `Path does not exist: ${workspacePath}`,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const stats = fsModule.statSync(workspacePath);
|
|
343
|
+
if (!stats.isDirectory()) {
|
|
344
|
+
return {
|
|
345
|
+
success: false,
|
|
346
|
+
error: `Path is not a directory: ${workspacePath}`,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
// Create a new workspace for this path
|
|
350
|
+
const name = pathModule.basename(workspacePath);
|
|
351
|
+
newWorkspace = this.daemon.createWorkspace(name, workspacePath);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (!newWorkspace) {
|
|
355
|
+
return {
|
|
356
|
+
success: false,
|
|
357
|
+
error: 'Failed to find or create workspace',
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
// Call the switch handler to update executor and task
|
|
361
|
+
await this.workspaceSwitchHandler(newWorkspace);
|
|
362
|
+
// Update the local workspace reference
|
|
363
|
+
this.setWorkspace(newWorkspace);
|
|
364
|
+
return {
|
|
365
|
+
success: true,
|
|
366
|
+
workspace: {
|
|
367
|
+
id: newWorkspace.id,
|
|
368
|
+
name: newWorkspace.name,
|
|
369
|
+
path: newWorkspace.path,
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
return {
|
|
375
|
+
success: false,
|
|
376
|
+
error: error.message || 'Failed to switch workspace',
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Get human-readable tool descriptions
|
|
382
|
+
*/
|
|
383
|
+
getToolDescriptions() {
|
|
384
|
+
let descriptions = `
|
|
385
|
+
File Operations:
|
|
386
|
+
- read_file: Read contents of a file (supports plain text, DOCX, and PDF)
|
|
387
|
+
- write_file: Write content to a file (creates or overwrites). Use edit_file for targeted changes instead.
|
|
388
|
+
- edit_file: Surgical text replacement (preferred over write_file for modifications)
|
|
389
|
+
- copy_file: Copy a file (supports binary files like DOCX, PDF, images)
|
|
390
|
+
- list_directory: List files and folders in a directory
|
|
391
|
+
- rename_file: Rename or move a file
|
|
392
|
+
- delete_file: Delete a file (requires approval)
|
|
393
|
+
- create_directory: Create a new directory
|
|
394
|
+
- search_files: Basic file search. Use glob for pattern matching, grep for content search instead.
|
|
395
|
+
|
|
396
|
+
Skills:
|
|
397
|
+
- create_spreadsheet: Create Excel spreadsheets with data and formulas
|
|
398
|
+
- create_document: Create Word documents or PDFs
|
|
399
|
+
- edit_document: Edit/append content to existing DOCX files
|
|
400
|
+
- create_presentation: Create PowerPoint presentations
|
|
401
|
+
- organize_folder: Organize and structure files in folders
|
|
402
|
+
- use_skill: Invoke a custom skill by ID to help accomplish tasks (see available skills below)
|
|
403
|
+
|
|
404
|
+
Skill Management (create, modify, duplicate skills):
|
|
405
|
+
- skill_list: List all skills with metadata (source, path, status)
|
|
406
|
+
- skill_get: Get full JSON content of a skill by ID
|
|
407
|
+
- skill_create: Create a new custom skill
|
|
408
|
+
- skill_duplicate: Duplicate an existing skill with modifications (great for variations)
|
|
409
|
+
- skill_update: Update an existing skill (managed/workspace only, not bundled)
|
|
410
|
+
- skill_delete: Delete a skill (managed/workspace only, not bundled)
|
|
411
|
+
Skills are stored in ~/Library/Application Support/cowork-os/skills/ (managed) or workspace/skills/ (workspace).
|
|
412
|
+
|
|
413
|
+
Code Tools (PREFERRED for code navigation and editing):
|
|
414
|
+
- glob: Fast pattern-based file search (e.g., "**/*.ts", "src/**/*.test.ts")
|
|
415
|
+
Use this FIRST to find files by pattern - much faster than search_files.
|
|
416
|
+
- grep: Powerful regex content search (e.g., "async function.*fetch", "class\\s+\\w+")
|
|
417
|
+
Use this FIRST for searching file contents - supports full regex.
|
|
418
|
+
- edit_file: Surgical text replacement in files (old_string -> new_string)
|
|
419
|
+
Use this INSTEAD of write_file for targeted changes - safer and preserves structure.
|
|
420
|
+
|
|
421
|
+
Web Fetch (PREFERRED for reading web content):
|
|
422
|
+
- web_fetch: Fetch and read content from a URL as markdown (fast, lightweight, no browser needed)
|
|
423
|
+
Use this FIRST when you need to read any web page, documentation, GitHub repo, or article.
|
|
424
|
+
- http_request: Make raw HTTP requests like curl (GET, POST, PUT, DELETE, etc.)
|
|
425
|
+
Use this for APIs, raw file downloads, or when you need custom headers/body.
|
|
426
|
+
|
|
427
|
+
Browser Automation (use only when interaction is needed):
|
|
428
|
+
- browser_navigate: Navigate to a URL (use only for pages requiring JS or when you need to interact)
|
|
429
|
+
- browser_screenshot: Take a screenshot of the page
|
|
430
|
+
- browser_get_content: Get page text, links, and forms (use after navigate, for inspecting interactive elements)
|
|
431
|
+
- browser_click: Click on an element
|
|
432
|
+
- browser_fill: Fill a form field
|
|
433
|
+
- browser_type: Type text character by character
|
|
434
|
+
- browser_press: Press a keyboard key
|
|
435
|
+
- browser_wait: Wait for an element to appear
|
|
436
|
+
- browser_scroll: Scroll the page
|
|
437
|
+
- browser_select: Select dropdown option
|
|
438
|
+
- browser_get_text: Get element text content
|
|
439
|
+
- browser_evaluate: Execute JavaScript
|
|
440
|
+
- browser_back/forward: Navigate history
|
|
441
|
+
- browser_reload: Reload the page
|
|
442
|
+
- browser_save_pdf: Save page as PDF
|
|
443
|
+
- browser_close: Close the browser`;
|
|
444
|
+
// Add search if configured
|
|
445
|
+
if (search_1.SearchProviderFactory.isAnyProviderConfigured()) {
|
|
446
|
+
descriptions += `
|
|
447
|
+
|
|
448
|
+
Web Search (for finding URLs, not reading them):
|
|
449
|
+
- web_search: Search the web for information (web, news, images)
|
|
450
|
+
Use to FIND relevant pages. To READ a specific URL, use web_fetch instead.`;
|
|
451
|
+
}
|
|
452
|
+
// Add shell if permitted
|
|
453
|
+
if (this.workspace.permissions.shell) {
|
|
454
|
+
descriptions += `
|
|
455
|
+
|
|
456
|
+
Shell Commands:
|
|
457
|
+
- run_command: Execute shell commands (requires user approval)`;
|
|
458
|
+
}
|
|
459
|
+
// Add image generation if Gemini is configured
|
|
460
|
+
if (image_tools_1.ImageTools.isAvailable()) {
|
|
461
|
+
descriptions += `
|
|
462
|
+
|
|
463
|
+
Image Generation (Nano Banana):
|
|
464
|
+
- generate_image: Generate images from text descriptions using AI
|
|
465
|
+
- nano-banana: Fast generation for quick iterations
|
|
466
|
+
- nano-banana-pro: High-quality generation for production use`;
|
|
467
|
+
}
|
|
468
|
+
// System tools are always available
|
|
469
|
+
descriptions += `
|
|
470
|
+
|
|
471
|
+
System Tools:
|
|
472
|
+
- system_info: Get OS, CPU, memory, and user info
|
|
473
|
+
- read_clipboard: Read system clipboard contents
|
|
474
|
+
- write_clipboard: Write text to system clipboard
|
|
475
|
+
- take_screenshot: Capture screen and save to workspace
|
|
476
|
+
- open_application: Open an app by name
|
|
477
|
+
- open_url: Open URL in default browser
|
|
478
|
+
- open_path: Open file/folder with default application
|
|
479
|
+
- show_in_folder: Reveal file in Finder/Explorer
|
|
480
|
+
- get_env: Read environment variable
|
|
481
|
+
- get_app_paths: Get system paths (home, downloads, etc.)
|
|
482
|
+
- run_applescript: Execute AppleScript on macOS (control apps, automate tasks)
|
|
483
|
+
|
|
484
|
+
Scheduling:
|
|
485
|
+
- schedule_task: Schedule tasks to run at specific times or intervals
|
|
486
|
+
- Create reminders: "remind me to X at Y"
|
|
487
|
+
- Recurring tasks: "every day at 9am, do X"
|
|
488
|
+
- One-time tasks: "at 3pm tomorrow, do X"
|
|
489
|
+
- Cron schedules: standard cron expressions supported
|
|
490
|
+
|
|
491
|
+
Live Canvas (Visual Workspace):
|
|
492
|
+
- canvas_create: Create a new canvas session for displaying interactive content
|
|
493
|
+
- canvas_push: Push HTML/CSS/JS content to the canvas. REQUIRED parameters: session_id and content (the HTML string).
|
|
494
|
+
Example: canvas_push({ session_id: "abc-123", content: "<!DOCTYPE html><html><body><h1>Hello</h1></body></html>" })
|
|
495
|
+
- canvas_open_url: Open a remote web page inside the canvas window for full in-app browsing (use for sites that block embedding)
|
|
496
|
+
- canvas_show: OPTIONAL - Only use if user needs full interactivity (clicking buttons, forms)
|
|
497
|
+
- canvas_hide: Hide the canvas window
|
|
498
|
+
- canvas_close: Close a canvas session
|
|
499
|
+
- canvas_eval: Execute JavaScript in the canvas context
|
|
500
|
+
- canvas_snapshot: Take a screenshot of the canvas
|
|
501
|
+
- canvas_list: List all active canvas sessions
|
|
502
|
+
IMPORTANT: When using canvas_push, you MUST provide the 'content' parameter with the full HTML string to display.
|
|
503
|
+
|
|
504
|
+
Plan Control:
|
|
505
|
+
- revise_plan: Modify remaining plan steps when obstacles are encountered or new information discovered
|
|
506
|
+
- switch_workspace: Switch to a different workspace/working directory. Use when you need to work in a different folder.
|
|
507
|
+
- set_personality: Change the assistant's communication style (professional, friendly, concise, creative, technical, casual).
|
|
508
|
+
- set_persona: Change the assistant's character persona (jarvis, friday, hal, computer, alfred, intern, sensei, pirate, noir, companion, or none).
|
|
509
|
+
- set_response_style: Adjust response preferences (emoji_usage, response_length, code_comments, explanation_depth).
|
|
510
|
+
- set_quirks: Set personality quirks (catchphrase, sign_off, analogy_domain).
|
|
511
|
+
- set_agent_name: Set or change the assistant's name when the user wants to give you a name.
|
|
512
|
+
- set_user_name: Store the user's name when they introduce themselves (e.g., "I'm Alice", "My name is Bob").`;
|
|
513
|
+
// Add custom skills available for use_skill
|
|
514
|
+
const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
|
|
515
|
+
const skillDescriptions = skillLoader.getSkillDescriptionsForModel();
|
|
516
|
+
if (skillDescriptions) {
|
|
517
|
+
descriptions += `
|
|
518
|
+
|
|
519
|
+
Custom Skills (invoke with use_skill tool):
|
|
520
|
+
${skillDescriptions}`;
|
|
521
|
+
}
|
|
522
|
+
return descriptions.trim();
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Execute a tool by name
|
|
526
|
+
*/
|
|
527
|
+
async executeTool(name, input) {
|
|
528
|
+
// File tools
|
|
529
|
+
if (name === 'read_file')
|
|
530
|
+
return await this.fileTools.readFile(input.path);
|
|
531
|
+
if (name === 'write_file')
|
|
532
|
+
return await this.fileTools.writeFile(input.path, input.content);
|
|
533
|
+
if (name === 'copy_file')
|
|
534
|
+
return await this.fileTools.copyFile(input.sourcePath, input.destPath);
|
|
535
|
+
if (name === 'list_directory')
|
|
536
|
+
return await this.fileTools.listDirectory(input.path);
|
|
537
|
+
if (name === 'list_directory_with_sizes')
|
|
538
|
+
return await this.fileTools.listDirectoryWithSizes(input.path);
|
|
539
|
+
if (name === 'get_file_info')
|
|
540
|
+
return await this.fileTools.getFileInfo(input.path);
|
|
541
|
+
if (name === 'rename_file')
|
|
542
|
+
return await this.fileTools.renameFile(input.oldPath, input.newPath);
|
|
543
|
+
if (name === 'delete_file')
|
|
544
|
+
return await this.fileTools.deleteFile(input.path);
|
|
545
|
+
if (name === 'create_directory')
|
|
546
|
+
return await this.fileTools.createDirectory(input.path);
|
|
547
|
+
if (name === 'search_files')
|
|
548
|
+
return await this.fileTools.searchFiles(input.query, input.path);
|
|
549
|
+
// Skill tools
|
|
550
|
+
if (name === 'create_spreadsheet')
|
|
551
|
+
return await this.skillTools.createSpreadsheet(input);
|
|
552
|
+
if (name === 'create_document')
|
|
553
|
+
return await this.skillTools.createDocument(input);
|
|
554
|
+
if (name === 'edit_document')
|
|
555
|
+
return await this.skillTools.editDocument(input);
|
|
556
|
+
if (name === 'create_presentation')
|
|
557
|
+
return await this.skillTools.createPresentation(input);
|
|
558
|
+
if (name === 'organize_folder')
|
|
559
|
+
return await this.skillTools.organizeFolder(input);
|
|
560
|
+
if (name === 'use_skill')
|
|
561
|
+
return await this.executeUseSkill(input);
|
|
562
|
+
// Skill management tools
|
|
563
|
+
if (name === 'skill_list')
|
|
564
|
+
return await this.executeSkillList(input);
|
|
565
|
+
if (name === 'skill_get')
|
|
566
|
+
return await this.executeSkillGet(input);
|
|
567
|
+
if (name === 'skill_create')
|
|
568
|
+
return await this.executeSkillCreate(input);
|
|
569
|
+
if (name === 'skill_duplicate')
|
|
570
|
+
return await this.executeSkillDuplicate(input);
|
|
571
|
+
if (name === 'skill_update')
|
|
572
|
+
return await this.executeSkillUpdate(input);
|
|
573
|
+
if (name === 'skill_delete')
|
|
574
|
+
return await this.executeSkillDelete(input);
|
|
575
|
+
// Code tools (glob, grep, edit)
|
|
576
|
+
if (name === 'glob')
|
|
577
|
+
return await this.globTools.glob(input);
|
|
578
|
+
if (name === 'grep')
|
|
579
|
+
return await this.grepTools.grep(input);
|
|
580
|
+
if (name === 'edit_file')
|
|
581
|
+
return await this.editTools.editFile(input);
|
|
582
|
+
// Web fetch tools (preferred for reading web content)
|
|
583
|
+
if (name === 'web_fetch')
|
|
584
|
+
return await this.webFetchTools.webFetch(input);
|
|
585
|
+
if (name === 'http_request')
|
|
586
|
+
return await this.webFetchTools.httpRequest(input);
|
|
587
|
+
// Browser tools
|
|
588
|
+
if (browser_tools_1.BrowserTools.isBrowserTool(name)) {
|
|
589
|
+
return await this.browserTools.executeTool(name, input);
|
|
590
|
+
}
|
|
591
|
+
// Search tools
|
|
592
|
+
if (name === 'web_search')
|
|
593
|
+
return await this.searchTools.webSearch(input);
|
|
594
|
+
// X/Twitter tools
|
|
595
|
+
if (name === 'x_action')
|
|
596
|
+
return await this.xTools.executeAction(input);
|
|
597
|
+
// Shell tools
|
|
598
|
+
if (name === 'run_command')
|
|
599
|
+
return await this.shellTools.runCommand(input.command, input);
|
|
600
|
+
// Image tools
|
|
601
|
+
if (name === 'generate_image')
|
|
602
|
+
return await this.imageTools.generateImage(input);
|
|
603
|
+
// System tools
|
|
604
|
+
if (name === 'system_info')
|
|
605
|
+
return await this.systemTools.getSystemInfo();
|
|
606
|
+
if (name === 'read_clipboard')
|
|
607
|
+
return await this.systemTools.readClipboard();
|
|
608
|
+
if (name === 'write_clipboard')
|
|
609
|
+
return await this.systemTools.writeClipboard(input.text);
|
|
610
|
+
if (name === 'take_screenshot')
|
|
611
|
+
return await this.systemTools.takeScreenshot(input);
|
|
612
|
+
if (name === 'open_application')
|
|
613
|
+
return await this.systemTools.openApplication(input.appName);
|
|
614
|
+
if (name === 'open_url')
|
|
615
|
+
return await this.systemTools.openUrl(input.url);
|
|
616
|
+
if (name === 'open_path')
|
|
617
|
+
return await this.systemTools.openPath(input.path);
|
|
618
|
+
if (name === 'show_in_folder')
|
|
619
|
+
return await this.systemTools.showInFolder(input.path);
|
|
620
|
+
if (name === 'get_env')
|
|
621
|
+
return await this.systemTools.getEnvVariable(input.name);
|
|
622
|
+
if (name === 'get_app_paths')
|
|
623
|
+
return this.systemTools.getAppPaths();
|
|
624
|
+
if (name === 'run_applescript')
|
|
625
|
+
return await this.systemTools.runAppleScript(input.script);
|
|
626
|
+
// Cron/scheduling tools
|
|
627
|
+
if (name === 'schedule_task')
|
|
628
|
+
return await this.cronTools.executeAction(input);
|
|
629
|
+
// Canvas tools
|
|
630
|
+
if (name === 'canvas_create')
|
|
631
|
+
return await this.canvasTools.createCanvas(input.title);
|
|
632
|
+
if (name === 'canvas_push') {
|
|
633
|
+
console.log(`[ToolRegistry] canvas_push input keys:`, Object.keys(input || {}));
|
|
634
|
+
console.log(`[ToolRegistry] canvas_push session_id:`, input?.session_id);
|
|
635
|
+
console.log(`[ToolRegistry] canvas_push content present:`, 'content' in (input || {}), `content length:`, input?.content?.length ?? 'N/A');
|
|
636
|
+
return await this.canvasTools.pushContent(input.session_id, input.content, input.filename);
|
|
637
|
+
}
|
|
638
|
+
if (name === 'canvas_open_url')
|
|
639
|
+
return await this.canvasTools.openUrl(input.session_id, input.url, input.show);
|
|
640
|
+
if (name === 'canvas_show')
|
|
641
|
+
return await this.canvasTools.showCanvas(input.session_id);
|
|
642
|
+
if (name === 'canvas_hide')
|
|
643
|
+
return this.canvasTools.hideCanvas(input.session_id);
|
|
644
|
+
if (name === 'canvas_close')
|
|
645
|
+
return await this.canvasTools.closeCanvas(input.session_id);
|
|
646
|
+
if (name === 'canvas_eval')
|
|
647
|
+
return await this.canvasTools.evalScript(input.session_id, input.script);
|
|
648
|
+
if (name === 'canvas_snapshot')
|
|
649
|
+
return await this.canvasTools.takeSnapshot(input.session_id);
|
|
650
|
+
if (name === 'canvas_list')
|
|
651
|
+
return this.canvasTools.listSessions();
|
|
652
|
+
// Mention tools (multi-agent collaboration)
|
|
653
|
+
if (name === 'list_agent_roles')
|
|
654
|
+
return await this.mentionTools.listAgentRoles();
|
|
655
|
+
if (name === 'mention_agent')
|
|
656
|
+
return await this.mentionTools.mentionAgent(input);
|
|
657
|
+
if (name === 'get_pending_mentions')
|
|
658
|
+
return await this.mentionTools.getPendingMentions();
|
|
659
|
+
if (name === 'acknowledge_mention')
|
|
660
|
+
return await this.mentionTools.acknowledgeMention(input.mentionId);
|
|
661
|
+
if (name === 'complete_mention')
|
|
662
|
+
return await this.mentionTools.completeMention(input.mentionId);
|
|
663
|
+
// Meta tools
|
|
664
|
+
if (name === 'revise_plan') {
|
|
665
|
+
if (!this.planRevisionHandler) {
|
|
666
|
+
throw new Error('Plan revision not available at this time');
|
|
667
|
+
}
|
|
668
|
+
const newSteps = input.newSteps || [];
|
|
669
|
+
const reason = input.reason || 'No reason provided';
|
|
670
|
+
const clearRemaining = input.clearRemaining || false;
|
|
671
|
+
this.planRevisionHandler(newSteps, reason, clearRemaining);
|
|
672
|
+
let message = '';
|
|
673
|
+
if (clearRemaining) {
|
|
674
|
+
message = `Plan revised: Cleared remaining steps. `;
|
|
675
|
+
}
|
|
676
|
+
if (newSteps.length > 0) {
|
|
677
|
+
message += `${newSteps.length} new steps added. `;
|
|
678
|
+
}
|
|
679
|
+
message += `Reason: ${reason}`;
|
|
680
|
+
return {
|
|
681
|
+
success: true,
|
|
682
|
+
message: message.trim(),
|
|
683
|
+
clearedRemaining: clearRemaining,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
if (name === 'switch_workspace') {
|
|
687
|
+
return await this.switchWorkspace(input);
|
|
688
|
+
}
|
|
689
|
+
if (name === 'set_personality') {
|
|
690
|
+
return this.setPersonality(input);
|
|
691
|
+
}
|
|
692
|
+
if (name === 'set_agent_name') {
|
|
693
|
+
return this.setAgentName(input);
|
|
694
|
+
}
|
|
695
|
+
if (name === 'set_user_name') {
|
|
696
|
+
return this.setUserName(input);
|
|
697
|
+
}
|
|
698
|
+
if (name === 'set_persona') {
|
|
699
|
+
return this.setPersona(input);
|
|
700
|
+
}
|
|
701
|
+
if (name === 'set_response_style') {
|
|
702
|
+
return this.setResponseStyle(input);
|
|
703
|
+
}
|
|
704
|
+
if (name === 'set_quirks') {
|
|
705
|
+
return this.setQuirks(input);
|
|
706
|
+
}
|
|
707
|
+
// Sub-Agent / Parallel Agent tools
|
|
708
|
+
if (name === 'spawn_agent') {
|
|
709
|
+
return await this.spawnAgent(input);
|
|
710
|
+
}
|
|
711
|
+
if (name === 'wait_for_agent') {
|
|
712
|
+
return await this.waitForAgent(input);
|
|
713
|
+
}
|
|
714
|
+
if (name === 'get_agent_status') {
|
|
715
|
+
return await this.getAgentStatus(input);
|
|
716
|
+
}
|
|
717
|
+
if (name === 'list_agents') {
|
|
718
|
+
return await this.listAgents(input);
|
|
719
|
+
}
|
|
720
|
+
// MCP tools (prefixed with mcp_ by default)
|
|
721
|
+
const mcpToolResult = await this.tryExecuteMCPTool(name, input);
|
|
722
|
+
if (mcpToolResult !== null) {
|
|
723
|
+
return mcpToolResult;
|
|
724
|
+
}
|
|
725
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Try to execute an MCP tool if the name matches
|
|
729
|
+
*/
|
|
730
|
+
async tryExecuteMCPTool(name, input) {
|
|
731
|
+
const settings = settings_1.MCPSettingsManager.loadSettings();
|
|
732
|
+
const prefix = settings.toolNamePrefix || 'mcp_';
|
|
733
|
+
// Not an MCP tool if it doesn't have the prefix
|
|
734
|
+
if (!name.startsWith(prefix)) {
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
const mcpToolName = name.slice(prefix.length);
|
|
738
|
+
// Try to get the MCP manager - if not initialized, this is not an MCP tool call
|
|
739
|
+
let mcpManager;
|
|
740
|
+
try {
|
|
741
|
+
mcpManager = MCPClientManager_1.MCPClientManager.getInstance();
|
|
742
|
+
}
|
|
743
|
+
catch (error) {
|
|
744
|
+
// MCP not initialized
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
// Check if the tool is registered
|
|
748
|
+
if (!mcpManager.hasTool(mcpToolName)) {
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
// Guard against using puppeteer_evaluate for Node/shell execution
|
|
752
|
+
if (mcpToolName === 'puppeteer_evaluate') {
|
|
753
|
+
const script = typeof input?.script === 'string' ? input.script : '';
|
|
754
|
+
if (/(require\s*\(|child_process|execSync|exec\(|spawn\()/i.test(script)) {
|
|
755
|
+
throw new Error("MCP tool 'puppeteer_evaluate' cannot run Node shell APIs. " +
|
|
756
|
+
"Use run_command for shell commands or browser_evaluate for DOM-only scripts.");
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// At this point, we know it's a valid MCP tool - any errors should be propagated
|
|
760
|
+
console.log(`[ToolRegistry] Executing MCP tool: ${mcpToolName}`);
|
|
761
|
+
try {
|
|
762
|
+
const result = await mcpManager.callTool(mcpToolName, input);
|
|
763
|
+
// Format MCP result and process any generated files
|
|
764
|
+
return await this.formatMCPResult(result, mcpToolName, input);
|
|
765
|
+
}
|
|
766
|
+
catch (error) {
|
|
767
|
+
// Tool was registered but execution failed - propagate the error with context
|
|
768
|
+
throw new Error(`MCP tool '${mcpToolName}' failed: ${error.message}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Format MCP call result for agent consumption
|
|
773
|
+
* Also handles file artifacts (screenshots, etc.) from MCP tools
|
|
774
|
+
*/
|
|
775
|
+
async formatMCPResult(result, toolName, input) {
|
|
776
|
+
if (!result)
|
|
777
|
+
return { success: true };
|
|
778
|
+
// Check if it's an MCP CallResult format
|
|
779
|
+
if (result.content && Array.isArray(result.content)) {
|
|
780
|
+
if (result.isError) {
|
|
781
|
+
throw new Error(result.content.map((c) => c.text || '').join('\n') || 'MCP tool execution failed');
|
|
782
|
+
}
|
|
783
|
+
// Handle image content from MCP tools (e.g., take_screenshot)
|
|
784
|
+
for (const content of result.content) {
|
|
785
|
+
if (content.type === 'image' && content.data) {
|
|
786
|
+
// Save inline image to workspace
|
|
787
|
+
const filename = input?.filePath
|
|
788
|
+
? path.basename(input.filePath)
|
|
789
|
+
: `mcp-screenshot-${Date.now()}.png`;
|
|
790
|
+
const outputPath = path.join(this.workspace.path, filename);
|
|
791
|
+
try {
|
|
792
|
+
const imageBuffer = Buffer.from(content.data, 'base64');
|
|
793
|
+
await fsPromises.writeFile(outputPath, imageBuffer);
|
|
794
|
+
// Emit file_created event
|
|
795
|
+
this.daemon.logEvent(this.taskId, 'file_created', {
|
|
796
|
+
path: filename,
|
|
797
|
+
type: 'screenshot',
|
|
798
|
+
source: 'mcp',
|
|
799
|
+
});
|
|
800
|
+
// Register as artifact
|
|
801
|
+
this.daemon.registerArtifact(this.taskId, outputPath, content.mimeType || 'image/png');
|
|
802
|
+
console.log(`[ToolRegistry] Saved MCP image artifact: ${filename}`);
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
console.error(`[ToolRegistry] Failed to save MCP image:`, error);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
// Combine text content
|
|
810
|
+
const textParts = result.content
|
|
811
|
+
.filter((c) => c.type === 'text')
|
|
812
|
+
.map((c) => c.text);
|
|
813
|
+
if (textParts.length > 0) {
|
|
814
|
+
return textParts.join('\n');
|
|
815
|
+
}
|
|
816
|
+
// Return raw result if no text content
|
|
817
|
+
return result;
|
|
818
|
+
}
|
|
819
|
+
// Handle file paths in MCP results (when filePath parameter was provided)
|
|
820
|
+
if (input?.filePath && typeof input.filePath === 'string') {
|
|
821
|
+
const providedPath = input.filePath;
|
|
822
|
+
const filename = path.basename(providedPath);
|
|
823
|
+
const workspacePath = path.join(this.workspace.path, filename);
|
|
824
|
+
// Check various possible locations for the file
|
|
825
|
+
const possiblePaths = [
|
|
826
|
+
providedPath, // Absolute path as provided
|
|
827
|
+
path.resolve(providedPath), // Resolved relative path
|
|
828
|
+
path.join(process.cwd(), providedPath), // Relative to current working directory
|
|
829
|
+
workspacePath, // Already in workspace
|
|
830
|
+
];
|
|
831
|
+
for (const sourcePath of possiblePaths) {
|
|
832
|
+
try {
|
|
833
|
+
if (fs.existsSync(sourcePath)) {
|
|
834
|
+
// File found - copy to workspace if not already there
|
|
835
|
+
if (sourcePath !== workspacePath && !sourcePath.startsWith(this.workspace.path)) {
|
|
836
|
+
await fsPromises.copyFile(sourcePath, workspacePath);
|
|
837
|
+
console.log(`[ToolRegistry] Copied MCP file to workspace: ${sourcePath} -> ${workspacePath}`);
|
|
838
|
+
}
|
|
839
|
+
// Emit file_created event with workspace-relative path
|
|
840
|
+
this.daemon.logEvent(this.taskId, 'file_created', {
|
|
841
|
+
path: filename,
|
|
842
|
+
type: 'screenshot',
|
|
843
|
+
source: 'mcp',
|
|
844
|
+
});
|
|
845
|
+
// Register as artifact if it's an image
|
|
846
|
+
const ext = path.extname(filename).toLowerCase();
|
|
847
|
+
const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp'];
|
|
848
|
+
if (imageExtensions.includes(ext)) {
|
|
849
|
+
const mimeTypes = {
|
|
850
|
+
'.png': 'image/png',
|
|
851
|
+
'.jpg': 'image/jpeg',
|
|
852
|
+
'.jpeg': 'image/jpeg',
|
|
853
|
+
'.gif': 'image/gif',
|
|
854
|
+
'.webp': 'image/webp',
|
|
855
|
+
'.bmp': 'image/bmp',
|
|
856
|
+
};
|
|
857
|
+
this.daemon.registerArtifact(this.taskId, workspacePath, mimeTypes[ext] || 'image/png');
|
|
858
|
+
}
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
catch (error) {
|
|
863
|
+
// Continue checking other paths
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
// Return as-is if not in MCP format
|
|
868
|
+
return result;
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Cleanup resources (call when task is done)
|
|
872
|
+
*/
|
|
873
|
+
async cleanup() {
|
|
874
|
+
await this.browserTools.cleanup();
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Execute the use_skill tool - invokes a custom skill by ID
|
|
878
|
+
*/
|
|
879
|
+
async executeUseSkill(input) {
|
|
880
|
+
const { skill_id, parameters = {} } = input;
|
|
881
|
+
const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
|
|
882
|
+
const skill = skillLoader.getSkill(skill_id);
|
|
883
|
+
if (!skill) {
|
|
884
|
+
// List available skills to help the agent
|
|
885
|
+
const availableSkills = skillLoader.listModelInvocableSkills().map(s => s.id);
|
|
886
|
+
return {
|
|
887
|
+
success: false,
|
|
888
|
+
error: `Skill '${skill_id}' not found`,
|
|
889
|
+
available_skills: availableSkills.slice(0, 20), // Show up to 20 skills
|
|
890
|
+
hint: 'Use one of the available skill IDs listed above',
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
// Check if skill can be invoked by model
|
|
894
|
+
if (skill.invocation?.disableModelInvocation) {
|
|
895
|
+
return {
|
|
896
|
+
success: false,
|
|
897
|
+
error: `Skill '${skill_id}' cannot be invoked automatically`,
|
|
898
|
+
reason: 'This skill is configured for manual invocation only',
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
// Check for required parameters
|
|
902
|
+
const missingParams = [];
|
|
903
|
+
if (skill.parameters) {
|
|
904
|
+
for (const param of skill.parameters) {
|
|
905
|
+
if (param.required && !(param.name in parameters) && param.default === undefined) {
|
|
906
|
+
missingParams.push(param.name);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (missingParams.length > 0) {
|
|
911
|
+
return {
|
|
912
|
+
success: false,
|
|
913
|
+
error: `Missing required parameters: ${missingParams.join(', ')}`,
|
|
914
|
+
skill_id,
|
|
915
|
+
parameters: skill.parameters?.map(p => ({
|
|
916
|
+
name: p.name,
|
|
917
|
+
type: p.type,
|
|
918
|
+
description: p.description,
|
|
919
|
+
required: p.required,
|
|
920
|
+
default: p.default,
|
|
921
|
+
options: p.options,
|
|
922
|
+
})),
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
// Expand the skill prompt with provided parameters
|
|
926
|
+
const expandedPrompt = skillLoader.expandPrompt(skill, parameters);
|
|
927
|
+
// Log the skill invocation
|
|
928
|
+
this.daemon.logEvent(this.taskId, 'log', {
|
|
929
|
+
message: `Using skill: ${skill.name}`,
|
|
930
|
+
skillId: skill_id,
|
|
931
|
+
parameters,
|
|
932
|
+
});
|
|
933
|
+
return {
|
|
934
|
+
success: true,
|
|
935
|
+
skill_id,
|
|
936
|
+
skill_name: skill.name,
|
|
937
|
+
skill_description: skill.description,
|
|
938
|
+
expanded_prompt: expandedPrompt,
|
|
939
|
+
instruction: 'Execute the task according to the expanded_prompt above. Follow its instructions to complete the user\'s request.',
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* List all skills with metadata
|
|
944
|
+
*/
|
|
945
|
+
async executeSkillList(input) {
|
|
946
|
+
const { source = 'all', include_disabled = true } = input;
|
|
947
|
+
const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
|
|
948
|
+
let skills = skillLoader.listSkills();
|
|
949
|
+
// Filter by source if specified
|
|
950
|
+
if (source !== 'all') {
|
|
951
|
+
skills = skills.filter(s => s.source === source);
|
|
952
|
+
}
|
|
953
|
+
// Filter out disabled if requested
|
|
954
|
+
if (!include_disabled) {
|
|
955
|
+
skills = skills.filter(s => s.enabled !== false);
|
|
956
|
+
}
|
|
957
|
+
// Format for agent consumption
|
|
958
|
+
const formattedSkills = skills.map(s => ({
|
|
959
|
+
id: s.id,
|
|
960
|
+
name: s.name,
|
|
961
|
+
description: s.description,
|
|
962
|
+
category: s.category || 'General',
|
|
963
|
+
icon: s.icon || '',
|
|
964
|
+
source: s.source,
|
|
965
|
+
filePath: s.filePath,
|
|
966
|
+
enabled: s.enabled !== false,
|
|
967
|
+
hasParameters: (s.parameters?.length || 0) > 0,
|
|
968
|
+
parameterCount: s.parameters?.length || 0,
|
|
969
|
+
}));
|
|
970
|
+
return {
|
|
971
|
+
success: true,
|
|
972
|
+
total: formattedSkills.length,
|
|
973
|
+
skills: formattedSkills,
|
|
974
|
+
directories: {
|
|
975
|
+
bundled: skillLoader.getBundledSkillsDir(),
|
|
976
|
+
managed: skillLoader.getManagedSkillsDir(),
|
|
977
|
+
workspace: skillLoader.getWorkspaceSkillsDir(),
|
|
978
|
+
},
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Get full details of a specific skill
|
|
983
|
+
*/
|
|
984
|
+
async executeSkillGet(input) {
|
|
985
|
+
const { skill_id } = input;
|
|
986
|
+
const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
|
|
987
|
+
const skill = skillLoader.getSkill(skill_id);
|
|
988
|
+
if (!skill) {
|
|
989
|
+
const availableSkills = skillLoader.listSkills().map(s => s.id);
|
|
990
|
+
return {
|
|
991
|
+
success: false,
|
|
992
|
+
error: `Skill '${skill_id}' not found`,
|
|
993
|
+
available_skills: availableSkills.slice(0, 30),
|
|
994
|
+
hint: 'Use skill_list to see all available skills',
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
// Return full skill definition (useful for duplication/modification)
|
|
998
|
+
return {
|
|
999
|
+
success: true,
|
|
1000
|
+
skill: {
|
|
1001
|
+
id: skill.id,
|
|
1002
|
+
name: skill.name,
|
|
1003
|
+
description: skill.description,
|
|
1004
|
+
prompt: skill.prompt,
|
|
1005
|
+
icon: skill.icon,
|
|
1006
|
+
category: skill.category,
|
|
1007
|
+
priority: skill.priority,
|
|
1008
|
+
parameters: skill.parameters,
|
|
1009
|
+
enabled: skill.enabled,
|
|
1010
|
+
type: skill.type,
|
|
1011
|
+
invocation: skill.invocation,
|
|
1012
|
+
requires: skill.requires,
|
|
1013
|
+
source: skill.source,
|
|
1014
|
+
filePath: skill.filePath,
|
|
1015
|
+
},
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Create a new skill
|
|
1020
|
+
*/
|
|
1021
|
+
async executeSkillCreate(input) {
|
|
1022
|
+
const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
|
|
1023
|
+
// Check if skill with this ID already exists
|
|
1024
|
+
const existing = skillLoader.getSkill(input.id);
|
|
1025
|
+
if (existing) {
|
|
1026
|
+
return {
|
|
1027
|
+
success: false,
|
|
1028
|
+
error: `Skill with ID '${input.id}' already exists`,
|
|
1029
|
+
existing_skill: {
|
|
1030
|
+
id: existing.id,
|
|
1031
|
+
name: existing.name,
|
|
1032
|
+
source: existing.source,
|
|
1033
|
+
},
|
|
1034
|
+
hint: 'Use a different ID or use skill_update to modify the existing skill',
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
// Validate ID format
|
|
1038
|
+
if (!/^[a-z0-9-]+$/.test(input.id)) {
|
|
1039
|
+
return {
|
|
1040
|
+
success: false,
|
|
1041
|
+
error: 'Invalid skill ID format',
|
|
1042
|
+
hint: 'Skill ID should be lowercase, using only letters, numbers, and hyphens (e.g., "my-custom-skill")',
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
try {
|
|
1046
|
+
const newSkill = await skillLoader.createSkill({
|
|
1047
|
+
id: input.id,
|
|
1048
|
+
name: input.name,
|
|
1049
|
+
description: input.description,
|
|
1050
|
+
prompt: input.prompt,
|
|
1051
|
+
icon: input.icon || '',
|
|
1052
|
+
category: input.category || 'Custom',
|
|
1053
|
+
parameters: input.parameters,
|
|
1054
|
+
enabled: input.enabled !== false,
|
|
1055
|
+
});
|
|
1056
|
+
this.daemon.logEvent(this.taskId, 'log', {
|
|
1057
|
+
message: `Created new skill: ${newSkill.name}`,
|
|
1058
|
+
skillId: newSkill.id,
|
|
1059
|
+
});
|
|
1060
|
+
return {
|
|
1061
|
+
success: true,
|
|
1062
|
+
message: `Skill '${newSkill.name}' created successfully`,
|
|
1063
|
+
skill: {
|
|
1064
|
+
id: newSkill.id,
|
|
1065
|
+
name: newSkill.name,
|
|
1066
|
+
source: newSkill.source,
|
|
1067
|
+
filePath: newSkill.filePath,
|
|
1068
|
+
},
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
catch (error) {
|
|
1072
|
+
return {
|
|
1073
|
+
success: false,
|
|
1074
|
+
error: `Failed to create skill: ${error.message}`,
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Duplicate an existing skill with modifications
|
|
1080
|
+
*/
|
|
1081
|
+
async executeSkillDuplicate(input) {
|
|
1082
|
+
const { source_skill_id, new_id, modifications = {} } = input;
|
|
1083
|
+
const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
|
|
1084
|
+
// Get the source skill
|
|
1085
|
+
const sourceSkill = skillLoader.getSkill(source_skill_id);
|
|
1086
|
+
if (!sourceSkill) {
|
|
1087
|
+
return {
|
|
1088
|
+
success: false,
|
|
1089
|
+
error: `Source skill '${source_skill_id}' not found`,
|
|
1090
|
+
hint: 'Use skill_list to see available skills',
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
// Check if new ID already exists
|
|
1094
|
+
const existing = skillLoader.getSkill(new_id);
|
|
1095
|
+
if (existing) {
|
|
1096
|
+
return {
|
|
1097
|
+
success: false,
|
|
1098
|
+
error: `Skill with ID '${new_id}' already exists`,
|
|
1099
|
+
hint: 'Use a different ID for the duplicate',
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
// Validate new ID format
|
|
1103
|
+
if (!/^[a-z0-9-]+$/.test(new_id)) {
|
|
1104
|
+
return {
|
|
1105
|
+
success: false,
|
|
1106
|
+
error: 'Invalid skill ID format',
|
|
1107
|
+
hint: 'Skill ID should be lowercase, using only letters, numbers, and hyphens',
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
try {
|
|
1111
|
+
// Create the duplicated skill with modifications
|
|
1112
|
+
const newSkill = await skillLoader.createSkill({
|
|
1113
|
+
id: new_id,
|
|
1114
|
+
name: modifications.name || `${sourceSkill.name} (Copy)`,
|
|
1115
|
+
description: modifications.description || sourceSkill.description,
|
|
1116
|
+
prompt: modifications.prompt || sourceSkill.prompt,
|
|
1117
|
+
icon: modifications.icon || sourceSkill.icon,
|
|
1118
|
+
category: modifications.category || sourceSkill.category,
|
|
1119
|
+
parameters: modifications.parameters || sourceSkill.parameters,
|
|
1120
|
+
priority: sourceSkill.priority,
|
|
1121
|
+
enabled: true,
|
|
1122
|
+
});
|
|
1123
|
+
this.daemon.logEvent(this.taskId, 'log', {
|
|
1124
|
+
message: `Duplicated skill '${sourceSkill.name}' as '${newSkill.name}'`,
|
|
1125
|
+
sourceSkillId: source_skill_id,
|
|
1126
|
+
newSkillId: new_id,
|
|
1127
|
+
});
|
|
1128
|
+
return {
|
|
1129
|
+
success: true,
|
|
1130
|
+
message: `Skill duplicated successfully`,
|
|
1131
|
+
source_skill: {
|
|
1132
|
+
id: sourceSkill.id,
|
|
1133
|
+
name: sourceSkill.name,
|
|
1134
|
+
},
|
|
1135
|
+
new_skill: {
|
|
1136
|
+
id: newSkill.id,
|
|
1137
|
+
name: newSkill.name,
|
|
1138
|
+
source: newSkill.source,
|
|
1139
|
+
filePath: newSkill.filePath,
|
|
1140
|
+
},
|
|
1141
|
+
modifications_applied: Object.keys(modifications),
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
catch (error) {
|
|
1145
|
+
return {
|
|
1146
|
+
success: false,
|
|
1147
|
+
error: `Failed to duplicate skill: ${error.message}`,
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Update an existing skill
|
|
1153
|
+
*/
|
|
1154
|
+
async executeSkillUpdate(input) {
|
|
1155
|
+
const { skill_id, updates } = input;
|
|
1156
|
+
const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
|
|
1157
|
+
const skill = skillLoader.getSkill(skill_id);
|
|
1158
|
+
if (!skill) {
|
|
1159
|
+
return {
|
|
1160
|
+
success: false,
|
|
1161
|
+
error: `Skill '${skill_id}' not found`,
|
|
1162
|
+
hint: 'Use skill_list to see available skills',
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
// Check if skill can be updated
|
|
1166
|
+
if (skill.source === 'bundled') {
|
|
1167
|
+
return {
|
|
1168
|
+
success: false,
|
|
1169
|
+
error: `Cannot update bundled skill '${skill_id}'`,
|
|
1170
|
+
hint: 'Bundled skills are read-only. Use skill_duplicate to create an editable copy.',
|
|
1171
|
+
skill_source: skill.source,
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
try {
|
|
1175
|
+
const updatedSkill = await skillLoader.updateSkill(skill_id, updates);
|
|
1176
|
+
if (!updatedSkill) {
|
|
1177
|
+
return {
|
|
1178
|
+
success: false,
|
|
1179
|
+
error: 'Failed to update skill',
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
this.daemon.logEvent(this.taskId, 'log', {
|
|
1183
|
+
message: `Updated skill: ${updatedSkill.name}`,
|
|
1184
|
+
skillId: skill_id,
|
|
1185
|
+
updatedFields: Object.keys(updates),
|
|
1186
|
+
});
|
|
1187
|
+
return {
|
|
1188
|
+
success: true,
|
|
1189
|
+
message: `Skill '${updatedSkill.name}' updated successfully`,
|
|
1190
|
+
updated_fields: Object.keys(updates),
|
|
1191
|
+
skill: {
|
|
1192
|
+
id: updatedSkill.id,
|
|
1193
|
+
name: updatedSkill.name,
|
|
1194
|
+
source: updatedSkill.source,
|
|
1195
|
+
filePath: updatedSkill.filePath,
|
|
1196
|
+
},
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
catch (error) {
|
|
1200
|
+
return {
|
|
1201
|
+
success: false,
|
|
1202
|
+
error: `Failed to update skill: ${error.message}`,
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Delete a skill
|
|
1208
|
+
*/
|
|
1209
|
+
async executeSkillDelete(input) {
|
|
1210
|
+
const { skill_id } = input;
|
|
1211
|
+
const skillLoader = (0, custom_skill_loader_1.getCustomSkillLoader)();
|
|
1212
|
+
const skill = skillLoader.getSkill(skill_id);
|
|
1213
|
+
if (!skill) {
|
|
1214
|
+
return {
|
|
1215
|
+
success: false,
|
|
1216
|
+
error: `Skill '${skill_id}' not found`,
|
|
1217
|
+
hint: 'Use skill_list to see available skills',
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
// Check if skill can be deleted
|
|
1221
|
+
if (skill.source === 'bundled') {
|
|
1222
|
+
return {
|
|
1223
|
+
success: false,
|
|
1224
|
+
error: `Cannot delete bundled skill '${skill_id}'`,
|
|
1225
|
+
hint: 'Bundled skills are read-only and cannot be deleted.',
|
|
1226
|
+
skill_source: skill.source,
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
try {
|
|
1230
|
+
const deleted = await skillLoader.deleteSkill(skill_id);
|
|
1231
|
+
if (!deleted) {
|
|
1232
|
+
return {
|
|
1233
|
+
success: false,
|
|
1234
|
+
error: 'Failed to delete skill',
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
this.daemon.logEvent(this.taskId, 'log', {
|
|
1238
|
+
message: `Deleted skill: ${skill.name}`,
|
|
1239
|
+
skillId: skill_id,
|
|
1240
|
+
});
|
|
1241
|
+
return {
|
|
1242
|
+
success: true,
|
|
1243
|
+
message: `Skill '${skill.name}' deleted successfully`,
|
|
1244
|
+
deleted_skill: {
|
|
1245
|
+
id: skill.id,
|
|
1246
|
+
name: skill.name,
|
|
1247
|
+
source: skill.source,
|
|
1248
|
+
},
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
catch (error) {
|
|
1252
|
+
return {
|
|
1253
|
+
success: false,
|
|
1254
|
+
error: `Failed to delete skill: ${error.message}`,
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Define file operation tools
|
|
1260
|
+
*/
|
|
1261
|
+
getFileToolDefinitions() {
|
|
1262
|
+
return [
|
|
1263
|
+
{
|
|
1264
|
+
name: 'read_file',
|
|
1265
|
+
description: 'Read the contents of a file in the workspace. Supports plain text files, DOCX (Word documents), and PDF files. For DOCX and PDF, extracts and returns the text content.',
|
|
1266
|
+
input_schema: {
|
|
1267
|
+
type: 'object',
|
|
1268
|
+
properties: {
|
|
1269
|
+
path: {
|
|
1270
|
+
type: 'string',
|
|
1271
|
+
description: 'Relative path to the file within the workspace',
|
|
1272
|
+
},
|
|
1273
|
+
},
|
|
1274
|
+
required: ['path'],
|
|
1275
|
+
},
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
name: 'write_file',
|
|
1279
|
+
description: 'Write content to a file in the workspace (creates or overwrites)',
|
|
1280
|
+
input_schema: {
|
|
1281
|
+
type: 'object',
|
|
1282
|
+
properties: {
|
|
1283
|
+
path: {
|
|
1284
|
+
type: 'string',
|
|
1285
|
+
description: 'Relative path to the file within the workspace',
|
|
1286
|
+
},
|
|
1287
|
+
content: {
|
|
1288
|
+
type: 'string',
|
|
1289
|
+
description: 'Content to write to the file',
|
|
1290
|
+
},
|
|
1291
|
+
},
|
|
1292
|
+
required: ['path', 'content'],
|
|
1293
|
+
},
|
|
1294
|
+
},
|
|
1295
|
+
{
|
|
1296
|
+
name: 'copy_file',
|
|
1297
|
+
description: 'Copy a file to a new location. Supports binary files (DOCX, PDF, images, etc.) and preserves exact file content.',
|
|
1298
|
+
input_schema: {
|
|
1299
|
+
type: 'object',
|
|
1300
|
+
properties: {
|
|
1301
|
+
sourcePath: {
|
|
1302
|
+
type: 'string',
|
|
1303
|
+
description: 'Path to the source file to copy',
|
|
1304
|
+
},
|
|
1305
|
+
destPath: {
|
|
1306
|
+
type: 'string',
|
|
1307
|
+
description: 'Path for the destination file (the copy)',
|
|
1308
|
+
},
|
|
1309
|
+
},
|
|
1310
|
+
required: ['sourcePath', 'destPath'],
|
|
1311
|
+
},
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
name: 'list_directory',
|
|
1315
|
+
description: 'List files and folders in a directory',
|
|
1316
|
+
input_schema: {
|
|
1317
|
+
type: 'object',
|
|
1318
|
+
properties: {
|
|
1319
|
+
path: {
|
|
1320
|
+
type: 'string',
|
|
1321
|
+
description: 'Relative path to the directory (or "." for workspace root)',
|
|
1322
|
+
},
|
|
1323
|
+
},
|
|
1324
|
+
required: ['path'],
|
|
1325
|
+
},
|
|
1326
|
+
},
|
|
1327
|
+
{
|
|
1328
|
+
name: 'list_directory_with_sizes',
|
|
1329
|
+
description: 'List files and folders in a directory with size summary (MCP-style output)',
|
|
1330
|
+
input_schema: {
|
|
1331
|
+
type: 'object',
|
|
1332
|
+
properties: {
|
|
1333
|
+
path: {
|
|
1334
|
+
type: 'string',
|
|
1335
|
+
description: 'Relative or absolute path to the directory',
|
|
1336
|
+
},
|
|
1337
|
+
},
|
|
1338
|
+
required: ['path'],
|
|
1339
|
+
},
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
name: 'get_file_info',
|
|
1343
|
+
description: 'Get file or directory metadata (size, timestamps, permissions)',
|
|
1344
|
+
input_schema: {
|
|
1345
|
+
type: 'object',
|
|
1346
|
+
properties: {
|
|
1347
|
+
path: {
|
|
1348
|
+
type: 'string',
|
|
1349
|
+
description: 'Path to the file or directory',
|
|
1350
|
+
},
|
|
1351
|
+
},
|
|
1352
|
+
required: ['path'],
|
|
1353
|
+
},
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
name: 'rename_file',
|
|
1357
|
+
description: 'Rename or move a file',
|
|
1358
|
+
input_schema: {
|
|
1359
|
+
type: 'object',
|
|
1360
|
+
properties: {
|
|
1361
|
+
oldPath: {
|
|
1362
|
+
type: 'string',
|
|
1363
|
+
description: 'Current path of the file',
|
|
1364
|
+
},
|
|
1365
|
+
newPath: {
|
|
1366
|
+
type: 'string',
|
|
1367
|
+
description: 'New path for the file',
|
|
1368
|
+
},
|
|
1369
|
+
},
|
|
1370
|
+
required: ['oldPath', 'newPath'],
|
|
1371
|
+
},
|
|
1372
|
+
},
|
|
1373
|
+
{
|
|
1374
|
+
name: 'delete_file',
|
|
1375
|
+
description: 'Delete a file (requires user approval)',
|
|
1376
|
+
input_schema: {
|
|
1377
|
+
type: 'object',
|
|
1378
|
+
properties: {
|
|
1379
|
+
path: {
|
|
1380
|
+
type: 'string',
|
|
1381
|
+
description: 'Path to the file to delete',
|
|
1382
|
+
},
|
|
1383
|
+
},
|
|
1384
|
+
required: ['path'],
|
|
1385
|
+
},
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
name: 'create_directory',
|
|
1389
|
+
description: 'Create a new directory',
|
|
1390
|
+
input_schema: {
|
|
1391
|
+
type: 'object',
|
|
1392
|
+
properties: {
|
|
1393
|
+
path: {
|
|
1394
|
+
type: 'string',
|
|
1395
|
+
description: 'Path for the new directory',
|
|
1396
|
+
},
|
|
1397
|
+
},
|
|
1398
|
+
required: ['path'],
|
|
1399
|
+
},
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
name: 'search_files',
|
|
1403
|
+
description: 'Search for files by name or content',
|
|
1404
|
+
input_schema: {
|
|
1405
|
+
type: 'object',
|
|
1406
|
+
properties: {
|
|
1407
|
+
query: {
|
|
1408
|
+
type: 'string',
|
|
1409
|
+
description: 'Search query (filename or content)',
|
|
1410
|
+
},
|
|
1411
|
+
path: {
|
|
1412
|
+
type: 'string',
|
|
1413
|
+
description: 'Directory to search in (optional, defaults to workspace root)',
|
|
1414
|
+
},
|
|
1415
|
+
},
|
|
1416
|
+
required: ['query'],
|
|
1417
|
+
},
|
|
1418
|
+
},
|
|
1419
|
+
];
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Define skill tools
|
|
1423
|
+
*/
|
|
1424
|
+
getSkillToolDefinitions() {
|
|
1425
|
+
return [
|
|
1426
|
+
{
|
|
1427
|
+
name: 'create_spreadsheet',
|
|
1428
|
+
description: 'Create an Excel spreadsheet with data, formulas, and formatting',
|
|
1429
|
+
input_schema: {
|
|
1430
|
+
type: 'object',
|
|
1431
|
+
properties: {
|
|
1432
|
+
filename: { type: 'string', description: 'Name of the Excel file (without extension)' },
|
|
1433
|
+
sheets: {
|
|
1434
|
+
type: 'array',
|
|
1435
|
+
description: 'Array of sheets to create',
|
|
1436
|
+
items: {
|
|
1437
|
+
type: 'object',
|
|
1438
|
+
properties: {
|
|
1439
|
+
name: { type: 'string', description: 'Sheet name' },
|
|
1440
|
+
data: {
|
|
1441
|
+
type: 'array',
|
|
1442
|
+
description: '2D array of cell values (rows of columns)',
|
|
1443
|
+
items: {
|
|
1444
|
+
type: 'array',
|
|
1445
|
+
description: 'Row of cell values',
|
|
1446
|
+
items: { type: 'string', description: 'Cell value' },
|
|
1447
|
+
},
|
|
1448
|
+
},
|
|
1449
|
+
},
|
|
1450
|
+
},
|
|
1451
|
+
},
|
|
1452
|
+
},
|
|
1453
|
+
required: ['filename', 'sheets'],
|
|
1454
|
+
},
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
name: 'create_document',
|
|
1458
|
+
description: 'Create a formatted Word document or PDF',
|
|
1459
|
+
input_schema: {
|
|
1460
|
+
type: 'object',
|
|
1461
|
+
properties: {
|
|
1462
|
+
filename: { type: 'string', description: 'Name of the document' },
|
|
1463
|
+
format: { type: 'string', enum: ['docx', 'pdf'], description: 'Output format' },
|
|
1464
|
+
content: {
|
|
1465
|
+
type: 'array',
|
|
1466
|
+
description: 'Document content blocks',
|
|
1467
|
+
items: {
|
|
1468
|
+
type: 'object',
|
|
1469
|
+
properties: {
|
|
1470
|
+
type: { type: 'string', enum: ['heading', 'paragraph', 'list'] },
|
|
1471
|
+
text: { type: 'string' },
|
|
1472
|
+
level: { type: 'number', description: 'For headings: 1-6' },
|
|
1473
|
+
},
|
|
1474
|
+
},
|
|
1475
|
+
},
|
|
1476
|
+
},
|
|
1477
|
+
required: ['filename', 'format', 'content'],
|
|
1478
|
+
},
|
|
1479
|
+
},
|
|
1480
|
+
{
|
|
1481
|
+
name: 'edit_document',
|
|
1482
|
+
description: 'Edit an existing Word document (DOCX). Supports multiple actions: append (default), move_section, insert_after_section, list_sections. Use this to modify existing documents without recreating them from scratch.',
|
|
1483
|
+
input_schema: {
|
|
1484
|
+
type: 'object',
|
|
1485
|
+
properties: {
|
|
1486
|
+
sourcePath: {
|
|
1487
|
+
type: 'string',
|
|
1488
|
+
description: 'Path to the existing DOCX file to edit',
|
|
1489
|
+
},
|
|
1490
|
+
destPath: {
|
|
1491
|
+
type: 'string',
|
|
1492
|
+
description: 'Optional: Path for the output file. If not specified, the source file will be overwritten.',
|
|
1493
|
+
},
|
|
1494
|
+
action: {
|
|
1495
|
+
type: 'string',
|
|
1496
|
+
enum: ['append', 'move_section', 'insert_after_section', 'list_sections'],
|
|
1497
|
+
description: 'Action to perform: append (default) adds content at end, move_section moves a section to a new position, insert_after_section inserts content after a specific section, list_sections lists all sections',
|
|
1498
|
+
},
|
|
1499
|
+
newContent: {
|
|
1500
|
+
type: 'array',
|
|
1501
|
+
description: 'For append/insert_after_section: Content blocks to add',
|
|
1502
|
+
items: {
|
|
1503
|
+
type: 'object',
|
|
1504
|
+
properties: {
|
|
1505
|
+
type: {
|
|
1506
|
+
type: 'string',
|
|
1507
|
+
enum: ['heading', 'paragraph', 'list', 'table'],
|
|
1508
|
+
description: 'Type of content block',
|
|
1509
|
+
},
|
|
1510
|
+
text: {
|
|
1511
|
+
type: 'string',
|
|
1512
|
+
description: 'Text content for the block',
|
|
1513
|
+
},
|
|
1514
|
+
level: {
|
|
1515
|
+
type: 'number',
|
|
1516
|
+
description: 'For headings: level 1-6',
|
|
1517
|
+
},
|
|
1518
|
+
items: {
|
|
1519
|
+
type: 'array',
|
|
1520
|
+
items: { type: 'string' },
|
|
1521
|
+
description: 'For lists: array of list items',
|
|
1522
|
+
},
|
|
1523
|
+
rows: {
|
|
1524
|
+
type: 'array',
|
|
1525
|
+
items: {
|
|
1526
|
+
type: 'array',
|
|
1527
|
+
items: { type: 'string' },
|
|
1528
|
+
},
|
|
1529
|
+
description: 'For tables: 2D array of cell values',
|
|
1530
|
+
},
|
|
1531
|
+
},
|
|
1532
|
+
required: ['type', 'text'],
|
|
1533
|
+
},
|
|
1534
|
+
},
|
|
1535
|
+
sectionToMove: {
|
|
1536
|
+
type: 'string',
|
|
1537
|
+
description: 'For move_section: Section number or heading text to move (e.g., "8" or "Ticket Indexing")',
|
|
1538
|
+
},
|
|
1539
|
+
afterSection: {
|
|
1540
|
+
type: 'string',
|
|
1541
|
+
description: 'For move_section: Section number or heading text after which to place the moved section (e.g., "7" or "Data Storage")',
|
|
1542
|
+
},
|
|
1543
|
+
insertAfterSection: {
|
|
1544
|
+
type: 'string',
|
|
1545
|
+
description: 'For insert_after_section: Section number or heading text after which to insert new content',
|
|
1546
|
+
},
|
|
1547
|
+
},
|
|
1548
|
+
required: ['sourcePath'],
|
|
1549
|
+
},
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
name: 'create_presentation',
|
|
1553
|
+
description: 'Create a PowerPoint presentation',
|
|
1554
|
+
input_schema: {
|
|
1555
|
+
type: 'object',
|
|
1556
|
+
properties: {
|
|
1557
|
+
filename: { type: 'string', description: 'Name of the presentation' },
|
|
1558
|
+
slides: {
|
|
1559
|
+
type: 'array',
|
|
1560
|
+
items: {
|
|
1561
|
+
type: 'object',
|
|
1562
|
+
properties: {
|
|
1563
|
+
title: { type: 'string' },
|
|
1564
|
+
content: { type: 'array', items: { type: 'string' } },
|
|
1565
|
+
},
|
|
1566
|
+
},
|
|
1567
|
+
},
|
|
1568
|
+
},
|
|
1569
|
+
required: ['filename', 'slides'],
|
|
1570
|
+
},
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
name: 'organize_folder',
|
|
1574
|
+
description: 'Organize files in a folder by type, date, or custom rules',
|
|
1575
|
+
input_schema: {
|
|
1576
|
+
type: 'object',
|
|
1577
|
+
properties: {
|
|
1578
|
+
path: { type: 'string', description: 'Folder path to organize' },
|
|
1579
|
+
strategy: {
|
|
1580
|
+
type: 'string',
|
|
1581
|
+
enum: ['by_type', 'by_date', 'custom'],
|
|
1582
|
+
description: 'Organization strategy',
|
|
1583
|
+
},
|
|
1584
|
+
rules: { type: 'object', description: 'Custom organization rules (if strategy is custom)' },
|
|
1585
|
+
},
|
|
1586
|
+
required: ['path', 'strategy'],
|
|
1587
|
+
},
|
|
1588
|
+
},
|
|
1589
|
+
{
|
|
1590
|
+
name: 'use_skill',
|
|
1591
|
+
description: 'Use a custom skill by ID to help accomplish a task. Skills are pre-configured prompt templates ' +
|
|
1592
|
+
'that provide specialized capabilities. Use this when a skill matches what you need to do. ' +
|
|
1593
|
+
'The skill\'s expanded prompt will be injected into your context to guide execution.',
|
|
1594
|
+
input_schema: {
|
|
1595
|
+
type: 'object',
|
|
1596
|
+
properties: {
|
|
1597
|
+
skill_id: {
|
|
1598
|
+
type: 'string',
|
|
1599
|
+
description: 'The ID of the skill to use (e.g., "git-commit", "code-review", "translate")',
|
|
1600
|
+
},
|
|
1601
|
+
parameters: {
|
|
1602
|
+
type: 'object',
|
|
1603
|
+
description: 'Parameter values for the skill. Check skill description for required parameters.',
|
|
1604
|
+
additionalProperties: true,
|
|
1605
|
+
},
|
|
1606
|
+
},
|
|
1607
|
+
required: ['skill_id'],
|
|
1608
|
+
},
|
|
1609
|
+
},
|
|
1610
|
+
// Skill Management Tools
|
|
1611
|
+
{
|
|
1612
|
+
name: 'skill_list',
|
|
1613
|
+
description: 'List all available skills with their metadata including source (bundled, managed, workspace), ' +
|
|
1614
|
+
'file paths, and status. Use this to discover what skills exist and where they are stored.',
|
|
1615
|
+
input_schema: {
|
|
1616
|
+
type: 'object',
|
|
1617
|
+
properties: {
|
|
1618
|
+
source: {
|
|
1619
|
+
type: 'string',
|
|
1620
|
+
enum: ['all', 'bundled', 'managed', 'workspace'],
|
|
1621
|
+
description: 'Filter skills by source. Default is "all".',
|
|
1622
|
+
},
|
|
1623
|
+
include_disabled: {
|
|
1624
|
+
type: 'boolean',
|
|
1625
|
+
description: 'Include disabled skills in the list. Default is true.',
|
|
1626
|
+
},
|
|
1627
|
+
},
|
|
1628
|
+
},
|
|
1629
|
+
},
|
|
1630
|
+
{
|
|
1631
|
+
name: 'skill_get',
|
|
1632
|
+
description: 'Get the full JSON content and metadata of a specific skill by ID. ' +
|
|
1633
|
+
'Returns the complete skill definition including prompt, parameters, and configuration.',
|
|
1634
|
+
input_schema: {
|
|
1635
|
+
type: 'object',
|
|
1636
|
+
properties: {
|
|
1637
|
+
skill_id: {
|
|
1638
|
+
type: 'string',
|
|
1639
|
+
description: 'The ID of the skill to retrieve',
|
|
1640
|
+
},
|
|
1641
|
+
},
|
|
1642
|
+
required: ['skill_id'],
|
|
1643
|
+
},
|
|
1644
|
+
},
|
|
1645
|
+
{
|
|
1646
|
+
name: 'skill_create',
|
|
1647
|
+
description: 'Create a new custom skill. The skill will be saved to the managed skills directory ' +
|
|
1648
|
+
'(~/Library/Application Support/cowork-os/skills/). Provide the full skill definition.',
|
|
1649
|
+
input_schema: {
|
|
1650
|
+
type: 'object',
|
|
1651
|
+
properties: {
|
|
1652
|
+
id: {
|
|
1653
|
+
type: 'string',
|
|
1654
|
+
description: 'Unique identifier for the skill (lowercase, hyphens allowed, e.g., "my-custom-skill")',
|
|
1655
|
+
},
|
|
1656
|
+
name: {
|
|
1657
|
+
type: 'string',
|
|
1658
|
+
description: 'Human-readable name for the skill',
|
|
1659
|
+
},
|
|
1660
|
+
description: {
|
|
1661
|
+
type: 'string',
|
|
1662
|
+
description: 'Brief description of what the skill does',
|
|
1663
|
+
},
|
|
1664
|
+
prompt: {
|
|
1665
|
+
type: 'string',
|
|
1666
|
+
description: 'The prompt template. Use {{paramName}} for parameter placeholders.',
|
|
1667
|
+
},
|
|
1668
|
+
icon: {
|
|
1669
|
+
type: 'string',
|
|
1670
|
+
description: 'Emoji icon for the skill (optional)',
|
|
1671
|
+
},
|
|
1672
|
+
category: {
|
|
1673
|
+
type: 'string',
|
|
1674
|
+
description: 'Category for grouping (e.g., "Research", "Development", "Writing")',
|
|
1675
|
+
},
|
|
1676
|
+
parameters: {
|
|
1677
|
+
type: 'array',
|
|
1678
|
+
description: 'Array of parameter definitions',
|
|
1679
|
+
items: {
|
|
1680
|
+
type: 'object',
|
|
1681
|
+
properties: {
|
|
1682
|
+
name: { type: 'string', description: 'Parameter name (used in {{name}} placeholders)' },
|
|
1683
|
+
type: { type: 'string', enum: ['string', 'number', 'boolean', 'select'], description: 'Parameter type' },
|
|
1684
|
+
description: { type: 'string', description: 'Parameter description' },
|
|
1685
|
+
required: { type: 'boolean', description: 'Whether the parameter is required' },
|
|
1686
|
+
default: { type: 'string', description: 'Default value' },
|
|
1687
|
+
options: { type: 'array', items: { type: 'string' }, description: 'Options for select type' },
|
|
1688
|
+
},
|
|
1689
|
+
required: ['name', 'type', 'description'],
|
|
1690
|
+
},
|
|
1691
|
+
},
|
|
1692
|
+
enabled: {
|
|
1693
|
+
type: 'boolean',
|
|
1694
|
+
description: 'Whether the skill is enabled. Default is true.',
|
|
1695
|
+
},
|
|
1696
|
+
},
|
|
1697
|
+
required: ['id', 'name', 'description', 'prompt'],
|
|
1698
|
+
},
|
|
1699
|
+
},
|
|
1700
|
+
{
|
|
1701
|
+
name: 'skill_duplicate',
|
|
1702
|
+
description: 'Duplicate an existing skill with a new ID and optional modifications. ' +
|
|
1703
|
+
'Great for creating variations of existing skills (e.g., changing time ranges, targets).',
|
|
1704
|
+
input_schema: {
|
|
1705
|
+
type: 'object',
|
|
1706
|
+
properties: {
|
|
1707
|
+
source_skill_id: {
|
|
1708
|
+
type: 'string',
|
|
1709
|
+
description: 'The ID of the skill to duplicate',
|
|
1710
|
+
},
|
|
1711
|
+
new_id: {
|
|
1712
|
+
type: 'string',
|
|
1713
|
+
description: 'The ID for the new duplicated skill',
|
|
1714
|
+
},
|
|
1715
|
+
modifications: {
|
|
1716
|
+
type: 'object',
|
|
1717
|
+
description: 'Fields to modify in the duplicated skill (name, description, prompt, etc.)',
|
|
1718
|
+
properties: {
|
|
1719
|
+
name: { type: 'string', description: 'New name for the skill' },
|
|
1720
|
+
description: { type: 'string', description: 'New description' },
|
|
1721
|
+
prompt: { type: 'string', description: 'New prompt template' },
|
|
1722
|
+
icon: { type: 'string', description: 'New icon' },
|
|
1723
|
+
category: { type: 'string', description: 'New category' },
|
|
1724
|
+
parameters: { type: 'array', description: 'New parameters array' },
|
|
1725
|
+
},
|
|
1726
|
+
},
|
|
1727
|
+
},
|
|
1728
|
+
required: ['source_skill_id', 'new_id'],
|
|
1729
|
+
},
|
|
1730
|
+
},
|
|
1731
|
+
{
|
|
1732
|
+
name: 'skill_update',
|
|
1733
|
+
description: 'Update an existing skill. Only managed and workspace skills can be updated (not bundled). ' +
|
|
1734
|
+
'Provide only the fields you want to change.',
|
|
1735
|
+
input_schema: {
|
|
1736
|
+
type: 'object',
|
|
1737
|
+
properties: {
|
|
1738
|
+
skill_id: {
|
|
1739
|
+
type: 'string',
|
|
1740
|
+
description: 'The ID of the skill to update',
|
|
1741
|
+
},
|
|
1742
|
+
updates: {
|
|
1743
|
+
type: 'object',
|
|
1744
|
+
description: 'Fields to update',
|
|
1745
|
+
properties: {
|
|
1746
|
+
name: { type: 'string', description: 'New name' },
|
|
1747
|
+
description: { type: 'string', description: 'New description' },
|
|
1748
|
+
prompt: { type: 'string', description: 'New prompt template' },
|
|
1749
|
+
icon: { type: 'string', description: 'New icon' },
|
|
1750
|
+
category: { type: 'string', description: 'New category' },
|
|
1751
|
+
parameters: { type: 'array', description: 'New parameters array' },
|
|
1752
|
+
enabled: { type: 'boolean', description: 'Enable/disable the skill' },
|
|
1753
|
+
},
|
|
1754
|
+
},
|
|
1755
|
+
},
|
|
1756
|
+
required: ['skill_id', 'updates'],
|
|
1757
|
+
},
|
|
1758
|
+
},
|
|
1759
|
+
{
|
|
1760
|
+
name: 'skill_delete',
|
|
1761
|
+
description: 'Delete a skill. Only managed and workspace skills can be deleted (not bundled). ' +
|
|
1762
|
+
'This permanently removes the skill file.',
|
|
1763
|
+
input_schema: {
|
|
1764
|
+
type: 'object',
|
|
1765
|
+
properties: {
|
|
1766
|
+
skill_id: {
|
|
1767
|
+
type: 'string',
|
|
1768
|
+
description: 'The ID of the skill to delete',
|
|
1769
|
+
},
|
|
1770
|
+
},
|
|
1771
|
+
required: ['skill_id'],
|
|
1772
|
+
},
|
|
1773
|
+
},
|
|
1774
|
+
];
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* Define search tools
|
|
1778
|
+
*/
|
|
1779
|
+
getSearchToolDefinitions() {
|
|
1780
|
+
const providers = search_1.SearchProviderFactory.getAvailableProviders();
|
|
1781
|
+
const configuredProviders = providers.filter((p) => p.configured);
|
|
1782
|
+
const allSupportedTypes = [
|
|
1783
|
+
...new Set(configuredProviders.flatMap((p) => p.supportedTypes)),
|
|
1784
|
+
];
|
|
1785
|
+
return [
|
|
1786
|
+
{
|
|
1787
|
+
name: 'web_search',
|
|
1788
|
+
description: `Search the web for information. This is the PRIMARY tool for research tasks - finding news, trends, discussions, and information on any topic. ` +
|
|
1789
|
+
`Use this FIRST for research, then use web_fetch if you need to read specific URLs from the results. ` +
|
|
1790
|
+
`Do NOT use browser_navigate for research - web_search is faster and more efficient. ` +
|
|
1791
|
+
`Configured providers: ${configuredProviders.map((p) => p.name).join(', ')}`,
|
|
1792
|
+
input_schema: {
|
|
1793
|
+
type: 'object',
|
|
1794
|
+
properties: {
|
|
1795
|
+
query: {
|
|
1796
|
+
type: 'string',
|
|
1797
|
+
description: 'The search query',
|
|
1798
|
+
},
|
|
1799
|
+
searchType: {
|
|
1800
|
+
type: 'string',
|
|
1801
|
+
enum: allSupportedTypes,
|
|
1802
|
+
description: `Type of search. Available: ${allSupportedTypes.join(', ')}`,
|
|
1803
|
+
},
|
|
1804
|
+
maxResults: {
|
|
1805
|
+
type: 'number',
|
|
1806
|
+
description: 'Maximum number of results (default: 10, max: 20)',
|
|
1807
|
+
},
|
|
1808
|
+
provider: {
|
|
1809
|
+
type: 'string',
|
|
1810
|
+
enum: configuredProviders.map((p) => p.type),
|
|
1811
|
+
description: `Override the search provider. Available: ${configuredProviders.map((p) => p.type).join(', ')}`,
|
|
1812
|
+
},
|
|
1813
|
+
dateRange: {
|
|
1814
|
+
type: 'string',
|
|
1815
|
+
enum: ['day', 'week', 'month', 'year'],
|
|
1816
|
+
description: 'Filter results by date range',
|
|
1817
|
+
},
|
|
1818
|
+
region: {
|
|
1819
|
+
type: 'string',
|
|
1820
|
+
description: 'Region code for localized results (e.g., "us", "uk", "de")',
|
|
1821
|
+
},
|
|
1822
|
+
},
|
|
1823
|
+
required: ['query'],
|
|
1824
|
+
},
|
|
1825
|
+
},
|
|
1826
|
+
];
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* Define X/Twitter tools (bird CLI)
|
|
1830
|
+
*/
|
|
1831
|
+
getXToolDefinitions() {
|
|
1832
|
+
return [
|
|
1833
|
+
{
|
|
1834
|
+
name: 'x_action',
|
|
1835
|
+
description: 'Use the connected X/Twitter account to read, search, and post. ' +
|
|
1836
|
+
'Posting actions (tweet/reply/follow/unfollow) require user approval.',
|
|
1837
|
+
input_schema: {
|
|
1838
|
+
type: 'object',
|
|
1839
|
+
properties: {
|
|
1840
|
+
action: {
|
|
1841
|
+
type: 'string',
|
|
1842
|
+
enum: [
|
|
1843
|
+
'whoami',
|
|
1844
|
+
'read',
|
|
1845
|
+
'thread',
|
|
1846
|
+
'replies',
|
|
1847
|
+
'search',
|
|
1848
|
+
'user_tweets',
|
|
1849
|
+
'mentions',
|
|
1850
|
+
'home',
|
|
1851
|
+
'tweet',
|
|
1852
|
+
'reply',
|
|
1853
|
+
'follow',
|
|
1854
|
+
'unfollow',
|
|
1855
|
+
],
|
|
1856
|
+
description: 'Action to perform',
|
|
1857
|
+
},
|
|
1858
|
+
id_or_url: {
|
|
1859
|
+
type: 'string',
|
|
1860
|
+
description: 'Tweet URL or ID (for read/thread/replies/reply)',
|
|
1861
|
+
},
|
|
1862
|
+
query: {
|
|
1863
|
+
type: 'string',
|
|
1864
|
+
description: 'Search query (for search)',
|
|
1865
|
+
},
|
|
1866
|
+
user: {
|
|
1867
|
+
type: 'string',
|
|
1868
|
+
description: 'User handle (with or without @) for user_tweets/mentions/follow/unfollow',
|
|
1869
|
+
},
|
|
1870
|
+
text: {
|
|
1871
|
+
type: 'string',
|
|
1872
|
+
description: 'Text for tweet/reply',
|
|
1873
|
+
},
|
|
1874
|
+
timeline: {
|
|
1875
|
+
type: 'string',
|
|
1876
|
+
enum: ['for_you', 'following'],
|
|
1877
|
+
description: 'Timeline for home (default: for_you)',
|
|
1878
|
+
},
|
|
1879
|
+
count: {
|
|
1880
|
+
type: 'number',
|
|
1881
|
+
description: 'Max results (1-50) for search/mentions/home/user_tweets',
|
|
1882
|
+
},
|
|
1883
|
+
media: {
|
|
1884
|
+
type: 'array',
|
|
1885
|
+
description: 'Media file paths (workspace-relative). Up to 4 images or 1 video.',
|
|
1886
|
+
items: { type: 'string' },
|
|
1887
|
+
},
|
|
1888
|
+
alt: {
|
|
1889
|
+
type: 'string',
|
|
1890
|
+
description: 'Alt text for media (single string)',
|
|
1891
|
+
},
|
|
1892
|
+
},
|
|
1893
|
+
required: ['action'],
|
|
1894
|
+
},
|
|
1895
|
+
},
|
|
1896
|
+
];
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Define shell tools
|
|
1900
|
+
*/
|
|
1901
|
+
getShellToolDefinitions() {
|
|
1902
|
+
return [
|
|
1903
|
+
{
|
|
1904
|
+
name: 'run_command',
|
|
1905
|
+
description: 'Execute a shell command in the workspace directory. IMPORTANT: This tool requires user approval before execution. The user will see the command and can approve or deny it. Use this for installing packages (npm, pip, brew), running build commands, git operations, or any terminal commands.',
|
|
1906
|
+
input_schema: {
|
|
1907
|
+
type: 'object',
|
|
1908
|
+
properties: {
|
|
1909
|
+
command: {
|
|
1910
|
+
type: 'string',
|
|
1911
|
+
description: 'The shell command to execute (e.g., "npm install", "git status", "ls -la")',
|
|
1912
|
+
},
|
|
1913
|
+
cwd: {
|
|
1914
|
+
type: 'string',
|
|
1915
|
+
description: 'Working directory for the command (optional, defaults to workspace root)',
|
|
1916
|
+
},
|
|
1917
|
+
timeout: {
|
|
1918
|
+
type: 'number',
|
|
1919
|
+
description: 'Timeout in milliseconds (optional, default: 60000, max: 300000)',
|
|
1920
|
+
},
|
|
1921
|
+
},
|
|
1922
|
+
required: ['command'],
|
|
1923
|
+
},
|
|
1924
|
+
},
|
|
1925
|
+
];
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Set the agent's personality
|
|
1929
|
+
*/
|
|
1930
|
+
setPersonality(input) {
|
|
1931
|
+
const personalityId = input.personality;
|
|
1932
|
+
const validIds = ['professional', 'friendly', 'concise', 'creative', 'technical', 'casual'];
|
|
1933
|
+
if (!validIds.includes(personalityId)) {
|
|
1934
|
+
throw new Error(`Invalid personality: ${personalityId}. Valid options are: ${validIds.join(', ')}`);
|
|
1935
|
+
}
|
|
1936
|
+
// Save the new personality
|
|
1937
|
+
personality_manager_1.PersonalityManager.setActivePersonality(personalityId);
|
|
1938
|
+
// Get the personality definition for the response
|
|
1939
|
+
const personality = types_1.PERSONALITY_DEFINITIONS.find(p => p.id === personalityId);
|
|
1940
|
+
const description = personality?.description || '';
|
|
1941
|
+
const name = personality?.name || personalityId;
|
|
1942
|
+
console.log(`[ToolRegistry] Personality changed to: ${personalityId}`);
|
|
1943
|
+
return {
|
|
1944
|
+
success: true,
|
|
1945
|
+
personality: personalityId,
|
|
1946
|
+
description,
|
|
1947
|
+
message: `Personality changed to "${name}". ${description}. This will take effect in future responses.`,
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Set the agent's name
|
|
1952
|
+
*/
|
|
1953
|
+
setAgentName(input) {
|
|
1954
|
+
const newName = input.name?.trim();
|
|
1955
|
+
if (!newName || newName.length === 0) {
|
|
1956
|
+
throw new Error('Name cannot be empty');
|
|
1957
|
+
}
|
|
1958
|
+
if (newName.length > 50) {
|
|
1959
|
+
throw new Error('Name is too long (max 50 characters)');
|
|
1960
|
+
}
|
|
1961
|
+
// Save the new name
|
|
1962
|
+
personality_manager_1.PersonalityManager.setAgentName(newName);
|
|
1963
|
+
console.log(`[ToolRegistry] Agent name changed to: ${newName}`);
|
|
1964
|
+
return {
|
|
1965
|
+
success: true,
|
|
1966
|
+
name: newName,
|
|
1967
|
+
message: `Great! From now on, I'll go by "${newName}". Nice to meet you!`,
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Set the agent's persona (character overlay)
|
|
1972
|
+
*/
|
|
1973
|
+
setPersona(input) {
|
|
1974
|
+
const personaId = input.persona;
|
|
1975
|
+
const validIds = ['none', 'jarvis', 'friday', 'hal', 'computer', 'alfred', 'intern', 'sensei', 'pirate', 'noir', 'companion'];
|
|
1976
|
+
if (!validIds.includes(personaId)) {
|
|
1977
|
+
throw new Error(`Invalid persona: ${personaId}. Valid options are: ${validIds.join(', ')}`);
|
|
1978
|
+
}
|
|
1979
|
+
// Save the new persona
|
|
1980
|
+
personality_manager_1.PersonalityManager.setActivePersona(personaId);
|
|
1981
|
+
// Get the persona definition for the response
|
|
1982
|
+
const persona = types_1.PERSONA_DEFINITIONS.find(p => p.id === personaId);
|
|
1983
|
+
const description = persona?.description || '';
|
|
1984
|
+
const name = persona?.name || personaId;
|
|
1985
|
+
console.log(`[ToolRegistry] Persona changed to: ${personaId}`);
|
|
1986
|
+
let message = '';
|
|
1987
|
+
if (personaId === 'none') {
|
|
1988
|
+
message = 'Persona cleared. I\'ll respond without any character overlay.';
|
|
1989
|
+
}
|
|
1990
|
+
else {
|
|
1991
|
+
message = `Persona changed to "${name}". ${description}. This character style will be applied in future responses.`;
|
|
1992
|
+
}
|
|
1993
|
+
return {
|
|
1994
|
+
success: true,
|
|
1995
|
+
persona: personaId,
|
|
1996
|
+
name,
|
|
1997
|
+
description,
|
|
1998
|
+
message,
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
/**
|
|
2002
|
+
* Set the user's name (for relationship tracking)
|
|
2003
|
+
*/
|
|
2004
|
+
setUserName(input) {
|
|
2005
|
+
const userName = input.name?.trim();
|
|
2006
|
+
if (!userName || userName.length === 0) {
|
|
2007
|
+
throw new Error('Name cannot be empty');
|
|
2008
|
+
}
|
|
2009
|
+
if (userName.length > 100) {
|
|
2010
|
+
throw new Error('Name is too long (max 100 characters)');
|
|
2011
|
+
}
|
|
2012
|
+
// Save the user's name
|
|
2013
|
+
personality_manager_1.PersonalityManager.setUserName(userName);
|
|
2014
|
+
console.log(`[ToolRegistry] User name set to: ${userName}`);
|
|
2015
|
+
const agentName = personality_manager_1.PersonalityManager.getAgentName();
|
|
2016
|
+
return {
|
|
2017
|
+
success: true,
|
|
2018
|
+
name: userName,
|
|
2019
|
+
message: `Nice to meet you, ${userName}! I'm ${agentName}. I'll remember your name for our future conversations.`,
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Set response style preferences
|
|
2024
|
+
*/
|
|
2025
|
+
setResponseStyle(input) {
|
|
2026
|
+
const changes = [];
|
|
2027
|
+
const style = {};
|
|
2028
|
+
// Validate and apply emoji usage
|
|
2029
|
+
if (input.emoji_usage) {
|
|
2030
|
+
const validEmoji = ['none', 'minimal', 'moderate', 'expressive'];
|
|
2031
|
+
if (!validEmoji.includes(input.emoji_usage)) {
|
|
2032
|
+
throw new Error(`Invalid emoji_usage: ${input.emoji_usage}. Valid options: ${validEmoji.join(', ')}`);
|
|
2033
|
+
}
|
|
2034
|
+
style.emojiUsage = input.emoji_usage;
|
|
2035
|
+
changes.push(`emoji usage: ${input.emoji_usage}`);
|
|
2036
|
+
}
|
|
2037
|
+
// Validate and apply response length
|
|
2038
|
+
if (input.response_length) {
|
|
2039
|
+
const validLength = ['terse', 'balanced', 'detailed'];
|
|
2040
|
+
if (!validLength.includes(input.response_length)) {
|
|
2041
|
+
throw new Error(`Invalid response_length: ${input.response_length}. Valid options: ${validLength.join(', ')}`);
|
|
2042
|
+
}
|
|
2043
|
+
style.responseLength = input.response_length;
|
|
2044
|
+
changes.push(`response length: ${input.response_length}`);
|
|
2045
|
+
}
|
|
2046
|
+
// Validate and apply code comment style
|
|
2047
|
+
if (input.code_comments) {
|
|
2048
|
+
const validComments = ['minimal', 'moderate', 'verbose'];
|
|
2049
|
+
if (!validComments.includes(input.code_comments)) {
|
|
2050
|
+
throw new Error(`Invalid code_comments: ${input.code_comments}. Valid options: ${validComments.join(', ')}`);
|
|
2051
|
+
}
|
|
2052
|
+
style.codeCommentStyle = input.code_comments;
|
|
2053
|
+
changes.push(`code comments: ${input.code_comments}`);
|
|
2054
|
+
}
|
|
2055
|
+
// Validate and apply explanation depth
|
|
2056
|
+
if (input.explanation_depth) {
|
|
2057
|
+
const validDepth = ['expert', 'balanced', 'teaching'];
|
|
2058
|
+
if (!validDepth.includes(input.explanation_depth)) {
|
|
2059
|
+
throw new Error(`Invalid explanation_depth: ${input.explanation_depth}. Valid options: ${validDepth.join(', ')}`);
|
|
2060
|
+
}
|
|
2061
|
+
style.explanationDepth = input.explanation_depth;
|
|
2062
|
+
changes.push(`explanation depth: ${input.explanation_depth}`);
|
|
2063
|
+
}
|
|
2064
|
+
if (changes.length === 0) {
|
|
2065
|
+
throw new Error('No valid style options provided. Use emoji_usage, response_length, code_comments, or explanation_depth.');
|
|
2066
|
+
}
|
|
2067
|
+
personality_manager_1.PersonalityManager.setResponseStyle(style);
|
|
2068
|
+
console.log(`[ToolRegistry] Response style updated:`, changes);
|
|
2069
|
+
return {
|
|
2070
|
+
success: true,
|
|
2071
|
+
changes,
|
|
2072
|
+
message: `Response style updated: ${changes.join(', ')}. Changes will apply to future responses.`,
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Sanitize user input to prevent prompt injection
|
|
2077
|
+
* Removes control characters and limits potentially harmful patterns
|
|
2078
|
+
*/
|
|
2079
|
+
sanitizeQuirkInput(input) {
|
|
2080
|
+
if (!input)
|
|
2081
|
+
return '';
|
|
2082
|
+
// Remove control characters and null bytes
|
|
2083
|
+
let sanitized = input.replace(/[\x00-\x1F\x7F]/g, '');
|
|
2084
|
+
// Remove patterns that could be used for prompt injection
|
|
2085
|
+
// These patterns try to override system instructions
|
|
2086
|
+
const dangerousPatterns = [
|
|
2087
|
+
/ignore\s+(all\s+)?(previous|above|prior)\s+(instructions?|prompts?)/gi,
|
|
2088
|
+
/disregard\s+(all\s+)?(previous|above|prior)\s+(instructions?|prompts?)/gi,
|
|
2089
|
+
/forget\s+(all\s+)?(previous|above|prior)\s+(instructions?|prompts?)/gi,
|
|
2090
|
+
/new\s+instructions?:/gi,
|
|
2091
|
+
/system\s*:/gi,
|
|
2092
|
+
/\[INST\]/gi,
|
|
2093
|
+
/<<SYS>>/gi,
|
|
2094
|
+
/<\|im_start\|>/gi,
|
|
2095
|
+
/###\s*(instruction|system|human|assistant)/gi,
|
|
2096
|
+
];
|
|
2097
|
+
for (const pattern of dangerousPatterns) {
|
|
2098
|
+
sanitized = sanitized.replace(pattern, '[filtered]');
|
|
2099
|
+
}
|
|
2100
|
+
return sanitized.trim();
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Set personality quirks
|
|
2104
|
+
*/
|
|
2105
|
+
setQuirks(input) {
|
|
2106
|
+
const changes = [];
|
|
2107
|
+
const quirks = {};
|
|
2108
|
+
// Maximum lengths for quirk fields
|
|
2109
|
+
const MAX_CATCHPHRASE_LENGTH = 100;
|
|
2110
|
+
const MAX_SIGNOFF_LENGTH = 150;
|
|
2111
|
+
// Apply catchphrase with validation
|
|
2112
|
+
if (input.catchphrase !== undefined) {
|
|
2113
|
+
if (input.catchphrase && input.catchphrase.length > MAX_CATCHPHRASE_LENGTH) {
|
|
2114
|
+
throw new Error(`Catchphrase too long (max ${MAX_CATCHPHRASE_LENGTH} characters, got ${input.catchphrase.length})`);
|
|
2115
|
+
}
|
|
2116
|
+
const sanitized = this.sanitizeQuirkInput(input.catchphrase || '');
|
|
2117
|
+
quirks.catchphrase = sanitized;
|
|
2118
|
+
if (sanitized) {
|
|
2119
|
+
changes.push(`catchphrase: "${sanitized}"`);
|
|
2120
|
+
}
|
|
2121
|
+
else {
|
|
2122
|
+
changes.push('catchphrase cleared');
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
// Apply sign-off with validation
|
|
2126
|
+
if (input.sign_off !== undefined) {
|
|
2127
|
+
if (input.sign_off && input.sign_off.length > MAX_SIGNOFF_LENGTH) {
|
|
2128
|
+
throw new Error(`Sign-off too long (max ${MAX_SIGNOFF_LENGTH} characters, got ${input.sign_off.length})`);
|
|
2129
|
+
}
|
|
2130
|
+
const sanitized = this.sanitizeQuirkInput(input.sign_off || '');
|
|
2131
|
+
quirks.signOff = sanitized;
|
|
2132
|
+
if (sanitized) {
|
|
2133
|
+
changes.push(`sign-off: "${sanitized}"`);
|
|
2134
|
+
}
|
|
2135
|
+
else {
|
|
2136
|
+
changes.push('sign-off cleared');
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
// Validate and apply analogy domain
|
|
2140
|
+
if (input.analogy_domain !== undefined) {
|
|
2141
|
+
const validDomains = ['none', 'cooking', 'sports', 'space', 'music', 'nature', 'gaming', 'movies', 'construction'];
|
|
2142
|
+
if (!validDomains.includes(input.analogy_domain)) {
|
|
2143
|
+
throw new Error(`Invalid analogy_domain: ${input.analogy_domain}. Valid options: ${validDomains.join(', ')}`);
|
|
2144
|
+
}
|
|
2145
|
+
quirks.analogyDomain = input.analogy_domain;
|
|
2146
|
+
if (input.analogy_domain === 'none') {
|
|
2147
|
+
changes.push('analogy domain cleared');
|
|
2148
|
+
}
|
|
2149
|
+
else {
|
|
2150
|
+
changes.push(`analogy domain: ${input.analogy_domain}`);
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
if (changes.length === 0) {
|
|
2154
|
+
throw new Error('No quirk options provided. Use catchphrase, sign_off, or analogy_domain.');
|
|
2155
|
+
}
|
|
2156
|
+
personality_manager_1.PersonalityManager.setQuirks(quirks);
|
|
2157
|
+
console.log(`[ToolRegistry] Quirks updated:`, changes);
|
|
2158
|
+
return {
|
|
2159
|
+
success: true,
|
|
2160
|
+
changes,
|
|
2161
|
+
message: `Personality quirks updated: ${changes.join(', ')}. Changes will apply to future responses.`,
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
// ============ Sub-Agent / Parallel Agent Methods ============
|
|
2165
|
+
/**
|
|
2166
|
+
* Get the current task's depth (nesting level)
|
|
2167
|
+
*/
|
|
2168
|
+
async getCurrentTaskDepth() {
|
|
2169
|
+
const currentTask = await this.daemon.getTaskById(this.taskId);
|
|
2170
|
+
return currentTask?.depth ?? 0;
|
|
2171
|
+
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Resolve model preference to a specific model key
|
|
2174
|
+
*/
|
|
2175
|
+
resolveModelPreference(preference) {
|
|
2176
|
+
switch (preference) {
|
|
2177
|
+
case 'same':
|
|
2178
|
+
// Use the current global settings - daemon will handle this
|
|
2179
|
+
return '';
|
|
2180
|
+
case 'cheaper':
|
|
2181
|
+
case 'haiku':
|
|
2182
|
+
return 'haiku-4-5';
|
|
2183
|
+
case 'smarter':
|
|
2184
|
+
case 'opus':
|
|
2185
|
+
return 'opus-4-5';
|
|
2186
|
+
case 'sonnet':
|
|
2187
|
+
return 'sonnet-4-5';
|
|
2188
|
+
default:
|
|
2189
|
+
// Default to cheaper model for sub-agents
|
|
2190
|
+
return 'haiku-4-5';
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Resolve personality preference
|
|
2195
|
+
*/
|
|
2196
|
+
resolvePersonality(preference) {
|
|
2197
|
+
if (!preference || preference === 'same') {
|
|
2198
|
+
return undefined; // Inherit from parent
|
|
2199
|
+
}
|
|
2200
|
+
const validPersonalities = ['professional', 'friendly', 'concise', 'creative', 'technical', 'casual'];
|
|
2201
|
+
if (validPersonalities.includes(preference)) {
|
|
2202
|
+
return preference;
|
|
2203
|
+
}
|
|
2204
|
+
return 'concise'; // Default for sub-agents
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Spawn a child agent to work on a subtask
|
|
2208
|
+
*/
|
|
2209
|
+
async spawnAgent(input) {
|
|
2210
|
+
const { prompt, title, model_preference, personality, wait = false, max_turns = 20 } = input;
|
|
2211
|
+
// Validate prompt
|
|
2212
|
+
if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) {
|
|
2213
|
+
throw new Error('spawn_agent requires a non-empty prompt');
|
|
2214
|
+
}
|
|
2215
|
+
// Check depth limit to prevent runaway spawning
|
|
2216
|
+
const currentDepth = await this.getCurrentTaskDepth();
|
|
2217
|
+
const maxDepth = 3;
|
|
2218
|
+
if (currentDepth >= maxDepth) {
|
|
2219
|
+
return {
|
|
2220
|
+
success: false,
|
|
2221
|
+
message: `Cannot spawn agent: maximum nesting depth (${maxDepth}) reached. Consider breaking the task into smaller parts or completing this task first.`,
|
|
2222
|
+
error: 'MAX_DEPTH_REACHED',
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
// Resolve model and personality
|
|
2226
|
+
const modelKey = this.resolveModelPreference(model_preference);
|
|
2227
|
+
const personalityId = this.resolvePersonality(personality);
|
|
2228
|
+
// Build agent config
|
|
2229
|
+
const agentConfig = {
|
|
2230
|
+
maxTurns: max_turns,
|
|
2231
|
+
retainMemory: false, // Sub-agents don't retain memory
|
|
2232
|
+
};
|
|
2233
|
+
if (modelKey) {
|
|
2234
|
+
agentConfig.modelKey = modelKey;
|
|
2235
|
+
}
|
|
2236
|
+
if (personalityId) {
|
|
2237
|
+
agentConfig.personalityId = personalityId;
|
|
2238
|
+
}
|
|
2239
|
+
// Generate title if not provided
|
|
2240
|
+
const taskTitle = title || `Sub-task: ${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}`;
|
|
2241
|
+
// Log spawn attempt
|
|
2242
|
+
this.daemon.logEvent(this.taskId, 'agent_spawned', {
|
|
2243
|
+
childTaskTitle: taskTitle,
|
|
2244
|
+
modelPreference: model_preference,
|
|
2245
|
+
personality: personality,
|
|
2246
|
+
maxTurns: max_turns,
|
|
2247
|
+
parentDepth: currentDepth,
|
|
2248
|
+
});
|
|
2249
|
+
try {
|
|
2250
|
+
// Create the child task via daemon
|
|
2251
|
+
const childTask = await this.daemon.createChildTask({
|
|
2252
|
+
title: taskTitle,
|
|
2253
|
+
prompt: prompt,
|
|
2254
|
+
workspaceId: this.workspace.id,
|
|
2255
|
+
parentTaskId: this.taskId,
|
|
2256
|
+
agentType: 'sub',
|
|
2257
|
+
agentConfig,
|
|
2258
|
+
depth: currentDepth + 1,
|
|
2259
|
+
});
|
|
2260
|
+
console.log(`[ToolRegistry] Spawned child agent: ${childTask.id} (depth: ${currentDepth + 1})`);
|
|
2261
|
+
// If wait=true, wait for completion
|
|
2262
|
+
if (wait) {
|
|
2263
|
+
const result = await this.waitForAgentInternal(childTask.id, 300);
|
|
2264
|
+
return {
|
|
2265
|
+
success: result.success,
|
|
2266
|
+
task_id: childTask.id,
|
|
2267
|
+
title: taskTitle,
|
|
2268
|
+
message: result.message,
|
|
2269
|
+
result: result.resultSummary,
|
|
2270
|
+
error: result.error,
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
return {
|
|
2274
|
+
success: true,
|
|
2275
|
+
task_id: childTask.id,
|
|
2276
|
+
title: taskTitle,
|
|
2277
|
+
message: `Sub-agent spawned successfully. Task ID: ${childTask.id}. Use wait_for_agent or get_agent_status to check progress.`,
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
catch (error) {
|
|
2281
|
+
console.error(`[ToolRegistry] Failed to spawn agent:`, error);
|
|
2282
|
+
this.daemon.logEvent(this.taskId, 'error', {
|
|
2283
|
+
tool: 'spawn_agent',
|
|
2284
|
+
error: error.message,
|
|
2285
|
+
});
|
|
2286
|
+
return {
|
|
2287
|
+
success: false,
|
|
2288
|
+
message: `Failed to spawn agent: ${error.message}`,
|
|
2289
|
+
error: error.message,
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
/**
|
|
2294
|
+
* Internal method to wait for an agent to complete
|
|
2295
|
+
*/
|
|
2296
|
+
async waitForAgentInternal(taskId, timeoutSeconds) {
|
|
2297
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
2298
|
+
const startTime = Date.now();
|
|
2299
|
+
const pollInterval = 1000; // Check every second
|
|
2300
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
2301
|
+
const task = await this.daemon.getTaskById(taskId);
|
|
2302
|
+
if (!task) {
|
|
2303
|
+
return {
|
|
2304
|
+
success: false,
|
|
2305
|
+
status: 'not_found',
|
|
2306
|
+
message: `Task ${taskId} not found`,
|
|
2307
|
+
error: 'TASK_NOT_FOUND',
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
// Check if task is complete
|
|
2311
|
+
if (['completed', 'failed', 'cancelled'].includes(task.status)) {
|
|
2312
|
+
const isSuccess = task.status === 'completed';
|
|
2313
|
+
// Log result event to parent
|
|
2314
|
+
this.daemon.logEvent(this.taskId, isSuccess ? 'agent_completed' : 'agent_failed', {
|
|
2315
|
+
childTaskId: taskId,
|
|
2316
|
+
childStatus: task.status,
|
|
2317
|
+
resultSummary: task.resultSummary,
|
|
2318
|
+
error: task.error,
|
|
2319
|
+
});
|
|
2320
|
+
return {
|
|
2321
|
+
success: isSuccess,
|
|
2322
|
+
status: task.status,
|
|
2323
|
+
message: isSuccess
|
|
2324
|
+
? `Agent completed successfully`
|
|
2325
|
+
: `Agent ${task.status}: ${task.error || 'Unknown error'}`,
|
|
2326
|
+
resultSummary: task.resultSummary,
|
|
2327
|
+
error: task.error,
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
// Wait before polling again
|
|
2331
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
2332
|
+
}
|
|
2333
|
+
// Timeout reached
|
|
2334
|
+
return {
|
|
2335
|
+
success: false,
|
|
2336
|
+
status: 'timeout',
|
|
2337
|
+
message: `Timeout waiting for agent ${taskId} (${timeoutSeconds}s)`,
|
|
2338
|
+
error: 'TIMEOUT',
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
/**
|
|
2342
|
+
* Wait for a spawned agent to complete
|
|
2343
|
+
*/
|
|
2344
|
+
async waitForAgent(input) {
|
|
2345
|
+
const { task_id, timeout_seconds = 300 } = input;
|
|
2346
|
+
if (!task_id) {
|
|
2347
|
+
throw new Error('wait_for_agent requires a task_id');
|
|
2348
|
+
}
|
|
2349
|
+
const result = await this.waitForAgentInternal(task_id, timeout_seconds);
|
|
2350
|
+
return {
|
|
2351
|
+
success: result.success,
|
|
2352
|
+
status: result.status,
|
|
2353
|
+
task_id: task_id,
|
|
2354
|
+
message: result.message,
|
|
2355
|
+
result_summary: result.resultSummary,
|
|
2356
|
+
error: result.error,
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
/**
|
|
2360
|
+
* Get status of spawned agents
|
|
2361
|
+
*/
|
|
2362
|
+
async getAgentStatus(input) {
|
|
2363
|
+
const { task_ids } = input;
|
|
2364
|
+
let tasks;
|
|
2365
|
+
if (task_ids && task_ids.length > 0) {
|
|
2366
|
+
// Get specific tasks
|
|
2367
|
+
tasks = [];
|
|
2368
|
+
for (const id of task_ids) {
|
|
2369
|
+
const task = await this.daemon.getTaskById(id);
|
|
2370
|
+
if (task) {
|
|
2371
|
+
tasks.push(task);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
else {
|
|
2376
|
+
// Get all child tasks of current task
|
|
2377
|
+
tasks = await this.daemon.getChildTasks(this.taskId);
|
|
2378
|
+
}
|
|
2379
|
+
const agents = tasks.map(task => ({
|
|
2380
|
+
task_id: task.id,
|
|
2381
|
+
title: task.title,
|
|
2382
|
+
status: task.status,
|
|
2383
|
+
agent_type: task.agentType || 'main',
|
|
2384
|
+
model_key: task.agentConfig?.modelKey,
|
|
2385
|
+
result_summary: task.resultSummary,
|
|
2386
|
+
error: task.error,
|
|
2387
|
+
created_at: task.createdAt,
|
|
2388
|
+
completed_at: task.completedAt,
|
|
2389
|
+
}));
|
|
2390
|
+
return {
|
|
2391
|
+
agents,
|
|
2392
|
+
message: `Found ${agents.length} agent(s)`,
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
/**
|
|
2396
|
+
* List all spawned child agents for the current task
|
|
2397
|
+
*/
|
|
2398
|
+
async listAgents(input) {
|
|
2399
|
+
const { status_filter = 'all' } = input;
|
|
2400
|
+
// Get all child tasks
|
|
2401
|
+
let tasks = await this.daemon.getChildTasks(this.taskId);
|
|
2402
|
+
// Apply filter
|
|
2403
|
+
if (status_filter !== 'all') {
|
|
2404
|
+
const runningStatuses = ['pending', 'queued', 'planning', 'executing', 'paused'];
|
|
2405
|
+
const completedStatuses = ['completed'];
|
|
2406
|
+
const failedStatuses = ['failed', 'cancelled'];
|
|
2407
|
+
tasks = tasks.filter(task => {
|
|
2408
|
+
switch (status_filter) {
|
|
2409
|
+
case 'running':
|
|
2410
|
+
return runningStatuses.includes(task.status);
|
|
2411
|
+
case 'completed':
|
|
2412
|
+
return completedStatuses.includes(task.status);
|
|
2413
|
+
case 'failed':
|
|
2414
|
+
return failedStatuses.includes(task.status);
|
|
2415
|
+
default:
|
|
2416
|
+
return true;
|
|
2417
|
+
}
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
// Calculate summary from all child tasks (not filtered)
|
|
2421
|
+
const allTasks = await this.daemon.getChildTasks(this.taskId);
|
|
2422
|
+
const summary = {
|
|
2423
|
+
total: allTasks.length,
|
|
2424
|
+
running: allTasks.filter(t => ['pending', 'queued', 'planning', 'executing', 'paused'].includes(t.status)).length,
|
|
2425
|
+
completed: allTasks.filter(t => t.status === 'completed').length,
|
|
2426
|
+
failed: allTasks.filter(t => ['failed', 'cancelled'].includes(t.status)).length,
|
|
2427
|
+
};
|
|
2428
|
+
const agents = tasks.map(task => ({
|
|
2429
|
+
task_id: task.id,
|
|
2430
|
+
title: task.title,
|
|
2431
|
+
status: task.status,
|
|
2432
|
+
agent_type: task.agentType || 'main',
|
|
2433
|
+
model_key: task.agentConfig?.modelKey,
|
|
2434
|
+
depth: task.depth ?? 0,
|
|
2435
|
+
created_at: task.createdAt,
|
|
2436
|
+
}));
|
|
2437
|
+
return {
|
|
2438
|
+
agents,
|
|
2439
|
+
summary,
|
|
2440
|
+
message: status_filter === 'all'
|
|
2441
|
+
? `Found ${agents.length} child agent(s)`
|
|
2442
|
+
: `Found ${agents.length} ${status_filter} agent(s) (${summary.total} total)`,
|
|
2443
|
+
};
|
|
2444
|
+
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Define meta tools for execution control
|
|
2447
|
+
*/
|
|
2448
|
+
getMetaToolDefinitions() {
|
|
2449
|
+
return [
|
|
2450
|
+
{
|
|
2451
|
+
name: 'revise_plan',
|
|
2452
|
+
description: 'Revise the execution plan. Use this when you encounter unexpected obstacles, ' +
|
|
2453
|
+
'discover that the original plan is insufficient, need to stop execution, or find a better approach. ' +
|
|
2454
|
+
'Can add new steps, clear remaining steps, or both.',
|
|
2455
|
+
input_schema: {
|
|
2456
|
+
type: 'object',
|
|
2457
|
+
properties: {
|
|
2458
|
+
reason: {
|
|
2459
|
+
type: 'string',
|
|
2460
|
+
description: 'Brief explanation of why the plan needs to be revised (e.g., "discovered missing dependency", "required path not found - need user input")',
|
|
2461
|
+
},
|
|
2462
|
+
clearRemaining: {
|
|
2463
|
+
type: 'boolean',
|
|
2464
|
+
description: 'Set to true to CLEAR/REMOVE all remaining pending steps. Use when the task cannot proceed (e.g., required files not found). Default is false.',
|
|
2465
|
+
},
|
|
2466
|
+
newSteps: {
|
|
2467
|
+
type: 'array',
|
|
2468
|
+
description: 'Array of new steps to add to the plan. Can be empty [] when clearing remaining steps.',
|
|
2469
|
+
items: {
|
|
2470
|
+
type: 'object',
|
|
2471
|
+
properties: {
|
|
2472
|
+
description: {
|
|
2473
|
+
type: 'string',
|
|
2474
|
+
description: 'Description of what this step should accomplish',
|
|
2475
|
+
},
|
|
2476
|
+
},
|
|
2477
|
+
required: ['description'],
|
|
2478
|
+
},
|
|
2479
|
+
},
|
|
2480
|
+
},
|
|
2481
|
+
required: ['reason'],
|
|
2482
|
+
},
|
|
2483
|
+
},
|
|
2484
|
+
{
|
|
2485
|
+
name: 'switch_workspace',
|
|
2486
|
+
description: 'Switch to a different workspace/working directory. Use this when you need to work in a different folder ' +
|
|
2487
|
+
'than the current workspace. You can specify either a path to the folder or a workspace ID. ' +
|
|
2488
|
+
'If the path doesn\'t have an existing workspace, a new one will be created.',
|
|
2489
|
+
input_schema: {
|
|
2490
|
+
type: 'object',
|
|
2491
|
+
properties: {
|
|
2492
|
+
path: {
|
|
2493
|
+
type: 'string',
|
|
2494
|
+
description: 'Absolute path to the folder to switch to (e.g., "/Users/user/projects/myapp")',
|
|
2495
|
+
},
|
|
2496
|
+
workspace_id: {
|
|
2497
|
+
type: 'string',
|
|
2498
|
+
description: 'ID of an existing workspace to switch to',
|
|
2499
|
+
},
|
|
2500
|
+
},
|
|
2501
|
+
},
|
|
2502
|
+
},
|
|
2503
|
+
{
|
|
2504
|
+
name: 'set_personality',
|
|
2505
|
+
description: 'Change the assistant\'s communication style and personality. Use this when the user asks you to be more friendly, ' +
|
|
2506
|
+
'professional, concise, creative, technical, or casual. Available personalities: professional (formal, business-oriented), ' +
|
|
2507
|
+
'friendly (warm, encouraging), concise (brief, to-the-point), creative (imaginative, expressive), ' +
|
|
2508
|
+
'technical (detailed, precise), casual (relaxed, informal). The change takes effect for all future interactions.',
|
|
2509
|
+
input_schema: {
|
|
2510
|
+
type: 'object',
|
|
2511
|
+
properties: {
|
|
2512
|
+
personality: {
|
|
2513
|
+
type: 'string',
|
|
2514
|
+
enum: ['professional', 'friendly', 'concise', 'creative', 'technical', 'casual'],
|
|
2515
|
+
description: 'The personality to switch to',
|
|
2516
|
+
},
|
|
2517
|
+
},
|
|
2518
|
+
required: ['personality'],
|
|
2519
|
+
},
|
|
2520
|
+
},
|
|
2521
|
+
{
|
|
2522
|
+
name: 'set_persona',
|
|
2523
|
+
description: 'Change the assistant\'s character persona. Personas are character overlays inspired by famous AI assistants. ' +
|
|
2524
|
+
'Use this when the user asks to change persona, act like a character, or wants a specific AI personality. ' +
|
|
2525
|
+
'Available personas: jarvis (sophisticated butler), friday (friendly colleague), hal (calm/formal), ' +
|
|
2526
|
+
'computer (Star Trek efficient), alfred (refined gentleman), intern (eager learner), sensei (wise teacher), ' +
|
|
2527
|
+
'pirate (swashbuckling adventurer), noir (1940s detective), companion (warm, thoughtful presence). ' +
|
|
2528
|
+
'Use "none" to remove persona overlay.',
|
|
2529
|
+
input_schema: {
|
|
2530
|
+
type: 'object',
|
|
2531
|
+
properties: {
|
|
2532
|
+
persona: {
|
|
2533
|
+
type: 'string',
|
|
2534
|
+
enum: ['none', 'jarvis', 'friday', 'hal', 'computer', 'alfred', 'intern', 'sensei', 'pirate', 'noir', 'companion'],
|
|
2535
|
+
description: 'The persona to adopt (or "none" to clear)',
|
|
2536
|
+
},
|
|
2537
|
+
},
|
|
2538
|
+
required: ['persona'],
|
|
2539
|
+
},
|
|
2540
|
+
},
|
|
2541
|
+
{
|
|
2542
|
+
name: 'set_agent_name',
|
|
2543
|
+
description: 'Set or change the assistant\'s name. Use this when the user wants to give you a name, rename you, or asks ' +
|
|
2544
|
+
'"what should I call you?" The name will be remembered and used in all future interactions. ' +
|
|
2545
|
+
'Default name is "CoWork" if not customized.',
|
|
2546
|
+
input_schema: {
|
|
2547
|
+
type: 'object',
|
|
2548
|
+
properties: {
|
|
2549
|
+
name: {
|
|
2550
|
+
type: 'string',
|
|
2551
|
+
description: 'The new name for the assistant (e.g., "Jarvis", "Friday", "Max")',
|
|
2552
|
+
},
|
|
2553
|
+
},
|
|
2554
|
+
required: ['name'],
|
|
2555
|
+
},
|
|
2556
|
+
},
|
|
2557
|
+
{
|
|
2558
|
+
name: 'set_user_name',
|
|
2559
|
+
description: 'Store the user\'s name when they introduce themselves. Use this PROACTIVELY when the user tells you their name ' +
|
|
2560
|
+
'(e.g., "I\'m Alice", "My name is Bob", "Call me Charlie"). This helps personalize future interactions. ' +
|
|
2561
|
+
'The name will be remembered across sessions and used in greetings and context.',
|
|
2562
|
+
input_schema: {
|
|
2563
|
+
type: 'object',
|
|
2564
|
+
properties: {
|
|
2565
|
+
name: {
|
|
2566
|
+
type: 'string',
|
|
2567
|
+
description: 'The user\'s name as they introduced themselves',
|
|
2568
|
+
},
|
|
2569
|
+
},
|
|
2570
|
+
required: ['name'],
|
|
2571
|
+
},
|
|
2572
|
+
},
|
|
2573
|
+
{
|
|
2574
|
+
name: 'set_response_style',
|
|
2575
|
+
description: 'Adjust how the assistant responds. Use when the user asks for different response styles like "use more emojis", ' +
|
|
2576
|
+
'"be more brief", "explain things simply", or "add more code comments". All parameters are optional - only set what the user wants to change.',
|
|
2577
|
+
input_schema: {
|
|
2578
|
+
type: 'object',
|
|
2579
|
+
properties: {
|
|
2580
|
+
emoji_usage: {
|
|
2581
|
+
type: 'string',
|
|
2582
|
+
enum: ['none', 'minimal', 'moderate', 'expressive'],
|
|
2583
|
+
description: 'How much to use emojis: none (never), minimal (rarely), moderate (sometimes), expressive (frequently)',
|
|
2584
|
+
},
|
|
2585
|
+
response_length: {
|
|
2586
|
+
type: 'string',
|
|
2587
|
+
enum: ['terse', 'balanced', 'detailed'],
|
|
2588
|
+
description: 'Response verbosity: terse (very brief), balanced (normal), detailed (comprehensive)',
|
|
2589
|
+
},
|
|
2590
|
+
code_comments: {
|
|
2591
|
+
type: 'string',
|
|
2592
|
+
enum: ['minimal', 'moderate', 'verbose'],
|
|
2593
|
+
description: 'Code commenting style: minimal (essential only), moderate (helpful comments), verbose (detailed explanations)',
|
|
2594
|
+
},
|
|
2595
|
+
explanation_depth: {
|
|
2596
|
+
type: 'string',
|
|
2597
|
+
enum: ['expert', 'balanced', 'teaching'],
|
|
2598
|
+
description: 'How deeply to explain: expert (assume knowledge), balanced (normal), teaching (thorough explanations)',
|
|
2599
|
+
},
|
|
2600
|
+
},
|
|
2601
|
+
},
|
|
2602
|
+
},
|
|
2603
|
+
{
|
|
2604
|
+
name: 'set_quirks',
|
|
2605
|
+
description: 'Set personality quirks like catchphrases, sign-offs, or analogy themes. Use when the user wants the assistant ' +
|
|
2606
|
+
'to have a signature phrase, end responses a certain way, or use analogies from a specific domain. ' +
|
|
2607
|
+
'Pass empty string to clear a quirk.',
|
|
2608
|
+
input_schema: {
|
|
2609
|
+
type: 'object',
|
|
2610
|
+
properties: {
|
|
2611
|
+
catchphrase: {
|
|
2612
|
+
type: 'string',
|
|
2613
|
+
description: 'A signature phrase to occasionally use (e.g., "At your service!", "Consider it done!")',
|
|
2614
|
+
},
|
|
2615
|
+
sign_off: {
|
|
2616
|
+
type: 'string',
|
|
2617
|
+
description: 'How to end longer responses (e.g., "Happy coding!", "May the force be with you!")',
|
|
2618
|
+
},
|
|
2619
|
+
analogy_domain: {
|
|
2620
|
+
type: 'string',
|
|
2621
|
+
enum: ['none', 'cooking', 'sports', 'space', 'music', 'nature', 'gaming', 'movies', 'construction'],
|
|
2622
|
+
description: 'Theme for analogies and examples: none (no preference), or a specific domain',
|
|
2623
|
+
},
|
|
2624
|
+
},
|
|
2625
|
+
},
|
|
2626
|
+
},
|
|
2627
|
+
// Sub-Agent / Parallel Agent tools
|
|
2628
|
+
{
|
|
2629
|
+
name: 'spawn_agent',
|
|
2630
|
+
description: 'Spawn a new agent (sub-task) to work on a specific task independently. Use this to delegate work, ' +
|
|
2631
|
+
'perform parallel operations, or use a cheaper/faster model for batch work. Sub-agents do not retain ' +
|
|
2632
|
+
'memory after completion. Returns immediately with the spawned task ID - use wait_for_agent or ' +
|
|
2633
|
+
'get_agent_status to check progress. Maximum nesting depth is 3 levels.',
|
|
2634
|
+
input_schema: {
|
|
2635
|
+
type: 'object',
|
|
2636
|
+
properties: {
|
|
2637
|
+
prompt: {
|
|
2638
|
+
type: 'string',
|
|
2639
|
+
description: 'The task/instruction for the spawned agent. Be specific and include all context needed.',
|
|
2640
|
+
},
|
|
2641
|
+
title: {
|
|
2642
|
+
type: 'string',
|
|
2643
|
+
description: 'A short title for the subtask (optional, derived from prompt if not provided)',
|
|
2644
|
+
},
|
|
2645
|
+
model_preference: {
|
|
2646
|
+
type: 'string',
|
|
2647
|
+
enum: ['same', 'cheaper', 'smarter'],
|
|
2648
|
+
description: 'Model selection: "same" uses parent model, "cheaper" selects Haiku (fast/cheap), "smarter" selects Opus (most capable). Default: "cheaper" for cost optimization.',
|
|
2649
|
+
},
|
|
2650
|
+
personality: {
|
|
2651
|
+
type: 'string',
|
|
2652
|
+
enum: ['same', 'professional', 'technical', 'concise', 'creative', 'friendly'],
|
|
2653
|
+
description: 'Personality for the spawned agent. "same" inherits from parent. Default: "concise"',
|
|
2654
|
+
},
|
|
2655
|
+
wait: {
|
|
2656
|
+
type: 'boolean',
|
|
2657
|
+
description: 'If true, wait for the agent to complete before returning (blocking). Default: false (async)',
|
|
2658
|
+
},
|
|
2659
|
+
max_turns: {
|
|
2660
|
+
type: 'number',
|
|
2661
|
+
description: 'Maximum number of LLM turns for the sub-agent. Default: 20',
|
|
2662
|
+
},
|
|
2663
|
+
},
|
|
2664
|
+
required: ['prompt'],
|
|
2665
|
+
},
|
|
2666
|
+
},
|
|
2667
|
+
{
|
|
2668
|
+
name: 'wait_for_agent',
|
|
2669
|
+
description: 'Wait for a spawned agent to complete and retrieve its results. Returns the agent\'s final status, ' +
|
|
2670
|
+
'result summary, and any error information. Use this to synchronize with sub-agents when you need ' +
|
|
2671
|
+
'their results before proceeding.',
|
|
2672
|
+
input_schema: {
|
|
2673
|
+
type: 'object',
|
|
2674
|
+
properties: {
|
|
2675
|
+
task_id: {
|
|
2676
|
+
type: 'string',
|
|
2677
|
+
description: 'The task ID of the spawned agent (returned by spawn_agent)',
|
|
2678
|
+
},
|
|
2679
|
+
timeout_seconds: {
|
|
2680
|
+
type: 'number',
|
|
2681
|
+
description: 'Maximum time to wait in seconds. Default: 300 (5 minutes)',
|
|
2682
|
+
},
|
|
2683
|
+
},
|
|
2684
|
+
required: ['task_id'],
|
|
2685
|
+
},
|
|
2686
|
+
},
|
|
2687
|
+
{
|
|
2688
|
+
name: 'get_agent_status',
|
|
2689
|
+
description: 'Check the status of spawned agents. Returns current status, progress, and any results. ' +
|
|
2690
|
+
'Use this for non-blocking status checks.',
|
|
2691
|
+
input_schema: {
|
|
2692
|
+
type: 'object',
|
|
2693
|
+
properties: {
|
|
2694
|
+
task_ids: {
|
|
2695
|
+
type: 'array',
|
|
2696
|
+
items: { type: 'string' },
|
|
2697
|
+
description: 'Array of task IDs to check. If empty or omitted, returns status of all child agents.',
|
|
2698
|
+
},
|
|
2699
|
+
},
|
|
2700
|
+
},
|
|
2701
|
+
},
|
|
2702
|
+
{
|
|
2703
|
+
name: 'list_agents',
|
|
2704
|
+
description: 'List all spawned child agents for the current task. Shows their status, model, title, and progress.',
|
|
2705
|
+
input_schema: {
|
|
2706
|
+
type: 'object',
|
|
2707
|
+
properties: {
|
|
2708
|
+
status_filter: {
|
|
2709
|
+
type: 'string',
|
|
2710
|
+
enum: ['all', 'running', 'completed', 'failed'],
|
|
2711
|
+
description: 'Filter agents by status. Default: "all"',
|
|
2712
|
+
},
|
|
2713
|
+
},
|
|
2714
|
+
},
|
|
2715
|
+
},
|
|
2716
|
+
];
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
exports.ToolRegistry = ToolRegistry;
|