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
@@ -49,11 +49,10 @@ const service_mode_1 = require("../service-mode");
49
49
  const index_1 = require("../providers/index");
50
50
  const sync_cli_config_1 = require("../mcp/sync-cli-config");
51
51
  const agent_config_1 = require("../agent-config");
52
+ // refreshOAuthToken removed — token refresh now handled per-request in message-loop.ts
52
53
  const local_db_1 = require("../local-db");
53
54
  const local_server_1 = require("../local-server");
54
55
  const data = __importStar(require("../local-data"));
55
- const default_tool_profile_1 = require("../default-tool-profile");
56
- const approval_1 = require("../approval");
57
56
  const AUTH_SESSION_KEY = 'auth.session';
58
57
  const DESKTOP_PREFS_KEY = 'desktop.preferences';
59
58
  const WIZARD_PROFILE_KEY = 'wizard.profile';
@@ -79,33 +78,15 @@ function readLastInteractionAtMs() {
79
78
  const parsed = Date.parse(raw);
80
79
  return Number.isNaN(parsed) ? 0 : parsed;
81
80
  }
82
- /** Minimum hours between maintenance restarts to prevent restart loops */
83
- const MAINTENANCE_COOLDOWN_MS = 6 * 60 * 60 * 1000; // 6 hours
84
- const MAINTENANCE_RESTART_KEY = 'agent.last_maintenance_restart_at';
85
81
  function shouldRunNightlyRestart(now, lastInteractionAtMs, botManager) {
86
- const hour = now.getUTCHours();
87
- if (hour < 7 || hour >= 10)
82
+ const hour = now.getHours();
83
+ if (hour < 1 || hour >= 4)
88
84
  return false;
89
85
  if (botManager.isBusy())
90
86
  return false;
91
87
  if (!lastInteractionAtMs)
92
88
  return false;
93
- if ((now.getTime() - lastInteractionAtMs) < AGENT_IDLE_RESTART_MS)
94
- return false;
95
- try {
96
- const lastRestart = data.getSetting(MAINTENANCE_RESTART_KEY);
97
- if (lastRestart) {
98
- const lastRestartMs = new Date(lastRestart).getTime();
99
- if (!isNaN(lastRestartMs) && (now.getTime() - lastRestartMs) < MAINTENANCE_COOLDOWN_MS)
100
- return false;
101
- }
102
- }
103
- catch { /* best effort */ }
104
- try {
105
- data.setSetting(MAINTENANCE_RESTART_KEY, now.toISOString());
106
- }
107
- catch { /* best effort */ }
108
- return true;
89
+ return (now.getTime() - lastInteractionAtMs) >= AGENT_IDLE_RESTART_MS;
109
90
  }
