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
@@ -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,21 +59,18 @@ 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,
81
72
  model,
73
+ accessMode: this.options.defaultAccessMode,
82
74
  apiKey,
83
75
  oauthToken,
84
76
  oauthRefreshToken: this.options.defaultOauthRefreshToken,
@@ -86,36 +78,28 @@ class BotManager {
86
78
  });
87
79
  if (auth) {
88
80
  this.resolvedAuth = auth;
89
- resolvedAuth = auth;
90
81
  provider = auth.provider;
91
82
  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;
83
+ oauthToken = auth.apiKey;
84
+ apiKey = auth.source === 'api-key' ? auth.apiKey : undefined;
97
85
  console.log(chalk_1.default.green(`✓ Auth resolved: provider=${auth.provider}, source=${auth.source}`));
98
86
  }
99
- else if (!apiKey) {
100
- console.log(chalk_1.default.yellow('⚠ No API key found — LLM calls may fail'));
87
+ else if (!apiKey && !oauthToken) {
88
+ console.log(chalk_1.default.yellow('⚠ No API key or OAuth credentials found — LLM calls may fail'));
101
89
  }
102
90
  }
103
91
  catch (err) {
104
92
  console.error(chalk_1.default.red(`✗ Auth resolution failed: ${err}`));
105
93
  }
106
- const effectiveKey = apiKey || oauthToken || '';
107
- const isOAuthBearer = effectiveKey.startsWith('sk-ant-oat');
108
94
  this.startLoop({
109
95
  id: '__default__',
110
96
  name: 'active',
111
97
  provider,
112
98
  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,
99
+ accessMode: this.options.defaultAccessMode,
100
+ apiKey,
101
+ oauthToken,
102
+ resolvedAuth: this.resolvedAuth || undefined,
119
103
  });
120
104
  }
