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
@@ -43,6 +43,7 @@ import { PersonalityManager } from '../settings/personality-manager';
43
43
  import {
44
44
  getChannelMessage,
45
45
  getCompletionMessage,
46
+ getChannelUiCopy,
46
47
  DEFAULT_CHANNEL_CONTEXT,
47
48
  type ChannelMessageContext,
48
49
  } from '../../shared/channelMessages';
@@ -152,6 +153,7 @@ export class MessageRouter {
152
153
  agentName: settings.agentName || 'CoWork',
153
154
  userName: settings.relationship?.userName,
154
155
  personality: settings.activePersonality || 'professional',
156
+ persona: settings.activePersona,
155
157
  emojiUsage: settings.responseStyle?.emojiUsage || 'minimal',
156
158
  quirks: settings.quirks || DEFAULT_QUIRKS,
157
159
  };
@@ -162,6 +164,13 @@ export class MessageRouter {
162
164
  return DEFAULT_CHANNEL_CONTEXT;
163
165
  }
164
166
 
167
+ private getUiCopy(
168
+ key: Parameters<typeof getChannelUiCopy>[0],
169
+ replacements?: Record<string, string | number>
170
+ ): string {
171
+ return getChannelUiCopy(key, this.getMessageContext(), replacements);
172
+ }
173
+
165
174
  /**
166
175
  * Get or create the temp workspace for sessions without a workspace
167
176
  */
