cowork-os 0.3.23 → 0.3.27

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.
Files changed (148) hide show
  1. package/README.md +89 -14
  2. package/dist/electron/electron/agent/custom-skill-loader.js +31 -1
  3. package/dist/electron/electron/agent/daemon.js +186 -13
  4. package/dist/electron/electron/agent/executor.js +862 -54
  5. package/dist/electron/electron/agent/llm/azure-openai-provider.js +328 -0
  6. package/dist/electron/electron/agent/llm/bedrock-provider.js +49 -9
  7. package/dist/electron/electron/agent/llm/index.js +3 -1
  8. package/dist/electron/electron/agent/llm/provider-factory.js +34 -2
  9. package/dist/electron/electron/agent/search/provider-factory.js +38 -2
  10. package/dist/electron/electron/agent/tools/builtin-settings.js +2 -0
  11. package/dist/electron/electron/agent/tools/file-tools.js +66 -3
  12. package/dist/electron/electron/agent/tools/gmail-tools.js +199 -0
  13. package/dist/electron/electron/agent/tools/google-calendar-tools.js +211 -0
  14. package/dist/electron/electron/agent/tools/google-drive-tools.js +12 -12
  15. package/dist/electron/electron/agent/tools/grep-tools.js +90 -10
  16. package/dist/electron/electron/agent/tools/image-tools.js +11 -1
  17. package/dist/electron/electron/agent/tools/registry.js +221 -10
  18. package/dist/electron/electron/agent/tools/search-tools.js +28 -10
  19. package/dist/electron/electron/agents/agent-dispatch.js +63 -0
  20. package/dist/electron/electron/database/repositories.js +55 -7
  21. package/dist/electron/electron/database/schema.js +8 -0
  22. package/dist/electron/electron/gateway/channels/discord.js +4 -0
  23. package/dist/electron/electron/gateway/channels/google-chat.js +2 -0
  24. package/dist/electron/electron/gateway/channels/line.js +2 -0
  25. package/dist/electron/electron/gateway/channels/matrix-client.js +12 -0
  26. package/dist/electron/electron/gateway/channels/matrix.js +27 -0
  27. package/dist/electron/electron/gateway/channels/mattermost.js +2 -0
  28. package/dist/electron/electron/gateway/channels/signal.js +2 -0
  29. package/dist/electron/electron/gateway/channels/slack.js +9 -4
  30. package/dist/electron/electron/gateway/channels/teams.js +3 -0
  31. package/dist/electron/electron/gateway/channels/telegram.js +2 -0
  32. package/dist/electron/electron/gateway/channels/twitch.js +2 -0
  33. package/dist/electron/electron/gateway/channels/whatsapp.js +58 -0
  34. package/dist/electron/electron/gateway/index.js +111 -1
  35. package/dist/electron/electron/gateway/router.js +93 -12
  36. package/dist/electron/electron/gateway/security.js +22 -9
  37. package/dist/electron/electron/ipc/handlers.js +180 -148
  38. package/dist/electron/electron/memory/MemoryService.js +2 -1
  39. package/dist/electron/electron/preload.js +21 -10
  40. package/dist/electron/electron/settings/appearance-manager.js +18 -1
  41. package/dist/electron/electron/settings/{google-drive-manager.js → google-workspace-manager.js} +10 -9
  42. package/dist/electron/electron/utils/gmail-api.js +99 -0
  43. package/dist/electron/electron/utils/google-calendar-api.js +92 -0
  44. package/dist/electron/electron/utils/google-workspace-api.js +196 -0
  45. package/dist/electron/electron/utils/google-workspace-auth.js +91 -0
  46. package/dist/electron/electron/utils/google-workspace-oauth.js +184 -0
  47. package/dist/electron/electron/utils/validation.js +25 -4
  48. package/dist/electron/shared/types.js +11 -4
  49. package/dist/renderer/assets/index-CAE-LL8U.js +3401 -0
  50. package/dist/renderer/assets/index-Cnu5QTSE.css +1 -0
  51. package/dist/renderer/cowork-os-logo.png +0 -0
  52. package/dist/renderer/index.html +13 -0
  53. package/package.json +5 -3
  54. package/resources/skills/nano-banana-pro.json +4 -4
  55. package/resources/skills/openai-image-gen.json +3 -3
  56. package/resources/skills/scripts/gen.py +163 -0
  57. package/resources/skills/scripts/generate_image.py +91 -0
  58. package/src/electron/agent/custom-skill-loader.ts +34 -1
  59. package/src/electron/agent/daemon.ts +210 -14
  60. package/src/electron/agent/executor.ts +919 -57
  61. package/src/electron/agent/llm/azure-openai-provider.ts +388 -0
  62. package/src/electron/agent/llm/bedrock-provider.ts +62 -9
  63. package/src/electron/agent/llm/index.ts +1 -0
  64. package/src/electron/agent/llm/provider-factory.ts +45 -0
  65. package/src/electron/agent/llm/types.ts +5 -0
  66. package/src/electron/agent/search/provider-factory.ts +43 -2
  67. package/src/electron/agent/tools/builtin-settings.ts +2 -0
  68. package/src/electron/agent/tools/file-tools.ts +66 -3
  69. package/src/electron/agent/tools/gmail-tools.ts +240 -0
  70. package/src/electron/agent/tools/google-calendar-tools.ts +258 -0
  71. package/src/electron/agent/tools/google-drive-tools.ts +5 -5
  72. package/src/electron/agent/tools/grep-tools.ts +97 -12
  73. package/src/electron/agent/tools/image-tools.ts +11 -1
  74. package/src/electron/agent/tools/registry.ts +229 -10
  75. package/src/electron/agent/tools/search-tools.ts +29 -11
  76. package/src/electron/agents/agent-dispatch.ts +79 -0
  77. package/src/electron/database/repositories.ts +58 -6
  78. package/src/electron/database/schema.ts +8 -0
  79. package/src/electron/gateway/channels/discord.ts +4 -0
  80. package/src/electron/gateway/channels/google-chat.ts +3 -0
  81. package/src/electron/gateway/channels/line.ts +3 -0
  82. package/src/electron/gateway/channels/matrix-client.ts +15 -0
  83. package/src/electron/gateway/channels/matrix.ts +31 -0
  84. package/src/electron/gateway/channels/mattermost.ts +3 -0
  85. package/src/electron/gateway/channels/signal.ts +3 -0
  86. package/src/electron/gateway/channels/slack.ts +9 -4
  87. package/src/electron/gateway/channels/teams.ts +4 -0
  88. package/src/electron/gateway/channels/telegram.ts +2 -0
  89. package/src/electron/gateway/channels/twitch.ts +2 -0
  90. package/src/electron/gateway/channels/types.ts +8 -0
  91. package/src/electron/gateway/channels/whatsapp.ts +66 -0
  92. package/src/electron/gateway/index.ts +94 -2
  93. package/src/electron/gateway/router.ts +97 -12
  94. package/src/electron/gateway/security.ts +21 -9
  95. package/src/electron/ipc/handlers.ts +199 -163
  96. package/src/electron/memory/MemoryService.ts +2 -0
  97. package/src/electron/preload.ts +48 -16
  98. package/src/electron/settings/appearance-manager.ts +20 -2
  99. package/src/electron/settings/google-workspace-manager.ts +59 -0
  100. package/src/electron/utils/gmail-api.ts +121 -0
  101. package/src/electron/utils/google-calendar-api.ts +115 -0
  102. package/src/electron/utils/google-workspace-api.ts +228 -0
  103. package/src/electron/utils/google-workspace-auth.ts +109 -0
  104. package/src/electron/utils/google-workspace-oauth.ts +232 -0
  105. package/src/electron/utils/validation.ts +28 -2
  106. package/src/renderer/App.tsx +99 -6
  107. package/src/renderer/components/ActivityFeedItem.tsx +34 -17
  108. package/src/renderer/components/AgentWorkingStatePanel.tsx +7 -5
  109. package/src/renderer/components/AppearanceSettings.tsx +37 -2
  110. package/src/renderer/components/BlueBubblesSettings.tsx +18 -7
  111. package/src/renderer/components/ControlPlaneSettings.tsx +2 -0
  112. package/src/renderer/components/DiscordSettings.tsx +18 -7
  113. package/src/renderer/components/EmailSettings.tsx +18 -7
  114. package/src/renderer/components/FileViewer.tsx +21 -13
  115. package/src/renderer/components/GoogleChatSettings.tsx +17 -7
  116. package/src/renderer/components/GoogleWorkspaceSettings.tsx +332 -0
  117. package/src/renderer/components/ImessageSettings.tsx +22 -11
  118. package/src/renderer/components/LineIcons.tsx +376 -0
  119. package/src/renderer/components/LineSettings.tsx +18 -7
  120. package/src/renderer/components/MainContent.tsx +522 -94
  121. package/src/renderer/components/MatrixSettings.tsx +18 -7
  122. package/src/renderer/components/MattermostSettings.tsx +18 -7
  123. package/src/renderer/components/NodesSettings.tsx +58 -99
  124. package/src/renderer/components/NotificationPanel.tsx +25 -11
  125. package/src/renderer/components/RightPanel.tsx +141 -28
  126. package/src/renderer/components/ScheduledTasksSettings.tsx +10 -62
  127. package/src/renderer/components/SearchSettings.tsx +118 -114
  128. package/src/renderer/components/Settings.tsx +1179 -1008
  129. package/src/renderer/components/Sidebar.tsx +78 -19
  130. package/src/renderer/components/SignalSettings.tsx +18 -7
  131. package/src/renderer/components/SkillHubBrowser.tsx +144 -185
  132. package/src/renderer/components/SlackSettings.tsx +18 -7
  133. package/src/renderer/components/TaskQuickActions.tsx +11 -6
  134. package/src/renderer/components/TaskTimeline.tsx +58 -26
  135. package/src/renderer/components/TeamsSettings.tsx +18 -7
  136. package/src/renderer/components/TelegramSettings.tsx +18 -7
  137. package/src/renderer/components/ThemeIcon.tsx +16 -0
  138. package/src/renderer/components/TwitchSettings.tsx +18 -7
  139. package/src/renderer/components/VoiceSettings.tsx +30 -74
  140. package/src/renderer/components/WhatsAppSettings.tsx +48 -37
  141. package/src/renderer/components/WorkingStateHistory.tsx +7 -5
  142. package/src/renderer/components/WorkspaceSelector.tsx +42 -13
  143. package/src/renderer/styles/index.css +1925 -214
  144. package/src/shared/types.ts +32 -8
  145. package/dist/electron/electron/utils/google-drive-api.js +0 -152
  146. package/src/electron/settings/google-drive-manager.ts +0 -58
  147. package/src/electron/utils/google-drive-api.ts +0 -183
  148. 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** | 1800+ unit tests, configurable guardrails, approval workflows |
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
- - **1800+ tests**: Comprehensive test coverage for access control and policies
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 Drive Integration
1602
+ ## Google Workspace Integration
1557
1603
 
