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
@@ -94,18 +94,36 @@ export class SearchTools {
94
94
  });
95
95
 
96
96
  // Use searchWithFallback for automatic fallback support
97
- const response = await SearchProviderFactory.searchWithFallback(searchQuery);
97
+ try {
98
+ const response = await SearchProviderFactory.searchWithFallback(searchQuery);
98
99
 
99
- this.daemon.logEvent(this.taskId, 'tool_result', {
100
- tool: 'web_search',
101
- result: {
102
- query: input.query,
103
- searchType: searchQuery.searchType,
104
- resultCount: response.results.length,
105
- provider: response.provider,
106
- },
107
- });
100
+ this.daemon.logEvent(this.taskId, 'tool_result', {
101
+ tool: 'web_search',
102
+ result: {
103
+ query: input.query,
104
+ searchType: searchQuery.searchType,
105
+ resultCount: response.results.length,
106
+ provider: response.provider,
107
+ },
108
+ });
109
+
110
+ return response;
111
+ } catch (error: any) {
112
+ const message = error?.message || 'Web search failed';
113
+ this.daemon.logEvent(this.taskId, 'tool_result', {
114
+ tool: 'web_search',
115
+ error: message,
116
+ });
108
117
 
109
- return response;
118
+ return {
119
+ query: input.query,
120
+ searchType: input.searchType || 'web',
121
+ results: [],
122
+ provider: (input.provider || settings.primaryProvider || 'none') as SearchProviderType | 'none',
123
+ metadata: {
124
+ error: message,
125
+ },
126
+ };
127
+ }
110
128
  }
111
129
  }
