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/local-server.js
CHANGED
|
@@ -77,6 +77,8 @@ const orchestrator_profile_1 = require("./orchestrator-profile");
|
|
|
77
77
|
const policy_detection_1 = require("./policy-detection");
|
|
78
78
|
const server_runtime_1 = require("./server-runtime");
|
|
79
79
|
const storage_mode_1 = require("./storage-mode");
|
|
80
|
+
const local_cli_pty_manager_1 = require("./local-cli-pty-manager");
|
|
81
|
+
const cli_session_epoch_1 = require("./cli-session-epoch");
|
|
80
82
|
const server_adapter_1 = require("./server-adapter");
|
|
81
83
|
const wizard_support_1 = require("./wizard-support");
|
|
82
84
|
const chalk_1 = __importDefault(require("chalk"));
|
|
@@ -1828,7 +1830,7 @@ function startLocalServer(opts) {
|
|
|
1828
1830
|
if (msgs.length < 3)
|
|
1829
1831
|
return res.json({ suggestion: null, reason: 'Not enough messages' });
|
|
1830
1832
|
const conv = data.getConversation(conversationId);
|
|
1831
|
-
const clerk = (0, clerk_model_1.getClerk)();
|
|
1833
|
+
const clerk = (0, clerk_model_1.getClerk)({ runtimeMode: 'local_desktop' });
|
|
1832
1834
|
if (!clerk) {
|
|
1833
1835
|
// Fallback: use first few words of first user message
|
|
1834
1836
|
const firstUser = msgs.find(m => m.role === 'user');
|
|
@@ -2037,6 +2039,28 @@ function startLocalServer(opts) {
|
|
|
2037
2039
|
res.status(500).json({ error: err.message });
|
|
2038
2040
|
}
|
|
2039
2041
|
});
|
|
2042
|
+
app.patch('/api/messages/:id', (req, res) => {
|
|
2043
|
+
try {
|
|
2044
|
+
if (isConnectedMode()) {
|
|
2045
|
+
return res.status(501).json({ error: 'Message updates are local-mode only' });
|
|
2046
|
+
}
|
|
2047
|
+
const updated = data.updateMessage(req.params.id, {
|
|
2048
|
+
content: req.body?.content,
|
|
2049
|
+
model: req.body?.model,
|
|
2050
|
+
botId: req.body?.botId,
|
|
2051
|
+
agentName: req.body?.agentName,
|
|
2052
|
+
resultArtifact: req.body?.resultArtifact,
|
|
2053
|
+
resultSummary: req.body?.resultSummary,
|
|
2054
|
+
resultStatus: req.body?.resultStatus,
|
|
2055
|
+
});
|
|
2056
|
+
if (!updated)
|
|
2057
|
+
return res.status(404).json({ error: 'Message not found' });
|
|
2058
|
+
res.json(updated);
|
|
2059
|
+
}
|
|
2060
|
+
catch (err) {
|
|
2061
|
+
res.status(500).json({ error: err.message });
|
|
2062
|
+
}
|
|
2063
|
+
});
|
|
2040
2064
|
app.get('/api/conversations/:id/orchestration-audit', (req, res) => {
|
|
2041
2065
|
try {
|
|
2042
2066
|
if (isConnectedMode()) {
|
|
@@ -2327,7 +2351,13 @@ function startLocalServer(opts) {
|
|
|
2327
2351
|
const limit = parseInt(req.query.limit, 10) || 25;
|
|
2328
2352
|
const beforeSeq = req.query.beforeSeq ? parseInt(req.query.beforeSeq, 10) : 0;
|
|
2329
2353
|
const rounds = req.query.rounds ? parseInt(req.query.rounds, 10) : 0;
|
|
2354
|
+
const startSeq = req.query.startSeq ? parseInt(req.query.startSeq, 10) : 0;
|
|
2355
|
+
const endSeq = req.query.endSeq ? parseInt(req.query.endSeq, 10) : 0;
|
|
2356
|
+
const hasDirectRange = startSeq > 0 && endSeq >= startSeq;
|
|
2330
2357
|
if (isConnectedMode()) {
|
|
2358
|
+
if (hasDirectRange) {
|
|
2359
|
+
return res.status(400).json({ error: 'Direct message range fetch is only available in local storage mode' });
|
|
2360
|
+
}
|
|
2331
2361
|
const runtime = (0, server_runtime_1.getRuntimeConnectionConfig)();
|
|
2332
2362
|
const auth = await getHydratedDesktopAuth();
|
|
2333
2363
|
const result = await (0, server_adapter_1.listServerConversationMessages)(auth, runtime, req.params.id, {
|
|
@@ -2336,6 +2366,9 @@ function startLocalServer(opts) {
|
|
|
2336
2366
|
});
|
|
2337
2367
|
return res.json(result.messages);
|
|
2338
2368
|
}
|
|
2369
|
+
if (hasDirectRange) {
|
|
2370
|
+
return res.json(data.getMessagesInRange(req.params.id, startSeq, endSeq));
|
|
2371
|
+
}
|
|
2339
2372
|
if (beforeSeq > 0) {
|
|
2340
2373
|
// Backward paging: get N rounds or messages before given seq, returned in ASC order
|
|
2341
2374
|
const msgs = rounds > 0
|
|
@@ -2552,10 +2585,13 @@ function startLocalServer(opts) {
|
|
|
2552
2585
|
const activityErrorContext = {};
|
|
2553
2586
|
const routeAbortController = new AbortController();
|
|
2554
2587
|
let responseEnded = false;
|
|
2555
|
-
const abortOnClientClose = () =>
|
|
2588
|
+
const abortOnClientClose = () => {
|
|
2589
|
+
responseEnded = true;
|
|
2590
|
+
routeAbortController.abort();
|
|
2591
|
+
};
|
|
2556
2592
|
req.on('close', abortOnClientClose);
|
|
2557
2593
|
try {
|
|
2558
|
-
|
|
2594
|
+
let { conversationId, message, botId, skipUserMessage, pinnedMessageIds, topicId, projectId, workflowTemplateId, orchestrationEnabled, chatJobId, assistantMessageId, persistAssistantPlaceholder, } = req.body;
|
|
2559
2595
|
if (!message)
|
|
2560
2596
|
return res.status(400).json({ error: 'message is required' });
|
|
2561
2597
|
if (await relayConnectedChat(req, res)) {
|
|
@@ -2613,6 +2649,7 @@ function startLocalServer(opts) {
|
|
|
2613
2649
|
const activityExpiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().replace('T', ' ').replace('Z', '');
|
|
2614
2650
|
activityErrorContext.conversationId = convId;
|
|
2615
2651
|
activityErrorContext.streamId = activityStreamId;
|
|
2652
|
+
activityErrorContext.messageId = assistantMessageId ? String(assistantMessageId) : null;
|
|
2616
2653
|
activityErrorContext.botId = profile?.id ?? null;
|
|
2617
2654
|
activityErrorContext.agentName = profile?.name ?? null;
|
|
2618
2655
|
activityErrorContext.expiresAt = activityExpiresAt;
|
|
@@ -2620,6 +2657,7 @@ function startLocalServer(opts) {
|
|
|
2620
2657
|
try {
|
|
2621
2658
|
data.createMessageActivity({
|
|
2622
2659
|
conversationId: convId,
|
|
2660
|
+
messageId: assistantMessageId ? String(assistantMessageId) : null,
|
|
2623
2661
|
streamId: activityStreamId,
|
|
2624
2662
|
botId: profile?.id ?? null,
|
|
2625
2663
|
agentName: profile?.name ?? null,
|
|
@@ -2649,6 +2687,7 @@ function startLocalServer(opts) {
|
|
|
2649
2687
|
try {
|
|
2650
2688
|
data.createMessageActivity({
|
|
2651
2689
|
conversationId: convId,
|
|
2690
|
+
messageId: assistantMessageId ? String(assistantMessageId) : null,
|
|
2652
2691
|
streamId: activityStreamId,
|
|
2653
2692
|
botId: resolveWorkerBotId(event.agentName) || null,
|
|
2654
2693
|
agentName: event.agentName || null,
|
|
@@ -2671,7 +2710,7 @@ function startLocalServer(opts) {
|
|
|
2671
2710
|
const effectiveProjectId = projectId ? String(projectId) : (convForPolicy?.project_id || undefined);
|
|
2672
2711
|
const projectForPolicy = effectiveProjectId ? data.getProject(effectiveProjectId) : undefined;
|
|
2673
2712
|
const currentPolicy = data.getEffectiveOrchestrationPolicy(effectiveProjectId);
|
|
2674
|
-
const clerkForPolicy = (0, clerk_model_1.getClerk)();
|
|
2713
|
+
const clerkForPolicy = (0, clerk_model_1.getClerk)({ runtimeMode: 'local_desktop' });
|
|
2675
2714
|
const agentNames = data.listAgentProfiles().map((agent) => agent.name);
|
|
2676
2715
|
if (clerkForPolicy && savedUserMessage) {
|
|
2677
2716
|
void (0, policy_detection_1.stagePolicyDetectionForMessage)({
|
|
@@ -2690,10 +2729,14 @@ function startLocalServer(opts) {
|
|
|
2690
2729
|
});
|
|
2691
2730
|
}
|
|
2692
2731
|
}
|
|
2732
|
+
if (!assistantMessageId && persistAssistantPlaceholder === true) {
|
|
2733
|
+
const placeholder = data.addMessage(convId, 'assistant', '', buildConfiguredMessageModel(profile), undefined, profile?.id || undefined, profile?.name || undefined);
|
|
2734
|
+
assistantMessageId = placeholder.id;
|
|
2735
|
+
}
|
|
2693
2736
|
// ─── Orchestrator Mode Branch ─────────────────────────
|
|
2694
2737
|
const shouldUseOrchestratorMode = orchestrationEnabled !== false && (0, orchestrator_profile_1.isOrchestratorProfile)(profile);
|
|
2695
2738
|
if (shouldUseOrchestratorMode) {
|
|
2696
|
-
const clerk = (0, clerk_model_1.getClerk)();
|
|
2739
|
+
const clerk = (0, clerk_model_1.getClerk)({ runtimeMode: 'local_desktop' });
|
|
2697
2740
|
if (!clerk) {
|
|
2698
2741
|
// Fix #2: Do not silently fall through to direct chat — return a clear error
|
|
2699
2742
|
return res.status(400).json({
|
|
@@ -2702,7 +2745,7 @@ function startLocalServer(opts) {
|
|
|
2702
2745
|
}
|
|
2703
2746
|
const { OrchestratorAgent } = require('./orchestrator');
|
|
2704
2747
|
const { getWorkflowEngine } = require('./workflow-engine');
|
|
2705
|
-
const workflowEngine = getWorkflowEngine(opts.projectDir);
|
|
2748
|
+
const workflowEngine = getWorkflowEngine(opts.projectDir, 'local_desktop');
|
|
2706
2749
|
const orchestrator = new OrchestratorAgent(clerk, workflowEngine);
|
|
2707
2750
|
// Resolve effective project ID from request or existing conversation
|
|
2708
2751
|
const conv = data.getConversation(convId);
|
|
@@ -2732,7 +2775,7 @@ function startLocalServer(opts) {
|
|
|
2732
2775
|
orchestratorRuntime.model || profile.model || '',
|
|
2733
2776
|
runtimeModeLabel(orchestratorRuntime.runtimeMode, orchestratorRuntime.runtimeSource),
|
|
2734
2777
|
].filter(Boolean).join(' | ');
|
|
2735
|
-
orchestratorRuntimePayload = runtimePayloadForDisplay(profile.provider, orchestratorRuntime.model || profile.model || null, orchestratorRuntime.runtimeMode, orchestratorRuntime.runtimeSource || null
|
|
2778
|
+
orchestratorRuntimePayload = runtimePayloadForDisplay(profile.provider, orchestratorRuntime.model || profile.model || null, orchestratorRuntime.runtimeMode, orchestratorRuntime.runtimeSource || null);
|
|
2736
2779
|
}
|
|
2737
2780
|
catch {
|
|
2738
2781
|
orchestratorRuntimeLabel = buildConfiguredMessageModel(profile);
|
|
@@ -2992,7 +3035,7 @@ function startLocalServer(opts) {
|
|
|
2992
3035
|
? configuredTz
|
|
2993
3036
|
: Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
2994
3037
|
const unrestrictedCliProfile = index_1.CLI_PROVIDERS.has(profile.provider);
|
|
2995
|
-
const allToolDefs = (0, index_2.getAllToolDefinitions)(mcpManager);
|
|
3038
|
+
const allToolDefs = (0, index_2.getAllToolDefinitions)('local_desktop', mcpManager);
|
|
2996
3039
|
const configuredBuiltinTools = parseToolSelectionJson(profile.enabled_builtin_tools_json);
|
|
2997
3040
|
const configuredMcpTools = parseToolSelectionJson(profile.enabled_mcp_tools_json);
|
|
2998
3041
|
const allowedToolNames = unrestrictedCliProfile
|
|
@@ -3002,7 +3045,7 @@ function startLocalServer(opts) {
|
|
|
3002
3045
|
// Build system prompt via clerk (token-budgeted context injection)
|
|
3003
3046
|
let systemPrompt;
|
|
3004
3047
|
let llmSpawnCwd = opts.projectDir;
|
|
3005
|
-
const clerk = (0, clerk_model_1.getClerk)();
|
|
3048
|
+
const clerk = (0, clerk_model_1.getClerk)({ runtimeMode: 'local_desktop' });
|
|
3006
3049
|
if (clerk) {
|
|
3007
3050
|
const conv = data.getConversation(convId);
|
|
3008
3051
|
const topicTitle = topicId ? data.getTopic(topicId)?.title : undefined;
|
|
@@ -3028,9 +3071,10 @@ function startLocalServer(opts) {
|
|
|
3028
3071
|
else {
|
|
3029
3072
|
// Fallback: manual prompt building
|
|
3030
3073
|
systemPrompt = '[Bot Identity]\n' + (profile.soul_md
|
|
3031
|
-
|| 'You are
|
|
3074
|
+
|| 'You are an AI assistant running locally. You have access to project files and can execute code.');
|
|
3032
3075
|
systemPrompt += '\n\nDo not end with a deferred promise (for example: "Let me check..."). Return a final answer in this turn, or state exactly what is unavailable.';
|
|
3033
3076
|
systemPrompt += '\n\nWhen [Project Overview] is present, treat Project/Topic/Workspace values there as authoritative for the current turn and override stale prior-chat claims.';
|
|
3077
|
+
systemPrompt += '\n\n[Response Style]\nWrite in short readable paragraphs. Put a blank line between distinct ideas. Use bullets when listing findings, steps, or issues. Do not return one dense wall of text. For progress updates, keep them compact and clearly separate what you checked, what you found, and what you are doing next.';
|
|
3034
3078
|
const convForFallback = data.getConversation(convId);
|
|
3035
3079
|
const projectForFallback = convForFallback?.project_id ? data.getProject(convForFallback.project_id) : undefined;
|
|
3036
3080
|
const workspaceForFallback = projectForFallback?.folder?.trim();
|
|
@@ -3101,18 +3145,25 @@ function startLocalServer(opts) {
|
|
|
3101
3145
|
let activeRuntimeMode = runtime.runtimeMode;
|
|
3102
3146
|
let activeRuntimeSource = runtime.runtimeSource;
|
|
3103
3147
|
let activeIsCliProvider = index_1.CLI_PROVIDERS.has(activeProviderName);
|
|
3104
|
-
const cliFallback = runtime.cliFallback;
|
|
3105
|
-
const apiKeyFallback = runtime.apiKeyFallback;
|
|
3106
|
-
let switchedToCliFallback = false;
|
|
3107
|
-
let switchedToApiKeyFallback = false;
|
|
3108
3148
|
const runtimePayload = () => ({
|
|
3109
3149
|
mode: activeRuntimeMode,
|
|
3110
3150
|
modeLabel: runtimeModeLabel(activeRuntimeMode, activeRuntimeSource),
|
|
3111
3151
|
provider: activeProviderName,
|
|
3112
3152
|
model: activeModelName || null,
|
|
3113
3153
|
source: activeRuntimeSource || null,
|
|
3114
|
-
fallbackUsed: switchedToCliFallback || switchedToApiKeyFallback,
|
|
3115
3154
|
});
|
|
3155
|
+
const enableCliSessionEpoch = activeIsCliProvider
|
|
3156
|
+
&& !shouldUseOrchestratorMode
|
|
3157
|
+
&& !workflowTemplateId
|
|
3158
|
+
&& !!convId
|
|
3159
|
+
&& !!profile?.id;
|
|
3160
|
+
const cliSessionEpochPlan = enableCliSessionEpoch
|
|
3161
|
+
? (0, cli_session_epoch_1.selectCliSessionEpoch)(convId, profile.id, activeProviderName)
|
|
3162
|
+
: { existing: undefined, resumeSessionId: null, resetReason: null };
|
|
3163
|
+
let activeCliSessionId = cliSessionEpochPlan.resumeSessionId;
|
|
3164
|
+
const cliEpochStartedAt = cliSessionEpochPlan.resumeSessionId
|
|
3165
|
+
? (cliSessionEpochPlan.existing?.epoch_started_at || localTimestamp())
|
|
3166
|
+
: localTimestamp();
|
|
3116
3167
|
if (!activeApiKey) {
|
|
3117
3168
|
return res.status(400).json({ error: `No API key for provider ${profile.provider}. Configure one in Settings.` });
|
|
3118
3169
|
}
|
|
@@ -3120,6 +3171,7 @@ function startLocalServer(opts) {
|
|
|
3120
3171
|
projectId: convId ? (data.getConversation(convId)?.project_id ?? null) : null,
|
|
3121
3172
|
actorType: 'llm',
|
|
3122
3173
|
actorId: profile?.name || profile?.id || 'LLM',
|
|
3174
|
+
runtimeMode: 'local_desktop',
|
|
3123
3175
|
restrictFileAccessToProject: unrestrictedCliProfile ? false : undefined,
|
|
3124
3176
|
abortSignal: routeAbortController.signal,
|
|
3125
3177
|
});
|
|
@@ -3180,6 +3232,7 @@ function startLocalServer(opts) {
|
|
|
3180
3232
|
sendEvent('meta', {
|
|
3181
3233
|
conversationId: convId,
|
|
3182
3234
|
botId: profile.id,
|
|
3235
|
+
assistantMessageId: assistantMessageId || null,
|
|
3183
3236
|
runtime: runtimePayload(),
|
|
3184
3237
|
tokenUsage: {
|
|
3185
3238
|
approxInputTokens,
|
|
@@ -3197,6 +3250,23 @@ function startLocalServer(opts) {
|
|
|
3197
3250
|
detail: `Sending request to ${activeProviderName}...`,
|
|
3198
3251
|
runtime: runtimePayload(),
|
|
3199
3252
|
}, `Sending request to ${activeProviderName}...`);
|
|
3253
|
+
if (cliSessionEpochPlan.resetReason && enableCliSessionEpoch) {
|
|
3254
|
+
const resetDetail = cliSessionEpochPlan.resetReason === 'turn_limit'
|
|
3255
|
+
? 'Resetting CLI session after reaching the turn limit.'
|
|
3256
|
+
: cliSessionEpochPlan.resetReason === 'token_limit'
|
|
3257
|
+
? 'Resetting CLI session after reaching the context budget.'
|
|
3258
|
+
: 'Resetting CLI session because the runtime changed.';
|
|
3259
|
+
sendEvent('status', {
|
|
3260
|
+
phase: 'thinking',
|
|
3261
|
+
detail: resetDetail,
|
|
3262
|
+
runtime: runtimePayload(),
|
|
3263
|
+
});
|
|
3264
|
+
recordActivity('status', {
|
|
3265
|
+
phase: 'thinking',
|
|
3266
|
+
detail: resetDetail,
|
|
3267
|
+
runtime: runtimePayload(),
|
|
3268
|
+
}, resetDetail);
|
|
3269
|
+
}
|
|
3200
3270
|
let partialPersistedContent = '';
|
|
3201
3271
|
let partialPersistedAt = 0;
|
|
3202
3272
|
const throwIfChatJobCancelled = () => {
|
|
@@ -3245,265 +3315,259 @@ function startLocalServer(opts) {
|
|
|
3245
3315
|
// Thinking/reasoning accumulator across multi-turn tool loops
|
|
3246
3316
|
let accumulatedThinking = '';
|
|
3247
3317
|
const thinkingEnabled = !!profile?.show_thinking;
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3318
|
+
let useInteractiveCliSession = enableCliSessionEpoch;
|
|
3319
|
+
if (useInteractiveCliSession) {
|
|
3320
|
+
const ptyManager = (0, local_cli_pty_manager_1.getLocalCliPtySessionManager)();
|
|
3321
|
+
let ptyAttempt = 0;
|
|
3322
|
+
while (true) {
|
|
3323
|
+
ptyAttempt++;
|
|
3324
|
+
try {
|
|
3325
|
+
const result = await ptyManager.runTurn({
|
|
3326
|
+
conversationId: convId,
|
|
3327
|
+
botId: profile.id,
|
|
3328
|
+
provider: activeProviderName,
|
|
3329
|
+
cwd: llmSpawnCwd,
|
|
3330
|
+
systemPrompt,
|
|
3331
|
+
messages: llmMessages,
|
|
3332
|
+
forceFreshSession: !cliSessionEpochPlan.resumeSessionId,
|
|
3333
|
+
onDetail: async (detail) => {
|
|
3334
|
+
sendEvent('status', {
|
|
3335
|
+
phase: 'thinking',
|
|
3336
|
+
detail,
|
|
3337
|
+
runtime: runtimePayload(),
|
|
3338
|
+
});
|
|
3339
|
+
recordActivity('status', {
|
|
3340
|
+
phase: 'thinking',
|
|
3341
|
+
detail,
|
|
3342
|
+
runtime: runtimePayload(),
|
|
3343
|
+
}, detail);
|
|
3344
|
+
},
|
|
3345
|
+
});
|
|
3346
|
+
if (result.sessionId) {
|
|
3347
|
+
activeCliSessionId = result.sessionId;
|
|
3271
3348
|
}
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
sendEvent('thinking_chunk', {
|
|
3280
|
-
text: chunk,
|
|
3281
|
-
botId: profile?.id || null,
|
|
3282
|
-
agentName: profile?.name || null,
|
|
3283
|
-
});
|
|
3284
|
-
},
|
|
3285
|
-
} : {}),
|
|
3286
|
-
};
|
|
3287
|
-
try {
|
|
3288
|
-
response = await activeLlm.chat(chatOptions);
|
|
3289
|
-
}
|
|
3290
|
-
catch (primaryErr) {
|
|
3291
|
-
if (routeAbortController.signal.aborted || primaryErr?.name === 'AbortError') {
|
|
3292
|
-
throw primaryErr;
|
|
3349
|
+
if (result.usage) {
|
|
3350
|
+
totalInputTokens += result.usage.inputTokens || 0;
|
|
3351
|
+
totalOutputTokens += result.usage.outputTokens || 0;
|
|
3352
|
+
hasExactUsage = true;
|
|
3353
|
+
}
|
|
3354
|
+
fullContent = (result.content || '').trim();
|
|
3355
|
+
break;
|
|
3293
3356
|
}
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
activeModelName = cliFallback.model;
|
|
3298
|
-
activeApiKey = cliFallback.apiKey;
|
|
3299
|
-
activeLlm = cliFallback.llm;
|
|
3300
|
-
activeRuntimeMode = cliFallback.runtimeMode;
|
|
3301
|
-
activeRuntimeSource = cliFallback.runtimeSource;
|
|
3302
|
-
activeIsCliProvider = true;
|
|
3303
|
-
const fallbackMsg = activeIsCliProvider
|
|
3304
|
-
? `CLI auth failed (${primaryErr?.message || primaryErr}); switching to fallback...`
|
|
3305
|
-
: `Primary provider failed (${primaryErr?.message || primaryErr}); switching to fallback...`;
|
|
3306
|
-
console.warn(chalk_1.default.yellow(` [chat] ${fallbackMsg}`));
|
|
3307
|
-
if (activeIsCliProvider) {
|
|
3308
|
-
console.warn(chalk_1.default.yellow(` [chat] If CLI auth keeps failing, run 'claude' or 'codex' in your terminal to re-authenticate.`));
|
|
3357
|
+
catch (ptyErr) {
|
|
3358
|
+
if (ptyAttempt >= LOCAL_RUNTIME_RETRY_LIMIT || !shouldRetrySelectedLocalRuntime(ptyErr)) {
|
|
3359
|
+
throw ptyErr;
|
|
3309
3360
|
}
|
|
3361
|
+
const retryDetail = `Selected runtime failed (${ptyErr?.message || ptyErr}); retrying the same connection (${ptyAttempt + 1}/${LOCAL_RUNTIME_RETRY_LIMIT})...`;
|
|
3362
|
+
console.warn(chalk_1.default.yellow(` [chat] ${retryDetail}`));
|
|
3310
3363
|
sendEvent('status', {
|
|
3311
3364
|
phase: 'thinking',
|
|
3312
|
-
detail:
|
|
3365
|
+
detail: retryDetail,
|
|
3313
3366
|
runtime: runtimePayload(),
|
|
3314
3367
|
});
|
|
3315
3368
|
recordActivity('status', {
|
|
3316
3369
|
phase: 'thinking',
|
|
3317
|
-
detail:
|
|
3370
|
+
detail: retryDetail,
|
|
3318
3371
|
runtime: runtimePayload(),
|
|
3319
|
-
},
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3372
|
+
}, retryDetail);
|
|
3373
|
+
await pauseLocalRuntimeRetry(ptyAttempt);
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
if (!useInteractiveCliSession)
|
|
3378
|
+
while (iteration < MAX_ITERATIONS) {
|
|
3379
|
+
iteration++;
|
|
3380
|
+
let iterationFirstChunk = true;
|
|
3381
|
+
throwIfChatJobCancelled();
|
|
3382
|
+
if (iteration > 1) {
|
|
3383
|
+
sendEvent('status', { phase: 'thinking', detail: 'Processing tool results...' });
|
|
3384
|
+
recordActivity('status', { phase: 'thinking', detail: 'Processing tool results...' }, 'Processing tool results...');
|
|
3385
|
+
}
|
|
3386
|
+
let response;
|
|
3387
|
+
const chatOptions = {
|
|
3388
|
+
messages: llmMessages,
|
|
3389
|
+
system: systemPrompt,
|
|
3390
|
+
stream: true,
|
|
3391
|
+
tools: toolDefs,
|
|
3392
|
+
cwd: llmSpawnCwd,
|
|
3393
|
+
abortSignal: routeAbortController.signal,
|
|
3394
|
+
resumeSessionId: enableCliSessionEpoch ? activeCliSessionId : null,
|
|
3395
|
+
persistSession: enableCliSessionEpoch,
|
|
3396
|
+
thinkingEnabled,
|
|
3397
|
+
onChunk: async (chunk) => {
|
|
3398
|
+
throwIfChatJobCancelled();
|
|
3399
|
+
if (iterationFirstChunk) {
|
|
3400
|
+
iterationFirstChunk = false;
|
|
3401
|
+
sendEvent('status', { phase: 'generating' });
|
|
3402
|
+
recordActivity('status', { phase: 'generating' }, 'Generating response...');
|
|
3403
|
+
}
|
|
3404
|
+
streamedAnyChunk = true;
|
|
3405
|
+
streamedContent += chunk;
|
|
3406
|
+
persistAssistantPartial(false);
|
|
3407
|
+
sendEvent('chunk', { text: chunk });
|
|
3408
|
+
},
|
|
3409
|
+
...(thinkingEnabled ? {
|
|
3410
|
+
onThinkingChunk: async (chunk) => {
|
|
3411
|
+
sendEvent('thinking_chunk', {
|
|
3412
|
+
text: chunk,
|
|
3413
|
+
botId: profile?.id || null,
|
|
3414
|
+
agentName: profile?.name || null,
|
|
3415
|
+
});
|
|
3416
|
+
},
|
|
3417
|
+
} : {}),
|
|
3418
|
+
};
|
|
3419
|
+
let chatAttempt = 0;
|
|
3420
|
+
while (true) {
|
|
3421
|
+
chatAttempt++;
|
|
3330
3422
|
try {
|
|
3331
3423
|
response = await activeLlm.chat(chatOptions);
|
|
3424
|
+
break;
|
|
3332
3425
|
}
|
|
3333
|
-
catch (
|
|
3334
|
-
if (
|
|
3335
|
-
throw
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
activeRuntimeSource = apiKeyFallback.runtimeSource;
|
|
3343
|
-
activeIsCliProvider = false;
|
|
3344
|
-
console.warn(chalk_1.default.yellow(` [chat] CLI fallback failed (${cliErr?.message || cliErr}); switching to API key fallback (${activeProviderName})`));
|
|
3345
|
-
sendEvent('status', {
|
|
3346
|
-
phase: 'thinking',
|
|
3347
|
-
detail: 'CLI fallback unavailable; switching to API key fallback...',
|
|
3348
|
-
runtime: runtimePayload(),
|
|
3349
|
-
});
|
|
3350
|
-
recordActivity('status', {
|
|
3351
|
-
phase: 'thinking',
|
|
3352
|
-
detail: 'CLI fallback unavailable; switching to API key fallback...',
|
|
3353
|
-
runtime: runtimePayload(),
|
|
3354
|
-
}, 'CLI fallback unavailable; switching to API key fallback...');
|
|
3426
|
+
catch (primaryErr) {
|
|
3427
|
+
if (routeAbortController.signal.aborted || primaryErr?.name === 'AbortError') {
|
|
3428
|
+
throw primaryErr;
|
|
3429
|
+
}
|
|
3430
|
+
if (chatAttempt >= LOCAL_RUNTIME_RETRY_LIMIT || !shouldRetrySelectedLocalRuntime(primaryErr)) {
|
|
3431
|
+
throw primaryErr;
|
|
3432
|
+
}
|
|
3433
|
+
const retryDetail = `Selected runtime failed (${primaryErr?.message || primaryErr}); retrying the same connection (${chatAttempt + 1}/${LOCAL_RUNTIME_RETRY_LIMIT})...`;
|
|
3434
|
+
console.warn(chalk_1.default.yellow(` [chat] ${retryDetail}`));
|
|
3355
3435
|
sendEvent('status', {
|
|
3356
3436
|
phase: 'thinking',
|
|
3357
|
-
detail:
|
|
3437
|
+
detail: retryDetail,
|
|
3358
3438
|
runtime: runtimePayload(),
|
|
3359
3439
|
});
|
|
3360
3440
|
recordActivity('status', {
|
|
3361
3441
|
phase: 'thinking',
|
|
3362
|
-
detail:
|
|
3442
|
+
detail: retryDetail,
|
|
3363
3443
|
runtime: runtimePayload(),
|
|
3364
|
-
},
|
|
3365
|
-
|
|
3444
|
+
}, retryDetail);
|
|
3445
|
+
await pauseLocalRuntimeRetry(chatAttempt);
|
|
3366
3446
|
}
|
|
3367
3447
|
}
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
activeRuntimeMode = apiKeyFallback.runtimeMode;
|
|
3375
|
-
activeRuntimeSource = apiKeyFallback.runtimeSource;
|
|
3376
|
-
activeIsCliProvider = false;
|
|
3377
|
-
console.warn(chalk_1.default.yellow(` [chat] Runtime failed (${primaryErr?.message || primaryErr}); switching to API key fallback (${activeProviderName})`));
|
|
3378
|
-
sendEvent('status', {
|
|
3379
|
-
phase: 'thinking',
|
|
3380
|
-
detail: 'Switching to API key fallback...',
|
|
3381
|
-
runtime: runtimePayload(),
|
|
3382
|
-
});
|
|
3383
|
-
recordActivity('status', {
|
|
3384
|
-
phase: 'thinking',
|
|
3385
|
-
detail: 'Switching to API key fallback...',
|
|
3386
|
-
runtime: runtimePayload(),
|
|
3387
|
-
}, 'Switching to API key fallback...');
|
|
3448
|
+
if (enableCliSessionEpoch && response?.session?.id) {
|
|
3449
|
+
activeCliSessionId = response.session.id;
|
|
3450
|
+
}
|
|
3451
|
+
throwIfChatJobCancelled();
|
|
3452
|
+
const authFailure = detectInteractiveAuthFailure(response?.content || '', activeProviderName, profile.provider);
|
|
3453
|
+
if (authFailure && (!response?.toolCalls || response.toolCalls.length === 0)) {
|
|
3388
3454
|
sendEvent('status', {
|
|
3389
|
-
phase: '
|
|
3390
|
-
detail:
|
|
3455
|
+
phase: 'auth_required',
|
|
3456
|
+
detail: authFailure.detail,
|
|
3391
3457
|
runtime: runtimePayload(),
|
|
3458
|
+
auth: authFailure,
|
|
3392
3459
|
});
|
|
3393
3460
|
recordActivity('status', {
|
|
3394
|
-
phase: '
|
|
3395
|
-
detail:
|
|
3461
|
+
phase: 'auth_required',
|
|
3462
|
+
detail: authFailure.detail,
|
|
3396
3463
|
runtime: runtimePayload(),
|
|
3397
|
-
|
|
3398
|
-
|
|
3464
|
+
auth: authFailure,
|
|
3465
|
+
}, authFailure.detail);
|
|
3466
|
+
const authErr = new Error(authFailure.message);
|
|
3467
|
+
authErr.authRequired = true;
|
|
3468
|
+
authErr.providerId = authFailure.providerId;
|
|
3469
|
+
authErr.cli = authFailure.cli;
|
|
3470
|
+
throw authErr;
|
|
3399
3471
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3472
|
+
if (response.usage) {
|
|
3473
|
+
totalInputTokens += response.usage.inputTokens || 0;
|
|
3474
|
+
totalOutputTokens += response.usage.outputTokens || 0;
|
|
3475
|
+
hasExactUsage = true;
|
|
3402
3476
|
}
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
const errMsg = `PERMISSION_DENIED: ${approval.reason}`;
|
|
3457
|
-
sendEvent('tool_result', { callId: tc.id, output: errMsg, isError: true });
|
|
3458
|
-
recordActivity('tool_result', { callId: tc.id, output: errMsg, isError: true }, `Tool denied: ${tc.name}`);
|
|
3459
|
-
llmMessages.push({ role: 'tool', content: errMsg, toolCallId: tc.id, toolName: tc.name });
|
|
3460
|
-
continue;
|
|
3461
|
-
}
|
|
3462
|
-
let result;
|
|
3463
|
-
try {
|
|
3464
|
-
const raw = await (0, index_2.executeToolWithMCP)({ id: tc.id, name: tc.name, arguments: tc.arguments }, toolCtx, mcpManager);
|
|
3465
|
-
const verified = await (0, index_2.verifyToolResult)(raw, tc.arguments, toolCtx);
|
|
3466
|
-
result = {
|
|
3467
|
-
success: verified.success,
|
|
3468
|
-
output: verified.output,
|
|
3469
|
-
error: verified.error,
|
|
3470
|
-
};
|
|
3471
|
-
}
|
|
3472
|
-
catch (toolErr) {
|
|
3473
|
-
result = { success: false, output: '', error: toolErr.message || 'Tool execution failed' };
|
|
3477
|
+
// Accumulate thinking/reasoning across multi-turn tool loops
|
|
3478
|
+
if (response.thinking) {
|
|
3479
|
+
accumulatedThinking += (accumulatedThinking ? '\n---\n' : '') + response.thinking;
|
|
3480
|
+
}
|
|
3481
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
3482
|
+
llmMessages.push({
|
|
3483
|
+
role: 'assistant',
|
|
3484
|
+
content: response.content || '',
|
|
3485
|
+
toolCalls: response.toolCalls,
|
|
3486
|
+
});
|
|
3487
|
+
for (const tc of response.toolCalls) {
|
|
3488
|
+
throwIfChatJobCancelled();
|
|
3489
|
+
sendEvent('status', { phase: 'calling_tool', detail: `Running ${tc.name}...`, toolName: tc.name });
|
|
3490
|
+
sendEvent('tool_call', { id: tc.id, name: tc.name, arguments: tc.arguments });
|
|
3491
|
+
recordActivity('tool_call', { id: tc.id, name: tc.name, arguments: tc.arguments }, `Tool call: ${tc.name}`);
|
|
3492
|
+
if (!allowedToolNames.has(tc.name)) {
|
|
3493
|
+
const errMsg = `TOOL_DISABLED: ${tc.name} is not enabled for this bot.`;
|
|
3494
|
+
sendEvent('tool_result', { callId: tc.id, output: errMsg, isError: true });
|
|
3495
|
+
recordActivity('tool_result', { callId: tc.id, output: errMsg, isError: true }, `Tool failed: ${tc.name}`);
|
|
3496
|
+
llmMessages.push({ role: 'tool', content: errMsg, toolCallId: tc.id, toolName: tc.name });
|
|
3497
|
+
continue;
|
|
3498
|
+
}
|
|
3499
|
+
const approval = unrestrictedCliProfile
|
|
3500
|
+
? { approved: true }
|
|
3501
|
+
: (0, approval_1.checkPermission)(tc.name, (profile.permission_mode || 'autopilot'));
|
|
3502
|
+
if (!approval.approved) {
|
|
3503
|
+
const errMsg = `PERMISSION_DENIED: ${approval.reason}`;
|
|
3504
|
+
sendEvent('tool_result', { callId: tc.id, output: errMsg, isError: true });
|
|
3505
|
+
recordActivity('tool_result', { callId: tc.id, output: errMsg, isError: true }, `Tool denied: ${tc.name}`);
|
|
3506
|
+
llmMessages.push({ role: 'tool', content: errMsg, toolCallId: tc.id, toolName: tc.name });
|
|
3507
|
+
continue;
|
|
3508
|
+
}
|
|
3509
|
+
let result;
|
|
3510
|
+
try {
|
|
3511
|
+
const raw = await (0, index_2.executeToolWithMCP)({ id: tc.id, name: tc.name, arguments: tc.arguments }, toolCtx, mcpManager);
|
|
3512
|
+
const verified = await (0, index_2.verifyToolResult)(raw, tc.arguments, toolCtx);
|
|
3513
|
+
result = {
|
|
3514
|
+
success: verified.success,
|
|
3515
|
+
output: verified.output,
|
|
3516
|
+
error: verified.error,
|
|
3517
|
+
};
|
|
3518
|
+
}
|
|
3519
|
+
catch (toolErr) {
|
|
3520
|
+
result = { success: false, output: '', error: toolErr.message || 'Tool execution failed' };
|
|
3521
|
+
}
|
|
3522
|
+
const output = result.success ? result.output : `ERROR: ${result.error || 'Unknown error'}`;
|
|
3523
|
+
sendEvent('tool_result', { callId: tc.id, output, isError: !result.success });
|
|
3524
|
+
recordActivity('tool_result', {
|
|
3525
|
+
callId: tc.id,
|
|
3526
|
+
output,
|
|
3527
|
+
isError: !result.success,
|
|
3528
|
+
}, `${result.success ? 'Tool completed' : 'Tool failed'}: ${tc.name}`);
|
|
3529
|
+
llmMessages.push({ role: 'tool', content: output, toolCallId: tc.id, toolName: tc.name });
|
|
3474
3530
|
}
|
|
3475
|
-
|
|
3476
|
-
sendEvent('tool_result', { callId: tc.id, output, isError: !result.success });
|
|
3477
|
-
recordActivity('tool_result', {
|
|
3478
|
-
callId: tc.id,
|
|
3479
|
-
output,
|
|
3480
|
-
isError: !result.success,
|
|
3481
|
-
}, `${result.success ? 'Tool completed' : 'Tool failed'}: ${tc.name}`);
|
|
3482
|
-
llmMessages.push({ role: 'tool', content: output, toolCallId: tc.id, toolName: tc.name });
|
|
3531
|
+
continue;
|
|
3483
3532
|
}
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3533
|
+
// Final response (guard against defer-only filler)
|
|
3534
|
+
const candidate = (response.content || '').trim();
|
|
3535
|
+
if (!forcedFinalizationPass && (0, response_guard_1.isLikelyDeferredReply)(candidate)) {
|
|
3536
|
+
forcedFinalizationPass = true;
|
|
3537
|
+
llmMessages.push({ role: 'assistant', content: candidate });
|
|
3538
|
+
llmMessages.push({
|
|
3539
|
+
role: 'user',
|
|
3540
|
+
content: 'Provide the final answer now. Do not say you will check later. Either provide concrete results or explicitly say what is unavailable.',
|
|
3541
|
+
});
|
|
3542
|
+
sendEvent('status', { phase: 'thinking', detail: 'Finalizing response...' });
|
|
3543
|
+
recordActivity('status', { phase: 'thinking', detail: 'Finalizing response...' }, 'Finalizing response...');
|
|
3544
|
+
continue;
|
|
3545
|
+
}
|
|
3546
|
+
fullContent = candidate;
|
|
3547
|
+
break;
|
|
3498
3548
|
}
|
|
3499
|
-
fullContent = candidate;
|
|
3500
|
-
break;
|
|
3501
|
-
}
|
|
3502
3549
|
const persistedContent = fullContent || streamedContent.trim();
|
|
3503
3550
|
if (!persistedContent) {
|
|
3504
3551
|
throw new Error('Assistant returned no final response');
|
|
3505
3552
|
}
|
|
3506
3553
|
persistAssistantPartial(true);
|
|
3554
|
+
if (enableCliSessionEpoch && activeCliSessionId) {
|
|
3555
|
+
const nextEpochTurnCount = cliSessionEpochPlan.resumeSessionId
|
|
3556
|
+
? ((cliSessionEpochPlan.existing?.epoch_turn_count || 0) + 1)
|
|
3557
|
+
: 1;
|
|
3558
|
+
data.upsertCliSessionEpoch({
|
|
3559
|
+
conversationId: convId,
|
|
3560
|
+
botId: profile.id,
|
|
3561
|
+
provider: activeProviderName,
|
|
3562
|
+
sessionId: activeCliSessionId,
|
|
3563
|
+
epochTurnCount: nextEpochTurnCount,
|
|
3564
|
+
lastInputTokens: hasExactUsage ? totalInputTokens : approxInputTokens,
|
|
3565
|
+
lastOutputTokens: hasExactUsage ? totalOutputTokens : 0,
|
|
3566
|
+
resetReason: cliSessionEpochPlan.resetReason,
|
|
3567
|
+
epochStartedAt: cliEpochStartedAt,
|
|
3568
|
+
lastUsedAt: localTimestamp(),
|
|
3569
|
+
});
|
|
3570
|
+
}
|
|
3507
3571
|
// Emit thinking_done event if we accumulated any thinking
|
|
3508
3572
|
if (accumulatedThinking) {
|
|
3509
3573
|
sendEvent('thinking_done', {
|
|
@@ -3587,6 +3651,7 @@ function startLocalServer(opts) {
|
|
|
3587
3651
|
if (activityErrorContext.conversationId) {
|
|
3588
3652
|
data.createMessageActivity({
|
|
3589
3653
|
conversationId: activityErrorContext.conversationId,
|
|
3654
|
+
messageId: activityErrorContext.messageId ?? null,
|
|
3590
3655
|
streamId: activityErrorContext.streamId ?? null,
|
|
3591
3656
|
botId: activityErrorContext.botId ?? null,
|
|
3592
3657
|
agentName: activityErrorContext.agentName ?? null,
|
|
@@ -3777,7 +3842,7 @@ function startLocalServer(opts) {
|
|
|
3777
3842
|
const { prompt } = req.body;
|
|
3778
3843
|
if (!prompt)
|
|
3779
3844
|
return res.status(400).json({ error: 'prompt is required' });
|
|
3780
|
-
const clerk = (0, clerk_model_1.getClerk)();
|
|
3845
|
+
const clerk = (0, clerk_model_1.getClerk)({ runtimeMode: 'local_desktop' });
|
|
3781
3846
|
if (!clerk)
|
|
3782
3847
|
return res.json({ routing: 'default', reason: 'No clerk configured' });
|
|
3783
3848
|
const agents = data.listAgentProfiles();
|
|
@@ -3794,7 +3859,7 @@ function startLocalServer(opts) {
|
|
|
3794
3859
|
const { prompt, conversationId, agentId, pinnedMessageIds } = req.body;
|
|
3795
3860
|
if (!prompt)
|
|
3796
3861
|
return res.status(400).json({ error: 'prompt is required' });
|
|
3797
|
-
const engine = (0, workflow_engine_1.getWorkflowEngine)(opts.projectDir);
|
|
3862
|
+
const engine = (0, workflow_engine_1.getWorkflowEngine)(opts.projectDir, 'local_desktop');
|
|
3798
3863
|
const profile = agentId
|
|
3799
3864
|
? data.getAgentProfile(agentId)
|
|
3800
3865
|
: data.getDefaultAgentProfile();
|
|
@@ -3812,6 +3877,7 @@ function startLocalServer(opts) {
|
|
|
3812
3877
|
const result = await engine.execute(prompt, conversationId || null, profile.id, {
|
|
3813
3878
|
onProgress: (p) => sendEvent('progress', p),
|
|
3814
3879
|
pinnedMessageIds: pinnedMessageIds || undefined,
|
|
3880
|
+
runtimeMode: 'local_desktop',
|
|
3815
3881
|
});
|
|
3816
3882
|
sendEvent('done', result);
|
|
3817
3883
|
res.end();
|
|
@@ -3839,7 +3905,7 @@ function startLocalServer(opts) {
|
|
|
3839
3905
|
return res.status(400).json({ error: 'template id is required' });
|
|
3840
3906
|
if (!Number.isFinite(taskId) || taskId <= 0)
|
|
3841
3907
|
return res.status(400).json({ error: 'taskId is required' });
|
|
3842
|
-
const engine = (0, workflow_engine_1.getWorkflowEngine)(opts.projectDir);
|
|
3908
|
+
const engine = (0, workflow_engine_1.getWorkflowEngine)(opts.projectDir, 'local_desktop');
|
|
3843
3909
|
res.writeHead(200, {
|
|
3844
3910
|
'Content-Type': 'text/event-stream',
|
|
3845
3911
|
'Cache-Control': 'no-cache',
|
|
@@ -3871,7 +3937,7 @@ function startLocalServer(opts) {
|
|
|
3871
3937
|
});
|
|
3872
3938
|
app.get('/api/workflow/active', (_req, res) => {
|
|
3873
3939
|
try {
|
|
3874
|
-
const engine = (0, workflow_engine_1.getWorkflowEngine)(opts.projectDir);
|
|
3940
|
+
const engine = (0, workflow_engine_1.getWorkflowEngine)(opts.projectDir, 'local_desktop');
|
|
3875
3941
|
const workflows = engine.getActiveWorkflows();
|
|
3876
3942
|
res.json(workflows);
|
|
3877
3943
|
}
|
|
@@ -3881,7 +3947,7 @@ function startLocalServer(opts) {
|
|
|
3881
3947
|
});
|
|
3882
3948
|
app.post('/api/workflow/:id/cancel', (req, res) => {
|
|
3883
3949
|
try {
|
|
3884
|
-
const engine = (0, workflow_engine_1.getWorkflowEngine)(opts.projectDir);
|
|
3950
|
+
const engine = (0, workflow_engine_1.getWorkflowEngine)(opts.projectDir, 'local_desktop');
|
|
3885
3951
|
const cancelled = engine.cancel(req.params.id);
|
|
3886
3952
|
res.json({ ok: cancelled });
|
|
3887
3953
|
}
|
|
@@ -4612,7 +4678,7 @@ function startLocalServer(opts) {
|
|
|
4612
4678
|
}
|
|
4613
4679
|
});
|
|
4614
4680
|
// Initialize workflow engine
|
|
4615
|
-
(0, workflow_engine_1.getWorkflowEngine)(opts.projectDir);
|
|
4681
|
+
(0, workflow_engine_1.getWorkflowEngine)(opts.projectDir, 'local_desktop');
|
|
4616
4682
|
// Start server
|
|
4617
4683
|
return new Promise((resolve, reject) => {
|
|
4618
4684
|
_server = app.listen(port, '127.0.0.1', () => {
|
|
@@ -4643,43 +4709,38 @@ function stopLocalServer() {
|
|
|
4643
4709
|
}
|
|
4644
4710
|
});
|
|
4645
4711
|
}
|
|
4646
|
-
function buildCliFallback(profile) {
|
|
4647
|
-
const providerName = profile.provider;
|
|
4648
|
-
const model = (profile.model || '').trim() || 'default';
|
|
4649
|
-
return {
|
|
4650
|
-
providerName,
|
|
4651
|
-
apiKey: 'cli-auth',
|
|
4652
|
-
model,
|
|
4653
|
-
llm: (0, index_1.createProvider)(providerName, { apiKey: 'cli-auth', model }),
|
|
4654
|
-
runtimeMode: 'subscription-cli',
|
|
4655
|
-
runtimeSource: 'cli-direct',
|
|
4656
|
-
};
|
|
4657
|
-
}
|
|
4658
4712
|
async function buildChatRuntime(profile) {
|
|
4659
4713
|
const providerName = profile.provider;
|
|
4660
|
-
const
|
|
4661
|
-
|
|
4662
|
-
if (providerName === 'claude-cli' || providerName === 'codex-cli') {
|
|
4714
|
+
const model = (profile.model || '').trim() || 'default';
|
|
4715
|
+
if (providerName === 'claude-cli') {
|
|
4663
4716
|
return {
|
|
4664
4717
|
providerName,
|
|
4665
4718
|
apiKey: 'cli-auth',
|
|
4666
|
-
model
|
|
4667
|
-
llm: (0, index_1.createProvider)(providerName, { apiKey: 'cli-auth', model:
|
|
4719
|
+
model,
|
|
4720
|
+
llm: (0, index_1.createProvider)(providerName, { apiKey: 'cli-auth', model, runtimeMode: 'local_desktop' }),
|
|
4721
|
+
runtimeMode: 'subscription-cli',
|
|
4722
|
+
runtimeSource: 'cli-direct',
|
|
4723
|
+
};
|
|
4724
|
+
}
|
|
4725
|
+
if (providerName === 'codex-cli') {
|
|
4726
|
+
return {
|
|
4727
|
+
providerName,
|
|
4728
|
+
apiKey: 'cli-auth',
|
|
4729
|
+
model,
|
|
4730
|
+
llm: (0, index_1.createProvider)(providerName, { apiKey: 'cli-auth', model, runtimeMode: 'local_desktop' }),
|
|
4668
4731
|
runtimeMode: 'subscription-cli',
|
|
4669
4732
|
runtimeSource: 'cli-direct',
|
|
4670
|
-
apiKeyFallback,
|
|
4671
4733
|
};
|
|
4672
4734
|
}
|
|
4673
4735
|
const apiKey = resolveApiKey(profile);
|
|
4674
4736
|
if (!apiKey) {
|
|
4675
4737
|
throw new Error(`No API key for provider ${profile.provider}. Configure one in Settings.`);
|
|
4676
4738
|
}
|
|
4677
|
-
const model = (profile.model || '').trim() || 'default';
|
|
4678
4739
|
return {
|
|
4679
4740
|
providerName,
|
|
4680
4741
|
apiKey,
|
|
4681
4742
|
model,
|
|
4682
|
-
llm: (0, index_1.createProvider)(providerName, { apiKey, model }),
|
|
4743
|
+
llm: (0, index_1.createProvider)(providerName, { apiKey, model, runtimeMode: 'local_desktop' }),
|
|
4683
4744
|
runtimeMode: 'api-key',
|
|
4684
4745
|
runtimeSource: 'api-key',
|
|
4685
4746
|
};
|
|
@@ -4691,14 +4752,13 @@ function runtimeModeLabel(mode, runtimeSource) {
|
|
|
4691
4752
|
return (0, subscription_runtime_1.claudeSubscriptionRuntimeLabel)(runtimeSource);
|
|
4692
4753
|
return 'API Key';
|
|
4693
4754
|
}
|
|
4694
|
-
function runtimePayloadForDisplay(providerName, model, runtimeMode, runtimeSource
|
|
4755
|
+
function runtimePayloadForDisplay(providerName, model, runtimeMode, runtimeSource) {
|
|
4695
4756
|
return {
|
|
4696
4757
|
mode: runtimeMode,
|
|
4697
4758
|
modeLabel: runtimeModeLabel(runtimeMode, runtimeSource),
|
|
4698
4759
|
provider: providerName,
|
|
4699
4760
|
model: model || null,
|
|
4700
4761
|
source: runtimeSource || null,
|
|
4701
|
-
fallbackUsed,
|
|
4702
4762
|
};
|
|
4703
4763
|
}
|
|
4704
4764
|
function configuredRuntimeLabelForProfile(profile) {
|
|
@@ -4870,27 +4930,19 @@ function detectInteractiveAuthFailure(text, activeProviderName, configuredProvid
|
|
|
4870
4930
|
cli,
|
|
4871
4931
|
};
|
|
4872
4932
|
}
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
return {
|
|
4887
|
-
providerName,
|
|
4888
|
-
apiKey,
|
|
4889
|
-
model,
|
|
4890
|
-
llm: (0, index_1.createProvider)(providerName, { apiKey, model }),
|
|
4891
|
-
runtimeMode: 'api-key',
|
|
4892
|
-
runtimeSource: 'api-key-fallback',
|
|
4893
|
-
};
|
|
4933
|
+
const LOCAL_RUNTIME_RETRY_LIMIT = 2;
|
|
4934
|
+
function shouldRetrySelectedLocalRuntime(err) {
|
|
4935
|
+
const text = String(err?.message || err || '').toLowerCase();
|
|
4936
|
+
if (!text)
|
|
4937
|
+
return false;
|
|
4938
|
+
if (/\b(no api key|configure one in settings|not available on this machine|not installed|please run \/login|not logged in|invalid api key)\b/i.test(text)) {
|
|
4939
|
+
return false;
|
|
4940
|
+
}
|
|
4941
|
+
return /\b(429|rate limit|timeout|timed out|temporar|temporarily|econnreset|etimedout|enotfound|econnrefused|socket hang up|network|try again|overloaded|busy)\b/i.test(text);
|
|
4942
|
+
}
|
|
4943
|
+
async function pauseLocalRuntimeRetry(attempt) {
|
|
4944
|
+
const delayMs = attempt <= 1 ? 750 : 1500;
|
|
4945
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
4894
4946
|
}
|
|
4895
4947
|
function resolveApiKey(profile) {
|
|
4896
4948
|
// Check profile-stored key first
|
|
@@ -4965,7 +5017,7 @@ function expandAllowedToolNames(allToolDefs, configuredBuiltinTools, configuredM
|
|
|
4965
5017
|
}
|
|
4966
5018
|
async function autoTitleConversation(convId, userMsg, assistantMsg, providerName, modelName, apiKey) {
|
|
4967
5019
|
try {
|
|
4968
|
-
const llm = (0, index_1.createProvider)(providerName, { apiKey, model: modelName || 'default' });
|
|
5020
|
+
const llm = (0, index_1.createProvider)(providerName, { apiKey, model: modelName || 'default', runtimeMode: 'local_desktop' });
|
|
4969
5021
|
const resp = await llm.chat({
|
|
4970
5022
|
messages: [{ role: 'user', content: `Generate a short title (max 6 words, no quotes) for this conversation:\n\nUser: ${userMsg.slice(0, 200)}\nAssistant: ${assistantMsg.slice(0, 200)}` }],
|
|
4971
5023
|
system: 'You generate short conversation titles. Return ONLY the title, nothing else.',
|