1558
- Configure in **Settings > Integrations > Google Drive**. Use `google_drive_action` to search, read, and manage Google Drive files and folders. Write actions (create, upload, delete) require approval.
1604
+ Configure in **Settings > Integrations > Google Workspace**. Unified access to Gmail, Google Calendar, and Google Drive with shared OAuth authentication.
1559
1605
 
1560
- ### List files
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
- google_drive_action({
1564
- action: "list_files",
1565
- page_size: 20
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
- ### Upload a file
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: "upload_file",
1574
- file_path: "reports/summary.pdf"
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] 1800+ unit tests
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),
@@ -173,6 +182,7 @@ class AgentDaemon extends events_1.EventEmitter {
173
182
  error: error.message || 'Failed to initialize task executor',
174
183
  completedAt: Date.now(),
175
184
  });
185
+ this.clearRetryState(task.id);
176
186
  this.logEvent(task.id, 'error', { error: error.message });
177
187
  // Notify queue manager so it can start next task
178
188
  this.queueManager.onTaskFinished(task.id);
@@ -195,6 +205,7 @@ class AgentDaemon extends events_1.EventEmitter {
195
205
  error: error.message,
196
206
  completedAt: Date.now(),
197
207
  });
208
+ this.clearRetryState(task.id);
198
209
  this.logEvent(task.id, 'error', { error: error.message });
