funolio-agent 1.0.47 → 1.0.49

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 (173) hide show
  1. package/dist/agent-config.d.ts +9 -1
  2. package/dist/agent-config.d.ts.map +1 -1
  3. package/dist/agent-config.js +4 -1
  4. package/dist/agent-config.js.map +1 -1
  5. package/dist/auth/auto-detect.d.ts +1 -0
  6. package/dist/auth/auto-detect.d.ts.map +1 -1
  7. package/dist/auth/auto-detect.js +16 -13
  8. package/dist/auth/auto-detect.js.map +1 -1
  9. package/dist/auto-organizer.d.ts.map +1 -1
  10. package/dist/auto-organizer.js +4 -3
  11. package/dist/auto-organizer.js.map +1 -1
  12. package/dist/backfill.d.ts.map +1 -1
  13. package/dist/backfill.js +3 -2
  14. package/dist/backfill.js.map +1 -1
  15. package/dist/bot-manager.d.ts +8 -23
  16. package/dist/bot-manager.d.ts.map +1 -1
  17. package/dist/bot-manager.js +61 -388
  18. package/dist/bot-manager.js.map +1 -1
  19. package/dist/clerk-model.d.ts +5 -1
  20. package/dist/clerk-model.d.ts.map +1 -1
  21. package/dist/clerk-model.js +40 -28
  22. package/dist/clerk-model.js.map +1 -1
  23. package/dist/cli-session-epoch.d.ts +10 -0
  24. package/dist/cli-session-epoch.d.ts.map +1 -0
  25. package/dist/cli-session-epoch.js +61 -0
  26. package/dist/cli-session-epoch.js.map +1 -0
  27. package/dist/commands/init.d.ts.map +1 -1
  28. package/dist/commands/init.js +30 -1
  29. package/dist/commands/init.js.map +1 -1
  30. package/dist/commands/pool.js +1 -1
  31. package/dist/commands/pool.js.map +1 -1
  32. package/dist/commands/setup.d.ts +37 -0
  33. package/dist/commands/setup.d.ts.map +1 -1
  34. package/dist/commands/setup.js +154 -43
  35. package/dist/commands/setup.js.map +1 -1
  36. package/dist/commands/start.d.ts.map +1 -1
  37. package/dist/commands/start.js +195 -164
  38. package/dist/commands/start.js.map +1 -1
  39. package/dist/config-cleanup.d.ts.map +1 -1
  40. package/dist/config-cleanup.js +2 -1
  41. package/dist/config-cleanup.js.map +1 -1
  42. package/dist/config.d.ts +6 -9
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +8 -30
  45. package/dist/config.js.map +1 -1
  46. package/dist/context-window.d.ts +33 -5
  47. package/dist/context-window.d.ts.map +1 -1
  48. package/dist/context-window.js +121 -20
  49. package/dist/context-window.js.map +1 -1
  50. package/dist/eval/orchestrator-front-door-replay.js +1 -1
  51. package/dist/eval/orchestrator-front-door-replay.js.map +1 -1
  52. package/dist/eval/policy-detection-replay.js +1 -1
  53. package/dist/eval/policy-detection-replay.js.map +1 -1
  54. package/dist/integration-tokens.d.ts +1 -6
  55. package/dist/integration-tokens.d.ts.map +1 -1
  56. package/dist/integration-tokens.js +38 -40
  57. package/dist/integration-tokens.js.map +1 -1
  58. package/dist/local-cli-pty-manager.d.ts +50 -0
  59. package/dist/local-cli-pty-manager.d.ts.map +1 -0
  60. package/dist/local-cli-pty-manager.js +645 -0
  61. package/dist/local-cli-pty-manager.js.map +1 -0
  62. package/dist/local-data.d.ts +30 -0
  63. package/dist/local-data.d.ts.map +1 -1
  64. package/dist/local-data.js +56 -1
  65. package/dist/local-data.js.map +1 -1
  66. package/dist/local-db.d.ts.map +1 -1
  67. package/dist/local-db.js +54 -1
  68. package/dist/local-db.js.map +1 -1
  69. package/dist/local-funnel.d.ts.map +1 -1
  70. package/dist/local-funnel.js +3 -2
  71. package/dist/local-funnel.js.map +1 -1
  72. package/dist/local-memory-search.d.ts +1 -0
  73. package/dist/local-memory-search.d.ts.map +1 -1
  74. package/dist/local-memory-search.js +101 -18
  75. package/dist/local-memory-search.js.map +1 -1
  76. package/dist/local-server.d.ts +0 -16
  77. package/dist/local-server.d.ts.map +1 -1
  78. package/dist/local-server.js +339 -287
  79. package/dist/local-server.js.map +1 -1
  80. package/dist/mcp/bridge-server.d.ts.map +1 -1
  81. package/dist/mcp/bridge-server.js +2 -1
  82. package/dist/mcp/bridge-server.js.map +1 -1
  83. package/dist/mcp/local-memory-server.d.ts +5 -0
  84. package/dist/mcp/local-memory-server.d.ts.map +1 -1
  85. package/dist/mcp/local-memory-server.js +15 -2
  86. package/dist/mcp/local-memory-server.js.map +1 -1
  87. package/dist/mcp/manager.d.ts +3 -22
  88. package/dist/mcp/manager.d.ts.map +1 -1
  89. package/dist/mcp/manager.js +66 -388
  90. package/dist/mcp/manager.js.map +1 -1
  91. package/dist/memory-extraction.d.ts +2 -0
  92. package/dist/memory-extraction.d.ts.map +1 -1
  93. package/dist/memory-extraction.js +3 -1
  94. package/dist/memory-extraction.js.map +1 -1
  95. package/dist/message-loop.d.ts +10 -6
  96. package/dist/message-loop.d.ts.map +1 -1
  97. package/dist/message-loop.js +241 -540
  98. package/dist/message-loop.js.map +1 -1
  99. package/dist/mqtt-client.d.ts +2 -31
  100. package/dist/mqtt-client.d.ts.map +1 -1
  101. package/dist/mqtt-client.js +2 -2
  102. package/dist/mqtt-client.js.map +1 -1
  103. package/dist/oauth.d.ts +6 -0
  104. package/dist/oauth.d.ts.map +1 -1
  105. package/dist/oauth.js +91 -0
  106. package/dist/oauth.js.map +1 -1
  107. package/dist/orchestration/front-door-policy.d.ts +5 -2
  108. package/dist/orchestration/front-door-policy.d.ts.map +1 -1
  109. package/dist/orchestration/front-door-policy.js +25 -28
  110. package/dist/orchestration/front-door-policy.js.map +1 -1
  111. package/dist/orchestration/orchestrator-blocked-prompt.js +1 -1
  112. package/dist/orchestration/orchestrator-final-response-prompt.js +1 -1
  113. package/dist/orchestration/orchestrator-operating-prompt.d.ts +11 -0
  114. package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
  115. package/dist/orchestration/orchestrator-operating-prompt.js +67 -44
  116. package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
  117. package/dist/orchestration/worker-operating-prompt.js +3 -3
  118. package/dist/orchestration/worker-operating-prompt.js.map +1 -1
  119. package/dist/orchestrator.d.ts +5 -1
  120. package/dist/orchestrator.d.ts.map +1 -1
  121. package/dist/orchestrator.js +141 -81
  122. package/dist/orchestrator.js.map +1 -1
  123. package/dist/prompt-template.js +3 -3
  124. package/dist/prompt-template.js.map +1 -1
  125. package/dist/providers/claude-cli-prompt.d.ts.map +1 -1
  126. package/dist/providers/claude-cli-prompt.js +22 -6
  127. package/dist/providers/claude-cli-prompt.js.map +1 -1
  128. package/dist/providers/claude-cli.d.ts.map +1 -1
  129. package/dist/providers/claude-cli.js +20 -2
  130. package/dist/providers/claude-cli.js.map +1 -1
  131. package/dist/providers/codex-cli.d.ts.map +1 -1
  132. package/dist/providers/codex-cli.js +71 -16
  133. package/dist/providers/codex-cli.js.map +1 -1
  134. package/dist/providers/index.d.ts +11 -0
  135. package/dist/providers/index.d.ts.map +1 -1
  136. package/dist/providers/index.js.map +1 -1
  137. package/dist/runtime-context.d.ts +10 -0
  138. package/dist/runtime-context.d.ts.map +1 -0
  139. package/dist/runtime-context.js +30 -0
  140. package/dist/runtime-context.js.map +1 -0
  141. package/dist/subagent/queue.d.ts.map +1 -1
  142. package/dist/subagent/queue.js +1 -0
  143. package/dist/subagent/queue.js.map +1 -1
  144. package/dist/summarization-pipeline.d.ts +1 -0
  145. package/dist/summarization-pipeline.d.ts.map +1 -1
  146. package/dist/summarization-pipeline.js +94 -25
  147. package/dist/summarization-pipeline.js.map +1 -1
  148. package/dist/tool-permissions.d.ts +2 -0
  149. package/dist/tool-permissions.d.ts.map +1 -0
  150. package/dist/tool-permissions.js +25 -0
  151. package/dist/tool-permissions.js.map +1 -0
  152. package/dist/tools/index.d.ts +7 -8
  153. package/dist/tools/index.d.ts.map +1 -1
  154. package/dist/tools/index.js +70 -60
  155. package/dist/tools/index.js.map +1 -1
  156. package/dist/tools/search-memory.d.ts.map +1 -1
  157. package/dist/tools/search-memory.js +9 -3
  158. package/dist/tools/search-memory.js.map +1 -1
  159. package/dist/tools/spawn-subagent.d.ts.map +1 -1
  160. package/dist/tools/spawn-subagent.js +1 -0
  161. package/dist/tools/spawn-subagent.js.map +1 -1
  162. package/dist/types.d.ts +3 -0
  163. package/dist/types.d.ts.map +1 -1
  164. package/dist/types.js +0 -3
  165. package/dist/types.js.map +1 -1
  166. package/dist/wizard-support.d.ts.map +1 -1
  167. package/dist/wizard-support.js +8 -6
  168. package/dist/wizard-support.js.map +1 -1
  169. package/dist/workflow-engine.d.ts +6 -2
  170. package/dist/workflow-engine.d.ts.map +1 -1
  171. package/dist/workflow-engine.js +254 -77
  172. package/dist/workflow-engine.js.map +1 -1
  173. package/package.json +2 -1