@@ -598,9 +607,9 @@ export class MessageRouter {
598
607
  let responseText: string;
599
608
 
600
609
  if (securityResult.pairingRequired) {
601
- responseText = this.config.pairingRequiredMessage!;
610
+ responseText = this.getUiCopy('pairingRequired');
602
611
  } else {
603
- responseText = this.config.unauthorizedMessage!;
612
+ responseText = this.getUiCopy('unauthorized');
604
613
  }
605
614
 
606
615
  try {
@@ -647,9 +656,11 @@ export class MessageRouter {
647
656
  if (!isNaN(num) && num > 0 && num <= workspaces.length) {
648
657
  const workspace = workspaces[num - 1];
649
658
  this.sessionManager.setSessionWorkspace(sessionId, workspace.id);
659
+ const selectedText = this.getUiCopy('workspaceSelected', { workspaceName: workspace.name });
660
+ const exampleText = this.getUiCopy('workspaceSelectedExample');
650
661
  await adapter.sendMessage({
651
662
  chatId: message.chatId,
652
- text: `✅ *${workspace.name}* selected!\n\nYou can now send me tasks.\n\nExample: "Create a new React component called Button"`,
663
+ text: `${selectedText}\n\n${exampleText}`,
653
664
  parseMode: 'markdown',
654
665
  });
655
666
  return;
@@ -662,9 +673,11 @@ export class MessageRouter {
662
673
  );
663
674
  if (matchedWorkspace) {
664
675
  this.sessionManager.setSessionWorkspace(sessionId, matchedWorkspace.id);
676
+ const selectedText = this.getUiCopy('workspaceSelected', { workspaceName: matchedWorkspace.name });
677
+ const exampleText = this.getUiCopy('workspaceSelectedExample');
665
678
  await adapter.sendMessage({
666
679
  chatId: message.chatId,
667
- text: `✅ *${matchedWorkspace.name}* selected!\n\nYou can now send me tasks.\n\nExample: "Create a new React component called Button"`,
680
+ text: `${selectedText}\n\n${exampleText}`,
668
681
  parseMode: 'markdown',
669
682
  });
670
683
  return;
@@ -746,7 +759,7 @@ export class MessageRouter {
746
759
  if (args.length === 0) {
747
760
  await adapter.sendMessage({
748
761
  chatId: message.chatId,
749
- text: '🔐 Please provide a pairing code.\n\nUsage: `/pair <code>`',
762
+ text: this.getUiCopy('pairingPrompt'),
750
763
  parseMode: 'markdown',
751
764
  });
752
765
  } else {
@@ -814,7 +827,7 @@ export class MessageRouter {
814
827
  default:
815
828
  await adapter.sendMessage({
816
829
  chatId: message.chatId,
817
- text: `Unknown command: ${command}\n\nUse /help to see available commands.`,
830
+ text: this.getUiCopy('unknownCommand', { command }),
818
831
  replyTo: message.messageId,
819
832
  });
820
833
  }
@@ -829,22 +842,25 @@ export class MessageRouter {
829
842
  sessionId: string
830
843
  ): Promise<void> {
831
844
  const session = this.sessionRepo.findById(sessionId);
832
- let statusText = '✅ Bot is online and ready.\n\n';
845
+ let statusText = `✅ ${this.getUiCopy('statusHeader')}\n\n`;
833
846
 
834
847
  if (session?.workspaceId) {
835
848
  const workspace = this.workspaceRepo.findById(session.workspaceId);
836
849
  if (workspace) {
837
- statusText += `📁 Current workspace: ${workspace.name}\n`;
838
- statusText += ` Path: ${workspace.path}\n`;
850
+ statusText += this.getUiCopy('workspaceCurrent', {
851
+ workspaceName: workspace.name,
852
+ workspacePath: workspace.path,
853
+ });
854
+ statusText += '\n';
839
855
  }
840
856
  } else {
841
- statusText += '⚠️ No workspace selected. Use /workspaces to see available workspaces.';
857
+ statusText += this.getUiCopy('statusNoWorkspace');
842
858
  }
843
859
 
844
860
  if (session?.taskId) {
845
861
  const task = this.taskRepo.findById(session.taskId);
846
862
  if (task) {
847
- statusText += `\n🔄 Active task: ${task.title} (${task.status})`;
863
+ statusText += `\n${this.getUiCopy('statusActiveTask', { taskTitle: task.title, status: task.status })}`;
848
864
  }
849
865
  }
850
866
 
@@ -866,7 +882,7 @@ export class MessageRouter {
866
882
  if (workspaces.length === 0) {
867
883
  await adapter.sendMessage({
868
884
  chatId: message.chatId,
869
- text: '📁 No workspaces configured yet.\n\nAdd a workspace in the CoWork desktop app first, or use:\n`/addworkspace /path/to/your/project`',
885
+ text: this.getUiCopy('workspacesNone'),
870
886
  parseMode: 'markdown',
871
887
  });
872
888
  return;
@@ -874,13 +890,12 @@ export class MessageRouter {
874
890
 
875
891
  // WhatsApp and iMessage don't support inline keyboards - use text-based selection
876
892
  if (adapter.type === 'whatsapp' || adapter.type === 'imessage') {
877
- let text = '📁 *Available Workspaces*\n\n';
893
+ let text = `${this.getUiCopy('workspacesHeader')}\n\n`;
878
894
  workspaces.forEach((ws, index) => {
879
895
  text += `${index + 1}. *${ws.name}*\n \`${ws.path}\`\n\n`;
880
896
  });
881
897
  text += '━━━━━━━━━━━━━━━\n';
882
- text += 'Reply with the number or name to select.\n';
883
- text += 'Example: `1` or `myproject`';
898
+ text += this.getUiCopy('workspacesFooter');
884
899
 
885
900
  await adapter.sendMessage({
886
901
  chatId: message.chatId,
@@ -900,7 +915,7 @@ export class MessageRouter {
900
915
  }]);
901
916
  }
902
917
 
903
- let text = '📁 *Available Workspaces*\n\nTap a workspace to select it:';
918
+ let text = `${this.getUiCopy('workspacesHeader')}\n\n${this.getUiCopy('workspacesSelectPrompt')}`;
904
919
 
905
920
  await adapter.sendMessage({
906
921
  chatId: message.chatId,
@@ -938,7 +953,10 @@ export class MessageRouter {
938
953
  const displayName = isTempWorkspace ? 'Temporary Workspace (work in a folder for persistence)' : workspace.name;
939
954
  await adapter.sendMessage({
940
955
  chatId: message.chatId,
941
- text: `📁 Current workspace: *${displayName}*\n\`${workspace.path}\`\n\nUse \`/workspaces\` to see available workspaces.`,
956
+ text: this.getUiCopy('workspaceCurrent', {
957
+ workspaceName: displayName,
958
+ workspacePath: workspace.path,
959
+ }),
942
960
  parseMode: 'markdown',
943
961
  });
944
962
  return;
@@ -946,7 +964,7 @@ export class MessageRouter {
946
964
  }
947
965
  await adapter.sendMessage({
948
966
  chatId: message.chatId,
949
- text: 'No workspace selected. Use `/workspaces` to see available workspaces.',
967
+ text: this.getUiCopy('workspaceNoneSelected'),
950
968
  parseMode: 'markdown',
951
969
  });
952
970
  return;
@@ -970,7 +988,7 @@ export class MessageRouter {
970
988
  if (!workspace) {
971
989
  await adapter.sendMessage({
972
990
  chatId: message.chatId,
973
- text: `❌ Workspace not found: "${selector}"\n\nUse /workspaces to see available workspaces.`,
991
+ text: this.getUiCopy('workspaceNotFound', { selector }),
974
992
  });
975
993
  return;
976
994
  }
@@ -980,7 +998,10 @@ export class MessageRouter {
980
998
 
981
999
  await adapter.sendMessage({
982
1000
  chatId: message.chatId,
983
- text: `✅ Workspace set to: *${workspace.name}*\n\`${workspace.path}\`\n\nYou can now send messages to create tasks in this workspace.`,
1001
+ text: this.getUiCopy('workspaceSet', {
1002
+ workspaceName: workspace.name,
1003
+ workspacePath: workspace.path,
1004
+ }),
984
1005
  parseMode: 'markdown',
985
1006
  });
986
1007
  }
@@ -997,7 +1018,7 @@ export class MessageRouter {
997
1018
  if (args.length === 0) {
998
1019
  await adapter.sendMessage({
999
1020
  chatId: message.chatId,
1000
- text: '📁 *Add Workspace*\n\nUsage: `/addworkspace <path>`\n\nExample:\n`/addworkspace /Users/john/projects/myapp`\n`/addworkspace ~/Documents`',
1021
+ text: this.getUiCopy('workspaceAddUsage'),
1001
1022
  parseMode: 'markdown',
1002
1023
  });
1003
1024
  return;
@@ -1021,7 +1042,7 @@ export class MessageRouter {
1021
1042
  if (!stats.isDirectory()) {
1022
1043
  await adapter.sendMessage({
1023
1044
  chatId: message.chatId,
1024
- text: `❌ Path is not a directory: \`${workspacePath}\``,
1045
+ text: this.getUiCopy('workspacePathNotDir', { workspacePath }),
1025
1046
  parseMode: 'markdown',
1026
1047
  });
1027
1048
  return;
@@ -1029,7 +1050,7 @@ export class MessageRouter {
1029
1050
  } catch {
1030
1051
  await adapter.sendMessage({
1031
1052
  chatId: message.chatId,
1032
- text: `❌ Directory not found: \`${workspacePath}\``,
1053
+ text: this.getUiCopy('workspacePathNotFound', { workspacePath }),
1033
1054
  parseMode: 'markdown',
1034
1055
  });
1035
1056
  return;
@@ -1043,7 +1064,10 @@ export class MessageRouter {
1043
1064
  this.sessionManager.setSessionWorkspace(sessionId, existing.id);
1044
1065
  await adapter.sendMessage({
1045
1066
  chatId: message.chatId,
1046
- text: `📁 Workspace already exists!\n\n✅ Selected: *${existing.name}*\n\`${existing.path}\``,
1067
+ text: this.getUiCopy('workspaceAlreadyExists', {
1068
+ workspaceName: existing.name,
1069
+ workspacePath: existing.path,
1070
+ }),
1047
1071
  parseMode: 'markdown',
1048
1072
  });
1049
1073
  return;
@@ -1080,7 +1104,10 @@ export class MessageRouter {
1080
1104
 
1081
1105
  await adapter.sendMessage({
1082
1106
  chatId: message.chatId,
1083
- text: `✅ Workspace added and selected!\n\n📁 *${workspace.name}*\n\`${workspace.path}\`\n\nYou can now send messages to create tasks in this workspace.`,
1107
+ text: this.getUiCopy('workspaceAdded', {
1108
+ workspaceName: workspace.name,
1109
+ workspacePath: workspace.path,
1110
+ }),
1084
1111
  parseMode: 'markdown',
1085
1112
  });
1086
1113
  }
@@ -1581,7 +1608,7 @@ export class MessageRouter {
1581
1608
  if (!workspace) {
1582
1609
  await adapter.sendMessage({
1583
1610
  chatId: message.chatId,
1584
- text: '❌ Workspace not found.',
1611
+ text: this.getUiCopy('workspaceNotFoundForShell'),
1585
1612
  });
1586
1613
  return;
1587
1614
  }
@@ -1607,7 +1634,7 @@ export class MessageRouter {
1607
1634
  } else {
1608
1635
  await adapter.sendMessage({
1609
1636
  chatId: message.chatId,
1610
- text: '❌ Invalid option. Use `/shell on` or `/shell off`',
1637
+ text: this.getUiCopy('shellInvalidOption'),
1611
1638
  parseMode: 'markdown',
1612
1639
  });
1613
1640
  return;
@@ -1743,7 +1770,7 @@ export class MessageRouter {
1743
1770
  if (result.success) {
1744
1771
  await adapter.sendMessage({
1745
1772
  chatId: message.chatId,
1746
- text: '✅ Pairing successful! You can now use the bot.',
1773
+ text: this.getUiCopy('pairingSuccess'),
1747
1774
  replyTo: message.messageId,
1748
1775
  });
1749
1776
 
@@ -1756,7 +1783,9 @@ export class MessageRouter {
1756
1783
  } else {
1757
1784
  await adapter.sendMessage({
1758
1785
  chatId: message.chatId,
1759
- text: `❌ ${result.error || 'Invalid pairing code. Please try again.'}`,
1786
+ text: this.getUiCopy('pairingFailed', {
1787
+ error: result.error || 'Invalid pairing code. Please try again.',
1788
+ }),
1760
1789
  replyTo: message.messageId,
1761
1790
  });
1762
1791
  }
@@ -1793,8 +1822,8 @@ export class MessageRouter {
1793
1822
  if (this.agentDaemon) {
1794
1823
  try {
1795
1824
  const statusMsg = isActive
1796
- ? '💬 Sending follow-up message...'
1797
- : '💬 Continuing conversation...';
1825
+ ? '💬 Got it — adding that to the current task...'
1826
+ : '💬 Picking up where we left off...';
1798
1827
  await adapter.sendMessage({
1799
1828
  chatId: message.chatId,
1800
1829
  text: statusMsg,
@@ -1809,13 +1838,13 @@ export class MessageRouter {
1809
1838
  });
1810
1839
 
1811
1840
  await this.agentDaemon.sendMessage(session!.taskId!, message.text);
1812
- } catch (error) {
1813
- console.error('Error sending follow-up message:', error);
1814
- await adapter.sendMessage({
1815
- chatId: message.chatId,
1816
- text: '❌ Failed to send message. Use /newtask to start a new task.',
1817
- });
1818
- }
1841
+ } catch (error) {
1842
+ console.error('Error sending follow-up message:', error);
1843
+ await adapter.sendMessage({
1844
+ chatId: message.chatId,
1845
+ text: this.getUiCopy('taskContinueFailed'),
1846
+ });
1847
+ }
1819
1848
  }
1820
1849
  return;
1821
1850
  }
@@ -1828,7 +1857,7 @@ export class MessageRouter {
1828
1857
  if (!this.agentDaemon) {
1829
1858
  await adapter.sendMessage({
1830
1859
  chatId: message.chatId,
1831
- text: '❌ Agent not available. Please try again later.',
1860
+ text: this.getUiCopy('agentUnavailable'),
1832
1861
  replyTo: message.messageId,
1833
1862
  });
1834
1863
  return;
@@ -1839,7 +1868,7 @@ export class MessageRouter {
1839
1868
  if (!workspace) {
1840
1869
  await adapter.sendMessage({
1841
1870
  chatId: message.chatId,
1842
- text: '❌ Workspace not found. Please select a workspace with /workspace.',
1871
+ text: this.getUiCopy('workspaceMissingForTask'),
1843
1872
  replyTo: message.messageId,
1844
1873
  });
1845
1874
  return;
@@ -1875,8 +1904,8 @@ export class MessageRouter {
1875
1904
 
1876
1905
  // Send acknowledgment - concise for WhatsApp and iMessage
1877
1906
  const ackMessage = (adapter.type === 'whatsapp' || adapter.type === 'imessage')
1878
- ? `⏳ Working on it...`
1879
- : `🚀 Task Started: "${taskTitle}"\n\nI'll notify you when it's complete or if I need your input.`;
1907
+ ? this.getUiCopy('taskStartAckSimple')
1908
+ : this.getUiCopy('taskStartAck', { taskTitle });
1880
1909
  await adapter.sendMessage({
1881
1910
  chatId: message.chatId,
1882
1911
  text: ackMessage,
@@ -1907,7 +1936,9 @@ export class MessageRouter {
1907
1936
  console.error('Error starting task:', error);
1908
1937
  await adapter.sendMessage({
1909
1938
  chatId: message.chatId,
1910
- text: `❌ Failed to start task: ${error instanceof Error ? error.message : 'Unknown error'}`,
1939
+ text: this.getUiCopy('taskStartFailed', {
1940
+ error: error instanceof Error ? error.message : 'Unknown error',
1941
+ }),
1911
1942
  });
1912
1943
 
1913
1944
  // Cleanup
@@ -2119,7 +2150,7 @@ export class MessageRouter {
2119
2150
  });
2120
2151
 
2121
2152
  // Format approval message
2122
- let message = `🔐 *Approval Required*\n\n`;
2153
+ let message = `🔐 *${this.getUiCopy('approvalRequiredTitle')}*\n\n`;
2123
2154
  message += `**${approval.description}**\n\n`;
2124
2155
 
2125
2156
  if (approval.type === 'run_command' && approval.details?.command) {
@@ -2147,8 +2178,8 @@ export class MessageRouter {
2147
2178
  // Create inline keyboard with Approve/Deny buttons for Telegram/Discord
2148
2179
  const keyboard: InlineKeyboardButton[][] = [
2149
2180
  [
2150
- { text: '✅ Approve', callbackData: 'approve:' + approval.id },
2151
- { text: '❌ Deny', callbackData: 'deny:' + approval.id },
2181
+ { text: this.getUiCopy('approvalButtonApprove'), callbackData: 'approve:' + approval.id },
2182
+ { text: this.getUiCopy('approvalButtonDeny'), callbackData: 'deny:' + approval.id },
2152
2183
  ],
2153
2184
  ];
2154
2185
 
@@ -2180,7 +2211,7 @@ export class MessageRouter {
2180
2211
  if (!approvalEntry) {
2181
2212
  await adapter.sendMessage({
2182
2213
  chatId: message.chatId,
2183
- text: '❌ No pending approval request.',
2214
+ text: this.getUiCopy('approvalNone'),
2184
2215
  });
2185
2216
  return;
2186
2217
  }
@@ -2192,13 +2223,13 @@ export class MessageRouter {
2192
2223
  await this.agentDaemon?.respondToApproval(approvalId, true);
2193
2224
  await adapter.sendMessage({
2194
2225
  chatId: message.chatId,
2195
- text: '✅ Approved! Executing...',
2226
+ text: this.getUiCopy('approvalApproved'),
2196
2227
  });
2197
2228
  } catch (error) {
2198
2229
  console.error('Error responding to approval:', error);
2199
2230
  await adapter.sendMessage({
2200
2231
  chatId: message.chatId,
2201
- text: '❌ Failed to process approval.',
2232
+ text: this.getUiCopy('approvalFailed'),
2202
2233
  });
2203
2234
  }
2204
2235
  }
@@ -2218,7 +2249,7 @@ export class MessageRouter {
2218
2249
  if (!approvalEntry) {
2219
2250
  await adapter.sendMessage({
2220
2251
  chatId: message.chatId,
2221
- text: '❌ No pending approval request.',
2252
+ text: this.getUiCopy('approvalNone'),
2222
2253
  });
2223
2254
  return;
2224
2255
  }
@@ -2230,13 +2261,13 @@ export class MessageRouter {
2230
2261
  await this.agentDaemon?.respondToApproval(approvalId, false);
2231
2262
  await adapter.sendMessage({
2232
2263
  chatId: message.chatId,
2233
- text: '🛑 Denied. Action cancelled.',
2264
+ text: this.getUiCopy('approvalDenied'),
2234
2265
  });
2235
2266
  } catch (error) {
2236
2267
  console.error('Error responding to denial:', error);
2237
2268
  await adapter.sendMessage({
2238
2269
  chatId: message.chatId,
2239
- text: '❌ Failed to process denial.',
2270
+ text: this.getUiCopy('approvalFailed'),
2240
2271
  });
2241
2272
  }
2242
2273
  }
@@ -2252,7 +2283,7 @@ export class MessageRouter {
2252
2283
  if (!this.agentDaemon) {
2253
2284
  await adapter.sendMessage({
2254
2285
  chatId: message.chatId,
2255
- text: '❌ Agent daemon not available.',
2286
+ text: this.getUiCopy('agentUnavailable'),
2256
2287
  });
2257
2288
  return;
2258
2289
  }
@@ -2264,7 +2295,10 @@ export class MessageRouter {
2264
2295
  const result = await this.agentDaemon.clearStuckTasks();
2265
2296
  await adapter.sendMessage({
2266
2297
  chatId: message.chatId,
2267
- 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.`,
2298
+ text: this.getUiCopy('queueCleared', {
2299
+ running: result.clearedRunning,
2300
+ queued: result.clearedQueued,
2301
+ }),
2268
2302
  });
2269
2303
  } else {
2270
2304
  // Show queue status
@@ -2283,7 +2317,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2283
2317
 
2284
2318
  await adapter.sendMessage({
2285
2319
  chatId: message.chatId,
2286
- text: statusText,
2320
+ text: this.getUiCopy('queueStatus', { statusText }),
2287
2321
  parseMode: 'markdown',
2288
2322
  });
2289
2323
  }
@@ -2348,12 +2382,12 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2348
2382
 
2349
2383
  await adapter.sendMessage({
2350
2384
  chatId: message.chatId,
2351
- text: '🛑 Task cancelled.',
2385
+ text: this.getUiCopy('cancelled'),
2352
2386
  });
2353
2387
  } else {
2354
2388
  await adapter.sendMessage({
2355
2389
  chatId: message.chatId,
2356
- text: 'No active task to cancel.',
2390
+ text: this.getUiCopy('cancelNoActive'),
2357
2391
  });
2358
2392
  }
2359
2393
  }
@@ -2376,7 +2410,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2376
2410
 
2377
2411
  await adapter.sendMessage({
2378
2412
  chatId: message.chatId,
2379
- text: '🆕 Ready for a new task!\n\nSend me a message describing what you want to do.',
2413
+ text: this.getUiCopy('newTaskReady'),
2380
2414
  });
2381
2415
  }
2382
2416
 
@@ -2392,7 +2426,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2392
2426
  if (args.length === 0) {
2393
2427
  await adapter.sendMessage({
2394
2428
  chatId: message.chatId,
2395
- text: '❌ Please specify a workspace name to remove.\n\nUsage: `/removeworkspace <name>`',
2429
+ text: this.getUiCopy('workspaceRemoveUsage'),
2396
2430
  parseMode: 'markdown',
2397
2431
  });
2398
2432
  return;
@@ -2407,7 +2441,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2407
2441
  if (!workspace) {
2408
2442
  await adapter.sendMessage({
2409
2443
  chatId: message.chatId,
2410
- text: `❌ Workspace "${workspaceName}" not found.\n\nUse /workspaces to see available workspaces.`,
2444
+ text: this.getUiCopy('workspaceNotFound', { selector: workspaceName }),
2411
2445
  });
2412
2446
  return;
2413
2447
  }
@@ -2424,7 +2458,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2424
2458
 
2425
2459
  await adapter.sendMessage({
2426
2460
  chatId: message.chatId,
2427
- text: `✅ Workspace "${workspace.name}" removed successfully.`,
2461
+ text: this.getUiCopy('workspaceRemoved', { workspaceName: workspace.name }),
2428
2462
  });
2429
2463
  }
2430
2464
 
@@ -2454,7 +2488,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2454
2488
  if (!lastFailedTask) {
2455
2489
  await adapter.sendMessage({
2456
2490
  chatId: message.chatId,
2457
- text: '❌ No failed task found to retry.\n\nStart a new task by sending a message.',
2491
+ text: this.getUiCopy('retryNone'),
2458
2492
  });
2459
2493
  return;
2460
2494
  }
@@ -2462,7 +2496,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2462
2496
  // Re-submit the task by sending the original prompt as a new message
2463
2497
  await adapter.sendMessage({
2464
2498
  chatId: message.chatId,
2465
- text: `🔄 Retrying task...\n\nOriginal prompt: "${lastFailedTask.title}"`,
2499
+ text: this.getUiCopy('retrying', { taskTitle: lastFailedTask.title }),
2466
2500
  });
2467
2501
 
2468
2502
  // Create a synthetic message with the original prompt
@@ -2500,7 +2534,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2500
2534
  if (recentTasks.length === 0) {
2501
2535
  await adapter.sendMessage({
2502
2536
  chatId: message.chatId,
2503
- text: '📋 No task history found.\n\nStart a new task by sending a message.',
2537
+ text: this.getUiCopy('historyNone'),
2504
2538
  });
2505
2539
  return;
2506
2540
  }
@@ -2524,7 +2558,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2524
2558
 
2525
2559
  await adapter.sendMessage({
2526
2560
  chatId: message.chatId,
2527
- text: `📋 *Recent Tasks*\n\n${historyText}`,
2561
+ text: this.getUiCopy('historyHeader', { history: historyText }),
2528
2562
  parseMode: 'markdown',
2529
2563
  });
2530
2564
  }
@@ -2545,7 +2579,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2545
2579
  if (skills.length === 0) {
2546
2580
  await adapter.sendMessage({
2547
2581
  chatId: message.chatId,
2548
- text: '📚 No skills available.\n\nSkills are stored in:\n`~/Library/Application Support/cowork-os/skills/`',
2582
+ text: this.getUiCopy('skillsNone'),
2549
2583
  parseMode: 'markdown',
2550
2584
  });
2551
2585
  return;
@@ -2582,7 +2616,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2582
2616
  } catch (error) {
2583
2617
  await adapter.sendMessage({
2584
2618
  chatId: message.chatId,
2585
- text: '❌ Failed to load skills.',
2619
+ text: this.getUiCopy('skillsLoadFailed'),
2586
2620
  });
2587
2621
  }
2588
2622
  }
@@ -2599,7 +2633,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2599
2633
  if (args.length === 0) {
2600
2634
  await adapter.sendMessage({
2601
2635
  chatId: message.chatId,
2602
- text: '❌ Please specify a skill ID.\n\nUsage: `/skill <id>`\n\nUse /skills to see available skills.',
2636
+ text: this.getUiCopy('skillSpecify'),
2603
2637
  parseMode: 'markdown',
2604
2638
  });
2605
2639
  return;
@@ -2614,7 +2648,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2614
2648
  if (!skill) {
2615
2649
  await adapter.sendMessage({
2616
2650
  chatId: message.chatId,
2617
- text: `❌ Skill "${skillId}" not found.\n\nUse /skills to see available skills.`,
2651
+ text: this.getUiCopy('skillNotFound', { skillId }),
2618
2652
  });
2619
2653
  return;
2620
2654
  }
@@ -2626,13 +2660,17 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2626
2660
  const statusText = newState ? '✅ enabled' : '❌ disabled';
2627
2661
  await adapter.sendMessage({
2628
2662
  chatId: message.chatId,
2629
- text: `${skill.icon || ''} *${skill.name}* is now ${statusText}`,
2663
+ text: this.getUiCopy('skillToggle', {
2664
+ emoji: skill.icon || '⚡',
2665
+ skillName: skill.name,
2666
+ statusText,
2667
+ }),
2630
2668
  parseMode: 'markdown',
2631
2669
  });
2632
2670
  } catch (error) {
2633
2671
  await adapter.sendMessage({
2634
2672
  chatId: message.chatId,
2635
- text: '❌ Failed to toggle skill.',
2673
+ text: this.getUiCopy('skillsLoadFailed'),
2636
2674
  });
2637
2675
  }
2638
2676
  }
@@ -2777,7 +2815,7 @@ ${status.queuedCount > 0 ? `Queued task IDs: ${status.queuedTaskIds.join(', ')}`
2777
2815
  const statusText = newDebug ? '✅ enabled' : '❌ disabled';
2778
2816
  await adapter.sendMessage({
2779
2817
  chatId: message.chatId,
2780
- text: `🐛 Debug mode is now ${statusText}`,
2818
+ text: this.getUiCopy('debugStatus', { statusText }),
2781
2819
  });
2782
2820
  }
2783
2821
 
@@ -2827,13 +2865,13 @@ Node.js: \`${nodeVersion}\`
2827
2865
  const workspace = this.workspaceRepo.findById(session.workspaceId);
2828
2866
  await adapter.sendMessage({
2829
2867
  chatId: message.chatId,
2830
- text: `👋 *Welcome back!*\n\nWorkspace: *${workspace?.name || 'Unknown'}*\n\nJust send me what you'd like me to do.\n\nType /help for commands.`,
2868
+ text: this.getUiCopy('welcomeBack', { workspaceName: workspace?.name || 'Unknown' }),
2831
2869
  parseMode: 'markdown',
2832
2870
  });
2833
2871
  } else if (workspaces.length === 0) {
2834
2872
  await adapter.sendMessage({
2835
2873
  chatId: message.chatId,
2836
- 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.`,
2874
+ text: this.getUiCopy('welcomeNoWorkspace'),
2837
2875
  parseMode: 'markdown',
2838
2876
  });
2839
2877
  } else if (workspaces.length === 1) {
@@ -2842,16 +2880,15 @@ Node.js: \`${nodeVersion}\`
2842
2880
  this.sessionManager.setSessionWorkspace(sessionId, workspace.id);
2843
2881
  await adapter.sendMessage({
2844
2882
  chatId: message.chatId,
2845
- 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"`,
2883
+ text: this.getUiCopy('welcomeSingleWorkspace', { workspaceName: workspace.name }),
2846
2884
  parseMode: 'markdown',
2847
2885
  });
2848
2886
  } else {
2849
2887
  // Multiple workspaces - show selection
2850
- let text = `👋 *Welcome to CoWork!*\n\nSelect a workspace to start:\n\n`;
2851
- workspaces.forEach((ws, index) => {
2852
- text += `${index + 1}. *${ws.name}*\n`;
2853
- });
2854
- text += `\nReply with a number (e.g., \`1\`)`;
2888
+ const workspaceList = workspaces
2889
+ .map((ws, index) => `${index + 1}. *${ws.name}*`)
2890
+ .join('\n');
2891
+ const text = this.getUiCopy('welcomeSelectWorkspace', { workspaceList });
2855
2892
 
2856
2893
  await adapter.sendMessage({
2857
2894
  chatId: message.chatId,
@@ -2865,7 +2902,7 @@ Node.js: \`${nodeVersion}\`
2865
2902
  // Standard welcome for Telegram/Discord
2866
2903
  await adapter.sendMessage({
2867
2904
  chatId: message.chatId,
2868
- text: this.config.welcomeMessage!,
2905
+ text: this.getUiCopy('welcomeStandard'),
2869
2906
  });
2870
2907
 
2871
2908
  // Show workspaces if none selected
@@ -2880,71 +2917,11 @@ Node.js: \`${nodeVersion}\`
2880
2917
  private getHelpText(channelType?: ChannelType): string {
2881
2918
  // Compact help for WhatsApp (mobile-friendly)
2882
2919
  if (channelType === 'whatsapp') {
2883
- return `📚 *Commands*
2884
-
2885
- *Basics*
2886
- /workspaces - Select workspace
2887
- /status - Current status
2888
- /newtask - Fresh start
2889
-
2890
- *Tasks*
2891
- /cancel - Stop task
2892
- /approve or /yes - Approve action
2893
- /deny or /no - Reject action
2894
-
2895
- *Settings*
2896
- /shell on|off - Shell access
2897
- /models - Change AI model
2898
-
2899
- ━━━━━━━━━━━━━━━
2900
- 💡 Just send your task directly!
2901
- Example: "Add a login form"`;
2920
+ return this.getUiCopy('helpCompact');
2902
2921
  }
2903
2922
 
2904
2923
  // Full help for other channels
2905
- return `📚 *Available Commands*
2906
-
2907
- *Core*
2908
- /start - Start the bot
2909
- /help - Show this help message
2910
- /status - Check bot status and workspace
2911
- /version - Show version information
2912
-
2913
- *Workspaces*
2914
- /workspaces - List available workspaces
2915
- /workspace <name> - Select a workspace
2916
- /addworkspace <path> - Add a new workspace
2917
- /removeworkspace <name> - Remove a workspace
2918
-
2919
- *Tasks*
2920
- /newtask - Start a fresh task/conversation
2921
- /cancel - Cancel current task
2922
- /retry - Retry the last failed task
2923
- /history - Show recent task history
2924
- /approve - Approve pending action (or /yes, /y)
2925
- /deny - Reject pending action (or /no, /n)
2926
- /queue - View/clear task queue
2927
-
2928
- *Models*
2929
- /providers - List available AI providers
2930
- /provider <name> - Show or change provider
2931
- /models - List available AI models
2932
- /model <name> - Show or change model
2933
-
2934
- *Skills*
2935
- /skills - List available skills
2936
- /skill <name> - Toggle a skill on/off
2937
-
2938
- *Settings*
2939
- /settings - View current settings
2940
- /shell - Enable/disable shell commands
2941
- /debug - Toggle debug mode
2942
-
2943
- 💬 *Quick Start*
2944
- 1. \`/workspaces\` → \`/workspace <name>\`
2945
- 2. \`/shell on\` (if needed)
2946
- 3. Send your task message
2947
- 4. \`/newtask\` to start fresh`;
2924
+ return this.getUiCopy('helpFull');
2948
2925
  }
2949
2926
 
2950
2927
  /**
@@ -3023,7 +3000,7 @@ Example: "Add a login form"`;
3023
3000
  if (!workspace) {
3024
3001
  await adapter.sendMessage({
3025
3002
  chatId: query.chatId,
3026
- text: '❌ Workspace not found.',
3003
+ text: this.getUiCopy('workspaceNotFoundShort'),
3027
3004
  });
3028
3005
  return;
3029
3006
  }
@@ -3036,12 +3013,18 @@ Example: "Add a login form"`;
3036
3013
  await adapter.editMessageWithKeyboard(
3037
3014
  query.chatId,
3038
3015
  query.messageId,
3039
- `✅ Workspace selected: *${workspace.name}*\n\`${workspace.path}\`\n\nYou can now send messages to create tasks.`
3016
+ this.getUiCopy('workspaceSet', {
3017
+ workspaceName: workspace.name,
3018
+ workspacePath: workspace.path,
3019
+ })
3040
3020
  );
3041
3021
  } else {
3042
3022
  await adapter.sendMessage({
3043
3023
  chatId: query.chatId,
3044
- text: `✅ Workspace set to: *${workspace.name}*\n\`${workspace.path}\``,
3024
+ text: this.getUiCopy('workspaceSet', {
3025
+ workspaceName: workspace.name,
3026
+ workspacePath: workspace.path,
3027
+ }),
3045
3028
  parseMode: 'markdown',
3046
3029
  });
3047
3030
  }
@@ -3157,7 +3140,7 @@ Example: "Add a login form"`;
3157
3140
  await adapter.editMessageWithKeyboard(
3158
3141
  query.chatId,
3159
3142
  query.messageId,
3160
- '❌ No pending approval request (may have expired).'
3143
+ this.getUiCopy('approvalNone')
3161
3144
  );
3162
3145
  }
3163
3146
  return;
@@ -3169,7 +3152,9 @@ Example: "Add a login form"`;
3169
3152
  try {
3170
3153
  await this.agentDaemon?.respondToApproval(approvalId, approved);
3171
3154
 
3172
- const statusText = approved ? '✅ Approved! Executing...' : '🛑 Denied. Action cancelled.';
3155
+ const statusText = approved
3156
+ ? this.getUiCopy('approvalApproved')
3157
+ : this.getUiCopy('approvalDenied');
3173
3158
  if (adapter.editMessageWithKeyboard) {
3174
3159
  await adapter.editMessageWithKeyboard(
3175
3160
  query.chatId,
@@ -3186,7 +3171,7 @@ Example: "Add a login form"`;
3186
3171
  console.error('Error responding to approval:', error);
3187
3172
  await adapter.sendMessage({
3188
3173
  chatId: query.chatId,
3189
- text: '❌ Failed to process response.',
3174
+ text: this.getUiCopy('responseFailed'),
3190
3175
  });
3191
3176
  }
3192
3177
  }