199
210
  this.activeTasks.delete(task.id);
200
211
  // Notify queue manager so it can start next task
@@ -250,6 +261,123 @@ class AgentDaemon extends events_1.EventEmitter {
250
261
  await this.startTask(task);
251
262
  return task;
252
263
  }
264
+ buildPlanSummary(plan) {
265
+ if (!plan)
266
+ return undefined;
267
+ const lines = [];
268
+ if (plan.description) {
269
+ lines.push(`Plan: ${plan.description}`);
270
+ }
271
+ if (plan.steps && plan.steps.length > 0) {
272
+ lines.push('Steps:');
273
+ const stepLines = plan.steps
274
+ .slice(0, 7)
275
+ .map((step) => `- ${step.description}`);
276
+ lines.push(...stepLines);
277
+ if (plan.steps.length > 7) {
278
+ lines.push(`- …and ${plan.steps.length - 7} more steps`);
279
+ }
280
+ }
281
+ return lines.length > 0 ? lines.join('\n') : undefined;
282
+ }
283
+ emitActivityEvent(activity) {
284
+ const windows = electron_1.BrowserWindow.getAllWindows();
285
+ windows.forEach(window => {
286
+ try {
287
+ if (!window.isDestroyed() && window.webContents && !window.webContents.isDestroyed()) {
288
+ window.webContents.send(types_1.IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity });
289
+ }
290
+ }
291
+ catch (error) {
292
+ console.error('[AgentDaemon] Error sending activity IPC:', error);
293
+ }
294
+ });
295
+ }
296
+ emitMentionEvent(mention) {
297
+ const windows = electron_1.BrowserWindow.getAllWindows();
298
+ windows.forEach(window => {
299
+ try {
300
+ if (!window.isDestroyed() && window.webContents && !window.webContents.isDestroyed()) {
301
+ window.webContents.send(types_1.IPC_CHANNELS.MENTION_EVENT, { type: 'created', mention });
302
+ }
303
+ }
304
+ catch (error) {
305
+ console.error('[AgentDaemon] Error sending mention IPC:', error);
306
+ }
307
+ });
308
+ }
309
+ /**
310
+ * Dispatch mentioned agent roles after the main plan is created.
311
+ * This avoids starting sub-agents before the task is clearly defined.
312
+ */
313
+ async dispatchMentionedAgents(taskId, plan) {
314
+ const task = this.taskRepo.findById(taskId);
315
+ if (!task || task.parentTaskId)
316
+ return;
317
+ const mentionedRoleIds = (task.mentionedAgentRoleIds || []).filter(Boolean);
318
+ if (mentionedRoleIds.length === 0)
319
+ return;
320
+ const activeRoles = this.agentRoleRepo.findAll(false).filter(role => role.isActive);
321
+ const mentionedRoles = activeRoles.filter(role => mentionedRoleIds.includes(role.id));
322
+ if (mentionedRoles.length === 0)
323
+ return;
324
+ const existingChildren = this.taskRepo.findByParent(taskId);
325
+ const assignedRoleIds = new Set(existingChildren
326
+ .map(child => child.assignedAgentRoleId)
327
+ .filter((id) => typeof id === 'string' && id.length > 0));
328
+ const rolesToDispatch = mentionedRoles.filter(role => !assignedRoleIds.has(role.id));
329
+ if (rolesToDispatch.length === 0)
330
+ return;
331
+ const planSummary = this.buildPlanSummary(plan);
332
+ for (const role of rolesToDispatch) {
333
+ const childPrompt = (0, agent_dispatch_1.buildAgentDispatchPrompt)(role, { title: task.title, prompt: task.prompt }, planSummary ? { planSummary } : undefined);
334
+ const childTask = await this.createChildTask({
335
+ title: `@${role.displayName}: ${task.title}`,
336
+ prompt: childPrompt,
337
+ workspaceId: task.workspaceId,
338
+ parentTaskId: task.id,
339
+ agentType: 'sub',
340
+ agentConfig: {
341
+ ...(role.modelKey ? { modelKey: role.modelKey } : {}),
342
+ ...(role.personalityId ? { personalityId: role.personalityId } : {}),
343
+ retainMemory: false,
344
+ },
345
+ });
346
+ this.taskRepo.update(childTask.id, {
347
+ assignedAgentRoleId: role.id,
348
+ boardColumn: 'todo',
349
+ });
350
+ const dispatchActivity = this.activityRepo.create({
351
+ workspaceId: task.workspaceId,
352
+ taskId: task.id,
353
+ agentRoleId: role.id,
354
+ actorType: 'system',
355
+ activityType: 'agent_assigned',
356
+ title: `Dispatched to ${role.displayName}`,
357
+ description: childTask.title,
358
+ });
359
+ this.emitActivityEvent(dispatchActivity);
360
+ const mention = this.mentionRepo.create({
361
+ workspaceId: task.workspaceId,
362
+ taskId: task.id,
363
+ toAgentRoleId: role.id,
364
+ mentionType: 'request',
365
+ context: `New task: ${task.title}`,
366
+ });
367
+ this.emitMentionEvent(mention);
368
+ const mentionActivity = this.activityRepo.create({
369
+ workspaceId: task.workspaceId,
370
+ taskId: task.id,
371
+ agentRoleId: role.id,
372
+ actorType: 'user',
373
+ activityType: 'mention',
374
+ title: `@${role.displayName} mentioned`,
375
+ description: mention.context,
376
+ metadata: { mentionId: mention.id, mentionType: mention.mentionType },
377
+ });
378
+ this.emitActivityEvent(mentionActivity);
379
+ }
380
+ }
253
381
  /**
254
382
  * Cancel a running or queued task
255
383
  */
