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,243 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SharePointTools = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const sharepoint_manager_1 = require("../../settings/sharepoint-manager");
40
+ const sharepoint_api_1 = require("../../utils/sharepoint-api");
41
+ class SharePointTools {
42
+ constructor(workspace, daemon, taskId) {
43
+ this.workspace = workspace;
44
+ this.daemon = daemon;
45
+ this.taskId = taskId;
46
+ }
47
+ setWorkspace(workspace) {
48
+ this.workspace = workspace;
49
+ }
50
+ static isEnabled() {
51
+ return sharepoint_manager_1.SharePointSettingsManager.loadSettings().enabled;
52
+ }
53
+ async requireApproval(summary, details) {
54
+ const approved = await this.daemon.requestApproval(this.taskId, 'external_service', summary, details);
55
+ if (!approved) {
56
+ throw new Error('User denied SharePoint action');
57
+ }
58
+ }
59
+ resolveFilePath(inputPath) {
60
+ if (!this.workspace.permissions.read) {
61
+ throw new Error('Read permission not granted for uploads');
62
+ }
63
+ const workspaceRoot = path.resolve(this.workspace.path);
64
+ const allowedPaths = this.workspace.permissions.allowedPaths || [];
65
+ const canReadOutside = this.workspace.isTemp || this.workspace.permissions.unrestrictedFileAccess;
66
+ const isPathAllowed = (absolutePath) => {
67
+ if (allowedPaths.length === 0)
68
+ return false;
69
+ const normalizedPath = path.normalize(absolutePath);
70
+ return allowedPaths.some((allowed) => {
71
+ const normalizedAllowed = path.normalize(allowed);
72
+ return normalizedPath === normalizedAllowed || normalizedPath.startsWith(normalizedAllowed + path.sep);
73
+ });
74
+ };
75
+ const candidate = path.isAbsolute(inputPath)
76
+ ? path.normalize(inputPath)
77
+ : path.resolve(workspaceRoot, inputPath);
78
+ const relative = path.relative(workspaceRoot, candidate);
79
+ const isInsideWorkspace = !(relative.startsWith('..') || path.isAbsolute(relative));
80
+ if (!isInsideWorkspace && !canReadOutside && !isPathAllowed(candidate)) {
81
+ throw new Error('File path must be inside the workspace or in Allowed Paths');
82
+ }
83
+ if (!fs.existsSync(candidate)) {
84
+ throw new Error(`File not found: ${inputPath}`);
85
+ }
86
+ const stats = fs.statSync(candidate);
87
+ if (!stats.isFile()) {
88
+ throw new Error(`Path is not a file: ${inputPath}`);
89
+ }
90
+ return candidate;
91
+ }
92
+ getSiteId(inputSiteId) {
93
+ const settings = sharepoint_manager_1.SharePointSettingsManager.loadSettings();
94
+ const siteId = inputSiteId || settings.siteId;
95
+ if (!siteId) {
96
+ throw new Error('Missing site_id. Provide it in settings or the tool input.');
97
+ }
98
+ return siteId;
99
+ }
100
+ getDriveId(inputDriveId) {
101
+ const settings = sharepoint_manager_1.SharePointSettingsManager.loadSettings();
102
+ const driveId = inputDriveId || settings.driveId;
103
+ if (!driveId) {
104
+ throw new Error('Missing drive_id. Provide it in settings or the tool input.');
105
+ }
106
+ return driveId;
107
+ }
108
+ async executeAction(input) {
109
+ const settings = sharepoint_manager_1.SharePointSettingsManager.loadSettings();
110
+ if (!settings.enabled) {
111
+ throw new Error('SharePoint integration is disabled. Enable it in Settings > Integrations > SharePoint.');
112
+ }
113
+ const action = input.action;
114
+ if (!action) {
115
+ throw new Error('Missing required "action" parameter');
116
+ }
117
+ let result;
118
+ switch (action) {
119
+ case 'get_current_user': {
120
+ result = await (0, sharepoint_api_1.sharepointRequest)(settings, { method: 'GET', path: '/me' });
121
+ break;
122
+ }
123
+ case 'search_sites': {
124
+ if (!input.query)
125
+ throw new Error('Missing query for search_sites');
126
+ result = await (0, sharepoint_api_1.sharepointRequest)(settings, {
127
+ method: 'GET',
128
+ path: '/sites',
129
+ query: { search: input.query },
130
+ });
131
+ break;
132
+ }
133
+ case 'get_site': {
134
+ const siteId = this.getSiteId(input.site_id);
135
+ result = await (0, sharepoint_api_1.sharepointRequest)(settings, { method: 'GET', path: `/sites/${siteId}` });
136
+ break;
137
+ }
138
+ case 'list_site_drives': {
139
+ const siteId = this.getSiteId(input.site_id);
140
+ result = await (0, sharepoint_api_1.sharepointRequest)(settings, { method: 'GET', path: `/sites/${siteId}/drives` });
141
+ break;
142
+ }
143
+ case 'list_drive_items': {
144
+ const driveId = this.getDriveId(input.drive_id);
145
+ const pathSuffix = input.item_id ? `/items/${input.item_id}/children` : '/root/children';
146
+ result = await (0, sharepoint_api_1.sharepointRequest)(settings, { method: 'GET', path: `/drives/${driveId}${pathSuffix}` });
147
+ break;
148
+ }
149
+ case 'get_item': {
150
+ if (!input.item_id)
151
+ throw new Error('Missing item_id for get_item');
152
+ const driveId = this.getDriveId(input.drive_id);
153
+ result = await (0, sharepoint_api_1.sharepointRequest)(settings, { method: 'GET', path: `/drives/${driveId}/items/${input.item_id}` });
154
+ break;
155
+ }
156
+ case 'create_folder': {
157
+ if (!input.name)
158
+ throw new Error('Missing name for create_folder');
159
+ const driveId = this.getDriveId(input.drive_id);
160
+ const parentPath = input.parent_id
161
+ ? `/items/${input.parent_id}/children`
162
+ : '/root/children';
163
+ await this.requireApproval('Create a SharePoint folder', {
164
+ action: 'create_folder',
165
+ parent_id: input.parent_id || 'root',
166
+ name: input.name,
167
+ });
168
+ result = await (0, sharepoint_api_1.sharepointRequest)(settings, {
169
+ method: 'POST',
170
+ path: `/drives/${driveId}${parentPath}`,
171
+ body: {
172
+ name: input.name,
173
+ folder: {},
174
+ '@microsoft.graph.conflictBehavior': input.conflict_behavior || 'rename',
175
+ },
176
+ });
177
+ break;
178
+ }
179
+ case 'upload_file': {
180
+ if (!input.file_path)
181
+ throw new Error('Missing file_path for upload_file');
182
+ const driveId = this.getDriveId(input.drive_id);
183
+ const resolved = this.resolveFilePath(input.file_path);
184
+ const data = fs.readFileSync(resolved);
185
+ const fileName = input.name || path.basename(resolved);
186
+ let uploadPath;
187
+ if (input.remote_path) {
188
+ const cleaned = input.remote_path.replace(/^\/+/, '');
189
+ const encoded = cleaned
190
+ .split('/')
191
+ .map(segment => encodeURIComponent(segment))
192
+ .join('/');
193
+ uploadPath = `/drives/${driveId}/root:/${encoded}:/content`;
194
+ }
195
+ else if (input.parent_id) {
196
+ uploadPath = `/drives/${driveId}/items/${input.parent_id}:/${encodeURIComponent(fileName)}:/content`;
197
+ }
198
+ else {
199
+ uploadPath = `/drives/${driveId}/root:/${encodeURIComponent(fileName)}:/content`;
200
+ }
201
+ await this.requireApproval(`Upload file to SharePoint: ${fileName}`, {
202
+ action: 'upload_file',
203
+ destination: input.remote_path || input.parent_id || 'root',
204
+ file: fileName,
205
+ });
206
+ result = await (0, sharepoint_api_1.sharepointRequest)(settings, {
207
+ method: 'PUT',
208
+ path: uploadPath,
209
+ body: data,
210
+ headers: { 'Content-Type': 'application/octet-stream' },
211
+ });
212
+ break;
213
+ }
214
+ case 'delete_item': {
215
+ if (!input.item_id)
216
+ throw new Error('Missing item_id for delete_item');
217
+ const driveId = this.getDriveId(input.drive_id);
218
+ await this.requireApproval('Delete a SharePoint item', {
219
+ action: 'delete_item',
220
+ item_id: input.item_id,
221
+ });
222
+ result = await (0, sharepoint_api_1.sharepointRequest)(settings, { method: 'DELETE', path: `/drives/${driveId}/items/${input.item_id}` });
223
+ break;
224
+ }
225
+ default:
226
+ throw new Error(`Unsupported action: ${action}`);
227
+ }
228
+ this.daemon.logEvent(this.taskId, 'tool_result', {
229
+ tool: 'sharepoint_action',
230
+ action,
231
+ status: result?.status,
232
+ hasData: result?.data ? true : false,
233
+ });
234
+ return {
235
+ success: true,
236
+ action,
237
+ status: result?.status,
238
+ data: result?.data,
239
+ raw: result?.raw,
240
+ };
241
+ }
242
+ }
243
+ exports.SharePointTools = SharePointTools;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports._testUtils = exports.ShellTools = void 0;
4
4
  const child_process_1 = require("child_process");