110
91
  function parseJson(value) {
111
92
  if (!value)
@@ -129,10 +110,11 @@ function loadDbRuntimeConfig() {
129
110
  const defaultBot = botRows.find((bot) => bot.is_default === 1) || botRows[0];
130
111
  const providers = providerRows.map((row) => ({
131
112
  id: row.provider_id,
132
- authType: row.access_mode === 'cli'
133
- ? 'cli'
134
- : (row.auth_type === 'oauth' ? 'oauth' : 'apiKey'),
113
+ authType: row.auth_type === 'oauth' ? 'oauth' : 'apiKey',
135
114
  apiKey: row.api_key_enc || undefined,
115
+ oauthToken: row.oauth_token || undefined,
116
+ oauthRefreshToken: row.oauth_refresh_token || undefined,
117
+ oauthExpiresAt: row.oauth_expires_at ?? undefined,
136
118
  defaultModel: row.default_model || config_1.DEFAULT_MODELS[row.provider_id] || '',
137
119
  label: row.label || undefined,
138
120
  }));
@@ -144,9 +126,9 @@ function loadDbRuntimeConfig() {
144
126
  projectDir,
145
127
  provider: row.provider,
146
128
  model: row.model,
147
- enabledTools: (0, default_tool_profile_1.normalizeEnabledTools)(parseJson(row.enabled_builtin_tools_json)),
129
+ enabledTools: parseJson(row.enabled_builtin_tools_json),
148
130
  enabledMcpTools: parseJson(row.enabled_mcp_tools_json),
149
- permissionMode: (0, approval_1.normalizePermissionMode)(row.permission_mode || default_tool_profile_1.DEFAULT_PERMISSION_MODE),
131
+ permissionMode: row.permission_mode || 'autopilot',
150
132
  systemPrompt: row.final_prompt || undefined,
151
133
  agentDescription: row.purpose_md || undefined,
152
134
  createdAt: row.created_at,
@@ -181,6 +163,16 @@ function persistRuntimeAuth(config, useDbRuntime) {
181
163
  }
182
164
  (0, config_1.saveConfig)(config);
183
165
  }
166
+ function persistRuntimeProviderToken(providerId, token, refreshToken, expiresAt) {
167
+ const existing = data.findProviderConnection(providerId);
168
+ if (!existing)
169
+ return;
170
+ data.updateProviderConnection(existing.id, {
171
+ oauthToken: token,
172
+ oauthRefreshToken: refreshToken ?? existing.oauth_refresh_token,
173
+ oauthExpiresAt: expiresAt ?? existing.oauth_expires_at,
174
+ });
175
+ }
184
176
  async function startCommand(projectDir, options) {
185
177
  if (options.mode === 'windows-service') {
186
178
  process.env.LOCAL_FIRST_ENABLED = 'true';
@@ -225,10 +217,10 @@ async function startCommand(projectDir, options) {
225
217
  });
226
218
  }
227
219
  if ((0, local_db_1.isLocalFirstEnabled)() && !localServerStarted) {
228
- console.log(chalk_1.default.blue('Local-first mode enabled initializing local database...'));
220
+ console.log(chalk_1.default.blue('Local-first mode enabled — initializing local database...'));
229
221
  try {
230
222
  const dbInfo = (0, local_db_1.initLocalDb)();
231
- console.log(chalk_1.default.green(`✓ Local DB ready: ${dbInfo.path}`));
223
+ console.log(chalk_1.default.green(`✓ Local DB ready: ${dbInfo.path}`));
232
224
  console.log(chalk_1.default.gray(` WAL mode: ${dbInfo.walMode}, tables: ${dbInfo.tableCount}, write: ${dbInfo.testWrite}, read: ${dbInfo.testRead}`));
233
225
  cleanupExpiredMessageActivityRows();
234
226
  if (isServiceMode) {
@@ -236,7 +228,7 @@ async function startCommand(projectDir, options) {
236
228
  }
237
229
  }
238
230
  catch (err) {
239
- console.error(chalk_1.default.red(`✗ Local DB init failed: ${err.message}`));
231
+ console.error(chalk_1.default.red(`✗ Local DB init failed: ${err.message}`));
240
232
  console.error(chalk_1.default.yellow(' Continuing without local-first features.'));
241
233
  if (isServiceMode) {
242
234
  (0, service_mode_1.emitEvent)('local_db', { status: 'failed', error: err.message });
@@ -245,13 +237,13 @@ async function startCommand(projectDir, options) {
245
237
  try {
246
238
  await (0, local_server_1.startLocalServer)({ projectDir: initialResolvedDir });
247
239
  localServerStarted = true;
248
- console.log(chalk_1.default.green(' Local HTTP server ready'));
240
+ console.log(chalk_1.default.green('✓ Local HTTP server ready'));
249
241
  if (isServiceMode) {
250
242
  (0, service_mode_1.emitEvent)('local_server', { status: 'ready', port: LOCAL_SERVER_PORT });
251
243
  }
252
244
  }
253
245
  catch (err) {
254
- console.error(chalk_1.default.red(`✗ Local server failed: ${err.message}`));
246
+ console.error(chalk_1.default.red(`✗ Local server failed: ${err.message}`));
255
247
  console.error(chalk_1.default.yellow(' Desktop UI will not be available.'));
256
248
  if (isServiceMode) {
257
249
  (0, service_mode_1.emitEvent)('local_server', { status: 'failed', error: err.message });
@@ -296,10 +288,10 @@ async function startCommand(projectDir, options) {
296
288
  const text = await res.text().catch(() => '');
297
289
  throw new Error(`${res.status} ${res.statusText}${text ? ': ' + text : ''}`);
298
290
  }
299
- const mqttData = await res.json();
300
- auth.mqttJwt = mqttData.mqttJwt;
301
- auth.mqttJwtExpiresAt = mqttData.mqttJwtExpiresAt;
302
- auth.expiresAt = mqttData.expiresAt;
291
+ const data = await res.json();
292
+ auth.mqttJwt = data.mqttJwt;
293
+ auth.mqttJwtExpiresAt = data.mqttJwtExpiresAt;
294
+ auth.expiresAt = data.expiresAt;
303
295
  persistRuntimeAuth(config, useDbRuntime);
304
296
  console.log(chalk_1.default.green('✓ MQTT token refreshed'));
305
297
  }
@@ -316,6 +308,56 @@ async function startCommand(projectDir, options) {
316
308
  process.exit(1);
317
309
  }
318
310
  }
311
+ // Re-sync provider credentials from server (picks up web re-auth tokens)
312
+ if (config.providers?.some(p => p.authType === 'oauth')) {
313
+ try {
314
+ const res = await fetch(`${config_1.FUNOLIO_API_URL}/api/v1/agent/config`, {
315
+ headers: { Authorization: `Bearer ${auth.token}` },
316
+ });
317
+ if (res.ok) {
318
+ const serverConfig = await res.json();
319
+ if (serverConfig.providers) {
320
+ let updated = false;
321
+ for (const sp of serverConfig.providers) {
322
+ if (sp.connectionType !== 'oauth' || !sp.access_token)
323
+ continue;
324
+ const local = config.providers.find(p => p.id === sp.id);
325
+ if (!local || local.authType !== 'oauth')
326
+ continue;
327
+ // Only update if server has a different (newer) token
328
+ if (local.oauthToken !== sp.access_token) {
329
+ local.oauthToken = sp.access_token;
330
+ local.oauthRefreshToken = sp.refresh_token;
331
+ local.oauthExpiresAt = sp.expires_at;
332
+ updated = true;
333
+ console.log(chalk_1.default.green(`✓ Updated ${sp.id} OAuth token from server`));
334
+ }
335
+ }
336
+ if (updated) {
337
+ (0, config_1.saveConfig)(config);
338
+ // Also update agent config files on disk
339
+ for (const p of config.providers) {
340
+ if (p.authType !== 'oauth' || !p.oauthToken)
341
+ continue;
342
+ const { listAgents, loadAgentConfig: loadAC, saveAgentConfig: saveAC } = await Promise.resolve().then(() => __importStar(require('../agent-config')));
343
+ for (const agentName of listAgents()) {
344
+ const ac = loadAC(agentName);
345
+ if (ac && ac.provider === p.id && ac.oauthToken) {
346
+ ac.oauthToken = p.oauthToken;
347
+ ac.oauthRefreshToken = p.oauthRefreshToken;
348
+ ac.oauthExpiresAt = p.oauthExpiresAt;
349
+ saveAC(agentName, ac);
350
+ }
351
+ }
352
+ }
353
+ }
354
+ }
355
+ }
356
+ }
357
+ catch (err) {
358
+ console.log(chalk_1.default.gray(` Server config re-sync skipped: ${err.message}`));
359
+ }
360
+ }
319
361
  // Load agent-specific config if --agent was specified
320
362
  const agentLocalConfig = options.agent ? (0, agent_config_1.loadAgentConfig)(options.agent) : null;
321
363
  if (options.agent && !agentLocalConfig) {
@@ -332,9 +374,12 @@ async function startCommand(projectDir, options) {
332
374
  id: bot.id,
333
375
  name: bot.name,
334
376
  provider: bot.provider,
377
+ runtimeProvider: bot.runtimeProvider,
335
378
  model: bot.model,
379
+ runtimeModel: bot.runtimeModel,
380
+ accessMode: bot.accessMode,
336
381
  projectDir: '.',
337
- permissionMode: default_tool_profile_1.DEFAULT_PERMISSION_MODE,
382
+ permissionMode: 'autopilot',
338
383
  createdAt: new Date().toISOString(),
339
384
  }));
340
385
  if (!config.activeAgentId && config.agents.length > 0) {
@@ -346,9 +391,13 @@ async function startCommand(projectDir, options) {
346
391
  }
347
392
  // Resolve active agent from agents[] + activeAgentId FIRST (before dir resolution)
348
393
  let activeAgentConfig = null;
349
- let agentPermissionMode = default_tool_profile_1.DEFAULT_PERMISSION_MODE;
394
+ let agentPermissionMode = 'autopilot';
350
395
  let agentEnabledTools;
396
+ let agentEnabledMcpTools;
351
397
  let agentSystemPrompt;
398
+ let reportedProvider;
399
+ let reportedModel;
400
+ let reportedAccessMode;
352
401
  if (config.agents && config.agents.length > 0) {
353
402
  const activeId = config.activeAgentId;
354
403
  activeAgentConfig = (activeId
@@ -356,9 +405,13 @@ async function startCommand(projectDir, options) {
356
405
  : config.agents[0]) || null;
357
406
  if (activeAgentConfig) {
358
407
  console.log(chalk_1.default.blue(`Active agent: ${activeAgentConfig.name || activeAgentConfig.id}`));
359
- agentPermissionMode = activeAgentConfig.permissionMode || default_tool_profile_1.DEFAULT_PERMISSION_MODE;
360
- agentEnabledTools = (0, default_tool_profile_1.normalizeEnabledTools)(activeAgentConfig.enabledTools ?? activeAgentConfig.enabledMcpTools);
408
+ agentPermissionMode = activeAgentConfig.permissionMode || 'autopilot';
409
+ agentEnabledTools = activeAgentConfig.enabledTools;
410
+ agentEnabledMcpTools = activeAgentConfig.enabledMcpTools;
361
411
  agentSystemPrompt = activeAgentConfig.systemPrompt || activeAgentConfig.agentDescription;
412
+ reportedProvider = activeAgentConfig.provider;
413
+ reportedModel = activeAgentConfig.model;
414
+ reportedAccessMode = activeAgentConfig.accessMode;
362
415
  // Use agent's project dir if specified
363
416
  if (activeAgentConfig.projectDir && activeAgentConfig.projectDir !== '.') {
364
417
  projectDir = activeAgentConfig.projectDir;
@@ -382,7 +435,11 @@ async function startCommand(projectDir, options) {
382
435
  console.log('');
383
436
  }
384
437
  // Resolve active provider — prefer --agent local config, then agent-specific provider, then CLI flag
385
- const providerOverride = agentLocalConfig?.provider || activeAgentConfig?.provider || options.provider;
438
+ const providerOverride = agentLocalConfig?.runtimeProvider ||
439
+ activeAgentConfig?.runtimeProvider ||
440
+ agentLocalConfig?.provider ||
441
+ activeAgentConfig?.provider ||
442
+ options.provider;
386
443
  const activeProvider = (0, config_1.getProvider)(config, providerOverride);
387
444
  // Determine provider/model/apiKey from either providers[] or CLI flags/env
388
445
  let provider;
@@ -391,19 +448,39 @@ async function startCommand(projectDir, options) {
391
448
  let oauthToken;
392
449
  if (activeProvider) {
393
450
  provider = activeProvider.id;
394
- model = options.model || agentLocalConfig?.model || activeAgentConfig?.model || activeProvider.defaultModel;
451
+ model =
452
+ options.model ||
453
+ agentLocalConfig?.runtimeModel ||
454
+ activeAgentConfig?.runtimeModel ||
455
+ agentLocalConfig?.model ||
456
+ activeAgentConfig?.model ||
457
+ activeProvider.defaultModel;
395
458
  apiKey = options.apiKey || agentLocalConfig?.apiKey || activeProvider.apiKey || getEnvApiKey(provider);
396
459
  oauthToken = agentLocalConfig?.oauthToken || activeProvider.oauthToken;
397
460
  }
398
461
  else {
399
462
  // Fallback to CLI args / env detection (no providers configured)
400
- provider = agentLocalConfig?.provider || options.provider || detectProvider();
401
- model = agentLocalConfig?.model || options.model || defaultModel(provider);
463
+ provider =
464
+ agentLocalConfig?.runtimeProvider ||
465
+ activeAgentConfig?.runtimeProvider ||
466
+ agentLocalConfig?.provider ||
467
+ activeAgentConfig?.provider ||
468
+ options.provider ||
469
+ detectProvider();
470
+ model =
471
+ agentLocalConfig?.runtimeModel ||
472
+ activeAgentConfig?.runtimeModel ||
473
+ agentLocalConfig?.model ||
474
+ activeAgentConfig?.model ||
475
+ options.model ||
476
+ defaultModel(provider);
402
477
  apiKey = options.apiKey || agentLocalConfig?.apiKey || getEnvApiKey(provider);
403
478
  oauthToken = agentLocalConfig?.oauthToken;
404
479
  }
405
- // Auto-detect Claude Code CLI if no API key and no provider explicitly set.
406
- if (!apiKey && !options.provider && !agentLocalConfig?.provider && provider !== 'claude-cli') {
480
+ // Auto-detect Claude Code CLI if no API key/token and no provider explicitly set.
481
+ // OAuth tokens from claude.ai can't be used with the Anthropic API directly,
482
+ // so we prefer claude-cli when Claude Code is installed.
483
+ if (!apiKey && !oauthToken && !options.provider && !agentLocalConfig?.provider && provider !== 'claude-cli') {
407
484
  const claudeVersion = detectClaudeCli();
408
485
  if (claudeVersion) {
409
486
  console.log(chalk_1.default.green(`✓ Claude Code detected (${claudeVersion}), using your subscription`));
@@ -417,6 +494,61 @@ async function startCommand(projectDir, options) {
417
494
  console.error(chalk_1.default.gray(' (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY)'));
418
495
  process.exit(1);
419
496
  }
497
+ // Check if stored OAuth token is expired before starting — try refresh instead of dying
498
+ if (activeProvider?.authType === 'oauth' && activeProvider.oauthExpiresAt) {
499
+ const oauthExpiresAtMs = activeProvider.oauthExpiresAt < 1e12
500
+ ? activeProvider.oauthExpiresAt * 1000
501
+ : activeProvider.oauthExpiresAt;
502
+ if (oauthExpiresAtMs < Date.now()) {
503
+ if (activeProvider.oauthRefreshToken) {
504
+ console.log(chalk_1.default.yellow(`⚠ ${activeProvider.id} OAuth token expired — attempting refresh...`));
505
+ try {
506
+ const { refreshToken } = await Promise.resolve().then(() => __importStar(require('../auth/token-refresh')));
507
+ const result = await refreshToken({
508
+ provider: activeProvider.id,
509
+ accessToken: oauthToken || activeProvider.oauthToken || '',
510
+ refreshToken: activeProvider.oauthRefreshToken,
511
+ expiresAt: oauthExpiresAtMs,
512
+ });
513
+ oauthToken = result.credential.accessToken;
514
+ // Update config so next startup doesn't hit this again
515
+ const freshConfig = (0, config_1.loadConfig)();
516
+ const providerIdx = freshConfig.providers?.findIndex(p => p.id === activeProvider.id);
517
+ if (freshConfig.providers && providerIdx !== undefined && providerIdx >= 0) {
518
+ freshConfig.providers[providerIdx].oauthToken = result.credential.accessToken;
519
+ freshConfig.providers[providerIdx].oauthRefreshToken = result.credential.refreshToken;
520
+ freshConfig.providers[providerIdx].oauthExpiresAt = result.credential.expiresAt;
521
+ (0, config_1.saveConfig)(freshConfig);
522
+ }
523
+ console.log(chalk_1.default.green(`✓ ${activeProvider.id} token refreshed successfully`));
524
+ }
525
+ catch (err) {
526
+ const loginCmds = {
527
+ anthropic: 'claude login',
528
+ openai: 'codex login',
529
+ 'google-gemini': 'gemini login',
530
+ google: 'gemini login',
531
+ };
532
+ const cmd = loginCmds[activeProvider.id] || `${activeProvider.id} login`;
533
+ console.error(chalk_1.default.red(`✗ Your ${activeProvider.id} OAuth token has expired and refresh failed: ${err.message}`));
534
+ console.error(chalk_1.default.yellow(` Please run \`${cmd}\` and then \`funolio-agent configure\` to re-authenticate.`));
535
+ process.exit(1);
536
+ }
537
+ }
538
+ else {
539
+ const loginCmds = {
540
+ anthropic: 'claude login',
541
+ openai: 'codex login',
542
+ 'google-gemini': 'gemini login',
543
+ google: 'gemini login',
544
+ };
545
+ const cmd = loginCmds[activeProvider.id] || `${activeProvider.id} login`;
546
+ console.error(chalk_1.default.red(`✗ Your ${activeProvider.id} OAuth token has expired (no refresh token available).`));
547
+ console.error(chalk_1.default.yellow(` Please run \`${cmd}\` and then \`funolio-agent configure\` to re-authenticate.`));
548
+ process.exit(1);
549
+ }
550
+ }
551
+ }
420
552
  // Local-first DB initialization (behind feature flag)
421
553
  if ((0, local_db_1.isLocalFirstEnabled)() && !localServerStarted) {
422
554
  console.log(chalk_1.default.blue('Local-first mode enabled — initializing local database...'));
@@ -425,10 +557,6 @@ async function startCommand(projectDir, options) {
425
557
  console.log(chalk_1.default.green(`✓ Local DB ready: ${dbInfo.path}`));
426
558
  console.log(chalk_1.default.gray(` WAL mode: ${dbInfo.walMode}, tables: ${dbInfo.tableCount}, write: ${dbInfo.testWrite}, read: ${dbInfo.testRead}`));
427
559
  cleanupExpiredMessageActivityRows();
428
- const migratedProfiles = data.migrateAgentProfilesToDefaultToolProfile();
429
- if (migratedProfiles.updated > 0) {
430
- console.log(chalk_1.default.green(`✓ Migrated ${migratedProfiles.updated} agent profile(s) to default tool profile`));
431
- }
432
560
  if (isServiceMode) {
433
561
  (0, service_mode_1.emitEvent)('local_db', { status: 'ready', path: dbInfo.path, walMode: dbInfo.walMode });
434
562
  }
@@ -478,97 +606,29 @@ async function startCommand(projectDir, options) {
478
606
  authToken: auth.token,
479
607
  refreshUrl: `${config_1.FUNOLIO_API_URL}/api/v1/agent/auth/refresh`,
480
608
  });
481
- // Sync integration tokens from server BEFORE launching MCP servers.
482
- try {
483
- const authToken = (0, config_1.loadConfig)().auth?.token;
484
- if (authToken) {
485
- const tokenRes = await fetch(`${config_1.FUNOLIO_API_URL}/api/v1/google/tokens`, {
486
- headers: { Authorization: `Bearer ${authToken}` },
487
- });
488
- if (tokenRes.ok) {
489
- const tokenBody = await tokenRes.json();
490
- if (tokenBody.connected && tokenBody.tokens?.access_token) {
491
- const { handleIntegrationTokens } = await Promise.resolve().then(() => __importStar(require('../integration-tokens')));
492
- const expiresAt = tokenBody.tokens.expires_at ? new Date(tokenBody.tokens.expires_at) : null;
493
- const expiresInSec = expiresAt ? Math.max(0, Math.floor((expiresAt.getTime() - Date.now()) / 1000)) : null;
494
- handleIntegrationTokens({
495
- type: 'integration_tokens',
496
- integration: 'google',
497
- tokens: {
498
- access_token: tokenBody.tokens.access_token,
499
- refresh_token: tokenBody.tokens.refresh_token,
500
- expires_in: expiresInSec,
501
- scope: tokenBody.tokens.scope || '',
502
- },
503
- clientId: tokenBody.clientId || '',
504
- clientSecret: tokenBody.clientSecret || '',
505
- email: tokenBody.email,
506
- timestamp: Date.now(),
507
- });
508
- console.log(chalk_1.default.green(`✓ Synced Google integration tokens from server${tokenBody.email ? ` (${tokenBody.email})` : ''}`));
509
- }
510
- else {
511
- console.log(chalk_1.default.gray(' No Google integration connected on server'));
512
- }
513
- }
514
- }
515
- }
516
- catch (err) {
517
- console.warn(chalk_1.default.yellow(` [Integration] Token sync failed: ${err.message}`));
518
- }
519
609
  // Create and auto-launch MCP manager for persistent tool discovery
520
610
  const { MCPManager } = await Promise.resolve().then(() => __importStar(require('../mcp/manager')));
521
611
  const mcpManager = new MCPManager();
522
612
  await mcpManager.autoLaunch();
523
- // Sync MCP servers approved on the server but not yet launched locally
524
- try {
525
- const authToken = (0, config_1.loadConfig)().auth?.token;
526
- if (authToken) {
527
- const res = await fetch(`${config_1.FUNOLIO_API_URL}/api/v1/agent/mcp/installed?includeEnvVars=true`, {
528
- headers: { Authorization: `Bearer ${authToken}` },
529
- });
530
- if (res.ok) {
531
- const { servers: approvedServers } = await res.json();
532
- const running = new Set(mcpManager.getRunningServers());
533
- const { BUILTIN_REGISTRY } = await Promise.resolve().then(() => __importStar(require('../mcp/registry')));
534
- for (const srv of approvedServers) {
535
- if (srv.status === 'approved' && !running.has(srv.serverId)) {
536
- const entry = BUILTIN_REGISTRY.find((e) => e.id === srv.serverId);
537
- if (entry) {
538
- console.log(chalk_1.default.cyan(` 📦 Auto-launching approved MCP server: ${srv.serverId}`));
539
- try {
540
- await mcpManager.installAndLaunch(entry, srv.envVars || {}, true);
541
- }
542
- catch (err) {
543
- console.error(chalk_1.default.red(` ✗ Failed to launch ${srv.serverId}: ${err.message}`));
544
- }
545
- }
546
- }
547
- }
548
- }
549
- }
550
- }
551
- catch (err) {
552
- console.warn(chalk_1.default.yellow(` [MCP] Server-side sync failed: ${err.message}`));
553
- }
554
613
  const mcpServers = mcpManager.getRunningServers();
555
614
  if (mcpServers.length > 0) {
556
615
  console.log(chalk_1.default.green(` MCP servers running: ${mcpServers.join(', ')} (${mcpManager.getToolDefinitions().length} tools)`));
557
616
  }
558
617
  // Create bot manager (multi-bot support)
559
- // Auth modes: CLI providers handle their own auth; API key providers use BYOK.
560
618
  const botManager = new bot_manager_1.BotManager({
561
619
  projectDir: resolvedDir,
562
620
  userId,
563
621
  mqttClient,
564
622
  defaultProvider: provider,
565
623
  defaultModel: model,
624
+ defaultAccessMode: reportedAccessMode,
566
625
  defaultApiKey: apiKey,
567
626
  defaultOauthToken: oauthToken,
568
- defaultOauthRefreshToken: activeProvider?.oauthRefreshToken || agentLocalConfig?.oauthRefreshToken,
569
- defaultOauthExpiresAt: activeProvider?.oauthExpiresAt || agentLocalConfig?.oauthExpiresAt,
627
+ defaultOauthRefreshToken: activeProvider?.oauthRefreshToken,
628
+ defaultOauthExpiresAt: activeProvider?.oauthExpiresAt,
570
629
  permissionMode: agentPermissionMode,
571
630
  enabledTools: agentEnabledTools,
631
+ enabledMcpTools: agentEnabledMcpTools,
572
632
  systemPrompt: agentSystemPrompt,
573
633
  mcpManager,
574
634
  });
@@ -639,37 +699,15 @@ async function startCommand(projectDir, options) {
639
699
  tools: builtinToolNames,
640
700
  serverInfo,
641
701
  mcpTools: mcpToolNames,
642
- llmProvider: provider,
643
- llmModel: model,
702
+ llmProvider: reportedProvider || provider,
703
+ llmModel: reportedModel || model,
704
+ accessMode: reportedAccessMode,
644
705
  };
645
706
  fetch(`${config_1.FUNOLIO_API_URL}/api/v1/agent/status`, {
646
707
  method: 'POST',
647
708
  headers: { 'Content-Type': 'application/json' },
648
709
  body: JSON.stringify(statusPayload),
649
710
  }).catch(() => { }); // best-effort
650
- // Sync projects from server to local DB
651
- try {
652
- const configRes = await fetch(`${config_1.FUNOLIO_API_URL}/api/v1/agent/config`, {
653
- headers: { Authorization: `Bearer ${auth.token}` },
654
- });
655
- if (configRes.ok) {
656
- const configBody = await configRes.json();
657
- if (configBody.projects && Array.isArray(configBody.projects)) {
658
- for (const p of configBody.projects) {
659
- data.upsertProject({
660
- id: p.id || p.name,
661
- name: p.name,
662
- folder: p.folderPath || null,
663
- description: p.description || null,
664
- });
665
- }
666
- console.log(chalk_1.default.green(` ✓ Synced ${configBody.projects.length} project(s) from server`));
667
- }
668
- }
669
- }
670
- catch (err) {
671
- console.warn(chalk_1.default.yellow(` [Projects] Sync failed: ${err.message}`));
672
- }
673
711
  // Publish available providers to MQTT so web UI can show provider picker
674
712
  if (config.providers && config.providers.length > 0) {
675
713
  mqttClient.publishProviders(config.providers.map((p) => ({
@@ -708,9 +746,6 @@ async function startCommand(projectDir, options) {
708
746
  await botManager.startActive();
709
747
  // Start all additional bot loops from ~/.funolio/agents/ configs
710
748
  await botManager.startAllBots();
711
- // Fix B: Sync cloud-configured bots that aren't in local config
712
- // This ensures bots created on the web UI are available without manual local setup
713
- await botManager.syncBotsFromCloud();
714
749
  try {
715
750
  if (!data.getSetting('agent.last_interaction_at')) {
716
751
  data.setSetting('agent.last_interaction_at', new Date().toISOString());
@@ -749,6 +784,7 @@ async function startCommand(projectDir, options) {
749
784
  console.log(chalk_1.default.blue(` ${config.agents.length} agent(s) configured (active: ${activeAgentConfig?.name || 'default'})`));
750
785
  }
751
786
  // Sync MCP tools into all configured CLI runtimes so shared tools are available
787
+ // regardless of which provider happens to be active for this launch.
752
788
  const cliProvidersToSync = new Set();
753
789
  if (index_1.CLI_PROVIDERS.has(provider)) {
754
790
  cliProvidersToSync.add(provider);
@@ -773,21 +809,22 @@ async function startCommand(projectDir, options) {
773
809
  }
774
810
  }
775
811
  console.log(chalk_1.default.gray(' Waiting for commands...\n'));
812
+ // OAuth token refresh is handled per-request in message-loop.ts via ensureFreshToken()
813
+ // No background timer needed — this matches the pi-ai/Claude Code CLI pattern of
814
+ // refreshing lazily before each API call, avoiding race conditions on single-use refresh tokens
776
815
  // Listen for integration tokens delivered from the web UI via MQTT
777
816
  mqttClient.onIntegrationTokens(async (message) => {
778
817
  const { handleIntegrationTokens } = require('../integration-tokens');
779
818
  handleIntegrationTokens(message);
780
- // If Google tokens were refreshed, reload any running Google MCP servers
781
- // so they pick up the fresh credentials without a full agent restart.
782
819
  if (message.integration === 'google') {
783
820
  try {
784
821
  const restarted = await mcpManager.reloadIntegrationServers('google');
785
822
  if (restarted.length > 0) {
786
- console.log(chalk_1.default.green(` ✓ Reloaded Google MCP servers after token refresh: ${restarted.join(', ')}`));
823
+ console.log(chalk_1.default.green(`✓ Reloaded Google MCP server(s): ${restarted.join(', ')}`));
787
824
  }
788
825
  }
789
826
  catch (err) {
790
- console.error(chalk_1.default.red(` ✗ Failed to reload Google MCP servers: ${err.message}`));
827
+ console.error(chalk_1.default.yellow(`⚠ Failed to reload Google MCP servers: ${err.message}`));
791
828
  }
792
829
  }
793
830
  });
@@ -795,21 +832,15 @@ async function startCommand(projectDir, options) {
795
832
  // Also handle MCP marketplace install/uninstall commands
796
833
  mqttClient.onCommand(async (message) => {
797
834
  if (message.type === 'command' && message.action === 'mcp_install') {
798
- const { serverId, envVars, npmPackage, pipPackage } = message;
835
+ const { serverId, envVars } = message;
799
836
  console.log(chalk_1.default.cyan(`📦 Marketplace: installing MCP server "${serverId}"...`));
800
837
  try {
801
838
  const { BUILTIN_REGISTRY } = await Promise.resolve().then(() => __importStar(require('../mcp/registry')));
802
- const { MCPManager: MCPMgr } = await Promise.resolve().then(() => __importStar(require('../mcp/manager')));
803
839
  const entry = BUILTIN_REGISTRY.find((s) => s.id === serverId);
804
840
  if (!entry)
805
841
  throw new Error(`Unknown MCP server: ${serverId}`);
806
- if (entry.npmPackage) {
807
- MCPMgr.preWarmPackage(entry.npmPackage);
808
- }
809
842
  await mcpManager.installAndLaunch(entry, envVars || {}, true);
810
- const tools = mcpManager.getToolDefinitions()
811
- .filter((t) => t.serverId === serverId)
812
- .map((t) => t.name);
843
+ const tools = mcpManager.getServerInfo(serverId)?.tools.map((t) => t.name) || [];
813
844
  console.log(chalk_1.default.green(`✓ MCP server "${serverId}" installed (${tools.length} tools)`));
814
845
  await mqttClient.publishResult({
815
846
  commandId: message.id,
@@ -834,7 +865,7 @@ async function startCommand(projectDir, options) {
834
865
  const { serverId } = message;
835
866
  console.log(chalk_1.default.cyan(`📦 Marketplace: uninstalling MCP server "${serverId}"...`));
836
867
  try {
837
- await mcpManager.stopServer(serverId);
868
+ await mcpManager.uninstallServer(serverId);
838
869
  console.log(chalk_1.default.green(`✓ MCP server "${serverId}" uninstalled`));
839
870
  await mqttClient.publishResult({
840
871
  commandId: message.id,