cowork-os 0.3.21 → 0.3.23

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 (170) hide show
  1. package/README.md +293 -6
  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/daemon.js +25 -0
  48. package/dist/electron/electron/agent/executor.js +181 -26
  49. package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
  50. package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
  51. package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
  52. package/dist/electron/electron/agent/llm/index.js +11 -1
  53. package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
  54. package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
  55. package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
  56. package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
  57. package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
  58. package/dist/electron/electron/agent/llm/provider-factory.js +318 -4
  59. package/dist/electron/electron/agent/llm/types.js +66 -1
  60. package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
  61. package/dist/electron/electron/agent/tools/box-tools.js +231 -0
  62. package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
  63. package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
  64. package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
  65. package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
  66. package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
  67. package/dist/electron/electron/agent/tools/registry.js +541 -0
  68. package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
  69. package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
  70. package/dist/electron/electron/agent/tools/x-tools.js +1 -1
  71. package/dist/electron/electron/gateway/index.js +1 -0
  72. package/dist/electron/electron/gateway/router.js +123 -143
  73. package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
  74. package/dist/electron/electron/ipc/handlers.js +627 -158
  75. package/dist/electron/electron/main.js +63 -0
  76. package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
  77. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
  78. package/dist/electron/electron/memory/MemoryService.js +1 -1
  79. package/dist/electron/electron/preload.js +74 -1
  80. package/dist/electron/electron/settings/box-manager.js +54 -0
  81. package/dist/electron/electron/settings/dropbox-manager.js +54 -0
  82. package/dist/electron/electron/settings/google-drive-manager.js +54 -0
  83. package/dist/electron/electron/settings/notion-manager.js +56 -0
  84. package/dist/electron/electron/settings/onedrive-manager.js +54 -0
  85. package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
  86. package/dist/electron/electron/utils/box-api.js +153 -0
  87. package/dist/electron/electron/utils/dropbox-api.js +144 -0
  88. package/dist/electron/electron/utils/env-migration.js +19 -0
  89. package/dist/electron/electron/utils/google-drive-api.js +152 -0
  90. package/dist/electron/electron/utils/notion-api.js +103 -0
  91. package/dist/electron/electron/utils/onedrive-api.js +113 -0
  92. package/dist/electron/electron/utils/sharepoint-api.js +109 -0
  93. package/dist/electron/electron/utils/validation.js +82 -3
  94. package/dist/electron/electron/utils/x-cli.js +1 -1
  95. package/dist/electron/shared/channelMessages.js +284 -3
  96. package/dist/electron/shared/llm-provider-catalog.js +198 -0
  97. package/dist/electron/shared/types.js +88 -1
  98. package/package.json +12 -2
  99. package/src/electron/agent/executor.ts +205 -28
  100. package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
  101. package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
  102. package/src/electron/agent/llm/groq-provider.ts +39 -0
  103. package/src/electron/agent/llm/index.ts +5 -0
  104. package/src/electron/agent/llm/kimi-provider.ts +39 -0
  105. package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
  106. package/src/electron/agent/llm/openai-compatible.ts +133 -0
  107. package/src/electron/agent/llm/openai-oauth.ts +2 -1
  108. package/src/electron/agent/llm/openrouter-provider.ts +2 -1
  109. package/src/electron/agent/llm/provider-factory.ts +414 -6
  110. package/src/electron/agent/llm/types.ts +90 -1
  111. package/src/electron/agent/llm/xai-provider.ts +39 -0
  112. package/src/electron/agent/tools/box-tools.ts +239 -0
  113. package/src/electron/agent/tools/builtin-settings.ts +34 -0
  114. package/src/electron/agent/tools/dropbox-tools.ts +237 -0
  115. package/src/electron/agent/tools/google-drive-tools.ts +228 -0
  116. package/src/electron/agent/tools/notion-tools.ts +330 -0
  117. package/src/electron/agent/tools/onedrive-tools.ts +217 -0
  118. package/src/electron/agent/tools/registry.ts +565 -0
  119. package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
  120. package/src/electron/agent/tools/shell-tools.ts +11 -3
  121. package/src/electron/agent/tools/x-tools.ts +1 -1
  122. package/src/electron/database/SecureSettingsRepository.ts +7 -1
  123. package/src/electron/gateway/index.ts +1 -0
  124. package/src/electron/gateway/router.ts +134 -149
  125. package/src/electron/ipc/canvas-handlers.ts +10 -0
  126. package/src/electron/ipc/handlers.ts +673 -153
  127. package/src/electron/main.ts +35 -0
  128. package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
  129. package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
  130. package/src/electron/memory/MemoryService.ts +5 -1
  131. package/src/electron/preload.ts +167 -4
  132. package/src/electron/settings/box-manager.ts +58 -0
  133. package/src/electron/settings/dropbox-manager.ts +58 -0
  134. package/src/electron/settings/google-drive-manager.ts +58 -0
  135. package/src/electron/settings/notion-manager.ts +60 -0
  136. package/src/electron/settings/onedrive-manager.ts +58 -0
  137. package/src/electron/settings/sharepoint-manager.ts +58 -0
  138. package/src/electron/utils/box-api.ts +184 -0
  139. package/src/electron/utils/dropbox-api.ts +171 -0
  140. package/src/electron/utils/env-migration.ts +22 -0
  141. package/src/electron/utils/google-drive-api.ts +183 -0
  142. package/src/electron/utils/notion-api.ts +126 -0
  143. package/src/electron/utils/onedrive-api.ts +137 -0
  144. package/src/electron/utils/sharepoint-api.ts +132 -0
  145. package/src/electron/utils/validation.ts +102 -1
  146. package/src/electron/utils/x-cli.ts +1 -1
  147. package/src/renderer/App.tsx +20 -2
  148. package/src/renderer/components/BoxSettings.tsx +203 -0
  149. package/src/renderer/components/BrowserView.tsx +101 -0
  150. package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
  151. package/src/renderer/components/CanvasPreview.tsx +68 -1
  152. package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
  153. package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
  154. package/src/renderer/components/ConnectorsSettings.tsx +397 -0
  155. package/src/renderer/components/DropboxSettings.tsx +202 -0
  156. package/src/renderer/components/GoogleDriveSettings.tsx +201 -0
  157. package/src/renderer/components/MCPSettings.tsx +56 -0
  158. package/src/renderer/components/MainContent.tsx +270 -34
  159. package/src/renderer/components/NotionSettings.tsx +231 -0
  160. package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
  161. package/src/renderer/components/OnboardingModal.tsx +70 -1
  162. package/src/renderer/components/OneDriveSettings.tsx +212 -0
  163. package/src/renderer/components/Settings.tsx +611 -8
  164. package/src/renderer/components/SharePointSettings.tsx +224 -0
  165. package/src/renderer/components/Sidebar.tsx +25 -9
  166. package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
  167. package/src/renderer/styles/index.css +438 -25
  168. package/src/shared/channelMessages.ts +367 -4
  169. package/src/shared/llm-provider-catalog.ts +217 -0
  170. package/src/shared/types.ts +226 -1
