cowork-os 0.3.23 → 0.3.25
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/README.md +89 -14
- package/dist/electron/electron/agent/custom-skill-loader.js +31 -1
- package/dist/electron/electron/agent/daemon.js +164 -13
- package/dist/electron/electron/agent/executor.js +716 -54
- package/dist/electron/electron/agent/llm/azure-openai-provider.js +328 -0
- package/dist/electron/electron/agent/llm/bedrock-provider.js +49 -9
- package/dist/electron/electron/agent/llm/index.js +3 -1
- package/dist/electron/electron/agent/llm/provider-factory.js +34 -2
- package/dist/electron/electron/agent/search/provider-factory.js +38 -2
- package/dist/electron/electron/agent/tools/file-tools.js +66 -3
- package/dist/electron/electron/agent/tools/grep-tools.js +90 -10
- package/dist/electron/electron/agent/tools/image-tools.js +11 -1
- package/dist/electron/electron/agent/tools/registry.js +7 -10
- package/dist/electron/electron/agent/tools/search-tools.js +28 -10
- package/dist/electron/electron/agents/agent-dispatch.js +63 -0
- package/dist/electron/electron/database/repositories.js +19 -5
- package/dist/electron/electron/database/schema.js +8 -0
- package/dist/electron/electron/gateway/channels/whatsapp.js +55 -0
- package/dist/electron/electron/gateway/index.js +74 -1
- package/dist/electron/electron/gateway/router.js +86 -11
- package/dist/electron/electron/ipc/handlers.js +156 -129
- package/dist/electron/electron/memory/MemoryService.js +2 -1
- package/dist/electron/electron/preload.js +4 -0
- package/dist/electron/electron/settings/appearance-manager.js +18 -1
- package/dist/electron/electron/utils/validation.js +18 -2
- package/dist/electron/shared/types.js +2 -0
- package/package.json +3 -2
- package/resources/skills/nano-banana-pro.json +4 -4
- package/resources/skills/openai-image-gen.json +3 -3
- package/resources/skills/scripts/gen.py +163 -0
- package/resources/skills/scripts/generate_image.py +91 -0
- package/src/electron/agent/custom-skill-loader.ts +34 -1
- package/src/electron/agent/daemon.ts +210 -14
- package/src/electron/agent/executor.ts +919 -57
- package/src/electron/agent/llm/azure-openai-provider.ts +388 -0
- package/src/electron/agent/llm/bedrock-provider.ts +62 -9
- package/src/electron/agent/llm/index.ts +1 -0
- package/src/electron/agent/llm/provider-factory.ts +45 -0
- package/src/electron/agent/llm/types.ts +5 -0
- package/src/electron/agent/search/provider-factory.ts +43 -2
- package/src/electron/agent/tools/builtin-settings.ts +2 -0
- package/src/electron/agent/tools/file-tools.ts +66 -3
- package/src/electron/agent/tools/gmail-tools.ts +240 -0
- package/src/electron/agent/tools/google-calendar-tools.ts +258 -0
- package/src/electron/agent/tools/google-drive-tools.ts +5 -5
- package/src/electron/agent/tools/grep-tools.ts +97 -12
- package/src/electron/agent/tools/image-tools.ts +11 -1
- package/src/electron/agent/tools/registry.ts +229 -10
- package/src/electron/agent/tools/search-tools.ts +29 -11
- package/src/electron/agents/agent-dispatch.ts +79 -0
- package/src/electron/database/repositories.ts +58 -6
- package/src/electron/database/schema.ts +8 -0
- package/src/electron/gateway/channels/discord.ts +4 -0
- package/src/electron/gateway/channels/google-chat.ts +3 -0
- package/src/electron/gateway/channels/line.ts +3 -0
- package/src/electron/gateway/channels/matrix-client.ts +15 -0
- package/src/electron/gateway/channels/matrix.ts +31 -0
- package/src/electron/gateway/channels/mattermost.ts +3 -0
- package/src/electron/gateway/channels/signal.ts +3 -0
- package/src/electron/gateway/channels/slack.ts +9 -4
- package/src/electron/gateway/channels/teams.ts +4 -0
- package/src/electron/gateway/channels/telegram.ts +2 -0
- package/src/electron/gateway/channels/twitch.ts +2 -0
- package/src/electron/gateway/channels/types.ts +8 -0
- package/src/electron/gateway/channels/whatsapp.ts +66 -0
- package/src/electron/gateway/index.ts +94 -2
- package/src/electron/gateway/router.ts +97 -12
- package/src/electron/gateway/security.ts +21 -9
- package/src/electron/ipc/handlers.ts +199 -163
- package/src/electron/memory/MemoryService.ts +2 -0
- package/src/electron/preload.ts +48 -16
- package/src/electron/settings/appearance-manager.ts +20 -2
- package/src/electron/settings/google-workspace-manager.ts +59 -0
- package/src/electron/utils/gmail-api.ts +121 -0
- package/src/electron/utils/google-calendar-api.ts +115 -0
- package/src/electron/utils/google-workspace-api.ts +228 -0
- package/src/electron/utils/google-workspace-auth.ts +109 -0
- package/src/electron/utils/google-workspace-oauth.ts +232 -0
- package/src/electron/utils/validation.ts +28 -2
- package/src/renderer/App.tsx +99 -6
- package/src/renderer/components/ActivityFeedItem.tsx +34 -17
- package/src/renderer/components/AgentWorkingStatePanel.tsx +7 -5
- package/src/renderer/components/AppearanceSettings.tsx +37 -2
- package/src/renderer/components/BlueBubblesSettings.tsx +18 -7
- package/src/renderer/components/ControlPlaneSettings.tsx +2 -0
- package/src/renderer/components/DiscordSettings.tsx +18 -7
- package/src/renderer/components/EmailSettings.tsx +18 -7
- package/src/renderer/components/FileViewer.tsx +21 -13
- package/src/renderer/components/GoogleChatSettings.tsx +17 -7
- package/src/renderer/components/GoogleWorkspaceSettings.tsx +332 -0
- package/src/renderer/components/ImessageSettings.tsx +22 -11
- package/src/renderer/components/LineIcons.tsx +376 -0
- package/src/renderer/components/LineSettings.tsx +18 -7
- package/src/renderer/components/MainContent.tsx +522 -94
- package/src/renderer/components/MatrixSettings.tsx +18 -7
- package/src/renderer/components/MattermostSettings.tsx +18 -7
- package/src/renderer/components/NodesSettings.tsx +58 -99
- package/src/renderer/components/NotificationPanel.tsx +25 -11
- package/src/renderer/components/RightPanel.tsx +141 -28
- package/src/renderer/components/ScheduledTasksSettings.tsx +10 -62
- package/src/renderer/components/SearchSettings.tsx +118 -114
- package/src/renderer/components/Settings.tsx +1179 -1008
- package/src/renderer/components/Sidebar.tsx +78 -19
- package/src/renderer/components/SignalSettings.tsx +18 -7
- package/src/renderer/components/SkillHubBrowser.tsx +144 -185
- package/src/renderer/components/SlackSettings.tsx +18 -7
- package/src/renderer/components/TaskQuickActions.tsx +11 -6
- package/src/renderer/components/TaskTimeline.tsx +58 -26
- package/src/renderer/components/TeamsSettings.tsx +18 -7
- package/src/renderer/components/TelegramSettings.tsx +18 -7
- package/src/renderer/components/ThemeIcon.tsx +16 -0
- package/src/renderer/components/TwitchSettings.tsx +18 -7
- package/src/renderer/components/VoiceSettings.tsx +30 -74
- package/src/renderer/components/WhatsAppSettings.tsx +48 -37
- package/src/renderer/components/WorkingStateHistory.tsx +7 -5
- package/src/renderer/components/WorkspaceSelector.tsx +42 -13
- package/src/renderer/styles/index.css +1925 -214
- package/src/shared/types.ts +32 -8
- package/src/electron/settings/google-drive-manager.ts +0 -58
- package/src/electron/utils/google-drive-api.ts +0 -183
- package/src/renderer/components/GoogleDriveSettings.tsx +0 -201
package/README.md
CHANGED
|
@@ -29,10 +29,35 @@ Your AI needs a secure home. CoWork OS provides the runtime, security layers, an
|
|
|
29
29
|
| **20+ AI Providers** | Claude, OpenAI, Gemini, Bedrock, OpenRouter, Ollama (free/local), Groq, xAI, Kimi, Mistral, Cerebras, MiniMax, Qwen, Copilot, and more |
|
|
30
30
|
| **14 Messaging Channels** | WhatsApp, Telegram, Discord, Slack, Teams, Google Chat, iMessage, Signal, Mattermost, Matrix, Twitch, LINE, BlueBubbles, Email |
|
|
31
31
|
| **8 Enterprise Connectors** | Salesforce, Jira, HubSpot, Zendesk, ServiceNow, Linear, Asana, Okta |
|
|
32
|
-
| **6 Cloud Storage** | Notion, Box, OneDrive, Google Drive, Dropbox, SharePoint |
|
|
33
|
-
| **Security-First** |
|
|
32
|
+
| **6 Cloud Storage** | Notion, Box, OneDrive, Google Workspace (Drive/Gmail/Calendar), Dropbox, SharePoint |
|
|
33
|
+
| **Security-First** | 2350+ unit tests, configurable guardrails, approval workflows |
|
|
34
34
|
| **Local-First** | Your data stays on your machine. BYOK (Bring Your Own Key) |
|
|
35
35
|
|
|
36
|
+
### Feature Comparison (CoWork OS vs OpenClaw vs Claude Cowork)
|
|
37
|
+
|
|
38
|
+
| Feature | CoWork OS | OpenClaw | Claude Cowork |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| Model providers / BYOK | 20+ providers (Claude, OpenAI, Gemini, Bedrock, OpenRouter, Ollama, Groq, xAI, Kimi, Mistral, Cerebras, MiniMax, Qwen, Copilot, and more); BYOK | Any model supported; OAuth + API keys (Anthropic/OpenAI listed) | Claude plans (Pro/Max/Team/Enterprise); other providers not mentioned |
|
|
41
|
+
| Local-first data control | Data stays on your machine; BYOK | Run on your own devices; local-first gateway | Runs locally in an isolated VM; choose folders/connectors; local history for Team/Enterprise |
|
|
42
|
+
| Desktop platforms | macOS (cross-platform planned) | macOS, Linux, Windows (via WSL2) | macOS only (research preview) |
|
|
43
|
+
| Messaging channels | 14 channels: WhatsApp, Telegram, Discord, Slack, Teams, Google Chat, iMessage, Signal, Mattermost, Matrix, Twitch, LINE, BlueBubbles, Email | Multi-channel inbox: WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, BlueBubbles, iMessage, Teams, Matrix, Zalo, WebChat, macOS, iOS/Android | Not stated (desktop app + connectors) |
|
|
44
|
+
| Enterprise connectors | Salesforce, Jira, HubSpot, Zendesk, ServiceNow, Linear, Asana, Okta | Not stated | Connectors supported; examples include Slack, Notion, GitHub, Linear |
|
|
45
|
+
| Cloud storage connectors | Notion, Box, OneDrive, Google Workspace, Dropbox, SharePoint | Not stated | Not stated |
|
|
46
|
+
| Mobile companions | iOS + Android companion apps (local network) | iOS/Android nodes + macOS menu bar app | Not stated (macOS only) |
|
|
47
|
+
| Scheduling / cron | Recurring tasks via cron expressions | Cron + wakeups | Not stated |
|
|
48
|
+
| Remote access | Tailscale or SSH tunnels | Tailscale Serve/Funnel or SSH tunnels | Not stated |
|
|
49
|
+
| WebSocket API / control plane | WebSocket API for custom integrations | Gateway WebSocket control plane | Not stated |
|
|
50
|
+
| Browser automation | Playwright automation | Browser control (Chrome/Chromium) | Browser access via Chrome connector |
|
|
51
|
+
| Skills / plugins | 75+ bundled skills | Skills platform (bundled, managed, workspace skills) | Not stated |
|
|
52
|
+
| MCP support | Yes | Not stated | Not stated |
|
|
53
|
+
| Guardrails (token/cost/iteration limits) | Configurable guardrails | Not stated | Not stated |
|
|
54
|
+
| Approval workflows | Required for destructive operations | Not stated | Human approval before significant actions |
|
|
55
|
+
| Access control (pairing/allowlists) | Pairing codes + per-channel allowlists | DM pairing + allowlists | Folder/connector access controls |
|
|
56
|
+
| Sandbox isolation | macOS sandbox-exec or Docker | Not stated | Runs locally in an isolated VM |
|
|
57
|
+
| Encrypted credential storage | OS keychain + AES-256 fallback | Not stated | Not stated |
|
|
58
|
+
|
|
59
|
+
_Sources: [OpenClaw README](https://github.com/openclaw/openclaw), [OpenClaw docs](https://docs.openclaw.ai), and the [Claude Cowork product page](https://claude.com/product/cowork). “Not stated” means not documented in public materials._
|
|
60
|
+
|
|
36
61
|
> **Status**: macOS desktop app (cross-platform support planned)
|
|
37
62
|
|
|
38
63
|
---
|
|
@@ -65,7 +90,7 @@ Your AI needs a secure home. CoWork OS provides the runtime, security layers, an
|
|
|
65
90
|
- **Dangerous command blocking**: Built-in patterns + custom regex rules
|
|
66
91
|
- **Approval workflows**: User consent required for destructive operations
|
|
67
92
|
- **Pairing & allowlists**: Control who can access your AI via messaging channels
|
|
68
|
-
- **
|
|
93
|
+
- **2350+ tests**: Comprehensive test coverage for access control and policies
|
|
69
94
|
|
|
70
95
|
### Your Data, Your Control
|
|
71
96
|
|
|
@@ -218,6 +243,18 @@ All channels support:
|
|
|
218
243
|
- Session management
|
|
219
244
|
- Rate limiting
|
|
220
245
|
|
|
246
|
+
### Visual Theme System
|
|
247
|
+
|
|
248
|
+
Customize the app appearance with multiple theme options.
|
|
249
|
+
|
|
250
|
+
| Theme | Description |
|
|
251
|
+
|-------|-------------|
|
|
252
|
+
| **System** | Follows your macOS light/dark mode preference |
|
|
253
|
+
| **Light** | Clean light interface |
|
|
254
|
+
| **Dark** | Dark mode for reduced eye strain |
|
|
255
|
+
|
|
256
|
+
Configure in **Settings** > **Appearance**.
|
|
257
|
+
|
|
221
258
|
### Agent Capabilities
|
|
222
259
|
|
|
223
260
|
- **Task-Based Workflow**: Multi-step execution with plan-execute-observe loops
|
|
@@ -226,6 +263,7 @@ All channels support:
|
|
|
226
263
|
- **75+ Built-in Skills**: GitHub, Slack, Notion, Spotify, Apple Notes, and more
|
|
227
264
|
- **Document Creation**: Excel, Word, PDF, PowerPoint with professional formatting
|
|
228
265
|
- **Persistent Memory**: Cross-session context with privacy-aware observation capture
|
|
266
|
+
- **Workspace Recency**: Workspaces ordered by last used time for quick access
|
|
229
267
|
|
|
230
268
|
### Voice Mode (NEW)
|
|
231
269
|
|
|
@@ -1377,7 +1415,13 @@ Access CoWork OS from your iPhone, iPad, or Android device via the local network
|
|
|
1377
1415
|
|
|
1378
1416
|
## Web Search Integration
|
|
1379
1417
|
|
|
1380
|
-
Multi-provider web search for research tasks.
|
|
1418
|
+
Multi-provider web search for research tasks with automatic retry and fallback.
|
|
1419
|
+
|
|
1420
|
+
### Features
|
|
1421
|
+
|
|
1422
|
+
- **Automatic Retry**: Transient errors (rate limits, timeouts) trigger automatic retry with exponential backoff
|
|
1423
|
+
- **Provider Fallback**: If one provider fails, automatically tries the next configured provider
|
|
1424
|
+
- **Graceful Degradation**: Returns helpful error messages instead of failing silently
|
|
1381
1425
|
|
|
1382
1426
|
### Supported Providers
|
|
1383
1427
|
|
|
@@ -1410,6 +1454,8 @@ Claude Code-style tools for developers.
|
|
|
1410
1454
|
→ grep pattern="TODO:" glob="*.ts"
|
|
1411
1455
|
```
|
|
1412
1456
|
|
|
1457
|
+
**Smart Document Detection**: Automatically detects document-heavy workspaces (PDF/DOCX) and provides helpful guidance to use `read_file` instead, since grep only searches text files.
|
|
1458
|
+
|
|
1413
1459
|
### edit_file - Surgical Editing
|
|
1414
1460
|
|
|
1415
1461
|
```
|
|
@@ -1553,25 +1599,47 @@ onedrive_action({
|
|
|
1553
1599
|
|
|
1554
1600
|
---
|
|
1555
1601
|
|
|
1556
|
-
## Google
|
|
1602
|
+
## Google Workspace Integration
|
|
1557
1603
|
|
|
1558
|
-
Configure in **Settings > Integrations > Google
|
|
1604
|
+
Configure in **Settings > Integrations > Google Workspace**. Unified access to Gmail, Google Calendar, and Google Drive with shared OAuth authentication.
|
|
1559
1605
|
|
|
1560
|
-
###
|
|
1606
|
+
### Available Tools
|
|
1607
|
+
|
|
1608
|
+
| Service | Tool | Actions |
|
|
1609
|
+
|---------|------|---------|
|
|
1610
|
+
| **Drive** | `google_drive_action` | list_files, search, upload_file, download_file, delete_file |
|
|
1611
|
+
| **Gmail** | `gmail_action` | list_messages, search, send_email, read_email, create_draft |
|
|
1612
|
+
| **Calendar** | `google_calendar_action` | list_events, create_event, update_event, delete_event |
|
|
1613
|
+
|
|
1614
|
+
### Gmail - Send an email
|
|
1561
1615
|
|
|
1562
1616
|
```ts
|
|
1563
|
-
|
|
1564
|
-
action: "
|
|
1565
|
-
|
|
1617
|
+
gmail_action({
|
|
1618
|
+
action: "send_email",
|
|
1619
|
+
to: "recipient@example.com",
|
|
1620
|
+
subject: "Weekly Report",
|
|
1621
|
+
body: "Please find the attached report..."
|
|
1566
1622
|
});
|
|
1567
1623
|
```
|
|
1568
1624
|
|
|
1569
|
-
###
|
|
1625
|
+
### Calendar - Create an event
|
|
1626
|
+
|
|
1627
|
+
```ts
|
|
1628
|
+
google_calendar_action({
|
|
1629
|
+
action: "create_event",
|
|
1630
|
+
title: "Team Standup",
|
|
1631
|
+
start_time: "2025-02-10T09:00:00",
|
|
1632
|
+
end_time: "2025-02-10T09:30:00",
|
|
1633
|
+
attendees: ["team@example.com"]
|
|
1634
|
+
});
|
|
1635
|
+
```
|
|
1636
|
+
|
|
1637
|
+
### Drive - List files
|
|
1570
1638
|
|
|
1571
1639
|
```ts
|
|
1572
1640
|
google_drive_action({
|
|
1573
|
-
action: "
|
|
1574
|
-
|
|
1641
|
+
action: "list_files",
|
|
1642
|
+
page_size: 20
|
|
1575
1643
|
});
|
|
1576
1644
|
```
|
|
1577
1645
|
|
|
@@ -1876,7 +1944,7 @@ Users must comply with their model provider's terms:
|
|
|
1876
1944
|
- [x] Tailscale and SSH remote access
|
|
1877
1945
|
- [x] Personality system
|
|
1878
1946
|
- [x] 75+ bundled skills
|
|
1879
|
-
- [x]
|
|
1947
|
+
- [x] 2350+ unit tests
|
|
1880
1948
|
- [x] Docker-based sandboxing (cross-platform)
|
|
1881
1949
|
- [x] Per-context security policies (DM vs group)
|
|
1882
1950
|
- [x] Enhanced pairing code UI with countdown
|
|
@@ -1885,6 +1953,13 @@ Users must comply with their model provider's terms:
|
|
|
1885
1953
|
- [x] Voice Mode with ElevenLabs and OpenAI integration
|
|
1886
1954
|
- [x] Enterprise MCP Connectors (Salesforce, Jira, HubSpot, Zendesk, ServiceNow, Linear, Asana, Okta)
|
|
1887
1955
|
- [x] Cloud Storage Integrations (Notion, Box, OneDrive, Google Drive, Dropbox, SharePoint)
|
|
1956
|
+
- [x] Visual Theme System (Light/Dark/System modes)
|
|
1957
|
+
- [x] Workspace recency ordering
|
|
1958
|
+
- [x] Web search retry with exponential backoff
|
|
1959
|
+
- [x] Google Workspace Integration (Gmail, Calendar, Drive with shared OAuth)
|
|
1960
|
+
- [x] Gateway channel cleanup and enhanced security (Matrix direct rooms, Slack groups)
|
|
1961
|
+
- [x] Agent transient error retry logic for improved reliability
|
|
1962
|
+
- [x] Smart parameter inference for document creation tools
|
|
1888
1963
|
|
|
1889
1964
|
### Planned
|
|
1890
1965
|
|
|
@@ -359,7 +359,7 @@ class CustomSkillLoader {
|
|
|
359
359
|
* Expand a skill's prompt template with parameter values
|
|
360
360
|
*/
|
|
361
361
|
expandPrompt(skill, parameterValues) {
|
|
362
|
-
let prompt = skill.prompt;
|
|
362
|
+
let prompt = this.expandBaseDir(skill.prompt, skill);
|
|
363
363
|
// Replace {{param}} placeholders with values
|
|
364
364
|
if (skill.parameters) {
|
|
365
365
|
for (const param of skill.parameters) {
|
|
@@ -372,6 +372,36 @@ class CustomSkillLoader {
|
|
|
372
372
|
prompt = prompt.replace(/\{\{[^}]+\}\}/g, '');
|
|
373
373
|
return prompt.trim();
|
|
374
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Expand {baseDir} placeholders to the resolved skill base directory.
|
|
377
|
+
*/
|
|
378
|
+
expandBaseDir(prompt, skill) {
|
|
379
|
+
if (!prompt.includes('{baseDir}')) {
|
|
380
|
+
return prompt;
|
|
381
|
+
}
|
|
382
|
+
const baseDir = this.resolveBaseDir(skill);
|
|
383
|
+
return prompt.replace(/\{baseDir\}/g, baseDir);
|
|
384
|
+
}
|
|
385
|
+
resolveBaseDir(skill) {
|
|
386
|
+
const fileDir = skill.filePath ? path.dirname(skill.filePath) : this.bundledSkillsDir;
|
|
387
|
+
const candidates = [
|
|
388
|
+
fileDir,
|
|
389
|
+
this.bundledSkillsDir,
|
|
390
|
+
this.managedSkillsDir,
|
|
391
|
+
this.workspaceSkillsDir || '',
|
|
392
|
+
].filter(Boolean);
|
|
393
|
+
for (const dir of candidates) {
|
|
394
|
+
try {
|
|
395
|
+
if (fs.existsSync(path.join(dir, 'scripts'))) {
|
|
396
|
+
return dir;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
// ignore and continue
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return fileDir;
|
|
404
|
+
}
|
|
375
405
|
/**
|
|
376
406
|
* Get eligible skills (those that meet all requirements)
|
|
377
407
|
*/
|
|
@@ -40,6 +40,9 @@ const fs = __importStar(require("fs"));
|
|
|
40
40
|
const crypto = __importStar(require("crypto"));
|
|
41
41
|
const repositories_1 = require("../database/repositories");
|
|
42
42
|
const ActivityRepository_1 = require("../activity/ActivityRepository");
|
|
43
|
+
const AgentRoleRepository_1 = require("../agents/AgentRoleRepository");
|
|
44
|
+
const MentionRepository_1 = require("../agents/MentionRepository");
|
|
45
|
+
const agent_dispatch_1 = require("../agents/agent-dispatch");
|
|
43
46
|
const types_1 = require("../../shared/types");
|
|
44
47
|
const executor_1 = require("./executor");
|
|
45
48
|
const queue_manager_1 = require("./queue-manager");
|
|
@@ -63,6 +66,10 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
63
66
|
this.pendingApprovals = new Map();
|
|
64
67
|
// Activity throttle: Map<taskId:eventType, lastTimestamp>
|
|
65
68
|
this.activityThrottle = new Map();
|
|
69
|
+
this.pendingRetries = new Map();
|
|
70
|
+
this.retryCounts = new Map();
|
|
71
|
+
this.maxTaskRetries = 2;
|
|
72
|
+
this.retryDelayMs = 30 * 1000;
|
|
66
73
|
const db = dbManager.getDatabase();
|
|
67
74
|
this.taskRepo = new repositories_1.TaskRepository(db);
|
|
68
75
|
this.eventRepo = new repositories_1.TaskEventRepository(db);
|
|
@@ -70,6 +77,8 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
70
77
|
this.approvalRepo = new repositories_1.ApprovalRepository(db);
|
|
71
78
|
this.artifactRepo = new repositories_1.ArtifactRepository(db);
|
|
72
79
|
this.activityRepo = new ActivityRepository_1.ActivityRepository(db);
|
|
80
|
+
this.agentRoleRepo = new AgentRoleRepository_1.AgentRoleRepository(db);
|
|
81
|
+
this.mentionRepo = new MentionRepository_1.MentionRepository(db);
|
|
73
82
|
// Initialize queue manager with callbacks
|
|
74
83
|
this.queueManager = new queue_manager_1.TaskQueueManager({
|
|
75
84
|
startTaskImmediate: (task) => this.startTaskImmediate(task),
|
|
@@ -250,6 +259,123 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
250
259
|
await this.startTask(task);
|
|
251
260
|
return task;
|
|
252
261
|
}
|
|
262
|
+
buildPlanSummary(plan) {
|
|
263
|
+
if (!plan)
|
|
264
|
+
return undefined;
|
|
265
|
+
const lines = [];
|
|
266
|
+
if (plan.description) {
|
|
267
|
+
lines.push(`Plan: ${plan.description}`);
|
|
268
|
+
}
|
|
269
|
+
if (plan.steps && plan.steps.length > 0) {
|
|
270
|
+
lines.push('Steps:');
|
|
271
|
+
const stepLines = plan.steps
|
|
272
|
+
.slice(0, 7)
|
|
273
|
+
.map((step) => `- ${step.description}`);
|
|
274
|
+
lines.push(...stepLines);
|
|
275
|
+
if (plan.steps.length > 7) {
|
|
276
|
+
lines.push(`- …and ${plan.steps.length - 7} more steps`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return lines.length > 0 ? lines.join('\n') : undefined;
|
|
280
|
+
}
|
|
281
|
+
emitActivityEvent(activity) {
|
|
282
|
+
const windows = electron_1.BrowserWindow.getAllWindows();
|
|
283
|
+
windows.forEach(window => {
|
|
284
|
+
try {
|
|
285
|
+
if (!window.isDestroyed() && window.webContents && !window.webContents.isDestroyed()) {
|
|
286
|
+
window.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
console.error('[AgentDaemon] Error sending activity IPC:', error);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
emitMentionEvent(mention) {
|
|
295
|
+
const windows = electron_1.BrowserWindow.getAllWindows();
|
|
296
|
+
windows.forEach(window => {
|
|
297
|
+
try {
|
|
298
|
+
if (!window.isDestroyed() && window.webContents && !window.webContents.isDestroyed()) {
|
|
299
|
+
window.webContents.send(types_1.IPC_CHANNELS.MENTION_EVENT, { type: 'created', mention });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
console.error('[AgentDaemon] Error sending mention IPC:', error);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Dispatch mentioned agent roles after the main plan is created.
|
|
309
|
+
* This avoids starting sub-agents before the task is clearly defined.
|
|
310
|
+
*/
|
|
311
|
+
async dispatchMentionedAgents(taskId, plan) {
|
|
312
|
+
const task = this.taskRepo.findById(taskId);
|
|
313
|
+
if (!task || task.parentTaskId)
|
|
314
|
+
return;
|
|
315
|
+
const mentionedRoleIds = (task.mentionedAgentRoleIds || []).filter(Boolean);
|
|
316
|
+
if (mentionedRoleIds.length === 0)
|
|
317
|
+
return;
|
|
318
|
+
const activeRoles = this.agentRoleRepo.findAll(false).filter(role => role.isActive);
|
|
319
|
+
const mentionedRoles = activeRoles.filter(role => mentionedRoleIds.includes(role.id));
|
|
320
|
+
if (mentionedRoles.length === 0)
|
|
321
|
+
return;
|
|
322
|
+
const existingChildren = this.taskRepo.findByParent(taskId);
|
|
323
|
+
const assignedRoleIds = new Set(existingChildren
|
|
324
|
+
.map(child => child.assignedAgentRoleId)
|
|
325
|
+
.filter((id) => typeof id === 'string' && id.length > 0));
|
|
326
|
+
const rolesToDispatch = mentionedRoles.filter(role => !assignedRoleIds.has(role.id));
|
|
327
|
+
if (rolesToDispatch.length === 0)
|
|
328
|
+
return;
|
|
329
|
+
const planSummary = this.buildPlanSummary(plan);
|
|
330
|
+
for (const role of rolesToDispatch) {
|
|
331
|
+
const childPrompt = (0, agent_dispatch_1.buildAgentDispatchPrompt)(role, { title: task.title, prompt: task.prompt }, planSummary ? { planSummary } : undefined);
|
|
332
|
+
const childTask = await this.createChildTask({
|
|
333
|
+
title: `@${role.displayName}: ${task.title}`,
|
|
334
|
+
prompt: childPrompt,
|
|
335
|
+
workspaceId: task.workspaceId,
|
|
336
|
+
parentTaskId: task.id,
|
|
337
|
+
agentType: 'sub',
|
|
338
|
+
agentConfig: {
|
|
339
|
+
...(role.modelKey ? { modelKey: role.modelKey } : {}),
|
|
340
|
+
...(role.personalityId ? { personalityId: role.personalityId } : {}),
|
|
341
|
+
retainMemory: false,
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
this.taskRepo.update(childTask.id, {
|
|
345
|
+
assignedAgentRoleId: role.id,
|
|
346
|
+
boardColumn: 'todo',
|
|
347
|
+
});
|
|
348
|
+
const dispatchActivity = this.activityRepo.create({
|
|
349
|
+
workspaceId: task.workspaceId,
|
|
350
|
+
taskId: task.id,
|
|
351
|
+
agentRoleId: role.id,
|
|
352
|
+
actorType: 'system',
|
|
353
|
+
activityType: 'agent_assigned',
|
|
354
|
+
title: `Dispatched to ${role.displayName}`,
|
|
355
|
+
description: childTask.title,
|
|
356
|
+
});
|
|
357
|
+
this.emitActivityEvent(dispatchActivity);
|
|
358
|
+
const mention = this.mentionRepo.create({
|
|
359
|
+
workspaceId: task.workspaceId,
|
|
360
|
+
taskId: task.id,
|
|
361
|
+
toAgentRoleId: role.id,
|
|
362
|
+
mentionType: 'request',
|
|
363
|
+
context: `New task: ${task.title}`,
|
|
364
|
+
});
|
|
365
|
+
this.emitMentionEvent(mention);
|
|
366
|
+
const mentionActivity = this.activityRepo.create({
|
|
367
|
+
workspaceId: task.workspaceId,
|
|
368
|
+
taskId: task.id,
|
|
369
|
+
agentRoleId: role.id,
|
|
370
|
+
actorType: 'user',
|
|
371
|
+
activityType: 'mention',
|
|
372
|
+
title: `@${role.displayName} mentioned`,
|
|
373
|
+
description: mention.context,
|
|
374
|
+
metadata: { mentionId: mention.id, mentionType: mention.mentionType },
|
|
375
|
+
});
|
|
376
|
+
this.emitActivityEvent(mentionActivity);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
253
379
|
/**
|
|
254
380
|
* Cancel a running or queued task
|
|
255
381
|
*/
|
|
@@ -276,6 +402,44 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
276
402
|
message: 'Task was stopped by user',
|
|
277
403
|
});
|
|
278
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Handle transient provider errors by scheduling a retry instead of failing.
|
|
407
|
+
* Returns true if a retry was scheduled, false if retries are exhausted.
|
|
408
|
+
*/
|
|
409
|
+
handleTransientTaskFailure(taskId, reason, delayMs = this.retryDelayMs) {
|
|
410
|
+
const currentCount = this.retryCounts.get(taskId) ?? 0;
|
|
411
|
+
const nextCount = currentCount + 1;
|
|
412
|
+
if (nextCount > this.maxTaskRetries) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
this.retryCounts.set(taskId, nextCount);
|
|
416
|
+
if (this.pendingRetries.has(taskId)) {
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
// Mark as queued with a helpful message
|
|
420
|
+
this.taskRepo.update(taskId, {
|
|
421
|
+
status: 'queued',
|
|
422
|
+
error: `Transient provider error. Retry ${nextCount}/${this.maxTaskRetries} in ${Math.ceil(delayMs / 1000)}s.`,
|
|
423
|
+
});
|
|
424
|
+
this.logEvent(taskId, 'log', {
|
|
425
|
+
message: `Transient provider error detected. Scheduling retry ${nextCount}/${this.maxTaskRetries} in ${Math.ceil(delayMs / 1000)}s.`,
|
|
426
|
+
reason,
|
|
427
|
+
});
|
|
428
|
+
// Clear executor and free queue slot
|
|
429
|
+
this.activeTasks.delete(taskId);
|
|
430
|
+
this.queueManager.onTaskFinished(taskId);
|
|
431
|
+
const handle = setTimeout(async () => {
|
|
432
|
+
this.pendingRetries.delete(taskId);
|
|
433
|
+
const task = this.taskRepo.findById(taskId);
|
|
434
|
+
if (!task)
|
|
435
|
+
return;
|
|
436
|
+
if (task.status === 'cancelled' || task.status === 'completed')
|
|
437
|
+
return;
|
|
438
|
+
await this.startTask(task);
|
|
439
|
+
}, delayMs);
|
|
440
|
+
this.pendingRetries.set(taskId, handle);
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
279
443
|
/**
|
|
280
444
|
* Pause a running task
|
|
281
445
|
*/
|
|
@@ -677,19 +841,6 @@ class AgentDaemon extends events_1.EventEmitter {
|
|
|
677
841
|
return undefined;
|
|
678
842
|
}
|
|
679
843
|
}
|
|
680
|
-
emitActivityEvent(activity) {
|
|
681
|
-
const windows = electron_1.BrowserWindow.getAllWindows();
|
|
682
|
-
windows.forEach(window => {
|
|
683
|
-
try {
|
|
684
|
-
if (!window.isDestroyed() && window.webContents && !window.webContents.isDestroyed()) {
|
|
685
|
-
window.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity });
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
catch (error) {
|
|
689
|
-
console.error('[AgentDaemon] Error sending activity IPC:', error);
|
|
690
|
-
}
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
844
|
/**
|
|
694
845
|
* Register an artifact (file created during task execution)
|
|
695
846
|
* This allows files like screenshots to be sent back to the user
|