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.
- package/dist/agent-config.d.ts +9 -1
- package/dist/agent-config.d.ts.map +1 -1
- package/dist/agent-config.js +4 -1
- package/dist/agent-config.js.map +1 -1
- package/dist/auto-organizer.d.ts.map +1 -1
- package/dist/auto-organizer.js +4 -3
- package/dist/auto-organizer.js.map +1 -1
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +3 -2
- package/dist/backfill.js.map +1 -1
- package/dist/bot-manager.d.ts +7 -23
- package/dist/bot-manager.d.ts.map +1 -1
- package/dist/bot-manager.js +52 -388
- package/dist/bot-manager.js.map +1 -1
- package/dist/clerk-model.d.ts +5 -1
- package/dist/clerk-model.d.ts.map +1 -1
- package/dist/clerk-model.js +40 -28
- package/dist/clerk-model.js.map +1 -1
- package/dist/cli-session-epoch.d.ts +10 -0
- package/dist/cli-session-epoch.d.ts.map +1 -0
- package/dist/cli-session-epoch.js +61 -0
- package/dist/cli-session-epoch.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +30 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pool.js +1 -1
- package/dist/commands/pool.js.map +1 -1
- package/dist/commands/setup.d.ts +37 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +146 -43
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +194 -164
- package/dist/commands/start.js.map +1 -1
- package/dist/config-cleanup.d.ts.map +1 -1
- package/dist/config-cleanup.js +2 -1
- package/dist/config-cleanup.js.map +1 -1
- package/dist/config.d.ts +6 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -30
- package/dist/config.js.map +1 -1
- package/dist/context-window.d.ts +33 -5
- package/dist/context-window.d.ts.map +1 -1
- package/dist/context-window.js +121 -20
- package/dist/context-window.js.map +1 -1
- package/dist/eval/orchestrator-front-door-replay.js +1 -1
- package/dist/eval/orchestrator-front-door-replay.js.map +1 -1
- package/dist/eval/policy-detection-replay.js +1 -1
- package/dist/eval/policy-detection-replay.js.map +1 -1
- package/dist/integration-tokens.d.ts +1 -6
- package/dist/integration-tokens.d.ts.map +1 -1
- package/dist/integration-tokens.js +38 -40
- package/dist/integration-tokens.js.map +1 -1
- package/dist/local-cli-pty-manager.d.ts +50 -0
- package/dist/local-cli-pty-manager.d.ts.map +1 -0
- package/dist/local-cli-pty-manager.js +645 -0
- package/dist/local-cli-pty-manager.js.map +1 -0
- package/dist/local-data.d.ts +30 -0
- package/dist/local-data.d.ts.map +1 -1
- package/dist/local-data.js +56 -1
- package/dist/local-data.js.map +1 -1
- package/dist/local-db.d.ts.map +1 -1
- package/dist/local-db.js +54 -1
- package/dist/local-db.js.map +1 -1
- package/dist/local-funnel.d.ts.map +1 -1
- package/dist/local-funnel.js +3 -2
- package/dist/local-funnel.js.map +1 -1
- package/dist/local-memory-search.d.ts +1 -0
- package/dist/local-memory-search.d.ts.map +1 -1
- package/dist/local-memory-search.js +101 -18
- package/dist/local-memory-search.js.map +1 -1
- package/dist/local-server.d.ts +0 -16
- package/dist/local-server.d.ts.map +1 -1
- package/dist/local-server.js +339 -287
- package/dist/local-server.js.map +1 -1
- package/dist/mcp/bridge-server.d.ts.map +1 -1
- package/dist/mcp/bridge-server.js +2 -1
- package/dist/mcp/bridge-server.js.map +1 -1
- package/dist/mcp/local-memory-server.d.ts +5 -0
- package/dist/mcp/local-memory-server.d.ts.map +1 -1
- package/dist/mcp/local-memory-server.js +15 -2
- package/dist/mcp/local-memory-server.js.map +1 -1
- package/dist/mcp/manager.d.ts +3 -22
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +66 -388
- package/dist/mcp/manager.js.map +1 -1
- package/dist/memory-extraction.d.ts +2 -0
- package/dist/memory-extraction.d.ts.map +1 -1
- package/dist/memory-extraction.js +3 -1
- package/dist/memory-extraction.js.map +1 -1
- package/dist/message-loop.d.ts +9 -6
- package/dist/message-loop.d.ts.map +1 -1
- package/dist/message-loop.js +217 -538
- package/dist/message-loop.js.map +1 -1
- package/dist/mqtt-client.d.ts +2 -31
- package/dist/mqtt-client.d.ts.map +1 -1
- package/dist/mqtt-client.js +2 -2
- package/dist/mqtt-client.js.map +1 -1
- package/dist/oauth.d.ts +6 -0
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +91 -0
- package/dist/oauth.js.map +1 -1
- package/dist/orchestration/front-door-policy.d.ts +5 -2
- package/dist/orchestration/front-door-policy.d.ts.map +1 -1
- package/dist/orchestration/front-door-policy.js +25 -28
- package/dist/orchestration/front-door-policy.js.map +1 -1
- package/dist/orchestration/orchestrator-blocked-prompt.js +1 -1
- package/dist/orchestration/orchestrator-final-response-prompt.js +1 -1
- package/dist/orchestration/orchestrator-operating-prompt.d.ts +11 -0
- package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-operating-prompt.js +67 -44
- package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
- package/dist/orchestration/worker-operating-prompt.js +3 -3
- package/dist/orchestration/worker-operating-prompt.js.map +1 -1
- package/dist/orchestrator.d.ts +5 -1
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +141 -81
- package/dist/orchestrator.js.map +1 -1
- package/dist/prompt-template.js +3 -3
- package/dist/prompt-template.js.map +1 -1
- package/dist/providers/claude-cli-prompt.d.ts.map +1 -1
- package/dist/providers/claude-cli-prompt.js +22 -6
- package/dist/providers/claude-cli-prompt.js.map +1 -1
- package/dist/providers/claude-cli.d.ts.map +1 -1
- package/dist/providers/claude-cli.js +20 -2
- package/dist/providers/claude-cli.js.map +1 -1
- package/dist/providers/codex-cli.d.ts.map +1 -1
- package/dist/providers/codex-cli.js +71 -16
- package/dist/providers/codex-cli.js.map +1 -1
- package/dist/providers/index.d.ts +11 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/runtime-context.d.ts +10 -0
- package/dist/runtime-context.d.ts.map +1 -0
- package/dist/runtime-context.js +30 -0
- package/dist/runtime-context.js.map +1 -0
- package/dist/subagent/queue.d.ts.map +1 -1
- package/dist/subagent/queue.js +1 -0
- package/dist/subagent/queue.js.map +1 -1
- package/dist/summarization-pipeline.d.ts +1 -0
- package/dist/summarization-pipeline.d.ts.map +1 -1
- package/dist/summarization-pipeline.js +94 -25
- package/dist/summarization-pipeline.js.map +1 -1
- package/dist/tool-permissions.d.ts +2 -0
- package/dist/tool-permissions.d.ts.map +1 -0
- package/dist/tool-permissions.js +25 -0
- package/dist/tool-permissions.js.map +1 -0
- package/dist/tools/index.d.ts +7 -8
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +70 -60
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/search-memory.d.ts.map +1 -1
- package/dist/tools/search-memory.js +9 -3
- package/dist/tools/search-memory.js.map +1 -1
- package/dist/tools/spawn-subagent.d.ts.map +1 -1
- package/dist/tools/spawn-subagent.js +1 -0
- package/dist/tools/spawn-subagent.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +1 -1
- package/dist/wizard-support.d.ts.map +1 -1
- package/dist/wizard-support.js +8 -6
- package/dist/wizard-support.js.map +1 -1
- package/dist/workflow-engine.d.ts +6 -2
- package/dist/workflow-engine.d.ts.map +1 -1
- package/dist/workflow-engine.js +254 -77
- package/dist/workflow-engine.js.map +1 -1
- package/package.json +2 -1
package/dist/bot-manager.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
|
114
|
-
oauthToken
|
|
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.
|
|
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
|
-
|
|
195
|
-
|
|
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
|
|
164
|
+
provider: agentCfg.runtimeProvider || agentCfg.provider,
|
|
165
|
+
model: agentCfg.runtimeModel || agentCfg.model,
|
|
166
|
+
apiKey,
|
|
218
167
|
oauthToken,
|
|
219
168
|
enabledTools: agentCfg.enabledTools,
|
|
220
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
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
|
-
|
|
375
|
-
|
|
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 ||
|
|
330
|
+
permissionMode: agent.permissionMode || 'autopilot',
|
|
625
331
|
finalPrompt: agent.systemPrompt || undefined,
|
|
626
332
|
purposeMd: agent.agentDescription || undefined,
|
|
627
|
-
enabledBuiltinToolsJson: JSON.stringify(
|
|
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(
|
|
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 —
|
|
665
|
-
|
|
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 ||
|
|
383
|
+
permissionMode: agent.permissionMode || 'autopilot',
|
|
727
384
|
finalPrompt: agent.systemPrompt || undefined,
|
|
728
385
|
purposeMd: agent.agentDescription || undefined,
|
|
729
|
-
enabledBuiltinToolsJson: JSON.stringify(
|
|
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(
|
|
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'));
|