5
5
  const guardrail_manager_1 = require("../../guardrails/guardrail-manager");
6
+ const builtin_settings_1 = require("./builtin-settings");
6
7
  // Limits to prevent runaway commands
7
8
  const MAX_TIMEOUT = 5 * 60 * 1000; // 5 minutes max
8
9
  const DEFAULT_TIMEOUT = 60 * 1000; // 1 minute default
@@ -321,9 +322,9 @@ class ShellTools {
321
322
  }
322
323
  }
323
324
  /**
324
- * Execute a shell command (requires user approval)
325
+ * Execute a shell command (requires user approval unless auto-approve is enabled)
325
326
  * Note: We don't check workspace.permissions.shell here because
326
- * shell commands always require explicit user approval via requestApproval()
327
+ * shell commands are gated by approval flow (or auto-approve/trust settings)
327
328
  */
328
329
  async runCommand(command, options) {
329
330
  // Check if command is blocked by guardrails BEFORE anything else
@@ -335,8 +336,16 @@ class ShellTools {
335
336
  }
336
337
  // Check if command is trusted (auto-approve without user confirmation)
337
338
  const trustCheck = guardrail_manager_1.GuardrailManager.isCommandTrusted(command);
339
+ const autoApproveEnabled = builtin_settings_1.BuiltinToolsSettingsManager.getToolAutoApprove('run_command');
338
340
  let approved = false;
339
- if (trustCheck.trusted) {
341
+ if (autoApproveEnabled && this.isAutoApprovalSafe(command)) {
342
+ approved = true;
343
+ this.daemon.logEvent(this.taskId, 'log', {
344
+ message: 'Auto-approved command (user setting enabled)',
345
+ command,
346
+ });
347
+ }
348
+ else if (trustCheck.trusted) {
340
349
  // Auto-approve trusted commands
341
350
  approved = true;
342
351
  this.daemon.logEvent(this.taskId, 'log', {
@@ -111,7 +111,7 @@ class XTools {
111
111
  async executeAction(input) {
112
112
  const settings = x_manager_1.XSettingsManager.loadSettings();
113
113
  if (!settings.enabled) {
114
- throw new Error('X integration is disabled. Enable it in Settings > More Channels > X.');
114
+ throw new Error('X integration is disabled. Enable it in Settings > X (Twitter).');
115
115
  }
116
116
  const action = input.action;
117
117
  if (!action) {
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAgentDispatchPrompt = void 0;
4
+ const buildSoulSummary = (soul) => {
5
+ if (!soul)
6
+ return null;
7
+ try {
8
+ const parsed = JSON.parse(soul);
9
+ const parts = [];
10
+ if (typeof parsed.name === 'string')
11
+ parts.push(`Name: ${parsed.name}`);
12
+ if (typeof parsed.role === 'string')
13
+ parts.push(`Role: ${parsed.role}`);
14
+ if (typeof parsed.personality === 'string')
15
+ parts.push(`Personality: ${parsed.personality}`);
16
+ if (typeof parsed.communicationStyle === 'string')
17
+ parts.push(`Style: ${parsed.communicationStyle}`);
18
+ if (Array.isArray(parsed.focusAreas))
19
+ parts.push(`Focus: ${parsed.focusAreas.join(', ')}`);
20
+ if (Array.isArray(parsed.strengths))
21
+ parts.push(`Strengths: ${parsed.strengths.join(', ')}`);
22
+ if (parts.length === 0) {
23
+ return null;
24
+ }
25
+ return parts.join('\n');
26
+ }
27
+ catch {
28
+ return soul;
29
+ }
30
+ };
31
+ const buildAgentDispatchPrompt = (role, parentTask, options) => {
32
+ const lines = [
33
+ `You are ${role.displayName}${role.description ? ` — ${role.description}` : ''}.`,
34
+ ];
35
+ if (role.capabilities && role.capabilities.length > 0) {
36
+ lines.push(`Capabilities: ${role.capabilities.join(', ')}`);
37
+ }
38
+ if (role.systemPrompt) {
39
+ lines.push('System guidance:');
40
+ lines.push(role.systemPrompt);
41
+ }
42
+ const soulSummary = buildSoulSummary(role.soul || undefined);
43
+ if (soulSummary) {
44
+ lines.push('Role notes:');
45
+ lines.push(soulSummary);
46
+ }
47
+ if (options?.planSummary) {
48
+ lines.push('');
49
+ lines.push('Main agent plan summary (context only):');
50
+ lines.push(options.planSummary);
51
+ }
52
+ lines.push('');
53
+ lines.push(`Parent task: ${parentTask.title}`);
54
+ lines.push('Request:');
55
+ lines.push(parentTask.prompt);
56
+ lines.push('');
57
+ lines.push('Deliverables:');
58
+ lines.push('- Provide a concise summary of your findings.');
59
+ lines.push('- Call out risks or open questions.');
60
+ lines.push('- Recommend next steps.');
61
+ return lines.join('\n');
62
+ };
63
+ exports.buildAgentDispatchPrompt = buildAgentDispatchPrompt;
@@ -20,18 +20,20 @@ class WorkspaceRepository {
20
20
  this.db = db;
21
21
  }
22
22
  create(name, path, permissions) {
23
+ const now = Date.now();
23
24
  const workspace = {
24
25
  id: (0, uuid_1.v4)(),
25
26
  name,
26
27
  path,
27
- createdAt: Date.now(),
28
+ createdAt: now,
29
+ lastUsedAt: now,
28
30
  permissions,
29
31
  };
30
32
  const stmt = this.db.prepare(`
31
- INSERT INTO workspaces (id, name, path, created_at, permissions)
32
- VALUES (?, ?, ?, ?, ?)
33
+ INSERT INTO workspaces (id, name, path, created_at, last_used_at, permissions)
34
+ VALUES (?, ?, ?, ?, ?, ?)
33
35
  `);
34
- stmt.run(workspace.id, workspace.name, workspace.path, workspace.createdAt, JSON.stringify(workspace.permissions));
36
+ stmt.run(workspace.id, workspace.name, workspace.path, workspace.createdAt, workspace.lastUsedAt, JSON.stringify(workspace.permissions));
35
37
  return workspace;
36
38
  }
37
39
  findById(id) {
@@ -40,7 +42,11 @@ class WorkspaceRepository {
40
42
  return row ? this.mapRowToWorkspace(row) : undefined;
41
43
  }
42
44
  findAll() {
43
- const stmt = this.db.prepare('SELECT * FROM workspaces ORDER BY created_at DESC');
45
+ const stmt = this.db.prepare(`
46
+ SELECT *
47
+ FROM workspaces
48
+ ORDER BY COALESCE(last_used_at, created_at) DESC
49
+ `);
44
50
  const rows = stmt.all();
45
51
  return rows.map(row => this.mapRowToWorkspace(row));
46
52
  }
@@ -67,6 +73,13 @@ class WorkspaceRepository {
67
73
  const stmt = this.db.prepare('UPDATE workspaces SET permissions = ? WHERE id = ?');
68
74
  stmt.run(JSON.stringify(permissions), id);
69
75
  }
76
+ /**
77
+ * Update last used timestamp for recency ordering
78
+ */
79
+ updateLastUsedAt(id, lastUsedAt = Date.now()) {
80
+ const stmt = this.db.prepare('UPDATE workspaces SET last_used_at = ? WHERE id = ?');
81
+ stmt.run(lastUsedAt, id);
82
+ }
70
83
  /**
71
84
  * Delete a workspace by ID
72
85
  */
@@ -94,6 +107,7 @@ class WorkspaceRepository {
94
107
  name: row.name,
95
108
  path: row.path,
96
109
  createdAt: row.created_at,
110
+ lastUsedAt: row.last_used_at ?? undefined,
97
111
  permissions: mergedPermissions,
98
112
  };
99
113
  }
@@ -207,6 +207,7 @@ class DatabaseManager {
207
207
  name TEXT NOT NULL,
208
208
  path TEXT NOT NULL UNIQUE,
209
209
  created_at INTEGER NOT NULL,
210
+ last_used_at INTEGER,
210
211
  permissions TEXT NOT NULL
211
212
  );
212
213
 
@@ -560,6 +561,13 @@ class DatabaseManager {
560
561
  // Column already exists, ignore
561
562
  }
562
563
  }
564
+ // Migration: Add last_used_at to workspaces for recency ordering
565
+ try {
566
+ this.db.exec('ALTER TABLE workspaces ADD COLUMN last_used_at INTEGER');
567
+ }
568
+ catch {
569
+ // Column already exists, ignore
570
+ }
563
571
  // Migration: Add Sub-Agent / Parallel Agent columns to tasks table
564
572
  const subAgentColumns = [
565
573
  'ALTER TABLE tasks ADD COLUMN parent_task_id TEXT REFERENCES tasks(id)',
@@ -72,6 +72,8 @@ class WhatsAppAdapter {
72
72
  this.connectedAtMs = 0;
73
73
  this.isReconnecting = false;
74
74
  this.backoffAttempt = 0;
75
+ this.shouldReconnect = true;
76
+ this.selfChatIgnoreLogAt = 0;
75
77
  this.DEFAULT_BACKOFF = {
76
78
  initialDelay: 2000,
77
79
  maxDelay: 30000,
@@ -135,6 +137,7 @@ class WhatsAppAdapter {
135
137
  if (this._status === 'connected' || this._status === 'connecting') {
136
138
  return;
137
139
  }
140
+ this.shouldReconnect = true;
138
141
  this.setStatus('connecting');
139
142
  this.resetBackoff();
140
143
  try {
@@ -194,6 +197,17 @@ class WhatsAppAdapter {
194
197
  */
195
198
  handleConnectionUpdate(update) {
196
199
  const { connection, lastDisconnect, qr } = update;
200
+ if (!this.shouldReconnect) {
201
+ if (connection === 'open') {
202
+ // If a manual disconnect happened mid-handshake, close immediately.
203
+ this.sock?.ws?.close();
204
+ this.setStatus('disconnected');
205
+ }
206
+ else if (connection === 'close') {
207
+ this.setStatus('disconnected');
208
+ }
209
+ return;
210
+ }
197
211
  // Handle QR code for authentication
198
212
  if (qr) {
199
213
  this.currentQr = qr;
@@ -275,6 +289,12 @@ class WhatsAppAdapter {
275
289
  const selfJidNormalized = normalizeJid(this._selfJid);
276
290
  const remoteJidNormalized = normalizeJid(remoteJid);
277
291
  if (remoteJidNormalized !== selfJidNormalized) {
292
+ const now = Date.now();
293
+ if (now - this.selfChatIgnoreLogAt > 30000) {
294
+ console.log(`WhatsApp: Ignoring message from ${remoteJidNormalized} because self-chat mode is enabled. ` +
295
+ 'Disable self-chat mode to accept messages from other numbers.');
296
+ this.selfChatIgnoreLogAt = now;
297
+ }
278
298
  // Message is NOT in self-chat, silently ignore it
279
299
  return;
280
300
  }
@@ -365,6 +385,7 @@ class WhatsAppAdapter {
365
385
  * Disconnect from WhatsApp
366
386
  */
367
387
  async disconnect() {
388
+ this.shouldReconnect = false;
368
389
  this.resetBackoff();
369
390
  // Clear timers
370
391
  if (this.dedupCleanupTimer) {
@@ -625,6 +646,33 @@ class WhatsAppAdapter {
625
646
  onQrCode(handler) {
626
647
  this.qrCodeHandlers.push(handler);
627
648
  }
649
+ /**
650
+ * Update adapter configuration at runtime
651
+ */
652
+ updateConfig(config) {
653
+ const next = config;
654
+ const prevDedupEnabled = this.config.deduplicationEnabled !== false;
655
+ const prevSelfChat = this.config.selfChatMode === true;
656
+ this.config = {
657
+ ...this.config,
658
+ ...next,
659
+ };
660
+ // If self-chat was just enabled and read receipts weren't explicitly set, default to false.
661
+ if (!prevSelfChat && this.config.selfChatMode && next.sendReadReceipts === undefined) {
662
+ this.config.sendReadReceipts = false;
663
+ }
664
+ const nextDedupEnabled = this.config.deduplicationEnabled !== false;
665
+ if (nextDedupEnabled && !prevDedupEnabled) {
666
+ this.startDedupCleanup();
667
+ }
668
+ else if (!nextDedupEnabled && prevDedupEnabled) {
669
+ if (this.dedupCleanupTimer) {
670
+ clearInterval(this.dedupCleanupTimer);
671
+ this.dedupCleanupTimer = undefined;
672
+ }
673
+ this.processedMessages.clear();
674
+ }
675
+ }
628
676
  /**
629
677
  * Get channel info
630
678
  */
@@ -712,6 +760,9 @@ class WhatsAppAdapter {
712
760
  * Attempt reconnection with exponential backoff
713
761
  */
714
762
  async attemptReconnection() {
763
+ if (!this.shouldReconnect) {
764
+ return;
765
+ }
715
766
  if (this.isReconnecting)
716
767
  return;
717
768
  const config = this.DEFAULT_BACKOFF;
@@ -726,6 +777,10 @@ class WhatsAppAdapter {
726
777
  console.log(`WhatsApp: Reconnection attempt ${this.backoffAttempt}/${config.maxAttempts} in ${delay}ms`);
727
778
  this.backoffTimer = setTimeout(async () => {
728
779
  try {
780
+ if (!this.shouldReconnect) {
781
+ this.isReconnecting = false;
782
+ return;
783
+ }
729
784
  this.sock = null;
730
785
  this.isReconnecting = false;
731
786
  this.setStatus('disconnected');
@@ -16,11 +16,36 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
16
16
  if (k2 === undefined) k2 = k;
17
17
  o[k2] = m[k];
18
18
  }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
19
41
  var __exportStar = (this && this.__exportStar) || function(m, exports) {
20
42
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
21
43
  };
22
44
  Object.defineProperty(exports, "__esModule", { value: true });
23
45
  exports.createAutoTunnel = exports.getAvailableTunnelProviders = exports.TunnelManager = exports.EmailClient = exports.createEmailAdapter = exports.EmailAdapter = exports.BlueBubblesClient = exports.createBlueBubblesAdapter = exports.BlueBubblesAdapter = exports.LineClient = exports.createLineAdapter = exports.LineAdapter = exports.TwitchClient = exports.createTwitchAdapter = exports.TwitchAdapter = exports.MatrixClient = exports.createMatrixAdapter = exports.MatrixAdapter = exports.MattermostClient = exports.createMattermostAdapter = exports.MattermostAdapter = exports.SignalClient = exports.createSignalAdapter = exports.SignalAdapter = exports.createImessageAdapter = exports.ImessageAdapter = exports.createWhatsAppAdapter = exports.WhatsAppAdapter = exports.createSlackAdapter = exports.SlackAdapter = exports.createDiscordAdapter = exports.DiscordAdapter = exports.createTelegramAdapter = exports.TelegramAdapter = exports.ChannelGateway = void 0;
46
+ const electron_1 = require("electron");
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
24
49
  const router_1 = require("./router");
25
50
  const security_1 = require("./security");
26
51
  const session_1 = require("./session");
@@ -77,6 +102,7 @@ class ChannelGateway {
77
102
  agentName: settings.agentName || 'CoWork',
78
103
  userName: settings.relationship?.userName,
79
104
  personality: settings.activePersonality || 'professional',
105
+ persona: settings.activePersona,
80
106
  emojiUsage: settings.responseStyle?.emojiUsage || 'minimal',
81
107
  quirks: settings.quirks || types_1.DEFAULT_QUIRKS,
82
108
  };
@@ -310,6 +336,8 @@ class ChannelGateway {
310
336
  if (existing) {
311
337
  throw new Error('WhatsApp channel already configured. Update or remove it first.');
312
338
  }
339
+ // Always clear any stale auth so a new QR is required for a new number.
340
+ this.clearWhatsAppAuthDir();
313
341
  // Create channel record
314
342
  const channel = this.channelRepo.create({
315
343
  type: 'whatsapp',
@@ -591,6 +619,15 @@ class ChannelGateway {
591
619
  */
592
620
  updateChannel(channelId, updates) {
593
621
  this.channelRepo.update(channelId, updates);
622
+ if (updates.config === undefined)
623
+ return;
624
+ const channel = this.channelRepo.findById(channelId);
625
+ if (!channel)
626
+ return;
627
+ const adapter = this.router.getAdapter(channel.type);
628
+ if (adapter?.updateConfig) {
629
+ adapter.updateConfig(channel.config);
630
+ }
594
631
  }
595
632
  /**
596
633
  * Enable a channel and connect
@@ -702,6 +739,9 @@ class ChannelGateway {
702
739
  if (adapter) {
703
740
  await adapter.logout();
704
741
  }
742
+ else {
743
+ this.clearWhatsAppAuthDir();
744
+ }
705
745
  const channel = this.channelRepo.findByType('whatsapp');
706
746
  if (channel) {
707
747
  this.channelRepo.update(channel.id, { enabled: false, status: 'disconnected', botUsername: undefined });
@@ -711,7 +751,23 @@ class ChannelGateway {
711
751
  * Remove a channel
712
752
  */
713
753
  async removeChannel(channelId) {
714
- await this.disableChannel(channelId);
754
+ const channel = this.channelRepo.findById(channelId);
755
+ if (!channel)
756
+ return;
757
+ if (channel.type === 'whatsapp') {
758
+ const adapter = this.router.getAdapter('whatsapp');
759
+ if (adapter) {
760
+ await adapter.logout();
761
+ }
762
+ else {
763
+ const tempAdapter = this.createAdapterForChannel(channel);
764
+ await tempAdapter.logout();
765
+ }
766
+ this.clearWhatsAppAuthDir(channel);
767
+ }
768
+ else {
769
+ await this.disableChannel(channelId);
770
+ }
715
771
  // Delete associated data first (to avoid foreign key constraint errors)
716
772
  this.messageRepo.deleteByChannelId(channelId);
717
773
  this.sessionRepo.deleteByChannelId(channelId);
@@ -853,6 +909,24 @@ class ChannelGateway {
853
909
  return this.router.handleTaskFailure(taskId, error);
854
910
  }
855
911
  // Private methods
912
+ resolveWhatsAppAuthDir(channel) {
913
+ const configured = channel?.config?.authDir;
914
+ if (configured && configured.trim()) {
915
+ return configured;
916
+ }
917
+ return path.join(electron_1.app.getPath('userData'), 'whatsapp-auth');
918
+ }
919
+ clearWhatsAppAuthDir(channel) {
920
+ try {
921
+ const authDir = this.resolveWhatsAppAuthDir(channel);
922
+ if (fs.existsSync(authDir)) {
923
+ fs.rmSync(authDir, { recursive: true, force: true });
924
+ }
925
+ }
926
+ catch (error) {
927
+ console.error('Failed to clear WhatsApp auth directory:', error);
928
+ }
929
+ }
856
930
  /**
857
931
  * Load and register channel adapters
858
932
  */