cowork-os 0.3.21 → 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.
Files changed (252) hide show
  1. package/README.md +372 -10
  2. package/connectors/README.md +20 -0
  3. package/connectors/asana-mcp/README.md +24 -0
  4. package/connectors/asana-mcp/dist/index.js +427 -0
  5. package/connectors/asana-mcp/package.json +15 -0
  6. package/connectors/asana-mcp/src/index.ts +553 -0
  7. package/connectors/asana-mcp/tsconfig.json +13 -0
  8. package/connectors/hubspot-mcp/README.md +35 -0
  9. package/connectors/hubspot-mcp/dist/index.js +454 -0
  10. package/connectors/hubspot-mcp/package.json +15 -0
  11. package/connectors/hubspot-mcp/src/index.ts +562 -0
  12. package/connectors/hubspot-mcp/tsconfig.json +13 -0
  13. package/connectors/jira-mcp/README.md +49 -0
  14. package/connectors/jira-mcp/dist/index.js +588 -0
  15. package/connectors/jira-mcp/package.json +15 -0
  16. package/connectors/jira-mcp/src/index.ts +711 -0
  17. package/connectors/jira-mcp/tsconfig.json +13 -0
  18. package/connectors/linear-mcp/README.md +22 -0
  19. package/connectors/linear-mcp/dist/index.js +402 -0
  20. package/connectors/linear-mcp/package.json +15 -0
  21. package/connectors/linear-mcp/src/index.ts +522 -0
  22. package/connectors/linear-mcp/tsconfig.json +13 -0
  23. package/connectors/okta-mcp/README.md +24 -0
  24. package/connectors/okta-mcp/dist/index.js +411 -0
  25. package/connectors/okta-mcp/package.json +15 -0
  26. package/connectors/okta-mcp/src/index.ts +520 -0
  27. package/connectors/okta-mcp/tsconfig.json +13 -0
  28. package/connectors/salesforce-mcp/README.md +47 -0
  29. package/connectors/salesforce-mcp/dist/index.js +584 -0
  30. package/connectors/salesforce-mcp/package.json +15 -0
  31. package/connectors/salesforce-mcp/src/index.ts +722 -0
  32. package/connectors/salesforce-mcp/tsconfig.json +13 -0
  33. package/connectors/servicenow-mcp/README.md +26 -0
  34. package/connectors/servicenow-mcp/dist/index.js +400 -0
  35. package/connectors/servicenow-mcp/package.json +15 -0
  36. package/connectors/servicenow-mcp/src/index.ts +500 -0
  37. package/connectors/servicenow-mcp/tsconfig.json +13 -0
  38. package/connectors/templates/mcp-connector/README.md +31 -0
  39. package/connectors/templates/mcp-connector/package.json +15 -0
  40. package/connectors/templates/mcp-connector/src/index.ts +330 -0
  41. package/connectors/templates/mcp-connector/tsconfig.json +13 -0
  42. package/connectors/zendesk-mcp/README.md +40 -0
  43. package/connectors/zendesk-mcp/dist/index.js +431 -0
  44. package/connectors/zendesk-mcp/package.json +15 -0
  45. package/connectors/zendesk-mcp/src/index.ts +543 -0
  46. package/connectors/zendesk-mcp/tsconfig.json +13 -0
  47. package/dist/electron/electron/agent/custom-skill-loader.js +31 -1
  48. package/dist/electron/electron/agent/daemon.js +189 -13
  49. package/dist/electron/electron/agent/executor.js +895 -78
  50. package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
  51. package/dist/electron/electron/agent/llm/azure-openai-provider.js +328 -0
  52. package/dist/electron/electron/agent/llm/bedrock-provider.js +49 -9
  53. package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
  54. package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
  55. package/dist/electron/electron/agent/llm/index.js +13 -1
  56. package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
  57. package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
  58. package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
  59. package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
  60. package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
  61. package/dist/electron/electron/agent/llm/provider-factory.js +350 -4
  62. package/dist/electron/electron/agent/llm/types.js +66 -1
  63. package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
  64. package/dist/electron/electron/agent/search/provider-factory.js +38 -2
  65. package/dist/electron/electron/agent/tools/box-tools.js +231 -0
  66. package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
  67. package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
  68. package/dist/electron/electron/agent/tools/file-tools.js +66 -3
  69. package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
  70. package/dist/electron/electron/agent/tools/grep-tools.js +90 -10
  71. package/dist/electron/electron/agent/tools/image-tools.js +11 -1
  72. package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
  73. package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
  74. package/dist/electron/electron/agent/tools/registry.js +548 -10
  75. package/dist/electron/electron/agent/tools/search-tools.js +28 -10
  76. package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
  77. package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
  78. package/dist/electron/electron/agent/tools/x-tools.js +1 -1
  79. package/dist/electron/electron/agents/agent-dispatch.js +63 -0
  80. package/dist/electron/electron/database/repositories.js +19 -5
  81. package/dist/electron/electron/database/schema.js +8 -0
  82. package/dist/electron/electron/gateway/channels/whatsapp.js +55 -0
  83. package/dist/electron/electron/gateway/index.js +75 -1
  84. package/dist/electron/electron/gateway/router.js +209 -154
  85. package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
  86. package/dist/electron/electron/ipc/handlers.js +763 -267
  87. package/dist/electron/electron/main.js +63 -0
  88. package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
  89. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
  90. package/dist/electron/electron/memory/MemoryService.js +2 -1
  91. package/dist/electron/electron/preload.js +78 -1
  92. package/dist/electron/electron/settings/appearance-manager.js +18 -1
  93. package/dist/electron/electron/settings/box-manager.js +54 -0
  94. package/dist/electron/electron/settings/dropbox-manager.js +54 -0
  95. package/dist/electron/electron/settings/google-drive-manager.js +54 -0
  96. package/dist/electron/electron/settings/notion-manager.js +56 -0
  97. package/dist/electron/electron/settings/onedrive-manager.js +54 -0
  98. package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
  99. package/dist/electron/electron/utils/box-api.js +153 -0
  100. package/dist/electron/electron/utils/dropbox-api.js +144 -0
  101. package/dist/electron/electron/utils/env-migration.js +19 -0
  102. package/dist/electron/electron/utils/google-drive-api.js +152 -0
  103. package/dist/electron/electron/utils/notion-api.js +103 -0
  104. package/dist/electron/electron/utils/onedrive-api.js +113 -0
  105. package/dist/electron/electron/utils/sharepoint-api.js +109 -0
  106. package/dist/electron/electron/utils/validation.js +98 -3
  107. package/dist/electron/electron/utils/x-cli.js +1 -1
  108. package/dist/electron/shared/channelMessages.js +284 -3
  109. package/dist/electron/shared/llm-provider-catalog.js +198 -0
  110. package/dist/electron/shared/types.js +90 -1
  111. package/package.json +14 -3
  112. package/resources/skills/nano-banana-pro.json +4 -4
  113. package/resources/skills/openai-image-gen.json +3 -3
  114. package/resources/skills/scripts/gen.py +163 -0
  115. package/resources/skills/scripts/generate_image.py +91 -0
  116. package/src/electron/agent/custom-skill-loader.ts +34 -1
  117. package/src/electron/agent/daemon.ts +210 -14
  118. package/src/electron/agent/executor.ts +1124 -85
  119. package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
  120. package/src/electron/agent/llm/azure-openai-provider.ts +388 -0
  121. package/src/electron/agent/llm/bedrock-provider.ts +62 -9
  122. package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
  123. package/src/electron/agent/llm/groq-provider.ts +39 -0
  124. package/src/electron/agent/llm/index.ts +6 -0
  125. package/src/electron/agent/llm/kimi-provider.ts +39 -0
  126. package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
  127. package/src/electron/agent/llm/openai-compatible.ts +133 -0
  128. package/src/electron/agent/llm/openai-oauth.ts +2 -1
  129. package/src/electron/agent/llm/openrouter-provider.ts +2 -1
  130. package/src/electron/agent/llm/provider-factory.ts +459 -6
  131. package/src/electron/agent/llm/types.ts +95 -1
  132. package/src/electron/agent/llm/xai-provider.ts +39 -0
  133. package/src/electron/agent/search/provider-factory.ts +43 -2
  134. package/src/electron/agent/tools/box-tools.ts +239 -0
  135. package/src/electron/agent/tools/builtin-settings.ts +36 -0
  136. package/src/electron/agent/tools/dropbox-tools.ts +237 -0
  137. package/src/electron/agent/tools/file-tools.ts +66 -3
  138. package/src/electron/agent/tools/gmail-tools.ts +240 -0
  139. package/src/electron/agent/tools/google-calendar-tools.ts +258 -0
  140. package/src/electron/agent/tools/google-drive-tools.ts +228 -0
  141. package/src/electron/agent/tools/grep-tools.ts +97 -12
  142. package/src/electron/agent/tools/image-tools.ts +11 -1
  143. package/src/electron/agent/tools/notion-tools.ts +330 -0
  144. package/src/electron/agent/tools/onedrive-tools.ts +217 -0
  145. package/src/electron/agent/tools/registry.ts +794 -10
  146. package/src/electron/agent/tools/search-tools.ts +29 -11
  147. package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
  148. package/src/electron/agent/tools/shell-tools.ts +11 -3
  149. package/src/electron/agent/tools/x-tools.ts +1 -1
  150. package/src/electron/agents/agent-dispatch.ts +79 -0
  151. package/src/electron/database/SecureSettingsRepository.ts +7 -1
  152. package/src/electron/database/repositories.ts +58 -6
  153. package/src/electron/database/schema.ts +8 -0
  154. package/src/electron/gateway/channels/discord.ts +4 -0
  155. package/src/electron/gateway/channels/google-chat.ts +3 -0
  156. package/src/electron/gateway/channels/line.ts +3 -0
  157. package/src/electron/gateway/channels/matrix-client.ts +15 -0
  158. package/src/electron/gateway/channels/matrix.ts +31 -0
  159. package/src/electron/gateway/channels/mattermost.ts +3 -0
  160. package/src/electron/gateway/channels/signal.ts +3 -0
  161. package/src/electron/gateway/channels/slack.ts +9 -4
  162. package/src/electron/gateway/channels/teams.ts +4 -0
  163. package/src/electron/gateway/channels/telegram.ts +2 -0
  164. package/src/electron/gateway/channels/twitch.ts +2 -0
  165. package/src/electron/gateway/channels/types.ts +8 -0
  166. package/src/electron/gateway/channels/whatsapp.ts +66 -0
  167. package/src/electron/gateway/index.ts +95 -2
  168. package/src/electron/gateway/router.ts +231 -161
  169. package/src/electron/gateway/security.ts +21 -9
  170. package/src/electron/ipc/canvas-handlers.ts +10 -0
  171. package/src/electron/ipc/handlers.ts +848 -292
  172. package/src/electron/main.ts +35 -0
  173. package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
  174. package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
  175. package/src/electron/memory/MemoryService.ts +7 -1
  176. package/src/electron/preload.ts +200 -5
  177. package/src/electron/settings/appearance-manager.ts +20 -2
  178. package/src/electron/settings/box-manager.ts +58 -0
  179. package/src/electron/settings/dropbox-manager.ts +58 -0
  180. package/src/electron/settings/google-workspace-manager.ts +59 -0
  181. package/src/electron/settings/notion-manager.ts +60 -0
  182. package/src/electron/settings/onedrive-manager.ts +58 -0
  183. package/src/electron/settings/sharepoint-manager.ts +58 -0
  184. package/src/electron/utils/box-api.ts +184 -0
  185. package/src/electron/utils/dropbox-api.ts +171 -0
  186. package/src/electron/utils/env-migration.ts +22 -0
  187. package/src/electron/utils/gmail-api.ts +121 -0
  188. package/src/electron/utils/google-calendar-api.ts +115 -0
  189. package/src/electron/utils/google-workspace-api.ts +228 -0
  190. package/src/electron/utils/google-workspace-auth.ts +109 -0
  191. package/src/electron/utils/google-workspace-oauth.ts +232 -0
  192. package/src/electron/utils/notion-api.ts +126 -0
  193. package/src/electron/utils/onedrive-api.ts +137 -0
  194. package/src/electron/utils/sharepoint-api.ts +132 -0
  195. package/src/electron/utils/validation.ts +128 -1
  196. package/src/electron/utils/x-cli.ts +1 -1
  197. package/src/renderer/App.tsx +119 -8
  198. package/src/renderer/components/ActivityFeedItem.tsx +34 -17
  199. package/src/renderer/components/AgentWorkingStatePanel.tsx +7 -5
  200. package/src/renderer/components/AppearanceSettings.tsx +37 -2
  201. package/src/renderer/components/BlueBubblesSettings.tsx +18 -7
  202. package/src/renderer/components/BoxSettings.tsx +203 -0
  203. package/src/renderer/components/BrowserView.tsx +101 -0
  204. package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
  205. package/src/renderer/components/CanvasPreview.tsx +68 -1
  206. package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
  207. package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
  208. package/src/renderer/components/ConnectorsSettings.tsx +397 -0
  209. package/src/renderer/components/ControlPlaneSettings.tsx +2 -0
  210. package/src/renderer/components/DiscordSettings.tsx +18 -7
  211. package/src/renderer/components/DropboxSettings.tsx +202 -0
  212. package/src/renderer/components/EmailSettings.tsx +18 -7
  213. package/src/renderer/components/FileViewer.tsx +21 -13
  214. package/src/renderer/components/GoogleChatSettings.tsx +17 -7
  215. package/src/renderer/components/GoogleWorkspaceSettings.tsx +332 -0
  216. package/src/renderer/components/ImessageSettings.tsx +22 -11
  217. package/src/renderer/components/LineIcons.tsx +376 -0
  218. package/src/renderer/components/LineSettings.tsx +18 -7
  219. package/src/renderer/components/MCPSettings.tsx +56 -0
  220. package/src/renderer/components/MainContent.tsx +740 -76
  221. package/src/renderer/components/MatrixSettings.tsx +18 -7
  222. package/src/renderer/components/MattermostSettings.tsx +18 -7
  223. package/src/renderer/components/NodesSettings.tsx +58 -99
  224. package/src/renderer/components/NotificationPanel.tsx +25 -11
  225. package/src/renderer/components/NotionSettings.tsx +231 -0
  226. package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
  227. package/src/renderer/components/OnboardingModal.tsx +70 -1
  228. package/src/renderer/components/OneDriveSettings.tsx +212 -0
  229. package/src/renderer/components/RightPanel.tsx +141 -28
  230. package/src/renderer/components/ScheduledTasksSettings.tsx +10 -62
  231. package/src/renderer/components/SearchSettings.tsx +118 -114
  232. package/src/renderer/components/Settings.tsx +1425 -651
  233. package/src/renderer/components/SharePointSettings.tsx +224 -0
  234. package/src/renderer/components/Sidebar.tsx +94 -19
  235. package/src/renderer/components/SignalSettings.tsx +18 -7
  236. package/src/renderer/components/SkillHubBrowser.tsx +144 -185
  237. package/src/renderer/components/SlackSettings.tsx +18 -7
  238. package/src/renderer/components/TaskQuickActions.tsx +11 -6
  239. package/src/renderer/components/TaskTimeline.tsx +58 -26
  240. package/src/renderer/components/TeamsSettings.tsx +18 -7
  241. package/src/renderer/components/TelegramSettings.tsx +18 -7
  242. package/src/renderer/components/ThemeIcon.tsx +16 -0
  243. package/src/renderer/components/TwitchSettings.tsx +18 -7
  244. package/src/renderer/components/VoiceSettings.tsx +30 -74
  245. package/src/renderer/components/WhatsAppSettings.tsx +48 -37
  246. package/src/renderer/components/WorkingStateHistory.tsx +7 -5
  247. package/src/renderer/components/WorkspaceSelector.tsx +42 -13
  248. package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
  249. package/src/renderer/styles/index.css +2333 -209
  250. package/src/shared/channelMessages.ts +367 -4
  251. package/src/shared/llm-provider-catalog.ts +217 -0
  252. package/src/shared/types.ts +251 -2
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env python3
2
+ """Simple image generator wrapper (OpenAI Images API, stdlib only)."""
3
+
4
+ import argparse
5
+ import base64
6
+ import datetime
7
+ import json
8
+ import os
9
+ import sys
10
+ import urllib.error
11
+ import urllib.request
12
+ from pathlib import Path
13
+
14
+ OPENAI_URL = "https://api.openai.com/v1/images/generations"
15
+
16
+ SIZE_MAP = {
17
+ "1K": "1024x1024",
18
+ "2K": "1536x1024",
19
+ "4K": "1536x1024",
20
+ }
21
+
22
+
23
+ def _require_api_key() -> str:
24
+ api_key = os.environ.get("OPENAI_API_KEY")
25
+ if not api_key:
26
+ print("ERROR: OPENAI_API_KEY is not set", file=sys.stderr)
27
+ sys.exit(2)
28
+ return api_key
29
+
30
+
31
+ def _post_json(payload: dict) -> dict:
32
+ api_key = _require_api_key()
33
+ data = json.dumps(payload).encode("utf-8")
34
+ req = urllib.request.Request(
35
+ OPENAI_URL,
36
+ data=data,
37
+ headers={
38
+ "Authorization": f"Bearer {api_key}",
39
+ "Content-Type": "application/json",
40
+ },
41
+ method="POST",
42
+ )
43
+ try:
44
+ with urllib.request.urlopen(req) as resp:
45
+ return json.load(resp)
46
+ except urllib.error.HTTPError as err:
47
+ body = err.read().decode("utf-8", "ignore")
48
+ print(f"OpenAI API error: {err.code} {err.reason}\n{body}", file=sys.stderr)
49
+ sys.exit(3)
50
+
51
+
52
+ def main() -> None:
53
+ parser = argparse.ArgumentParser(description="Generate a single image")
54
+ parser.add_argument("--prompt", required=True)
55
+ parser.add_argument("--filename", required=True)
56
+ parser.add_argument("--resolution", default="1K")
57
+ parser.add_argument("-i", dest="inputs", action="append", default=[])
58
+ args = parser.parse_args()
59
+
60
+ if args.inputs:
61
+ print("WARNING: edit/composition inputs are not supported in this build; ignoring -i", file=sys.stderr)
62
+
63
+ size = SIZE_MAP.get(args.resolution.upper(), "1024x1024")
64
+
65
+ payload = {
66
+ "model": "gpt-image-1",
67
+ "prompt": args.prompt,
68
+ "n": 1,
69
+ "size": size,
70
+ "response_format": "b64_json",
71
+ "quality": "high",
72
+ }
73
+
74
+ result = _post_json(payload)
75
+ data = result.get("data", [])
76
+ if not data or not data[0].get("b64_json"):
77
+ print("ERROR: No image data returned", file=sys.stderr)
78
+ sys.exit(4)
79
+
80
+ out_path = Path(args.filename).expanduser()
81
+ out_path.parent.mkdir(parents=True, exist_ok=True)
82
+
83
+ with open(out_path, "wb") as f:
84
+ f.write(base64.b64decode(data[0]["b64_json"]))
85
+
86
+ print(f"MEDIA: {out_path}")
87
+ print(f"Image saved as: {out_path}")
88
+
89
+
90
+ if __name__ == "__main__":
91
+ main()
@@ -390,7 +390,7 @@ export class CustomSkillLoader {
390
390
  skill: CustomSkill,
391
391
  parameterValues: Record<string, string | number | boolean>
392
392
  ): string {
393
- let prompt = skill.prompt;
393
+ let prompt = this.expandBaseDir(skill.prompt, skill);
394
394
 
395
395
  // Replace {{param}} placeholders with values
396
396
  if (skill.parameters) {
@@ -407,6 +407,39 @@ export class CustomSkillLoader {
407
407
  return prompt.trim();
408
408
  }
409
409
 
410
+ /**
411
+ * Expand {baseDir} placeholders to the resolved skill base directory.
412
+ */
413
+ expandBaseDir(prompt: string, skill: CustomSkill): string {
414
+ if (!prompt.includes('{baseDir}')) {
415
+ return prompt;
416
+ }
417
+ const baseDir = this.resolveBaseDir(skill);
418
+ return prompt.replace(/\{baseDir\}/g, baseDir);
419
+ }
420
+
421
+ private resolveBaseDir(skill: CustomSkill): string {
422
+ const fileDir = skill.filePath ? path.dirname(skill.filePath) : this.bundledSkillsDir;
423
+ const candidates = [
424
+ fileDir,
425
+ this.bundledSkillsDir,
426
+ this.managedSkillsDir,
427
+ this.workspaceSkillsDir || '',
428
+ ].filter(Boolean) as string[];
429
+
430
+ for (const dir of candidates) {
431
+ try {
432
+ if (fs.existsSync(path.join(dir, 'scripts'))) {
433
+ return dir;
434
+ }
435
+ } catch {
436
+ // ignore and continue
437
+ }
438
+ }
439
+
440
+ return fileDir;
441
+ }
442
+
410
443
  /**
411
444
  * Get eligible skills (those that meet all requirements)
412
445
  */
@@ -12,7 +12,10 @@ import {
12
12
  MemoryType,
13
13
  } from '../database/repositories';
14
14
  import { ActivityRepository } from '../activity/ActivityRepository';
15
- import { Task, TaskStatus, IPC_CHANNELS, QueueSettings, QueueStatus, Workspace, WorkspacePermissions, AgentConfig, AgentType, ActivityActorType, ActivityType, CreateActivityRequest } from '../../shared/types';
15
+ import { AgentRoleRepository } from '../agents/AgentRoleRepository';
16
+ import { MentionRepository } from '../agents/MentionRepository';
17
+ import { buildAgentDispatchPrompt } from '../agents/agent-dispatch';
18
+ import { Task, TaskStatus, IPC_CHANNELS, QueueSettings, QueueStatus, Workspace, WorkspacePermissions, AgentConfig, AgentType, ActivityActorType, ActivityType, CreateActivityRequest, Plan, BoardColumn, Activity, AgentMention } from '../../shared/types';
16
19
  import { TaskExecutor } from './executor';
17
20
  import { TaskQueueManager } from './queue-manager';
18
21
  import { approvalIdempotency, taskIdempotency, IdempotencyManager } from '../security/concurrency';
@@ -43,12 +46,18 @@ export class AgentDaemon extends EventEmitter {
43
46
  private approvalRepo: ApprovalRepository;
44
47
  private artifactRepo: ArtifactRepository;
45
48
  private activityRepo: ActivityRepository;
49
+ private agentRoleRepo: AgentRoleRepository;
50
+ private mentionRepo: MentionRepository;
46
51
  private activeTasks: Map<string, CachedExecutor> = new Map();
47
52
  private pendingApprovals: Map<string, { taskId: string; resolve: (value: boolean) => void; reject: (reason?: unknown) => void; resolved: boolean; timeoutHandle: ReturnType<typeof setTimeout> }> = new Map();
48
53
  private cleanupIntervalHandle?: ReturnType<typeof setInterval>;
49
54
  private queueManager: TaskQueueManager;
50
55
  // Activity throttle: Map<taskId:eventType, lastTimestamp>
51
56
  private activityThrottle: Map<string, number> = new Map();
57
+ private pendingRetries: Map<string, ReturnType<typeof setTimeout>> = new Map();
58
+ private retryCounts: Map<string, number> = new Map();
59
+ private readonly maxTaskRetries = 2;
60
+ private readonly retryDelayMs = 30 * 1000;
52
61
 
53
62
  constructor(private dbManager: DatabaseManager) {
54
63
  super();
@@ -59,6 +68,8 @@ export class AgentDaemon extends EventEmitter {
59
68
  this.approvalRepo = new ApprovalRepository(db);
60
69
  this.artifactRepo = new ArtifactRepository(db);
61
70
  this.activityRepo = new ActivityRepository(db);
71
+ this.agentRoleRepo = new AgentRoleRepository(db);
72
+ this.mentionRepo = new MentionRepository(db);
62
73
 
63
74
  // Initialize queue manager with callbacks
64
75
  this.queueManager = new TaskQueueManager({
@@ -177,6 +188,7 @@ export class AgentDaemon extends EventEmitter {
177
188
  error: error.message || 'Failed to initialize task executor',
178
189
  completedAt: Date.now(),
179
190
  });
191
+ this.clearRetryState(task.id);
180
192
  this.logEvent(task.id, 'error', { error: error.message });
181
193
  // Notify queue manager so it can start next task
182
194
  this.queueManager.onTaskFinished(task.id);
@@ -202,6 +214,7 @@ export class AgentDaemon extends EventEmitter {
202
214
  error: error.message,
203
215
  completedAt: Date.now(),
204
216
  });
217
+ this.clearRetryState(task.id);
205
218
  this.logEvent(task.id, 'error', { error: error.message });
206
219
  this.activeTasks.delete(task.id);
207
220
  // Notify queue manager so it can start next task
@@ -282,6 +295,136 @@ export class AgentDaemon extends EventEmitter {
282
295
  return task;
283
296
  }
284
297
 
298
+ private buildPlanSummary(plan?: Plan): string | undefined {
299
+ if (!plan) return undefined;
300
+ const lines: string[] = [];
301
+ if (plan.description) {
302
+ lines.push(`Plan: ${plan.description}`);
303
+ }
304
+ if (plan.steps && plan.steps.length > 0) {
305
+ lines.push('Steps:');
306
+ const stepLines = plan.steps
307
+ .slice(0, 7)
308
+ .map((step) => `- ${step.description}`);
309
+ lines.push(...stepLines);
310
+ if (plan.steps.length > 7) {
311
+ lines.push(`- …and ${plan.steps.length - 7} more steps`);
312
+ }
313
+ }
314
+ return lines.length > 0 ? lines.join('\n') : undefined;
315
+ }
316
+
317
+ private emitActivityEvent(activity: Activity): void {
318
+ const windows = BrowserWindow.getAllWindows();
319
+ windows.forEach(window => {
320
+ try {
321
+ if (!window.isDestroyed() && window.webContents && !window.webContents.isDestroyed()) {
322
+ window.webContents.send(IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity });
323
+ }
324
+ } catch (error) {
325
+ console.error('[AgentDaemon] Error sending activity IPC:', error);
326
+ }
327
+ });
328
+ }
329
+
330
+ private emitMentionEvent(mention: AgentMention): void {
331
+ const windows = BrowserWindow.getAllWindows();
332
+ windows.forEach(window => {
333
+ try {
334
+ if (!window.isDestroyed() && window.webContents && !window.webContents.isDestroyed()) {
335
+ window.webContents.send(IPC_CHANNELS.MENTION_EVENT, { type: 'created', mention });
336
+ }
337
+ } catch (error) {
338
+ console.error('[AgentDaemon] Error sending mention IPC:', error);
339
+ }
340
+ });
341
+ }
342
+
343
+ /**
344
+ * Dispatch mentioned agent roles after the main plan is created.
345
+ * This avoids starting sub-agents before the task is clearly defined.
346
+ */
347
+ async dispatchMentionedAgents(taskId: string, plan?: Plan): Promise<void> {
348
+ const task = this.taskRepo.findById(taskId);
349
+ if (!task || task.parentTaskId) return;
350
+
351
+ const mentionedRoleIds = (task.mentionedAgentRoleIds || []).filter(Boolean);
352
+ if (mentionedRoleIds.length === 0) return;
353
+
354
+ const activeRoles = this.agentRoleRepo.findAll(false).filter(role => role.isActive);
355
+ const mentionedRoles = activeRoles.filter(role => mentionedRoleIds.includes(role.id));
356
+ if (mentionedRoles.length === 0) return;
357
+
358
+ const existingChildren = this.taskRepo.findByParent(taskId);
359
+ const assignedRoleIds = new Set(
360
+ existingChildren
361
+ .map(child => child.assignedAgentRoleId)
362
+ .filter((id): id is string => typeof id === 'string' && id.length > 0)
363
+ );
364
+
365
+ const rolesToDispatch = mentionedRoles.filter(role => !assignedRoleIds.has(role.id));
366
+ if (rolesToDispatch.length === 0) return;
367
+
368
+ const planSummary = this.buildPlanSummary(plan);
369
+
370
+ for (const role of rolesToDispatch) {
371
+ const childPrompt = buildAgentDispatchPrompt(
372
+ role,
373
+ { title: task.title, prompt: task.prompt },
374
+ planSummary ? { planSummary } : undefined
375
+ );
376
+ const childTask = await this.createChildTask({
377
+ title: `@${role.displayName}: ${task.title}`,
378
+ prompt: childPrompt,
379
+ workspaceId: task.workspaceId,
380
+ parentTaskId: task.id,
381
+ agentType: 'sub',
382
+ agentConfig: {
383
+ ...(role.modelKey ? { modelKey: role.modelKey } : {}),
384
+ ...(role.personalityId ? { personalityId: role.personalityId } : {}),
385
+ retainMemory: false,
386
+ },
387
+ });
388
+
389
+ this.taskRepo.update(childTask.id, {
390
+ assignedAgentRoleId: role.id,
391
+ boardColumn: 'todo' as BoardColumn,
392
+ });
393
+
394
+ const dispatchActivity = this.activityRepo.create({
395
+ workspaceId: task.workspaceId,
396
+ taskId: task.id,
397
+ agentRoleId: role.id,
398
+ actorType: 'system',
399
+ activityType: 'agent_assigned',
400
+ title: `Dispatched to ${role.displayName}`,
401
+ description: childTask.title,
402
+ });
403
+ this.emitActivityEvent(dispatchActivity);
404
+
405
+ const mention = this.mentionRepo.create({
406
+ workspaceId: task.workspaceId,
407
+ taskId: task.id,
408
+ toAgentRoleId: role.id,
409
+ mentionType: 'request',
410
+ context: `New task: ${task.title}`,
411
+ });
412
+ this.emitMentionEvent(mention);
413
+
414
+ const mentionActivity = this.activityRepo.create({
415
+ workspaceId: task.workspaceId,
416
+ taskId: task.id,
417
+ agentRoleId: role.id,
418
+ actorType: 'user',
419
+ activityType: 'mention',
420
+ title: `@${role.displayName} mentioned`,
421
+ description: mention.context,
422
+ metadata: { mentionId: mention.id, mentionType: mention.mentionType },
423
+ });
424
+ this.emitActivityEvent(mentionActivity);
425
+ }
426
+ }
427
+
285
428
  /**
286
429
  * Cancel a running or queued task
287
430
  */
@@ -289,6 +432,7 @@ export class AgentDaemon extends EventEmitter {
289
432
  // Check if task is queued (not yet started)
290
433
  if (this.queueManager.cancelQueuedTask(taskId)) {
291
434
  this.taskRepo.update(taskId, { status: 'cancelled', completedAt: Date.now() });
435
+ this.clearRetryState(taskId);
292
436
  this.logEvent(taskId, 'task_cancelled', {
293
437
  message: 'Task removed from queue',
294
438
  });
@@ -307,11 +451,62 @@ export class AgentDaemon extends EventEmitter {
307
451
  this.queueManager.onTaskFinished(taskId);
308
452
 
309
453
  // Always emit cancelled event so UI updates
454
+ this.clearRetryState(taskId);
310
455
  this.logEvent(taskId, 'task_cancelled', {
311
456
  message: 'Task was stopped by user',
312
457
  });
313
458
  }
314
459
 
460
+ /**
461
+ * Handle transient provider errors by scheduling a retry instead of failing.
462
+ * Returns true if a retry was scheduled, false if retries are exhausted.
463
+ */
464
+ handleTransientTaskFailure(taskId: string, reason: string, delayMs: number = this.retryDelayMs): boolean {
465
+ const currentCount = this.retryCounts.get(taskId) ?? 0;
466
+ const nextCount = currentCount + 1;
467
+ if (nextCount > this.maxTaskRetries) {
468
+ return false;
469
+ }
470
+
471
+ this.retryCounts.set(taskId, nextCount);
472
+
473
+ if (this.pendingRetries.has(taskId)) {
474
+ return true;
475
+ }
476
+
477
+ // Mark as queued with a helpful message
478
+ this.taskRepo.update(taskId, {
479
+ status: 'queued',
480
+ error: `Transient provider error. Retry ${nextCount}/${this.maxTaskRetries} in ${Math.ceil(delayMs / 1000)}s.`,
481
+ });
482
+
483
+ this.logEvent(taskId, 'log', {
484
+ message: `Transient provider error detected. Scheduling retry ${nextCount}/${this.maxTaskRetries} in ${Math.ceil(delayMs / 1000)}s.`,
485
+ reason,
486
+ });
487
+
488
+ // Clear executor and free queue slot
489
+ this.activeTasks.delete(taskId);
490
+ this.queueManager.onTaskFinished(taskId);
491
+
492
+ const handle = setTimeout(async () => {
493
+ this.pendingRetries.delete(taskId);
494
+ const task = this.taskRepo.findById(taskId);
495
+ if (!task) {
496
+ this.retryCounts.delete(taskId);
497
+ return;
498
+ }
499
+ if (task.status !== 'queued') return;
500
+ if (this.activeTasks.has(taskId) || this.queueManager.isRunning(taskId) || this.queueManager.isQueued(taskId)) {
501
+ return;
502
+ }
503
+ await this.startTask(task);
504
+ }, delayMs);
505
+
506
+ this.pendingRetries.set(taskId, handle);
507
+ return true;
508
+ }
509
+
315
510
  /**
316
511
  * Pause a running task
317
512
  */
@@ -741,19 +936,6 @@ export class AgentDaemon extends EventEmitter {
741
936
  }
742
937
  }
743
938
 
744
- private emitActivityEvent(activity: any): void {
745
- const windows = BrowserWindow.getAllWindows();
746
- windows.forEach(window => {
747
- try {
748
- if (!window.isDestroyed() && window.webContents && !window.webContents.isDestroyed()) {
749
- window.webContents.send(IPC_CHANNELS.ACTIVITY_EVENT, { type: 'created', activity });
750
- }
751
- } catch (error) {
752
- console.error('[AgentDaemon] Error sending activity IPC:', error);
753
- }
754
- });
755
- }
756
-
757
939
  /**
758
940
  * Register an artifact (file created during task execution)
759
941
  * This allows files like screenshots to be sent back to the user
@@ -820,6 +1002,9 @@ export class AgentDaemon extends EventEmitter {
820
1002
  */
821
1003
  updateTaskStatus(taskId: string, status: Task['status']): void {
822
1004
  this.taskRepo.update(taskId, { status });
1005
+ if (status === 'completed' || status === 'failed' || status === 'cancelled') {
1006
+ this.clearRetryState(taskId);
1007
+ }
823
1008
  }
824
1009
 
825
1010
  /**
@@ -871,6 +1056,15 @@ export class AgentDaemon extends EventEmitter {
871
1056
  this.taskRepo.update(taskId, updates);
872
1057
  }
873
1058
 
1059
+ private clearRetryState(taskId: string): void {
1060
+ const pending = this.pendingRetries.get(taskId);
1061
+ if (pending) {
1062
+ clearTimeout(pending);
1063
+ this.pendingRetries.delete(taskId);
1064
+ }
1065
+ this.retryCounts.delete(taskId);
1066
+ }
1067
+
874
1068
  /**
875
1069
  * Mark task as completed
876
1070
  * Note: We keep the executor in memory for follow-up messages (with TTL-based cleanup)
@@ -880,6 +1074,7 @@ export class AgentDaemon extends EventEmitter {
880
1074
  status: 'completed',
881
1075
  completedAt: Date.now(),
882
1076
  });
1077
+ this.clearRetryState(taskId);
883
1078
  // Mark executor as completed for TTL-based cleanup
884
1079
  const cached = this.activeTasks.get(taskId);
885
1080
  if (cached) {
@@ -1014,6 +1209,7 @@ export class AgentDaemon extends EventEmitter {
1014
1209
  status: 'failed',
1015
1210
  error: 'Task timed out - exceeded maximum allowed execution time',
1016
1211
  });
1212
+ this.clearRetryState(taskId);
1017
1213
 
1018
1214
  // Emit timeout event
1019
1215
  this.logEvent(taskId, 'step_timeout', {