@@ -118,6 +118,7 @@ class MessageRouter {
118
118
  agentName: settings.agentName || 'CoWork',
119
119
  userName: settings.relationship?.userName,
120
120
  personality: settings.activePersonality || 'professional',
121
+ persona: settings.activePersona,
121
122
  emojiUsage: settings.responseStyle?.emojiUsage || 'minimal',
122
123
  quirks: settings.quirks || types_2.DEFAULT_QUIRKS,
123
124
  };
@@ -128,6 +129,9 @@ class MessageRouter {
128
129
  }
129
130
  return channelMessages_1.DEFAULT_CHANNEL_CONTEXT;
130
131
  }
132
+ getUiCopy(key, replacements) {
133
+ return (0, channelMessages_1.getChannelUiCopy)(key, this.getMessageContext(), replacements);
134
+ }
131
135
  /**
132
136
  * Get or create the temp workspace for sessions without a workspace
133
137
  */
@@ -507,10 +511,10 @@ class MessageRouter {
507
511
  // Not a pairing code or pairing not required - send appropriate message
508
512
  let responseText;
509
513
  if (securityResult.pairingRequired) {
510
- responseText = this.config.pairingRequiredMessage;
514
+ responseText = this.getUiCopy('pairingRequired');
511
515
  }
512
516
  else {
513
- responseText = this.config.unauthorizedMessage;
517
+ responseText = this.getUiCopy('unauthorized');
514
518
  }
515
519
  try {
516
520
  await adapter.sendMessage({
@@ -549,9 +553,11 @@ class MessageRouter {
549
553
  if (!isNaN(num) && num > 0 && num <= workspaces.length) {
550
554
  const workspace = workspaces[num - 1];
551
555
  this.sessionManager.setSessionWorkspace(sessionId, workspace.id);
556
+ const selectedText = this.getUiCopy('workspaceSelected', { workspaceName: workspace.name });
557
+ const exampleText = this.getUiCopy('workspaceSelectedExample');
552
558
  await adapter.sendMessage({
553
559
  chatId: message.chatId,
554
- text: `✅ *${workspace.name}* selected!\n\nYou can now send me tasks.\n\nExample: "Create a new React component called Button"`,
560
+ text: `${selectedText}\n\n${exampleText}`,
555
561
  parseMode: 'markdown',
556
562
  });
557
563
  return;
@@ -561,9 +567,11 @@ class MessageRouter {
561
567
  ws.name.toLowerCase().startsWith(text.toLowerCase()));
562
568
  if (matchedWorkspace) {
563
569
  this.sessionManager.setSessionWorkspace(sessionId, matchedWorkspace.id);
570
+ const selectedText = this.getUiCopy('workspaceSelected', { workspaceName: matchedWorkspace.name });
571
+ const exampleText = this.getUiCopy('workspaceSelectedExample');
564
572
  await adapter.sendMessage({
565
573
  chatId: message.chatId,
566
- text: `✅ *${matchedWorkspace.name}* selected!\n\nYou can now send me tasks.\n\nExample: "Create a new React component called Button"`,
574
+ text: `${selectedText}\n\n${exampleText}`,
567
575
  parseMode: 'markdown',
568
576
  });
569
577
  return;
@@ -626,7 +634,7 @@ class MessageRouter {
626
634
  if (args.length === 0) {
627
635
  await adapter.sendMessage({
628
636
  chatId: message.chatId,
629
- text: '🔐 Please provide a pairing code.\n\nUsage: `/pair <code>`',
637
+ text: this.getUiCopy('pairingPrompt'),
630
638
  parseMode: 'markdown',
631
639
  });
632
640
  }
@@ -681,7 +689,7 @@ class MessageRouter {
681
689
  default:
682
690
  await adapter.sendMessage({
683
691
  chatId: message.chatId,
684
- text: `Unknown command: ${command}\n\nUse /help to see available commands.`,
692
+ text: this.getUiCopy('unknownCommand', { command }),
685
693
  replyTo: message.messageId,
686
694
  });
687
695
  }
@@ -691,21 +699,24 @@ class MessageRouter {
691
699
  */
692
700
  async handleStatusCommand(adapter, message, sessionId) {
693
701
  const session = this.sessionRepo.findById(sessionId);
694
- let statusText = '✅ Bot is online and ready.\n\n';
702
+ let statusText = `✅ ${this.getUiCopy('statusHeader')}\n\n`;
695
703
  if (session?.workspaceId) {
696
704
  const workspace = this.workspaceRepo.findById(session.workspaceId);
697
705
  if (workspace) {
698
- statusText += `📁 Current workspace: ${workspace.name}\n`;
699
- statusText += ` Path: ${workspace.path}\n`;
706
+ statusText += this.getUiCopy('workspaceCurrent', {
707
+ workspaceName: workspace.name,
708
+ workspacePath: workspace.path,
709
+ });
710
+ statusText += '\n';
700
711
  }
701
712
  }
702
713
  else {
703
- statusText += '⚠️ No workspace selected. Use /workspaces to see available workspaces.';
714
+ statusText += this.getUiCopy('statusNoWorkspace');
704
715
  }
705
716
  if (session?.taskId) {
706
717
  const task = this.taskRepo.findById(session.taskId);
707
718
  if (task) {
708
- statusText += `\n🔄 Active task: ${task.title} (${task.status})`;
719
+ statusText += `\n${this.getUiCopy('statusActiveTask', { taskTitle: task.title, status: task.status })}`;
709
720
  }
710
721
  }
711
722
  await adapter.sendMessage({
@@ -721,20 +732,19 @@ class MessageRouter {
721
732
  if (workspaces.length === 0) {
722
733
  await adapter.sendMessage({
723
734
  chatId: message.chatId,
724
- text: '📁 No workspaces configured yet.\n\nAdd a workspace in the CoWork desktop app first, or use:\n`/addworkspace /path/to/your/project`',
735
+ text: this.getUiCopy('workspacesNone'),
725
736
  parseMode: 'markdown',
726
737
  });
727
738
  return;
728
739
  }
729
740
  // WhatsApp and iMessage don't support inline keyboards - use text-based selection
730
741
  if (adapter.type === 'whatsapp' || adapter.type === 'imessage') {
731
- let text = '📁 *Available Workspaces*\n\n';
742
+ let text = `${this.getUiCopy('workspacesHeader')}\n\n`;
732
743
  workspaces.forEach((ws, index) => {
733
744
  text += `${index + 1}. *${ws.name}*\n \`${ws.path}\`\n\n`;
734
745
  });
735
746
  text += '━━━━━━━━━━━━━━━\n';
736
- text += 'Reply with the number or name to select.\n';
737
- text += 'Example: `1` or `myproject`';
747
+ text += this.getUiCopy('workspacesFooter');
738
748
  await adapter.sendMessage({
739
749
  chatId: message.chatId,
740
750
  text,
@@ -751,7 +761,7 @@ class MessageRouter {
751
761
  callbackData: `workspace:${ws.id}`,
752
762
  }]);
753
763
  }
754
- let text = '📁 *Available Workspaces*\n\nTap a workspace to select it:';
764
+ let text = `${this.getUiCopy('workspacesHeader')}\n\n${this.getUiCopy('workspacesSelectPrompt')}`;
755
765
  await adapter.sendMessage({
756
766
  chatId: message.chatId,
757
767
  text,
@@ -780,7 +790,10 @@ class MessageRouter {
780
790
  const displayName = isTempWorkspace ? 'Temporary Workspace (work in a folder for persistence)' : workspace.name;
781
791
  await adapter.sendMessage({
782
792
  chatId: message.chatId,
783
- text: `📁 Current workspace: *${displayName}*\n\`${workspace.path}\`\n\nUse \`/workspaces\` to see available workspaces.`,
793
+ text: this.getUiCopy('workspaceCurrent', {
794
+ workspaceName: displayName,
795
+ workspacePath: workspace.path,
796
+ }),
784
797
  parseMode: 'markdown',
785
798
  });
786
799
  return;
@@ -788,7 +801,7 @@ class MessageRouter {
788
801
  }
789
802
  await adapter.sendMessage({
790
803
  chatId: message.chatId,
791
- text: 'No workspace selected. Use `/workspaces` to see available workspaces.',
804
+ text: this.getUiCopy('workspaceNoneSelected'),
792
805
  parseMode: 'markdown',
793
806
  });
794
807
  return;
@@ -808,7 +821,7 @@ class MessageRouter {
808
821
  if (!workspace) {
809
822
  await adapter.sendMessage({
810
823
  chatId: message.chatId,
811
- text: `❌ Workspace not found: "${selector}"\n\nUse /workspaces to see available workspaces.`,
824
+ text: this.getUiCopy('workspaceNotFound', { selector }),
812
825
  });
813
826
  return;
814
827
  }
@@ -816,7 +829,10 @@ class MessageRouter {
816
829
  this.sessionManager.setSessionWorkspace(sessionId, workspace.id);
817
830
  await adapter.sendMessage({
818
831
  chatId: message.chatId,
819
- text: `✅ Workspace set to: *${workspace.name}*\n\`${workspace.path}\`\n\nYou can now send messages to create tasks in this workspace.`,
832
+ text: this.getUiCopy('workspaceSet', {
833
+ workspaceName: workspace.name,
834
+ workspacePath: workspace.path,
835
+ }),
820
836
  parseMode: 'markdown',
821
837
  });
822
838
  }
@@ -827,7 +843,7 @@ class MessageRouter {
827
843
  if (args.length === 0) {
828
844
  await adapter.sendMessage({
829
845
  chatId: message.chatId,
830
- text: '📁 *Add Workspace*\n\nUsage: `/addworkspace <path>`\n\nExample:\n`/addworkspace /Users/john/projects/myapp`\n`/addworkspace ~/Documents`',
846
+ text: this.getUiCopy('workspaceAddUsage'),
831
847
  parseMode: 'markdown',
832
848
  });
833
849
  return;
@@ -847,7 +863,7 @@ class MessageRouter {
847
863
  if (!stats.isDirectory()) {
848
864
  await adapter.sendMessage({
849
865
  chatId: message.chatId,
850
- text: `❌ Path is not a directory: \`${workspacePath}\``,
866
+ text: this.getUiCopy('workspacePathNotDir', { workspacePath }),
851
867
  parseMode: 'markdown',
852
868
  });
853
869
  return;
@@ -856,7 +872,7 @@ class MessageRouter {
856
872
  catch {
857
873
  await adapter.sendMessage({
858
874
  chatId: message.chatId,
859
- text: `❌ Directory not found: \`${workspacePath}\``,
875
+ text: this.getUiCopy('workspacePathNotFound', { workspacePath }),
860
876
  parseMode: 'markdown',
861
877
  });
862
878
  return;
@@ -869,7 +885,10 @@ class MessageRouter {
869
885
  this.sessionManager.setSessionWorkspace(sessionId, existing.id);
870
886
  await adapter.sendMessage({
871
887
  chatId: message.chatId,
872
- text: `📁 Workspace already exists!\n\n✅ Selected: *${existing.name}*\n\`${existing.path}\``,
888
+ text: this.getUiCopy('workspaceAlreadyExists', {
889
+ workspaceName: existing.name,
890
+ workspacePath: existing.path,
891
+ }),
873
892
  parseMode: 'markdown',
874
893
  });
875
894
  return;
@@ -897,7 +916,10 @@ class MessageRouter {
897
916
  }
898
917
  await adapter.sendMessage({
899
918
  chatId: message.chatId,
900
- text: `✅ Workspace added and selected!\n\n📁 *${workspace.name}*\n\`${workspace.path}\`\n\nYou can now send messages to create tasks in this workspace.`,
919
+ text: this.getUiCopy('workspaceAdded', {
920
+ workspaceName: workspace.name,
921
+ workspacePath: workspace.path,
922
+ }),
901
923
  parseMode: 'markdown',
902
924
  });
903
925
  }
@@ -1337,7 +1359,7 @@ class MessageRouter {
1337
1359
  if (!workspace) {
1338
1360
  await adapter.sendMessage({
1339
1361
  chatId: message.chatId,
1340
- text: '❌ Workspace not found.',
1362
+ text: this.getUiCopy('workspaceNotFoundForShell'),
1341
1363
  });
1342
1364
  return;
1343
1365
  }
@@ -1362,7 +1384,7 @@ class MessageRouter {
1362
1384
  else {
1363
1385
  await adapter.sendMessage({
1364
1386
  chatId: message.chatId,
1365
- text: '❌ Invalid option. Use `/shell on` or `/shell off`',
1387
+ text: this.getUiCopy('shellInvalidOption'),
1366
1388
  parseMode: 'markdown',
1367
1389
  });
1368
1390
  return;
@@ -1470,7 +1492,7 @@ class MessageRouter {
1470
1492
  if (result.success) {
1471
1493
  await adapter.sendMessage({
1472
1494
  chatId: message.chatId,
1473
- text: '✅ Pairing successful! You can now use the bot.',
1495
+ text: this.getUiCopy('pairingSuccess'),
1474
1496
  replyTo: message.messageId,
1475
1497
  });
1476
1498
  this.emitEvent({
@@ -1483,7 +1505,9 @@ class MessageRouter {
1483
1505
  else {
1484
1506
  await adapter.sendMessage({
1485
1507
  chatId: message.chatId,
1486
- text: `❌ ${result.error || 'Invalid pairing code. Please try again.'}`,
1508
+ text: this.getUiCopy('pairingFailed', {
1509
+ error: result.error || 'Invalid pairing code. Please try again.',
1510
+ }),
1487
1511
  replyTo: message.messageId,
1488
1512
  });
1489
1513
  }
@@ -1512,8 +1536,8 @@ class MessageRouter {
1512
1536
  if (this.agentDaemon) {
1513
1537
  try {
1514
1538
  const statusMsg = isActive
1515
- ? '💬 Sending follow-up message...'
1516
- : '💬 Continuing conversation...';
1539
+ ? '💬 Got it — adding that to the current task...'
1540
+ : '💬 Picking up where we left off...';
1517
1541
  await adapter.sendMessage({
1518
1542
  chatId: message.chatId,
1519
1543
  text: statusMsg,
@@ -1531,7 +1555,7 @@ class MessageRouter {
1531
1555
  console.error('Error sending follow-up message:', error);
1532
1556
  await adapter.sendMessage({
1533
1557
  chatId: message.chatId,
1534
- text: '❌ Failed to send message. Use /newtask to start a new task.',
1558
+ text: this.getUiCopy('taskContinueFailed'),
1535
1559
  });
1536
1560
  }
1537
1561
  }
@@ -1545,7 +1569,7 @@ class MessageRouter {
1545
1569
  if (!this.agentDaemon) {
1546
1570
  await adapter.sendMessage({
1547
1571
  chatId: message.chatId,
1548
- text: '❌ Agent not available. Please try again later.',
1572
+ text: this.getUiCopy('agentUnavailable'),
1549
1573
  replyTo: message.messageId,
1550
1574
  });
1551
1575
  return;
@@ -1555,7 +1579,7 @@ class MessageRouter {
1555
1579
  if (!workspace) {
1556
1580
  await adapter.sendMessage({
1557
1581
  chatId: message.chatId,
1558
- text: '❌ Workspace not found. Please select a workspace with /workspace.',
1582
+ text: this.getUiCopy('workspaceMissingForTask'),
1559
1583
  replyTo: message.messageId,
1560
1584
  });
1561
1585
  return;
@@ -1585,8 +1609,8 @@ class MessageRouter {
1585
1609
  }
1586
1610
  // Send acknowledgment - concise for WhatsApp and iMessage
1587
1611
  const ackMessage = (adapter.type === 'whatsapp' || adapter.type === 'imessage')
1588
- ? `⏳ Working on it...`
1589
- : `🚀 Task Started: "${taskTitle}"\n\nI'll notify you when it's complete or if I need your input.`;
1612
+ ? this.getUiCopy('taskStartAckSimple')
1613
+ : this.getUiCopy('taskStartAck', { taskTitle });
1590
1614
  await adapter.sendMessage({
1591
1615
  chatId: message.chatId,
1592
1616
  text: ackMessage,
@@ -1616,7 +1640,9 @@ class MessageRouter {
1616
1640
  console.error('Error starting task:', error);
1617
1641
  await adapter.sendMessage({
1618
1642
  chatId: message.chatId,
1619
- text: `❌ Failed to start task: ${error instanceof Error ? error.message : 'Unknown error'}`,
1643
+ text: this.getUiCopy('taskStartFailed', {
1644
+ error: error instanceof Error ? error.message : 'Unknown error',
1645
+ }),
1620
1646
  });
1621
1647
  // Cleanup
1622
1648
  this.pendingTaskResponses.delete(task.id);
@@ -1813,7 +1839,7 @@ class MessageRouter {
1813
1839
  sessionId: pending.sessionId,
1814
1840
  });
1815
1841
  // Format approval message
1816
- let message = `🔐 *Approval Required*\n\n`;
1842
+ let message = `🔐 *${this.getUiCopy('approvalRequiredTitle')}*\n\n`;
1817
1843
  message += `**${approval.description}**\n\n`;
1818
1844
  if (approval.type === 'run_command' && approval.details?.command) {
1819
1845
  message += `\`\`\`\n${approval.details.command}\n\`\`\`\n\n`;
@@ -1840,8 +1866,8 @@ class MessageRouter {
1840
1866
  // Create inline keyboard with Approve/Deny buttons for Telegram/Discord
1841
1867
  const keyboard = [
1842
1868
  [
1843
- { text: '✅ Approve', callbackData: 'approve:' + approval.id },
1844
- { text: '❌ Deny', callbackData: 'deny:' + approval.id },
1869
+ { text: this.getUiCopy('approvalButtonApprove'), callbackData: 'approve:' + approval.id },
1870
+ { text: this.getUiCopy('approvalButtonDeny'), callbackData: 'deny:' + approval.id },
1845
1871
  ],
1846
1872
  ];
1847
1873
  try {
@@ -1867,7 +1893,7 @@ class MessageRouter {
1867
1893
  if (!approvalEntry) {
1868
1894
  await adapter.sendMessage({
1869
1895
  chatId: message.chatId,
1870
- text: '❌ No pending approval request.',
1896
+ text: this.getUiCopy('approvalNone'),
1871
1897
  });
1872
1898
  return;
1873
1899
  }
@@ -1877,14 +1903,14 @@ class MessageRouter {
1877
1903
  await this.agentDaemon?.respondToApproval(approvalId, true);
1878
1904
  await adapter.sendMessage({
1879
1905
  chatId: message.chatId,
1880
- text: '✅ Approved! Executing...',
1906
+ text: this.getUiCopy('approvalApproved'),
1881
1907
  });
1882
1908
  }
1883
1909
  catch (error) {
1884
1910
  console.error('Error responding to approval:', error);
1885
1911
  await adapter.sendMessage({
1886
1912
  chatId: message.chatId,
1887
- text: '❌ Failed to process approval.',
1913
+ text: this.getUiCopy('approvalFailed'),
1888
1914
  });
1889
1915
  }
1890
1916
  }
@@ -1898,7 +1924,7 @@ class MessageRouter {
1898
1924
  if (!approvalEntry) {
1899
1925
  await adapter.sendMessage({
1900
1926
  chatId: message.chatId,
1901
- text: '❌ No pending approval request.',
1927
+ text: this.getUiCopy('approvalNone'),
1902
1928
  });
1903
1929
  return;
1904
1930
  }
@@ -1908,14 +1934,14 @@ class MessageRouter {
1908
1934
  await this.agentDaemon?.respondToApproval(approvalId, false);
1909
1935
  await adapter.sendMessage({
1910
1936
  chatId: message.chatId,
1911
- text: '🛑 Denied. Action cancelled.',
1937
+ text: this.getUiCopy('approvalDenied'),
1912
1938
  });
1913
1939
  }
1914
1940
  catch (error) {
1915
1941
  console.error('Error responding to denial:', error);
1916
1942
  await adapter.sendMessage({
1917
1943
  chatId: message.chatId,
1918
- text: '❌ Failed to process denial.',
1944
+ text: this.getUiCopy('approvalFailed'),
1919
1945
  });
1920
1946
  }
1921
1947
  }
@@ -1926,7 +1952,7 @@ class MessageRouter {
1926
1952
  if (!this.agentDaemon) {
1927
1953
  await adapter.sendMessage({
1928
1954
  chatId: message.chatId,
1929
- text: '❌ Agent daemon not available.',
1955
+ text: this.getUiCopy('agentUnavailable'),
1930
1956
  });
1931
1957
  return;
1932
1958
  }
@@ -1936,7 +1962,10 @@ class MessageRouter {
1936
1962
  const result = await this.agentDaemon.clearStuckTasks();
1937
1963
  await adapter.sendMessage({
1938
1964
  chatId: message.chatId,
1939
- text: `✅ Queue cleared!\n\n• Running tasks cancelled: ${result.clearedRunning}\n• Queued tasks removed: ${result.clearedQueued}\n\nBrowser sessions and other resources have been cleaned up. You can now start new tasks.`,
1965
+ text: this.getUiCopy('queueCleared', {
1966
+ running: result.clearedRunning,
1967
+ queued: result.clearedQueued,
1968
+ }),
1940
1969
  });
1941
1970
  }
1942
1971
  else {
@@ -1955,7 +1984,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
1955
1984
  • \`/queue clear\` - Clear stuck tasks`;
1956
1985
  await adapter.sendMessage({
1957
1986
  chatId: message.chatId,
1958
- text: statusText,
1987
+ text: this.getUiCopy('queueStatus', { statusText }),
1959
1988
  parseMode: 'markdown',
1960
1989
  });
1961
1990
  }
@@ -2006,13 +2035,13 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2006
2035
  this.sessionRepo.update(sessionId, { state: 'idle', taskId: undefined });
2007
2036
  await adapter.sendMessage({
2008
2037
  chatId: message.chatId,
2009
- text: '🛑 Task cancelled.',
2038
+ text: this.getUiCopy('cancelled'),
2010
2039
  });
2011
2040
  }
2012
2041
  else {
2013
2042
  await adapter.sendMessage({
2014
2043
  chatId: message.chatId,
2015
- text: 'No active task to cancel.',
2044
+ text: this.getUiCopy('cancelNoActive'),
2016
2045
  });
2017
2046
  }
2018
2047
  }
@@ -2028,7 +2057,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2028
2057
  }
2029
2058
  await adapter.sendMessage({
2030
2059
  chatId: message.chatId,
2031
- text: '🆕 Ready for a new task!\n\nSend me a message describing what you want to do.',
2060
+ text: this.getUiCopy('newTaskReady'),
2032
2061
  });
2033
2062
  }
2034
2063
  /**
@@ -2038,7 +2067,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2038
2067
  if (args.length === 0) {
2039
2068
  await adapter.sendMessage({
2040
2069
  chatId: message.chatId,
2041
- text: '❌ Please specify a workspace name to remove.\n\nUsage: `/removeworkspace <name>`',
2070
+ text: this.getUiCopy('workspaceRemoveUsage'),
2042
2071
  parseMode: 'markdown',
2043
2072
  });
2044
2073
  return;
@@ -2049,7 +2078,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2049
2078
  if (!workspace) {
2050
2079
  await adapter.sendMessage({
2051
2080
  chatId: message.chatId,
2052
- text: `❌ Workspace "${workspaceName}" not found.\n\nUse /workspaces to see available workspaces.`,
2081
+ text: this.getUiCopy('workspaceNotFound', { selector: workspaceName }),
2053
2082
  });
2054
2083
  return;
2055
2084
  }
@@ -2063,7 +2092,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2063
2092
  this.workspaceRepo.delete(workspace.id);
2064
2093
  await adapter.sendMessage({
2065
2094
  chatId: message.chatId,
2066
- text: `✅ Workspace "${workspace.name}" removed successfully.`,
2095
+ text: this.getUiCopy('workspaceRemoved', { workspaceName: workspace.name }),
2067
2096
  });
2068
2097
  }
2069
2098
  /**
@@ -2085,14 +2114,14 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2085
2114
  if (!lastFailedTask) {
2086
2115
  await adapter.sendMessage({
2087
2116
  chatId: message.chatId,
2088
- text: '❌ No failed task found to retry.\n\nStart a new task by sending a message.',
2117
+ text: this.getUiCopy('retryNone'),
2089
2118
  });
2090
2119
  return;
2091
2120
  }
2092
2121
  // Re-submit the task by sending the original prompt as a new message
2093
2122
  await adapter.sendMessage({
2094
2123
  chatId: message.chatId,
2095
- text: `🔄 Retrying task...\n\nOriginal prompt: "${lastFailedTask.title}"`,
2124
+ text: this.getUiCopy('retrying', { taskTitle: lastFailedTask.title }),
2096
2125
  });
2097
2126
  // Create a synthetic message with the original prompt
2098
2127
  const retryMessage = {
@@ -2120,7 +2149,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2120
2149
  if (recentTasks.length === 0) {
2121
2150
  await adapter.sendMessage({
2122
2151
  chatId: message.chatId,
2123
- text: '📋 No task history found.\n\nStart a new task by sending a message.',
2152
+ text: this.getUiCopy('historyNone'),
2124
2153
  });
2125
2154
  return;
2126
2155
  }
@@ -2141,7 +2170,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2141
2170
  .join('\n\n');
2142
2171
  await adapter.sendMessage({
2143
2172
  chatId: message.chatId,
2144
- text: `📋 *Recent Tasks*\n\n${historyText}`,
2173
+ text: this.getUiCopy('historyHeader', { history: historyText }),
2145
2174
  parseMode: 'markdown',
2146
2175
  });
2147
2176
  }
@@ -2156,7 +2185,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2156
2185
  if (skills.length === 0) {
2157
2186
  await adapter.sendMessage({
2158
2187
  chatId: message.chatId,
2159
- text: '📚 No skills available.\n\nSkills are stored in:\n`~/Library/Application Support/cowork-os/skills/`',
2188
+ text: this.getUiCopy('skillsNone'),
2160
2189
  parseMode: 'markdown',
2161
2190
  });
2162
2191
  return;
@@ -2190,7 +2219,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2190
2219
  catch (error) {
2191
2220
  await adapter.sendMessage({
2192
2221
  chatId: message.chatId,
2193
- text: '❌ Failed to load skills.',
2222
+ text: this.getUiCopy('skillsLoadFailed'),
2194
2223
  });
2195
2224
  }
2196
2225
  }
@@ -2201,7 +2230,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2201
2230
  if (args.length === 0) {
2202
2231
  await adapter.sendMessage({
2203
2232
  chatId: message.chatId,
2204
- text: '❌ Please specify a skill ID.\n\nUsage: `/skill <id>`\n\nUse /skills to see available skills.',
2233
+ text: this.getUiCopy('skillSpecify'),
2205
2234
  parseMode: 'markdown',
2206
2235
  });
2207
2236
  return;
@@ -2214,7 +2243,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2214
2243
  if (!skill) {
2215
2244
  await adapter.sendMessage({
2216
2245
  chatId: message.chatId,
2217
- text: `❌ Skill "${skillId}" not found.\n\nUse /skills to see available skills.`,
2246
+ text: this.getUiCopy('skillNotFound', { skillId }),
2218
2247
  });
2219
2248
  return;
2220
2249
  }
@@ -2224,14 +2253,18 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2224
2253
  const statusText = newState ? '✅ enabled' : '❌ disabled';
2225
2254
  await adapter.sendMessage({
2226
2255
  chatId: message.chatId,
2227
- text: `${skill.icon || ''} *${skill.name}* is now ${statusText}`,
2256
+ text: this.getUiCopy('skillToggle', {
2257
+ emoji: skill.icon || '⚡',
2258
+ skillName: skill.name,
2259
+ statusText,
2260
+ }),
2228
2261
  parseMode: 'markdown',
2229
2262
  });
2230
2263
  }
2231
2264
  catch (error) {
2232
2265
  await adapter.sendMessage({
2233
2266
  chatId: message.chatId,
2234
- text: '❌ Failed to toggle skill.',
2267
+ text: this.getUiCopy('skillsLoadFailed'),
2235
2268
  });
2236
2269
  }
2237
2270
  }
@@ -2345,7 +2378,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2345
2378
  const statusText = newDebug ? '✅ enabled' : '❌ disabled';
2346
2379
  await adapter.sendMessage({
2347
2380
  chatId: message.chatId,
2348
- text: `🐛 Debug mode is now ${statusText}`,
2381
+ text: this.getUiCopy('debugStatus', { statusText }),
2349
2382
  });
2350
2383
  }
2351
2384
  /**
@@ -2383,14 +2416,14 @@ Node.js: \`${nodeVersion}\`
2383
2416
  const workspace = this.workspaceRepo.findById(session.workspaceId);
2384
2417
  await adapter.sendMessage({
2385
2418
  chatId: message.chatId,
2386
- text: `👋 *Welcome back!*\n\nWorkspace: *${workspace?.name || 'Unknown'}*\n\nJust send me what you'd like me to do.\n\nType /help for commands.`,
2419
+ text: this.getUiCopy('welcomeBack', { workspaceName: workspace?.name || 'Unknown' }),
2387
2420
  parseMode: 'markdown',
2388
2421
  });
2389
2422
  }
2390
2423
  else if (workspaces.length === 0) {
2391
2424
  await adapter.sendMessage({
2392
2425
  chatId: message.chatId,
2393
- text: `👋 *Welcome to CoWork!*\n\nI'm your AI coding assistant.\n\nFirst, add a workspace:\n\`/addworkspace /path/to/project\`\n\nOr add one from the desktop app.`,
2426
+ text: this.getUiCopy('welcomeNoWorkspace'),
2394
2427
  parseMode: 'markdown',
2395
2428
  });
2396
2429
  }
@@ -2400,17 +2433,16 @@ Node.js: \`${nodeVersion}\`
2400
2433
  this.sessionManager.setSessionWorkspace(sessionId, workspace.id);
2401
2434
  await adapter.sendMessage({
2402
2435
  chatId: message.chatId,
2403
- text: `👋 *Welcome to CoWork!*\n\n✅ Workspace: *${workspace.name}*\n\nJust tell me what you'd like me to do!\n\nExamples:\n• "Add dark mode support"\n• "Fix the login bug"\n• "Create a new API endpoint"`,
2436
+ text: this.getUiCopy('welcomeSingleWorkspace', { workspaceName: workspace.name }),
2404
2437
  parseMode: 'markdown',
2405
2438
  });
2406
2439
  }
2407
2440
  else {
2408
2441
  // Multiple workspaces - show selection
2409
- let text = `👋 *Welcome to CoWork!*\n\nSelect a workspace to start:\n\n`;
2410
- workspaces.forEach((ws, index) => {
2411
- text += `${index + 1}. *${ws.name}*\n`;
2412
- });
2413
- text += `\nReply with a number (e.g., \`1\`)`;
2442
+ const workspaceList = workspaces
2443
+ .map((ws, index) => `${index + 1}. *${ws.name}*`)
2444
+ .join('\n');
2445
+ const text = this.getUiCopy('welcomeSelectWorkspace', { workspaceList });
2414
2446
  await adapter.sendMessage({
2415
2447
  chatId: message.chatId,
2416
2448
  text,
@@ -2422,7 +2454,7 @@ Node.js: \`${nodeVersion}\`
2422
2454
  // Standard welcome for Telegram/Discord
2423
2455
  await adapter.sendMessage({
2424
2456
  chatId: message.chatId,
2425
- text: this.config.welcomeMessage,
2457
+ text: this.getUiCopy('welcomeStandard'),
2426
2458
  });
2427
2459
  // Show workspaces if none selected
2428
2460
  if (!session?.workspaceId && workspaces.length > 0) {
@@ -2435,70 +2467,10 @@ Node.js: \`${nodeVersion}\`
2435
2467
  getHelpText(channelType) {
2436
2468
  // Compact help for WhatsApp (mobile-friendly)
2437
2469
  if (channelType === 'whatsapp') {
2438
- return `📚 *Commands*
2439
-
2440
- *Basics*
2441
- /workspaces - Select workspace
2442
- /status - Current status
2443
- /newtask - Fresh start
2444
-
2445
- *Tasks*
2446
- /cancel - Stop task
2447
- /approve or /yes - Approve action
2448
- /deny or /no - Reject action
2449
-
2450
- *Settings*
2451
- /shell on|off - Shell access
2452
- /models - Change AI model
2453
-
2454
- ━━━━━━━━━━━━━━━
2455
- 💡 Just send your task directly!
2456
- Example: "Add a login form"`;
2470
+ return this.getUiCopy('helpCompact');
2457
2471
  }
2458
2472
  // Full help for other channels
2459
- return `📚 *Available Commands*
2460
-
2461
- *Core*
2462
- /start - Start the bot
2463
- /help - Show this help message
2464
- /status - Check bot status and workspace
2465
- /version - Show version information
2466
-
2467
- *Workspaces*
2468
- /workspaces - List available workspaces
2469
- /workspace <name> - Select a workspace
2470
- /addworkspace <path> - Add a new workspace
2471
- /removeworkspace <name> - Remove a workspace
2472
-
2473
- *Tasks*
2474
- /newtask - Start a fresh task/conversation
2475
- /cancel - Cancel current task
2476
- /retry - Retry the last failed task
2477
- /history - Show recent task history
2478
- /approve - Approve pending action (or /yes, /y)
2479
- /deny - Reject pending action (or /no, /n)
2480
- /queue - View/clear task queue
2481
-
2482
- *Models*
2483
- /providers - List available AI providers
2484
- /provider <name> - Show or change provider
2485
- /models - List available AI models
2486
- /model <name> - Show or change model
2487
-
2488
- *Skills*
2489
- /skills - List available skills
2490
- /skill <name> - Toggle a skill on/off
2491
-
2492
- *Settings*
2493
- /settings - View current settings
2494
- /shell - Enable/disable shell commands
2495
- /debug - Toggle debug mode
2496
-
2497
- 💬 *Quick Start*
2498
- 1. \`/workspaces\` → \`/workspace <name>\`
2499
- 2. \`/shell on\` (if needed)
2500
- 3. Send your task message
2501
- 4. \`/newtask\` to start fresh`;
2473
+ return this.getUiCopy('helpFull');
2502
2474
  }
2503
2475
  /**
2504
2476
  * Handle callback query from inline keyboard button press
@@ -2561,7 +2533,7 @@ Example: "Add a login form"`;
2561
2533
  if (!workspace) {
2562
2534
  await adapter.sendMessage({
2563
2535
  chatId: query.chatId,
2564
- text: '❌ Workspace not found.',
2536
+ text: this.getUiCopy('workspaceNotFoundShort'),
2565
2537
  });
2566
2538
  return;
2567
2539
  }
@@ -2569,12 +2541,18 @@ Example: "Add a login form"`;
2569
2541
  this.sessionManager.setSessionWorkspace(sessionId, workspace.id);
2570
2542
  // Update the original message with the selection
2571
2543
  if (adapter.editMessageWithKeyboard) {
2572
- await adapter.editMessageWithKeyboard(query.chatId, query.messageId, `✅ Workspace selected: *${workspace.name}*\n\`${workspace.path}\`\n\nYou can now send messages to create tasks.`);
2544
+ await adapter.editMessageWithKeyboard(query.chatId, query.messageId, this.getUiCopy('workspaceSet', {
2545
+ workspaceName: workspace.name,
2546
+ workspacePath: workspace.path,
2547
+ }));
2573
2548
  }
2574
2549
  else {
2575
2550
  await adapter.sendMessage({
2576
2551
  chatId: query.chatId,
2577
- text: `✅ Workspace set to: *${workspace.name}*\n\`${workspace.path}\``,
2552
+ text: this.getUiCopy('workspaceSet', {
2553
+ workspaceName: workspace.name,
2554
+ workspacePath: workspace.path,
2555
+ }),
2578
2556
  parseMode: 'markdown',
2579
2557
  });
2580
2558
  }
@@ -2656,7 +2634,7 @@ Example: "Add a login form"`;
2656
2634
  .find(([, data]) => data.sessionId === sessionId);
2657
2635
  if (!approvalEntry) {
2658
2636
  if (adapter.editMessageWithKeyboard) {
2659
- await adapter.editMessageWithKeyboard(query.chatId, query.messageId, '❌ No pending approval request (may have expired).');
2637
+ await adapter.editMessageWithKeyboard(query.chatId, query.messageId, this.getUiCopy('approvalNone'));
2660
2638
  }
2661
2639
  return;
2662
2640
  }
@@ -2664,7 +2642,9 @@ Example: "Add a login form"`;
2664
2642
  this.pendingApprovals.delete(approvalId);
2665
2643
  try {
2666
2644
  await this.agentDaemon?.respondToApproval(approvalId, approved);
2667
- const statusText = approved ? '✅ Approved! Executing...' : '🛑 Denied. Action cancelled.';
2645
+ const statusText = approved
2646
+ ? this.getUiCopy('approvalApproved')
2647
+ : this.getUiCopy('approvalDenied');
2668
2648
  if (adapter.editMessageWithKeyboard) {
2669
2649
  await adapter.editMessageWithKeyboard(query.chatId, query.messageId, statusText);
2670
2650
  }
@@ -2679,7 +2659,7 @@ Example: "Add a login form"`;
2679
2659
  console.error('Error responding to approval:', error);
2680
2660
  await adapter.sendMessage({
2681
2661
  chatId: query.chatId,
2682
- text: '❌ Failed to process response.',
2662
+ text: this.getUiCopy('responseFailed'),
2683
2663
  });
2684
2664
  }
2685
2665
  }