@@ -47,15 +47,12 @@ const approval_1 = require("./approval");
47
47
  const local_db_1 = require("./local-db");
48
48
  const config_1 = require("./config");
49
49
  const auto_detect_1 = require("./auth/auto-detect");
50
+ const anthropic_subscription_1 = require("./auth/anthropic-subscription");
50
51
  const clerk_model_1 = require("./clerk-model");
52
+ const local_funnel_1 = require("./local-funnel");
51
53
  const auto_organizer_1 = require("./auto-organizer");
52
- const orchestrator_profile_1 = require("./orchestrator-profile");
53
- const response_guard_1 = require("./response-guard");
54
- const context_compressor_1 = require("./context-compressor");
55
- const crypto = __importStar(require("crypto"));
54
+ const tool_permissions_1 = require("./tool-permissions");
56
55
  const data = __importStar(require("./local-data"));
57
- const prompt_template_1 = require("./prompt-template");
58
- const local_funnel_1 = require("./local-funnel");
59
56
  /** Determine priority from an AgentCommand */
60
57
  function getCommandPriority(command) {
61
58
  if (command.priority)
@@ -111,12 +108,20 @@ function summarizeToolResult(output, isError) {
111
108
  const compact = output.replace(/\s+/g, ' ').trim();
112
109
  return `${prefix}${compact}`.slice(0, 240);
113
110
  }
111
+ function normalizeMcpToolNames(toolNames) {
112
+ if (!Array.isArray(toolNames))
113
+ return undefined;
114
+ const trimmed = toolNames
115
+ .filter((toolName) => typeof toolName === 'string')
116
+ .map((toolName) => toolName.trim())
117
+ .filter(Boolean);
118
+ return [...new Set(trimmed)];
119
+ }
114
120
  class MessageLoop {
115
121
  options;
116
122
  llmProvider;
117
123
  activeCommandId = null;
118
124
  activeCommandSource = null;
119
- chunkSeq = 0;
120
125
  toolContext;
121
126
  toolDefinitions;
122
127
  builtinToolDefinitions;
@@ -126,7 +131,6 @@ class MessageLoop {
126
131
  resolvedAuth = null;
127
132
  idleTimer = null;
128
133
  approvalManager;
129
- _activeAbortController = null;
130
134
  /** Rate limiting / cost guardrails */
131
135
  maxToolCallsPerMessage;
132
136
  maxTokensPerMessage;
@@ -139,41 +143,47 @@ class MessageLoop {
139
143
  static IDLE_THRESHOLD_MS = 15 * 60 * 1000; // 15 minutes
140
144
  constructor(options) {
141
145
  this.options = options;
142
- const effectiveKey = options.apiKey || '';
146
+ // DO NOT remap Claude CLI OAuth sessions to the public Anthropic runtime
147
+ // without explicit user approval.
148
+ const effectiveKey = options.oauthToken || options.apiKey || '';
143
149
  const effectiveProvider = options.provider;
144
- const llmAuthMode = options.authMode || (effectiveKey.startsWith('sk-ant-oat') ? 'oauth-bearer' : 'api-key');
150
+ const isOAuthToken = effectiveKey.startsWith('sk-ant-oat01-');
151
+ const resolved = options.resolvedAuth;
152
+ const resolvedMatchesProvider = !!resolved && resolved.provider === effectiveProvider;
145
153
  this.llmProvider = (0, index_1.createProvider)(effectiveProvider, {
146
154
  apiKey: effectiveKey,
147
155
  model: options.model,
148
- authMode: llmAuthMode,
149
- ...(options.baseUrl ? { baseUrl: options.baseUrl } : {}),
150
- ...(options.apiStyle ? { apiStyle: options.apiStyle } : {}),
156
+ ...(resolvedMatchesProvider && resolved?.authMode ? { authMode: resolved.authMode } : {}),
157
+ ...(resolvedMatchesProvider && resolved?.baseUrl ? { baseUrl: resolved.baseUrl } : {}),
158
+ ...(resolvedMatchesProvider && resolved?.apiStyle ? { apiStyle: resolved.apiStyle } : {}),
159
+ ...(!resolvedMatchesProvider && isOAuthToken ? { authMode: 'oauth-bearer' } : {}),
151
160
  });
152
- // Resolve Funolio API credentials for bot status reporting
153
- const cfg = (0, config_1.loadConfig)();
154
- const funoliApiKey = cfg.auth?.token || process.env.FUNOLIO_API_KEY || '';
155
- const funoliApiBaseUrl = config_1.FUNOLIO_API_URL;
156
- const unrestrictedCliProvider = index_1.CLI_PROVIDERS.has(effectiveProvider);
161
+ // Async: attempt to resolve Anthropic subscription auth (create_api_key exchange)
162
+ if (isOAuthToken && effectiveProvider === 'anthropic') {
163
+ this.resolveAndUpgradeAuth(effectiveKey, options.model);
164
+ }
157
165
  this.toolContext = (0, index_2.createToolContext)(options.projectDir, {
158
166
  actorType: 'llm',
159
167
  actorId: options.agentName || options.userId,
160
- llmProvider: effectiveProvider,
161
- llmModel: options.model,
162
- llmApiKey: effectiveKey,
163
- llmAuthMode,
164
- restrictFileAccessToProject: unrestrictedCliProvider ? false : undefined,
165
- apiKey: funoliApiKey,
166
- apiBaseUrl: funoliApiBaseUrl,
167
- botName: options.agentName || 'funolio-agent',
168
168
  });
169
+ this.options.enabledTools = (0, tool_permissions_1.normalizeConfiguredToolNames)(options.enabledTools);
170
+ this.options.enabledMcpTools = normalizeMcpToolNames(options.enabledMcpTools);
169
171
  let allTools = (0, index_2.getAllToolDefinitions)(options.mcpManager);
170
172
  let builtinTools = (0, index_2.getToolDefinitions)();
171
- // Filter tools by enabledTools whitelist if specified
172
- if (!unrestrictedCliProvider && options.enabledTools && options.enabledTools.length > 0) {
173
- const allowed = new Set(options.enabledTools);
174
- allTools = allTools.filter((t) => allowed.has(t.name));
175
- builtinTools = builtinTools.filter((t) => allowed.has(t.name));
176
- console.log(chalk_1.default.gray(` [${options.agentName || 'agent'}] Tool filter: ${allTools.length} tools enabled (${options.enabledTools.length} configured)`));
173
+ // Filter builtin and MCP tools independently.
174
+ // An explicit empty array means "deny all" for that category.
175
+ if (this.options.enabledTools !== undefined || this.options.enabledMcpTools !== undefined) {
176
+ const builtinNames = new Set(builtinTools.map((t) => t.name));
177
+ const allowedBuiltin = this.options.enabledTools !== undefined ? new Set(this.options.enabledTools) : null;
178
+ const allowedMcp = this.options.enabledMcpTools !== undefined ? new Set(this.options.enabledMcpTools) : null;
179
+ allTools = allTools.filter((t) => {
180
+ if (builtinNames.has(t.name)) {
181
+ return allowedBuiltin ? allowedBuiltin.has(t.name) : true;
182
+ }
183
+ return allowedMcp ? allowedMcp.has(t.name) : true;
184
+ });
185
+ builtinTools = builtinTools.filter((t) => (allowedBuiltin ? allowedBuiltin.has(t.name) : true));
186
+ console.log(chalk_1.default.gray(` [${options.agentName || 'agent'}] Tool filter: ${allTools.length} enabled (builtin=${this.options.enabledTools?.length ?? 'all'}, mcp=${this.options.enabledMcpTools?.length ?? 'all'})`));
177
187
  }
178
188
  this.toolDefinitions = allTools;
179
189
  this.builtinToolDefinitions = builtinTools;
@@ -192,12 +202,59 @@ class MessageLoop {
192
202
  });
193
203
  // Rate limiting defaults (can be overridden via options from agent config)
194
204
  this.maxToolCallsPerMessage = options.maxToolCallsPerMessage ?? 50;
195
- // maxTokensPerMessage retained for backwards compat but no longer enforced.
196
- // LLM providers handle their own context limits with proper error responses.
197
- this.maxTokensPerMessage = options.maxTokensPerMessage ?? 1_000_000;
205
+ this.maxTokensPerMessage = options.maxTokensPerMessage ?? 200_000;
198
206
  // Session idle detection timer (checks every 60s)
199
207
  this.idleTimer = setInterval(() => this.checkIdleSession(), 60_000);
200
208
  }
209
+ /**
210
+ * Attempt to resolve Anthropic subscription auth via create_api_key exchange.
211
+ * If successful, recreates the LLM provider with the exchanged API key (which
212
+ * works with standard x-api-key auth and unlocks all models on the subscription).
213
+ * Falls back to bearer mode silently if exchange fails.
214
+ */
215
+ async resolveAndUpgradeAuth(oauthToken, model) {
216
+ try {
217
+ const result = await (0, anthropic_subscription_1.resolveAnthropicSubscriptionAuth)({
218
+ accessToken: oauthToken,
219
+ refreshToken: this.options.resolvedAuth?.credential?.refreshToken || null,
220
+ expiresAt: this.options.resolvedAuth?.credential?.expiresAt || null,
221
+ onRefresh: (credential) => {
222
+ // Propagate refreshed credentials to BOTH auth stores
223
+ if (this.options.resolvedAuth) {
224
+ this.options.resolvedAuth.credential = credential;
225
+ }
226
+ if (this.resolvedAuth) {
227
+ this.resolvedAuth.credential = credential;
228
+ // Also update the apiKey field so ensureFreshToken() sees the new token
229
+ this.resolvedAuth.apiKey = credential.accessToken;
230
+ }
231
+ console.log('[message-loop] Anthropic OAuth token refreshed and propagated to runtime state');
232
+ },
233
+ });
234
+ if (result.source === 'api-key-exchange') {
235
+ // Successfully exchanged OAuth token for a temporary API key
236
+ console.log('[message-loop] Anthropic subscription auth: using exchanged API key');
237
+ this.llmProvider = (0, index_1.createProvider)('anthropic', {
238
+ apiKey: result.token,
239
+ model,
240
+ });
241
+ }
242
+ else {
243
+ // Bearer fallback — recreate provider with the (possibly refreshed) token
244
+ const currentToken = this.options.resolvedAuth?.credential?.accessToken || oauthToken;
245
+ console.log('[message-loop] Anthropic subscription auth: using bearer fallback' +
246
+ (currentToken !== oauthToken ? ' (with refreshed token)' : ''));
247
+ this.llmProvider = (0, index_1.createProvider)('anthropic', {
248
+ apiKey: currentToken,
249
+ model,
250
+ authMode: 'oauth-bearer',
251
+ });
252
+ }
253
+ }
254
+ catch (err) {
255
+ console.warn('[message-loop] Anthropic subscription auth resolution failed, keeping bearer mode:', err?.message || err);
256
+ }
257
+ }
201
258
  /** Check if a session has gone idle and trigger auto-organize */
202
259
  async checkIdleSession() {
203
260
  if (!this.lastMessageAt || !(0, auto_organizer_1.isAutoOrganizeEnabled)())
@@ -298,11 +355,6 @@ class MessageLoop {
298
355
  console.log(chalk_1.default.yellow(`Cancelling command ${command.id}`));
299
356
  this.activeCommandId = null;
300
357
  this.approvalManager.cancelAll();
301
- // Abort any running tool execution
302
- if (this._activeAbortController) {
303
- this._activeAbortController.abort();
304
- this._activeAbortController = null;
305
- }
306
358
  }
307
359
  return;
308
360
  }
@@ -313,39 +365,65 @@ class MessageLoop {
313
365
  // Use model override from command if provided (but NOT apiKey — agent uses its own stored credentials)
314
366
  const overrideProvider = command.model?.provider;
315
367
  const overrideModel = command.model?.model;
316
- let provider = overrideProvider || this.options.provider;
368
+ const isSubscriptionRuntime = /_subscription$/i.test(String(this.options.accessMode || ''))
369
+ || this.options.provider === 'claude-cli'
370
+ || this.options.provider === 'codex-cli';
371
+ // For subscription runtimes, never let server command metadata swap us to API-provider paths.
372
+ let provider = isSubscriptionRuntime ? this.options.provider : (overrideProvider || this.options.provider);
317
373
  const model = overrideModel || this.options.model;
318
- const apiKey = this.options.apiKey;
374
+ const apiKey = this.options.oauthToken || this.options.apiKey;
319
375
  // Only create a new provider if provider or model actually changed (ignore apiKey overrides from server)
320
- const hasOverride = (overrideProvider && overrideProvider !== this.options.provider) ||
376
+ const hasOverride = (provider !== this.options.provider) ||
321
377
  (overrideModel && overrideModel !== this.options.model);
322
378
  let llm;
323
379
  if (hasOverride) {
324
- llm = (0, index_1.createProvider)(provider, {
325
- apiKey,
326
- model,
327
- ...(provider === this.options.provider && this.options.authMode ? { authMode: this.options.authMode } : {}),
328
- ...(provider === this.options.provider && this.options.baseUrl ? { baseUrl: this.options.baseUrl } : {}),
329
- ...(provider === this.options.provider && this.options.apiStyle ? { apiStyle: this.options.apiStyle } : {}),
330
- });
380
+ // For overrides with OAuth tokens, try subscription auth resolution
381
+ if ((apiKey || '').startsWith('sk-ant-oat01-') && provider === 'anthropic') {
382
+ try {
383
+ const authResult = await (0, anthropic_subscription_1.resolveAnthropicSubscriptionAuth)({
384
+ accessToken: apiKey,
385
+ refreshToken: this.options.resolvedAuth?.credential?.refreshToken || null,
386
+ expiresAt: this.options.resolvedAuth?.credential?.expiresAt || null,
387
+ onRefresh: (credential) => {
388
+ if (this.options.resolvedAuth) {
389
+ this.options.resolvedAuth.credential = credential;
390
+ }
391
+ if (this.resolvedAuth) {
392
+ this.resolvedAuth.credential = credential;
393
+ this.resolvedAuth.apiKey = credential.accessToken;
394
+ }
395
+ console.log('[message-loop] Anthropic OAuth token refreshed (override path)');
396
+ },
397
+ });
398
+ llm = (0, index_1.createProvider)(provider, {
399
+ apiKey: authResult.token,
400
+ model,
401
+ ...(authResult.authMode === 'oauth-bearer' ? { authMode: 'oauth-bearer' } : {}),
402
+ });
403
+ }
404
+ catch {
405
+ llm = (0, index_1.createProvider)(provider, { apiKey, model, authMode: 'oauth-bearer' });
406
+ }
407
+ }
408
+ else {
409
+ const resolved = this.options.resolvedAuth;
410
+ const resolvedMatchesProvider = !!resolved && resolved.provider === provider;
411
+ llm = (0, index_1.createProvider)(provider, {
412
+ apiKey,
413
+ model,
414
+ ...(resolvedMatchesProvider && resolved?.authMode ? { authMode: resolved.authMode } : {}),
415
+ ...(resolvedMatchesProvider && resolved?.baseUrl ? { baseUrl: resolved.baseUrl } : {}),
416
+ ...(resolvedMatchesProvider && resolved?.apiStyle ? { apiStyle: resolved.apiStyle } : {}),
417
+ });
418
+ }
331
419
  }
332
420
  else {
333
421
  llm = this.llmProvider;
334
422
  }
335
423
  // Refresh tool definitions each command so newly installed MCP tools are visible
336
424
  this.toolDefinitions = (0, index_2.getAllToolDefinitions)(this._mcpManager);
337
- // Fix 3: Check for dead MCP servers and note unavailable tools
338
- let deadServerNote = '';
339
- if (this._mcpManager) {
340
- const dead = this._mcpManager.getDeadServers();
341
- if (dead.length > 0) {
342
- deadServerNote = `\n\n⚠️ UNAVAILABLE TOOL SERVERS: The following MCP servers have crashed and are no longer available: ${dead.join(', ')}. Do NOT attempt to use tools from these servers — they will fail. Inform the user if they ask for functionality from these servers.`;
343
- console.log(chalk_1.default.yellow(` [MCP] Dead servers detected: ${dead.join(', ')}`));
344
- }
345
- }
346
425
  this.activeCommandId = command.id;
347
426
  this.activeCommandSource = command.source || 'user';
348
- this.chunkSeq = 0;
349
427
  this.processing = true;
350
428
  this.lastMessageAt = Date.now();
351
429
  const commandStartMs = Date.now();
@@ -406,21 +484,15 @@ class MessageLoop {
406
484
  : undefined;
407
485
  const effectiveProjectId = command.context?.projectId || activeConversation?.project_id || undefined;
408
486
  const effectiveProject = effectiveProjectId ? localData?.getProject(effectiveProjectId) : undefined;
409
- // Create abort controller for this command execution
410
- const commandAbortController = new AbortController();
411
- this._activeAbortController = commandAbortController;
412
487
  const commandToolContext = {
413
488
  ...this.toolContext,
414
489
  projectId: effectiveProjectId ?? null,
415
- abortSignal: commandAbortController.signal,
416
- // Use project folder as working directory if available
417
- ...(effectiveProject?.folder ? { projectDir: effectiveProject.folder } : {}),
418
490
  };
419
491
  // ─── Orchestrator Mode Branch ─────────────────────────
420
492
  // Resolve the selected bot profile — from command.bot, conversation bot_id, or default
421
493
  const selectedBotId = command.bot?.id || activeConversation?.bot_id || localAgentId;
422
494
  const activeProfile = selectedBotId ? localData?.getAgentProfile(selectedBotId) : null;
423
- if ((0, orchestrator_profile_1.isOrchestratorProfile)(activeProfile) && localFirst) {
495
+ if (activeProfile?.role_class === 'orchestrator' && localFirst) {
424
496
  const clerk = (0, clerk_model_1.getClerk)();
425
497
  if (!clerk) {
426
498
  // Do not silently fall through to direct chat — report error
@@ -435,15 +507,6 @@ class MessageLoop {
435
507
  const { getWorkflowEngine } = await Promise.resolve().then(() => __importStar(require('./workflow-engine')));
436
508
  const workflowEngine = getWorkflowEngine(this.options.projectDir);
437
509
  const orchestrator = new OrchestratorAgent(clerk, workflowEngine);
438
- // Orchestrator work is conversation-scoped and can continue in parallel.
439
- // Release the global message-loop lock here so another conversation can start.
440
- this.activeCommandId = null;
441
- this.processing = false;
442
- if (this.scheduledTaskTimer) {
443
- clearTimeout(this.scheduledTaskTimer);
444
- this.scheduledTaskTimer = null;
445
- }
446
- void this.drainQueue();
447
510
  try {
448
511
  const response = await orchestrator.handleUserMessage(command.prompt, localConvId || '', {
449
512
  projectDir: this.options.projectDir,
@@ -453,33 +516,27 @@ class MessageLoop {
453
516
  await this.options.mqttClient.publishResult({
454
517
  commandId: command.id,
455
518
  type: 'chunk',
456
- seq: this.chunkSeq++,
457
519
  content: status + '\n',
458
520
  timestamp: Date.now(),
459
521
  });
460
522
  },
461
523
  mqttPublish: (result) => this.options.mqttClient.publishResult(result),
462
524
  });
463
- const responseMeta = orchestrator.getLastResponseMeta();
464
- const finalAgentName = responseMeta?.agentName || 'Orchestrator';
465
- const finalBotId = responseMeta?.botId || (finalAgentName === 'Orchestrator' ? activeProfile?.id : undefined);
466
- const finalModelLabel = responseMeta?.modelLabel || undefined;
467
- // Save final response to local DB
468
- if (localConvId && localData && activeProfile) {
469
- localData.addMessage(localConvId, 'assistant', response, finalModelLabel, undefined, finalBotId, finalAgentName);
525
+ // Save O's response to local DB
526
+ if (localConvId && localData) {
527
+ localData.addMessage(localConvId, 'assistant', response, undefined, undefined, activeProfile.id, 'Project Manager');
470
528
  }
471
529
  // Publish final response and complete
472
530
  await this.options.mqttClient.publishResult({
473
531
  commandId: command.id,
474
532
  type: 'chunk',
475
- seq: this.chunkSeq++,
476
533
  content: response,
477
534
  timestamp: Date.now(),
478
535
  });
479
536
  await this.options.mqttClient.publishResult({
480
537
  commandId: command.id,
481
538
  type: 'complete',
482
- content: response || '',
539
+ content: '',
483
540
  timestamp: Date.now(),
484
541
  });
485
542
  }
@@ -493,7 +550,7 @@ class MessageLoop {
493
550
  clearTimeout(this.scheduledTaskTimer);
494
551
  this.scheduledTaskTimer = null;
495
552
  }
496
- void this.drainQueue();
553
+ this.drainQueue();
497
554
  }
498
555
  return;
499
556
  }
@@ -524,7 +581,7 @@ class MessageLoop {
524
581
  includeKeyDecisions: false,
525
582
  });
526
583
  systemPrompt = built.systemPrompt;
527
- console.log(chalk_1.default.gray(` [clerk] Context: ${built.injectedSummaries} summaries (${built.contextTokensUsed} tokens)`));
584
+ console.log(chalk_1.default.gray(` [clerk] Context: ${built.injectedFacts} facts, ${built.injectedDecisions} decisions, ${built.injectedSummaries} summaries (${built.contextTokensUsed} tokens)`));
528
585
  }
529
586
  else {
530
587
  systemPrompt = this.buildFallbackSystemPrompt(command);
@@ -538,17 +595,13 @@ class MessageLoop {
538
595
  systemPrompt = this.buildFallbackSystemPrompt(command);
539
596
  }
540
597
  // --- Context priority rules & tool fallback (AFTER context) ---
541
- // Fix 3: Inject dead server warnings into system prompt
542
- if (deadServerNote) {
543
- systemPrompt += deadServerNote;
544
- }
545
598
  systemPrompt += `\n\nCONTEXT PRIORITY RULES:
546
- 1. ALWAYS use the context provided above (summaries, workflow history, and conversations) to answer questions about past work, decisions, or projects. This is your PRIMARY knowledge source — it comes from the user's stored conversation history.
599
+ 1. ALWAYS use the context provided above (decisions, memory facts, conversations) to answer questions about past work, decisions, or projects. This is your PRIMARY knowledge source — it comes from the user's stored conversation history.
547
600
  2. Do NOT browse local files or run shell commands unless:
548
601
  a. The provided context does not contain the answer, OR
549
602
  b. The user explicitly asks you to look at local files or run a command, OR
550
603
  c. The user asks about current runtime state (logs, running processes, file contents right now)
551
- 3. When you use provided context, briefly cite the source (e.g. "Based on your stored summary..." or "From your conversation history...").
604
+ 3. When you use provided context, briefly cite the source (e.g. "Based on your stored decisions..." or "From your conversation history...").
552
605
  4. If no relevant context was provided AND the question is about past work/decisions, say "I don't have stored context about that topic yet" — don't guess or hallucinate.
553
606
 
554
607
  TOOL EFFICIENCY RULES:
@@ -556,245 +609,56 @@ TOOL EFFICIENCY RULES:
556
609
  - PREFER one broad command over many narrow ones (e.g. "find . -name '*.json' -exec grep 'error' {} +" instead of checking files one by one)
557
610
  - PLAN your approach before starting — outline what you need to find, then execute with minimal tool calls`;
558
611
  // Count context sections injected into system prompt
559
- const contextSectionCount = ['[Relevant Conversations]', '[Code References]', '[Relevant Files]', '[Bot Memory]', '[Retrieved Context]', '[Summaries]']
612
+ const contextSectionCount = ['[Key Decisions]', '[Memory Facts]', '[Relevant Conversations]', '[Code References]', '[Relevant Files]', '[Bot Memory]', '[Retrieved Context]']
560
613
  .filter(header => systemPrompt.includes(header)).length;
561
- // Add the current prompt (with image attachments if present)
562
- if (command.attachments && command.attachments.length > 0) {
563
- const contentParts = [];
564
- for (const att of command.attachments) {
565
- if (att.fileType === 'image' && att.dataUrl) {
566
- // Anthropic native format: source block with base64
567
- const commaIdx = att.dataUrl.indexOf(',');
568
- if (commaIdx >= 0) {
569
- const mimeMatch = att.dataUrl.match(/^data:([^;]+);/);
570
- const mediaType = mimeMatch?.[1] || att.mimeType || 'image/png';
571
- const base64Data = att.dataUrl.slice(commaIdx + 1);
572
- contentParts.push({ type: 'image', source: { type: 'base64', media_type: mediaType, data: base64Data } });
573
- }
574
- else {
575
- // Fallback: treat as URL
576
- contentParts.push({ type: 'image_url', image_url: { url: att.dataUrl, detail: 'auto' } });
577
- }
578
- }
579
- if (att.extractedText) {
580
- contentParts.push({ type: 'text', text: `[File: ${att.filename}]\n${att.extractedText}` });
581
- }
582
- }
583
- contentParts.push({ type: 'text', text: command.prompt });
584
- messages.push({ role: 'user', content: contentParts });
585
- }
586
- else {
587
- messages.push({ role: 'user', content: command.prompt });
588
- }
589
- // Retry budget tracker (Optimization 2)
590
- const toolFailuresByKey = new Map(); // toolName:argsHash → count
591
- const toolFailuresByName = new Map(); // toolName → count
592
- function getArgsHash(args) {
593
- const str = JSON.stringify(args || {}).slice(0, 200);
594
- return crypto.createHash('md5').update(str).digest('hex').slice(0, 12);
595
- }
596
- function getRetryKey(name, args) {
597
- // Bug 6: For meta-tools, include action in retry key so different actions don't share budget
598
- if (args?.action)
599
- return `${name}:${args.action}`;
600
- return name;
601
- }
602
- function trackToolFailure(name, args) {
603
- const key = `${getRetryKey(name, args)}:${getArgsHash(args)}`;
604
- const retryKey = getRetryKey(name, args);
605
- toolFailuresByKey.set(key, (toolFailuresByKey.get(key) || 0) + 1);
606
- toolFailuresByName.set(retryKey, (toolFailuresByName.get(retryKey) || 0) + 1);
607
- }
608
- // Futility detection: track consecutive low-value tool results
609
- let consecutiveEmptyResults = 0;
610
- const FUTILITY_THRESHOLD = 6; // after 6 consecutive empty/low-value results, nudge the LLM
611
- let futilityNudgeInjected = false;
612
- function isLowValueResult(output) {
613
- if (!output || output.length < 30)
614
- return true;
615
- const lower = output.toLowerCase();
616
- if (lower.includes('no matches found') || lower.includes('no results'))
617
- return true;
618
- if (lower.includes('0 bytes') || lower.includes('empty'))
619
- return true;
620
- // exit code 1 with no meaningful stdout
621
- if (/\[exit code: [^0]/.test(lower) && output.replace(/\[exit code:.*?\]/g, '').trim().length < 20)
622
- return true;
623
- return false;
624
- }
625
- function checkRetryBudget(name, args) {
626
- const key = `${getRetryKey(name, args)}:${getArgsHash(args)}`;
627
- const retryKey = getRetryKey(name, args);
628
- const keyCount = toolFailuresByKey.get(key) || 0;
629
- const nameCount = toolFailuresByName.get(retryKey) || 0;
630
- if (keyCount >= 2) {
631
- return `This exact operation has failed ${keyCount} times. Stop retrying and inform the user what went wrong. Suggest an alternative approach or ask for help.`;
632
- }
633
- if (nameCount >= 3) {
634
- return `The ${name} tool has failed ${nameCount} times in this conversation. Consider a completely different approach.`;
635
- }
636
- return null;
637
- }
614
+ // Add the current prompt
615
+ messages.push({ role: 'user', content: command.prompt });
638
616
  // Agentic loop - keep calling LLM until no more tool calls
639
617
  let iteration = 0;
640
- // Phase 1d: hard caps on iterations, tokens, and tool calls
641
- // Web/MQTT commands (source undefined or 'user') get tighter cap; scheduled/system tasks get more room
642
- const MAX_ITERATIONS = (command.source === 'scheduled' || command.source === 'system') ? 10 : 5;
643
- const MAX_INPUT_TOKENS = 150_000; // 150K cumulative input token budget
644
- const MAX_TOOL_CALLS = 20; // hard cap on tool calls per turn
645
- const INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes of no LLM/tool activity
646
- let lastActivityMs = Date.now();
647
- let lastProgressMs = Date.now();
618
+ const MAX_ITERATIONS = Infinity;
648
619
  let totalTokensUsed = 0;
649
- let totalInputTokens = 0;
650
- let totalOutputTokens = 0;
651
- let totalCacheReadTokens = 0;
652
- let totalCacheCreationTokens = 0;
653
- let totalToolCallDurationMs = 0;
654
- const deniedTools = new Set(); // Track tools denied by user — don't re-ask
655
- const commandStartMs = Date.now();
656
- // Publish prompt context for "View Prompt" panel
657
- try {
658
- const promptSnapshot = [
659
- { role: 'system', content: systemPrompt.slice(0, 4000) + (systemPrompt.length > 4000 ? '\n...(truncated)' : '') },
660
- ...messages.map(m => ({ role: m.role, content: typeof m.content === 'string' ? m.content.slice(0, 2000) : '' })),
661
- ];
662
- await this.options.mqttClient.publishResult({
663
- commandId: command.id,
664
- type: 'prompt_messages',
665
- messages: promptSnapshot,
666
- contextSections: contextSectionCount,
667
- model,
668
- timestamp: Date.now(),
669
- });
670
- }
671
- catch { }
672
620
  while (iteration < MAX_ITERATIONS && this.activeCommandId === command.id) {
673
621
  iteration++;
674
- // Emit progress every 60 seconds
675
- if (Date.now() - lastProgressMs >= 60_000) {
676
- lastProgressMs = Date.now();
677
- await this.options.mqttClient.publishResult({
678
- commandId: command.id,
679
- type: 'progress',
680
- elapsed: Math.round((Date.now() - commandStartMs) / 1000),
681
- iteration,
682
- toolsUsed: totalToolCalls,
683
- timestamp: Date.now(),
684
- });
685
- }
686
- // Inactivity timeout check (5 min of no LLM response or tool completion)
687
- if (Date.now() - lastActivityMs > INACTIVITY_TIMEOUT_MS) {
688
- console.log(chalk_1.default.red(` ⏱ Inactivity timeout exceeded (${INACTIVITY_TIMEOUT_MS / 1000}s) — attempting graceful wind-down`));
689
- // Inject wind-down system message and do one final LLM call
690
- messages.push({
691
- role: 'user',
692
- content: '[System] You are running out of time. Stop using tools immediately. Summarize everything you have so far and provide your best answer now.',
693
- });
694
- let windDownSucceeded = false;
695
- let windDownResponse = null;
696
- try {
697
- windDownResponse = await llm.chat({
698
- messages,
699
- system: systemPrompt,
700
- stream: true,
701
- onChunk: async (chunk) => {
702
- if (this.activeCommandId !== command.id)
703
- return;
704
- await this.options.mqttClient.publishResult({
705
- commandId: command.id,
706
- type: 'chunk',
707
- seq: this.chunkSeq++,
708
- content: chunk,
709
- timestamp: Date.now(),
710
- });
711
- },
712
- });
713
- if (windDownResponse.usage) {
714
- totalTokensUsed += windDownResponse.usage.inputTokens + windDownResponse.usage.outputTokens;
715
- totalInputTokens += windDownResponse.usage.inputTokens;
716
- totalOutputTokens += windDownResponse.usage.outputTokens;
717
- }
718
- windDownSucceeded = true;
719
- }
720
- catch (windDownErr) {
721
- console.error(chalk_1.default.red(` Wind-down LLM call failed: ${windDownErr.message}`));
722
- commandAbortController.abort();
723
- }
724
- // Emit stats
725
- await this.options.mqttClient.publishResult({
726
- commandId: command.id,
727
- type: 'stats',
728
- llmCalls: iteration,
729
- toolsUsed: totalToolCalls,
730
- inputTokens: totalInputTokens,
731
- outputTokens: totalOutputTokens,
732
- cacheReadTokens: totalCacheReadTokens || undefined,
733
- cacheCreationTokens: totalCacheCreationTokens || undefined,
734
- totalTokens: totalTokensUsed,
735
- latencyMs: Date.now() - commandStartMs,
736
- toolCallDurationMs: totalToolCallDurationMs || undefined,
737
- contextSections: contextSectionCount || undefined,
738
- model,
739
- timestamp: Date.now(),
740
- });
741
- if (windDownSucceeded) {
742
- // Wind-down produced a summary — treat as completed
743
- await this.options.mqttClient.publishResult({
744
- commandId: command.id,
745
- type: 'complete',
746
- content: windDownResponse?.content || '',
747
- timestamp: Date.now(),
748
- });
749
- console.log(chalk_1.default.green(` Command completed (inactivity wind-down): ${iteration} iterations, ${totalToolCalls} tool calls`));
750
- }
751
- else {
752
- // Wind-down failed — emit error so the job is marked failed, not completed
753
- await this.options.mqttClient.publishResult({
754
- commandId: command.id,
755
- type: 'error',
756
- error: `Command timed out after ${Math.round((Date.now() - commandStartMs) / 60000)} minutes of inactivity and wind-down failed.`,
757
- timestamp: Date.now(),
758
- });
759
- console.log(chalk_1.default.red(` Command failed (wind-down failed): ${iteration} iterations, ${totalToolCalls} tool calls`));
760
- }
761
- break;
762
- }
763
622
  // Send a separator between agentic loop iterations so streamed text
764
623
  // from different LLM calls doesn't run together in the UI.
765
624
  if (iteration > 1) {
766
625
  await this.options.mqttClient.publishResult({
767
626
  commandId: command.id,
768
627
  type: 'chunk',
769
- seq: this.chunkSeq++,
770
628
  content: '\n\n',
771
629
  timestamp: Date.now(),
772
630
  });
773
631
  }
774
- // API keys don't expire ensureFreshToken is a no-op for api-key source
632
+ // Refresh OAuth token before each LLM call if needed
775
633
  if (this.resolvedAuth) {
776
634
  const refreshed = await (0, auto_detect_1.ensureFreshToken)(this.resolvedAuth);
635
+ if (refreshed.expired) {
636
+ console.error(chalk_1.default.red(` [auth] Token refresh failed: ${refreshed.error}`));
637
+ await this.options.mqttClient.publishResult({
638
+ commandId: command.id,
639
+ type: 'error',
640
+ error: 'Your authentication token has expired or been revoked. Please re-authenticate by running `funolio-agent login` or `funolio-agent configure`.',
641
+ timestamp: Date.now(),
642
+ });
643
+ break;
644
+ }
777
645
  if (refreshed.apiKey !== this.resolvedAuth.apiKey) {
778
646
  this.resolvedAuth = refreshed;
779
647
  this.updateToken(refreshed.apiKey);
780
648
  }
781
649
  }
650
+ // With OAuth auto-detection, all providers now use direct API calls
651
+ // with full tool support. The old CLI_PROVIDERS check is kept only
652
+ // for logging/diagnostics but no longer gates tool availability.
782
653
  // Smart tool filtering: only send relevant tools to reduce token usage
783
- const filteredTools = index_1.CLI_PROVIDERS.has(this.options.provider)
784
- ? this.toolDefinitions
785
- : (0, tool_filter_1.filterToolsForMessage)(this.toolDefinitions, this.builtinToolDefinitions, command.prompt);
786
- if (!index_1.CLI_PROVIDERS.has(this.options.provider) && filteredTools.length < this.toolDefinitions.length) {
654
+ const filteredTools = (0, tool_filter_1.filterToolsForMessage)(this.toolDefinitions, this.builtinToolDefinitions, command.prompt);
655
+ if (filteredTools.length < this.toolDefinitions.length) {
787
656
  console.log(chalk_1.default.gray(` [tool-filter] ${filteredTools.length}/${this.toolDefinitions.length} tools selected`));
788
657
  }
789
- // Compress context before sending to LLM (Optimizations 3+4)
790
- const compressedMessages = (0, context_compressor_1.compressContext)(messages, 40, 3, 200);
791
- if (compressedMessages.length < messages.length) {
792
- console.log(chalk_1.default.gray(` [context] Compressed ${messages.length} → ${compressedMessages.length} messages`));
793
- }
794
658
  let response;
795
659
  try {
796
660
  response = await llm.chat({
797
- messages: compressedMessages,
661
+ messages,
798
662
  system: systemPrompt,
799
663
  stream: true,
800
664
  tools: filteredTools,
@@ -804,7 +668,6 @@ TOOL EFFICIENCY RULES:
804
668
  await this.options.mqttClient.publishResult({
805
669
  commandId: command.id,
806
670
  type: 'chunk',
807
- seq: this.chunkSeq++,
808
671
  content: chunk,
809
672
  timestamp: Date.now(),
810
673
  });
@@ -819,76 +682,16 @@ TOOL EFFICIENCY RULES:
819
682
  await this.options.mqttClient.publishResult({
820
683
  commandId: command.id,
821
684
  type: 'error',
822
- error: 'API authentication failed. Check your API key or run `funolio-agent configure` to update it.',
685
+ error: 'API authentication failed. Your token may have expired run `funolio-agent login` to refresh.',
823
686
  timestamp: Date.now(),
824
687
  });
825
688
  break;
826
689
  }
827
690
  throw llmError;
828
691
  }
829
- // Reset inactivity timer after LLM response
830
- lastActivityMs = Date.now();
831
692
  // Track token usage across iterations
832
693
  if (response.usage) {
833
694
  totalTokensUsed += response.usage.inputTokens + response.usage.outputTokens;
834
- totalInputTokens += response.usage.inputTokens;
835
- totalOutputTokens += response.usage.outputTokens;
836
- totalCacheReadTokens += response.usage.cacheReadTokens || 0;
837
- totalCacheCreationTokens += response.usage.cacheCreationTokens || 0;
838
- }
839
- // Phase 1d: Check cumulative input token budget
840
- if (totalInputTokens >= MAX_INPUT_TOKENS) {
841
- console.log(`[token-cap] Input token budget exceeded: ${totalInputTokens}/${MAX_INPUT_TOKENS} — triggering wind-down`);
842
- messages.push({
843
- role: 'user',
844
- content: '[System] Token budget exceeded. Stop using tools immediately. Summarize everything you have so far and provide your best answer now.',
845
- });
846
- try {
847
- const capResponse = await llm.chat({
848
- messages, system: systemPrompt, stream: true,
849
- onChunk: async (chunk) => {
850
- if (this.activeCommandId !== command.id)
851
- return;
852
- await this.options.mqttClient.publishResult({ commandId: command.id, type: 'chunk', seq: this.chunkSeq++, content: chunk, timestamp: Date.now() });
853
- },
854
- });
855
- if (capResponse.usage) {
856
- totalTokensUsed += capResponse.usage.inputTokens + capResponse.usage.outputTokens;
857
- totalInputTokens += capResponse.usage.inputTokens;
858
- totalOutputTokens += capResponse.usage.outputTokens;
859
- }
860
- }
861
- catch (e) {
862
- console.error(`[token-cap] Wind-down LLM call failed: ${e.message}`);
863
- }
864
- break;
865
- }
866
- // Phase 1d: Check tool call hard cap
867
- if (totalToolCalls >= MAX_TOOL_CALLS) {
868
- console.log(`[token-cap] Tool call cap exceeded: ${totalToolCalls}/${MAX_TOOL_CALLS} — triggering wind-down`);
869
- messages.push({
870
- role: 'user',
871
- content: '[System] Tool call limit reached. Stop using tools immediately. Summarize everything you have so far and provide your best answer now.',
872
- });
873
- try {
874
- const capResponse = await llm.chat({
875
- messages, system: systemPrompt, stream: true,
876
- onChunk: async (chunk) => {
877
- if (this.activeCommandId !== command.id)
878
- return;
879
- await this.options.mqttClient.publishResult({ commandId: command.id, type: 'chunk', seq: this.chunkSeq++, content: chunk, timestamp: Date.now() });
880
- },
881
- });
882
- if (capResponse.usage) {
883
- totalTokensUsed += capResponse.usage.inputTokens + capResponse.usage.outputTokens;
884
- totalInputTokens += capResponse.usage.inputTokens;
885
- totalOutputTokens += capResponse.usage.outputTokens;
886
- }
887
- }
888
- catch (e) {
889
- console.error(`[token-cap] Wind-down LLM call failed: ${e.message}`);
890
- }
891
- break;
892
695
  }
893
696
  // If there are tool calls, publish them and continue the loop
894
697
  if (response.toolCalls && response.toolCalls.length > 0) {
@@ -904,16 +707,13 @@ TOOL EFFICIENCY RULES:
904
707
  const toolSummary = (0, approval_1.generateToolSummary)(toolCall.name, toolCall.arguments);
905
708
  const toolLocation = extractToolLocation(toolCall.name, toolCall.arguments, commandToolContext.projectDir);
906
709
  // Check permission before execution
907
- const permMode = index_1.CLI_PROVIDERS.has(this.options.provider)
908
- ? 'autopilot'
909
- : (this.options.permissionMode || 'autopilot');
910
- let approval = (0, approval_1.checkPermission)(toolCall.name, permMode, index_1.CLI_PROVIDERS.has(this.options.provider) ? undefined : this.options.enabledTools);
911
- // If tool was already denied once in this command, auto-deny without re-asking
912
- if (!approval.approved && deniedTools.has(toolCall.name)) {
913
- approval = { approved: false, reason: `Tool "${toolCall.name}" was already denied in this session.` };
914
- }
710
+ const permMode = this.options.permissionMode || 'autopilot';
711
+ const allowedToolNames = this.options.enabledTools !== undefined || this.options.enabledMcpTools !== undefined
712
+ ? [...(this.options.enabledTools || []), ...(this.options.enabledMcpTools || [])]
713
+ : undefined;
714
+ let approval = (0, approval_1.checkPermission)(toolCall.name, permMode, allowedToolNames);
915
715
  // If not auto-approved, request interactive approval via MQTT
916
- if (!approval.approved && permMode !== 'autopilot' && !deniedTools.has(toolCall.name)) {
716
+ if (!approval.approved && permMode !== 'autopilot') {
917
717
  console.log(chalk_1.default.yellow(` 🔐 Requesting user approval for: ${toolCall.name}`));
918
718
  approval = await this.approvalManager.requestApproval(command.id, toolCall.name, toolCall.arguments, {
919
719
  onRequest: async (request) => {
@@ -945,7 +745,6 @@ TOOL EFFICIENCY RULES:
945
745
  }
946
746
  }
947
747
  if (!approval.approved) {
948
- deniedTools.add(toolCall.name);
949
748
  console.log(chalk_1.default.yellow(` ⚠ Tool denied: ${approval.reason}`));
950
749
  const deniedOutput = `PERMISSION_DENIED: ${approval.reason}`;
951
750
  await this.options.mqttClient.publishResult({
@@ -988,62 +787,20 @@ TOOL EFFICIENCY RULES:
988
787
  },
989
788
  timestamp: Date.now(),
990
789
  });
991
- // Check retry budget before execution (Optimization 2)
992
- const retryBlock = checkRetryBudget(toolCall.name, toolCall.arguments);
993
- if (retryBlock) {
994
- console.log(chalk_1.default.yellow(` ⚠ Retry budget exceeded for ${toolCall.name}`));
995
- const retryOutput = `ERROR: ${retryBlock}`;
996
- await this.options.mqttClient.publishResult({
997
- commandId: command.id,
998
- type: 'tool_result',
999
- toolResult: {
1000
- callId: toolCall.id,
1001
- name: toolCall.name,
1002
- output: retryOutput,
1003
- isError: true,
1004
- durationMs: 0,
1005
- },
1006
- timestamp: Date.now(),
1007
- });
1008
- messages.push({
1009
- role: 'tool',
1010
- content: retryOutput,
1011
- toolCallId: toolCall.id,
1012
- toolName: toolCall.name,
1013
- });
1014
- continue;
1015
- }
1016
790
  // Execute the tool (with MCP fallback)
1017
- const toolStartMs = Date.now();
1018
791
  const rawResult = await (0, tools_1.executeToolWithMCP)({ id: toolCall.id, name: toolCall.name, arguments: toolCall.arguments }, commandToolContext, this.options.mcpManager);
1019
792
  const toolResult = await (0, verification_1.verifyToolResult)(rawResult, toolCall.arguments, commandToolContext);
1020
- let resultOutput = toolResult.success
793
+ const resultOutput = toolResult.success
1021
794
  ? toolResult.output
1022
795
  : `ERROR: ${toolResult.error || 'Unknown error'}`;
1023
- // Bug 2 & 7: Cap tool result size to prevent context blowup
1024
- const MAX_TOOL_RESULT_CHARS = 50_000;
1025
- if (resultOutput.length > MAX_TOOL_RESULT_CHARS) {
1026
- const originalLength = resultOutput.length;
1027
- resultOutput = resultOutput.slice(0, MAX_TOOL_RESULT_CHARS) +
1028
- `\n\n[OUTPUT TRUNCATED — was ${originalLength} chars. Use more specific commands to read smaller portions.]`;
1029
- }
1030
- // Track failures for retry budget (Optimization 2)
1031
- if (!toolResult.success) {
1032
- trackToolFailure(toolCall.name, toolCall.arguments);
1033
- }
1034
796
  console.log(chalk_1.default.gray(` Result: ${toolResult.success ? '✓' : '✗'} ${resultOutput.slice(0, 100)}${resultOutput.length > 100 ? '...' : ''}`));
1035
- const toolDurationMs = Date.now() - toolStartMs;
1036
- totalToolCallDurationMs += toolDurationMs;
1037
- lastActivityMs = Date.now(); // Reset inactivity timer after tool completion
1038
797
  await this.options.mqttClient.publishResult({
1039
798
  commandId: command.id,
1040
799
  type: 'tool_result',
1041
800
  toolResult: {
1042
801
  callId: toolCall.id,
1043
- name: toolCall.name,
1044
802
  output: resultOutput,
1045
803
  isError: !toolResult.success,
1046
- durationMs: toolDurationMs,
1047
804
  success: toolResult.success,
1048
805
  summary: summarizeToolResult(resultOutput, !toolResult.success),
1049
806
  path: toolLocation.path,
@@ -1061,32 +818,15 @@ TOOL EFFICIENCY RULES:
1061
818
  toolCallId: toolCall.id,
1062
819
  toolName: toolCall.name,
1063
820
  });
1064
- // Futility detection: track consecutive low-value results
1065
- if (isLowValueResult(resultOutput)) {
1066
- consecutiveEmptyResults++;
1067
- }
1068
- else {
1069
- consecutiveEmptyResults = 0; // reset on any meaningful result
1070
- }
1071
- }
1072
- // Futility check: if too many consecutive empty results, nudge the LLM to stop spinning
1073
- if (consecutiveEmptyResults >= FUTILITY_THRESHOLD && !futilityNudgeInjected) {
1074
- futilityNudgeInjected = true;
1075
- console.log(chalk_1.default.yellow(` ⚠ Futility detected: ${consecutiveEmptyResults} consecutive low-value tool results — nudging LLM`));
1076
- messages.push({
1077
- role: 'user',
1078
- content: '[System] Your last several tool calls returned empty or no-match results. Stop searching and work with what you have. Summarize what you found (or could not find) and suggest next steps to the user. Do not make more search attempts.',
1079
- });
1080
821
  }
1081
822
  // --- Rate limit checks ---
1082
823
  // Warn at 80% of tool call limit
1083
824
  if (totalToolCalls >= this.maxToolCallsPerMessage * 0.8 && totalToolCalls < this.maxToolCallsPerMessage) {
1084
825
  console.log(chalk_1.default.yellow(` ⚠ Approaching tool call limit: ${totalToolCalls}/${this.maxToolCallsPerMessage}`));
1085
826
  }
1086
- // Log context window usage for monitoring (no artificial cap — let the LLM
1087
- // provider return its own error if context exceeds the model's real limit)
1088
- if (response.usage?.inputTokens) {
1089
- console.log(chalk_1.default.gray(` [tokens] iteration ${iteration}: ${response.usage.inputTokens} input, ${response.usage.outputTokens} output`));
827
+ // Warn at 80% of token limit
828
+ if (totalTokensUsed >= this.maxTokensPerMessage * 0.8 && totalTokensUsed < this.maxTokensPerMessage) {
829
+ console.log(chalk_1.default.yellow(` ⚠ Approaching token limit: ${totalTokensUsed}/${this.maxTokensPerMessage}`));
1090
830
  }
1091
831
  // Check tool call limit
1092
832
  if (totalToolCalls >= this.maxToolCallsPerMessage) {
@@ -1103,7 +843,6 @@ TOOL EFFICIENCY RULES:
1103
843
  await this.options.mqttClient.publishResult({
1104
844
  commandId: command.id,
1105
845
  type: 'chunk',
1106
- seq: this.chunkSeq++,
1107
846
  content: chunk,
1108
847
  timestamp: Date.now(),
1109
848
  });
@@ -1114,74 +853,55 @@ TOOL EFFICIENCY RULES:
1114
853
  }
1115
854
  await this.options.mqttClient.publishResult({
1116
855
  commandId: command.id,
1117
- type: 'stats',
1118
- llmCalls: iteration,
1119
- toolsUsed: totalToolCalls,
1120
- inputTokens: totalInputTokens,
1121
- outputTokens: totalOutputTokens,
1122
- cacheReadTokens: totalCacheReadTokens || undefined,
1123
- cacheCreationTokens: totalCacheCreationTokens || undefined,
1124
- totalTokens: totalTokensUsed,
1125
- latencyMs: Date.now() - commandStartMs,
1126
- toolCallDurationMs: totalToolCallDurationMs || undefined,
1127
- contextSections: contextSectionCount || undefined,
1128
- model,
856
+ type: 'complete',
857
+ content: '',
1129
858
  timestamp: Date.now(),
1130
859
  });
860
+ console.log(chalk_1.default.green(` Command completed: ${iteration} iterations, ${totalToolCalls} tool calls, ~${totalTokensUsed} tokens`));
861
+ break;
862
+ }
863
+ // Check token limit
864
+ if (totalTokensUsed >= this.maxTokensPerMessage) {
865
+ console.log(chalk_1.default.red(` 🛑 Token limit reached: ${totalTokensUsed}/${this.maxTokensPerMessage}`));
866
+ messages.push({ role: 'user', content: 'Token limit reached. Please summarize what you\'ve done so far.' });
867
+ const summaryResponse = await llm.chat({
868
+ messages,
869
+ system: systemPrompt,
870
+ stream: true,
871
+ onChunk: async (chunk) => {
872
+ if (this.activeCommandId !== command.id)
873
+ return;
874
+ await this.options.mqttClient.publishResult({
875
+ commandId: command.id,
876
+ type: 'chunk',
877
+ content: chunk,
878
+ timestamp: Date.now(),
879
+ });
880
+ },
881
+ });
882
+ if (summaryResponse.usage) {
883
+ totalTokensUsed += summaryResponse.usage.inputTokens + summaryResponse.usage.outputTokens;
884
+ }
1131
885
  await this.options.mqttClient.publishResult({
1132
886
  commandId: command.id,
1133
887
  type: 'complete',
1134
- content: response.content || '',
888
+ content: '',
1135
889
  timestamp: Date.now(),
1136
890
  });
1137
891
  console.log(chalk_1.default.green(` Command completed: ${iteration} iterations, ${totalToolCalls} tool calls, ~${totalTokensUsed} tokens`));
1138
892
  break;
1139
893
  }
1140
- // No artificial token limit — LLM providers return proper errors (400/413)
1141
- // when context exceeds the model's real limit, and those are caught by the
1142
- // error handler above which surfaces them to the user.
1143
894
  // Continue the loop for the next LLM call
1144
895
  continue;
1145
896
  }
1146
897
  // No tool calls - final response.
1147
- // Phase 2a: The 'complete' message includes the full response content.
1148
- // The server-side relay (mqtt-agent-relay.ts) prefers this over
1149
- // reassembled chunks for higher-fidelity persistence.
1150
- // Fix 1: Detect phantom tool use — LLM claims it did something but made 0 tool calls
1151
- const phantom = (0, response_guard_1.detectPhantomToolUse)(response.content || '', totalToolCalls);
1152
- if (phantom.detected) {
1153
- console.log(chalk_1.default.yellow(` ⚠ Phantom tool use detected — injecting correction`));
1154
- // Stream the correction to the user
1155
- await this.options.mqttClient.publishResult({
1156
- commandId: command.id,
1157
- type: 'chunk',
1158
- seq: this.chunkSeq++,
1159
- content: '\n\n---\n⚠️ ' + phantom.correction,
1160
- timestamp: Date.now(),
1161
- });
1162
- }
1163
- // Publish stats before complete
1164
- const latencyMs = Date.now() - commandStartMs;
1165
- await this.options.mqttClient.publishResult({
1166
- commandId: command.id,
1167
- type: 'stats',
1168
- llmCalls: iteration,
1169
- toolsUsed: totalToolCalls,
1170
- inputTokens: totalInputTokens,
1171
- outputTokens: totalOutputTokens,
1172
- cacheReadTokens: totalCacheReadTokens || undefined,
1173
- cacheCreationTokens: totalCacheCreationTokens || undefined,
1174
- totalTokens: totalTokensUsed,
1175
- latencyMs,
1176
- toolCallDurationMs: totalToolCallDurationMs || undefined,
1177
- contextSections: contextSectionCount || undefined,
1178
- model,
1179
- timestamp: Date.now(),
1180
- });
898
+ // DEDUP FIX: Content was already streamed via onChunk callbacks above.
899
+ // The 'complete' message signals end-of-response; we send empty content
900
+ // to avoid the receiver concatenating streamed chunks + full content.
1181
901
  await this.options.mqttClient.publishResult({
1182
902
  commandId: command.id,
1183
903
  type: 'complete',
1184
- content: response.content || '',
904
+ content: '',
1185
905
  timestamp: Date.now(),
1186
906
  });
1187
907
  // Local-first: save assistant response to SQLite + trigger funnel
@@ -1189,6 +909,7 @@ TOOL EFFICIENCY RULES:
1189
909
  try {
1190
910
  const localData = await Promise.resolve().then(() => __importStar(require('./local-data')));
1191
911
  localData.addMessage(localConvId, 'assistant', response.content || '', model);
912
+ // Trigger background memory extraction
1192
913
  (0, local_funnel_1.scheduleFunnelProcessing)(localConvId);
1193
914
  // Track for session idle auto-organize
1194
915
  this.lastOrganizedConvId = localConvId;
@@ -1200,6 +921,7 @@ TOOL EFFICIENCY RULES:
1200
921
  }
1201
922
  }
1202
923
  console.log(chalk_1.default.white(` Response: ${response.content?.slice(0, 200)}${(response.content?.length || 0) > 200 ? '...' : ''}`));
924
+ const latencyMs = Date.now() - commandStartMs;
1203
925
  console.log(`[agent-metrics] commandId=${command.id} contextSections=${contextSectionCount} toolCallsMade=${totalToolCalls} iterations=${iteration} totalTokens=${totalTokensUsed} latencyMs=${latencyMs}`);
1204
926
  console.log(chalk_1.default.green(` Command completed: ${iteration} iterations, ${totalToolCalls} tool calls, ~${totalTokensUsed} tokens`));
1205
927
  break;
@@ -1209,22 +931,7 @@ TOOL EFFICIENCY RULES:
1209
931
  }
1210
932
  }
1211
933
  catch (error) {
1212
- // Capture detailed provider error info for diagnosis
1213
- const details = [` ✗ Error: ${error.message}`];
1214
- if (error.status)
1215
- details.push(` ✗ HTTP status: ${error.status}`);
1216
- if (error.type)
1217
- details.push(` ✗ Error type: ${error.type}`);
1218
- if (error.response?.body)
1219
- details.push(` ✗ Response body: ${JSON.stringify(error.response.body).slice(0, 500)}`);
1220
- if (error.error?.type)
1221
- details.push(` ✗ Provider error type: ${error.error.type}`);
1222
- if (error.cause)
1223
- details.push(` ✗ Cause: ${error.cause}`);
1224
- // For Anthropic "terminated" / stop_reason issues
1225
- if (error.stop_reason)
1226
- details.push(` ✗ Stop reason: ${error.stop_reason}`);
1227
- details.forEach(d => console.error(chalk_1.default.red(d)));
934
+ console.error(chalk_1.default.red(` ✗ Error: ${error.message}`));
1228
935
  await this.publishError(command.id, error.message);
1229
936
  }
1230
937
  finally {
@@ -1246,31 +953,10 @@ TOOL EFFICIENCY RULES:
1246
953
  }
1247
954
  /** Build system prompt manually (without clerk) — original logic */
1248
955
  buildFallbackSystemPrompt(command) {
1249
- let systemPrompt = (0, prompt_template_1.buildDefaultSystemPrompt)({
1250
- botName: this.options.agentName,
1251
- model: this.options.model,
1252
- soulMd: command.context?.soulMd || this.options.systemPrompt,
1253
- });
1254
- // Auto-enumerate available tools so the bot always knows its capabilities
1255
- const toolDefs = (0, index_2.getToolDefinitions)();
1256
- const toolList = toolDefs.map(t => `- **${t.name}**: ${t.description.slice(0, 120)}`).join('\n');
1257
- systemPrompt += `\n\n## Your Available Tools\nYou have ${toolDefs.length} tools available. Use them proactively:\n${toolList}`;
1258
- systemPrompt += `\n\n## Working Style
1259
- - You are autonomous — take action, don't just describe what you could do.
1260
- - For complex tasks, use spawn_subagent to parallelize work.
1261
- - Read project files to understand context before making changes.
1262
- - Commit your work with git_commit when you've made meaningful progress.
1263
- - Use web_search and web_fetch to research unfamiliar topics.
1264
- - Write important context to files so you remember it across sessions.
1265
- - If you're unsure about something destructive, ask first. Otherwise, just do it.`;
1266
- // Platform awareness — always present regardless of soulMd content
1267
- systemPrompt += `\n\n## Extending Your Capabilities
1268
- - You have built-in tools (file ops, shell, git, web search, web fetch, tasks, sub-agents, memory search).
1269
- - If the user asks you to do something you don't have a tool for (e.g. Google Drive, GitHub issues, Slack, email, databases), search the Funolio marketplace with \`search_marketplace\`.
1270
- - If a matching MCP tool server exists, use \`request_mcp_install\` to request permission to install it.
1271
- - You can discover and gain new capabilities on-the-fly through the marketplace.
1272
- - **NEVER tell the user to "do it manually" or "upload it yourself"** — always check the marketplace first and offer to install the right tool.`;
1273
- systemPrompt += '\n\nIMPORTANT: When the user references a project, topic, or past work, use the relevant summaries and conversation history provided below. If no relevant context is available, say so honestly rather than guessing. Use your tools (file browsing, commands) to find project files on the local machine.';
956
+ let systemPrompt = command.context?.soulMd
957
+ || this.options.systemPrompt
958
+ || 'You are a Funolio AI agent running locally on the user\'s machine. You have access to their project files and can execute code.';
959
+ systemPrompt += '\n\nIMPORTANT: When the user references a project, topic, or past work, use the relevant memory/facts provided below. If no relevant facts are available, say so honestly rather than guessing. Use your tools (file browsing, commands) to find project files on the local machine.';
1274
960
  systemPrompt += '\n\nIMPORTANT: If a Workspace Manifest or Recent Operational State section is present, use it before repeating discovery work. Check known local files and directories before fetching from external sources. Do not re-fetch from Drive, GitHub, or the web if the state context shows the artifact is already local unless the user explicitly asks for a fresh external copy.';
1275
961
  systemPrompt += '\n\nDo not end with a deferred promise (for example: "Let me check..."). Return a final answer in this turn, or state exactly what is unavailable.';
1276
962
  systemPrompt += '\n\n' + (0, clerk_model_1.buildTodoInstructions)(this.options.agentName || 'LLM');
@@ -1293,7 +979,13 @@ When a user asks to "use Opus" or "switch to GPT-4o", identify the right model I
1293
979
  systemPrompt += '\n\n[Bot Memory]\n' + command.context.memoryMd;
1294
980
  }
1295
981
  const sc = command.context?.structuredContext;
1296
- if (sc && (sc.codeRefs?.length || sc.conversations?.length)) {
982
+ if (sc && (sc.decisions?.length || sc.memoryFacts?.length || sc.codeRefs?.length || sc.conversations?.length)) {
983
+ if (sc.decisions?.length) {
984
+ systemPrompt += '\n\n[Key Decisions]\n' + sc.decisions.map(d => `- ${d}`).join('\n');
985
+ }
986
+ if (sc.memoryFacts?.length) {
987
+ systemPrompt += '\n\n[Memory Facts]\n' + sc.memoryFacts.map(f => `- ${f}`).join('\n');
988
+ }
1297
989
  if (sc.conversations?.length) {
1298
990
  systemPrompt += '\n\n[Relevant Conversations]\n' + sc.conversations.map(c => `- ${c}`).join('\n');
1299
991
  }
@@ -1301,8 +993,11 @@ When a user asks to "use Opus" or "switch to GPT-4o", identify the right model I
1301
993
  systemPrompt += '\n\n[Code References]\n' + sc.codeRefs.map(r => `- ${r}`).join('\n');
1302
994
  }
1303
995
  }
996
+ else if (command.context?.facts?.length) {
997
+ systemPrompt += '\n\nRelevant memory/facts from the user\'s stored conversations:\n' + command.context.facts.map(f => `- ${f}`).join('\n');
998
+ }
1304
999
  else {
1305
- systemPrompt += '\n\nNo relevant stored summaries were found for this query. If the user is asking about a specific project or past work, let them know you don\'t have stored context about it and offer to search their local files instead.';
1000
+ systemPrompt += '\n\nNo relevant memories were found for this query. If the user is asking about a specific project or past work, let them know you don\'t have stored context about it and offer to search their local files instead.';
1306
1001
  }
1307
1002
  if (command.context?.files?.length) {
1308
1003
  systemPrompt += '\n\nRelevant files: ' + command.context.files.join(', ');
@@ -1345,18 +1040,24 @@ When a user asks to "use Opus" or "switch to GPT-4o", identify the right model I
1345
1040
  timestamp: Date.now(),
1346
1041
  });
1347
1042
  }
1348
- /** Update the API key used for requests */
1043
+ /** Update the OAuth token used for requests (called by token refresh) */
1349
1044
  updateToken(token) {
1350
- this.options.apiKey = token;
1045
+ this.options.oauthToken = token;
1046
+ // Recreate the default provider with the new token
1351
1047
  const effectiveProvider = this.options.provider;
1352
- const authMode = this.options.authMode || (token.startsWith('sk-ant-oat') ? 'oauth-bearer' : 'api-key');
1048
+ const resolved = this.options.resolvedAuth;
1049
+ const resolvedMatchesProvider = !!resolved && resolved.provider === effectiveProvider;
1353
1050
  this.llmProvider = (0, index_1.createProvider)(effectiveProvider, {
1354
1051
  apiKey: token,
1355
1052
  model: this.options.model,
1356
- authMode,
1357
- ...(this.options.baseUrl ? { baseUrl: this.options.baseUrl } : {}),
1358
- ...(this.options.apiStyle ? { apiStyle: this.options.apiStyle } : {}),
1053
+ ...(resolvedMatchesProvider && resolved?.authMode ? { authMode: resolved.authMode } : {}),
1054
+ ...(resolvedMatchesProvider && resolved?.baseUrl ? { baseUrl: resolved.baseUrl } : {}),
1055
+ ...(resolvedMatchesProvider && resolved?.apiStyle ? { apiStyle: resolved.apiStyle } : {}),
1056
+ ...(!resolvedMatchesProvider && token.startsWith('sk-ant-oat01-') ? { authMode: 'oauth-bearer' } : {}),
1359
1057
  });
1058
+ if (token.startsWith('sk-ant-oat01-') && effectiveProvider === 'anthropic') {
1059
+ this.resolveAndUpgradeAuth(token, this.options.model);
1060
+ }
1360
1061
  }
1361
1062
  }
1362
1063
  exports.MessageLoop = MessageLoop;