funolio-agent 0.17.8 → 1.0.3

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 (223) hide show
  1. package/dist/approval.d.ts +7 -6
  2. package/dist/approval.d.ts.map +1 -1
  3. package/dist/approval.js +40 -10
  4. package/dist/approval.js.map +1 -1
  5. package/dist/auth/anthropic-subscription.d.ts +9 -29
  6. package/dist/auth/anthropic-subscription.d.ts.map +1 -1
  7. package/dist/auth/anthropic-subscription.js +12 -133
  8. package/dist/auth/anthropic-subscription.js.map +1 -1
  9. package/dist/auth/auto-detect.d.ts +28 -6
  10. package/dist/auth/auto-detect.d.ts.map +1 -1
  11. package/dist/auth/auto-detect.js +52 -205
  12. package/dist/auth/auto-detect.js.map +1 -1
  13. package/dist/auth/credential-reader.d.ts +24 -4
  14. package/dist/auth/credential-reader.d.ts.map +1 -1
  15. package/dist/auth/credential-reader.js +31 -256
  16. package/dist/auth/credential-reader.js.map +1 -1
  17. package/dist/auth/credential-status.d.ts +7 -27
  18. package/dist/auth/credential-status.d.ts.map +1 -1
  19. package/dist/auth/credential-status.js +7 -95
  20. package/dist/auth/credential-status.js.map +1 -1
  21. package/dist/auth/index.d.ts +9 -2
  22. package/dist/auth/index.d.ts.map +1 -1
  23. package/dist/auth/index.js +10 -10
  24. package/dist/auth/index.js.map +1 -1
  25. package/dist/auth/subscription-runtime.d.ts +19 -23
  26. package/dist/auth/subscription-runtime.d.ts.map +1 -1
  27. package/dist/auth/subscription-runtime.js +24 -292
  28. package/dist/auth/subscription-runtime.js.map +1 -1
  29. package/dist/auth/token-refresh.d.ts +19 -28
  30. package/dist/auth/token-refresh.d.ts.map +1 -1
  31. package/dist/auth/token-refresh.js +26 -464
  32. package/dist/auth/token-refresh.js.map +1 -1
  33. package/dist/backfill.js +2 -2
  34. package/dist/backfill.js.map +1 -1
  35. package/dist/bot-manager.d.ts +5 -6
  36. package/dist/bot-manager.d.ts.map +1 -1
  37. package/dist/bot-manager.js +20 -62
  38. package/dist/bot-manager.js.map +1 -1
  39. package/dist/clerk-model.d.ts +0 -1
  40. package/dist/clerk-model.d.ts.map +1 -1
  41. package/dist/clerk-model.js +24 -50
  42. package/dist/clerk-model.js.map +1 -1
  43. package/dist/commands/configure-provider.js +2 -2
  44. package/dist/commands/configure-provider.js.map +1 -1
  45. package/dist/commands/configure.d.ts.map +1 -1
  46. package/dist/commands/configure.js +10 -14
  47. package/dist/commands/configure.js.map +1 -1
  48. package/dist/commands/start.d.ts.map +1 -1
  49. package/dist/commands/start.js +51 -229
  50. package/dist/commands/start.js.map +1 -1
  51. package/dist/config.d.ts +1 -1
  52. package/dist/config.d.ts.map +1 -1
  53. package/dist/config.js +2 -2
  54. package/dist/config.js.map +1 -1
  55. package/dist/context-compressor.d.ts +6 -1
  56. package/dist/context-compressor.d.ts.map +1 -1
  57. package/dist/context-compressor.js +87 -18
  58. package/dist/context-compressor.js.map +1 -1
  59. package/dist/context-window.d.ts +2 -8
  60. package/dist/context-window.d.ts.map +1 -1
  61. package/dist/context-window.js +4 -4
  62. package/dist/context-window.js.map +1 -1
  63. package/dist/eval/orchestrator-front-door-replay.js +43 -2
  64. package/dist/eval/orchestrator-front-door-replay.js.map +1 -1
  65. package/dist/eval/orchestrator-todo-dispatch-replay.d.ts +2 -0
  66. package/dist/eval/orchestrator-todo-dispatch-replay.d.ts.map +1 -0
  67. package/dist/eval/orchestrator-todo-dispatch-replay.js +253 -0
  68. package/dist/eval/orchestrator-todo-dispatch-replay.js.map +1 -0
  69. package/dist/eval/orchestrator-todo-planning-replay.d.ts +2 -0
  70. package/dist/eval/orchestrator-todo-planning-replay.d.ts.map +1 -0
  71. package/dist/eval/orchestrator-todo-planning-replay.js +247 -0
  72. package/dist/eval/orchestrator-todo-planning-replay.js.map +1 -0
  73. package/dist/eval/policy-detection-replay.d.ts +2 -0
  74. package/dist/eval/policy-detection-replay.d.ts.map +1 -0
  75. package/dist/eval/policy-detection-replay.js +122 -0
  76. package/dist/eval/policy-detection-replay.js.map +1 -0
  77. package/dist/eval/todo-worker-runtime-replay.d.ts +2 -0
  78. package/dist/eval/todo-worker-runtime-replay.d.ts.map +1 -0
  79. package/dist/eval/todo-worker-runtime-replay.js +520 -0
  80. package/dist/eval/todo-worker-runtime-replay.js.map +1 -0
  81. package/dist/integration-tokens.d.ts +6 -0
  82. package/dist/integration-tokens.d.ts.map +1 -1
  83. package/dist/integration-tokens.js +43 -0
  84. package/dist/integration-tokens.js.map +1 -1
  85. package/dist/local-data.d.ts +134 -1
  86. package/dist/local-data.d.ts.map +1 -1
  87. package/dist/local-data.js +711 -18
  88. package/dist/local-data.js.map +1 -1
  89. package/dist/local-db.d.ts.map +1 -1
  90. package/dist/local-db.js +216 -12
  91. package/dist/local-db.js.map +1 -1
  92. package/dist/local-funnel.d.ts.map +1 -1
  93. package/dist/local-funnel.js +7 -0
  94. package/dist/local-funnel.js.map +1 -1
  95. package/dist/local-server.d.ts.map +1 -1
  96. package/dist/local-server.js +119 -96
  97. package/dist/local-server.js.map +1 -1
  98. package/dist/mcp/bridge-server.d.ts.map +1 -1
  99. package/dist/mcp/bridge-server.js +8 -2
  100. package/dist/mcp/bridge-server.js.map +1 -1
  101. package/dist/mcp/manager.d.ts +5 -0
  102. package/dist/mcp/manager.d.ts.map +1 -1
  103. package/dist/mcp/manager.js +131 -2
  104. package/dist/mcp/manager.js.map +1 -1
  105. package/dist/mcp/sync-cli-config.d.ts +5 -0
  106. package/dist/mcp/sync-cli-config.d.ts.map +1 -1
  107. package/dist/mcp/sync-cli-config.js +10 -2
  108. package/dist/mcp/sync-cli-config.js.map +1 -1
  109. package/dist/message-loop.d.ts +1 -10
  110. package/dist/message-loop.d.ts.map +1 -1
  111. package/dist/message-loop.js +179 -250
  112. package/dist/message-loop.js.map +1 -1
  113. package/dist/mqtt-client.d.ts +44 -0
  114. package/dist/mqtt-client.d.ts.map +1 -1
  115. package/dist/mqtt-client.js +2 -2
  116. package/dist/mqtt-client.js.map +1 -1
  117. package/dist/orchestration/front-door-policy.d.ts +26 -9
  118. package/dist/orchestration/front-door-policy.d.ts.map +1 -1
  119. package/dist/orchestration/front-door-policy.js +242 -69
  120. package/dist/orchestration/front-door-policy.js.map +1 -1
  121. package/dist/orchestration/orchestrator-blocked-prompt.d.ts +18 -0
  122. package/dist/orchestration/orchestrator-blocked-prompt.d.ts.map +1 -0
  123. package/dist/orchestration/orchestrator-blocked-prompt.js +46 -0
  124. package/dist/orchestration/orchestrator-blocked-prompt.js.map +1 -0
  125. package/dist/orchestration/orchestrator-final-response-prompt.d.ts +10 -0
  126. package/dist/orchestration/orchestrator-final-response-prompt.d.ts.map +1 -0
  127. package/dist/orchestration/orchestrator-final-response-prompt.js +39 -0
  128. package/dist/orchestration/orchestrator-final-response-prompt.js.map +1 -0
  129. package/dist/orchestration/orchestrator-operating-prompt.d.ts +11 -0
  130. package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
  131. package/dist/orchestration/orchestrator-operating-prompt.js +106 -36
  132. package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
  133. package/dist/orchestration/policy-prompt.d.ts +6 -0
  134. package/dist/orchestration/policy-prompt.d.ts.map +1 -0
  135. package/dist/orchestration/policy-prompt.js +40 -0
  136. package/dist/orchestration/policy-prompt.js.map +1 -0
  137. package/dist/orchestration/worker-operating-prompt.d.ts +16 -0
  138. package/dist/orchestration/worker-operating-prompt.d.ts.map +1 -0
  139. package/dist/orchestration/worker-operating-prompt.js +75 -0
  140. package/dist/orchestration/worker-operating-prompt.js.map +1 -0
  141. package/dist/orchestrator.d.ts +19 -0
  142. package/dist/orchestrator.d.ts.map +1 -1
  143. package/dist/orchestrator.js +614 -54
  144. package/dist/orchestrator.js.map +1 -1
  145. package/dist/policy-detection.d.ts +40 -0
  146. package/dist/policy-detection.d.ts.map +1 -0
  147. package/dist/policy-detection.js +298 -0
  148. package/dist/policy-detection.js.map +1 -0
  149. package/dist/providers/anthropic.d.ts +0 -5
  150. package/dist/providers/anthropic.d.ts.map +1 -1
  151. package/dist/providers/anthropic.js +34 -51
  152. package/dist/providers/anthropic.js.map +1 -1
  153. package/dist/providers/claude-cli.d.ts.map +1 -1
  154. package/dist/providers/claude-cli.js +11 -2
  155. package/dist/providers/claude-cli.js.map +1 -1
  156. package/dist/providers/codex-cli.d.ts.map +1 -1
  157. package/dist/providers/codex-cli.js +121 -2
  158. package/dist/providers/codex-cli.js.map +1 -1
  159. package/dist/providers/index.d.ts +6 -2
  160. package/dist/providers/index.d.ts.map +1 -1
  161. package/dist/providers/index.js.map +1 -1
  162. package/dist/summarization-pipeline.d.ts +1 -4
  163. package/dist/summarization-pipeline.d.ts.map +1 -1
  164. package/dist/summarization-pipeline.js +43 -56
  165. package/dist/summarization-pipeline.js.map +1 -1
  166. package/dist/tools/analyze-image.js +2 -2
  167. package/dist/tools/analyze-image.js.map +1 -1
  168. package/dist/tools/edit-file.d.ts.map +1 -1
  169. package/dist/tools/edit-file.js +16 -1
  170. package/dist/tools/edit-file.js.map +1 -1
  171. package/dist/tools/git-tools.d.ts.map +1 -1
  172. package/dist/tools/git-tools.js +32 -1
  173. package/dist/tools/git-tools.js.map +1 -1
  174. package/dist/tools/index.d.ts.map +1 -1
  175. package/dist/tools/index.js +2 -0
  176. package/dist/tools/index.js.map +1 -1
  177. package/dist/tools/list-directory.d.ts.map +1 -1
  178. package/dist/tools/list-directory.js +23 -0
  179. package/dist/tools/list-directory.js.map +1 -1
  180. package/dist/tools/notify-user.d.ts.map +1 -1
  181. package/dist/tools/notify-user.js +15 -1
  182. package/dist/tools/notify-user.js.map +1 -1
  183. package/dist/tools/read-file.d.ts.map +1 -1
  184. package/dist/tools/read-file.js +3 -1
  185. package/dist/tools/read-file.js.map +1 -1
  186. package/dist/tools/request-mcp-install.js +5 -5
  187. package/dist/tools/request-mcp-install.js.map +1 -1
  188. package/dist/tools/run-command.d.ts.map +1 -1
  189. package/dist/tools/run-command.js +16 -4
  190. package/dist/tools/run-command.js.map +1 -1
  191. package/dist/tools/sandbox.d.ts.map +1 -1
  192. package/dist/tools/sandbox.js +6 -0
  193. package/dist/tools/sandbox.js.map +1 -1
  194. package/dist/tools/schedule-task.d.ts.map +1 -1
  195. package/dist/tools/schedule-task.js +37 -0
  196. package/dist/tools/schedule-task.js.map +1 -1
  197. package/dist/tools/search-codebase.d.ts.map +1 -1
  198. package/dist/tools/search-codebase.js +251 -32
  199. package/dist/tools/search-codebase.js.map +1 -1
  200. package/dist/tools/todo-tasks.d.ts +2 -0
  201. package/dist/tools/todo-tasks.d.ts.map +1 -1
  202. package/dist/tools/todo-tasks.js +203 -6
  203. package/dist/tools/todo-tasks.js.map +1 -1
  204. package/dist/tools/web-fetch.d.ts.map +1 -1
  205. package/dist/tools/web-fetch.js +21 -2
  206. package/dist/tools/web-fetch.js.map +1 -1
  207. package/dist/tools/web-search.d.ts.map +1 -1
  208. package/dist/tools/web-search.js +38 -34
  209. package/dist/tools/web-search.js.map +1 -1
  210. package/dist/types.d.ts +2 -0
  211. package/dist/types.d.ts.map +1 -1
  212. package/dist/wizard-state.d.ts.map +1 -1
  213. package/dist/wizard-state.js +30 -8
  214. package/dist/wizard-state.js.map +1 -1
  215. package/dist/wizard-support.d.ts +2 -2
  216. package/dist/wizard-support.d.ts.map +1 -1
  217. package/dist/wizard-support.js +80 -93
  218. package/dist/wizard-support.js.map +1 -1
  219. package/dist/workflow-engine.d.ts +3 -0
  220. package/dist/workflow-engine.d.ts.map +1 -1
  221. package/dist/workflow-engine.js +111 -82
  222. package/dist/workflow-engine.js.map +1 -1
  223. package/package.json +6 -1
