funolio-agent 1.0.75 → 1.1.65
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/auth/credential-reader.d.ts.map +1 -1
- package/dist/auth/credential-reader.js +4 -3
- package/dist/auth/credential-reader.js.map +1 -1
- package/dist/auth/token-refresh.d.ts +8 -0
- package/dist/auth/token-refresh.d.ts.map +1 -1
- package/dist/auth/token-refresh.js +82 -52
- package/dist/auth/token-refresh.js.map +1 -1
- package/dist/auto-organizer.d.ts.map +1 -1
- package/dist/auto-organizer.js +6 -7
- package/dist/auto-organizer.js.map +1 -1
- package/dist/bench-prefix.d.ts +16 -0
- package/dist/bench-prefix.d.ts.map +1 -0
- package/dist/bench-prefix.js +25 -0
- package/dist/bench-prefix.js.map +1 -0
- package/dist/bot-manager.d.ts.map +1 -1
- package/dist/bot-manager.js +23 -14
- package/dist/bot-manager.js.map +1 -1
- package/dist/chat-sync.d.ts +42 -0
- package/dist/chat-sync.d.ts.map +1 -0
- package/dist/chat-sync.js +95 -0
- package/dist/chat-sync.js.map +1 -0
- package/dist/clerk-model.d.ts +7 -0
- package/dist/clerk-model.d.ts.map +1 -1
- package/dist/clerk-model.js +42 -8
- package/dist/clerk-model.js.map +1 -1
- package/dist/cli-bootstrap-history.d.ts +10 -0
- package/dist/cli-bootstrap-history.d.ts.map +1 -0
- package/dist/cli-bootstrap-history.js +112 -0
- package/dist/cli-bootstrap-history.js.map +1 -0
- package/dist/cli-models.d.ts +8 -0
- package/dist/cli-models.d.ts.map +1 -0
- package/dist/cli-models.js +91 -0
- package/dist/cli-models.js.map +1 -0
- package/dist/cli-session-epoch.d.ts +13 -3
- package/dist/cli-session-epoch.d.ts.map +1 -1
- package/dist/cli-session-epoch.js +53 -4
- package/dist/cli-session-epoch.js.map +1 -1
- package/dist/codex-app-server-manager.d.ts +64 -4
- package/dist/codex-app-server-manager.d.ts.map +1 -1
- package/dist/codex-app-server-manager.js +755 -55
- package/dist/codex-app-server-manager.js.map +1 -1
- package/dist/commands/pool.d.ts +32 -0
- package/dist/commands/pool.d.ts.map +1 -1
- package/dist/commands/pool.js +145 -66
- package/dist/commands/pool.js.map +1 -1
- package/dist/commands/start.d.ts +21 -0
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +484 -63
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +5 -2
- package/dist/commands/status.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +170 -58
- package/dist/config.js.map +1 -1
- package/dist/context-window.d.ts +37 -1
- package/dist/context-window.d.ts.map +1 -1
- package/dist/context-window.js +202 -16
- package/dist/context-window.js.map +1 -1
- package/dist/live-activity.d.ts +3 -1
- package/dist/live-activity.d.ts.map +1 -1
- package/dist/live-activity.js.map +1 -1
- package/dist/local-chat-execution.d.ts +114 -0
- package/dist/local-chat-execution.d.ts.map +1 -0
- package/dist/local-chat-execution.js +349 -0
- package/dist/local-chat-execution.js.map +1 -0
- package/dist/local-cli-pty-manager.d.ts +138 -3
- package/dist/local-cli-pty-manager.d.ts.map +1 -1
- package/dist/local-cli-pty-manager.js +1415 -111
- package/dist/local-cli-pty-manager.js.map +1 -1
- package/dist/local-conversation-gateway.d.ts +110 -0
- package/dist/local-conversation-gateway.d.ts.map +1 -0
- package/dist/local-conversation-gateway.js +175 -0
- package/dist/local-conversation-gateway.js.map +1 -0
- package/dist/local-data.d.ts +235 -5
- package/dist/local-data.d.ts.map +1 -1
- package/dist/local-data.js +1066 -87
- package/dist/local-data.js.map +1 -1
- package/dist/local-db.d.ts +6 -0
- package/dist/local-db.d.ts.map +1 -1
- package/dist/local-db.js +376 -4
- package/dist/local-db.js.map +1 -1
- package/dist/local-funnel.d.ts.map +1 -1
- package/dist/local-funnel.js +6 -5
- package/dist/local-funnel.js.map +1 -1
- package/dist/local-server.d.ts +30 -0
- package/dist/local-server.d.ts.map +1 -1
- package/dist/local-server.js +2898 -319
- package/dist/local-server.js.map +1 -1
- package/dist/managed-process-registry.d.ts +59 -0
- package/dist/managed-process-registry.d.ts.map +1 -0
- package/dist/managed-process-registry.js +390 -0
- package/dist/managed-process-registry.js.map +1 -0
- package/dist/mcp/claude-config-writer.d.ts +5 -5
- package/dist/mcp/claude-config-writer.d.ts.map +1 -1
- package/dist/mcp/claude-config-writer.js +19 -11
- package/dist/mcp/claude-config-writer.js.map +1 -1
- package/dist/mcp/index.d.ts +4 -2
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/sync-cli-config.d.ts +42 -4
- package/dist/mcp/sync-cli-config.d.ts.map +1 -1
- package/dist/mcp/sync-cli-config.js +497 -17
- package/dist/mcp/sync-cli-config.js.map +1 -1
- package/dist/message-loop.d.ts.map +1 -1
- package/dist/message-loop.js +43 -1
- package/dist/message-loop.js.map +1 -1
- package/dist/mqtt-client.d.ts +34 -0
- package/dist/mqtt-client.d.ts.map +1 -1
- package/dist/mqtt-client.js +270 -45
- package/dist/mqtt-client.js.map +1 -1
- package/dist/mqtt-data-relay.d.ts +44 -0
- package/dist/mqtt-data-relay.d.ts.map +1 -0
- package/dist/mqtt-data-relay.js +106 -0
- package/dist/mqtt-data-relay.js.map +1 -0
- package/dist/orchestration/capabilities.d.ts +13 -0
- package/dist/orchestration/capabilities.d.ts.map +1 -0
- package/dist/orchestration/capabilities.js +152 -0
- package/dist/orchestration/capabilities.js.map +1 -0
- package/dist/orchestration/dispatch-executor.d.ts +83 -0
- package/dist/orchestration/dispatch-executor.d.ts.map +1 -0
- package/dist/orchestration/dispatch-executor.js +266 -0
- package/dist/orchestration/dispatch-executor.js.map +1 -0
- package/dist/orchestration/dispatch-hint.d.ts +134 -0
- package/dist/orchestration/dispatch-hint.d.ts.map +1 -0
- package/dist/orchestration/dispatch-hint.js +247 -0
- package/dist/orchestration/dispatch-hint.js.map +1 -0
- package/dist/orchestration/dispatch-runner.d.ts +106 -0
- package/dist/orchestration/dispatch-runner.d.ts.map +1 -0
- package/dist/orchestration/dispatch-runner.js +604 -0
- package/dist/orchestration/dispatch-runner.js.map +1 -0
- package/dist/orchestration/dispatch-tools.d.ts +167 -0
- package/dist/orchestration/dispatch-tools.d.ts.map +1 -0
- package/dist/orchestration/dispatch-tools.js +328 -0
- package/dist/orchestration/dispatch-tools.js.map +1 -0
- package/dist/orchestration/front-door-policy.d.ts +35 -10
- package/dist/orchestration/front-door-policy.d.ts.map +1 -1
- package/dist/orchestration/front-door-policy.js +30 -267
- package/dist/orchestration/front-door-policy.js.map +1 -1
- package/dist/orchestration/orchestrator-dispatch-prompt.d.ts +43 -0
- package/dist/orchestration/orchestrator-dispatch-prompt.d.ts.map +1 -0
- package/dist/orchestration/orchestrator-dispatch-prompt.js +267 -0
- package/dist/orchestration/orchestrator-dispatch-prompt.js.map +1 -0
- package/dist/orchestration/orchestrator-operating-prompt.d.ts +14 -0
- package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-operating-prompt.js +157 -31
- package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
- package/dist/orchestration/plan-import.d.ts +39 -0
- package/dist/orchestration/plan-import.d.ts.map +1 -0
- package/dist/orchestration/plan-import.js +547 -0
- package/dist/orchestration/plan-import.js.map +1 -0
- package/dist/orchestration/worker-operating-prompt.d.ts +2 -0
- package/dist/orchestration/worker-operating-prompt.d.ts.map +1 -1
- package/dist/orchestration/worker-operating-prompt.js +36 -46
- package/dist/orchestration/worker-operating-prompt.js.map +1 -1
- package/dist/orchestrator.d.ts +195 -3
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +1970 -432
- package/dist/orchestrator.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +8 -4
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/claude-cli.d.ts.map +1 -1
- package/dist/providers/claude-cli.js +28 -3
- package/dist/providers/claude-cli.js.map +1 -1
- package/dist/providers/codex-cli.d.ts +10 -6
- package/dist/providers/codex-cli.d.ts.map +1 -1
- package/dist/providers/codex-cli.js +190 -17
- package/dist/providers/codex-cli.js.map +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +15 -5
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/index.d.ts +15 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +13 -5
- package/dist/providers/openai.js.map +1 -1
- package/dist/server-adapter.d.ts +8 -0
- package/dist/server-adapter.d.ts.map +1 -1
- package/dist/server-adapter.js +7 -0
- package/dist/server-adapter.js.map +1 -1
- package/dist/service-mode.d.ts +1 -1
- package/dist/service-mode.d.ts.map +1 -1
- package/dist/service-mode.js +64 -1
- package/dist/service-mode.js.map +1 -1
- package/dist/service-setup-only.d.ts +8 -0
- package/dist/service-setup-only.d.ts.map +1 -0
- package/dist/service-setup-only.js +37 -0
- package/dist/service-setup-only.js.map +1 -0
- package/dist/slash-commands.d.ts +21 -0
- package/dist/slash-commands.d.ts.map +1 -0
- package/dist/slash-commands.js +99 -0
- package/dist/slash-commands.js.map +1 -0
- package/dist/subagent/index.d.ts +4 -2
- package/dist/subagent/index.d.ts.map +1 -1
- package/dist/subagent/index.js.map +1 -1
- package/dist/summarization-pipeline.d.ts.map +1 -1
- package/dist/summarization-pipeline.js +1 -9
- package/dist/summarization-pipeline.js.map +1 -1
- package/dist/token-counter.d.ts.map +1 -1
- package/dist/token-counter.js +11 -4
- package/dist/token-counter.js.map +1 -1
- package/dist/tool-filter.d.ts.map +1 -1
- package/dist/tool-filter.js +10 -6
- package/dist/tool-filter.js.map +1 -1
- package/dist/tools/admin-tools.d.ts.map +1 -1
- package/dist/tools/admin-tools.js +13 -4
- package/dist/tools/admin-tools.js.map +1 -1
- package/dist/tools/run-command.d.ts.map +1 -1
- package/dist/tools/run-command.js +5 -1
- package/dist/tools/run-command.js.map +1 -1
- package/dist/tools/search-conversation-history.d.ts.map +1 -1
- package/dist/tools/search-conversation-history.js +12 -2
- package/dist/tools/search-conversation-history.js.map +1 -1
- package/dist/tools/todo-tasks.d.ts.map +1 -1
- package/dist/tools/todo-tasks.js +77 -5
- package/dist/tools/todo-tasks.js.map +1 -1
- package/dist/usage-log.d.ts +62 -0
- package/dist/usage-log.d.ts.map +1 -0
- package/dist/usage-log.js +98 -0
- package/dist/usage-log.js.map +1 -0
- package/dist/wizard-state.d.ts +13 -0
- package/dist/wizard-state.d.ts.map +1 -1
- package/dist/wizard-state.js +61 -3
- package/dist/wizard-state.js.map +1 -1
- package/dist/wizard-support.d.ts.map +1 -1
- package/dist/wizard-support.js +27 -1
- package/dist/wizard-support.js.map +1 -1
- package/dist/workflow-engine.d.ts +40 -1
- package/dist/workflow-engine.d.ts.map +1 -1
- package/dist/workflow-engine.js +753 -93
- package/dist/workflow-engine.js.map +1 -1
- package/package.json +2 -2
package/dist/commands/start.js
CHANGED
|
@@ -36,6 +36,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.mapServerBotsToAgents = mapServerBotsToAgents;
|
|
40
|
+
exports.isServerSeedAgentId = isServerSeedAgentId;
|
|
41
|
+
exports.selectPreferredActiveAgentId = selectPreferredActiveAgentId;
|
|
42
|
+
exports.normalizeLocalModeAgents = normalizeLocalModeAgents;
|
|
39
43
|
exports.startCommand = startCommand;
|
|
40
44
|
const path = __importStar(require("path"));
|
|
41
45
|
const fs = __importStar(require("fs"));
|
|
@@ -45,14 +49,18 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
45
49
|
const config_1 = require("../config");
|
|
46
50
|
const mqtt_client_1 = require("../mqtt-client");
|
|
47
51
|
const bot_manager_1 = require("../bot-manager");
|
|
52
|
+
const mqtt_data_relay_1 = require("../mqtt-data-relay");
|
|
48
53
|
const service_mode_1 = require("../service-mode");
|
|
49
54
|
const index_1 = require("../providers/index");
|
|
50
55
|
const sync_cli_config_1 = require("../mcp/sync-cli-config");
|
|
51
56
|
const agent_config_1 = require("../agent-config");
|
|
57
|
+
const service_setup_only_1 = require("../service-setup-only");
|
|
52
58
|
// refreshOAuthToken removed — token refresh now handled per-request in message-loop.ts
|
|
53
59
|
const local_db_1 = require("../local-db");
|
|
54
60
|
const local_server_1 = require("../local-server");
|
|
55
61
|
const data = __importStar(require("../local-data"));
|
|
62
|
+
const chat_sync_1 = require("../chat-sync");
|
|
63
|
+
const pool_1 = require("./pool");
|
|
56
64
|
const AUTH_SESSION_KEY = 'auth.session';
|
|
57
65
|
const DESKTOP_PREFS_KEY = 'desktop.preferences';
|
|
58
66
|
const WIZARD_PROFILE_KEY = 'wizard.profile';
|
|
@@ -140,7 +148,7 @@ function readLastMaintenanceRestartAtMs() {
|
|
|
140
148
|
function markMaintenanceRestartAt(now) {
|
|
141
149
|
data.setSetting(LAST_MAINTENANCE_RESTART_KEY, now.toISOString());
|
|
142
150
|
}
|
|
143
|
-
function shouldRunNightlyRestart(now, lastInteractionAtMs, lastMaintenanceRestartAtMs, botManager) {
|
|
151
|
+
function shouldRunNightlyRestart(now, lastInteractionAtMs, lastMaintenanceRestartAtMs, botManager, processStartAtMs) {
|
|
144
152
|
const hour = now.getHours();
|
|
145
153
|
if (hour < 1 || hour >= 4)
|
|
146
154
|
return false;
|
|
@@ -150,7 +158,280 @@ function shouldRunNightlyRestart(now, lastInteractionAtMs, lastMaintenanceRestar
|
|
|
150
158
|
return false;
|
|
151
159
|
if (lastMaintenanceRestartAtMs && (now.getTime() - lastMaintenanceRestartAtMs) < MIN_MAINTENANCE_RESTART_INTERVAL_MS)
|
|
152
160
|
return false;
|
|
153
|
-
|
|
161
|
+
// Count idle from the most recent of (process start, last interaction). Without this
|
|
162
|
+
// floor, a stale `agent.last_interaction_at` value in local.db would make the timer
|
|
163
|
+
// fire ~5 minutes after a cold start instead of after a real idle window.
|
|
164
|
+
const idleReference = Math.max(lastInteractionAtMs, processStartAtMs);
|
|
165
|
+
return (now.getTime() - idleReference) >= AGENT_IDLE_RESTART_MS;
|
|
166
|
+
}
|
|
167
|
+
function isAgentPermissionMode(value) {
|
|
168
|
+
return value === 'autopilot' || value === 'approve-destructive' || value === 'approve-all';
|
|
169
|
+
}
|
|
170
|
+
function toManagedAgentConfigName(agent) {
|
|
171
|
+
const safeName = (agent.name || 'agent')
|
|
172
|
+
.toLowerCase()
|
|
173
|
+
.replace(/[^a-z0-9-_]+/g, '-')
|
|
174
|
+
.replace(/^-+|-+$/g, '')
|
|
175
|
+
.slice(0, 24) || 'agent';
|
|
176
|
+
const safeId = (agent.id || 'bot')
|
|
177
|
+
.toLowerCase()
|
|
178
|
+
.replace(/[^a-z0-9]/g, '')
|
|
179
|
+
.slice(0, 12) || 'bot';
|
|
180
|
+
return `managed-${safeName}-${safeId}`;
|
|
181
|
+
}
|
|
182
|
+
function mapServerBotsToAgents(serverBots, existingAgents = []) {
|
|
183
|
+
const existingById = new Map(existingAgents.map((agent) => [agent.id, agent]));
|
|
184
|
+
const mappedById = new Map(serverBots
|
|
185
|
+
.filter((bot) => !!bot?.id && !!bot?.name)
|
|
186
|
+
.map((bot) => {
|
|
187
|
+
const existing = existingById.get(bot.id);
|
|
188
|
+
return [
|
|
189
|
+
bot.id,
|
|
190
|
+
{
|
|
191
|
+
id: bot.id,
|
|
192
|
+
name: bot.name,
|
|
193
|
+
projectDir: existing?.projectDir || '.',
|
|
194
|
+
provider: bot.llmProvider || existing?.provider || bot.runtimeProvider || 'openai',
|
|
195
|
+
model: bot.llmModel || existing?.model || bot.runtimeModel || 'claude-opus-4-6',
|
|
196
|
+
runtimeProvider: bot.runtimeProvider || existing?.runtimeProvider || undefined,
|
|
197
|
+
runtimeModel: bot.runtimeModel || existing?.runtimeModel || undefined,
|
|
198
|
+
accessMode: bot.accessMode || existing?.accessMode || undefined,
|
|
199
|
+
enabledTools: Array.isArray(bot.tools) ? bot.tools : existing?.enabledTools,
|
|
200
|
+
enabledMcpTools: Array.isArray(bot.enabledMcpTools) ? bot.enabledMcpTools : existing?.enabledMcpTools,
|
|
201
|
+
permissionMode: isAgentPermissionMode(bot.permissionMode)
|
|
202
|
+
? bot.permissionMode
|
|
203
|
+
: (existing?.permissionMode || 'autopilot'),
|
|
204
|
+
systemPrompt: existing?.systemPrompt,
|
|
205
|
+
agentDescription: existing?.agentDescription,
|
|
206
|
+
createdAt: existing?.createdAt || new Date().toISOString(),
|
|
207
|
+
},
|
|
208
|
+
];
|
|
209
|
+
}));
|
|
210
|
+
const merged = [];
|
|
211
|
+
for (const existing of existingAgents) {
|
|
212
|
+
const mapped = mappedById.get(existing.id);
|
|
213
|
+
merged.push(mapped || existing);
|
|
214
|
+
if (mapped)
|
|
215
|
+
mappedById.delete(existing.id);
|
|
216
|
+
}
|
|
217
|
+
for (const mapped of mappedById.values()) {
|
|
218
|
+
merged.push(mapped);
|
|
219
|
+
}
|
|
220
|
+
return merged;
|
|
221
|
+
}
|
|
222
|
+
function isServerSeedAgentId(agentId) {
|
|
223
|
+
return typeof agentId === 'string' && /^seed-bot-/i.test(agentId);
|
|
224
|
+
}
|
|
225
|
+
function selectPreferredActiveAgentId(agents, providers, preferredId) {
|
|
226
|
+
if (!Array.isArray(agents) || agents.length === 0)
|
|
227
|
+
return null;
|
|
228
|
+
const providerIds = new Set((providers || []).map((provider) => provider.id));
|
|
229
|
+
const isRunnable = (agent) => providerIds.has(agent.runtimeProvider || agent.provider);
|
|
230
|
+
const preferred = preferredId ? agents.find((agent) => agent.id === preferredId) : undefined;
|
|
231
|
+
if (preferred && isRunnable(preferred))
|
|
232
|
+
return preferred.id;
|
|
233
|
+
const firstRunnable = agents.find(isRunnable);
|
|
234
|
+
if (firstRunnable)
|
|
235
|
+
return firstRunnable.id;
|
|
236
|
+
return preferred?.id || agents[0]?.id || null;
|
|
237
|
+
}
|
|
238
|
+
function normalizeLocalModeAgents(config) {
|
|
239
|
+
const existingAgents = Array.isArray(config.agents) ? config.agents : [];
|
|
240
|
+
const filteredAgents = existingAgents.filter((agent) => !isServerSeedAgentId(agent.id));
|
|
241
|
+
const removedCount = existingAgents.length - filteredAgents.length;
|
|
242
|
+
const preferredId = isServerSeedAgentId(config.activeAgentId) ? null : config.activeAgentId;
|
|
243
|
+
const selectedActiveAgentId = selectPreferredActiveAgentId(filteredAgents, config.providers, preferredId);
|
|
244
|
+
let changed = false;
|
|
245
|
+
if (removedCount > 0) {
|
|
246
|
+
config.agents = filteredAgents;
|
|
247
|
+
changed = true;
|
|
248
|
+
}
|
|
249
|
+
if ((selectedActiveAgentId || null) !== (config.activeAgentId || null)) {
|
|
250
|
+
config.activeAgentId = selectedActiveAgentId || undefined;
|
|
251
|
+
changed = true;
|
|
252
|
+
}
|
|
253
|
+
return { changed, removedCount };
|
|
254
|
+
}
|
|
255
|
+
function syncConfiguredAgentsToLocalConfigs(agents) {
|
|
256
|
+
if (!Array.isArray(agents) || agents.length === 0)
|
|
257
|
+
return;
|
|
258
|
+
let synced = 0;
|
|
259
|
+
for (const agent of agents) {
|
|
260
|
+
if (!agent?.id)
|
|
261
|
+
continue;
|
|
262
|
+
const configName = toManagedAgentConfigName(agent);
|
|
263
|
+
(0, agent_config_1.saveAgentConfig)(configName, {
|
|
264
|
+
name: agent.name || agent.id,
|
|
265
|
+
botId: agent.id,
|
|
266
|
+
provider: agent.provider,
|
|
267
|
+
runtimeProvider: agent.runtimeProvider,
|
|
268
|
+
model: agent.model,
|
|
269
|
+
runtimeModel: agent.runtimeModel,
|
|
270
|
+
accessMode: agent.accessMode,
|
|
271
|
+
workspace: agent.projectDir || '.',
|
|
272
|
+
permissionMode: agent.permissionMode,
|
|
273
|
+
enabledTools: agent.enabledTools,
|
|
274
|
+
enabledMcpTools: agent.enabledMcpTools,
|
|
275
|
+
systemPrompt: agent.systemPrompt || agent.agentDescription,
|
|
276
|
+
createdAt: agent.createdAt || new Date().toISOString(),
|
|
277
|
+
});
|
|
278
|
+
synced++;
|
|
279
|
+
}
|
|
280
|
+
if (synced > 0) {
|
|
281
|
+
console.log(chalk_1.default.gray(` Synced ${synced} managed agent config(s) to ~/.funolio/agents/`));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async function fetchMcpInstallations(apiUrl, authToken) {
|
|
285
|
+
const res = await fetch(`${apiUrl}/api/v1/bot/mcp-installations`, {
|
|
286
|
+
headers: { Authorization: `Bearer ${authToken}` },
|
|
287
|
+
});
|
|
288
|
+
if (!res.ok) {
|
|
289
|
+
const text = await res.text().catch(() => '');
|
|
290
|
+
throw new Error(`mcp-installations ${res.status}: ${text}`);
|
|
291
|
+
}
|
|
292
|
+
const data = (await res.json());
|
|
293
|
+
return Array.isArray(data.installations) ? data.installations : [];
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Sync approved MCP installations from the cloud into the local MCP manager.
|
|
297
|
+
*
|
|
298
|
+
* Flow:
|
|
299
|
+
* 1. Fetch installations from /api/v1/bot/mcp-installations
|
|
300
|
+
* 2. For each "ready" installation, ensure the MCP is launched with the
|
|
301
|
+
* current env vars (Google refresh token in particular rotates).
|
|
302
|
+
* 3. Skip installations with status "needs_reauth" or "unknown_server";
|
|
303
|
+
* log why.
|
|
304
|
+
*
|
|
305
|
+
* Idempotent: MCPManager.installAndLaunch short-circuits when a server is
|
|
306
|
+
* already running. Env-var changes trigger reloadIntegrationServers for the
|
|
307
|
+
* Google family so the subprocess restarts with fresh tokens.
|
|
308
|
+
*/
|
|
309
|
+
async function syncCloudMcpInstallations(opts) {
|
|
310
|
+
const { apiUrl, authToken, mcpManager } = opts;
|
|
311
|
+
let installations;
|
|
312
|
+
try {
|
|
313
|
+
installations = await fetchMcpInstallations(apiUrl, authToken);
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
throw new Error(`fetch failed: ${err?.message || err}`);
|
|
317
|
+
}
|
|
318
|
+
if (installations.length === 0) {
|
|
319
|
+
if (process.env.FUNOLIO_AGENT_DEBUG === 'true') {
|
|
320
|
+
console.log(chalk_1.default.gray(' [mcp-sync] no approved MCP installations for this user'));
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const { MCP_REGISTRY } = await Promise.resolve().then(() => __importStar(require('../mcp/registry-shared')));
|
|
325
|
+
let launched = 0;
|
|
326
|
+
let skipped = 0;
|
|
327
|
+
let googleRefreshed = false;
|
|
328
|
+
for (const inst of installations) {
|
|
329
|
+
if (inst.status !== 'ready') {
|
|
330
|
+
skipped++;
|
|
331
|
+
if (process.env.FUNOLIO_AGENT_DEBUG === 'true') {
|
|
332
|
+
console.log(chalk_1.default.gray(` [mcp-sync] skipping ${inst.serverId}: ${inst.status}${inst.error ? ` (${inst.error})` : ''}`));
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const entry = MCP_REGISTRY.find((e) => e.id === inst.serverId);
|
|
337
|
+
if (!entry) {
|
|
338
|
+
skipped++;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const runningInfo = mcpManager.getServerInfo(inst.serverId);
|
|
342
|
+
if (runningInfo) {
|
|
343
|
+
// Already running. If this is a Google-family MCP we may still want
|
|
344
|
+
// to restart it so the subprocess picks up a rotated refresh token.
|
|
345
|
+
// The batch-restart below handles that.
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
await mcpManager.installAndLaunch(entry, inst.envVars, inst.alwaysOn);
|
|
350
|
+
launched++;
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
console.error(chalk_1.default.yellow(` [mcp-sync] failed to launch ${inst.serverId}: ${err?.message || err}`));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// If any already-running Google MCP has a changed refresh token, restart
|
|
357
|
+
// the Google family so they pick up the fresh credential. The manager's
|
|
358
|
+
// existing reloadIntegrationServers does the heavy lifting.
|
|
359
|
+
try {
|
|
360
|
+
const hasGoogleReady = installations.some((i) => i.status === 'ready' && /^google-|^gmail$/.test(i.serverId));
|
|
361
|
+
if (hasGoogleReady) {
|
|
362
|
+
const running = mcpManager.getRunningServers();
|
|
363
|
+
const googleRunning = running.some((id) => /^google-|^gmail$/.test(id));
|
|
364
|
+
if (googleRunning) {
|
|
365
|
+
await mcpManager.reloadIntegrationServers('google');
|
|
366
|
+
googleRefreshed = true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
console.error(chalk_1.default.yellow(` [mcp-sync] google reload failed: ${err?.message || err}`));
|
|
372
|
+
}
|
|
373
|
+
if (launched > 0 || googleRefreshed) {
|
|
374
|
+
console.log(chalk_1.default.green(` ✓ MCP sync: ${launched} launched, ${skipped} skipped${googleRefreshed ? ', google refreshed' : ''}`));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async function fetchPoolSubscriptions(apiUrl, authToken) {
|
|
378
|
+
const res = await fetch(`${apiUrl}/api/v1/bot/pool-subscriptions`, {
|
|
379
|
+
headers: { Authorization: `Bearer ${authToken}` },
|
|
380
|
+
});
|
|
381
|
+
if (!res.ok) {
|
|
382
|
+
const text = await res.text().catch(() => '');
|
|
383
|
+
throw new Error(`pool-subscriptions ${res.status}: ${text}`);
|
|
384
|
+
}
|
|
385
|
+
const data = (await res.json());
|
|
386
|
+
return Array.isArray(data.subscriptions) ? data.subscriptions : [];
|
|
387
|
+
}
|
|
388
|
+
async function setupPoolSubscriptions(opts) {
|
|
389
|
+
const { apiUrl, authToken, mqttClient, config } = opts;
|
|
390
|
+
let subs = [];
|
|
391
|
+
try {
|
|
392
|
+
subs = await fetchPoolSubscriptions(apiUrl, authToken);
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
console.error(chalk_1.default.yellow(`⚠ Failed to fetch pool subscriptions: ${err?.message || err}`));
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (subs.length === 0) {
|
|
399
|
+
console.log(chalk_1.default.gray(' No team pool provider instances for this user.'));
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
console.log(chalk_1.default.blue(` Subscribing to ${subs.length} team-pool request topic(s)...`));
|
|
403
|
+
const publisher = {
|
|
404
|
+
publish: (topic, payload, o) => mqttClient.publish(topic, payload, { qos: (o?.qos ?? 1) }),
|
|
405
|
+
};
|
|
406
|
+
for (const sub of subs) {
|
|
407
|
+
const { teamId, instanceId, provider: providerName } = sub;
|
|
408
|
+
const providerConfig = (config.providers || []).find((p) => p.id === providerName);
|
|
409
|
+
if (!providerConfig) {
|
|
410
|
+
console.log(chalk_1.default.yellow(` ⚠ No local provider config for "${providerName}" (teamId=${teamId.slice(0, 8)}); skipping`));
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
const topic = `funolio/team/${teamId}/provider/${instanceId}/request/+`;
|
|
414
|
+
mqttClient.subscribeTopic(topic, async (_t, payload) => {
|
|
415
|
+
let request;
|
|
416
|
+
try {
|
|
417
|
+
request = JSON.parse(payload.toString());
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
console.error(chalk_1.default.red('Pool request: failed to parse payload'));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
console.log(chalk_1.default.blue(`📨 Pool request ${String(request.requestId).slice(0, 8)}... team=${teamId.slice(0, 8)} model=${request.model}`));
|
|
424
|
+
await (0, pool_1.handlePoolRequest)({
|
|
425
|
+
teamId,
|
|
426
|
+
instanceId,
|
|
427
|
+
providerName,
|
|
428
|
+
providerConfig,
|
|
429
|
+
request,
|
|
430
|
+
publisher,
|
|
431
|
+
});
|
|
432
|
+
}, { qos: 1 });
|
|
433
|
+
console.log(chalk_1.default.gray(` • ${topic}`));
|
|
434
|
+
}
|
|
154
435
|
}
|
|
155
436
|
function parseJson(value) {
|
|
156
437
|
if (!value)
|
|
@@ -171,10 +452,25 @@ function loadDbRuntimeConfig() {
|
|
|
171
452
|
const botRows = data.listAgentProfiles();
|
|
172
453
|
const projects = data.listProjects({ includeArchived: true });
|
|
173
454
|
const firstProviderId = providerRows[0]?.provider_id;
|
|
174
|
-
const
|
|
455
|
+
const providerByConnectionId = new Map(providerRows.map((row) => [row.id, row]));
|
|
456
|
+
const providerByProviderId = new Map(providerRows.map((row) => [row.provider_id, row]));
|
|
457
|
+
const resolveBotProviderConnection = (bot) => {
|
|
458
|
+
if (bot.provider_connection_id) {
|
|
459
|
+
const direct = providerByConnectionId.get(bot.provider_connection_id);
|
|
460
|
+
if (direct)
|
|
461
|
+
return direct;
|
|
462
|
+
}
|
|
463
|
+
return providerByProviderId.get(bot.provider);
|
|
464
|
+
};
|
|
465
|
+
const isRunnableBot = (bot) => !!resolveBotProviderConnection(bot);
|
|
466
|
+
const defaultBot = botRows.find((bot) => bot.is_default === 1 && isRunnableBot(bot))
|
|
467
|
+
|| botRows.find((bot) => bot.is_active === 1 && isRunnableBot(bot))
|
|
468
|
+
|| botRows.find(isRunnableBot)
|
|
469
|
+
|| botRows.find((bot) => bot.is_default === 1)
|
|
470
|
+
|| botRows[0];
|
|
175
471
|
const providers = providerRows.map((row) => ({
|
|
176
472
|
id: row.provider_id,
|
|
177
|
-
authType: row.auth_type === 'oauth' ? 'oauth' : 'apiKey',
|
|
473
|
+
authType: row.auth_type === 'oauth' ? 'oauth' : row.auth_type === 'cli' ? 'cli' : 'apiKey',
|
|
178
474
|
apiKey: row.api_key_enc || undefined,
|
|
179
475
|
oauthToken: row.oauth_token || undefined,
|
|
180
476
|
oauthRefreshToken: row.oauth_refresh_token || undefined,
|
|
@@ -183,13 +479,16 @@ function loadDbRuntimeConfig() {
|
|
|
183
479
|
label: row.label || undefined,
|
|
184
480
|
}));
|
|
185
481
|
const agents = botRows.map((row) => {
|
|
482
|
+
const resolvedConnection = resolveBotProviderConnection(row);
|
|
483
|
+
const resolvedProviderId = resolvedConnection?.provider_id || row.provider;
|
|
484
|
+
const resolvedModel = row.model || resolvedConnection?.default_model || config_1.DEFAULT_MODELS[resolvedProviderId] || '';
|
|
186
485
|
const projectDir = projects.find((project) => project.bot_ids.includes(row.id) && project.folder)?.folder || '.';
|
|
187
486
|
return {
|
|
188
487
|
id: row.id,
|
|
189
488
|
name: row.name,
|
|
190
489
|
projectDir,
|
|
191
|
-
provider:
|
|
192
|
-
model:
|
|
490
|
+
provider: resolvedProviderId,
|
|
491
|
+
model: resolvedModel,
|
|
193
492
|
enabledTools: parseJson(row.enabled_builtin_tools_json),
|
|
194
493
|
enabledMcpTools: parseJson(row.enabled_mcp_tools_json),
|
|
195
494
|
permissionMode: row.permission_mode || 'autopilot',
|
|
@@ -204,7 +503,7 @@ function loadDbRuntimeConfig() {
|
|
|
204
503
|
return {
|
|
205
504
|
auth,
|
|
206
505
|
providers,
|
|
207
|
-
defaultProvider: defaultBot?.provider || firstProviderId,
|
|
506
|
+
defaultProvider: (defaultBot ? (resolveBotProviderConnection(defaultBot)?.provider_id || defaultBot.provider) : undefined) || firstProviderId,
|
|
208
507
|
agents,
|
|
209
508
|
activeAgentId: defaultBot?.id,
|
|
210
509
|
autostart: !!desktopPrefs?.launchOnStartup,
|
|
@@ -253,10 +552,12 @@ async function startCommand(projectDir, options) {
|
|
|
253
552
|
}
|
|
254
553
|
serviceLockPath = lock.lockPath;
|
|
255
554
|
}
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
555
|
+
const config = (0, config_1.migrateConfig)((0, config_1.loadConfig)());
|
|
556
|
+
const useDbRuntime = (0, config_1.hasDbConfig)();
|
|
557
|
+
if (options.mode === 'service' && config.connectionMode === 'local') {
|
|
558
|
+
process.env.LOCAL_FIRST_ENABLED = 'true';
|
|
559
|
+
process.env.FUNOLIO_RUN_CONTEXT = process.env.FUNOLIO_RUN_CONTEXT || 'service';
|
|
560
|
+
}
|
|
260
561
|
const authConfig = config.auth || null;
|
|
261
562
|
const hasAuth = !!authConfig;
|
|
262
563
|
const authExpired = hasAuth
|
|
@@ -264,9 +565,7 @@ async function startCommand(projectDir, options) {
|
|
|
264
565
|
: true;
|
|
265
566
|
const runSetupOnly = async (reason) => {
|
|
266
567
|
if (isServiceMode) {
|
|
267
|
-
|
|
268
|
-
(0, service_mode_1.emitEvent)('status', { status: 'setup_only', reason });
|
|
269
|
-
await new Promise(() => { });
|
|
568
|
+
await (0, service_setup_only_1.waitForServiceSetup)(reason, service_mode_1.emitEvent, options.setupOnlySignal);
|
|
270
569
|
return true;
|
|
271
570
|
}
|
|
272
571
|
return false;
|
|
@@ -275,6 +574,15 @@ async function startCommand(projectDir, options) {
|
|
|
275
574
|
let localServerStarted = false;
|
|
276
575
|
const runtimeConnectionMode = config.connectionMode === 'server' ? 'server' : 'local';
|
|
277
576
|
const runtimeServerBaseUrl = typeof config.serverBaseUrl === 'string' ? config.serverBaseUrl.trim() : '';
|
|
577
|
+
if (runtimeConnectionMode === 'local') {
|
|
578
|
+
const localNormalization = normalizeLocalModeAgents(config);
|
|
579
|
+
if (localNormalization.changed) {
|
|
580
|
+
(0, config_1.saveConfig)(config);
|
|
581
|
+
if (localNormalization.removedCount > 0) {
|
|
582
|
+
console.log(chalk_1.default.gray(` Removed ${localNormalization.removedCount} server-only bot profile(s) from local mode`));
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
278
586
|
process.env.FUNOLIO_CONNECTION_MODE = runtimeConnectionMode;
|
|
279
587
|
if (runtimeServerBaseUrl) {
|
|
280
588
|
process.env.FUNOLIO_SERVER_BASE_URL = runtimeServerBaseUrl;
|
|
@@ -293,6 +601,9 @@ async function startCommand(projectDir, options) {
|
|
|
293
601
|
const dbInfo = (0, local_db_1.initLocalDb)();
|
|
294
602
|
console.log(chalk_1.default.green(`✓ Local DB ready: ${dbInfo.path}`));
|
|
295
603
|
console.log(chalk_1.default.gray(` WAL mode: ${dbInfo.walMode}, tables: ${dbInfo.tableCount}, write: ${dbInfo.testWrite}, read: ${dbInfo.testRead}`));
|
|
604
|
+
if (dbInfo.claudeSessionCounterSync?.updated) {
|
|
605
|
+
console.log(chalk_1.default.gray(` Claude session counter advanced from ${dbInfo.claudeSessionCounterSync.dbValueBefore} to ${dbInfo.claudeSessionCounterSync.dbValueAfter} to match existing ~/.claude Funolio sessions`));
|
|
606
|
+
}
|
|
296
607
|
cleanupExpiredMessageActivityRows();
|
|
297
608
|
if (isServiceMode) {
|
|
298
609
|
(0, service_mode_1.emitEvent)('local_db', { status: 'ready', path: dbInfo.path, walMode: dbInfo.walMode });
|
|
@@ -379,55 +690,69 @@ async function startCommand(projectDir, options) {
|
|
|
379
690
|
process.exit(1);
|
|
380
691
|
}
|
|
381
692
|
}
|
|
382
|
-
// Re-sync
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
693
|
+
// Re-sync providers from server (picks up web re-auth tokens).
|
|
694
|
+
// In local mode, keep the local bot roster authoritative and do not import
|
|
695
|
+
// the server seed-bot catalog into the desktop agent list.
|
|
696
|
+
try {
|
|
697
|
+
const res = await fetch(`${config_1.FUNOLIO_API_URL}/api/v1/agent/config`, {
|
|
698
|
+
headers: { Authorization: `Bearer ${auth.token}` },
|
|
699
|
+
});
|
|
700
|
+
if (res.ok) {
|
|
701
|
+
const serverConfig = await res.json();
|
|
702
|
+
let updated = false;
|
|
703
|
+
if (serverConfig.providers && config.providers?.some(p => p.authType === 'oauth')) {
|
|
704
|
+
for (const sp of serverConfig.providers) {
|
|
705
|
+
if (sp.connectionType !== 'oauth' || !sp.access_token)
|
|
706
|
+
continue;
|
|
707
|
+
const local = config.providers.find(p => p.id === sp.id);
|
|
708
|
+
if (!local || local.authType !== 'oauth')
|
|
709
|
+
continue;
|
|
710
|
+
// Only update if server has a different (newer) token
|
|
711
|
+
if (local.oauthToken !== sp.access_token) {
|
|
712
|
+
local.oauthToken = sp.access_token;
|
|
713
|
+
local.oauthRefreshToken = sp.refresh_token;
|
|
714
|
+
local.oauthExpiresAt = sp.expires_at;
|
|
715
|
+
updated = true;
|
|
716
|
+
console.log(chalk_1.default.green(`✓ Updated ${sp.id} OAuth token from server`));
|
|
406
717
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (runtimeConnectionMode === 'server' && Array.isArray(serverConfig.bots) && serverConfig.bots.length > 0) {
|
|
721
|
+
const nextAgents = mapServerBotsToAgents(serverConfig.bots, config.agents || []);
|
|
722
|
+
const sameAgents = JSON.stringify(config.agents || []) === JSON.stringify(nextAgents);
|
|
723
|
+
if (!sameAgents) {
|
|
724
|
+
config.agents = nextAgents;
|
|
725
|
+
updated = true;
|
|
726
|
+
console.log(chalk_1.default.green(`✓ Synced ${nextAgents.length} bot profile(s) from server`));
|
|
727
|
+
}
|
|
728
|
+
const selectedActiveAgentId = selectPreferredActiveAgentId(nextAgents, config.providers, config.activeAgentId);
|
|
729
|
+
if (selectedActiveAgentId && selectedActiveAgentId !== config.activeAgentId) {
|
|
730
|
+
config.activeAgentId = selectedActiveAgentId;
|
|
731
|
+
updated = true;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (updated) {
|
|
735
|
+
(0, config_1.saveConfig)(config);
|
|
736
|
+
// Also update agent config files on disk for OAuth providers
|
|
737
|
+
for (const p of config.providers || []) {
|
|
738
|
+
if (p.authType !== 'oauth' || !p.oauthToken)
|
|
739
|
+
continue;
|
|
740
|
+
const { listAgents, loadAgentConfig: loadAC, saveAgentConfig: saveAC } = await Promise.resolve().then(() => __importStar(require('../agent-config')));
|
|
741
|
+
for (const agentName of listAgents()) {
|
|
742
|
+
const ac = loadAC(agentName);
|
|
743
|
+
if (ac && ac.provider === p.id && ac.oauthToken) {
|
|
744
|
+
ac.oauthToken = p.oauthToken;
|
|
745
|
+
ac.oauthRefreshToken = p.oauthRefreshToken;
|
|
746
|
+
ac.oauthExpiresAt = p.oauthExpiresAt;
|
|
747
|
+
saveAC(agentName, ac);
|
|
423
748
|
}
|
|
424
749
|
}
|
|
425
750
|
}
|
|
426
751
|
}
|
|
427
752
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
753
|
+
}
|
|
754
|
+
catch (err) {
|
|
755
|
+
console.log(chalk_1.default.gray(` Server config re-sync skipped: ${err.message}`));
|
|
431
756
|
}
|
|
432
757
|
// Load agent-specific config if --agent was specified
|
|
433
758
|
const agentLocalConfig = options.agent ? (0, agent_config_1.loadAgentConfig)(options.agent) : null;
|
|
@@ -552,8 +877,10 @@ async function startCommand(projectDir, options) {
|
|
|
552
877
|
// Enforce strict subscription routing semantics.
|
|
553
878
|
if (effectiveAccessMode === 'openai_subscription') {
|
|
554
879
|
apiKey = undefined;
|
|
555
|
-
|
|
556
|
-
|
|
880
|
+
oauthToken = undefined;
|
|
881
|
+
// Preserve codex-cli when it was explicitly selected. The CLI manages its
|
|
882
|
+
// own auth/session state and should not be rewritten into the raw OpenAI
|
|
883
|
+
// API path during local-agent startup.
|
|
557
884
|
}
|
|
558
885
|
if (effectiveAccessMode === 'anthropic_subscription') {
|
|
559
886
|
provider = 'claude-cli';
|
|
@@ -563,10 +890,17 @@ async function startCommand(projectDir, options) {
|
|
|
563
890
|
model = 'claude-code';
|
|
564
891
|
}
|
|
565
892
|
}
|
|
566
|
-
// Auto-detect Claude Code CLI
|
|
893
|
+
// Auto-detect Claude Code CLI only when the provider was not explicitly chosen.
|
|
894
|
+
// Otherwise we can accidentally rewrite an explicit Codex/OpenAI bot selection
|
|
895
|
+
// into Claude just because no API key was passed on the outer config object.
|
|
896
|
+
const providerWasExplicit = !!(options.provider ||
|
|
897
|
+
agentLocalConfig?.runtimeProvider ||
|
|
898
|
+
activeAgentConfig?.runtimeProvider ||
|
|
899
|
+
agentLocalConfig?.provider ||
|
|
900
|
+
activeAgentConfig?.provider);
|
|
567
901
|
// OAuth tokens from claude.ai can't be used with the Anthropic API directly,
|
|
568
902
|
// so we prefer claude-cli when Claude Code is installed.
|
|
569
|
-
if (!apiKey && !oauthToken && !
|
|
903
|
+
if (!apiKey && !oauthToken && !providerWasExplicit && provider !== 'claude-cli') {
|
|
570
904
|
const claudeVersion = detectClaudeCli();
|
|
571
905
|
if (claudeVersion) {
|
|
572
906
|
console.log(chalk_1.default.green(`✓ Claude Code detected (${claudeVersion}), using your subscription`));
|
|
@@ -642,6 +976,9 @@ async function startCommand(projectDir, options) {
|
|
|
642
976
|
const dbInfo = (0, local_db_1.initLocalDb)();
|
|
643
977
|
console.log(chalk_1.default.green(`✓ Local DB ready: ${dbInfo.path}`));
|
|
644
978
|
console.log(chalk_1.default.gray(` WAL mode: ${dbInfo.walMode}, tables: ${dbInfo.tableCount}, write: ${dbInfo.testWrite}, read: ${dbInfo.testRead}`));
|
|
979
|
+
if (dbInfo.claudeSessionCounterSync?.updated) {
|
|
980
|
+
console.log(chalk_1.default.gray(` Claude session counter advanced from ${dbInfo.claudeSessionCounterSync.dbValueBefore} to ${dbInfo.claudeSessionCounterSync.dbValueAfter} to match existing ~/.claude Funolio sessions`));
|
|
981
|
+
}
|
|
645
982
|
cleanupExpiredMessageActivityRows();
|
|
646
983
|
if (isServiceMode) {
|
|
647
984
|
(0, service_mode_1.emitEvent)('local_db', { status: 'ready', path: dbInfo.path, walMode: dbInfo.walMode });
|
|
@@ -692,6 +1029,10 @@ async function startCommand(projectDir, options) {
|
|
|
692
1029
|
authToken: auth.token,
|
|
693
1030
|
refreshUrl: `${config_1.FUNOLIO_API_URL}/api/v1/agent/auth/refresh`,
|
|
694
1031
|
});
|
|
1032
|
+
(0, chat_sync_1.configureChatSyncPublisher)({
|
|
1033
|
+
userId: auth.userId,
|
|
1034
|
+
publish: (topic, payload, opts) => mqttClient.publish(topic, payload, opts),
|
|
1035
|
+
});
|
|
695
1036
|
// Create and auto-launch MCP manager for persistent tool discovery
|
|
696
1037
|
const { MCPManager } = await Promise.resolve().then(() => __importStar(require('../mcp/manager')));
|
|
697
1038
|
const mcpManager = new MCPManager();
|
|
@@ -720,6 +1061,7 @@ async function startCommand(projectDir, options) {
|
|
|
720
1061
|
});
|
|
721
1062
|
let maintenanceTimer = null;
|
|
722
1063
|
let maintenanceRestartPending = false;
|
|
1064
|
+
const processStartAtMs = Date.now();
|
|
723
1065
|
// Handle shutdown
|
|
724
1066
|
const shutdown = async () => {
|
|
725
1067
|
console.log(chalk_1.default.yellow('\nShutting down...'));
|
|
@@ -832,6 +1174,9 @@ async function startCommand(projectDir, options) {
|
|
|
832
1174
|
console.log(chalk_1.default.gray(' This terminal can stay open or run in background.\n'));
|
|
833
1175
|
// Start the active agent loop (driven by agents[] + activeAgentId)
|
|
834
1176
|
await botManager.startActive();
|
|
1177
|
+
// Materialize configured server bots into local agent files so explicit
|
|
1178
|
+
// bot routing can resolve botId -> loop mappings after restarts.
|
|
1179
|
+
syncConfiguredAgentsToLocalConfigs(config.agents);
|
|
835
1180
|
// Start all additional bot loops from ~/.funolio/agents/ configs
|
|
836
1181
|
await botManager.startAllBots();
|
|
837
1182
|
try {
|
|
@@ -849,7 +1194,7 @@ async function startCommand(projectDir, options) {
|
|
|
849
1194
|
const now = new Date();
|
|
850
1195
|
const lastInteractionAtMs = readLastInteractionAtMs();
|
|
851
1196
|
const lastMaintenanceRestartAtMs = readLastMaintenanceRestartAtMs();
|
|
852
|
-
if (!shouldRunNightlyRestart(now, lastInteractionAtMs, lastMaintenanceRestartAtMs, botManager))
|
|
1197
|
+
if (!shouldRunNightlyRestart(now, lastInteractionAtMs, lastMaintenanceRestartAtMs, botManager, processStartAtMs))
|
|
853
1198
|
return;
|
|
854
1199
|
maintenanceRestartPending = true;
|
|
855
1200
|
markMaintenanceRestartAt(now);
|
|
@@ -868,7 +1213,10 @@ async function startCommand(projectDir, options) {
|
|
|
868
1213
|
if (isServiceMode) {
|
|
869
1214
|
(0, service_mode_1.emitEvent)('agent_restart', { reason });
|
|
870
1215
|
}
|
|
871
|
-
|
|
1216
|
+
// Exit with EX_TEMPFAIL (75) so systemd's `Restart=on-failure` triggers a
|
|
1217
|
+
// restart. `process.exit(0)` is treated as clean and would leave the agent
|
|
1218
|
+
// dead until a human starts it (observed incident: ~2 days of downtime).
|
|
1219
|
+
setTimeout(() => process.exit(75), 1500);
|
|
872
1220
|
}, MAINTENANCE_INTERVAL_MS);
|
|
873
1221
|
if (config.agents && config.agents.length > 1) {
|
|
874
1222
|
console.log(chalk_1.default.blue(` ${config.agents.length} agent(s) configured (active: ${activeAgentConfig?.name || 'default'})`));
|
|
@@ -899,6 +1247,71 @@ async function startCommand(projectDir, options) {
|
|
|
899
1247
|
}
|
|
900
1248
|
}
|
|
901
1249
|
console.log(chalk_1.default.gray(' Waiting for commands...\n'));
|
|
1250
|
+
// Subscribe to team-pool request topics for every provider instance this user hosts.
|
|
1251
|
+
// Without this, V2 TeamBot chats routed through the team pool never reach this agent
|
|
1252
|
+
// and time out at 120s. Previously this subscription was only done by the dedicated
|
|
1253
|
+
// `funolio-agent pool provide` command, not by `start --mode service`.
|
|
1254
|
+
setupPoolSubscriptions({
|
|
1255
|
+
apiUrl: config_1.FUNOLIO_API_URL,
|
|
1256
|
+
authToken: auth.token,
|
|
1257
|
+
mqttClient,
|
|
1258
|
+
config,
|
|
1259
|
+
}).catch((err) => {
|
|
1260
|
+
console.error(chalk_1.default.yellow(`⚠ Pool subscription setup failed: ${err?.message || err}`));
|
|
1261
|
+
});
|
|
1262
|
+
// Keep liveness fresh: re-poll /api/v1/bot/pool-subscriptions every 2 minutes.
|
|
1263
|
+
// The GET side-effects a `lastSeenAt = NOW()` update on all of this user's
|
|
1264
|
+
// TeamLlmProviderInstance rows. Without this, the server's V2 completions
|
|
1265
|
+
// route fast-fails with 503 NO_RUNTIME because the staleness check
|
|
1266
|
+
// (>5 min = offline) trips — even while the agent is actively subscribed
|
|
1267
|
+
// to the pool topic.
|
|
1268
|
+
//
|
|
1269
|
+
// The re-poll also picks up any new teams the user joined after the agent
|
|
1270
|
+
// started: fetchPoolSubscriptions returns the up-to-date set, so any newly
|
|
1271
|
+
// added instance gets subscribed on the next tick.
|
|
1272
|
+
const POOL_LIVENESS_INTERVAL_MS = 2 * 60 * 1000;
|
|
1273
|
+
setInterval(() => {
|
|
1274
|
+
fetchPoolSubscriptions(config_1.FUNOLIO_API_URL, auth.token)
|
|
1275
|
+
.then(() => {
|
|
1276
|
+
// Discovery of brand-new team memberships is handled in a later
|
|
1277
|
+
// iteration; for now the GET alone is enough to keep liveness fresh.
|
|
1278
|
+
})
|
|
1279
|
+
.catch((err) => {
|
|
1280
|
+
// Non-fatal — next tick will retry. Don't spam logs.
|
|
1281
|
+
if (process.env.FUNOLIO_AGENT_DEBUG === 'true') {
|
|
1282
|
+
console.error(chalk_1.default.gray(` [pool-liveness] refresh failed: ${err?.message || err}`));
|
|
1283
|
+
}
|
|
1284
|
+
});
|
|
1285
|
+
}, POOL_LIVENESS_INTERVAL_MS).unref();
|
|
1286
|
+
// Sync approved MCP installations from the cloud. The user approves MCPs
|
|
1287
|
+
// via the web UI (stored in UserMcpServer rows server-side), but the
|
|
1288
|
+
// agent's MCP manager only auto-launches from ~/.funolio/mcp-servers.json
|
|
1289
|
+
// which is never populated on VM/remote agent installs. Without this
|
|
1290
|
+
// sync, users who approved Google Workspace/Drive/Gmail/Chat via the web
|
|
1291
|
+
// see `mcp=0` in the tool filter at startup and tool calls fail.
|
|
1292
|
+
syncCloudMcpInstallations({
|
|
1293
|
+
apiUrl: config_1.FUNOLIO_API_URL,
|
|
1294
|
+
authToken: auth.token,
|
|
1295
|
+
mcpManager,
|
|
1296
|
+
}).catch((err) => {
|
|
1297
|
+
console.error(chalk_1.default.yellow(`⚠ MCP installation sync failed: ${err?.message || err}`));
|
|
1298
|
+
});
|
|
1299
|
+
// Re-sync every 10 minutes so newly approved MCPs show up and Google
|
|
1300
|
+
// tokens get refreshed before they expire (GOOGLE_REFRESH_BUFFER_MS on
|
|
1301
|
+
// the server is 5 min; polling at 10 min is well inside the safety
|
|
1302
|
+
// margin of a typical 1-hour access token TTL).
|
|
1303
|
+
const MCP_SYNC_INTERVAL_MS = 10 * 60 * 1000;
|
|
1304
|
+
setInterval(() => {
|
|
1305
|
+
syncCloudMcpInstallations({
|
|
1306
|
+
apiUrl: config_1.FUNOLIO_API_URL,
|
|
1307
|
+
authToken: auth.token,
|
|
1308
|
+
mcpManager,
|
|
1309
|
+
}).catch((err) => {
|
|
1310
|
+
if (process.env.FUNOLIO_AGENT_DEBUG === 'true') {
|
|
1311
|
+
console.error(chalk_1.default.gray(` [mcp-sync] refresh failed: ${err?.message || err}`));
|
|
1312
|
+
}
|
|
1313
|
+
});
|
|
1314
|
+
}, MCP_SYNC_INTERVAL_MS).unref();
|
|
902
1315
|
// OAuth token refresh is handled per-request in message-loop.ts via ensureFreshToken()
|
|
903
1316
|
// No background timer needed — this matches the pi-ai/Claude Code CLI pattern of
|
|
904
1317
|
// refreshing lazily before each API call, avoiding race conditions on single-use refresh tokens
|
|
@@ -921,6 +1334,14 @@ async function startCommand(projectDir, options) {
|
|
|
921
1334
|
// Start listening for commands — BotManager routes to the right bot
|
|
922
1335
|
// Also handle MCP marketplace install/uninstall commands
|
|
923
1336
|
mqttClient.onCommand(async (message) => {
|
|
1337
|
+
// Local-DB data relay (mobile1.txt Phase 1). When the cloud needs to
|
|
1338
|
+
// serve a personal-desktop user's data via mobile/web, it publishes a
|
|
1339
|
+
// data_request command here; we forward to the local HTTP server and
|
|
1340
|
+
// reply on the results topic. Always handle this BEFORE existing
|
|
1341
|
+
// command dispatch since it's its own message type.
|
|
1342
|
+
if (await (0, mqtt_data_relay_1.handleDataRequestMessage)(message, mqttClient)) {
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
924
1345
|
if (message.type === 'command' && message.action === 'mcp_install') {
|
|
925
1346
|
const { serverId, envVars } = message;
|
|
926
1347
|
console.log(chalk_1.default.cyan(`📦 Marketplace: installing MCP server "${serverId}"...`));
|