@@ -257,6 +385,7 @@ class AgentDaemon extends events_1.EventEmitter {
257
385
  // Check if task is queued (not yet started)
258
386
  if (this.queueManager.cancelQueuedTask(taskId)) {
259
387
  this.taskRepo.update(taskId, { status: 'cancelled', completedAt: Date.now() });
388
+ this.clearRetryState(taskId);
260
389
  this.logEvent(taskId, 'task_cancelled', {
261
390
  message: 'Task removed from queue',
262
391
  });
@@ -272,10 +401,54 @@ class AgentDaemon extends events_1.EventEmitter {
272
401
  // (handles orphaned tasks that are in runningTaskIds but have no executor)
273
402
  this.queueManager.onTaskFinished(taskId);
274
403
  // Always emit cancelled event so UI updates
404
+ this.clearRetryState(taskId);
275
405
  this.logEvent(taskId, 'task_cancelled', {
276
406
  message: 'Task was stopped by user',
277
407
  });
278
408
  }
409
+ /**
410
+ * Handle transient provider errors by scheduling a retry instead of failing.
411
+ * Returns true if a retry was scheduled, false if retries are exhausted.
412
+ */
413
+ handleTransientTaskFailure(taskId, reason, delayMs = this.retryDelayMs) {
414
+ const currentCount = this.retryCounts.get(taskId) ?? 0;
415
+ const nextCount = currentCount + 1;
416
+ if (nextCount > this.maxTaskRetries) {
417
+ return false;
418
+ }
419
+ this.retryCounts.set(taskId, nextCount);
420
+ if (this.pendingRetries.has(taskId)) {
421
+ return true;
422
+ }
423
+ // Mark as queued with a helpful message
424
+ this.taskRepo.update(taskId, {
425
+ status: 'queued',
426
+ error: `Transient provider error. Retry ${nextCount}/${this.maxTaskRetries} in ${Math.ceil(delayMs / 1000)}s.`,
427
+ });
428
+ this.logEvent(taskId, 'log', {
429
+ message: `Transient provider error detected. Scheduling retry ${nextCount}/${this.maxTaskRetries} in ${Math.ceil(delayMs / 1000)}s.`,
430
+ reason,
431
+ });
432
+ // Clear executor and free queue slot
433
+ this.activeTasks.delete(taskId);
434
+ this.queueManager.onTaskFinished(taskId);
435
+ const handle = setTimeout(async () => {
436
+ this.pendingRetries.delete(taskId);
437
+ const task = this.taskRepo.findById(taskId);
438
+ if (!task) {
439
+ this.retryCounts.delete(taskId);
440
+ return;
441
+ }
442
+ if (task.status !== 'queued')
443
+ return;
444
+ if (this.activeTasks.has(taskId) || this.queueManager.isRunning(taskId) || this.queueManager.isQueued(taskId)) {
445
+ return;
446
+ }
447
+ await this.startTask(task);
448
+ }, delayMs);
449
+ this.pendingRetries.set(taskId, handle);
450
+ return true;
451
+ }
279
452
  /**
280
453
  * Pause a running task
281
454
  */
@@ -677,19 +850,6 @@ class AgentDaemon extends events_1.EventEmitter {
677
850
  return undefined;
678
851
  }
679
852
  }
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
853
  /**
694
854
  * Register an artifact (file created during task execution)
695
855
  * This allows files like screenshots to be sent back to the user
@@ -753,6 +913,9 @@ class AgentDaemon extends events_1.EventEmitter {
753
913
  */
754
914
  updateTaskStatus(taskId, status) {
755
915
  this.taskRepo.update(taskId, { status });
916
+ if (status === 'completed' || status === 'failed' || status === 'cancelled') {
917
+ this.clearRetryState(taskId);
918
+ }
756
919
  }
757
920
  /**
758
921
  * Get task by ID
@@ -797,6 +960,14 @@ class AgentDaemon extends events_1.EventEmitter {
797
960
  updateTask(taskId, updates) {
798
961
  this.taskRepo.update(taskId, updates);
799
962
  }
963
+ clearRetryState(taskId) {
964
+ const pending = this.pendingRetries.get(taskId);
965
+ if (pending) {
966
+ clearTimeout(pending);
967
+ this.pendingRetries.delete(taskId);
968
+ }
969
+ this.retryCounts.delete(taskId);
970
+ }
800
971
  /**
801
972
  * Mark task as completed
802
973
  * Note: We keep the executor in memory for follow-up messages (with TTL-based cleanup)
@@ -806,6 +977,7 @@ class AgentDaemon extends events_1.EventEmitter {
806
977
  status: 'completed',
807
978
  completedAt: Date.now(),
808
979
  });
980
+ this.clearRetryState(taskId);
809
981
  // Mark executor as completed for TTL-based cleanup
810
982
  const cached = this.activeTasks.get(taskId);
811
983
  if (cached) {
@@ -925,6 +1097,7 @@ class AgentDaemon extends events_1.EventEmitter {
925
1097
  status: 'failed',
926
1098
  error: 'Task timed out - exceeded maximum allowed execution time',
927
1099
  });
1100
+ this.clearRetryState(taskId);
928
1101
  // Emit timeout event
929
1102
  this.logEvent(taskId, 'step_timeout', {
930
1103
  message: 'Task exceeded maximum execution time and was automatically cancelled',