@@ -0,0 +1,247 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { Workspace } from '../../../shared/types';
4
+ import { AgentDaemon } from '../daemon';
5
+ import { SharePointSettingsManager } from '../../settings/sharepoint-manager';
6
+ import { sharepointRequest } from '../../utils/sharepoint-api';
7
+
8
+ type SharePointAction =
9
+ | 'get_current_user'
10
+ | 'search_sites'
11
+ | 'get_site'
12
+ | 'list_site_drives'
13
+ | 'list_drive_items'
14
+ | 'get_item'
15
+ | 'create_folder'
16
+ | 'upload_file'
17
+ | 'delete_item';
18
+
19
+ interface SharePointActionInput {
20
+ action: SharePointAction;
21
+ site_id?: string;
22
+ drive_id?: string;
23
+ item_id?: string;
24
+ query?: string;
25
+ parent_id?: string;
26
+ name?: string;
27
+ conflict_behavior?: 'rename' | 'fail' | 'replace';
28
+ file_path?: string;
29
+ remote_path?: string;
30
+ }
31
+
32
+ export class SharePointTools {
33
+ constructor(
34
+ private workspace: Workspace,
35
+ private daemon: AgentDaemon,
36
+ private taskId: string
37
+ ) {}
38
+
39
+ setWorkspace(workspace: Workspace): void {
40
+ this.workspace = workspace;
41
+ }
42
+
43
+ static isEnabled(): boolean {
44
+ return SharePointSettingsManager.loadSettings().enabled;
45
+ }
46
+
47
+ private async requireApproval(summary: string, details: Record<string, unknown>): Promise<void> {
48
+ const approved = await this.daemon.requestApproval(
49
+ this.taskId,
50
+ 'external_service',
51
+ summary,
52
+ details
53
+ );
54
+
55
+ if (!approved) {
56
+ throw new Error('User denied SharePoint action');
57
+ }
58
+ }
59
+
60
+ private resolveFilePath(inputPath: string): string {
61
+ if (!this.workspace.permissions.read) {
62
+ throw new Error('Read permission not granted for uploads');
63
+ }
64
+
65
+ const workspaceRoot = path.resolve(this.workspace.path);
66
+ const allowedPaths = this.workspace.permissions.allowedPaths || [];
67
+ const canReadOutside = this.workspace.isTemp || this.workspace.permissions.unrestrictedFileAccess;
68
+
69
+ const isPathAllowed = (absolutePath: string): boolean => {
70
+ if (allowedPaths.length === 0) return false;
71
+ const normalizedPath = path.normalize(absolutePath);
72
+ return allowedPaths.some((allowed) => {
73
+ const normalizedAllowed = path.normalize(allowed);
74
+ return normalizedPath === normalizedAllowed || normalizedPath.startsWith(normalizedAllowed + path.sep);
75
+ });
76
+ };
77
+
78
+ const candidate = path.isAbsolute(inputPath)
79
+ ? path.normalize(inputPath)
80
+ : path.resolve(workspaceRoot, inputPath);
81
+
82
+ const relative = path.relative(workspaceRoot, candidate);
83
+ const isInsideWorkspace = !(relative.startsWith('..') || path.isAbsolute(relative));
84
+ if (!isInsideWorkspace && !canReadOutside && !isPathAllowed(candidate)) {
85
+ throw new Error('File path must be inside the workspace or in Allowed Paths');
86
+ }
87
+ if (!fs.existsSync(candidate)) {
88
+ throw new Error(`File not found: ${inputPath}`);
89
+ }
90
+ const stats = fs.statSync(candidate);
91
+ if (!stats.isFile()) {
92
+ throw new Error(`Path is not a file: ${inputPath}`);
93
+ }
94
+ return candidate;
95
+ }
96
+
97
+ private getSiteId(inputSiteId?: string): string {
98
+ const settings = SharePointSettingsManager.loadSettings();
99
+ const siteId = inputSiteId || settings.siteId;
100
+ if (!siteId) {
101
+ throw new Error('Missing site_id. Provide it in settings or the tool input.');
102
+ }
103
+ return siteId;
104
+ }
105
+
106
+ private getDriveId(inputDriveId?: string): string {
107
+ const settings = SharePointSettingsManager.loadSettings();
108
+ const driveId = inputDriveId || settings.driveId;
109
+ if (!driveId) {
110
+ throw new Error('Missing drive_id. Provide it in settings or the tool input.');
111
+ }
112
+ return driveId;
113
+ }
114
+
115
+ async executeAction(input: SharePointActionInput): Promise<any> {
116
+ const settings = SharePointSettingsManager.loadSettings();
117
+ if (!settings.enabled) {
118
+ throw new Error('SharePoint integration is disabled. Enable it in Settings > Integrations > SharePoint.');
119
+ }
120
+
121
+ const action = input.action;
122
+ if (!action) {
123
+ throw new Error('Missing required "action" parameter');
124
+ }
125
+
126
+ let result;
127
+
128
+ switch (action) {
129
+ case 'get_current_user': {
130
+ result = await sharepointRequest(settings, { method: 'GET', path: '/me' });
131
+ break;
132
+ }
133
+ case 'search_sites': {
134
+ if (!input.query) throw new Error('Missing query for search_sites');
135
+ result = await sharepointRequest(settings, {
136
+ method: 'GET',
137
+ path: '/sites',
138
+ query: { search: input.query },
139
+ });
140
+ break;
141
+ }
142
+ case 'get_site': {
143
+ const siteId = this.getSiteId(input.site_id);
144
+ result = await sharepointRequest(settings, { method: 'GET', path: `/sites/${siteId}` });
145
+ break;
146
+ }
147
+ case 'list_site_drives': {
148
+ const siteId = this.getSiteId(input.site_id);
149
+ result = await sharepointRequest(settings, { method: 'GET', path: `/sites/${siteId}/drives` });
150
+ break;
151
+ }
152
+ case 'list_drive_items': {
153
+ const driveId = this.getDriveId(input.drive_id);
154
+ const pathSuffix = input.item_id ? `/items/${input.item_id}/children` : '/root/children';
155
+ result = await sharepointRequest(settings, { method: 'GET', path: `/drives/${driveId}${pathSuffix}` });
156
+ break;
157
+ }
158
+ case 'get_item': {
159
+ if (!input.item_id) throw new Error('Missing item_id for get_item');
160
+ const driveId = this.getDriveId(input.drive_id);
161
+ result = await sharepointRequest(settings, { method: 'GET', path: `/drives/${driveId}/items/${input.item_id}` });
162
+ break;
163
+ }
164
+ case 'create_folder': {
165
+ if (!input.name) throw new Error('Missing name for create_folder');
166
+ const driveId = this.getDriveId(input.drive_id);
167
+ const parentPath = input.parent_id
168
+ ? `/items/${input.parent_id}/children`
169
+ : '/root/children';
170
+ await this.requireApproval('Create a SharePoint folder', {
171
+ action: 'create_folder',
172
+ parent_id: input.parent_id || 'root',
173
+ name: input.name,
174
+ });
175
+ result = await sharepointRequest(settings, {
176
+ method: 'POST',
177
+ path: `/drives/${driveId}${parentPath}`,
178
+ body: {
179
+ name: input.name,
180
+ folder: {},
181
+ '@microsoft.graph.conflictBehavior': input.conflict_behavior || 'rename',
182
+ },
183
+ });
184
+ break;
185
+ }
186
+ case 'upload_file': {
187
+ if (!input.file_path) throw new Error('Missing file_path for upload_file');
188
+ const driveId = this.getDriveId(input.drive_id);
189
+ const resolved = this.resolveFilePath(input.file_path);
190
+ const data = fs.readFileSync(resolved);
191
+ const fileName = input.name || path.basename(resolved);
192
+ let uploadPath: string;
193
+ if (input.remote_path) {
194
+ const cleaned = input.remote_path.replace(/^\/+/, '');
195
+ const encoded = cleaned
196
+ .split('/')
197
+ .map(segment => encodeURIComponent(segment))
198
+ .join('/');
199
+ uploadPath = `/drives/${driveId}/root:/${encoded}:/content`;
200
+ } else if (input.parent_id) {
201
+ uploadPath = `/drives/${driveId}/items/${input.parent_id}:/${encodeURIComponent(fileName)}:/content`;
202
+ } else {
203
+ uploadPath = `/drives/${driveId}/root:/${encodeURIComponent(fileName)}:/content`;
204
+ }
205
+ await this.requireApproval(`Upload file to SharePoint: ${fileName}`, {
206
+ action: 'upload_file',
207
+ destination: input.remote_path || input.parent_id || 'root',
208
+ file: fileName,
209
+ });
210
+ result = await sharepointRequest(settings, {
211
+ method: 'PUT',
212
+ path: uploadPath,
213
+ body: data,
214
+ headers: { 'Content-Type': 'application/octet-stream' },
215
+ });
216
+ break;
217
+ }
218
+ case 'delete_item': {
219
+ if (!input.item_id) throw new Error('Missing item_id for delete_item');
220
+ const driveId = this.getDriveId(input.drive_id);
221
+ await this.requireApproval('Delete a SharePoint item', {
222
+ action: 'delete_item',
223
+ item_id: input.item_id,
224
+ });
225
+ result = await sharepointRequest(settings, { method: 'DELETE', path: `/drives/${driveId}/items/${input.item_id}` });
226
+ break;
227
+ }
228
+ default:
229
+ throw new Error(`Unsupported action: ${action}`);
230
+ }
231
+
232
+ this.daemon.logEvent(this.taskId, 'tool_result', {
233
+ tool: 'sharepoint_action',
234
+ action,
235
+ status: result?.status,
236
+ hasData: result?.data ? true : false,
237
+ });
238
+
239
+ return {
240
+ success: true,
241
+ action,
242
+ status: result?.status,
243
+ data: result?.data,
244
+ raw: result?.raw,
245
+ };
246
+ }
247
+ }
@@ -2,6 +2,7 @@ import { spawn, ChildProcess, execSync } from 'child_process';
2
2
  import { Workspace, CommandTerminationReason } from '../../../shared/types';
