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,651 @@
|
|
|
1
|
+
import { spawn, ChildProcess, execSync } from 'child_process';
|
|
2
|
+
import { Workspace, CommandTerminationReason } from '../../../shared/types';
|
|
3
|
+
import { AgentDaemon } from '../daemon';
|
|
4
|
+
import { GuardrailManager } from '../../guardrails/guardrail-manager';
|
|
5
|
+
|
|
6
|
+
// Limits to prevent runaway commands
|
|
7
|
+
const MAX_TIMEOUT = 5 * 60 * 1000; // 5 minutes max
|
|
8
|
+
const DEFAULT_TIMEOUT = 60 * 1000; // 1 minute default
|
|
9
|
+
const MAX_OUTPUT_SIZE = 100 * 1024; // 100KB max output
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate that a PID is a safe positive integer
|
|
13
|
+
* Prevents command injection if PID is somehow not a number
|
|
14
|
+
*/
|
|
15
|
+
function isValidPid(pid: unknown): pid is number {
|
|
16
|
+
return typeof pid === 'number' &&
|
|
17
|
+
Number.isInteger(pid) &&
|
|
18
|
+
pid > 0 &&
|
|
19
|
+
pid <= 4194304; // Max PID on Linux (can be configured higher, but this is safe default)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if a process with the given PID exists and is owned by the current user
|
|
24
|
+
* Returns false if the process doesn't exist or is owned by another user
|
|
25
|
+
*/
|
|
26
|
+
function isProcessOwnedByCurrentUser(pid: number): boolean {
|
|
27
|
+
if (!isValidPid(pid)) return false;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Use kill with signal 0 to check if process exists and we have permission to signal it
|
|
31
|
+
// This will throw EPERM if process exists but is owned by another user
|
|
32
|
+
// This will throw ESRCH if process doesn't exist
|
|
33
|
+
process.kill(pid, 0);
|
|
34
|
+
return true;
|
|
35
|
+
} catch (error: any) {
|
|
36
|
+
// ESRCH = no such process (that's fine, process exited)
|
|
37
|
+
// EPERM = permission denied (process exists but owned by another user - DON'T KILL)
|
|
38
|
+
if (error.code === 'EPERM') {
|
|
39
|
+
console.warn(`[ShellTools] Process ${pid} exists but is owned by another user, skipping`);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// Process doesn't exist, that's fine
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validate username for safe use in shell commands
|
|
49
|
+
* Prevents command injection via USER environment variable
|
|
50
|
+
*/
|
|
51
|
+
function isValidUsername(username: string | undefined): username is string {
|
|
52
|
+
if (!username) return false;
|
|
53
|
+
// Username must be alphanumeric, underscore, or dash (standard POSIX username chars)
|
|
54
|
+
// Max length 32 chars (common limit)
|
|
55
|
+
return /^[a-zA-Z0-9_-]{1,32}$/.test(username);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get all descendant process IDs for a given parent PID
|
|
60
|
+
* Uses pgrep to find child processes recursively
|
|
61
|
+
* Only returns processes owned by the current user for security
|
|
62
|
+
*/
|
|
63
|
+
function getDescendantPids(parentPid: number): number[] {
|
|
64
|
+
if (!isValidPid(parentPid)) {
|
|
65
|
+
console.error(`[ShellTools] Invalid parent PID: ${parentPid}`);
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const currentUser = process.env.USER;
|
|
70
|
+
// Validate username to prevent command injection
|
|
71
|
+
const safeUser = isValidUsername(currentUser) ? currentUser : undefined;
|
|
72
|
+
if (currentUser && !safeUser) {
|
|
73
|
+
console.warn(`[ShellTools] Invalid USER env var: ${currentUser}, skipping user filter`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const descendants: number[] = [];
|
|
77
|
+
const toProcess: number[] = [parentPid];
|
|
78
|
+
const seen = new Set<number>(); // Prevent infinite loops from circular references
|
|
79
|
+
|
|
80
|
+
while (toProcess.length > 0) {
|
|
81
|
+
const pid = toProcess.pop()!;
|
|
82
|
+
if (seen.has(pid)) continue;
|
|
83
|
+
seen.add(pid);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// pgrep -P finds direct children of the given PID
|
|
87
|
+
// Add -U $USER to only find processes owned by current user (security)
|
|
88
|
+
const pgrepCmd = safeUser
|
|
89
|
+
? `pgrep -P ${pid} -U ${safeUser}`
|
|
90
|
+
: `pgrep -P ${pid}`;
|
|
91
|
+
|
|
92
|
+
const output = execSync(pgrepCmd, {
|
|
93
|
+
encoding: 'utf-8',
|
|
94
|
+
timeout: 1000,
|
|
95
|
+
// Don't inherit env to avoid any injection via environment
|
|
96
|
+
env: { PATH: '/usr/bin:/bin' },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const childPids = output.trim().split('\n')
|
|
100
|
+
.filter(line => line.length > 0)
|
|
101
|
+
.map(line => parseInt(line, 10))
|
|
102
|
+
.filter(childPid => isValidPid(childPid) && !seen.has(childPid));
|
|
103
|
+
|
|
104
|
+
descendants.push(...childPids);
|
|
105
|
+
toProcess.push(...childPids);
|
|
106
|
+
} catch {
|
|
107
|
+
// pgrep returns non-zero if no children found, which is fine
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return descendants;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Kill a process and all its descendants
|
|
116
|
+
* Sends the signal to children first, then to the parent (bottom-up killing)
|
|
117
|
+
* Only kills processes owned by the current user for security
|
|
118
|
+
*/
|
|
119
|
+
function killProcessTree(pid: number, signal: NodeJS.Signals): void {
|
|
120
|
+
if (!isValidPid(pid)) {
|
|
121
|
+
console.error(`[ShellTools] Refusing to kill invalid PID: ${pid}`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const descendants = getDescendantPids(pid);
|
|
126
|
+
|
|
127
|
+
// Kill descendants first (in reverse order, deepest children first)
|
|
128
|
+
for (const descendantPid of descendants.reverse()) {
|
|
129
|
+
// Double-check ownership before killing each process
|
|
130
|
+
if (isProcessOwnedByCurrentUser(descendantPid)) {
|
|
131
|
+
try {
|
|
132
|
+
process.kill(descendantPid, signal);
|
|
133
|
+
} catch {
|
|
134
|
+
// Process may have already exited
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Kill the parent process (also verify ownership)
|
|
140
|
+
if (isProcessOwnedByCurrentUser(pid)) {
|
|
141
|
+
try {
|
|
142
|
+
process.kill(pid, signal);
|
|
143
|
+
} catch {
|
|
144
|
+
// Process may have already exited
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* ShellTools implements shell command execution with user approval
|
|
151
|
+
*/
|
|
152
|
+
export class ShellTools {
|
|
153
|
+
private readonly recentApprovals = new Map<string, { approvedAt: number; count: number }>();
|
|
154
|
+
private readonly approvalWindowMs = 2 * 60 * 1000;
|
|
155
|
+
// Track the currently running child process for stdin support
|
|
156
|
+
private activeProcess: ChildProcess | null = null;
|
|
157
|
+
// Track escalation timeouts so we can cancel them when process exits
|
|
158
|
+
private escalationTimeouts: ReturnType<typeof setTimeout>[] = [];
|
|
159
|
+
// Prevent multiple concurrent kill attempts
|
|
160
|
+
private killInProgress = false;
|
|
161
|
+
// Unique identifier for the current process session (prevents PID reuse issues)
|
|
162
|
+
private processSessionId = 0;
|
|
163
|
+
// Track user-initiated kills to signal termination reason to agent
|
|
164
|
+
private userKillRequested = false;
|
|
165
|
+
|
|
166
|
+
constructor(
|
|
167
|
+
private workspace: Workspace,
|
|
168
|
+
private daemon: AgentDaemon,
|
|
169
|
+
private taskId: string
|
|
170
|
+
) {}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Update the workspace for this tool
|
|
174
|
+
*/
|
|
175
|
+
setWorkspace(workspace: Workspace): void {
|
|
176
|
+
this.workspace = workspace;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Clear all pending escalation timeouts
|
|
181
|
+
* Called when process exits to prevent killing reused PIDs
|
|
182
|
+
*/
|
|
183
|
+
private clearEscalationTimeouts(): void {
|
|
184
|
+
for (const timeout of this.escalationTimeouts) {
|
|
185
|
+
clearTimeout(timeout);
|
|
186
|
+
}
|
|
187
|
+
this.escalationTimeouts = [];
|
|
188
|
+
this.killInProgress = false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Send input to the currently running command's stdin
|
|
193
|
+
*/
|
|
194
|
+
sendStdin(input: string): boolean {
|
|
195
|
+
if (!this.activeProcess || !this.activeProcess.stdin || this.activeProcess.killed) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
this.activeProcess.stdin.write(input);
|
|
200
|
+
// Echo the input to show it was sent
|
|
201
|
+
this.daemon.logEvent(this.taskId, 'command_output', {
|
|
202
|
+
type: 'stdin',
|
|
203
|
+
output: input,
|
|
204
|
+
});
|
|
205
|
+
return true;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error('Failed to write to stdin:', error);
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Check if a command is currently running
|
|
214
|
+
*/
|
|
215
|
+
hasActiveProcess(): boolean {
|
|
216
|
+
return this.activeProcess !== null && !this.activeProcess.killed;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Kill the currently running command and all its child processes
|
|
221
|
+
* @param force - If true, send SIGKILL immediately. Otherwise, try SIGINT first, then SIGTERM, then SIGKILL.
|
|
222
|
+
*/
|
|
223
|
+
killProcess(force: boolean = false): boolean {
|
|
224
|
+
if (!this.activeProcess || this.activeProcess.killed) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const pid = this.activeProcess.pid;
|
|
229
|
+
if (!isValidPid(pid)) {
|
|
230
|
+
console.error(`[ShellTools] Invalid PID for kill: ${pid}`);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Prevent multiple concurrent kill chains (security: avoid race conditions)
|
|
235
|
+
if (this.killInProgress && !force) {
|
|
236
|
+
console.log(`[ShellTools] Kill already in progress, ignoring duplicate request`);
|
|
237
|
+
return true; // Return true since a kill is already underway
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Capture session ID to verify we're killing the right process in escalation timeouts
|
|
241
|
+
const currentSessionId = this.processSessionId;
|
|
242
|
+
|
|
243
|
+
// Mark this as a user-initiated kill so the close handler can signal the agent
|
|
244
|
+
this.userKillRequested = true;
|
|
245
|
+
|
|
246
|
+
if (force) {
|
|
247
|
+
// Force kill - immediate SIGKILL to entire process tree
|
|
248
|
+
// Clear any pending escalation timeouts first
|
|
249
|
+
this.clearEscalationTimeouts();
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
killProcessTree(pid, 'SIGKILL');
|
|
253
|
+
this.daemon.logEvent(this.taskId, 'command_output', {
|
|
254
|
+
type: 'error',
|
|
255
|
+
output: '\n[Process tree force killed by user]\n',
|
|
256
|
+
});
|
|
257
|
+
return true;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error('Failed to force kill process tree:', error);
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Mark kill as in progress to prevent duplicate escalation chains
|
|
265
|
+
this.killInProgress = true;
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
// Send SIGINT (Ctrl+C) to gracefully interrupt the process tree
|
|
269
|
+
killProcessTree(pid, 'SIGINT');
|
|
270
|
+
this.daemon.logEvent(this.taskId, 'command_output', {
|
|
271
|
+
type: 'error',
|
|
272
|
+
output: '\n^C [Process tree interrupted by user]\n',
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Set up escalation: if still running after 2s, send SIGTERM to tree
|
|
276
|
+
// If still running after 4s, send SIGKILL to tree
|
|
277
|
+
// These timeouts are tracked so they can be cancelled if process exits
|
|
278
|
+
const childProcess = this.activeProcess;
|
|
279
|
+
|
|
280
|
+
const sigtermTimeout = setTimeout(() => {
|
|
281
|
+
// Verify this is still the same process session (prevents PID reuse attacks)
|
|
282
|
+
if (currentSessionId !== this.processSessionId) {
|
|
283
|
+
console.log(`[ShellTools] Session ID mismatch, skipping SIGTERM escalation`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (childProcess && !childProcess.killed && childProcess.pid === pid) {
|
|
287
|
+
// Additional safety: verify we own this process before killing
|
|
288
|
+
if (!isProcessOwnedByCurrentUser(pid)) {
|
|
289
|
+
console.warn(`[ShellTools] Process ${pid} no longer owned by current user, skipping SIGTERM`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
killProcessTree(pid, 'SIGTERM');
|
|
294
|
+
this.daemon.logEvent(this.taskId, 'command_output', {
|
|
295
|
+
type: 'error',
|
|
296
|
+
output: '[Escalating to SIGTERM for process tree...]\n',
|
|
297
|
+
});
|
|
298
|
+
} catch { /* Process may have exited */ }
|
|
299
|
+
}
|
|
300
|
+
}, 2000);
|
|
301
|
+
this.escalationTimeouts.push(sigtermTimeout);
|
|
302
|
+
|
|
303
|
+
const sigkillTimeout = setTimeout(() => {
|
|
304
|
+
// Verify this is still the same process session (prevents PID reuse attacks)
|
|
305
|
+
if (currentSessionId !== this.processSessionId) {
|
|
306
|
+
console.log(`[ShellTools] Session ID mismatch, skipping SIGKILL escalation`);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (childProcess && !childProcess.killed && childProcess.pid === pid) {
|
|
310
|
+
// Additional safety: verify we own this process before killing
|
|
311
|
+
if (!isProcessOwnedByCurrentUser(pid)) {
|
|
312
|
+
console.warn(`[ShellTools] Process ${pid} no longer owned by current user, skipping SIGKILL`);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
killProcessTree(pid, 'SIGKILL');
|
|
317
|
+
this.daemon.logEvent(this.taskId, 'command_output', {
|
|
318
|
+
type: 'error',
|
|
319
|
+
output: '[Escalating to SIGKILL for process tree...]\n',
|
|
320
|
+
});
|
|
321
|
+
} catch { /* Process may have exited */ }
|
|
322
|
+
}
|
|
323
|
+
}, 4000);
|
|
324
|
+
this.escalationTimeouts.push(sigkillTimeout);
|
|
325
|
+
|
|
326
|
+
return true;
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.error('Failed to kill process tree:', error);
|
|
329
|
+
this.killInProgress = false;
|
|
330
|
+
|
|
331
|
+
// Try SIGTERM as fallback
|
|
332
|
+
try {
|
|
333
|
+
killProcessTree(pid, 'SIGTERM');
|
|
334
|
+
return true;
|
|
335
|
+
} catch {
|
|
336
|
+
// Last resort: SIGKILL
|
|
337
|
+
try {
|
|
338
|
+
killProcessTree(pid, 'SIGKILL');
|
|
339
|
+
return true;
|
|
340
|
+
} catch {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Execute a shell command (requires user approval)
|
|
349
|
+
* Note: We don't check workspace.permissions.shell here because
|
|
350
|
+
* shell commands always require explicit user approval via requestApproval()
|
|
351
|
+
*/
|
|
352
|
+
async runCommand(
|
|
353
|
+
command: string,
|
|
354
|
+
options?: {
|
|
355
|
+
cwd?: string;
|
|
356
|
+
timeout?: number;
|
|
357
|
+
env?: Record<string, string>;
|
|
358
|
+
}
|
|
359
|
+
): Promise<{
|
|
360
|
+
success: boolean;
|
|
361
|
+
stdout: string;
|
|
362
|
+
stderr: string;
|
|
363
|
+
exitCode: number | null;
|
|
364
|
+
truncated?: boolean;
|
|
365
|
+
terminationReason?: CommandTerminationReason;
|
|
366
|
+
}> {
|
|
367
|
+
// Check if command is blocked by guardrails BEFORE anything else
|
|
368
|
+
const blockCheck = GuardrailManager.isCommandBlocked(command);
|
|
369
|
+
if (blockCheck.blocked) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
`Command blocked by guardrails: "${command}"\n` +
|
|
372
|
+
`Matched pattern: ${blockCheck.pattern}\n` +
|
|
373
|
+
`This command has been blocked for safety. You can modify blocked patterns in Settings > Guardrails.`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Check if command is trusted (auto-approve without user confirmation)
|
|
378
|
+
const trustCheck = GuardrailManager.isCommandTrusted(command);
|
|
379
|
+
let approved = false;
|
|
380
|
+
|
|
381
|
+
if (trustCheck.trusted) {
|
|
382
|
+
// Auto-approve trusted commands
|
|
383
|
+
approved = true;
|
|
384
|
+
this.daemon.logEvent(this.taskId, 'log', {
|
|
385
|
+
message: `Auto-approved trusted command (matched: ${trustCheck.pattern})`,
|
|
386
|
+
command,
|
|
387
|
+
});
|
|
388
|
+
} else {
|
|
389
|
+
const signature = this.getCommandSignature(command);
|
|
390
|
+
const previousApproval = signature ? this.recentApprovals.get(signature) : undefined;
|
|
391
|
+
const now = Date.now();
|
|
392
|
+
|
|
393
|
+
if (
|
|
394
|
+
signature &&
|
|
395
|
+
previousApproval &&
|
|
396
|
+
now - previousApproval.approvedAt <= this.approvalWindowMs &&
|
|
397
|
+
this.isAutoApprovalSafe(command)
|
|
398
|
+
) {
|
|
399
|
+
approved = true;
|
|
400
|
+
previousApproval.count += 1;
|
|
401
|
+
previousApproval.approvedAt = now;
|
|
402
|
+
this.recentApprovals.set(signature, previousApproval);
|
|
403
|
+
this.daemon.logEvent(this.taskId, 'log', {
|
|
404
|
+
message: `Auto-approved similar command (approved ${previousApproval.count}x in last ${Math.round(this.approvalWindowMs / 1000)}s)`,
|
|
405
|
+
command,
|
|
406
|
+
});
|
|
407
|
+
} else {
|
|
408
|
+
// Request user approval before executing
|
|
409
|
+
approved = await this.daemon.requestApproval(
|
|
410
|
+
this.taskId,
|
|
411
|
+
'run_command',
|
|
412
|
+
`Run command: ${command}`,
|
|
413
|
+
{
|
|
414
|
+
command,
|
|
415
|
+
cwd: options?.cwd || this.workspace.path,
|
|
416
|
+
timeout: options?.timeout || DEFAULT_TIMEOUT,
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
if (approved && signature) {
|
|
421
|
+
this.recentApprovals.set(signature, { approvedAt: now, count: 1 });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (!approved) {
|
|
427
|
+
throw new Error('User denied command execution');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Log the command execution attempt
|
|
431
|
+
this.daemon.logEvent(this.taskId, 'tool_call', {
|
|
432
|
+
tool: 'run_command',
|
|
433
|
+
command,
|
|
434
|
+
cwd: options?.cwd || this.workspace.path,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const timeout = Math.min(options?.timeout || DEFAULT_TIMEOUT, MAX_TIMEOUT);
|
|
438
|
+
|
|
439
|
+
// Create a minimal, safe environment (don't leak sensitive process.env vars like API keys)
|
|
440
|
+
const safeEnv: Record<string, string> = {
|
|
441
|
+
// Essential system variables only
|
|
442
|
+
PATH: process.env.PATH || '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin',
|
|
443
|
+
HOME: process.env.HOME || '',
|
|
444
|
+
USER: process.env.USER || '',
|
|
445
|
+
SHELL: process.env.SHELL || '/bin/bash',
|
|
446
|
+
LANG: process.env.LANG || 'en_US.UTF-8',
|
|
447
|
+
TERM: process.env.TERM || 'xterm-256color',
|
|
448
|
+
TMPDIR: process.env.TMPDIR || '/tmp',
|
|
449
|
+
// Add any user-provided env vars (explicitly passed by caller)
|
|
450
|
+
...options?.env,
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const cwd = options?.cwd || this.workspace.path;
|
|
454
|
+
|
|
455
|
+
// Emit the command being executed
|
|
456
|
+
this.daemon.logEvent(this.taskId, 'command_output', {
|
|
457
|
+
command,
|
|
458
|
+
cwd,
|
|
459
|
+
type: 'start',
|
|
460
|
+
output: `$ ${command}\n`,
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
return new Promise((resolve) => {
|
|
464
|
+
let stdout = '';
|
|
465
|
+
let stderr = '';
|
|
466
|
+
let killed = false;
|
|
467
|
+
|
|
468
|
+
// Increment session ID to invalidate any pending escalation timeouts from previous commands
|
|
469
|
+
this.processSessionId++;
|
|
470
|
+
// Clear any leftover escalation timeouts from previous commands
|
|
471
|
+
this.clearEscalationTimeouts();
|
|
472
|
+
|
|
473
|
+
// Use shell to handle complex commands with pipes, redirects, etc.
|
|
474
|
+
const shell = process.env.SHELL || '/bin/bash';
|
|
475
|
+
const child = spawn(shell, ['-c', command], {
|
|
476
|
+
cwd,
|
|
477
|
+
env: safeEnv,
|
|
478
|
+
stdio: ['pipe', 'pipe', 'pipe'], // Enable stdin for interactive commands
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Store reference to active process for stdin support
|
|
482
|
+
this.activeProcess = child;
|
|
483
|
+
|
|
484
|
+
// Set timeout
|
|
485
|
+
const timeoutId = setTimeout(() => {
|
|
486
|
+
killed = true;
|
|
487
|
+
child.kill('SIGTERM');
|
|
488
|
+
this.daemon.logEvent(this.taskId, 'command_output', {
|
|
489
|
+
command,
|
|
490
|
+
type: 'error',
|
|
491
|
+
output: `\n[Command timed out after ${timeout / 1000}s]\n`,
|
|
492
|
+
});
|
|
493
|
+
}, timeout);
|
|
494
|
+
|
|
495
|
+
// Stream stdout
|
|
496
|
+
child.stdout.on('data', (data: Buffer) => {
|
|
497
|
+
const chunk = data.toString('utf-8');
|
|
498
|
+
stdout += chunk;
|
|
499
|
+
// Emit live output
|
|
500
|
+
this.daemon.logEvent(this.taskId, 'command_output', {
|
|
501
|
+
command,
|
|
502
|
+
type: 'stdout',
|
|
503
|
+
output: chunk,
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// Stream stderr
|
|
508
|
+
child.stderr.on('data', (data: Buffer) => {
|
|
509
|
+
const chunk = data.toString('utf-8');
|
|
510
|
+
stderr += chunk;
|
|
511
|
+
// Emit live output
|
|
512
|
+
this.daemon.logEvent(this.taskId, 'command_output', {
|
|
513
|
+
command,
|
|
514
|
+
type: 'stderr',
|
|
515
|
+
output: chunk,
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
child.on('close', (code: number | null) => {
|
|
520
|
+
clearTimeout(timeoutId);
|
|
521
|
+
this.activeProcess = null; // Clear active process reference
|
|
522
|
+
// Clear any pending escalation timeouts to prevent killing reused PIDs
|
|
523
|
+
this.clearEscalationTimeouts();
|
|
524
|
+
|
|
525
|
+
// Determine termination reason to signal the agent
|
|
526
|
+
let terminationReason: CommandTerminationReason = 'normal';
|
|
527
|
+
if (this.userKillRequested) {
|
|
528
|
+
terminationReason = 'user_stopped';
|
|
529
|
+
} else if (killed) {
|
|
530
|
+
terminationReason = 'timeout';
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Reset for next command
|
|
534
|
+
this.userKillRequested = false;
|
|
535
|
+
|
|
536
|
+
const success = terminationReason === 'normal' && code === 0;
|
|
537
|
+
const truncatedStdout = this.truncateOutput(stdout);
|
|
538
|
+
const truncatedStderr = this.truncateOutput(stderr);
|
|
539
|
+
const exitCodeLabel = code === null ? 'unknown' : String(code);
|
|
540
|
+
const errorMessage = terminationReason === 'timeout'
|
|
541
|
+
? 'Command timed out'
|
|
542
|
+
: terminationReason === 'user_stopped'
|
|
543
|
+
? 'Command stopped by user'
|
|
544
|
+
: !success
|
|
545
|
+
? `Command exited with code ${exitCodeLabel}`
|
|
546
|
+
: undefined;
|
|
547
|
+
|
|
548
|
+
// Emit command completion with termination reason
|
|
549
|
+
this.daemon.logEvent(this.taskId, 'command_output', {
|
|
550
|
+
command,
|
|
551
|
+
type: 'end',
|
|
552
|
+
exitCode: code,
|
|
553
|
+
success,
|
|
554
|
+
terminationReason,
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
this.daemon.logEvent(this.taskId, 'tool_result', {
|
|
558
|
+
tool: 'run_command',
|
|
559
|
+
success,
|
|
560
|
+
exitCode: code,
|
|
561
|
+
terminationReason,
|
|
562
|
+
error: errorMessage,
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
resolve({
|
|
566
|
+
success,
|
|
567
|
+
stdout: truncatedStdout,
|
|
568
|
+
stderr: truncatedStderr,
|
|
569
|
+
exitCode: code,
|
|
570
|
+
truncated: stdout.length > MAX_OUTPUT_SIZE || stderr.length > MAX_OUTPUT_SIZE,
|
|
571
|
+
terminationReason,
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
child.on('error', (error: Error) => {
|
|
576
|
+
clearTimeout(timeoutId);
|
|
577
|
+
this.activeProcess = null; // Clear active process reference
|
|
578
|
+
// Clear any pending escalation timeouts to prevent killing reused PIDs
|
|
579
|
+
this.clearEscalationTimeouts();
|
|
580
|
+
// Reset user kill flag
|
|
581
|
+
this.userKillRequested = false;
|
|
582
|
+
|
|
583
|
+
const terminationReason: CommandTerminationReason = 'error';
|
|
584
|
+
|
|
585
|
+
this.daemon.logEvent(this.taskId, 'command_output', {
|
|
586
|
+
command,
|
|
587
|
+
type: 'error',
|
|
588
|
+
output: `\n[Error: ${error.message}]\n`,
|
|
589
|
+
terminationReason,
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
this.daemon.logEvent(this.taskId, 'tool_result', {
|
|
593
|
+
tool: 'run_command',
|
|
594
|
+
success: false,
|
|
595
|
+
error: error.message,
|
|
596
|
+
terminationReason,
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
resolve({
|
|
600
|
+
success: false,
|
|
601
|
+
stdout: this.truncateOutput(stdout),
|
|
602
|
+
stderr: error.message,
|
|
603
|
+
exitCode: null,
|
|
604
|
+
terminationReason,
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Generate a normalized signature for a command to detect similar repeats
|
|
612
|
+
*/
|
|
613
|
+
private getCommandSignature(command: string): string {
|
|
614
|
+
if (!command) return '';
|
|
615
|
+
let signature = command.trim();
|
|
616
|
+
signature = signature.replace(/\s+/g, ' ');
|
|
617
|
+
signature = signature.replace(/"(?:[^"\\]|\\.)*"/g, '"<arg>"');
|
|
618
|
+
signature = signature.replace(/'(?:[^'\\]|\\.)*'/g, "'<arg>'");
|
|
619
|
+
signature = signature.replace(/(?:\/Users\/[^\s]+|~\/[^\s]+|\/[^\s]+)/g, '<path>');
|
|
620
|
+
return signature;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Safety check for auto-approving similar commands
|
|
625
|
+
*/
|
|
626
|
+
private isAutoApprovalSafe(command: string): boolean {
|
|
627
|
+
return !/(^|\s)(sudo|rm|dd|mkfs|diskutil|shutdown|reboot|killall)\b/i.test(command);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Truncate output to prevent context overflow
|
|
632
|
+
*/
|
|
633
|
+
private truncateOutput(output: string): string {
|
|
634
|
+
if (output.length <= MAX_OUTPUT_SIZE) {
|
|
635
|
+
return output;
|
|
636
|
+
}
|
|
637
|
+
return (
|
|
638
|
+
output.slice(0, MAX_OUTPUT_SIZE) +
|
|
639
|
+
`\n\n[... Output truncated. Showing first ${Math.round(MAX_OUTPUT_SIZE / 1024)}KB ...]`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Export validation functions for testing
|
|
645
|
+
export const _testUtils = {
|
|
646
|
+
isValidPid,
|
|
647
|
+
isValidUsername,
|
|
648
|
+
isProcessOwnedByCurrentUser,
|
|
649
|
+
getDescendantPids,
|
|
650
|
+
killProcessTree,
|
|
651
|
+
};
|