funolio-agent 1.0.47 → 1.0.48

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 (169) 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/auto-organizer.d.ts.map +1 -1
  6. package/dist/auto-organizer.js +4 -3
  7. package/dist/auto-organizer.js.map +1 -1
  8. package/dist/backfill.d.ts.map +1 -1
  9. package/dist/backfill.js +3 -2
  10. package/dist/backfill.js.map +1 -1
  11. package/dist/bot-manager.d.ts +7 -23
  12. package/dist/bot-manager.d.ts.map +1 -1
  13. package/dist/bot-manager.js +52 -388
  14. package/dist/bot-manager.js.map +1 -1
  15. package/dist/clerk-model.d.ts +5 -1
  16. package/dist/clerk-model.d.ts.map +1 -1
  17. package/dist/clerk-model.js +40 -28
  18. package/dist/clerk-model.js.map +1 -1
  19. package/dist/cli-session-epoch.d.ts +10 -0
  20. package/dist/cli-session-epoch.d.ts.map +1 -0
  21. package/dist/cli-session-epoch.js +61 -0
  22. package/dist/cli-session-epoch.js.map +1 -0
  23. package/dist/commands/init.d.ts.map +1 -1
  24. package/dist/commands/init.js +30 -1
  25. package/dist/commands/init.js.map +1 -1
  26. package/dist/commands/pool.js +1 -1
  27. package/dist/commands/pool.js.map +1 -1
  28. package/dist/commands/setup.d.ts +37 -0
  29. package/dist/commands/setup.d.ts.map +1 -1
  30. package/dist/commands/setup.js +146 -43
  31. package/dist/commands/setup.js.map +1 -1
  32. package/dist/commands/start.d.ts.map +1 -1
  33. package/dist/commands/start.js +194 -164
  34. package/dist/commands/start.js.map +1 -1
  35. package/dist/config-cleanup.d.ts.map +1 -1
  36. package/dist/config-cleanup.js +2 -1
  37. package/dist/config-cleanup.js.map +1 -1
  38. package/dist/config.d.ts +6 -9
  39. package/dist/config.d.ts.map +1 -1
  40. package/dist/config.js +8 -30
  41. package/dist/config.js.map +1 -1
  42. package/dist/context-window.d.ts +33 -5
  43. package/dist/context-window.d.ts.map +1 -1
  44. package/dist/context-window.js +121 -20
  45. package/dist/context-window.js.map +1 -1
  46. package/dist/eval/orchestrator-front-door-replay.js +1 -1
  47. package/dist/eval/orchestrator-front-door-replay.js.map +1 -1
  48. package/dist/eval/policy-detection-replay.js +1 -1
  49. package/dist/eval/policy-detection-replay.js.map +1 -1
  50. package/dist/integration-tokens.d.ts +1 -6
  51. package/dist/integration-tokens.d.ts.map +1 -1
  52. package/dist/integration-tokens.js +38 -40
  53. package/dist/integration-tokens.js.map +1 -1
  54. package/dist/local-cli-pty-manager.d.ts +50 -0
  55. package/dist/local-cli-pty-manager.d.ts.map +1 -0
  56. package/dist/local-cli-pty-manager.js +645 -0
  57. package/dist/local-cli-pty-manager.js.map +1 -0
  58. package/dist/local-data.d.ts +30 -0
  59. package/dist/local-data.d.ts.map +1 -1
  60. package/dist/local-data.js +56 -1
  61. package/dist/local-data.js.map +1 -1
  62. package/dist/local-db.d.ts.map +1 -1
  63. package/dist/local-db.js +54 -1
  64. package/dist/local-db.js.map +1 -1
  65. package/dist/local-funnel.d.ts.map +1 -1
  66. package/dist/local-funnel.js +3 -2
  67. package/dist/local-funnel.js.map +1 -1
  68. package/dist/local-memory-search.d.ts +1 -0
  69. package/dist/local-memory-search.d.ts.map +1 -1
  70. package/dist/local-memory-search.js +101 -18
  71. package/dist/local-memory-search.js.map +1 -1
  72. package/dist/local-server.d.ts +0 -16
  73. package/dist/local-server.d.ts.map +1 -1
  74. package/dist/local-server.js +339 -287
  75. package/dist/local-server.js.map +1 -1
  76. package/dist/mcp/bridge-server.d.ts.map +1 -1
  77. package/dist/mcp/bridge-server.js +2 -1
  78. package/dist/mcp/bridge-server.js.map +1 -1
  79. package/dist/mcp/local-memory-server.d.ts +5 -0
  80. package/dist/mcp/local-memory-server.d.ts.map +1 -1
  81. package/dist/mcp/local-memory-server.js +15 -2
  82. package/dist/mcp/local-memory-server.js.map +1 -1
  83. package/dist/mcp/manager.d.ts +3 -22
  84. package/dist/mcp/manager.d.ts.map +1 -1
  85. package/dist/mcp/manager.js +66 -388
  86. package/dist/mcp/manager.js.map +1 -1
  87. package/dist/memory-extraction.d.ts +2 -0
  88. package/dist/memory-extraction.d.ts.map +1 -1
  89. package/dist/memory-extraction.js +3 -1
  90. package/dist/memory-extraction.js.map +1 -1
  91. package/dist/message-loop.d.ts +9 -6
  92. package/dist/message-loop.d.ts.map +1 -1
  93. package/dist/message-loop.js +217 -538
  94. package/dist/message-loop.js.map +1 -1
  95. package/dist/mqtt-client.d.ts +2 -31
  96. package/dist/mqtt-client.d.ts.map +1 -1
  97. package/dist/mqtt-client.js +2 -2
  98. package/dist/mqtt-client.js.map +1 -1
  99. package/dist/oauth.d.ts +6 -0
  100. package/dist/oauth.d.ts.map +1 -1
  101. package/dist/oauth.js +91 -0
  102. package/dist/oauth.js.map +1 -1
  103. package/dist/orchestration/front-door-policy.d.ts +5 -2
  104. package/dist/orchestration/front-door-policy.d.ts.map +1 -1
  105. package/dist/orchestration/front-door-policy.js +25 -28
  106. package/dist/orchestration/front-door-policy.js.map +1 -1
  107. package/dist/orchestration/orchestrator-blocked-prompt.js +1 -1
  108. package/dist/orchestration/orchestrator-final-response-prompt.js +1 -1
  109. package/dist/orchestration/orchestrator-operating-prompt.d.ts +11 -0
  110. package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
  111. package/dist/orchestration/orchestrator-operating-prompt.js +67 -44
  112. package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
  113. package/dist/orchestration/worker-operating-prompt.js +3 -3
  114. package/dist/orchestration/worker-operating-prompt.js.map +1 -1
  115. package/dist/orchestrator.d.ts +5 -1
  116. package/dist/orchestrator.d.ts.map +1 -1
  117. package/dist/orchestrator.js +141 -81
  118. package/dist/orchestrator.js.map +1 -1
  119. package/dist/prompt-template.js +3 -3
  120. package/dist/prompt-template.js.map +1 -1
  121. package/dist/providers/claude-cli-prompt.d.ts.map +1 -1
  122. package/dist/providers/claude-cli-prompt.js +22 -6
  123. package/dist/providers/claude-cli-prompt.js.map +1 -1
  124. package/dist/providers/claude-cli.d.ts.map +1 -1
  125. package/dist/providers/claude-cli.js +20 -2
  126. package/dist/providers/claude-cli.js.map +1 -1
  127. package/dist/providers/codex-cli.d.ts.map +1 -1
  128. package/dist/providers/codex-cli.js +71 -16
  129. package/dist/providers/codex-cli.js.map +1 -1
  130. package/dist/providers/index.d.ts +11 -0
  131. package/dist/providers/index.d.ts.map +1 -1
  132. package/dist/providers/index.js.map +1 -1
  133. package/dist/runtime-context.d.ts +10 -0
  134. package/dist/runtime-context.d.ts.map +1 -0
  135. package/dist/runtime-context.js +30 -0
  136. package/dist/runtime-context.js.map +1 -0
  137. package/dist/subagent/queue.d.ts.map +1 -1
  138. package/dist/subagent/queue.js +1 -0
  139. package/dist/subagent/queue.js.map +1 -1
  140. package/dist/summarization-pipeline.d.ts +1 -0
  141. package/dist/summarization-pipeline.d.ts.map +1 -1
  142. package/dist/summarization-pipeline.js +94 -25
  143. package/dist/summarization-pipeline.js.map +1 -1
  144. package/dist/tool-permissions.d.ts +2 -0
  145. package/dist/tool-permissions.d.ts.map +1 -0
  146. package/dist/tool-permissions.js +25 -0
  147. package/dist/tool-permissions.js.map +1 -0
  148. package/dist/tools/index.d.ts +7 -8
  149. package/dist/tools/index.d.ts.map +1 -1
  150. package/dist/tools/index.js +70 -60
  151. package/dist/tools/index.js.map +1 -1
  152. package/dist/tools/search-memory.d.ts.map +1 -1
  153. package/dist/tools/search-memory.js +9 -3
  154. package/dist/tools/search-memory.js.map +1 -1
  155. package/dist/tools/spawn-subagent.d.ts.map +1 -1
  156. package/dist/tools/spawn-subagent.js +1 -0
  157. package/dist/tools/spawn-subagent.js.map +1 -1
  158. package/dist/types.d.ts +3 -0
  159. package/dist/types.d.ts.map +1 -1
  160. package/dist/types.js +0 -3
  161. package/dist/types.js.map +1 -1
  162. package/dist/wizard-support.d.ts.map +1 -1
  163. package/dist/wizard-support.js +8 -6
  164. package/dist/wizard-support.js.map +1 -1
  165. package/dist/workflow-engine.d.ts +6 -2
  166. package/dist/workflow-engine.d.ts.map +1 -1
  167. package/dist/workflow-engine.js +254 -77
  168. package/dist/workflow-engine.js.map +1 -1
  169. package/package.json +2 -1