3
3
  import { AgentDaemon } from '../daemon';
4
4
  import { GuardrailManager } from '../../guardrails/guardrail-manager';
5
+ import { BuiltinToolsSettingsManager } from './builtin-settings';
5
6
 
6
7
  // Limits to prevent runaway commands
7
8
  const MAX_TIMEOUT = 5 * 60 * 1000; // 5 minutes max
@@ -345,9 +346,9 @@ export class ShellTools {
345
346
  }
346
347
 
347
348
  /**
348
- * Execute a shell command (requires user approval)
349
+ * Execute a shell command (requires user approval unless auto-approve is enabled)
349
350
  * Note: We don't check workspace.permissions.shell here because
350
- * shell commands always require explicit user approval via requestApproval()
351
+ * shell commands are gated by approval flow (or auto-approve/trust settings)
351
352
  */
352
353
  async runCommand(
353
354
  command: string,
@@ -376,9 +377,16 @@ export class ShellTools {
376
377
 
377
378
  // Check if command is trusted (auto-approve without user confirmation)
378
379
  const trustCheck = GuardrailManager.isCommandTrusted(command);
380
+ const autoApproveEnabled = BuiltinToolsSettingsManager.getToolAutoApprove('run_command');
379
381
  let approved = false;
380
382
 
381
- if (trustCheck.trusted) {
383
+ if (autoApproveEnabled && this.isAutoApprovalSafe(command)) {
384
+ approved = true;
385
+ this.daemon.logEvent(this.taskId, 'log', {
386
+ message: 'Auto-approved command (user setting enabled)',
387
+ command,
388
+ });
389
+ } else if (trustCheck.trusted) {
382
390
  // Auto-approve trusted commands
383
391
  approved = true;
384
392
  this.daemon.logEvent(this.taskId, 'log', {
@@ -119,7 +119,7 @@ export class XTools {
119
119
  async executeAction(input: XActionInput): Promise<any> {
120
120
  const settings = XSettingsManager.loadSettings();
121
121
  if (!settings.enabled) {
122
- throw new Error('X integration is disabled. Enable it in Settings > More Channels > X.');
122
+ throw new Error('X integration is disabled. Enable it in Settings > X (Twitter).');
123
123
  }
124
124
 
125
125
  const action = input.action;
@@ -0,0 +1,79 @@
1
+ export type DispatchRole = {
2
+ displayName: string;
3
+ description?: string | null;
4
+ capabilities?: string[];
5
+ systemPrompt?: string | null;
6
+ soul?: string | null;
7
+ };
8
+
9
+ export type DispatchParentTask = {
10
+ title: string;
11
+ prompt: string;
12
+ };
13
+
14
+ export type DispatchPromptOptions = {
15
+ planSummary?: string;
16
+ };
17
+
18
+ const buildSoulSummary = (soul?: string): string | null => {
19
+ if (!soul) return null;
20
+ try {
21
+ const parsed = JSON.parse(soul) as Record<string, unknown>;
22
+ const parts: string[] = [];
23
+ if (typeof parsed.name === 'string') parts.push(`Name: ${parsed.name}`);
24
+ if (typeof parsed.role === 'string') parts.push(`Role: ${parsed.role}`);
25
+ if (typeof parsed.personality === 'string') parts.push(`Personality: ${parsed.personality}`);
26
+ if (typeof parsed.communicationStyle === 'string') parts.push(`Style: ${parsed.communicationStyle}`);
27
+ if (Array.isArray(parsed.focusAreas)) parts.push(`Focus: ${parsed.focusAreas.join(', ')}`);
28
+ if (Array.isArray(parsed.strengths)) parts.push(`Strengths: ${parsed.strengths.join(', ')}`);
29
+ if (parts.length === 0) {
30
+ return null;
31
+ }
32
+ return parts.join('\n');
33
+ } catch {
34
+ return soul;
35
+ }
36
+ };
37
+
38
+ export const buildAgentDispatchPrompt = (
39
+ role: DispatchRole,
40
+ parentTask: DispatchParentTask,
41
+ options?: DispatchPromptOptions
42
+ ): string => {
43
+ const lines: string[] = [
44
+ `You are ${role.displayName}${role.description ? ` — ${role.description}` : ''}.`,
45
+ ];
46
+
47
+ if (role.capabilities && role.capabilities.length > 0) {
48
+ lines.push(`Capabilities: ${role.capabilities.join(', ')}`);
49
+ }
50
+
51
+ if (role.systemPrompt) {
52
+ lines.push('System guidance:');
53
+ lines.push(role.systemPrompt);
54
+ }
55
+
56
+ const soulSummary = buildSoulSummary(role.soul || undefined);
57
+ if (soulSummary) {
58
+ lines.push('Role notes:');
59
+ lines.push(soulSummary);
60
+ }
61
+
62
+ if (options?.planSummary) {
63
+ lines.push('');
64
+ lines.push('Main agent plan summary (context only):');
65
+ lines.push(options.planSummary);
66
+ }
67
+
68
+ lines.push('');
69
+ lines.push(`Parent task: ${parentTask.title}`);
70
+ lines.push('Request:');
71
+ lines.push(parentTask.prompt);
72
+ lines.push('');
73
+ lines.push('Deliverables:');
74
+ lines.push('- Provide a concise summary of your findings.');
75
+ lines.push('- Call out risks or open questions.');
76
+ lines.push('- Recommend next steps.');
77
+
78
+ return lines.join('\n');
79
+ };
@@ -49,7 +49,13 @@ export type SettingsCategory =
49
49
  | 'claude-auth'
50
50
  | 'queue'
51
51
  | 'tray'
52
- | 'x';
52
+ | 'x'
53
+ | 'notion'
54
+ | 'box'
55
+ | 'onedrive'
56
+ | 'google-drive'
57
+ | 'dropbox'
58
+ | 'sharepoint';
53
59
 
54
60
  interface SecureSettingsRow {
55
61
  id: string;
@@ -27,17 +27,19 @@ export class WorkspaceRepository {
27
27
  constructor(private db: Database.Database) {}
28
28
 
29
29
  create(name: string, path: string, permissions: WorkspacePermissions): Workspace {
30
+ const now = Date.now();
30
31
  const workspace: Workspace = {
31
32
  id: uuidv4(),
32
33
  name,
33
34
  path,
34
- createdAt: Date.now(),
35
+ createdAt: now,
36
+ lastUsedAt: now,
35
37
  permissions,
36
38
  };
37
39
 
38
40
  const stmt = this.db.prepare(`
39
- INSERT INTO workspaces (id, name, path, created_at, permissions)
40
- VALUES (?, ?, ?, ?, ?)
41
+ INSERT INTO workspaces (id, name, path, created_at, last_used_at, permissions)
42
+ VALUES (?, ?, ?, ?, ?, ?)
41
43
  `);
42
44
 
43
45
  stmt.run(
@@ -45,6 +47,7 @@ export class WorkspaceRepository {
45
47
  workspace.name,
46
48
  workspace.path,
47
49
  workspace.createdAt,
50
+ workspace.lastUsedAt,
48
51
  JSON.stringify(workspace.permissions)
49
52
  );
50
53
 
@@ -58,7 +61,11 @@ export class WorkspaceRepository {
58
61
  }
59
62
 
60
63
  findAll(): Workspace[] {
61
- const stmt = this.db.prepare('SELECT * FROM workspaces ORDER BY created_at DESC');
64
+ const stmt = this.db.prepare(`
65
+ SELECT *
66
+ FROM workspaces
67
+ ORDER BY COALESCE(last_used_at, created_at) DESC
68
+ `);
62
69
  const rows = stmt.all() as any[];
63
70
  return rows.map(row => this.mapRowToWorkspace(row));
64
71
  }
@@ -89,6 +96,14 @@ export class WorkspaceRepository {
89
96
  stmt.run(JSON.stringify(permissions), id);
90
97
  }
91
98
 
99
+ /**
100
+ * Update last used timestamp for recency ordering
101
+ */
102
+ updateLastUsedAt(id: string, lastUsedAt: number = Date.now()): void {
103
+ const stmt = this.db.prepare('UPDATE workspaces SET last_used_at = ? WHERE id = ?');
104
+ stmt.run(lastUsedAt, id);
105
+ }
106
+
92
107
  /**
93
108
  * Delete a workspace by ID
94
109
  */
@@ -120,6 +135,7 @@ export class WorkspaceRepository {
120
135
  name: row.name,
121
136
  path: row.path,
122
137
  createdAt: row.created_at,
138
+ lastUsedAt: row.last_used_at ?? undefined,
123
139
  permissions: mergedPermissions,
124
140
  };
125
141
  }
@@ -1054,13 +1070,49 @@ export class ChannelUserRepository {
1054
1070
  WHERE channel_id = ?
1055
1071
  AND allowed = 0
1056
1072
  AND channel_user_id LIKE 'pending_%'
1057
- AND pairing_expires_at IS NOT NULL
1058
- AND pairing_expires_at < ?
1073
+ AND (
1074
+ pairing_expires_at IS NULL
1075
+ OR pairing_code IS NULL
1076
+ OR pairing_expires_at < ?
1077
+ )
1059
1078
  `);
1060
1079
  const result = stmt.run(channelId, now);
1061
1080
  return result.changes;
1062
1081
  }
1063
1082
 
1083
+ /**
1084
+ * Delete all pending pairing entries for a channel (valid or expired).
1085
+ */
1086
+ deletePendingByChannel(channelId: string): number {
1087
+ const stmt = this.db.prepare(`
1088
+ DELETE FROM channel_users
1089
+ WHERE channel_id = ?
1090
+ AND allowed = 0
1091
+ AND channel_user_id LIKE 'pending_%'
1092
+ `);
1093
+ const result = stmt.run(channelId);
1094
+ return result.changes;
1095
+ }
1096
+
1097
+ /**
1098
+ * Delete expired or empty pending pairing entries across all channels.
1099
+ */
1100
+ deleteExpiredPendingAll(): number {
1101
+ const now = Date.now();
1102
+ const stmt = this.db.prepare(`
1103
+ DELETE FROM channel_users
1104
+ WHERE allowed = 0
1105
+ AND channel_user_id LIKE 'pending_%'
1106
+ AND (
1107
+ pairing_expires_at IS NULL
1108
+ OR pairing_code IS NULL
1109
+ OR pairing_expires_at < ?
1110
+ )
1111
+ `);
1112
+ const result = stmt.run(now);
1113
+ return result.changes;
1114
+ }
1115
+
1064
1116
  findByPairingCode(channelId: string, pairingCode: string): ChannelUser | undefined {
1065
1117
  const stmt = this.db.prepare('SELECT * FROM channel_users WHERE channel_id = ? AND UPPER(pairing_code) = UPPER(?)');
1066
1118
  const row = stmt.get(channelId, pairingCode) as Record<string, unknown> | undefined;
@@ -234,6 +234,7 @@ export class DatabaseManager {
234
234
  name TEXT NOT NULL,
235
235
  path TEXT NOT NULL UNIQUE,
236
236
  created_at INTEGER NOT NULL,
237
+ last_used_at INTEGER,
237
238
  permissions TEXT NOT NULL
238
239
  );
239
240
 
@@ -592,6 +593,13 @@ export class DatabaseManager {
592
593
  }
593
594
  }
594
595
 
596
+ // Migration: Add last_used_at to workspaces for recency ordering
597
+ try {
598
+ this.db.exec('ALTER TABLE workspaces ADD COLUMN last_used_at INTEGER');
599
+ } catch {
600
+ // Column already exists, ignore
601
+ }
602
+
595
603
  // Migration: Add Sub-Agent / Parallel Agent columns to tasks table
596
604
  const subAgentColumns = [
597
605
  'ALTER TABLE tasks ADD COLUMN parent_task_id TEXT REFERENCES tasks(id)',
@@ -1014,6 +1014,7 @@ export class DiscordAdapter implements ChannelAdapter {
1014
1014
  // Check for thread context
1015
1015
  const isThread = message.channel.isThread();
1016
1016
  const threadId = isThread ? message.channelId : undefined;
1017
+ const isGroup = message.channel.type !== DiscordChannelType.DM;
1017
1018
 
1018
1019
  return {
1019
1020
  messageId: message.id,
@@ -1021,6 +1022,7 @@ export class DiscordAdapter implements ChannelAdapter {
1021
1022
  userId: message.author.id,
1022
1023
  userName: message.author.displayName || message.author.username,
1023
1024
  chatId: isThread ? (message.channel as ThreadChannel).parentId! : message.channelId,
1025
+ isGroup,
1024
1026
  text: commandText || text,
1025
1027
  timestamp: message.createdAt,
1026
1028
  replyTo: message.reference?.messageId,
@@ -1073,6 +1075,7 @@ export class DiscordAdapter implements ChannelAdapter {
1073
1075
 
1074
1076
  // Check for thread context
1075
1077
  const isThread = interaction.channel?.isThread() ?? false;
1078
+ const isGroup = Boolean(interaction.guildId);
1076
1079
 
1077
1080
  return {
1078
1081
  messageId: interaction.id,
@@ -1080,6 +1083,7 @@ export class DiscordAdapter implements ChannelAdapter {
1080
1083
  userId: interaction.user.id,
1081
1084
  userName: interaction.user.displayName || interaction.user.username,
1082
1085
  chatId: interaction.channelId!,
1086
+ isGroup,
1083
1087
  text,
1084
1088
  timestamp: new Date(interaction.createdTimestamp),
1085
1089
  threadId: isThread ? interaction.channelId! : undefined,
@@ -464,6 +464,8 @@ export class GoogleChatAdapter implements ChannelAdapter {
464
464
  const userName = message.sender?.displayName || 'Unknown User';
465
465
  const userId = message.sender?.name || '';
466
466
 
467
+ const isGroup = message.space.type !== 'DM';
468
+
467
469
  // Map to IncomingMessage format
468
470
  const incomingMessage: IncomingMessage = {
469
471
  messageId: messageId,
@@ -471,6 +473,7 @@ export class GoogleChatAdapter implements ChannelAdapter {
471
473
  userId: userId,
472
474
  userName: userName,
473
475
  chatId: spaceId,
476
+ isGroup,
474
477
  text: text,
475
478
  timestamp: message.createTime ? new Date(message.createTime) : new Date(),
476
479
  threadId: message.thread?.name,
@@ -451,6 +451,8 @@ export class LineAdapter implements ChannelAdapter {
451
451
  });
452
452
  }
453
453
 
454
+ const isGroup = lineMessage.source.type !== 'user';
455
+
454
456
  // Convert to IncomingMessage
455
457
  const message: IncomingMessage = {
456
458
  messageId: lineMessage.id,
@@ -458,6 +460,7 @@ export class LineAdapter implements ChannelAdapter {
458
460
  userId: lineMessage.source.userId || '',
459
461
  userName,
460
462
  chatId,
463
+ isGroup,
461
464
  text: lineMessage.text,
462
465
  timestamp: lineMessage.timestamp,
463
466
  raw: lineMessage,
@@ -191,6 +191,21 @@ export class MatrixClient extends EventEmitter {
191
191
  return response.joined_rooms;
192
192
  }
193
193
 
194
+ /**
195
+ * Get direct (1:1) room IDs from account data
196
+ */
197
+ async getDirectRooms(): Promise<string[]> {
198
+ try {
199
+ const response = await this.apiRequest<Record<string, string[]>>(
200
+ 'GET',
201
+ `/_matrix/client/v3/user/${encodeURIComponent(this.options.userId)}/account_data/m.direct`
202
+ );
203
+ return Object.values(response || {}).flat().filter(Boolean);
204
+ } catch {
205
+ return [];
206
+ }
207
+ }
208
+
194
209
  /**
195
210
  * Start syncing (receiving messages)
196
211
  */