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,330 @@
1
+ import { Workspace } from '../../../shared/types';
2
+ import { AgentDaemon } from '../daemon';
3
+ import { NotionSettingsManager } from '../../settings/notion-manager';
4
+ import { notionRequest } from '../../utils/notion-api';
5
+
6
+ type NotionAction =
7
+ | 'search'
8
+ | 'list_users'
9
+ | 'get_user'
10
+ | 'get_page'
11
+ | 'get_page_property'
12
+ | 'get_database'
13
+ | 'get_block'
14
+ | 'get_block_children'
15
+ | 'update_block'
16
+ | 'delete_block'
17
+ | 'create_page'
18
+ | 'update_page'
19
+ | 'append_blocks'
20
+ | 'query_data_source'
21
+ | 'get_data_source'
22
+ | 'create_data_source'
23
+ | 'update_data_source';
24
+
25
+ interface NotionActionInput {
26
+ action: NotionAction;
27
+ query?: string;
28
+ user_id?: string;
29
+ page_id?: string;
30
+ property_id?: string;
31
+ block_id?: string;
32
+ block_type?: string;
33
+ block?: Record<string, any>;
34
+ data_source_id?: string;
35
+ database_id?: string;
36
+ parent_page_id?: string;
37
+ properties?: Record<string, any>;
38
+ children?: any[];
39
+ filter?: any;
40
+ sort?: any;
41
+ sorts?: any[];
42
+ start_cursor?: string;
43
+ page_size?: number;
44
+ archived?: boolean;
45
+ icon?: any;
46
+ cover?: any;
47
+ title?: string;
48
+ is_inline?: boolean;
49
+ payload?: Record<string, any>;
50
+ }
51
+
52
+ export class NotionTools {
53
+ constructor(
54
+ private workspace: Workspace,
55
+ private daemon: AgentDaemon,
56
+ private taskId: string
57
+ ) {}
58
+
59
+ setWorkspace(workspace: Workspace): void {
60
+ this.workspace = workspace;
61
+ }
62
+
63
+ static isEnabled(): boolean {
64
+ return NotionSettingsManager.loadSettings().enabled;
65
+ }
66
+
67
+ private async requireApproval(summary: string, details: Record<string, unknown>): Promise<void> {
68
+ const approved = await this.daemon.requestApproval(
69
+ this.taskId,
70
+ 'external_service',
71
+ summary,
72
+ details
73
+ );
74
+
75
+ if (!approved) {
76
+ throw new Error('User denied Notion action');
77
+ }
78
+ }
79
+
80
+ private buildPagination(input: NotionActionInput): Record<string, any> {
81
+ const body: Record<string, any> = {};
82
+ if (input.start_cursor) body.start_cursor = input.start_cursor;
83
+ if (typeof input.page_size === 'number') body.page_size = input.page_size;
84
+ return body;
85
+ }
86
+
87
+ private buildTitle(title?: string): Array<{ text: { content: string } }> | undefined {
88
+ if (!title) return undefined;
89
+ const trimmed = title.trim();
90
+ if (!trimmed) return undefined;
91
+ return [{ text: { content: trimmed } }];
92
+ }
93
+
94
+ private buildParent(input: NotionActionInput): Record<string, string> {
95
+ if (input.database_id) {
96
+ return { database_id: input.database_id };
97
+ }
98
+ if (input.parent_page_id) {
99
+ return { page_id: input.parent_page_id };
100
+ }
101
+ throw new Error('Missing parent identifier (database_id or parent_page_id)');
102
+ }
103
+
104
+ async executeAction(input: NotionActionInput): Promise<any> {
105
+ const settings = NotionSettingsManager.loadSettings();
106
+ if (!settings.enabled) {
107
+ throw new Error('Notion integration is disabled. Enable it in Settings > Integrations > Notion.');
108
+ }
109
+
110
+ const action = input.action;
111
+ if (!action) {
112
+ throw new Error('Missing required "action" parameter');
113
+ }
114
+
115
+ let result;
116
+
117
+ switch (action) {
118
+ case 'search': {
119
+ const body = input.payload ? { ...input.payload } : {};
120
+ if (!input.payload) {
121
+ if (input.query) body.query = input.query;
122
+ if (input.filter) body.filter = input.filter;
123
+ if (input.sort) body.sort = input.sort;
124
+ else if (input.sorts) body.sort = input.sorts;
125
+ Object.assign(body, this.buildPagination(input));
126
+ }
127
+ result = await notionRequest(settings, { method: 'POST', path: '/search', body });
128
+ break;
129
+ }
130
+ case 'list_users': {
131
+ const params = new URLSearchParams();
132
+ if (input.start_cursor) params.set('start_cursor', input.start_cursor);
133
+ if (typeof input.page_size === 'number') params.set('page_size', String(input.page_size));
134
+ const suffix = params.toString() ? `?${params.toString()}` : '';
135
+ result = await notionRequest(settings, { method: 'GET', path: `/users${suffix}` });
136
+ break;
137
+ }
138
+ case 'get_user': {
139
+ if (!input.user_id) throw new Error('Missing user_id for get_user');
140
+ result = await notionRequest(settings, { method: 'GET', path: `/users/${input.user_id}` });
141
+ break;
142
+ }
143
+ case 'get_page': {
144
+ if (!input.page_id) throw new Error('Missing page_id for get_page');
145
+ result = await notionRequest(settings, { method: 'GET', path: `/pages/${input.page_id}` });
146
+ break;
147
+ }
148
+ case 'get_page_property': {
149
+ if (!input.page_id) throw new Error('Missing page_id for get_page_property');
150
+ if (!input.property_id) throw new Error('Missing property_id for get_page_property');
151
+ const params = new URLSearchParams();
152
+ if (input.start_cursor) params.set('start_cursor', input.start_cursor);
153
+ if (typeof input.page_size === 'number') params.set('page_size', String(input.page_size));
154
+ const suffix = params.toString() ? `?${params.toString()}` : '';
155
+ result = await notionRequest(settings, { method: 'GET', path: `/pages/${input.page_id}/properties/${input.property_id}${suffix}` });
156
+ break;
157
+ }
158
+ case 'get_database': {
159
+ if (!input.database_id) throw new Error('Missing database_id for get_database');
160
+ result = await notionRequest(settings, { method: 'GET', path: `/databases/${input.database_id}` });
161
+ break;
162
+ }
163
+ case 'get_block': {
164
+ if (!input.block_id) throw new Error('Missing block_id for get_block');
165
+ result = await notionRequest(settings, { method: 'GET', path: `/blocks/${input.block_id}` });
166
+ break;
167
+ }
168
+ case 'get_block_children': {
169
+ if (!input.block_id) throw new Error('Missing block_id for get_block_children');
170
+ const params = new URLSearchParams();
171
+ if (input.start_cursor) params.set('start_cursor', input.start_cursor);
172
+ if (typeof input.page_size === 'number') params.set('page_size', String(input.page_size));
173
+ const suffix = params.toString() ? `?${params.toString()}` : '';
174
+ result = await notionRequest(settings, { method: 'GET', path: `/blocks/${input.block_id}/children${suffix}` });
175
+ break;
176
+ }
177
+ case 'update_block': {
178
+ if (!input.block_id) throw new Error('Missing block_id for update_block');
179
+ const body = input.payload ? { ...input.payload } : {};
180
+ if (!input.payload) {
181
+ if (typeof input.archived === 'boolean') body.archived = input.archived;
182
+ if (input.block_type && input.block) {
183
+ body[input.block_type] = input.block;
184
+ }
185
+ if (Object.keys(body).length === 0) {
186
+ throw new Error('Missing update payload (archived or block content)');
187
+ }
188
+ }
189
+ await this.requireApproval('Update a Notion block', {
190
+ action: 'update_block',
191
+ block_id: input.block_id,
192
+ });
193
+ result = await notionRequest(settings, { method: 'PATCH', path: `/blocks/${input.block_id}`, body });
194
+ break;
195
+ }
196
+ case 'delete_block': {
197
+ if (!input.block_id) throw new Error('Missing block_id for delete_block');
198
+ await this.requireApproval('Delete a Notion block', {
199
+ action: 'delete_block',
200
+ block_id: input.block_id,
201
+ });
202
+ result = await notionRequest(settings, { method: 'DELETE', path: `/blocks/${input.block_id}` });
203
+ break;
204
+ }
205
+ case 'get_data_source': {
206
+ if (!input.data_source_id) throw new Error('Missing data_source_id for get_data_source');
207
+ result = await notionRequest(settings, { method: 'GET', path: `/data_sources/${input.data_source_id}` });
208
+ break;
209
+ }
210
+ case 'query_data_source': {
211
+ if (!input.data_source_id) throw new Error('Missing data_source_id for query_data_source');
212
+ const body = input.payload ? { ...input.payload } : {};
213
+ if (!input.payload) {
214
+ if (input.filter) body.filter = input.filter;
215
+ if (input.sorts) body.sorts = input.sorts;
216
+ Object.assign(body, this.buildPagination(input));
217
+ }
218
+ result = await notionRequest(settings, { method: 'POST', path: `/data_sources/${input.data_source_id}/query`, body });
219
+ break;
220
+ }
221
+ case 'create_page': {
222
+ const body = input.payload ? { ...input.payload } : {};
223
+ if (!input.payload) {
224
+ body.parent = this.buildParent(input);
225
+ if (!input.properties) throw new Error('Missing properties for create_page');
226
+ body.properties = input.properties;
227
+ if (input.children) body.children = input.children;
228
+ if (input.icon) body.icon = input.icon;
229
+ if (input.cover) body.cover = input.cover;
230
+ }
231
+ await this.requireApproval('Create a Notion page', {
232
+ action: 'create_page',
233
+ parent: body.parent,
234
+ });
235
+ result = await notionRequest(settings, { method: 'POST', path: '/pages', body });
236
+ break;
237
+ }
238
+ case 'update_page': {
239
+ if (!input.page_id) throw new Error('Missing page_id for update_page');
240
+ const body = input.payload ? { ...input.payload } : {};
241
+ if (!input.payload) {
242
+ if (input.properties) body.properties = input.properties;
243
+ if (typeof input.archived === 'boolean') body.archived = input.archived;
244
+ if (input.icon) body.icon = input.icon;
245
+ if (input.cover) body.cover = input.cover;
246
+ if (Object.keys(body).length === 0) {
247
+ throw new Error('Missing update payload (properties, archived, icon, or cover)');
248
+ }
249
+ }
250
+ await this.requireApproval('Update a Notion page', {
251
+ action: 'update_page',
252
+ page_id: input.page_id,
253
+ });
254
+ result = await notionRequest(settings, { method: 'PATCH', path: `/pages/${input.page_id}`, body });
255
+ break;
256
+ }
257
+ case 'append_blocks': {
258
+ if (!input.block_id) throw new Error('Missing block_id for append_blocks');
259
+ const body = input.payload ? { ...input.payload } : {};
260
+ if (!input.payload) {
261
+ if (!input.children || input.children.length === 0) {
262
+ throw new Error('Missing children for append_blocks');
263
+ }
264
+ body.children = input.children;
265
+ }
266
+ await this.requireApproval('Append blocks in Notion', {
267
+ action: 'append_blocks',
268
+ block_id: input.block_id,
269
+ count: Array.isArray(body.children) ? body.children.length : undefined,
270
+ });
271
+ result = await notionRequest(settings, { method: 'PATCH', path: `/blocks/${input.block_id}/children`, body });
272
+ break;
273
+ }
274
+ case 'create_data_source': {
275
+ const body = input.payload ? { ...input.payload } : {};
276
+ if (!input.payload) {
277
+ if (!input.parent_page_id) throw new Error('Missing parent_page_id for create_data_source');
278
+ if (!input.properties) throw new Error('Missing properties for create_data_source');
279
+ const title = this.buildTitle(input.title);
280
+ if (!title) throw new Error('Missing title for create_data_source');
281
+ body.parent = { page_id: input.parent_page_id };
282
+ body.title = title;
283
+ body.properties = input.properties;
284
+ if (typeof input.is_inline === 'boolean') body.is_inline = input.is_inline;
285
+ }
286
+ await this.requireApproval('Create a Notion data source', {
287
+ action: 'create_data_source',
288
+ parent: body.parent,
289
+ });
290
+ result = await notionRequest(settings, { method: 'POST', path: '/data_sources', body });
291
+ break;
292
+ }
293
+ case 'update_data_source': {
294
+ if (!input.data_source_id) throw new Error('Missing data_source_id for update_data_source');
295
+ const body = input.payload ? { ...input.payload } : {};
296
+ if (!input.payload) {
297
+ if (input.properties) body.properties = input.properties;
298
+ const title = this.buildTitle(input.title);
299
+ if (title) body.title = title;
300
+ if (Object.keys(body).length === 0) {
301
+ throw new Error('Missing update payload (properties or title)');
302
+ }
303
+ }
304
+ await this.requireApproval('Update a Notion data source', {
305
+ action: 'update_data_source',
306
+ data_source_id: input.data_source_id,
307
+ });
308
+ result = await notionRequest(settings, { method: 'PATCH', path: `/data_sources/${input.data_source_id}`, body });
309
+ break;
310
+ }
311
+ default:
312
+ throw new Error(`Unsupported action: ${action}`);
313
+ }
314
+
315
+ this.daemon.logEvent(this.taskId, 'tool_result', {
316
+ tool: 'notion_action',
317
+ action,
318
+ status: result?.status,
319
+ hasData: !!result?.data,
320
+ });
321
+
322
+ return {
323
+ success: true,
324
+ action,
325
+ status: result?.status,
326
+ data: result?.data,
327
+ raw: result?.raw,
328
+ };
329
+ }
330
+ }
@@ -0,0 +1,217 @@
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 { OneDriveSettingsManager } from '../../settings/onedrive-manager';
6
+ import { onedriveRequest } from '../../utils/onedrive-api';
7
+
8
+ type OneDriveAction =
9
+ | 'get_drive'
10
+ | 'search'
11
+ | 'list_children'
12
+ | 'get_item'
13
+ | 'create_folder'
14
+ | 'upload_file'
15
+ | 'delete_item';
16
+
17
+ interface OneDriveActionInput {
18
+ action: OneDriveAction;
19
+ drive_id?: string;
20
+ item_id?: string;
21
+ query?: string;
22
+ parent_id?: string;
23
+ name?: string;
24
+ conflict_behavior?: 'rename' | 'fail' | 'replace';
25
+ file_path?: string;
26
+ remote_path?: string;
27
+ }
28
+
29
+ export class OneDriveTools {
30
+ constructor(
31
+ private workspace: Workspace,
32
+ private daemon: AgentDaemon,
33
+ private taskId: string
34
+ ) {}
35
+
36
+ setWorkspace(workspace: Workspace): void {
37
+ this.workspace = workspace;
38
+ }
39
+
40
+ static isEnabled(): boolean {
41
+ return OneDriveSettingsManager.loadSettings().enabled;
42
+ }
43
+
44
+ private async requireApproval(summary: string, details: Record<string, unknown>): Promise<void> {
45
+ const approved = await this.daemon.requestApproval(
46
+ this.taskId,
47
+ 'external_service',
48
+ summary,
49
+ details
50
+ );
51
+
52
+ if (!approved) {
53
+ throw new Error('User denied OneDrive action');
54
+ }
55
+ }
56
+
57
+ private resolveFilePath(inputPath: string): string {
58
+ if (!this.workspace.permissions.read) {
59
+ throw new Error('Read permission not granted for uploads');
60
+ }
61
+
62
+ const workspaceRoot = path.resolve(this.workspace.path);
63
+ const allowedPaths = this.workspace.permissions.allowedPaths || [];
64
+ const canReadOutside = this.workspace.isTemp || this.workspace.permissions.unrestrictedFileAccess;
65
+
66
+ const isPathAllowed = (absolutePath: string): boolean => {
67
+ if (allowedPaths.length === 0) return false;
68
+ const normalizedPath = path.normalize(absolutePath);
69
+ return allowedPaths.some((allowed) => {
70
+ const normalizedAllowed = path.normalize(allowed);
71
+ return normalizedPath === normalizedAllowed || normalizedPath.startsWith(normalizedAllowed + path.sep);
72
+ });
73
+ };
74
+
75
+ const candidate = path.isAbsolute(inputPath)
76
+ ? path.normalize(inputPath)
77
+ : path.resolve(workspaceRoot, inputPath);
78
+
79
+ const relative = path.relative(workspaceRoot, candidate);
80
+ const isInsideWorkspace = !(relative.startsWith('..') || path.isAbsolute(relative));
81
+ if (!isInsideWorkspace && !canReadOutside && !isPathAllowed(candidate)) {
82
+ throw new Error('File path must be inside the workspace or in Allowed Paths');
83
+ }
84
+ if (!fs.existsSync(candidate)) {
85
+ throw new Error(`File not found: ${inputPath}`);
86
+ }
87
+ const stats = fs.statSync(candidate);
88
+ if (!stats.isFile()) {
89
+ throw new Error(`Path is not a file: ${inputPath}`);
90
+ }
91
+ return candidate;
92
+ }
93
+
94
+ private getDrivePrefix(inputDriveId?: string): string {
95
+ const settingsDriveId = OneDriveSettingsManager.loadSettings().driveId;
96
+ const driveId = inputDriveId || settingsDriveId;
97
+ return driveId ? `/drives/${driveId}` : '/me/drive';
98
+ }
99
+
100
+ async executeAction(input: OneDriveActionInput): Promise<any> {
101
+ const settings = OneDriveSettingsManager.loadSettings();
102
+ if (!settings.enabled) {
103
+ throw new Error('OneDrive integration is disabled. Enable it in Settings > Integrations > OneDrive.');
104
+ }
105
+
106
+ const action = input.action;
107
+ if (!action) {
108
+ throw new Error('Missing required "action" parameter');
109
+ }
110
+
111
+ let result;
112
+ const drivePrefix = this.getDrivePrefix(input.drive_id);
113
+
114
+ switch (action) {
115
+ case 'get_drive': {
116
+ result = await onedriveRequest(settings, { method: 'GET', path: drivePrefix });
117
+ break;
118
+ }
119
+ case 'search': {
120
+ if (!input.query) throw new Error('Missing query for search');
121
+ const escaped = input.query.replace(/'/g, "''");
122
+ result = await onedriveRequest(settings, { method: 'GET', path: `${drivePrefix}/root/search(q='${escaped}')` });
123
+ break;
124
+ }
125
+ case 'list_children': {
126
+ const pathSuffix = input.item_id
127
+ ? `/items/${input.item_id}/children`
128
+ : '/root/children';
129
+ result = await onedriveRequest(settings, { method: 'GET', path: `${drivePrefix}${pathSuffix}` });
130
+ break;
131
+ }
132
+ case 'get_item': {
133
+ if (!input.item_id) throw new Error('Missing item_id for get_item');
134
+ result = await onedriveRequest(settings, { method: 'GET', path: `${drivePrefix}/items/${input.item_id}` });
135
+ break;
136
+ }
137
+ case 'create_folder': {
138
+ if (!input.name) throw new Error('Missing name for create_folder');
139
+ const parentPath = input.parent_id
140
+ ? `/items/${input.parent_id}/children`
141
+ : '/root/children';
142
+ await this.requireApproval('Create a OneDrive folder', {
143
+ action: 'create_folder',
144
+ parent_id: input.parent_id || 'root',
145
+ name: input.name,
146
+ });
147
+ result = await onedriveRequest(settings, {
148
+ method: 'POST',
149
+ path: `${drivePrefix}${parentPath}`,
150
+ body: {
151
+ name: input.name,
152
+ folder: {},
153
+ '@microsoft.graph.conflictBehavior': input.conflict_behavior || 'rename',
154
+ },
155
+ });
156
+ break;
157
+ }
158
+ case 'upload_file': {
159
+ if (!input.file_path) throw new Error('Missing file_path for upload_file');
160
+ const resolved = this.resolveFilePath(input.file_path);
161
+ const data = fs.readFileSync(resolved);
162
+ const fileName = input.name || path.basename(resolved);
163
+ let uploadPath: string;
164
+ if (input.remote_path) {
165
+ const cleaned = input.remote_path.replace(/^\/+/, '');
166
+ const encoded = cleaned
167
+ .split('/')
168
+ .map(segment => encodeURIComponent(segment))
169
+ .join('/');
170
+ uploadPath = `${drivePrefix}/root:/${encoded}:/content`;
171
+ } else if (input.parent_id) {
172
+ uploadPath = `${drivePrefix}/items/${input.parent_id}:/${encodeURIComponent(fileName)}:/content`;
173
+ } else {
174
+ uploadPath = `${drivePrefix}/root:/${encodeURIComponent(fileName)}:/content`;
175
+ }
176
+ await this.requireApproval(`Upload file to OneDrive: ${fileName}`, {
177
+ action: 'upload_file',
178
+ destination: input.remote_path || input.parent_id || 'root',
179
+ file: fileName,
180
+ });
181
+ result = await onedriveRequest(settings, {
182
+ method: 'PUT',
183
+ path: uploadPath,
184
+ body: data,
185
+ headers: { 'Content-Type': 'application/octet-stream' },
186
+ });
187
+ break;
188
+ }
189
+ case 'delete_item': {
190
+ if (!input.item_id) throw new Error('Missing item_id for delete_item');
191
+ await this.requireApproval('Delete a OneDrive item', {
192
+ action: 'delete_item',
193
+ item_id: input.item_id,
194
+ });
195
+ result = await onedriveRequest(settings, { method: 'DELETE', path: `${drivePrefix}/items/${input.item_id}` });
196
+ break;
197
+ }
198
+ default:
199
+ throw new Error(`Unsupported action: ${action}`);
200
+ }
201
+
202
+ this.daemon.logEvent(this.taskId, 'tool_result', {
203
+ tool: 'onedrive_action',
204
+ action,
205
+ status: result?.status,
206
+ hasData: result?.data ? true : false,
207
+ });
208
+
209
+ return {
210
+ success: true,
211
+ action,
212
+ status: result?.status,
213
+ data: result?.data,
214
+ raw: result?.raw,
215
+ };
216
+ }
217
+ }