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.
- 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/auth/auto-detect.d.ts +1 -0
- package/dist/auth/auto-detect.d.ts.map +1 -1
- package/dist/auth/auto-detect.js +16 -13
- package/dist/auth/auto-detect.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 +8 -23
- package/dist/bot-manager.d.ts.map +1 -1
- package/dist/bot-manager.js +61 -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 +154 -43
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +195 -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 +10 -6
- package/dist/message-loop.d.ts.map +1 -1
- package/dist/message-loop.js +241 -540
- 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,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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
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
|
-
|
|
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) {
|
|
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 ||
|
|
339
|
+
permissionMode: agent.permissionMode || 'autopilot',
|
|
625
340
|
finalPrompt: agent.systemPrompt || undefined,
|
|
626
341
|
purposeMd: agent.agentDescription || undefined,
|
|
627
|
-
enabledBuiltinToolsJson: JSON.stringify(
|
|
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(
|
|
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 —
|
|
665
|
-
|
|
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 ||
|
|
392
|
+
permissionMode: agent.permissionMode || 'autopilot',
|
|
727
393
|
finalPrompt: agent.systemPrompt || undefined,
|
|
728
394
|
purposeMd: agent.agentDescription || undefined,
|
|
729
|
-
enabledBuiltinToolsJson: JSON.stringify(
|
|
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(
|
|
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'));
|