@@ -55,8 +55,6 @@ const response_guard_1 = require("./response-guard");
55
55
  const context_compressor_1 = require("./context-compressor");
56
56
  const crypto = __importStar(require("crypto"));
57
57
  const data = __importStar(require("./local-data"));
58
- const agent_config_1 = require("./agent-config");
59
- const subscription_runtime_1 = require("./auth/subscription-runtime");
60
58
  const prompt_template_1 = require("./prompt-template");
61
59
  /** Determine priority from an AgentCommand */
62
60
  function getCommandPriority(command) {
@@ -72,6 +70,47 @@ function getCommandPriority(command) {
72
70
  return 'medium';
73
71
  }
74
72
  const PRIORITY_ORDER = { high: 0, medium: 1, low: 2 };
73
+ function classifyToolName(toolName) {
74
+ switch (toolName) {
75
+ case 'read_file':
76
+ return 'file_read';
77
+ case 'list_directory':
78
+ return 'directory_scan';
79
+ case 'write_file':
80
+ case 'edit_file':
81
+ return 'file_write';
82
+ case 'run_command':
83
+ case 'git_status':
84
+ case 'git_diff':
85
+ case 'git_commit':
86
+ return 'command_run';
87
+ case 'web_fetch':
88
+ return 'web_fetch';
89
+ default:
90
+ return 'workspace_hint';
91
+ }
92
+ }
93
+ function extractToolLocation(toolName, args, projectDir) {
94
+ const path = typeof args.path === 'string'
95
+ ? args.path
96
+ : typeof args.filePath === 'string'
97
+ ? args.filePath
98
+ : typeof args.directory === 'string'
99
+ ? args.directory
100
+ : undefined;
101
+ const cwd = typeof args.cwd === 'string'
102
+ ? args.cwd
103
+ : toolName === 'run_command' || toolName.startsWith('git_')
104
+ ? projectDir
105
+ : undefined;
106
+ const url = typeof args.url === 'string' ? args.url : undefined;
107
+ return { path, cwd, url };
108
+ }
109
+ function summarizeToolResult(output, isError) {
110
+ const prefix = isError ? 'Tool failed: ' : 'Tool completed: ';
111
+ const compact = output.replace(/\s+/g, ' ').trim();
112
+ return `${prefix}${compact}`.slice(0, 240);
113
+ }
75
114
  class MessageLoop {
76
115
  options;
77
116
  llmProvider;
@@ -97,23 +136,16 @@ class MessageLoop {
97
136
  static SCHEDULED_TASK_TIMEOUT_MS = 5 * 60 * 1000;
98
137
  scheduledTaskTimer = null;
99
138
  static IDLE_THRESHOLD_MS = 15 * 60 * 1000; // 15 minutes
100
- static SERVER_SYNC_RETRY_MAX = 1;
101
139
  constructor(options) {
102
140
  this.options = options;
103
- // DO NOT remap Claude CLI OAuth sessions to the public Anthropic runtime
104
- // without explicit user approval.
105
- const effectiveKey = options.oauthToken || options.apiKey || '';
141
+ // Use API key directly subscription OAuth has been removed.
142
+ // CLI providers (claude-cli, codex-cli) don't use this path at all.
143
+ const effectiveKey = options.apiKey || '';
106
144
  const effectiveProvider = options.provider;
107
- const isOAuthToken = effectiveKey.startsWith('sk-ant-oat01-');
108
145
  this.llmProvider = (0, index_1.createProvider)(effectiveProvider, {
109
146
  apiKey: effectiveKey,
110
147
  model: options.model,
111
- ...(isOAuthToken ? { authMode: 'oauth-bearer' } : {}),
112
148
  });
113
- // Async: attempt to resolve Anthropic subscription auth (create_api_key exchange)
114
- if (isOAuthToken && effectiveProvider === 'anthropic') {
115
- this.resolveAndUpgradeAuth(effectiveKey, options.model);
116
- }
117
149
  // Resolve Funolio API credentials for bot status reporting
118
150
  const cfg = (0, config_1.loadConfig)();
119
151
  const funoliApiKey = cfg.auth?.token || process.env.FUNOLIO_API_KEY || '';
@@ -124,7 +156,7 @@ class MessageLoop {
124
156
  llmProvider: effectiveProvider,
125
157
  llmModel: options.model,
126
158
  llmApiKey: effectiveKey,
127
- llmAuthMode: isOAuthToken ? 'oauth-bearer' : 'api-key',
159
+ llmAuthMode: 'api-key',
128
160
  apiKey: funoliApiKey,
129
161
  apiBaseUrl: funoliApiBaseUrl,
130
162
  botName: options.agentName || 'funolio-agent',
@@ -161,147 +193,6 @@ class MessageLoop {
161
193
  // Session idle detection timer (checks every 60s)
162
194
  this.idleTimer = setInterval(() => this.checkIdleSession(), 60_000);
163
195
  }
164
- /**
165
- * Attempt to resolve Anthropic subscription auth via create_api_key exchange.
166
- * If successful, recreates the LLM provider with the exchanged API key (which
167
- * works with standard x-api-key auth and unlocks all models on the subscription).
168
- * Falls back to bearer mode silently if exchange fails.
169
- */
170
- async resolveAndUpgradeAuth(oauthToken, model) {
171
- try {
172
- const runtime = await (0, subscription_runtime_1.resolveClaudeSubscriptionRuntime)({
173
- preferredModel: model,
174
- inputCredential: {
175
- provider: 'anthropic',
176
- accessToken: oauthToken,
177
- refreshToken: this.options.resolvedAuth?.credential?.refreshToken || '',
178
- expiresAt: this.options.resolvedAuth?.credential?.expiresAt || 0,
179
- },
180
- inputSource: 'request:anthropic',
181
- persistInputCredential: true,
182
- });
183
- if (!runtime)
184
- return;
185
- if (this.options.resolvedAuth?.credential && runtime.authMode === 'oauth-bearer') {
186
- this.options.resolvedAuth.credential.accessToken = runtime.apiKey;
187
- }
188
- if (this.resolvedAuth?.credential && runtime.authMode === 'oauth-bearer') {
189
- this.resolvedAuth.credential.accessToken = runtime.apiKey;
190
- this.resolvedAuth.apiKey = runtime.apiKey;
191
- }
192
- if (runtime.authMode !== 'oauth-bearer') {
193
- // Successfully exchanged OAuth token for a temporary API key
194
- console.log('[message-loop] Anthropic subscription auth: using exchanged API key');
195
- this.llmProvider = (0, index_1.createProvider)('anthropic', {
196
- apiKey: runtime.apiKey,
197
- model,
198
- });
199
- }
200
- else {
201
- // Bearer fallback — recreate provider with the (possibly refreshed) token
202
- const currentToken = runtime.apiKey || this.options.resolvedAuth?.credential?.accessToken || oauthToken;
203
- console.log('[message-loop] Anthropic subscription auth: using bearer fallback' +
204
- (currentToken !== oauthToken ? ' (with refreshed token)' : ''));
205
- this.llmProvider = (0, index_1.createProvider)('anthropic', {
206
- apiKey: currentToken,
207
- model,
208
- authMode: 'oauth-bearer',
209
- });
210
- }
211
- }
212
- catch (err) {
213
- console.warn('[message-loop] Anthropic subscription auth resolution failed, keeping bearer mode:', err?.message || err);
214
- }
215
- }
216
- async syncOAuthFromServer(reason) {
217
- const providerId = this.options.provider;
218
- if (!providerId || providerId.endsWith('-cli'))
219
- return false;
220
- const cfg = (0, config_1.loadConfig)();
221
- const authToken = cfg.auth?.token;
222
- if (!authToken)
223
- return false;
224
- try {
225
- const res = await fetch(`${config_1.FUNOLIO_API_URL}/api/v1/agent/config`, {
226
- headers: { Authorization: `Bearer ${authToken}` },
227
- });
228
- if (!res.ok)
229
- return false;
230
- const body = await res.json();
231
- const provider = (body.providers || []).find((p) => p.id === providerId && p.connectionType === 'oauth');
232
- if (!provider?.access_token)
233
- return false;
234
- const currentAccess = this.resolvedAuth?.credential?.accessToken || this.options.oauthToken || '';
235
- const currentRefresh = this.resolvedAuth?.credential?.refreshToken || this.options.resolvedAuth?.credential?.refreshToken || '';
236
- const currentExpiry = this.resolvedAuth?.credential?.expiresAt || this.options.resolvedAuth?.credential?.expiresAt || 0;
237
- const nextExpiry = provider.expires_at || 0;
238
- const changed = provider.access_token !== currentAccess
239
- || (provider.refresh_token || '') !== currentRefresh
240
- || nextExpiry > currentExpiry;
241
- if (!changed)
242
- return false;
243
- if (this.resolvedAuth) {
244
- this.resolvedAuth.apiKey = provider.access_token;
245
- this.resolvedAuth.expired = false;
246
- this.resolvedAuth.error = undefined;
247
- if (this.resolvedAuth.credential) {
248
- this.resolvedAuth.credential.accessToken = provider.access_token;
249
- if (provider.refresh_token)
250
- this.resolvedAuth.credential.refreshToken = provider.refresh_token;
251
- if (provider.expires_at)
252
- this.resolvedAuth.credential.expiresAt = provider.expires_at;
253
- }
254
- }
255
- if (this.options.resolvedAuth) {
256
- this.options.resolvedAuth.apiKey = provider.access_token;
257
- this.options.resolvedAuth.expired = false;
258
- this.options.resolvedAuth.error = undefined;
259
- if (this.options.resolvedAuth.credential) {
260
- this.options.resolvedAuth.credential.accessToken = provider.access_token;
261
- if (provider.refresh_token)
262
- this.options.resolvedAuth.credential.refreshToken = provider.refresh_token;
263
- if (provider.expires_at)
264
- this.options.resolvedAuth.credential.expiresAt = provider.expires_at;
265
- }
266
- }
267
- this.options.oauthToken = provider.access_token;
268
- const local = (0, config_1.loadConfig)();
269
- if (!local.providers)
270
- local.providers = [];
271
- const idx = local.providers.findIndex((p) => p.id === providerId);
272
- if (idx >= 0) {
273
- local.providers[idx].authType = 'oauth';
274
- local.providers[idx].oauthToken = provider.access_token;
275
- if (provider.refresh_token)
276
- local.providers[idx].oauthRefreshToken = provider.refresh_token;
277
- if (provider.expires_at)
278
- local.providers[idx].oauthExpiresAt = provider.expires_at;
279
- if (provider.defaultModel)
280
- local.providers[idx].defaultModel = provider.defaultModel;
281
- }
282
- (0, config_1.saveConfig)(local);
283
- if (this.options.agentName) {
284
- const agentLocal = (0, agent_config_1.loadAgentConfig)(this.options.agentName);
285
- if (agentLocal && agentLocal.provider === providerId) {
286
- agentLocal.oauthToken = provider.access_token;
287
- if (provider.refresh_token)
288
- agentLocal.oauthRefreshToken = provider.refresh_token;
289
- if (provider.expires_at)
290
- agentLocal.oauthExpiresAt = provider.expires_at;
291
- if (provider.defaultModel)
292
- agentLocal.model = provider.defaultModel;
293
- (0, agent_config_1.saveAgentConfig)(this.options.agentName, agentLocal);
294
- }
295
- }
296
- this.updateToken(provider.access_token);
297
- console.log(chalk_1.default.green(` [auth] Synced updated ${providerId} OAuth credentials from server (${reason})`));
298
- return true;
299
- }
300
- catch (err) {
301
- console.log(chalk_1.default.gray(` [auth] Server OAuth sync skipped (${reason}): ${err?.message || err}`));
302
- return false;
303
- }
304
- }
305
196
  /** Check if a session has gone idle and trigger auto-organize */
306
197
  async checkIdleSession() {
307
198
  if (!this.lastMessageAt || !(0, auto_organizer_1.isAutoOrganizeEnabled)())
@@ -419,48 +310,13 @@ class MessageLoop {
419
310
  const overrideModel = command.model?.model;
420
311
  let provider = overrideProvider || this.options.provider;
421
312
  const model = overrideModel || this.options.model;
422
- const apiKey = this.options.oauthToken || this.options.apiKey;
313
+ const apiKey = this.options.apiKey;
423
314
  // Only create a new provider if provider or model actually changed (ignore apiKey overrides from server)
424
315
  const hasOverride = (overrideProvider && overrideProvider !== this.options.provider) ||
425
316
  (overrideModel && overrideModel !== this.options.model);
426
317
  let llm;
427
318
  if (hasOverride) {
428
- // For overrides with OAuth tokens, try subscription auth resolution
429
- if ((apiKey || '').startsWith('sk-ant-oat01-') && provider === 'anthropic') {
430
- try {
431
- const runtime = await (0, subscription_runtime_1.resolveClaudeSubscriptionRuntime)({
432
- preferredModel: model,
433
- inputCredential: {
434
- provider: 'anthropic',
435
- accessToken: apiKey,
436
- refreshToken: this.options.resolvedAuth?.credential?.refreshToken || '',
437
- expiresAt: this.options.resolvedAuth?.credential?.expiresAt || 0,
438
- },
439
- inputSource: 'request:anthropic',
440
- persistInputCredential: true,
441
- });
442
- const resolvedKey = runtime?.apiKey || apiKey;
443
- const authMode = runtime?.authMode;
444
- if (this.options.resolvedAuth?.credential && authMode === 'oauth-bearer') {
445
- this.options.resolvedAuth.credential.accessToken = resolvedKey;
446
- }
447
- if (this.resolvedAuth?.credential && authMode === 'oauth-bearer') {
448
- this.resolvedAuth.credential.accessToken = resolvedKey;
449
- this.resolvedAuth.apiKey = resolvedKey;
450
- }
451
- llm = (0, index_1.createProvider)(provider, {
452
- apiKey: resolvedKey,
453
- model,
454
- ...(authMode === 'oauth-bearer' ? { authMode: 'oauth-bearer' } : {}),
455
- });
456
- }
457
- catch {
458
- llm = (0, index_1.createProvider)(provider, { apiKey, model, authMode: 'oauth-bearer' });
459
- }
460
- }
461
- else {
462
- llm = (0, index_1.createProvider)(provider, { apiKey, model });
463
- }
319
+ llm = (0, index_1.createProvider)(provider, { apiKey, model });
464
320
  }
465
321
  else {
466
322
  llm = this.llmProvider;
@@ -545,6 +401,8 @@ class MessageLoop {
545
401
  ...this.toolContext,
546
402
  projectId: effectiveProjectId ?? null,
547
403
  abortSignal: commandAbortController.signal,
404
+ // Use project folder as working directory if available
405
+ ...(effectiveProject?.folder ? { projectDir: effectiveProject.folder } : {}),
548
406
  };
549
407
  // ─── Orchestrator Mode Branch ─────────────────────────
550
408
  // Resolve the selected bot profile — from command.bot, conversation bot_id, or default
@@ -717,15 +575,40 @@ TOOL EFFICIENCY RULES:
717
575
  const str = JSON.stringify(args || {}).slice(0, 200);
718
576
  return crypto.createHash('md5').update(str).digest('hex').slice(0, 12);
719
577
  }
578
+ function getRetryKey(name, args) {
579
+ // Bug 6: For meta-tools, include action in retry key so different actions don't share budget
580
+ if (args?.action)
581
+ return `${name}:${args.action}`;
582
+ return name;
583
+ }
720
584
  function trackToolFailure(name, args) {
721
- const key = `${name}:${getArgsHash(args)}`;
585
+ const key = `${getRetryKey(name, args)}:${getArgsHash(args)}`;
586
+ const retryKey = getRetryKey(name, args);
722
587
  toolFailuresByKey.set(key, (toolFailuresByKey.get(key) || 0) + 1);
723
- toolFailuresByName.set(name, (toolFailuresByName.get(name) || 0) + 1);
588
+ toolFailuresByName.set(retryKey, (toolFailuresByName.get(retryKey) || 0) + 1);
589
+ }
590
+ // Futility detection: track consecutive low-value tool results
591
+ let consecutiveEmptyResults = 0;
592
+ const FUTILITY_THRESHOLD = 6; // after 6 consecutive empty/low-value results, nudge the LLM
593
+ let futilityNudgeInjected = false;
594
+ function isLowValueResult(output) {
595
+ if (!output || output.length < 30)
596
+ return true;
597
+ const lower = output.toLowerCase();
598
+ if (lower.includes('no matches found') || lower.includes('no results'))
599
+ return true;
600
+ if (lower.includes('0 bytes') || lower.includes('empty'))
601
+ return true;
602
+ // exit code 1 with no meaningful stdout
603
+ if (/\[exit code: [^0]/.test(lower) && output.replace(/\[exit code:.*?\]/g, '').trim().length < 20)
604
+ return true;
605
+ return false;
724
606
  }
725
607
  function checkRetryBudget(name, args) {
726
- const key = `${name}:${getArgsHash(args)}`;
608
+ const key = `${getRetryKey(name, args)}:${getArgsHash(args)}`;
609
+ const retryKey = getRetryKey(name, args);
727
610
  const keyCount = toolFailuresByKey.get(key) || 0;
728
- const nameCount = toolFailuresByName.get(name) || 0;
611
+ const nameCount = toolFailuresByName.get(retryKey) || 0;
729
612
  if (keyCount >= 2) {
730
613
  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.`;
731
614
  }
@@ -736,7 +619,7 @@ TOOL EFFICIENCY RULES:
736
619
  }
737
620
  // Agentic loop - keep calling LLM until no more tool calls
738
621
  let iteration = 0;
739
- const MAX_ITERATIONS = 100;
622
+ const MAX_ITERATIONS = 30;
740
623
  const GLOBAL_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes wall-clock timeout
741
624
  let totalTokensUsed = 0;
742
625
  let totalInputTokens = 0;
@@ -744,7 +627,6 @@ TOOL EFFICIENCY RULES:
744
627
  let totalCacheReadTokens = 0;
745
628
  let totalCacheCreationTokens = 0;
746
629
  let totalToolCallDurationMs = 0;
747
- let serverSyncRetries = 0;
748
630
  const deniedTools = new Set(); // Track tools denied by user — don't re-ask
749
631
  const commandStartMs = Date.now();
750
632
  // Publish prompt context for "View Prompt" panel
@@ -787,34 +669,14 @@ TOOL EFFICIENCY RULES:
787
669
  timestamp: Date.now(),
788
670
  });
789
671
  }
790
- // Refresh OAuth token before each LLM call if needed
672
+ // API keys don't expire ensureFreshToken is a no-op for api-key source
791
673
  if (this.resolvedAuth) {
792
674
  const refreshed = await (0, auto_detect_1.ensureFreshToken)(this.resolvedAuth);
793
- if (refreshed.expired) {
794
- if (serverSyncRetries < MessageLoop.SERVER_SYNC_RETRY_MAX) {
795
- const synced = await this.syncOAuthFromServer('refresh-failed');
796
- if (synced) {
797
- serverSyncRetries++;
798
- continue;
799
- }
800
- }
801
- console.error(chalk_1.default.red(` [auth] Token refresh failed: ${refreshed.error}`));
802
- await this.options.mqttClient.publishResult({
803
- commandId: command.id,
804
- type: 'error',
805
- error: 'Your authentication token has expired or been revoked. Please re-authenticate by running `funolio-agent login` or `funolio-agent configure`.',
806
- timestamp: Date.now(),
807
- });
808
- break;
809
- }
810
675
  if (refreshed.apiKey !== this.resolvedAuth.apiKey) {
811
676
  this.resolvedAuth = refreshed;
812
677
  this.updateToken(refreshed.apiKey);
813
678
  }
814
679
  }
815
- // With OAuth auto-detection, all providers now use direct API calls
816
- // with full tool support. The old CLI_PROVIDERS check is kept only
817
- // for logging/diagnostics but no longer gates tool availability.
818
680
  // Smart tool filtering: only send relevant tools to reduce token usage
819
681
  const filteredTools = (0, tool_filter_1.filterToolsForMessage)(this.toolDefinitions, this.builtinToolDefinitions, command.prompt);
820
682
  if (filteredTools.length < this.toolDefinitions.length) {
@@ -848,26 +710,17 @@ TOOL EFFICIENCY RULES:
848
710
  const msg = llmError?.message || String(llmError);
849
711
  const isAuthError = /401|403|unauthorized|forbidden|authentication/i.test(msg);
850
712
  if (isAuthError) {
851
- if (serverSyncRetries < MessageLoop.SERVER_SYNC_RETRY_MAX) {
852
- const synced = await this.syncOAuthFromServer('llm-auth-error');
853
- if (synced) {
854
- serverSyncRetries++;
855
- continue;
856
- }
857
- }
858
713
  console.error(chalk_1.default.red(` [auth] LLM API authentication failed: ${msg}`));
859
714
  await this.options.mqttClient.publishResult({
860
715
  commandId: command.id,
861
716
  type: 'error',
862
- error: 'API authentication failed. Your token may have expired run `funolio-agent login` to refresh.',
717
+ error: 'API authentication failed. Check your API key or run `funolio-agent configure` to update it.',
863
718
  timestamp: Date.now(),
864
719
  });
865
720
  break;
866
721
  }
867
722
  throw llmError;
868
723
  }
869
- // Any successful provider response clears one-shot sync retry budget
870
- serverSyncRetries = 0;
871
724
  // Track token usage across iterations
872
725
  if (response.usage) {
873
726
  totalTokensUsed += response.usage.inputTokens + response.usage.outputTokens;
@@ -887,6 +740,8 @@ TOOL EFFICIENCY RULES:
887
740
  for (const toolCall of response.toolCalls) {
888
741
  totalToolCalls++;
889
742
  console.log(chalk_1.default.cyan(` 🔧 Tool: ${toolCall.name}(${JSON.stringify(toolCall.arguments).slice(0, 60)}...)`));
743
+ const toolSummary = (0, approval_1.generateToolSummary)(toolCall.name, toolCall.arguments);
744
+ const toolLocation = extractToolLocation(toolCall.name, toolCall.arguments, commandToolContext.projectDir);
890
745
  // Check permission before execution
891
746
  const permMode = this.options.permissionMode || 'autopilot';
892
747
  let approval = (0, approval_1.checkPermission)(toolCall.name, permMode, this.options.enabledTools);
@@ -897,20 +752,29 @@ TOOL EFFICIENCY RULES:
897
752
  // If not auto-approved, request interactive approval via MQTT
898
753
  if (!approval.approved && permMode !== 'autopilot' && !deniedTools.has(toolCall.name)) {
899
754
  console.log(chalk_1.default.yellow(` 🔐 Requesting user approval for: ${toolCall.name}`));
900
- // Generate requestId up front so the MQTT result and pending map use the same ID
901
- const approvalRequestId = require('crypto').randomUUID();
902
- await this.options.mqttClient.publishResult({
903
- commandId: command.id,
904
- type: 'approval_request',
905
- requestId: approvalRequestId,
906
- toolCall: {
907
- id: toolCall.id,
908
- name: toolCall.name,
909
- arguments: toolCall.arguments,
755
+ approval = await this.approvalManager.requestApproval(command.id, toolCall.name, toolCall.arguments, {
756
+ onRequest: async (request) => {
757
+ await this.options.mqttClient.publishResult({
758
+ commandId: command.id,
759
+ type: 'approval_request',
760
+ requestId: request.requestId,
761
+ riskLevel: request.riskLevel,
762
+ summary: request.summary,
763
+ toolCall: {
764
+ id: toolCall.id,
765
+ name: toolCall.name,
766
+ arguments: toolCall.arguments,
767
+ classification: classifyToolName(toolCall.name),
768
+ summary: toolSummary,
769
+ path: toolLocation.path,
770
+ cwd: toolLocation.cwd,
771
+ url: toolLocation.url,
772
+ importance: request.riskLevel === 'destructive' ? 'high' : 'medium',
773
+ },
774
+ timestamp: Date.now(),
775
+ });
910
776
  },
911
- timestamp: Date.now(),
912
777
  });
913
- approval = await this.approvalManager.requestApprovalWithId(approvalRequestId, command.id, toolCall.name, toolCall.arguments);
914
778
  // If user chose "always allow", persist it
915
779
  if (approval.approved && approval.remember) {
916
780
  (0, approval_1.rememberTool)(toolCall.name);
@@ -920,19 +784,26 @@ TOOL EFFICIENCY RULES:
920
784
  if (!approval.approved) {
921
785
  deniedTools.add(toolCall.name);
922
786
  console.log(chalk_1.default.yellow(` ⚠ Tool denied: ${approval.reason}`));
787
+ const deniedOutput = `PERMISSION_DENIED: ${approval.reason}`;
923
788
  await this.options.mqttClient.publishResult({
924
789
  commandId: command.id,
925
790
  type: 'tool_result',
926
791
  toolResult: {
927
792
  callId: toolCall.id,
928
- output: `PERMISSION_DENIED: ${approval.reason}`,
793
+ output: deniedOutput,
929
794
  isError: true,
795
+ success: false,
796
+ summary: summarizeToolResult(deniedOutput, true),
797
+ path: toolLocation.path,
798
+ cwd: toolLocation.cwd,
799
+ url: toolLocation.url,
800
+ pathsTouched: toolLocation.path ? [toolLocation.path] : [],
930
801
  },
931
802
  timestamp: Date.now(),
932
803
  });
933
804
  messages.push({
934
805
  role: 'tool',
935
- content: `PERMISSION_DENIED: ${approval.reason}`,
806
+ content: deniedOutput,
936
807
  toolCallId: toolCall.id,
937
808
  toolName: toolCall.name,
938
809
  });
@@ -945,6 +816,12 @@ TOOL EFFICIENCY RULES:
945
816
  id: toolCall.id,
946
817
  name: toolCall.name,
947
818
  arguments: toolCall.arguments,
819
+ classification: classifyToolName(toolCall.name),
820
+ summary: toolSummary,
821
+ path: toolLocation.path,
822
+ cwd: toolLocation.cwd,
823
+ url: toolLocation.url,
824
+ importance: (0, approval_1.getRiskLevel)(toolCall.name) === 'destructive' ? 'high' : 'medium',
948
825
  },
949
826
  timestamp: Date.now(),
950
827
  });
@@ -977,9 +854,16 @@ TOOL EFFICIENCY RULES:
977
854
  const toolStartMs = Date.now();
978
855
  const rawResult = await (0, tools_1.executeToolWithMCP)({ id: toolCall.id, name: toolCall.name, arguments: toolCall.arguments }, commandToolContext, this.options.mcpManager);
979
856
  const toolResult = await (0, verification_1.verifyToolResult)(rawResult, toolCall.arguments, commandToolContext);
980
- const resultOutput = toolResult.success
857
+ let resultOutput = toolResult.success
981
858
  ? toolResult.output
982
859
  : `ERROR: ${toolResult.error || 'Unknown error'}`;
860
+ // Bug 2 & 7: Cap tool result size to prevent context blowup
861
+ const MAX_TOOL_RESULT_CHARS = 50_000;
862
+ if (resultOutput.length > MAX_TOOL_RESULT_CHARS) {
863
+ const originalLength = resultOutput.length;
864
+ resultOutput = resultOutput.slice(0, MAX_TOOL_RESULT_CHARS) +
865
+ `\n\n[OUTPUT TRUNCATED — was ${originalLength} chars. Use more specific commands to read smaller portions.]`;
866
+ }
983
867
  // Track failures for retry budget (Optimization 2)
984
868
  if (!toolResult.success) {
985
869
  trackToolFailure(toolCall.name, toolCall.arguments);
@@ -996,6 +880,13 @@ TOOL EFFICIENCY RULES:
996
880
  output: resultOutput,
997
881
  isError: !toolResult.success,
998
882
  durationMs: toolDurationMs,
883
+ success: toolResult.success,
884
+ summary: summarizeToolResult(resultOutput, !toolResult.success),
885
+ path: toolLocation.path,
886
+ cwd: toolLocation.cwd,
887
+ url: toolLocation.url,
888
+ pathsTouched: toolLocation.path ? [toolLocation.path] : [],
889
+ exitCode: typeof toolResult.exit_code === 'number' ? toolResult.exit_code : undefined,
999
890
  },
1000
891
  timestamp: Date.now(),
1001
892
  });
@@ -1006,6 +897,22 @@ TOOL EFFICIENCY RULES:
1006
897
  toolCallId: toolCall.id,
1007
898
  toolName: toolCall.name,
1008
899
  });
900
+ // Futility detection: track consecutive low-value results
901
+ if (isLowValueResult(resultOutput)) {
902
+ consecutiveEmptyResults++;
903
+ }
904
+ else {
905
+ consecutiveEmptyResults = 0; // reset on any meaningful result
906
+ }
907
+ }
908
+ // Futility check: if too many consecutive empty results, nudge the LLM to stop spinning
909
+ if (consecutiveEmptyResults >= FUTILITY_THRESHOLD && !futilityNudgeInjected) {
910
+ futilityNudgeInjected = true;
911
+ console.log(chalk_1.default.yellow(` ⚠ Futility detected: ${consecutiveEmptyResults} consecutive low-value tool results — nudging LLM`));
912
+ messages.push({
913
+ role: 'user',
914
+ 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.',
915
+ });
1009
916
  }
1010
917
  // --- Rate limit checks ---
1011
918
  // Warn at 80% of tool call limit
@@ -1184,6 +1091,7 @@ TOOL EFFICIENCY RULES:
1184
1091
  - You can discover and gain new capabilities on-the-fly through the marketplace.
1185
1092
  - **NEVER tell the user to "do it manually" or "upload it yourself"** — always check the marketplace first and offer to install the right tool.`;
1186
1093
  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.';
1094
+ 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.';
1187
1095
  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.';
1188
1096
  systemPrompt += '\n\n' + (0, clerk_model_1.buildTodoInstructions)(this.options.agentName || 'LLM');
1189
1097
  // Inject model self-switch info
@@ -1228,6 +1136,32 @@ When a user asks to "use Opus" or "switch to GPT-4o", identify the right model I
1228
1136
  if (command.context?.files?.length) {
1229
1137
  systemPrompt += '\n\nRelevant files: ' + command.context.files.join(', ');
1230
1138
  }
1139
+ if (command.context?.stateContext?.summaryText) {
1140
+ systemPrompt += '\n\n[Recent Operational State]\n' + command.context.stateContext.summaryText;
1141
+ }
1142
+ if (command.context?.workspaceManifest) {
1143
+ const manifestLines = [];
1144
+ const manifest = command.context.workspaceManifest;
1145
+ if (manifest.projectRoot)
1146
+ manifestLines.push(`- Project root: ${manifest.projectRoot}`);
1147
+ if (manifest.likelyWorkingDirectory)
1148
+ manifestLines.push(`- Likely working directory: ${manifest.likelyWorkingDirectory}`);
1149
+ if (manifest.recentlyReadFiles?.length)
1150
+ manifestLines.push(`- Recently read files: ${manifest.recentlyReadFiles.join(', ')}`);
1151
+ if (manifest.recentlyWrittenFiles?.length)
1152
+ manifestLines.push(`- Recently written files: ${manifest.recentlyWrittenFiles.join(', ')}`);
1153
+ if (manifest.recentlyScannedDirectories?.length)
1154
+ manifestLines.push(`- Recently scanned directories: ${manifest.recentlyScannedDirectories.join(', ')}`);
1155
+ if (manifest.recentDownloads?.length)
1156
+ manifestLines.push(`- Recent downloads: ${manifest.recentDownloads.join(', ')}`);
1157
+ if (manifest.recentFailures?.length)
1158
+ manifestLines.push(`- Recent failures: ${manifest.recentFailures.join(' | ')}`);
1159
+ if (manifest.startHereHints?.length)
1160
+ manifestLines.push(`- Start here: ${manifest.startHereHints.join(', ')}`);
1161
+ if (manifestLines.length) {
1162
+ systemPrompt += '\n\n[Workspace Manifest]\n' + manifestLines.join('\n');
1163
+ }
1164
+ }
1231
1165
  return systemPrompt;
1232
1166
  }
1233
1167
  async publishError(commandId, error) {
@@ -1240,19 +1174,14 @@ When a user asks to "use Opus" or "switch to GPT-4o", identify the right model I
1240
1174
  timestamp: Date.now(),
1241
1175
  });
1242
1176
  }
1243
- /** Update the OAuth token used for requests (called by token refresh) */
1177
+ /** Update the API key used for requests */
1244
1178
  updateToken(token) {
1245
- this.options.oauthToken = token;
1246
- // Recreate the default provider with the new token
1179
+ this.options.apiKey = token;
1247
1180
  const effectiveProvider = this.options.provider;
1248
1181
  this.llmProvider = (0, index_1.createProvider)(effectiveProvider, {
1249
1182
  apiKey: token,
1250
1183
  model: this.options.model,
1251
- ...(token.startsWith('sk-ant-oat01-') ? { authMode: 'oauth-bearer' } : {}),
1252
1184
  });
1253
- if (token.startsWith('sk-ant-oat01-') && effectiveProvider === 'anthropic') {
1254
- this.resolveAndUpgradeAuth(token, this.options.model);
1255
- }
1256
1185
  }
1257
1186
  }
1258
1187
  exports.MessageLoop = MessageLoop;