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
@@ -46,6 +46,7 @@ const pricing_1 = require("./llm/pricing");
46
46
  const custom_skill_loader_1 = require("./custom-skill-loader");
47
47
  const MemoryService_1 = require("../memory/MemoryService");
48
48
  const security_1 = require("./security");
49
+ const builtin_settings_1 = require("./tools/builtin-settings");
49
50
  class AwaitingUserInputError extends Error {
50
51
  constructor(message) {
51
52
  super(message);
@@ -56,7 +57,7 @@ class AwaitingUserInputError extends Error {
56
57
  const LLM_TIMEOUT_MS = 2 * 60 * 1000;
57
58
  // Per-step timeout (5 minutes max per step)
58
59
  const STEP_TIMEOUT_MS = 5 * 60 * 1000;
59
- // Per-tool execution timeout (45 seconds - balance responsiveness with heavier tools)
60
+ // Default per-tool execution timeout (overrideable per tool)
60
61
  const TOOL_TIMEOUT_MS = 30 * 1000;
61
62
  // Maximum consecutive failures for the same tool before giving up
62
63
  const MAX_TOOL_FAILURES = 2;
@@ -915,6 +916,7 @@ class TaskExecutor {
915
916
  // Global turn tracking (across all steps) - similar to Claude Agent SDK's maxTurns
916
917
  this.globalTurnCount = 0;
917
918
  this.maxGlobalTurns = 100; // Configurable global limit
919
+ this.lastUserMessage = task.prompt;
918
920
  this.requiresTestRun = this.detectTestRequirement(`${task.title}\n${task.prompt}`);
919
921
  // Get base settings
920
922
  const settings = llm_1.LLMProviderFactory.loadSettings();
@@ -927,7 +929,7 @@ class TaskExecutor {
927
929
  // Use task's model key if specified, otherwise use global settings
928
930
  const effectiveModelKey = taskModelKey || settings.modelKey;
929
931
  // Get the model ID
930
- this.modelId = llm_1.LLMProviderFactory.getModelId(effectiveModelKey, settings.providerType, settings.ollama?.model, settings.gemini?.model, settings.openrouter?.model, settings.openai?.model);
932
+ this.modelId = llm_1.LLMProviderFactory.getModelId(effectiveModelKey, settings.providerType, settings.ollama?.model, settings.gemini?.model, settings.openrouter?.model, settings.openai?.model, settings.groq?.model, settings.xai?.model, settings.kimi?.model, settings.customProviders);
931
933
  this.modelKey = effectiveModelKey;
932
934
  // Initialize context manager for handling long conversations
933
935
  this.contextManager = new context_manager_1.ContextManager(effectiveModelKey);
@@ -1046,6 +1048,20 @@ class TaskExecutor {
1046
1048
  this.iterationCount++;
1047
1049
  this.globalTurnCount++; // Track global turns across all steps
1048
1050
  }
1051
+ getToolTimeoutMs(toolName, input) {
1052
+ const settingsTimeout = builtin_settings_1.BuiltinToolsSettingsManager.getToolTimeoutMs(toolName);
1053
+ const normalizedSettingsTimeout = settingsTimeout && settingsTimeout > 0 ? settingsTimeout : null;
1054
+ if (toolName === 'run_command') {
1055
+ const inputTimeout = typeof input?.timeout === 'number'
1056
+ ? input.timeout
1057
+ : undefined;
1058
+ if (typeof inputTimeout === 'number' && Number.isFinite(inputTimeout) && inputTimeout > 0) {
1059
+ return Math.round(inputTimeout);
1060
+ }
1061
+ return normalizedSettingsTimeout ?? TOOL_TIMEOUT_MS;
1062
+ }
1063
+ return normalizedSettingsTimeout ?? TOOL_TIMEOUT_MS;
1064
+ }
1049
1065
  /**
1050
1066
  * Check if a file operation should be blocked (redundant read or duplicate creation)
1051
1067
  * @returns Object with blocked flag, reason, and suggestion if blocked, plus optional cached result
@@ -1266,6 +1282,33 @@ class TaskExecutor {
1266
1282
  }
1267
1283
  return { input, modified: false };
1268
1284
  }
1285
+ async handleCanvasPushFallback(content, assistantText) {
1286
+ if (content.name !== 'canvas_push') {
1287
+ return;
1288
+ }
1289
+ const inputContent = content.input?.content;
1290
+ const hasContent = typeof inputContent === 'string' && inputContent.trim().length > 0;
1291
+ const filename = content.input?.filename;
1292
+ const isHtmlTarget = !filename || filename === 'index.html';
1293
+ if (hasContent || !isHtmlTarget) {
1294
+ return;
1295
+ }
1296
+ const extracted = this.extractHtmlFromText(assistantText);
1297
+ const generated = extracted || await this.generateCanvasHtml(this.lastUserMessage || this.task.prompt);
1298
+ if (!generated) {
1299
+ return;
1300
+ }
1301
+ content.input = {
1302
+ ...(content.input || {}),
1303
+ content: generated,
1304
+ };
1305
+ this.daemon.logEvent(this.task.id, 'parameter_inference', {
1306
+ tool: content.name,
1307
+ inference: extracted
1308
+ ? 'Recovered HTML from assistant text'
1309
+ : 'Auto-generated HTML from latest user request',
1310
+ });
1311
+ }
1269
1312
  /**
1270
1313
  * Get available tools, filtering out disabled ones
1271
1314
  * This prevents the LLM from trying to use tools that have been disabled by the circuit breaker
@@ -2610,12 +2653,13 @@ SCHEDULING & REMINDERS:
2610
2653
  this.checkBudgets();
2611
2654
  // Compact messages if context is getting too large
2612
2655
  messages = this.contextManager.compactMessages(messages, systemPromptTokens);
2656
+ const availableTools = this.getAvailableTools();
2613
2657
  // Use retry wrapper for resilient API calls
2614
2658
  const response = await this.callLLMWithRetry(() => withTimeout(this.provider.createMessage({
2615
2659
  model: this.modelId,
2616
2660
  maxTokens: 4096,
2617
2661
  system: this.systemPrompt,
2618
- tools: this.getAvailableTools(),
2662
+ tools: availableTools,
2619
2663
  messages,
2620
2664
  signal: this.abortController.signal,
2621
2665
  }), LLM_TIMEOUT_MS, 'LLM execution step'), `Step execution (iteration ${iterationCount})`);
@@ -2630,6 +2674,10 @@ SCHEDULING & REMINDERS:
2630
2674
  }
2631
2675
  // Log any text responses from the assistant and check if asking a question
2632
2676
  let assistantAskedQuestion = false;
2677
+ const assistantText = (response.content || [])
2678
+ .filter((item) => item.type === 'text' && item.text)
2679
+ .map((item) => item.text)
2680
+ .join('\n');
2633
2681
  if (response.content) {
2634
2682
  for (const content of response.content) {
2635
2683
  if (content.type === 'text' && content.text) {
@@ -2674,6 +2722,8 @@ SCHEDULING & REMINDERS:
2674
2722
  const toolResults = [];
2675
2723
  let hasDisabledToolAttempt = false;
2676
2724
  let hasDuplicateToolAttempt = false;
2725
+ let hasUnavailableToolAttempt = false;
2726
+ const availableToolNames = new Set(availableTools.map(tool => tool.name));
2677
2727
  for (const content of response.content || []) {
2678
2728
  if (content.type === 'tool_use') {
2679
2729
  // Check if this tool is disabled (circuit breaker tripped)
@@ -2697,6 +2747,37 @@ SCHEDULING & REMINDERS:
2697
2747
  hasDisabledToolAttempt = true;
2698
2748
  continue;
2699
2749
  }
2750
+ // Validate tool availability before attempting any inference
2751
+ if (!availableToolNames.has(content.name)) {
2752
+ console.log(`[TaskExecutor] Tool not available in this context: ${content.name}`);
2753
+ this.daemon.logEvent(this.task.id, 'tool_error', {
2754
+ tool: content.name,
2755
+ error: 'Tool not available in current context or permissions',
2756
+ blocked: true,
2757
+ });
2758
+ toolResults.push({
2759
+ type: 'tool_result',
2760
+ tool_use_id: content.id,
2761
+ content: JSON.stringify({
2762
+ error: `Tool "${content.name}" is not available in this context. Please choose a different tool or check permissions/integrations.`,
2763
+ unavailable: true,
2764
+ }),
2765
+ is_error: true,
2766
+ });
2767
+ hasUnavailableToolAttempt = true;
2768
+ continue;
2769
+ }
2770
+ // Infer missing parameters for weaker models (normalize inputs before deduplication)
2771
+ const inference = this.inferMissingParameters(content.name, content.input);
2772
+ if (inference.modified) {
2773
+ content.input = inference.input;
2774
+ this.daemon.logEvent(this.task.id, 'parameter_inference', {
2775
+ tool: content.name,
2776
+ inference: inference.inference,
2777
+ });
2778
+ }
2779
+ // If canvas_push is missing content, try extracting HTML from assistant text or auto-generate
2780
+ await this.handleCanvasPushFallback(content, assistantText);
2700
2781
  // Check for duplicate tool calls (prevents stuck loops)
2701
2782
  const duplicateCheck = this.toolCallDeduplicator.checkDuplicate(content.name, content.input);
2702
2783
  if (duplicateCheck.isDuplicate) {
@@ -2767,22 +2848,14 @@ SCHEDULING & REMINDERS:
2767
2848
  }
2768
2849
  continue;
2769
2850
  }
2770
- // Infer missing parameters for weaker models
2771
- const inference = this.inferMissingParameters(content.name, content.input);
2772
- if (inference.modified) {
2773
- content.input = inference.input;
2774
- this.daemon.logEvent(this.task.id, 'parameter_inference', {
2775
- tool: content.name,
2776
- inference: inference.inference,
2777
- });
2778
- }
2779
2851
  this.daemon.logEvent(this.task.id, 'tool_call', {
2780
2852
  tool: content.name,
2781
2853
  input: content.input,
2782
2854
  });
2783
2855
  try {
2784
2856
  // Execute tool with timeout to prevent hanging
2785
- const result = await withTimeout(this.toolRegistry.executeTool(content.name, content.input), TOOL_TIMEOUT_MS, `Tool ${content.name}`);
2857
+ const toolTimeoutMs = this.getToolTimeoutMs(content.name, content.input);
2858
+ const result = await withTimeout(this.toolRegistry.executeTool(content.name, content.input), toolTimeoutMs, `Tool ${content.name}`);
2786
2859
  // Tool succeeded - reset failure counter
2787
2860
  this.toolFailureTracker.recordSuccess(content.name);
2788
2861
  // Record this call for deduplication
@@ -2882,7 +2955,7 @@ SCHEDULING & REMINDERS:
2882
2955
  // If all tool attempts were for disabled or duplicate tools, don't continue looping
2883
2956
  // This prevents infinite retry loops
2884
2957
  const allToolsFailed = toolResults.every(r => r.is_error);
2885
- if ((hasDisabledToolAttempt || hasDuplicateToolAttempt) && allToolsFailed) {
2958
+ if ((hasDisabledToolAttempt || hasDuplicateToolAttempt || hasUnavailableToolAttempt) && allToolsFailed) {
2886
2959
  console.log('[TaskExecutor] All tool calls failed, were disabled, or duplicates - stopping iteration');
2887
2960
  if (hasDuplicateToolAttempt) {
2888
2961
  // Duplicate detection triggered - step is likely complete
@@ -2993,6 +3066,57 @@ SCHEDULING & REMINDERS:
2993
3066
  });
2994
3067
  }
2995
3068
  }
3069
+ extractHtmlFromText(text) {
3070
+ if (!text)
3071
+ return null;
3072
+ const fenceMatch = text.match(/```html([\s\S]*?)```/i);
3073
+ const raw = fenceMatch ? fenceMatch[1].trim() : text;
3074
+ const doctypeIndex = raw.indexOf('<!DOCTYPE html');
3075
+ if (doctypeIndex >= 0) {
3076
+ const endIndex = raw.lastIndexOf('</html>');
3077
+ if (endIndex > doctypeIndex) {
3078
+ return raw.slice(doctypeIndex, endIndex + '</html>'.length).trim();
3079
+ }
3080
+ }
3081
+ const htmlIndex = raw.indexOf('<html');
3082
+ if (htmlIndex >= 0) {
3083
+ const endIndex = raw.lastIndexOf('</html>');
3084
+ if (endIndex > htmlIndex) {
3085
+ return raw.slice(htmlIndex, endIndex + '</html>'.length).trim();
3086
+ }
3087
+ }
3088
+ return null;
3089
+ }
3090
+ async generateCanvasHtml(prompt) {
3091
+ const system = [
3092
+ 'You generate a single self-contained HTML document for an in-app canvas.',
3093
+ 'Output ONLY the HTML document (no markdown, no commentary).',
3094
+ 'Use inline CSS and JS. Do not reference external assets or remote URLs.',
3095
+ 'Keep it reasonably compact and interactive where appropriate.',
3096
+ ].join(' ');
3097
+ try {
3098
+ const response = await this.provider.createMessage({
3099
+ model: this.modelId,
3100
+ maxTokens: 1800,
3101
+ system,
3102
+ messages: [
3103
+ {
3104
+ role: 'user',
3105
+ content: `Build an interactive HTML demo for this request:\n${prompt}`,
3106
+ },
3107
+ ],
3108
+ });
3109
+ const text = (response.content || [])
3110
+ .filter((c) => c.type === 'text')
3111
+ .map((c) => c.text)
3112
+ .join('\n');
3113
+ return this.extractHtmlFromText(text);
3114
+ }
3115
+ catch (error) {
3116
+ console.error('[TaskExecutor] Failed to auto-generate canvas HTML:', error);
3117
+ return null;
3118
+ }
3119
+ }
2996
3120
  /**
2997
3121
  * Send a follow-up message to continue the conversation
2998
3122
  */
@@ -3003,6 +3127,7 @@ SCHEDULING & REMINDERS:
3003
3127
  let resumeAttempted = false;
3004
3128
  this.waitingForUserInput = false;
3005
3129
  this.paused = false;
3130
+ this.lastUserMessage = message;
3006
3131
  this.toolRegistry.setCanvasSessionCutoff(shouldStartNewCanvasSession ? Date.now() : null);
3007
3132
  this.daemon.updateTaskStatus(this.task.id, 'executing');
3008
3133
  this.daemon.logEvent(this.task.id, 'executing', { message: 'Processing follow-up message' });
@@ -3198,12 +3323,14 @@ SCHEDULING & REMINDERS:
3198
3323
  this.checkBudgets();
3199
3324
  // Compact messages if context is getting too large
3200
3325
  messages = this.contextManager.compactMessages(messages, systemPromptTokens);
3326
+ const availableTools = this.getAvailableTools();
3327
+ const availableToolNames = new Set(availableTools.map(tool => tool.name));
3201
3328
  // Use retry wrapper for resilient API calls
3202
3329
  const response = await this.callLLMWithRetry(() => withTimeout(this.provider.createMessage({
3203
3330
  model: this.modelId,
3204
3331
  maxTokens: 4096,
3205
3332
  system: this.systemPrompt,
3206
- tools: this.getAvailableTools(),
3333
+ tools: availableTools,
3207
3334
  messages,
3208
3335
  signal: this.abortController.signal,
3209
3336
  }), LLM_TIMEOUT_MS, 'LLM message processing'), `Message processing (iteration ${iterationCount})`);
@@ -3216,6 +3343,10 @@ SCHEDULING & REMINDERS:
3216
3343
  // Log any text responses from the assistant and check if asking a question
3217
3344
  let assistantAskedQuestion = false;
3218
3345
  let hasTextInThisResponse = false;
3346
+ const assistantText = (response.content || [])
3347
+ .filter((item) => item.type === 'text' && item.text)
3348
+ .map((item) => item.text)
3349
+ .join('\n');
3219
3350
  if (response.content) {
3220
3351
  for (const content of response.content) {
3221
3352
  if (content.type === 'text' && content.text && content.text.trim().length > 0) {
@@ -3262,6 +3393,7 @@ SCHEDULING & REMINDERS:
3262
3393
  const toolResults = [];
3263
3394
  let hasDisabledToolAttempt = false;
3264
3395
  let hasDuplicateToolAttempt = false;
3396
+ let hasUnavailableToolAttempt = false;
3265
3397
  for (const content of response.content || []) {
3266
3398
  if (content.type === 'tool_use') {
3267
3399
  // Check if this tool is disabled (circuit breaker tripped)
@@ -3285,6 +3417,37 @@ SCHEDULING & REMINDERS:
3285
3417
  hasDisabledToolAttempt = true;
3286
3418
  continue;
3287
3419
  }
3420
+ // Validate tool availability before attempting any inference
3421
+ if (!availableToolNames.has(content.name)) {
3422
+ console.log(`[TaskExecutor] Tool not available in this context: ${content.name}`);
3423
+ this.daemon.logEvent(this.task.id, 'tool_error', {
3424
+ tool: content.name,
3425
+ error: 'Tool not available in current context or permissions',
3426
+ blocked: true,
3427
+ });
3428
+ toolResults.push({
3429
+ type: 'tool_result',
3430
+ tool_use_id: content.id,
3431
+ content: JSON.stringify({
3432
+ error: `Tool "${content.name}" is not available in this context. Please choose a different tool or check permissions/integrations.`,
3433
+ unavailable: true,
3434
+ }),
3435
+ is_error: true,
3436
+ });
3437
+ hasUnavailableToolAttempt = true;
3438
+ continue;
3439
+ }
3440
+ // Infer missing parameters for weaker models (normalize inputs before deduplication)
3441
+ const inference = this.inferMissingParameters(content.name, content.input);
3442
+ if (inference.modified) {
3443
+ content.input = inference.input;
3444
+ this.daemon.logEvent(this.task.id, 'parameter_inference', {
3445
+ tool: content.name,
3446
+ inference: inference.inference,
3447
+ });
3448
+ }
3449
+ // If canvas_push is missing content, try extracting HTML from assistant text or auto-generate
3450
+ await this.handleCanvasPushFallback(content, assistantText);
3288
3451
  // Check for duplicate tool calls (prevents stuck loops)
3289
3452
  const duplicateCheck = this.toolCallDeduplicator.checkDuplicate(content.name, content.input);
3290
3453
  if (duplicateCheck.isDuplicate) {
@@ -3353,22 +3516,14 @@ SCHEDULING & REMINDERS:
3353
3516
  }
3354
3517
  continue;
3355
3518
  }
3356
- // Infer missing parameters for weaker models
3357
- const inference = this.inferMissingParameters(content.name, content.input);
3358
- if (inference.modified) {
3359
- content.input = inference.input;
3360
- this.daemon.logEvent(this.task.id, 'parameter_inference', {
3361
- tool: content.name,
3362
- inference: inference.inference,
3363
- });
3364
- }
3365
3519
  this.daemon.logEvent(this.task.id, 'tool_call', {
3366
3520
  tool: content.name,
3367
3521
  input: content.input,
3368
3522
  });
3369
3523
  try {
3370
3524
  // Execute tool with timeout to prevent hanging
3371
- const result = await withTimeout(this.toolRegistry.executeTool(content.name, content.input), TOOL_TIMEOUT_MS, `Tool ${content.name}`);
3525
+ const toolTimeoutMs = this.getToolTimeoutMs(content.name, content.input);
3526
+ const result = await withTimeout(this.toolRegistry.executeTool(content.name, content.input), toolTimeoutMs, `Tool ${content.name}`);
3372
3527
  // Tool succeeded - reset failure counter
3373
3528
  this.toolFailureTracker.recordSuccess(content.name);
3374
3529
  // Record this call for deduplication
@@ -3430,7 +3585,7 @@ SCHEDULING & REMINDERS:
3430
3585
  });
3431
3586
  // If all tool attempts were for disabled or duplicate tools, don't continue looping
3432
3587
  const allToolsFailed = toolResults.every(r => r.is_error);
3433
- if ((hasDisabledToolAttempt || hasDuplicateToolAttempt) && allToolsFailed) {
3588
+ if ((hasDisabledToolAttempt || hasDuplicateToolAttempt || hasUnavailableToolAttempt) && allToolsFailed) {
3434
3589
  console.log('[TaskExecutor] All tool calls failed, were disabled, or duplicates - stopping iteration');
3435
3590
  continueLoop = false;
3436
3591
  }
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AnthropicCompatibleProvider = void 0;
4
+ const ANTHROPIC_VERSION = '2023-06-01';
5
+ function joinUrl(baseUrl, path) {
6
+ const trimmedBase = baseUrl.replace(/\/+$/, '');
7
+ const trimmedPath = path.startsWith('/') ? path : `/${path}`;
8
+ return `${trimmedBase}${trimmedPath}`;
9
+ }
10
+ class AnthropicCompatibleProvider {
11
+ constructor(options) {
12
+ this.type = options.type;
13
+ this.apiKey = options.apiKey;
14
+ this.baseUrl = options.baseUrl;
15
+ this.defaultModel = options.defaultModel;
16
+ this.providerName = options.providerName;
17
+ }
18
+ async createMessage(request) {
19
+ const messages = this.convertMessages(request.messages);
20
+ const tools = request.tools ? this.convertTools(request.tools) : undefined;
21
+ const model = request.model || this.defaultModel;
22
+ try {
23
+ console.log(`[${this.providerName}] Calling API with model: ${model}`);
24
+ const response = await fetch(joinUrl(this.baseUrl, '/messages'), {
25
+ method: 'POST',
26
+ headers: {
27
+ 'Content-Type': 'application/json',
28
+ 'x-api-key': this.apiKey,
29
+ 'anthropic-version': ANTHROPIC_VERSION,
30
+ },
31
+ body: JSON.stringify({
32
+ model,
33
+ max_tokens: request.maxTokens,
34
+ system: request.system,
35
+ messages,
36
+ ...(tools && { tools }),
37
+ }),
38
+ signal: request.signal,
39
+ });
40
+ if (!response.ok) {
41
+ const errorData = await response.json().catch(() => ({}));
42
+ throw new Error(`${this.providerName} API error: ${response.status} ${response.statusText}` +
43
+ (errorData.error?.message ? ` - ${errorData.error.message}` : ''));
44
+ }
45
+ const data = await response.json();
46
+ return this.convertResponse(data);
47
+ }
48
+ catch (error) {
49
+ if (error.name === 'AbortError' || error.message?.includes('aborted')) {
50
+ console.log(`[${this.providerName}] Request aborted`);
51
+ throw new Error('Request cancelled');
52
+ }
53
+ console.error(`[${this.providerName}] API error:`, {
54
+ message: error.message,
55
+ status: error.status,
56
+ });
57
+ throw error;
58
+ }
59
+ }
60
+ async testConnection() {
61
+ try {
62
+ const response = await fetch(joinUrl(this.baseUrl, '/messages'), {
63
+ method: 'POST',
64
+ headers: {
65
+ 'Content-Type': 'application/json',
66
+ 'x-api-key': this.apiKey,
67
+ 'anthropic-version': ANTHROPIC_VERSION,
68
+ },
69
+ body: JSON.stringify({
70
+ model: this.defaultModel,
71
+ max_tokens: 10,
72
+ messages: [{ role: 'user', content: 'Hi' }],
73
+ }),
74
+ });
75
+ if (!response.ok) {
76
+ const errorData = await response.json().catch(() => ({}));
77
+ return {
78
+ success: false,
79
+ error: errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`,
80
+ };
81
+ }
82
+ return { success: true };
83
+ }
84
+ catch (error) {
85
+ return {
86
+ success: false,
87
+ error: error.message || `Failed to connect to ${this.providerName} API`,
88
+ };
89
+ }
90
+ }
91
+ convertMessages(messages) {
92
+ return messages.map((msg) => {
93
+ if (typeof msg.content === 'string') {
94
+ return {
95
+ role: msg.role,
96
+ content: msg.content,
97
+ };
98
+ }
99
+ const content = msg.content.map((item) => {
100
+ if (item.type === 'tool_result') {
101
+ return {
102
+ type: 'tool_result',
103
+ tool_use_id: item.tool_use_id,
104
+ content: item.content,
105
+ ...(item.is_error && { is_error: true }),
106
+ };
107
+ }
108
+ if (item.type === 'tool_use') {
109
+ return {
110
+ type: 'tool_use',
111
+ id: item.id,
112
+ name: item.name,
113
+ input: item.input,
114
+ };
115
+ }
116
+ return {
117
+ type: 'text',
118
+ text: item.text,
119
+ };
120
+ });
121
+ return {
122
+ role: msg.role,
123
+ content,
124
+ };
125
+ });
126
+ }
127
+ convertTools(tools) {
128
+ return tools.map((tool) => ({
129
+ name: tool.name,
130
+ description: tool.description,
131
+ input_schema: tool.input_schema,
132
+ }));
133
+ }
134
+ convertResponse(response) {
135
+ const content = (response.content || [])
136
+ .filter((block) => block.type === 'text' || block.type === 'tool_use')
137
+ .map((block) => {
138
+ if (block.type === 'tool_use') {
139
+ return {
140
+ type: 'tool_use',
141
+ id: block.id,
142
+ name: block.name,
143
+ input: block.input,
144
+ };
145
+ }
146
+ return {
147
+ type: 'text',
148
+ text: block.text || '',
149
+ };
150
+ });
151
+ return {
152
+ content: content.length > 0 ? content : [{ type: 'text', text: '' }],
153
+ stopReason: this.mapStopReason(response.stop_reason),
154
+ usage: response.usage
155
+ ? {
156
+ inputTokens: response.usage.input_tokens || 0,
157
+ outputTokens: response.usage.output_tokens || 0,
158
+ }
159
+ : undefined,
160
+ };
161
+ }
162
+ mapStopReason(reason) {
163
+ switch (reason) {
164
+ case 'end_turn':
165
+ return 'end_turn';
166
+ case 'tool_use':
167
+ return 'tool_use';
168
+ case 'max_tokens':
169
+ return 'max_tokens';
170
+ case 'stop_sequence':
171
+ return 'stop_sequence';
172
+ default:
173
+ return 'end_turn';
174
+ }
175
+ }
176
+ }
177
+ exports.AnthropicCompatibleProvider = AnthropicCompatibleProvider;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitHubCopilotProvider = void 0;
4
+ const openai_compatible_provider_1 = require("./openai-compatible-provider");
5
+ const COPILOT_TOKEN_URL = 'https://api.github.com/copilot_internal/v2/token';
6
+ const DEFAULT_COPILOT_BASE_URL = 'https://api.individual.githubcopilot.com';
7
+ function isTokenValid(cache, now = Date.now()) {
8
+ return cache.expiresAt - now > 5 * 60 * 1000;
9
+ }
10
+ function parseCopilotTokenResponse(payload) {
11
+ if (!payload || typeof payload !== 'object') {
12
+ throw new Error('Unexpected response from Copilot token endpoint');
13
+ }
14
+ const token = payload.token;
15
+ const expiresAt = payload.expires_at;
16
+ if (typeof token !== 'string' || !token.trim()) {
17
+ throw new Error('Copilot token response missing token');
18
+ }
19
+ if (typeof expiresAt === 'number' && Number.isFinite(expiresAt)) {
20
+ return { token, expiresAt: expiresAt > 10000000000 ? expiresAt : expiresAt * 1000 };
21
+ }
22
+ if (typeof expiresAt === 'string' && expiresAt.trim()) {
23
+ const parsed = Number.parseInt(expiresAt, 10);
24
+ if (!Number.isFinite(parsed)) {
25
+ throw new Error('Copilot token response has invalid expires_at');
26
+ }
27
+ return { token, expiresAt: parsed > 10000000000 ? parsed : parsed * 1000 };
28
+ }
29
+ throw new Error('Copilot token response missing expires_at');
30
+ }
31
+ function deriveCopilotBaseUrl(token) {
32
+ const match = token.match(/(?:^|;)\s*proxy-ep=([^;\s]+)/i);
33
+ const proxyEp = match?.[1]?.trim();
34
+ if (!proxyEp)
35
+ return DEFAULT_COPILOT_BASE_URL;
36
+ const host = proxyEp.replace(/^https?:\/\//, '').replace(/^proxy\./i, 'api.');
37
+ return host ? `https://${host}` : DEFAULT_COPILOT_BASE_URL;
38
+ }
39
+ class GitHubCopilotProvider {
40
+ constructor(config) {
41
+ this.type = 'github-copilot';
42
+ const token = config.providerApiKey;
43
+ if (!token) {
44
+ throw new Error('GitHub token is required for Copilot. Configure it in Settings.');
45
+ }
46
+ this.githubToken = token;
47
+ this.model = config.model || 'gpt-4o';
48
+ }
49
+ async createMessage(request) {
50
+ const client = await this.getClient(request.model || this.model);
51
+ return client.createMessage(request);
52
+ }
53
+ async testConnection() {
54
+ try {
55
+ const client = await this.getClient(this.model);
56
+ return await client.testConnection();
57
+ }
58
+ catch (error) {
59
+ return { success: false, error: error.message || 'Failed to connect to Copilot' };
60
+ }
61
+ }
62
+ async getClient(model) {
63
+ const auth = await this.getCopilotAuth();
64
+ return new openai_compatible_provider_1.OpenAICompatibleProvider({
65
+ type: 'github-copilot',
66
+ providerName: 'GitHub Copilot',
67
+ apiKey: auth.token,
68
+ baseUrl: auth.baseUrl,
69
+ defaultModel: model,
70
+ });
71
+ }
72
+ async getCopilotAuth() {
73
+ if (GitHubCopilotProvider.cache && isTokenValid(GitHubCopilotProvider.cache)) {
74
+ return GitHubCopilotProvider.cache;
75
+ }
76
+ const response = await fetch(COPILOT_TOKEN_URL, {
77
+ method: 'GET',
78
+ headers: {
79
+ Accept: 'application/json',
80
+ Authorization: `Bearer ${this.githubToken}`,
81
+ },
82
+ });
83
+ if (!response.ok) {
84
+ throw new Error(`Copilot token exchange failed: HTTP ${response.status}`);
85
+ }
86
+ const json = await response.json();
87
+ const parsed = parseCopilotTokenResponse(json);
88
+ const cache = {
89
+ token: parsed.token,
90
+ expiresAt: parsed.expiresAt,
91
+ baseUrl: deriveCopilotBaseUrl(parsed.token),
92
+ };
93
+ GitHubCopilotProvider.cache = cache;
94
+ return cache;
95
+ }
96
+ }
97
+ exports.GitHubCopilotProvider = GitHubCopilotProvider;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GroqProvider = void 0;
4
+ const openai_compatible_provider_1 = require("./openai-compatible-provider");
5
+ const GROQ_BASE_URL = 'https://api.groq.com/openai/v1';
6
+ const DEFAULT_GROQ_MODEL = 'llama-3.1-8b-instant';
7
+ class GroqProvider {
8
+ constructor(config) {
9
+ this.type = 'groq';
10
+ const apiKey = config.groqApiKey;
11
+ if (!apiKey) {
12
+ throw new Error('Groq API key is required. Configure it in Settings.');
13
+ }
14
+ const baseUrl = config.groqBaseUrl || GROQ_BASE_URL;
15
+ this.client = new openai_compatible_provider_1.OpenAICompatibleProvider({
16
+ type: 'groq',
17
+ providerName: 'Groq',
18
+ apiKey,
19
+ baseUrl,
20
+ defaultModel: config.model || DEFAULT_GROQ_MODEL,
21
+ });
22
+ }
23
+ createMessage(request) {
24
+ return this.client.createMessage(request);
25
+ }
26
+ testConnection() {
27
+ return this.client.testConnection();
28
+ }
29
+ getAvailableModels() {
30
+ return this.client.getAvailableModels();
31
+ }
32
+ }
33
+ exports.GroqProvider = GroqProvider;