121
105
  /**
@@ -140,31 +124,11 @@ class BotManager {
140
124
  const defaultLoop = this.loops.get('__default__');
141
125
  if (defaultLoop) {
142
126
  const defaultOpts = defaultLoop.options;
143
- if (defaultOpts.provider === agentCfg.provider && defaultOpts.model === agentCfg.model) {
127
+ if (defaultOpts.provider === (agentCfg.runtimeProvider || agentCfg.provider) &&
128
+ defaultOpts.model === (agentCfg.runtimeModel || agentCfg.model)) {
144
129
  // Same provider+model as default — just register the botId mapping
145
130
  if (agentCfg.botId) {
146
131
  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
132
  }
169
133
  continue;
170
134
  }
@@ -175,30 +139,21 @@ class BotManager {
175
139
  // Resolve auth for this bot
176
140
  let apiKey = agentCfg.apiKey;
177
141
  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;
142
+ let loopResolvedAuth = null;
184
143
  try {
185
144
  const auth = await (0, auto_detect_1.resolveAuth)({
186
- provider,
187
- model,
145
+ provider: agentCfg.runtimeProvider || agentCfg.provider,
146
+ model: agentCfg.runtimeModel || agentCfg.model,
147
+ accessMode: agentCfg.accessMode,
188
148
  apiKey,
189
149
  oauthToken,
190
150
  oauthRefreshToken: agentCfg.oauthRefreshToken,
191
151
  oauthExpiresAt: agentCfg.oauthExpiresAt,
192
152
  });
193
153
  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;
154
+ loopResolvedAuth = auth;
155
+ oauthToken = auth.apiKey;
156
+ apiKey = auth.source === 'api-key' ? auth.apiKey : undefined;
202
157
  }
203
158
  }
204
159
  catch (err) {
@@ -206,28 +161,27 @@ class BotManager {
206
161
  continue;
207
162
  }
208
163
  if (!apiKey && !oauthToken) {
209
- console.warn(chalk_1.default.yellow(`⚠ No credentials for bot "${name}" (${provider}) — skipping`));
164
+ console.warn(chalk_1.default.yellow(`⚠ No credentials for bot "${name}" (${agentCfg.provider}) — skipping`));
210
165
  continue;
211
166
  }
212
167
  this.startLoop({
213
168
  id: loopId,
214
169
  name: agentCfg.name || name,
215
- provider,
216
- model,
217
- apiKey: apiKey || oauthToken,
170
+ provider: agentCfg.runtimeProvider || agentCfg.provider,
171
+ model: agentCfg.runtimeModel || agentCfg.model,
172
+ accessMode: agentCfg.accessMode,
173
+ apiKey,
218
174
  oauthToken,
175
+ resolvedAuth: loopResolvedAuth || undefined,
219
176
  enabledTools: agentCfg.enabledTools,
220
- authMode,
221
- baseUrl,
222
- apiStyle,
223
- resolvedAuth,
177
+ enabledMcpTools: agentCfg.enabledMcpTools,
224
178
  });
225
179
  // Register botId → loopId mapping
226
180
  if (agentCfg.botId) {
227
181
  this.botIdToLoopId.set(agentCfg.botId, loopId);
228
182
  }
229
183
  started++;
230
- console.log(chalk_1.default.green(`✓ Bot "${agentCfg.name || name}" loaded (${agentCfg.provider}/${agentCfg.model})`));
184
+ console.log(chalk_1.default.green(`✓ Bot "${agentCfg.name || name}" loaded (${agentCfg.runtimeProvider || agentCfg.provider}/${agentCfg.runtimeModel || agentCfg.model})`));
231
185
  }
232
186
  if (started > 0) {
233
187
  console.log(chalk_1.default.blue(` ${started + 1} bot(s) active (1 default + ${started} additional)`));
@@ -242,7 +196,6 @@ class BotManager {
242
196
  // Try direct lookup by botId
243
197
  if (this.loops.has(botId))
244
198
  return this.loops.get(botId);
245
- return undefined;
246
199
  }
247
200
  return this.getDefaultLoop();
248
201
  }
@@ -340,25 +293,25 @@ class BotManager {
340
293
  startLoop(agent) {
341
294
  if (this.loops.has(agent.id))
342
295
  return;
343
- const isOAuthBearer = !!agent.oauthToken && agent.oauthToken.startsWith('sk-ant-oat');
344
296
  const loop = new message_loop_1.MessageLoop({
345
297
  provider: agent.provider,
346
298
  model: agent.model,
347
299
  apiKey: agent.apiKey || '',
348
300
  oauthToken: agent.oauthToken,
349
- ...(agent.authMode ? { authMode: agent.authMode } : {}),
350
- ...(agent.baseUrl ? { baseUrl: agent.baseUrl } : {}),
351
- ...(agent.apiStyle ? { apiStyle: agent.apiStyle } : {}),
301
+ accessMode: agent.accessMode,
302
+ resolvedAuth: agent.resolvedAuth ? {
303
+ ...agent.resolvedAuth,
304
+ credential: agent.resolvedAuth.credential ? { ...agent.resolvedAuth.credential } : undefined,
305
+ } : undefined,
352
306
  projectDir: this.options.projectDir,
353
307
  userId: this.options.userId,
354
308
  mqttClient: this.options.mqttClient,
355
309
  permissionMode: this.options.permissionMode,
356
- enabledTools: agent.enabledTools || this.options.enabledTools,
310
+ enabledTools: agent.enabledTools !== undefined ? agent.enabledTools : this.options.enabledTools,
311
+ enabledMcpTools: agent.enabledMcpTools !== undefined ? agent.enabledMcpTools : this.options.enabledMcpTools,
357
312
  systemPrompt: this.options.systemPrompt,
358
313
  mcpManager: this.options.mcpManager,
359
314
  agentName: agent.name,
360
- resolvedAuth: agent.resolvedAuth || undefined,
361
- ...(!agent.authMode && isOAuthBearer ? { authMode: 'oauth-bearer' } : {}),
362
315
  });
363
316
  this.loops.set(agent.id, loop);
364
317
  }
@@ -371,260 +324,22 @@ class BotManager {
371
324
  getDefaultLoop() {
372
325
  return this.loops.get('__default__');
373
326
  }
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) {
327
+ /** Add a new agent — persists to config.agents[] */
328
+ handleAgentAdd(command) {
563
329
  if (!command.bot) {
564
330
  console.error(chalk_1.default.red('agent_add command missing agent payload'));
565
331
  return;
566
332
  }
567
333
  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
334
  const existing = data.getAgentProfile(agent.id);
620
335
  const fields = {
621
- provider,
622
- model,
336
+ provider: agent.provider,
337
+ model: agent.model,
623
338
  name: agent.name,
624
- permissionMode: agent.permissionMode || default_tool_profile_1.DEFAULT_PERMISSION_MODE,
339
+ permissionMode: agent.permissionMode || 'autopilot',
625
340
  finalPrompt: agent.systemPrompt || undefined,
626
341
  purposeMd: agent.agentDescription || undefined,
627
- enabledBuiltinToolsJson: JSON.stringify((0, default_tool_profile_1.normalizeEnabledTools)(agent.enabledTools)),
342
+ enabledBuiltinToolsJson: JSON.stringify(agent.enabledTools || []),
628
343
  enabledMcpToolsJson: JSON.stringify(agent.enabledMcpTools || []),
629
344
  isActive: true,
630
345
  };
@@ -634,15 +349,16 @@ class BotManager {
634
349
  else {
635
350
  data.createAgentProfile({
636
351
  ...fields,
637
- providerConnectionId: data.findProviderConnection(provider)?.id,
352
+ providerConnectionId: data.findProviderConnection(agent.provider)?.id,
638
353
  isDefault: false,
639
354
  });
640
355
  }
641
- this.startLoop({ ...agent, provider, model, apiKey: effectiveKey, oauthToken, authMode, baseUrl, apiStyle, resolvedAuth });
356
+ this.startLoop(agent);
357
+ // Register botId → loopId mapping
642
358
  if (agent.id) {
643
359
  this.botIdToLoopId.set(agent.id, agent.id);
644
360
  }
645
- console.log(chalk_1.default.green(`✓ Agent added: "${agent.name}" (${provider} / ${model})`));
361
+ console.log(chalk_1.default.green(`✓ Agent added: "${agent.name}" (${agent.provider} / ${agent.model})`));
646
362
  }
647
363
  /** Remove an agent — persists to config.agents[] */
648
364
  handleAgentRemove(command) {
@@ -661,72 +377,22 @@ class BotManager {
661
377
  console.log(chalk_1.default.gray(` Agent "${agentId}" was not running`));
662
378
  }
663
379
  }
664
- /** Update an agent — re-fetches config from server to get credentials */
665
- async handleAgentUpdate(command) {
380
+ /** Update an agent — persists to config.agents[] */
381
+ handleAgentUpdate(command) {
666
382
  if (!command.bot) {
667
383
  console.error(chalk_1.default.red('agent_update command missing agent payload'));
668
384
  return;
669
385
  }
670
386
  const agent = command.bot;
671
- let provider = agent.provider;
672
- let model = agent.model;
673
387
  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
388
  const fields = {
723
- provider,
724
- model,
389
+ provider: agent.provider,
390
+ model: agent.model,
725
391
  name: agent.name,
726
- permissionMode: agent.permissionMode || default_tool_profile_1.DEFAULT_PERMISSION_MODE,
392
+ permissionMode: agent.permissionMode || 'autopilot',
727
393
  finalPrompt: agent.systemPrompt || undefined,
728
394
  purposeMd: agent.agentDescription || undefined,
729
- enabledBuiltinToolsJson: JSON.stringify((0, default_tool_profile_1.normalizeEnabledTools)(agent.enabledTools)),
395
+ enabledBuiltinToolsJson: JSON.stringify(agent.enabledTools || []),
730
396
  enabledMcpToolsJson: JSON.stringify(agent.enabledMcpTools || []),
731
397
  isActive: true,
732
398
  };
@@ -736,15 +402,22 @@ class BotManager {
736
402
  else {
737
403
  data.createAgentProfile({
738
404
  ...fields,
739
- providerConnectionId: data.findProviderConnection(provider)?.id,
405
+ providerConnectionId: data.findProviderConnection(agent.provider)?.id,
740
406
  isDefault: false,
741
407
  });
742
408
  }
743
- this.startLoop({ ...agent, provider, model, apiKey: effectiveKey, oauthToken, authMode, baseUrl, apiStyle, resolvedAuth });
409
+ this.startLoop(agent);
744
410
  if (agent.id) {
745
411
  this.botIdToLoopId.set(agent.id, agent.id);
746
412
  }
747
- console.log(chalk_1.default.blue(`✓ Agent updated: "${agent.name}" (${provider} / ${agent.model})`));
413
+ console.log(chalk_1.default.blue(`✓ Agent updated: "${agent.name}" (${agent.provider} / ${agent.model})`));
414
+ }
415
+ /** Update OAuth token for all running loops (used by token refresh) */
416
+ updateDefaultToken(token) {
417
+ this.options.defaultOauthToken = token;
418
+ for (const [_id, loop] of this.loops) {
419
+ loop.updateToken(token);
420
+ }
748
421
  }
749
422
  async handleUpdateCommand(command) {
750
423
  console.log(chalk_1.default.cyan('⟳ Remote update requested via MQTT'));