@@ -42,7 +42,6 @@ const data = __importStar(require("./local-data"));
42
42
  const message_loop_1 = require("./message-loop");
43
43
  const auto_detect_1 = require("./auth/auto-detect");
44
44
  const agent_config_1 = require("./agent-config");
45
- const default_tool_profile_1 = require("./default-tool-profile");
46
45
  /** Dedup recent command IDs (MQTT QoS 1 can redeliver) */
47
46
  const DEDUP_WINDOW_MS = 30_000;
48
47
  const DEDUP_MAX_SIZE = 200;
@@ -50,10 +49,6 @@ const DEDUP_MAX_SIZE = 200;
50
49
  * Manages agent MessageLoops, keyed by agent ID.
51
50
  * The active agent runs as "__default__" loop.
52
51
  * Runtime source of truth: config.agents[] + config.activeAgentId.
53
- *
54
- * Auth modes supported:
55
- * 1. CLI providers (claude-cli, codex-cli) — handle their own auth
56
- * 2. API key (BYOK) — standard x-api-key authentication
57
52
  */
58
53
  class BotManager {
59
54
  loops = new Map();
@@ -64,17 +59,13 @@ class BotManager {
64
59
  constructor(options) {
65
60
  this.options = options;
66
61
  }
67
- /** Start the active agent loop */
62
+ /** Start the active agent loop, auto-detecting OAuth credentials if no API key is configured */
68
63
  async startActive() {
69
64
  let provider = this.options.defaultProvider;
70
65
  let model = this.options.defaultModel;
71
66
  let apiKey = this.options.defaultApiKey;
72
67
  let oauthToken = this.options.defaultOauthToken;
73
- let authMode;
74
- let baseUrl;
75
- let apiStyle;
76
- let resolvedAuth;
77
- // Resolve auth — handles API key detection plus OAuth/subscription runtimes
68
+ // Resolve auth — handles token refresh for OAuth, auto-detection if no key/token
78
69
  try {
79
70
  const auth = await (0, auto_detect_1.resolveAuth)({
80
71
  provider,
@@ -86,36 +77,26 @@ class BotManager {
86
77
  });
87
78
  if (auth) {
88
79
  this.resolvedAuth = auth;
89
- resolvedAuth = auth;
90
80
  provider = auth.provider;
91
81
  model = auth.model;
92
- apiKey = auth.source === 'api-key' || auth.source === 'env' ? auth.apiKey : undefined;
93
- oauthToken = auth.source === 'oauth' ? auth.apiKey : oauthToken;
94
- authMode = auth.authMode;
95
- baseUrl = auth.baseUrl;
96
- apiStyle = auth.apiStyle;
82
+ oauthToken = auth.apiKey;
83
+ apiKey = auth.source === 'api-key' ? auth.apiKey : undefined;
97
84
  console.log(chalk_1.default.green(`✓ Auth resolved: provider=${auth.provider}, source=${auth.source}`));
98
85
  }
99
- else if (!apiKey) {
100
- console.log(chalk_1.default.yellow('⚠ No API key found — LLM calls may fail'));
86
+ else if (!apiKey && !oauthToken) {
87
+ console.log(chalk_1.default.yellow('⚠ No API key or OAuth credentials found — LLM calls may fail'));
101
88
  }
102
89
  }
103
90
  catch (err) {
104
91
  console.error(chalk_1.default.red(`✗ Auth resolution failed: ${err}`));
105
92
  }
106
- const effectiveKey = apiKey || oauthToken || '';
107
- const isOAuthBearer = effectiveKey.startsWith('sk-ant-oat');
108
93
  this.startLoop({
109
94
  id: '__default__',
110
95
  name: 'active',
111
96
  provider,
112
97
  model,
113
- apiKey: effectiveKey || undefined,
114
- oauthToken: authMode === 'oauth-bearer' || provider === 'openai' ? effectiveKey : undefined,
115
- authMode: authMode || (isOAuthBearer ? 'oauth-bearer' : undefined),
116
- baseUrl,
117
- apiStyle,
118
- resolvedAuth,
98
+ apiKey,
99
+ oauthToken,
119
100
  });
120
101
  }
121
102
  /**
@@ -140,31 +121,11 @@ class BotManager {
140
121
  const defaultLoop = this.loops.get('__default__');
141
122
  if (defaultLoop) {
142
123
  const defaultOpts = defaultLoop.options;
143
- if (defaultOpts.provider === agentCfg.provider && defaultOpts.model === agentCfg.model) {
124
+ if (defaultOpts.provider === (agentCfg.runtimeProvider || agentCfg.provider) &&
125
+ defaultOpts.model === (agentCfg.runtimeModel || agentCfg.model)) {
144
126
  // Same provider+model as default — just register the botId mapping
145
127
  if (agentCfg.botId) {
146
128
  this.botIdToLoopId.set(agentCfg.botId, '__default__');
147
- // Ensure agent_profile exists in local DB so the clerk prompt builder works.
148
- const profileFields = {
149
- provider: agentCfg.provider,
150
- model: agentCfg.model,
151
- name: agentCfg.name || name,
152
- permissionMode: agentCfg.permissionMode || 'approve-destructive',
153
- enabledBuiltinToolsJson: JSON.stringify((0, default_tool_profile_1.normalizeEnabledTools)(agentCfg.enabledTools)),
154
- enabledMcpToolsJson: JSON.stringify(agentCfg.enabledMcpTools || []),
155
- isActive: true,
156
- };
157
- const existingProfile = data.getAgentProfile(agentCfg.botId);
158
- if (existingProfile) {
159
- data.updateAgentProfile(agentCfg.botId, profileFields);
160
- }
161
- else {
162
- data.createAgentProfile({
163
- ...profileFields,
164
- providerConnectionId: data.findProviderConnection(agentCfg.provider)?.id,
165
- isDefault: false,
166
- });
167
- }
168
129
  }
169
130
  continue;
170
131
  }
@@ -175,30 +136,18 @@ class BotManager {
175
136
  // Resolve auth for this bot
176
137
  let apiKey = agentCfg.apiKey;
177
138
  let oauthToken = agentCfg.oauthToken;
178
- let provider = agentCfg.provider;
179
- let model = agentCfg.model;
180
- let authMode;
181
- let baseUrl;
182
- let apiStyle;
183
- let resolvedAuth;
184
139
  try {
185
140
  const auth = await (0, auto_detect_1.resolveAuth)({
186
- provider,
187
- model,
141
+ provider: agentCfg.runtimeProvider || agentCfg.provider,
142
+ model: agentCfg.runtimeModel || agentCfg.model,
188
143
  apiKey,
189
144
  oauthToken,
190
145
  oauthRefreshToken: agentCfg.oauthRefreshToken,
191
146
  oauthExpiresAt: agentCfg.oauthExpiresAt,
192
147
  });
193
148
  if (auth) {
194
- resolvedAuth = auth;
195
- provider = auth.provider;
196
- model = auth.model;
197
- apiKey = auth.source === 'api-key' || auth.source === 'env' ? auth.apiKey : undefined;
198
- oauthToken = auth.source === 'oauth' ? auth.apiKey : oauthToken;
199
- authMode = auth.authMode;
200
- baseUrl = auth.baseUrl;
201
- apiStyle = auth.apiStyle;
149
+ oauthToken = auth.apiKey;
150
+ apiKey = auth.source === 'api-key' ? auth.apiKey : undefined;
202
151
  }
203
152
  }
204
153
  catch (err) {
@@ -206,28 +155,25 @@ class BotManager {
206
155
  continue;
207
156
  }
208
157
  if (!apiKey && !oauthToken) {
209
- console.warn(chalk_1.default.yellow(`⚠ No credentials for bot "${name}" (${provider}) — skipping`));
158
+ console.warn(chalk_1.default.yellow(`⚠ No credentials for bot "${name}" (${agentCfg.provider}) — skipping`));
210
159
  continue;
211
160
  }
212
161
  this.startLoop({
213
162
  id: loopId,
214
163
  name: agentCfg.name || name,
215
- provider,
216
- model,
217
- apiKey: apiKey || oauthToken,
164
+ provider: agentCfg.runtimeProvider || agentCfg.provider,
165
+ model: agentCfg.runtimeModel || agentCfg.model,
166
+ apiKey,
218
167
  oauthToken,
219
168
  enabledTools: agentCfg.enabledTools,
220
- authMode,
221
- baseUrl,
222
- apiStyle,
223
- resolvedAuth,
169
+ enabledMcpTools: agentCfg.enabledMcpTools,
224
170
  });
225
171
  // Register botId → loopId mapping
226
172
  if (agentCfg.botId) {
227
173
  this.botIdToLoopId.set(agentCfg.botId, loopId);
228
174
  }
229
175
  started++;
230
- console.log(chalk_1.default.green(`✓ Bot "${agentCfg.name || name}" loaded (${agentCfg.provider}/${agentCfg.model})`));
176
+ console.log(chalk_1.default.green(`✓ Bot "${agentCfg.name || name}" loaded (${agentCfg.runtimeProvider || agentCfg.provider}/${agentCfg.runtimeModel || agentCfg.model})`));
231
177
  }
232
178
  if (started > 0) {
233
179
  console.log(chalk_1.default.blue(` ${started + 1} bot(s) active (1 default + ${started} additional)`));
@@ -242,7 +188,6 @@ class BotManager {
242
188
  // Try direct lookup by botId
243
189
  if (this.loops.has(botId))
244
190
  return this.loops.get(botId);
245
- return undefined;
246
191
  }
247
192
  return this.getDefaultLoop();
248
193
  }
@@ -340,25 +285,24 @@ class BotManager {
340
285
  startLoop(agent) {
341
286
  if (this.loops.has(agent.id))
342
287
  return;
343
- const isOAuthBearer = !!agent.oauthToken && agent.oauthToken.startsWith('sk-ant-oat');
344
288
  const loop = new message_loop_1.MessageLoop({
345
289
  provider: agent.provider,
346
290
  model: agent.model,
347
291
  apiKey: agent.apiKey || '',
348
292
  oauthToken: agent.oauthToken,
349
- ...(agent.authMode ? { authMode: agent.authMode } : {}),
350
- ...(agent.baseUrl ? { baseUrl: agent.baseUrl } : {}),
351
- ...(agent.apiStyle ? { apiStyle: agent.apiStyle } : {}),
293
+ resolvedAuth: this.resolvedAuth ? {
294
+ ...this.resolvedAuth,
295
+ credential: this.resolvedAuth.credential ? { ...this.resolvedAuth.credential } : undefined,
296
+ } : undefined,
352
297
  projectDir: this.options.projectDir,
353
298
  userId: this.options.userId,
354
299
  mqttClient: this.options.mqttClient,
355
300
  permissionMode: this.options.permissionMode,
356
- enabledTools: agent.enabledTools || this.options.enabledTools,
301
+ enabledTools: agent.enabledTools !== undefined ? agent.enabledTools : this.options.enabledTools,
302
+ enabledMcpTools: agent.enabledMcpTools !== undefined ? agent.enabledMcpTools : this.options.enabledMcpTools,
357
303
  systemPrompt: this.options.systemPrompt,
358
304
  mcpManager: this.options.mcpManager,
359
305
  agentName: agent.name,
360
- resolvedAuth: agent.resolvedAuth || undefined,
361
- ...(!agent.authMode && isOAuthBearer ? { authMode: 'oauth-bearer' } : {}),
362
306
  });
363
307
  this.loops.set(agent.id, loop);
364
308
  }
@@ -371,260 +315,22 @@ class BotManager {
371
315
  getDefaultLoop() {
372
316
  return this.loops.get('__default__');
373
317
  }
374
- async fetchServerConfig() {
375
- const { loadConfig, FUNOLIO_API_URL } = require('./config');
376
- const cfg = loadConfig();
377
- const authToken = cfg.auth?.token;
378
- if (!authToken) {
379
- console.warn(chalk_1.default.yellow(' [bot-manager] No auth token — cannot fetch config from server'));
380
- return null;
381
- }
382
- try {
383
- const res = await fetch(`${FUNOLIO_API_URL}/api/v1/agent/config`, {
384
- headers: { Authorization: `Bearer ${authToken}` },
385
- });
386
- if (!res.ok)
387
- return null;
388
- return await res.json();
389
- }
390
- catch (err) {
391
- console.warn(chalk_1.default.yellow(` [bot-manager] Server config fetch failed: ${err?.message}`));
392
- return null;
393
- }
394
- }
395
- /**
396
- * Re-fetch full config from server, returning the bot and its resolved credential.
397
- * This is the single source of truth for bot credentials — no secrets over MQTT.
398
- * Handles both API key and OAuth credential types.
399
- */
400
- async fetchBotFromServer(botId) {
401
- const body = await this.fetchServerConfig();
402
- if (!body)
403
- return null;
404
- return this.resolveBotFromConfig(botId, body);
405
- }
406
- /** Extract a single bot's credentials from a server config response */
407
- resolveBotFromConfig(botId, body) {
408
- const bot = (body.bots || []).find((b) => b.id === botId);
409
- if (!bot?.llmProvider)
410
- return null;
411
- // Find the credential for this bot — match by provider id AND credential type
412
- // Multiple providers can share the same id (e.g., two "openai" entries: one oauth, one apiKey)
413
- const providers = body.providers || [];
414
- const botRole = bot.role || bot.credentialSource; // "oauth", "apikey", "subscription"
415
- let credential;
416
- if (botRole === 'oauth') {
417
- // OAuth bots → match provider with connectionType "oauth"
418
- credential = providers.find((p) => p.id === bot.llmProvider && p.connectionType === 'oauth');
419
- }
420
- else if (botRole === 'apikey') {
421
- // API key bots → prefer bot-specific key (label starts with "bot:"), then any apiKey type
422
- credential = providers.find((p) => p.label?.startsWith('bot:') && p.id === bot.llmProvider && p.connectionType === 'apiKey')
423
- || providers.find((p) => p.id === bot.llmProvider && p.connectionType === 'apiKey');
424
- }
425
- else if (botRole === 'subscription') {
426
- // Subscription bots → match provider with connectionType "subscription"
427
- credential = providers.find((p) => p.id === bot.llmProvider && p.connectionType === 'subscription');
428
- }
429
- // Fallback: any provider matching the bot's provider id
430
- if (!credential) {
431
- credential = providers.find((p) => p.id === bot.llmProvider);
432
- }
433
- if (!credential)
434
- return null;
435
- // Handle OAuth credentials (access_token/refresh_token) vs plain API keys
436
- if (credential.connectionType === 'oauth' || credential.access_token) {
437
- return {
438
- provider: bot.llmProvider,
439
- model: bot.llmModel || '',
440
- name: bot.name,
441
- oauthToken: credential.access_token,
442
- oauthRefreshToken: credential.refresh_token,
443
- };
444
- }
445
- return {
446
- provider: bot.llmProvider,
447
- model: bot.llmModel || '',
448
- name: bot.name,
449
- apiKey: credential.apiKey,
450
- };
451
- }
452
- /**
453
- * Fix B: Sync all cloud-configured bots at startup.
454
- * Calls /api/v1/agent/config and starts loops for bots that aren't already running locally.
455
- * This ensures bots created on the web UI are available without manual local config.
456
- */
457
- async syncBotsFromCloud() {
458
- console.log(chalk_1.default.gray(' Syncing bot configs from cloud...'));
459
- const body = await this.fetchServerConfig();
460
- if (!body) {
461
- console.log(chalk_1.default.gray(' Cloud sync skipped — no server config available'));
462
- return;
463
- }
464
- const bots = body.bots || [];
465
- let synced = 0;
466
- for (const bot of bots) {
467
- if (!bot.id || !bot.llmProvider)
468
- continue;
469
- // Skip if this bot already has a loop running (loaded from local config)
470
- if (this.botIdToLoopId.has(bot.id) || this.loops.has(bot.id)) {
471
- continue;
472
- }
473
- // Resolve credentials from the server config
474
- const resolved = this.resolveBotFromConfig(bot.id, body);
475
- if (!resolved) {
476
- console.warn(chalk_1.default.yellow(` ⚠ Cloud bot "${bot.name}" (${bot.llmProvider}) — no credentials found, skipping`));
477
- continue;
478
- }
479
- // Resolve auth (handles OAuth token detection, refresh, baseUrl/apiStyle for OpenAI sub)
480
- let provider = resolved.provider;
481
- let model = resolved.model;
482
- let apiKey = resolved.apiKey;
483
- let oauthToken = resolved.oauthToken;
484
- let authMode;
485
- let baseUrl;
486
- let apiStyle;
487
- let resolvedAuth;
488
- try {
489
- const auth = await (0, auto_detect_1.resolveAuth)({
490
- provider,
491
- model,
492
- apiKey,
493
- oauthToken,
494
- oauthRefreshToken: resolved.oauthRefreshToken,
495
- });
496
- if (auth) {
497
- resolvedAuth = auth;
498
- provider = auth.provider;
499
- model = auth.model;
500
- apiKey = auth.source === 'api-key' || auth.source === 'env' ? auth.apiKey : undefined;
501
- oauthToken = auth.source === 'oauth' ? auth.apiKey : oauthToken;
502
- authMode = auth.authMode;
503
- baseUrl = auth.baseUrl;
504
- apiStyle = auth.apiStyle;
505
- }
506
- }
507
- catch (err) {
508
- console.warn(chalk_1.default.yellow(` ⚠ Auth resolution failed for cloud bot "${bot.name}": ${err} — skipping`));
509
- continue;
510
- }
511
- if (!apiKey && !oauthToken) {
512
- console.warn(chalk_1.default.yellow(` ⚠ No usable credentials for cloud bot "${bot.name}" (${provider}) — skipping`));
513
- continue;
514
- }
515
- const effectiveKey = apiKey || oauthToken || '';
516
- // Start the loop
517
- this.startLoop({
518
- id: bot.id,
519
- name: bot.name,
520
- provider,
521
- model,
522
- apiKey: effectiveKey,
523
- oauthToken,
524
- authMode,
525
- baseUrl,
526
- apiStyle,
527
- resolvedAuth,
528
- });
529
- this.botIdToLoopId.set(bot.id, bot.id);
530
- // Persist agent profile locally for prompt building
531
- const profileFields = {
532
- provider,
533
- model,
534
- name: bot.name,
535
- permissionMode: default_tool_profile_1.DEFAULT_PERMISSION_MODE,
536
- enabledBuiltinToolsJson: JSON.stringify([]),
537
- enabledMcpToolsJson: JSON.stringify([]),
538
- isActive: true,
539
- };
540
- const existing = data.getAgentProfile(bot.id);
541
- if (existing) {
542
- data.updateAgentProfile(bot.id, profileFields);
543
- }
544
- else {
545
- data.createAgentProfile({
546
- ...profileFields,
547
- providerConnectionId: data.findProviderConnection(provider)?.id,
548
- isDefault: false,
549
- });
550
- }
551
- synced++;
552
- console.log(chalk_1.default.green(` ✓ Cloud bot "${bot.name}" synced (${provider}/${model})`));
553
- }
554
- if (synced > 0) {
555
- console.log(chalk_1.default.blue(` ${synced} bot(s) synced from cloud`));
556
- }
557
- else {
558
- console.log(chalk_1.default.gray(' No additional cloud bots to sync'));
559
- }
560
- }
561
- /** Add a new agent — re-fetches config from server to get credentials */
562
- async handleAgentAdd(command) {
318
+ /** Add a new agent — persists to config.agents[] */
319
+ handleAgentAdd(command) {
563
320
  if (!command.bot) {
564
321
  console.error(chalk_1.default.red('agent_add command missing agent payload'));
565
322
  return;
566
323
  }
567
324
  const agent = command.bot;
568
- let provider = agent.provider;
569
- let model = agent.model;
570
- // Fail closed: if provider is missing, do not fall through to default
571
- if (!provider) {
572
- console.error(chalk_1.default.red(`✗ Agent "${agent.name}" has no provider binding — cannot start. Fix in Mission Control.`));
573
- return;
574
- }
575
- // Re-fetch credentials from server (secrets stay on authenticated HTTPS, not MQTT)
576
- const serverCreds = await this.fetchBotFromServer(agent.id);
577
- let apiKey;
578
- let oauthToken;
579
- let oauthRefreshToken;
580
- if (serverCreds) {
581
- apiKey = serverCreds.apiKey;
582
- oauthToken = serverCreds.oauthToken;
583
- oauthRefreshToken = serverCreds.oauthRefreshToken;
584
- }
585
- else {
586
- // Fallback: try local provider config
587
- const { loadConfig, getProvider } = require('./config');
588
- const localProvider = getProvider(loadConfig(), provider);
589
- if (localProvider) {
590
- apiKey = localProvider.apiKey;
591
- }
592
- }
593
- if (!apiKey && !oauthToken) {
594
- console.error(chalk_1.default.red(`✗ Agent "${agent.name}" (${provider}/${model}) has no credential binding — cannot start.`));
595
- return;
596
- }
597
- // Resolve auth (handles OAuth → baseUrl/apiStyle for OpenAI subscription, token refresh, etc.)
598
- let authMode;
599
- let baseUrl;
600
- let apiStyle;
601
- let resolvedAuth;
602
- try {
603
- const auth = await (0, auto_detect_1.resolveAuth)({ provider, model, apiKey, oauthToken, oauthRefreshToken });
604
- if (auth) {
605
- resolvedAuth = auth;
606
- provider = auth.provider;
607
- model = auth.model;
608
- apiKey = auth.source === 'api-key' || auth.source === 'env' ? auth.apiKey : undefined;
609
- oauthToken = auth.source === 'oauth' ? auth.apiKey : oauthToken;
610
- authMode = auth.authMode;
611
- baseUrl = auth.baseUrl;
612
- apiStyle = auth.apiStyle;
613
- }
614
- }
615
- catch (err) {
616
- console.warn(chalk_1.default.yellow(` ⚠ Auth resolution failed for "${agent.name}": ${err}`));
617
- }
618
- const effectiveKey = apiKey || oauthToken || '';
619
325
  const existing = data.getAgentProfile(agent.id);
620
326
  const fields = {
621
- provider,
622
- model,
327
+ provider: agent.provider,
328
+ model: agent.model,
623
329
  name: agent.name,
624
- permissionMode: agent.permissionMode || default_tool_profile_1.DEFAULT_PERMISSION_MODE,
330
+ permissionMode: agent.permissionMode || 'autopilot',
625
331
  finalPrompt: agent.systemPrompt || undefined,
626
332
  purposeMd: agent.agentDescription || undefined,
627
- enabledBuiltinToolsJson: JSON.stringify((0, default_tool_profile_1.normalizeEnabledTools)(agent.enabledTools)),
333
+ enabledBuiltinToolsJson: JSON.stringify(agent.enabledTools || []),
628
334
  enabledMcpToolsJson: JSON.stringify(agent.enabledMcpTools || []),
629
335
  isActive: true,
630
336
  };
@@ -634,15 +340,16 @@ class BotManager {
634
340
  else {
635
341
  data.createAgentProfile({
636
342
  ...fields,
637
- providerConnectionId: data.findProviderConnection(provider)?.id,
343
+ providerConnectionId: data.findProviderConnection(agent.provider)?.id,
638
344
  isDefault: false,
639
345
  });
640
346
  }
641
- this.startLoop({ ...agent, provider, model, apiKey: effectiveKey, oauthToken, authMode, baseUrl, apiStyle, resolvedAuth });
347
+ this.startLoop(agent);
348
+ // Register botId → loopId mapping
642
349
  if (agent.id) {
643
350
  this.botIdToLoopId.set(agent.id, agent.id);
644
351
  }
645
- console.log(chalk_1.default.green(`✓ Agent added: "${agent.name}" (${provider} / ${model})`));
352
+ console.log(chalk_1.default.green(`✓ Agent added: "${agent.name}" (${agent.provider} / ${agent.model})`));
646
353
  }
647
354
  /** Remove an agent — persists to config.agents[] */
648
355
  handleAgentRemove(command) {
@@ -661,72 +368,22 @@ class BotManager {
661
368
  console.log(chalk_1.default.gray(` Agent "${agentId}" was not running`));
662
369
  }
663
370
  }
664
- /** Update an agent — re-fetches config from server to get credentials */
665
- async handleAgentUpdate(command) {
371
+ /** Update an agent — persists to config.agents[] */
372
+ handleAgentUpdate(command) {
666
373
  if (!command.bot) {
667
374
  console.error(chalk_1.default.red('agent_update command missing agent payload'));
668
375
  return;
669
376
  }
670
377
  const agent = command.bot;
671
- let provider = agent.provider;
672
- let model = agent.model;
673
378
  this.stopLoop(agent.id);
674
- // Fail closed: if provider is missing, do not fall through to default
675
- if (!provider) {
676
- console.error(chalk_1.default.red(`✗ Agent "${agent.name}" has no provider binding — cannot update. Fix in Mission Control.`));
677
- return;
678
- }
679
- // Re-fetch credentials from server
680
- const serverCreds = await this.fetchBotFromServer(agent.id);
681
- let apiKey;
682
- let oauthToken;
683
- let oauthRefreshToken;
684
- if (serverCreds) {
685
- apiKey = serverCreds.apiKey;
686
- oauthToken = serverCreds.oauthToken;
687
- oauthRefreshToken = serverCreds.oauthRefreshToken;
688
- }
689
- else {
690
- const { loadConfig, getProvider } = require('./config');
691
- const localProvider = getProvider(loadConfig(), provider);
692
- if (localProvider) {
693
- apiKey = localProvider.apiKey;
694
- }
695
- }
696
- if (!apiKey && !oauthToken) {
697
- console.error(chalk_1.default.red(`✗ Agent "${agent.name}" (${provider}/${model}) has no credential binding — cannot update.`));
698
- return;
699
- }
700
- // Resolve auth (handles OAuth → baseUrl/apiStyle for OpenAI subscription, token refresh, etc.)
701
- let authMode;
702
- let baseUrl;
703
- let apiStyle;
704
- let resolvedAuth;
705
- try {
706
- const auth = await (0, auto_detect_1.resolveAuth)({ provider, model, apiKey, oauthToken, oauthRefreshToken });
707
- if (auth) {
708
- resolvedAuth = auth;
709
- provider = auth.provider;
710
- model = auth.model;
711
- apiKey = auth.source === 'api-key' || auth.source === 'env' ? auth.apiKey : undefined;
712
- oauthToken = auth.source === 'oauth' ? auth.apiKey : oauthToken;
713
- authMode = auth.authMode;
714
- baseUrl = auth.baseUrl;
715
- apiStyle = auth.apiStyle;
716
- }
717
- }
718
- catch (err) {
719
- console.warn(chalk_1.default.yellow(` ⚠ Auth resolution failed for "${agent.name}": ${err}`));
720
- }
721
- const effectiveKey = apiKey || oauthToken || '';
722
379
  const fields = {
723
- provider,
724
- model,
380
+ provider: agent.provider,
381
+ model: agent.model,
725
382
  name: agent.name,
726
- permissionMode: agent.permissionMode || default_tool_profile_1.DEFAULT_PERMISSION_MODE,
383
+ permissionMode: agent.permissionMode || 'autopilot',
727
384
  finalPrompt: agent.systemPrompt || undefined,
728
385
  purposeMd: agent.agentDescription || undefined,
729
- enabledBuiltinToolsJson: JSON.stringify((0, default_tool_profile_1.normalizeEnabledTools)(agent.enabledTools)),
386
+ enabledBuiltinToolsJson: JSON.stringify(agent.enabledTools || []),
730
387
  enabledMcpToolsJson: JSON.stringify(agent.enabledMcpTools || []),
731
388
  isActive: true,
732
389
  };
@@ -736,15 +393,22 @@ class BotManager {
736
393
  else {
737
394
  data.createAgentProfile({
738
395
  ...fields,
739
- providerConnectionId: data.findProviderConnection(provider)?.id,
396
+ providerConnectionId: data.findProviderConnection(agent.provider)?.id,
740
397
  isDefault: false,
741
398
  });
742
399
  }
743
- this.startLoop({ ...agent, provider, model, apiKey: effectiveKey, oauthToken, authMode, baseUrl, apiStyle, resolvedAuth });
400
+ this.startLoop(agent);
744
401
  if (agent.id) {
745
402
  this.botIdToLoopId.set(agent.id, agent.id);
746
403
  }
747
- console.log(chalk_1.default.blue(`✓ Agent updated: "${agent.name}" (${provider} / ${agent.model})`));
404
+ console.log(chalk_1.default.blue(`✓ Agent updated: "${agent.name}" (${agent.provider} / ${agent.model})`));
405
+ }
406
+ /** Update OAuth token for all running loops (used by token refresh) */
407
+ updateDefaultToken(token) {
408
+ this.options.defaultOauthToken = token;
409
+ for (const [_id, loop] of this.loops) {
410
+ loop.updateToken(token);
411
+ }
748
412
  }
749
413
  async handleUpdateCommand(command) {
750
414
  console.log(chalk_1.default.cyan('⟳ Remote update requested via MQTT'));