funolio-agent 1.0.53 → 1.0.75
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/approval.d.ts +1 -6
- package/dist/approval.d.ts.map +1 -1
- package/dist/approval.js +2 -7
- package/dist/approval.js.map +1 -1
- package/dist/bot-manager.d.ts +5 -1
- package/dist/bot-manager.d.ts.map +1 -1
- package/dist/bot-manager.js +23 -13
- package/dist/bot-manager.js.map +1 -1
- package/dist/cli-session-epoch.d.ts +1 -1
- package/dist/cli-session-epoch.d.ts.map +1 -1
- package/dist/cli-session-epoch.js +1 -1
- package/dist/cli-session-epoch.js.map +1 -1
- package/dist/cli-session-registry.d.ts +35 -0
- package/dist/cli-session-registry.d.ts.map +1 -0
- package/dist/cli-session-registry.js +177 -0
- package/dist/cli-session-registry.js.map +1 -0
- package/dist/cli.js +62 -0
- package/dist/cli.js.map +1 -1
- package/dist/codex-app-server-manager.d.ts +129 -0
- package/dist/codex-app-server-manager.d.ts.map +1 -0
- package/dist/codex-app-server-manager.js +768 -0
- package/dist/codex-app-server-manager.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +8 -30
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/setup.d.ts +4 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +9 -25
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +77 -2
- package/dist/commands/start.js.map +1 -1
- package/dist/completion-marker.d.ts +7 -0
- package/dist/completion-marker.d.ts.map +1 -0
- package/dist/completion-marker.js +28 -0
- package/dist/completion-marker.js.map +1 -0
- package/dist/config.d.ts +6 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -3
- package/dist/config.js.map +1 -1
- package/dist/context-window.d.ts.map +1 -1
- package/dist/context-window.js +8 -1
- package/dist/context-window.js.map +1 -1
- package/dist/live-activity.d.ts +29 -0
- package/dist/live-activity.d.ts.map +1 -0
- package/dist/live-activity.js +36 -0
- package/dist/live-activity.js.map +1 -0
- package/dist/local-cli-pty-manager.d.ts +51 -0
- package/dist/local-cli-pty-manager.d.ts.map +1 -1
- package/dist/local-cli-pty-manager.js +1227 -114
- package/dist/local-cli-pty-manager.js.map +1 -1
- package/dist/local-data.d.ts +41 -0
- package/dist/local-data.d.ts.map +1 -1
- package/dist/local-data.js +140 -4
- package/dist/local-data.js.map +1 -1
- package/dist/local-db.d.ts.map +1 -1
- package/dist/local-db.js +55 -1
- package/dist/local-db.js.map +1 -1
- package/dist/local-server.d.ts +25 -0
- package/dist/local-server.d.ts.map +1 -1
- package/dist/local-server.js +528 -267
- package/dist/local-server.js.map +1 -1
- package/dist/message-loop.d.ts +6 -0
- package/dist/message-loop.d.ts.map +1 -1
- package/dist/message-loop.js +239 -89
- package/dist/message-loop.js.map +1 -1
- package/dist/mqtt-client.d.ts +10 -1
- package/dist/mqtt-client.d.ts.map +1 -1
- package/dist/mqtt-client.js +14 -1
- package/dist/mqtt-client.js.map +1 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +69 -29
- package/dist/oauth.js.map +1 -1
- package/dist/orchestration/orchestrator-operating-prompt.d.ts +1 -0
- package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-operating-prompt.js +60 -0
- package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
- package/dist/orchestration/validation.d.ts +40 -0
- package/dist/orchestration/validation.d.ts.map +1 -0
- package/dist/orchestration/validation.js +203 -0
- package/dist/orchestration/validation.js.map +1 -0
- package/dist/orchestrator.d.ts +21 -32
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +287 -725
- package/dist/orchestrator.js.map +1 -1
- package/dist/providers/claude-cli-prompt.d.ts.map +1 -1
- package/dist/providers/claude-cli-prompt.js +49 -5
- 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 +56 -5
- 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 +15 -10
- package/dist/providers/codex-cli.js.map +1 -1
- package/dist/response-guard.js +1 -1
- package/dist/response-guard.js.map +1 -1
- package/dist/tools/admin-tools.d.ts.map +1 -1
- package/dist/tools/admin-tools.js +8 -2
- package/dist/tools/admin-tools.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/search-conversation-history.d.ts +16 -0
- package/dist/tools/search-conversation-history.d.ts.map +1 -0
- package/dist/tools/search-conversation-history.js +324 -0
- package/dist/tools/search-conversation-history.js.map +1 -0
- package/dist/wizard-state.d.ts +7 -0
- package/dist/wizard-state.d.ts.map +1 -1
- package/dist/wizard-state.js +31 -2
- package/dist/wizard-state.js.map +1 -1
- package/dist/workflow-engine.d.ts +4 -1
- package/dist/workflow-engine.d.ts.map +1 -1
- package/dist/workflow-engine.js +190 -29
- package/dist/workflow-engine.js.map +1 -1
- package/package.json +1 -1
package/dist/local-server.js
CHANGED
|
@@ -39,6 +39,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.startLocalServer = startLocalServer;
|
|
40
40
|
exports.stopLocalServer = stopLocalServer;
|
|
41
41
|
exports.buildChatRuntimeForTest = buildChatRuntimeForTest;
|
|
42
|
+
exports.resolveDirectCliSessionTransportForTest = resolveDirectCliSessionTransportForTest;
|
|
43
|
+
exports.buildLocalDesktopDirectPromptForTest = buildLocalDesktopDirectPromptForTest;
|
|
42
44
|
/**
|
|
43
45
|
* Local HTTP server for desktop-first operation.
|
|
44
46
|
*
|
|
@@ -47,6 +49,7 @@ exports.buildChatRuntimeForTest = buildChatRuntimeForTest;
|
|
|
47
49
|
* Reuses existing LLM providers and tool execution from message-loop/providers.
|
|
48
50
|
*/
|
|
49
51
|
const index_1 = require("./providers/index");
|
|
52
|
+
const completion_marker_1 = require("./completion-marker");
|
|
50
53
|
const index_2 = require("./index");
|
|
51
54
|
const approval_1 = require("./approval");
|
|
52
55
|
const config_1 = require("./config");
|
|
@@ -61,7 +64,6 @@ const config_cleanup_1 = require("./config-cleanup");
|
|
|
61
64
|
const status_parser_1 = require("./orchestration/status-parser");
|
|
62
65
|
const guided_actions_1 = require("./orchestration/guided-actions");
|
|
63
66
|
const topic_normalizer_1 = require("./orchestration/topic-normalizer");
|
|
64
|
-
const policy_prompt_1 = require("./orchestration/policy-prompt");
|
|
65
67
|
const safeguards_1 = require("./orchestration/safeguards");
|
|
66
68
|
const token_counter_1 = require("./token-counter");
|
|
67
69
|
const response_guard_1 = require("./response-guard");
|
|
@@ -78,6 +80,7 @@ const policy_detection_1 = require("./policy-detection");
|
|
|
78
80
|
const server_runtime_1 = require("./server-runtime");
|
|
79
81
|
const storage_mode_1 = require("./storage-mode");
|
|
80
82
|
const local_cli_pty_manager_1 = require("./local-cli-pty-manager");
|
|
83
|
+
const codex_app_server_manager_1 = require("./codex-app-server-manager");
|
|
81
84
|
const cli_session_epoch_1 = require("./cli-session-epoch");
|
|
82
85
|
const server_adapter_1 = require("./server-adapter");
|
|
83
86
|
const wizard_support_1 = require("./wizard-support");
|
|
@@ -611,7 +614,10 @@ function startLocalServer(opts) {
|
|
|
611
614
|
if (isConnectedMode())
|
|
612
615
|
return;
|
|
613
616
|
while (runningChatJobControllers.size < MAX_LOCAL_CHAT_JOBS) {
|
|
614
|
-
const
|
|
617
|
+
const runningKeys = new Set(data.listRunningChatJobs(MAX_LOCAL_CHAT_JOBS + 20).map((job) => `${job.conversation_id}::${job.bot_id}`));
|
|
618
|
+
const next = data
|
|
619
|
+
.listQueuedChatJobs(50)
|
|
620
|
+
.find((job) => !runningKeys.has(`${job.conversation_id}::${job.bot_id}`));
|
|
615
621
|
if (!next)
|
|
616
622
|
return;
|
|
617
623
|
if (runningChatJobControllers.has(next.id))
|
|
@@ -1233,7 +1239,7 @@ function startLocalServer(opts) {
|
|
|
1233
1239
|
app.post('/api/bots', (req, res) => {
|
|
1234
1240
|
(async () => {
|
|
1235
1241
|
try {
|
|
1236
|
-
const { provider, model, name, soulMd, memoryMd, toolsMd, skillsMd, apiKeyEnc, permissionMode, isDefault, roleLabel, roleClass, isActive, priority, isOrchestrator, is_orchestrator } = req.body;
|
|
1242
|
+
const { provider, model, name, soulMd, memoryMd, toolsMd, skillsMd, apiKeyEnc, permissionMode, isDefault, roleLabel, roleClass, isActive, priority, isOrchestrator, is_orchestrator, codexReasoningEffort, codex_reasoning_effort, codexReasoningSummary, codex_reasoning_summary, codexPersonality, codex_personality, codexServiceTier, codex_service_tier, codexSandboxPolicy, codex_sandbox_policy, codexApprovalPolicy, codex_approval_policy, } = req.body;
|
|
1237
1243
|
if (!provider || !model || !name) {
|
|
1238
1244
|
return res.status(400).json({ error: 'provider, model, and name are required' });
|
|
1239
1245
|
}
|
|
@@ -1263,6 +1269,12 @@ function startLocalServer(opts) {
|
|
|
1263
1269
|
isActive,
|
|
1264
1270
|
priority,
|
|
1265
1271
|
isOrchestrator: isOrchestrator ?? is_orchestrator,
|
|
1272
|
+
codexReasoningEffort: codexReasoningEffort ?? codex_reasoning_effort,
|
|
1273
|
+
codexReasoningSummary: codexReasoningSummary ?? codex_reasoning_summary,
|
|
1274
|
+
codexPersonality: codexPersonality ?? codex_personality,
|
|
1275
|
+
codexServiceTier: codexServiceTier ?? codex_service_tier,
|
|
1276
|
+
codexSandboxPolicy: codexSandboxPolicy ?? codex_sandbox_policy,
|
|
1277
|
+
codexApprovalPolicy: codexApprovalPolicy ?? codex_approval_policy,
|
|
1266
1278
|
});
|
|
1267
1279
|
res.status(201).json(profile);
|
|
1268
1280
|
}
|
|
@@ -1309,6 +1321,18 @@ function startLocalServer(opts) {
|
|
|
1309
1321
|
fields.showThinking = b.showThinking ?? b.show_thinking;
|
|
1310
1322
|
if (b.isOrchestrator !== undefined || b.is_orchestrator !== undefined)
|
|
1311
1323
|
fields.isOrchestrator = b.isOrchestrator ?? b.is_orchestrator;
|
|
1324
|
+
if (b.codexReasoningEffort !== undefined || b.codex_reasoning_effort !== undefined)
|
|
1325
|
+
fields.codexReasoningEffort = b.codexReasoningEffort ?? b.codex_reasoning_effort;
|
|
1326
|
+
if (b.codexReasoningSummary !== undefined || b.codex_reasoning_summary !== undefined)
|
|
1327
|
+
fields.codexReasoningSummary = b.codexReasoningSummary ?? b.codex_reasoning_summary;
|
|
1328
|
+
if (b.codexPersonality !== undefined || b.codex_personality !== undefined)
|
|
1329
|
+
fields.codexPersonality = b.codexPersonality ?? b.codex_personality;
|
|
1330
|
+
if (b.codexServiceTier !== undefined || b.codex_service_tier !== undefined)
|
|
1331
|
+
fields.codexServiceTier = b.codexServiceTier ?? b.codex_service_tier;
|
|
1332
|
+
if (b.codexSandboxPolicy !== undefined || b.codex_sandbox_policy !== undefined)
|
|
1333
|
+
fields.codexSandboxPolicy = b.codexSandboxPolicy ?? b.codex_sandbox_policy;
|
|
1334
|
+
if (b.codexApprovalPolicy !== undefined || b.codex_approval_policy !== undefined)
|
|
1335
|
+
fields.codexApprovalPolicy = b.codexApprovalPolicy ?? b.codex_approval_policy;
|
|
1312
1336
|
if (isConnectedMode()) {
|
|
1313
1337
|
const runtime = (0, server_runtime_1.getRuntimeConnectionConfig)();
|
|
1314
1338
|
const auth = await getHydratedDesktopAuth();
|
|
@@ -2450,15 +2474,15 @@ function startLocalServer(opts) {
|
|
|
2450
2474
|
if (!profile) {
|
|
2451
2475
|
return res.status(400).json({ error: 'No bot configured. Create one first.' });
|
|
2452
2476
|
}
|
|
2453
|
-
const shouldUseOrchestratorMode = orchestrationEnabled !== false && (0, orchestrator_profile_1.isOrchestratorProfile)(profile);
|
|
2477
|
+
const shouldUseOrchestratorMode = orchestrationEnabled !== false && (data.isClerkOrchestratorEnabled() || (0, orchestrator_profile_1.isOrchestratorProfile)(profile));
|
|
2454
2478
|
if (shouldUseOrchestratorMode) {
|
|
2455
2479
|
return res.status(400).json({ error: 'Background chat jobs do not support orchestrator mode yet.' });
|
|
2456
2480
|
}
|
|
2457
2481
|
let convId = conversationId ? String(conversationId) : '';
|
|
2458
2482
|
if (convId) {
|
|
2459
|
-
const latestJob = data.
|
|
2483
|
+
const latestJob = data.getLatestConversationBotChatJob(convId, profile.id);
|
|
2460
2484
|
if (latestJob && (latestJob.status === 'queued' || latestJob.status === 'running')) {
|
|
2461
|
-
return res.status(409).json({ error: 'This
|
|
2485
|
+
return res.status(409).json({ error: 'This bot already has a pending response in this conversation.' });
|
|
2462
2486
|
}
|
|
2463
2487
|
}
|
|
2464
2488
|
if (!convId) {
|
|
@@ -2554,7 +2578,10 @@ function startLocalServer(opts) {
|
|
|
2554
2578
|
// ─── Chat (SSE streaming) ──────────────────────────────────
|
|
2555
2579
|
app.post('/api/conversations/:id/chat-job/cancel', async (req, res) => {
|
|
2556
2580
|
try {
|
|
2557
|
-
const
|
|
2581
|
+
const requestedBotId = String(req.body?.botId || '').trim();
|
|
2582
|
+
const latestJob = requestedBotId
|
|
2583
|
+
? data.getLatestConversationBotChatJob(req.params.id, requestedBotId)
|
|
2584
|
+
: data.getLatestConversationChatJob(req.params.id);
|
|
2558
2585
|
if (!latestJob)
|
|
2559
2586
|
return res.status(404).json({ error: 'Chat job not found' });
|
|
2560
2587
|
if (latestJob.status === 'completed' || latestJob.status === 'failed' || latestJob.status === 'cancelled') {
|
|
@@ -2590,6 +2617,7 @@ function startLocalServer(opts) {
|
|
|
2590
2617
|
routeAbortController.abort();
|
|
2591
2618
|
};
|
|
2592
2619
|
req.on('close', abortOnClientClose);
|
|
2620
|
+
res.on('close', abortOnClientClose);
|
|
2593
2621
|
try {
|
|
2594
2622
|
let { conversationId, message, botId, skipUserMessage, pinnedMessageIds, topicId, projectId, workflowTemplateId, orchestrationEnabled, chatJobId, assistantMessageId, persistAssistantPlaceholder, } = req.body;
|
|
2595
2623
|
if (!message)
|
|
@@ -2734,19 +2762,22 @@ function startLocalServer(opts) {
|
|
|
2734
2762
|
assistantMessageId = placeholder.id;
|
|
2735
2763
|
}
|
|
2736
2764
|
// ─── Orchestrator Mode Branch ─────────────────────────
|
|
2737
|
-
const shouldUseOrchestratorMode = orchestrationEnabled !== false && (0, orchestrator_profile_1.isOrchestratorProfile)(profile);
|
|
2765
|
+
const shouldUseOrchestratorMode = orchestrationEnabled !== false && (data.isClerkOrchestratorEnabled() || (0, orchestrator_profile_1.isOrchestratorProfile)(profile));
|
|
2738
2766
|
if (shouldUseOrchestratorMode) {
|
|
2739
|
-
const clerk = (0, clerk_model_1.getClerk)({ runtimeMode: 'local_desktop' });
|
|
2740
|
-
if (!clerk) {
|
|
2741
|
-
// Fix #2: Do not silently fall through to direct chat — return a clear error
|
|
2742
|
-
return res.status(400).json({
|
|
2743
|
-
error: 'Orchestrator mode requires a clerk model to be configured. Please add a provider connection in Settings.',
|
|
2744
|
-
});
|
|
2745
|
-
}
|
|
2746
2767
|
const { OrchestratorAgent } = require('./orchestrator');
|
|
2768
|
+
const { buildLocalDesktopOrchestratorRuntime } = require('./orchestrator');
|
|
2747
2769
|
const { getWorkflowEngine } = require('./workflow-engine');
|
|
2748
2770
|
const workflowEngine = getWorkflowEngine(opts.projectDir, 'local_desktop');
|
|
2749
|
-
|
|
2771
|
+
let orchestratorRuntime;
|
|
2772
|
+
try {
|
|
2773
|
+
orchestratorRuntime = buildLocalDesktopOrchestratorRuntime(profile);
|
|
2774
|
+
}
|
|
2775
|
+
catch (runtimeErr) {
|
|
2776
|
+
return res.status(400).json({
|
|
2777
|
+
error: runtimeErr?.message || 'Orchestrator mode is not configured correctly.',
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
const orchestrator = new OrchestratorAgent(orchestratorRuntime, workflowEngine);
|
|
2750
2781
|
// Resolve effective project ID from request or existing conversation
|
|
2751
2782
|
const conv = data.getConversation(convId);
|
|
2752
2783
|
const effectiveProjectId = projectId ? String(projectId) : (conv?.project_id || undefined);
|
|
@@ -2769,13 +2800,34 @@ function startLocalServer(opts) {
|
|
|
2769
2800
|
};
|
|
2770
2801
|
let orchestratorRuntimeLabel = '';
|
|
2771
2802
|
let orchestratorRuntimePayload;
|
|
2803
|
+
const clerkSelectedAsOrchestrator = data.isClerkOrchestratorEnabled();
|
|
2772
2804
|
try {
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2805
|
+
if (clerkSelectedAsOrchestrator) {
|
|
2806
|
+
const clerkProvider = data.getSetting('clerk_provider') || profile.provider;
|
|
2807
|
+
const clerkModel = data.getSetting('clerk_model') || profile.model || null;
|
|
2808
|
+
const clerkConnection = data.findProviderConnection(clerkProvider);
|
|
2809
|
+
const clerkRuntimeMode = clerkProvider === 'claude-cli' || clerkProvider === 'codex-cli'
|
|
2810
|
+
? 'subscription-cli'
|
|
2811
|
+
: 'api-key';
|
|
2812
|
+
const clerkRuntimeSource = clerkConnection?.access_mode === 'oauth'
|
|
2813
|
+
? 'oauth-token'
|
|
2814
|
+
: clerkConnection?.access_mode === 'cli'
|
|
2815
|
+
? 'cli-direct'
|
|
2816
|
+
: 'api-key';
|
|
2817
|
+
orchestratorRuntimeLabel = [
|
|
2818
|
+
clerkModel || '',
|
|
2819
|
+
runtimeModeLabel(clerkRuntimeMode, clerkRuntimeSource),
|
|
2820
|
+
].filter(Boolean).join(' | ');
|
|
2821
|
+
orchestratorRuntimePayload = runtimePayloadForDisplay(clerkProvider, clerkModel, clerkRuntimeMode, clerkRuntimeSource);
|
|
2822
|
+
}
|
|
2823
|
+
else {
|
|
2824
|
+
const orchestratorRuntime = await buildChatRuntime(profile);
|
|
2825
|
+
orchestratorRuntimeLabel = [
|
|
2826
|
+
orchestratorRuntime.model || profile.model || '',
|
|
2827
|
+
runtimeModeLabel(orchestratorRuntime.runtimeMode, orchestratorRuntime.runtimeSource),
|
|
2828
|
+
].filter(Boolean).join(' | ');
|
|
2829
|
+
orchestratorRuntimePayload = runtimePayloadForDisplay(profile.provider, orchestratorRuntime.model || profile.model || null, orchestratorRuntime.runtimeMode, orchestratorRuntime.runtimeSource || null);
|
|
2830
|
+
}
|
|
2779
2831
|
}
|
|
2780
2832
|
catch {
|
|
2781
2833
|
orchestratorRuntimeLabel = buildConfiguredMessageModel(profile);
|
|
@@ -2836,6 +2888,18 @@ function startLocalServer(opts) {
|
|
|
2836
2888
|
text: event.text,
|
|
2837
2889
|
});
|
|
2838
2890
|
}
|
|
2891
|
+
else if (event.type === 'worker_terminal_chunk') {
|
|
2892
|
+
hasWorkerActivity = true;
|
|
2893
|
+
sendEvent('worker_terminal_chunk', {
|
|
2894
|
+
stepId: event.stepId,
|
|
2895
|
+
botId: resolveWorkerBotId(event.agentName),
|
|
2896
|
+
agentName: event.agentName,
|
|
2897
|
+
description: event.description,
|
|
2898
|
+
stepIndex: event.stepIndex,
|
|
2899
|
+
totalSteps: event.totalSteps,
|
|
2900
|
+
text: event.rawText,
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2839
2903
|
else if (event.type === 'worker_tool_call') {
|
|
2840
2904
|
hasWorkerActivity = true;
|
|
2841
2905
|
recordWorkerActivity('worker_tool_call', event, {
|
|
@@ -3027,9 +3091,6 @@ function startLocalServer(opts) {
|
|
|
3027
3091
|
}
|
|
3028
3092
|
return;
|
|
3029
3093
|
}
|
|
3030
|
-
// Prompt Contract v1: system carries summary + last 5 turns.
|
|
3031
|
-
// Send only the current user request as the primary user message.
|
|
3032
|
-
const llmMessages = [{ role: 'user', content: message }];
|
|
3033
3094
|
const configuredTz = (data.getSetting('timezone') || '').trim();
|
|
3034
3095
|
const effectiveTimezone = configuredTz && configuredTz.toLowerCase() !== 'system'
|
|
3035
3096
|
? configuredTz
|
|
@@ -3042,101 +3103,16 @@ function startLocalServer(opts) {
|
|
|
3042
3103
|
? new Set(allToolDefs.map((tool) => tool.name))
|
|
3043
3104
|
: expandAllowedToolNames(allToolDefs, configuredBuiltinTools, configuredMcpTools);
|
|
3044
3105
|
const toolDefs = allToolDefs.filter((tool) => allowedToolNames.has(tool.name));
|
|
3045
|
-
|
|
3046
|
-
|
|
3106
|
+
const conversation = data.getConversation(convId);
|
|
3107
|
+
const topicTitle = topicId ? data.getTopic(topicId)?.title : undefined;
|
|
3108
|
+
const project = conversation?.project_id ? data.getProject(conversation.project_id) : undefined;
|
|
3109
|
+
const workspacePath = project?.folder?.trim() || undefined;
|
|
3047
3110
|
let llmSpawnCwd = opts.projectDir;
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
const conv = data.getConversation(convId);
|
|
3051
|
-
const topicTitle = topicId ? data.getTopic(topicId)?.title : undefined;
|
|
3052
|
-
const project = conv?.project_id ? data.getProject(conv.project_id) : undefined;
|
|
3053
|
-
const workspacePath = project?.folder?.trim() || undefined;
|
|
3054
|
-
if (workspacePath && fs.existsSync(workspacePath)) {
|
|
3055
|
-
llmSpawnCwd = workspacePath;
|
|
3056
|
-
}
|
|
3057
|
-
const built = clerk.buildPrompt(message, profile.id, profile, {
|
|
3058
|
-
targetModel: profile.model,
|
|
3059
|
-
conversationId: convId,
|
|
3060
|
-
projectName: conv?.project_name || undefined,
|
|
3061
|
-
projectId: conv?.project_id || undefined,
|
|
3062
|
-
topicTitle: topicTitle || undefined,
|
|
3063
|
-
workspacePath,
|
|
3064
|
-
timezone: effectiveTimezone,
|
|
3065
|
-
includeKeyDecisions: false,
|
|
3066
|
-
availableTools: toolDefs.map((tool) => ({ name: tool.name, description: tool.description })),
|
|
3067
|
-
});
|
|
3068
|
-
systemPrompt = built.systemPrompt;
|
|
3069
|
-
console.log(chalk_1.default.gray(` [clerk] Context: ${built.injectedSummaries} summaries (${built.contextTokensUsed} tokens)`));
|
|
3111
|
+
if (workspacePath && fs.existsSync(workspacePath)) {
|
|
3112
|
+
llmSpawnCwd = workspacePath;
|
|
3070
3113
|
}
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
systemPrompt = '[Bot Identity]\n' + (profile.soul_md
|
|
3074
|
-
|| 'You are an AI assistant running locally. You have access to project files and can execute code.');
|
|
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.';
|
|
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.';
|
|
3078
|
-
const convForFallback = data.getConversation(convId);
|
|
3079
|
-
const projectForFallback = convForFallback?.project_id ? data.getProject(convForFallback.project_id) : undefined;
|
|
3080
|
-
const workspaceForFallback = projectForFallback?.folder?.trim();
|
|
3081
|
-
if (workspaceForFallback && fs.existsSync(workspaceForFallback)) {
|
|
3082
|
-
llmSpawnCwd = workspaceForFallback;
|
|
3083
|
-
}
|
|
3084
|
-
const fallbackMeta = [];
|
|
3085
|
-
if (convForFallback?.project_name)
|
|
3086
|
-
fallbackMeta.push(`Project: ${convForFallback.project_name}`);
|
|
3087
|
-
if (topicId) {
|
|
3088
|
-
const fallbackTopic = data.getTopic(topicId);
|
|
3089
|
-
if (fallbackTopic?.title)
|
|
3090
|
-
fallbackMeta.push(`Topic: ${fallbackTopic.title}`);
|
|
3091
|
-
}
|
|
3092
|
-
if (convForFallback?.project_id) {
|
|
3093
|
-
fallbackMeta.push(`Workspace: ${workspaceForFallback || '(project folder not configured)'}`);
|
|
3094
|
-
}
|
|
3095
|
-
if (fallbackMeta.length > 0) {
|
|
3096
|
-
systemPrompt += '\n\n[Project Overview]\n' + fallbackMeta.join('\n');
|
|
3097
|
-
}
|
|
3098
|
-
const effectivePolicy = data.getEffectiveOrchestrationPolicy(convForFallback?.project_id || undefined);
|
|
3099
|
-
systemPrompt += '\n\n' + (0, policy_prompt_1.buildEffectivePolicyPromptSection)(effectivePolicy, {
|
|
3100
|
-
heading: '[Effective Policy]',
|
|
3101
|
-
defaultLine: 'No confirmed special policy is set.',
|
|
3102
|
-
});
|
|
3103
|
-
try {
|
|
3104
|
-
const todoStatus = data.getTodoStatusMarker(convForFallback?.project_id ?? undefined);
|
|
3105
|
-
systemPrompt += `\n\n[TODO Coordination]\nTODO STATUS: ${todoStatus}`;
|
|
3106
|
-
}
|
|
3107
|
-
catch { /* best-effort */ }
|
|
3108
|
-
systemPrompt += '\n\n' + (0, clerk_model_1.buildTodoInstructions)(profile?.name || profile?.id || 'LLM');
|
|
3109
|
-
let hasSummary = false;
|
|
3110
|
-
try {
|
|
3111
|
-
const summaryContext = (0, context_window_1.getPromptContextWindow)(convId, safeguards_1.SAFEGUARDS.CONTEXT_WINDOW_TURNS);
|
|
3112
|
-
if (summaryContext.summary?.summary_text) {
|
|
3113
|
-
const summaryHeader = summaryContext.carriedForward
|
|
3114
|
-
? '[Context Summary (Carried Forward from Previous Conversation in This Topic)]'
|
|
3115
|
-
: '[Context Summary]';
|
|
3116
|
-
systemPrompt += `\n\n${summaryHeader}\n` + summaryContext.summary.summary_text;
|
|
3117
|
-
hasSummary = true;
|
|
3118
|
-
}
|
|
3119
|
-
}
|
|
3120
|
-
catch { /* best-effort */ }
|
|
3121
|
-
try {
|
|
3122
|
-
const turnWindow = hasSummary
|
|
3123
|
-
? safeguards_1.SAFEGUARDS.CONTEXT_WINDOW_TURNS
|
|
3124
|
-
: safeguards_1.SAFEGUARDS.NO_SUMMARY_CONTEXT_WINDOW_TURNS;
|
|
3125
|
-
const promptContext = (0, context_window_1.getPromptContextWindow)(convId, turnWindow);
|
|
3126
|
-
if (promptContext.turns.length > 0) {
|
|
3127
|
-
const turnsHeader = promptContext.carriedForward
|
|
3128
|
-
? `[Recent Messages (Last ${promptContext.turns.length} Turns from Previous Conversation in This Topic)]`
|
|
3129
|
-
: `[Recent Messages (Last ${promptContext.turns.length} Turns)]`;
|
|
3130
|
-
systemPrompt += `\n\n${turnsHeader}\n` + (0, context_window_1.formatTurnsForPrompt)(promptContext.turns);
|
|
3131
|
-
}
|
|
3132
|
-
}
|
|
3133
|
-
catch { /* best-effort */ }
|
|
3134
|
-
}
|
|
3135
|
-
// Resolve LLM runtime.
|
|
3136
|
-
// Desktop local mode intentionally supports only:
|
|
3137
|
-
// - Subscription CLI
|
|
3138
|
-
// - API Key
|
|
3139
|
-
// We do not use subscription-token API routing for local CLI bots.
|
|
3114
|
+
// Resolve LLM runtime early so the local desktop prompt contract can differ
|
|
3115
|
+
// between API/fresh CLI and recurring CLI sessions without affecting server paths.
|
|
3140
3116
|
const runtime = await buildChatRuntime(profile);
|
|
3141
3117
|
let activeProviderName = runtime.providerName;
|
|
3142
3118
|
let activeModelName = runtime.model;
|
|
@@ -3164,6 +3140,33 @@ function startLocalServer(opts) {
|
|
|
3164
3140
|
const cliEpochStartedAt = cliSessionEpochPlan.resumeSessionId
|
|
3165
3141
|
? (cliSessionEpochPlan.existing?.epoch_started_at || localTimestamp())
|
|
3166
3142
|
: localTimestamp();
|
|
3143
|
+
const primaryTopicId = topicId || data.getPrimaryTopicIdForConversation(convId) || undefined;
|
|
3144
|
+
const cliHistoryFilePath = activeIsCliProvider && !cliSessionEpochPlan.resumeSessionId
|
|
3145
|
+
? writeCliBootstrapHistoryFile({
|
|
3146
|
+
conversationId: convId,
|
|
3147
|
+
botId: profile.id,
|
|
3148
|
+
projectPath: llmSpawnCwd,
|
|
3149
|
+
topicId: primaryTopicId,
|
|
3150
|
+
})
|
|
3151
|
+
: null;
|
|
3152
|
+
const directPrompt = buildLocalDesktopDirectPrompt({
|
|
3153
|
+
conversationId: convId,
|
|
3154
|
+
currentBotId: profile.id,
|
|
3155
|
+
currentBotName: profile.name,
|
|
3156
|
+
currentProvider: activeProviderName,
|
|
3157
|
+
userPrompt: message,
|
|
3158
|
+
soulMd: profile.soul_md || 'You are an AI assistant running locally. You have access to project files and can execute code.',
|
|
3159
|
+
projectName: conversation?.project_name || undefined,
|
|
3160
|
+
topicTitle: topicTitle || undefined,
|
|
3161
|
+
workspacePath,
|
|
3162
|
+
timezone: effectiveTimezone,
|
|
3163
|
+
availableTools: toolDefs.map((tool) => ({ name: tool.name, description: tool.description })),
|
|
3164
|
+
isCliRecurring: !!cliSessionEpochPlan.resumeSessionId,
|
|
3165
|
+
cliHistoryFilePath,
|
|
3166
|
+
useCompletionSentinel: resolveDirectCliSessionTransport(activeProviderName, enableCliSessionEpoch, (0, storage_mode_1.isLocalStorageMode)()) === 'pty',
|
|
3167
|
+
});
|
|
3168
|
+
const llmMessages = [{ role: 'user', content: directPrompt.userPrompt }];
|
|
3169
|
+
let systemPrompt = directPrompt.systemPrompt;
|
|
3167
3170
|
if (!activeApiKey) {
|
|
3168
3171
|
return res.status(400).json({ error: `No API key for provider ${profile.provider}. Configure one in Settings.` });
|
|
3169
3172
|
}
|
|
@@ -3175,14 +3178,6 @@ function startLocalServer(opts) {
|
|
|
3175
3178
|
restrictFileAccessToProject: unrestrictedCliProfile ? false : undefined,
|
|
3176
3179
|
abortSignal: routeAbortController.signal,
|
|
3177
3180
|
});
|
|
3178
|
-
const toolManifest = toolDefs
|
|
3179
|
-
.map(t => `- ${t.name}: ${t.description}`)
|
|
3180
|
-
.join('\n');
|
|
3181
|
-
if (toolManifest.trim()) {
|
|
3182
|
-
systemPrompt += unrestrictedCliProfile
|
|
3183
|
-
? '\n\n[Available Tools]\nThe following tools are available in the current runtime:\n' + toolManifest
|
|
3184
|
-
: '\n\n[Available Tools]\nOnly the following tools are enabled for this bot in the current runtime:\n' + toolManifest;
|
|
3185
|
-
}
|
|
3186
3181
|
// Inject pinned messages as context (user-selected cross-bot references)
|
|
3187
3182
|
if (pinnedMessageIds && Array.isArray(pinnedMessageIds) && pinnedMessageIds.length > 0) {
|
|
3188
3183
|
const pinnedLines = [];
|
|
@@ -3240,33 +3235,6 @@ function startLocalServer(opts) {
|
|
|
3240
3235
|
isApproximate: true,
|
|
3241
3236
|
},
|
|
3242
3237
|
});
|
|
3243
|
-
sendEvent('status', {
|
|
3244
|
-
phase: 'thinking',
|
|
3245
|
-
detail: `Sending request to ${activeProviderName}...`,
|
|
3246
|
-
runtime: runtimePayload(),
|
|
3247
|
-
});
|
|
3248
|
-
recordActivity('status', {
|
|
3249
|
-
phase: 'thinking',
|
|
3250
|
-
detail: `Sending request to ${activeProviderName}...`,
|
|
3251
|
-
runtime: runtimePayload(),
|
|
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
|
-
}
|
|
3270
3238
|
let partialPersistedContent = '';
|
|
3271
3239
|
let partialPersistedAt = 0;
|
|
3272
3240
|
const throwIfChatJobCancelled = () => {
|
|
@@ -3306,6 +3274,7 @@ function startLocalServer(opts) {
|
|
|
3306
3274
|
let fullContent = '';
|
|
3307
3275
|
let streamedContent = '';
|
|
3308
3276
|
let streamedAnyChunk = false;
|
|
3277
|
+
let rawCliTranscript = '';
|
|
3309
3278
|
let iteration = 0;
|
|
3310
3279
|
const MAX_ITERATIONS = 10; // Phase 1d: reduced from 20
|
|
3311
3280
|
let totalInputTokens = 0;
|
|
@@ -3316,31 +3285,141 @@ function startLocalServer(opts) {
|
|
|
3316
3285
|
let accumulatedThinking = '';
|
|
3317
3286
|
const thinkingEnabled = !!profile?.show_thinking;
|
|
3318
3287
|
let useInteractiveCliSession = enableCliSessionEpoch;
|
|
3319
|
-
|
|
3288
|
+
const directCliSessionTransport = resolveDirectCliSessionTransport(activeProviderName, useInteractiveCliSession, (0, storage_mode_1.isLocalStorageMode)());
|
|
3289
|
+
const useCodexAppServerInteractive = directCliSessionTransport === 'codex-app-server';
|
|
3290
|
+
const usePtyInteractiveCliSession = directCliSessionTransport === 'pty';
|
|
3291
|
+
const startDetail = `Started response via ${runtimeModeLabel(activeRuntimeMode, activeRuntimeSource) || activeProviderName}`;
|
|
3292
|
+
sendEvent('status', { phase: 'thinking', detail: startDetail });
|
|
3293
|
+
recordActivity('status', { phase: 'thinking', detail: startDetail }, startDetail);
|
|
3294
|
+
if (useCodexAppServerInteractive) {
|
|
3295
|
+
const codexAppServerManager = (0, codex_app_server_manager_1.getCodexAppServerManager)();
|
|
3296
|
+
let appServerAttempt = 0;
|
|
3297
|
+
let forceFreshInteractiveCliSession = false;
|
|
3298
|
+
while (true) {
|
|
3299
|
+
appServerAttempt++;
|
|
3300
|
+
try {
|
|
3301
|
+
const isFreshSession = forceFreshInteractiveCliSession || !cliSessionEpochPlan.resumeSessionId;
|
|
3302
|
+
const result = await codexAppServerManager.runTurn({
|
|
3303
|
+
runtimeMode: 'local_desktop',
|
|
3304
|
+
conversationId: convId,
|
|
3305
|
+
botId: profile.id,
|
|
3306
|
+
botName: profile.name,
|
|
3307
|
+
cwd: llmSpawnCwd,
|
|
3308
|
+
systemPrompt,
|
|
3309
|
+
messages: llmMessages,
|
|
3310
|
+
forceFreshSession: isFreshSession,
|
|
3311
|
+
resumeSessionId: cliSessionEpochPlan.resumeSessionId || undefined,
|
|
3312
|
+
model: activeModelName || profile.model || null,
|
|
3313
|
+
projectId: conversation?.project_id ?? null,
|
|
3314
|
+
codexSettings: {
|
|
3315
|
+
reasoningEffort: profile.codex_reasoning_effort,
|
|
3316
|
+
reasoningSummary: profile.codex_reasoning_summary,
|
|
3317
|
+
personality: profile.codex_personality,
|
|
3318
|
+
serviceTier: profile.codex_service_tier,
|
|
3319
|
+
sandboxPolicy: profile.codex_sandbox_policy,
|
|
3320
|
+
approvalPolicy: profile.codex_approval_policy,
|
|
3321
|
+
},
|
|
3322
|
+
abortSignal: routeAbortController.signal,
|
|
3323
|
+
onChunk: async (chunk) => {
|
|
3324
|
+
streamedAnyChunk = true;
|
|
3325
|
+
streamedContent += chunk;
|
|
3326
|
+
persistAssistantPartial(false);
|
|
3327
|
+
sendEvent('chunk', { text: chunk });
|
|
3328
|
+
},
|
|
3329
|
+
onDetail: async (detail) => {
|
|
3330
|
+
const text = String(detail || '').trim();
|
|
3331
|
+
if (!text)
|
|
3332
|
+
return;
|
|
3333
|
+
sendEvent('status', { phase: 'thinking', detail: text });
|
|
3334
|
+
recordActivity('status', { phase: 'thinking', detail: text }, text);
|
|
3335
|
+
},
|
|
3336
|
+
});
|
|
3337
|
+
if (result.sessionId) {
|
|
3338
|
+
activeCliSessionId = result.sessionId;
|
|
3339
|
+
}
|
|
3340
|
+
if (result.usage) {
|
|
3341
|
+
totalInputTokens += result.usage.inputTokens || 0;
|
|
3342
|
+
totalOutputTokens += result.usage.outputTokens || 0;
|
|
3343
|
+
hasExactUsage = true;
|
|
3344
|
+
}
|
|
3345
|
+
rawCliTranscript = result.rawOutput || '';
|
|
3346
|
+
fullContent = (0, completion_marker_1.stripCompletionSentinel)((result.content || '').trim()).text.trim();
|
|
3347
|
+
if (!fullContent && appServerAttempt < LOCAL_RUNTIME_RETRY_LIMIT) {
|
|
3348
|
+
forceFreshInteractiveCliSession = true;
|
|
3349
|
+
codexAppServerManager.closeSessionByConversation(convId, profile.id);
|
|
3350
|
+
const retryDetail = `Selected runtime returned an empty response; retrying with a fresh ${activeProviderName} session (${appServerAttempt + 1}/${LOCAL_RUNTIME_RETRY_LIMIT})...`;
|
|
3351
|
+
console.warn(chalk_1.default.yellow(` [chat] ${retryDetail}`));
|
|
3352
|
+
await pauseLocalRuntimeRetry(appServerAttempt);
|
|
3353
|
+
continue;
|
|
3354
|
+
}
|
|
3355
|
+
break;
|
|
3356
|
+
}
|
|
3357
|
+
catch (codexErr) {
|
|
3358
|
+
if (routeAbortController.signal.aborted || codexErr?.name === 'AbortError') {
|
|
3359
|
+
throw codexErr;
|
|
3360
|
+
}
|
|
3361
|
+
if (appServerAttempt >= LOCAL_RUNTIME_RETRY_LIMIT || !shouldRetrySelectedLocalRuntime(codexErr)) {
|
|
3362
|
+
throw codexErr;
|
|
3363
|
+
}
|
|
3364
|
+
forceFreshInteractiveCliSession = true;
|
|
3365
|
+
codexAppServerManager.closeSessionByConversation(convId, profile.id);
|
|
3366
|
+
const retryDetail = `Selected runtime failed (${codexErr?.message || codexErr}); retrying with a fresh ${activeProviderName} session (${appServerAttempt + 1}/${LOCAL_RUNTIME_RETRY_LIMIT})...`;
|
|
3367
|
+
console.warn(chalk_1.default.yellow(` [chat] ${retryDetail}`));
|
|
3368
|
+
await pauseLocalRuntimeRetry(appServerAttempt);
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
if (usePtyInteractiveCliSession) {
|
|
3373
|
+
if (activeProviderName !== 'claude-cli') {
|
|
3374
|
+
throw new Error(`Legacy PTY interactive sessions are Claude-only in local desktop mode; expected ${activeProviderName} to use Codex app-server.`);
|
|
3375
|
+
}
|
|
3320
3376
|
const ptyManager = (0, local_cli_pty_manager_1.getLocalCliPtySessionManager)();
|
|
3321
3377
|
let ptyAttempt = 0;
|
|
3378
|
+
let forceFreshInteractiveCliSession = false;
|
|
3379
|
+
let currentAttemptSessionId = null;
|
|
3380
|
+
let currentAttemptWasFreshSession = false;
|
|
3322
3381
|
while (true) {
|
|
3323
3382
|
ptyAttempt++;
|
|
3324
3383
|
try {
|
|
3384
|
+
// Pattern A (Claude CLI): generate a sequential session ID for new sessions
|
|
3385
|
+
// Pattern B (Codex CLI): let the CLI generate its own ID
|
|
3386
|
+
const isFreshSession = forceFreshInteractiveCliSession || !cliSessionEpochPlan.resumeSessionId;
|
|
3387
|
+
const newSessionId = isFreshSession && activeProviderName === 'claude-cli'
|
|
3388
|
+
? data.generateNextSessionId()
|
|
3389
|
+
: undefined;
|
|
3390
|
+
currentAttemptWasFreshSession = isFreshSession;
|
|
3391
|
+
currentAttemptSessionId = newSessionId || cliSessionEpochPlan.resumeSessionId || activeCliSessionId || null;
|
|
3325
3392
|
const result = await ptyManager.runTurn({
|
|
3326
3393
|
conversationId: convId,
|
|
3327
3394
|
botId: profile.id,
|
|
3328
|
-
provider:
|
|
3395
|
+
provider: 'claude-cli',
|
|
3329
3396
|
cwd: llmSpawnCwd,
|
|
3330
3397
|
systemPrompt,
|
|
3331
3398
|
messages: llmMessages,
|
|
3332
|
-
forceFreshSession:
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3399
|
+
forceFreshSession: isFreshSession,
|
|
3400
|
+
resumeSessionId: cliSessionEpochPlan.resumeSessionId || undefined,
|
|
3401
|
+
newSessionId,
|
|
3402
|
+
abortSignal: routeAbortController.signal,
|
|
3403
|
+
onRawChunk: async (chunk) => {
|
|
3404
|
+
sendEvent('terminal_chunk', {
|
|
3405
|
+
text: chunk,
|
|
3406
|
+
provider: activeProviderName,
|
|
3407
|
+
botId: profile?.id || null,
|
|
3408
|
+
agentName: profile?.name || null,
|
|
3338
3409
|
});
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3410
|
+
},
|
|
3411
|
+
onChunk: async (chunk) => {
|
|
3412
|
+
streamedAnyChunk = true;
|
|
3413
|
+
streamedContent += chunk;
|
|
3414
|
+
persistAssistantPartial(false);
|
|
3415
|
+
sendEvent('chunk', { text: chunk });
|
|
3416
|
+
},
|
|
3417
|
+
onDetail: async (detail) => {
|
|
3418
|
+
const text = String(detail || '').trim();
|
|
3419
|
+
if (!text)
|
|
3420
|
+
return;
|
|
3421
|
+
sendEvent('status', { phase: 'thinking', detail: text });
|
|
3422
|
+
recordActivity('status', { phase: 'thinking', detail: text }, text);
|
|
3344
3423
|
},
|
|
3345
3424
|
});
|
|
3346
3425
|
if (result.sessionId) {
|
|
@@ -3351,25 +3430,36 @@ function startLocalServer(opts) {
|
|
|
3351
3430
|
totalOutputTokens += result.usage.outputTokens || 0;
|
|
3352
3431
|
hasExactUsage = true;
|
|
3353
3432
|
}
|
|
3354
|
-
|
|
3433
|
+
rawCliTranscript = result.rawOutput || '';
|
|
3434
|
+
fullContent = (0, completion_marker_1.stripCompletionSentinel)((result.content || '').trim()).text.trim();
|
|
3435
|
+
if (!fullContent && ptyAttempt < LOCAL_RUNTIME_RETRY_LIMIT) {
|
|
3436
|
+
forceFreshInteractiveCliSession = true;
|
|
3437
|
+
ptyManager.closeSessionByConversation(convId, profile.id);
|
|
3438
|
+
const retryDetail = `Selected runtime returned an empty response; retrying with a fresh ${activeProviderName} session (${ptyAttempt + 1}/${LOCAL_RUNTIME_RETRY_LIMIT})...`;
|
|
3439
|
+
console.warn(chalk_1.default.yellow(` [chat] ${retryDetail}`));
|
|
3440
|
+
await pauseLocalRuntimeRetry(ptyAttempt);
|
|
3441
|
+
continue;
|
|
3442
|
+
}
|
|
3355
3443
|
break;
|
|
3356
3444
|
}
|
|
3357
3445
|
catch (ptyErr) {
|
|
3446
|
+
if (routeAbortController.signal.aborted || ptyErr?.name === 'AbortError') {
|
|
3447
|
+
throw ptyErr;
|
|
3448
|
+
}
|
|
3358
3449
|
if (ptyAttempt >= LOCAL_RUNTIME_RETRY_LIMIT || !shouldRetrySelectedLocalRuntime(ptyErr)) {
|
|
3359
3450
|
throw ptyErr;
|
|
3360
3451
|
}
|
|
3361
|
-
const
|
|
3452
|
+
const startupRetry = isClaudeFreshSessionStartupFailure(ptyErr);
|
|
3453
|
+
if (startupRetry || currentAttemptWasFreshSession) {
|
|
3454
|
+
forceFreshInteractiveCliSession = true;
|
|
3455
|
+
ptyManager.closeSessionByConversation(convId, profile.id);
|
|
3456
|
+
}
|
|
3457
|
+
const retryDetail = startupRetry
|
|
3458
|
+
? `Fresh ${activeProviderName} session ${currentAttemptSessionId || '(unknown)'} did not create a transcript within 7s; killing it and retrying with a new session (${ptyAttempt + 1}/${LOCAL_RUNTIME_RETRY_LIMIT})...`
|
|
3459
|
+
: currentAttemptWasFreshSession && activeProviderName === 'claude-cli'
|
|
3460
|
+
? `Fresh ${activeProviderName} session ${currentAttemptSessionId || '(unknown)'} failed (${ptyErr?.message || ptyErr}); killing it and retrying with a new session (${ptyAttempt + 1}/${LOCAL_RUNTIME_RETRY_LIMIT})...`
|
|
3461
|
+
: `Selected runtime failed (${ptyErr?.message || ptyErr}); retrying the same connection (${ptyAttempt + 1}/${LOCAL_RUNTIME_RETRY_LIMIT})...`;
|
|
3362
3462
|
console.warn(chalk_1.default.yellow(` [chat] ${retryDetail}`));
|
|
3363
|
-
sendEvent('status', {
|
|
3364
|
-
phase: 'thinking',
|
|
3365
|
-
detail: retryDetail,
|
|
3366
|
-
runtime: runtimePayload(),
|
|
3367
|
-
});
|
|
3368
|
-
recordActivity('status', {
|
|
3369
|
-
phase: 'thinking',
|
|
3370
|
-
detail: retryDetail,
|
|
3371
|
-
runtime: runtimePayload(),
|
|
3372
|
-
}, retryDetail);
|
|
3373
3463
|
await pauseLocalRuntimeRetry(ptyAttempt);
|
|
3374
3464
|
}
|
|
3375
3465
|
}
|
|
@@ -3379,10 +3469,6 @@ function startLocalServer(opts) {
|
|
|
3379
3469
|
iteration++;
|
|
3380
3470
|
let iterationFirstChunk = true;
|
|
3381
3471
|
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
3472
|
let response;
|
|
3387
3473
|
const chatOptions = {
|
|
3388
3474
|
messages: llmMessages,
|
|
@@ -3398,8 +3484,6 @@ function startLocalServer(opts) {
|
|
|
3398
3484
|
throwIfChatJobCancelled();
|
|
3399
3485
|
if (iterationFirstChunk) {
|
|
3400
3486
|
iterationFirstChunk = false;
|
|
3401
|
-
sendEvent('status', { phase: 'generating' });
|
|
3402
|
-
recordActivity('status', { phase: 'generating' }, 'Generating response...');
|
|
3403
3487
|
}
|
|
3404
3488
|
streamedAnyChunk = true;
|
|
3405
3489
|
streamedContent += chunk;
|
|
@@ -3531,7 +3615,7 @@ function startLocalServer(opts) {
|
|
|
3531
3615
|
continue;
|
|
3532
3616
|
}
|
|
3533
3617
|
// Final response (guard against defer-only filler)
|
|
3534
|
-
const candidate = (response.content || '').trim();
|
|
3618
|
+
const candidate = (0, completion_marker_1.stripCompletionSentinel)((response.content || '').trim()).text.trim();
|
|
3535
3619
|
if (!forcedFinalizationPass && (0, response_guard_1.isLikelyDeferredReply)(candidate)) {
|
|
3536
3620
|
forcedFinalizationPass = true;
|
|
3537
3621
|
llmMessages.push({ role: 'assistant', content: candidate });
|
|
@@ -3539,14 +3623,12 @@ function startLocalServer(opts) {
|
|
|
3539
3623
|
role: 'user',
|
|
3540
3624
|
content: 'Provide the final answer now. Do not say you will check later. Either provide concrete results or explicitly say what is unavailable.',
|
|
3541
3625
|
});
|
|
3542
|
-
sendEvent('status', { phase: 'thinking', detail: 'Finalizing response...' });
|
|
3543
|
-
recordActivity('status', { phase: 'thinking', detail: 'Finalizing response...' }, 'Finalizing response...');
|
|
3544
3626
|
continue;
|
|
3545
3627
|
}
|
|
3546
3628
|
fullContent = candidate;
|
|
3547
3629
|
break;
|
|
3548
3630
|
}
|
|
3549
|
-
const persistedContent = fullContent || streamedContent.trim();
|
|
3631
|
+
const persistedContent = (0, completion_marker_1.stripCompletionSentinel)(fullContent || streamedContent.trim()).text.trim();
|
|
3550
3632
|
if (!persistedContent) {
|
|
3551
3633
|
throw new Error('Assistant returned no final response');
|
|
3552
3634
|
}
|
|
@@ -3594,6 +3676,7 @@ function startLocalServer(opts) {
|
|
|
3594
3676
|
model: modelWithRuntime || null,
|
|
3595
3677
|
botId: profile.id,
|
|
3596
3678
|
agentName: profile.name,
|
|
3679
|
+
resultArtifact: useInteractiveCliSession ? (rawCliTranscript || null) : undefined,
|
|
3597
3680
|
}) || data.addMessage(convId, 'assistant', persistedContent, modelWithRuntime || undefined, undefined, profile.id, profile.name))
|
|
3598
3681
|
: data.addMessage(convId, 'assistant', persistedContent, modelWithRuntime || undefined, undefined, profile.id, profile.name);
|
|
3599
3682
|
data.attachMessageActivitiesToMessage(activityStreamId, savedMessage.id);
|
|
@@ -3606,6 +3689,7 @@ function startLocalServer(opts) {
|
|
|
3606
3689
|
summary: 'Final assistant response',
|
|
3607
3690
|
payload: {
|
|
3608
3691
|
content: persistedContent,
|
|
3692
|
+
...(useInteractiveCliSession && rawCliTranscript ? { rawOutput: rawCliTranscript } : {}),
|
|
3609
3693
|
runtime: runtimePayload(),
|
|
3610
3694
|
},
|
|
3611
3695
|
expiresAt: activityExpiresAt,
|
|
@@ -3689,6 +3773,7 @@ function startLocalServer(opts) {
|
|
|
3689
3773
|
}
|
|
3690
3774
|
finally {
|
|
3691
3775
|
req.off?.('close', abortOnClientClose);
|
|
3776
|
+
res.off?.('close', abortOnClientClose);
|
|
3692
3777
|
}
|
|
3693
3778
|
});
|
|
3694
3779
|
// ─── Memory Facts ───────────────────────────────────────────
|
|
@@ -3807,11 +3892,18 @@ function startLocalServer(opts) {
|
|
|
3807
3892
|
const clerkProvider = data.getSetting('clerk_provider');
|
|
3808
3893
|
const clerkModel = data.getSetting('clerk_model');
|
|
3809
3894
|
const hasKey = !!data.getSetting('clerk_api_key');
|
|
3895
|
+
const currentOrchestrator = data.getCurrentOrchestratorSelection();
|
|
3896
|
+
const currentDefaultBot = data.getDefaultAgentProfile();
|
|
3810
3897
|
res.json({
|
|
3811
3898
|
provider: clerkProvider || null,
|
|
3812
3899
|
model: clerkModel || null,
|
|
3813
3900
|
hasApiKey: hasKey,
|
|
3814
3901
|
configured: !!(clerkProvider && clerkModel && hasKey),
|
|
3902
|
+
isOrchestrator: data.isClerkOrchestratorEnabled(),
|
|
3903
|
+
currentOrchestrator,
|
|
3904
|
+
currentDefaultBot: currentDefaultBot
|
|
3905
|
+
? { botId: currentDefaultBot.id, botName: currentDefaultBot.name }
|
|
3906
|
+
: null,
|
|
3815
3907
|
});
|
|
3816
3908
|
}
|
|
3817
3909
|
catch (err) {
|
|
@@ -3820,13 +3912,16 @@ function startLocalServer(opts) {
|
|
|
3820
3912
|
});
|
|
3821
3913
|
app.put('/api/clerk/config', (req, res) => {
|
|
3822
3914
|
try {
|
|
3823
|
-
const { provider, model, apiKey } = req.body;
|
|
3915
|
+
const { provider, model, apiKey, isOrchestrator } = req.body;
|
|
3824
3916
|
if (!provider || !model)
|
|
3825
3917
|
return res.status(400).json({ error: 'provider and model are required' });
|
|
3826
3918
|
data.setSetting('clerk_provider', provider);
|
|
3827
3919
|
data.setSetting('clerk_model', model);
|
|
3828
3920
|
if (apiKey)
|
|
3829
3921
|
data.setSetting('clerk_api_key', apiKey);
|
|
3922
|
+
if (typeof isOrchestrator === 'boolean') {
|
|
3923
|
+
data.setClerkAsOrchestrator(isOrchestrator);
|
|
3924
|
+
}
|
|
3830
3925
|
// Reset clerk instance so it picks up new config
|
|
3831
3926
|
const { resetClerk } = require('./clerk-model');
|
|
3832
3927
|
resetClerk();
|
|
@@ -3842,11 +3937,31 @@ function startLocalServer(opts) {
|
|
|
3842
3937
|
const { prompt } = req.body;
|
|
3843
3938
|
if (!prompt)
|
|
3844
3939
|
return res.status(400).json({ error: 'prompt is required' });
|
|
3845
|
-
const clerk = (0, clerk_model_1.getClerk)({ runtimeMode: 'local_desktop' });
|
|
3846
|
-
if (!clerk)
|
|
3847
|
-
return res.json({ routing: 'default', reason: 'No clerk configured' });
|
|
3848
3940
|
const agents = data.listAgentProfiles();
|
|
3849
|
-
const
|
|
3941
|
+
const normalized = String(prompt || '').toLowerCase();
|
|
3942
|
+
const defaultAgent = data.getDefaultAgentProfile() || agents[0];
|
|
3943
|
+
const namedAgent = agents.find((agent) => normalized.includes(agent.name.toLowerCase()));
|
|
3944
|
+
const roleMatchedAgent = /\b(qa|review|verify|test)\b/i.test(normalized)
|
|
3945
|
+
? agents.find((agent) => /\bqa|review\b/i.test(String(agent.role_class || agent.role_label || '')))
|
|
3946
|
+
: /\b(research|brainstorm|analy[sz]e|evaluate|investigate)\b/i.test(normalized)
|
|
3947
|
+
? agents.find((agent) => /\bresearch|analyst\b/i.test(String(agent.role_class || agent.role_label || '')))
|
|
3948
|
+
: /\b(build|code|implement|create|fix|write|update)\b/i.test(normalized)
|
|
3949
|
+
? agents.find((agent) => /\bcode|coding|developer|builder\b/i.test(String(agent.role_class || agent.role_label || '')))
|
|
3950
|
+
: undefined;
|
|
3951
|
+
const routeAgent = namedAgent || roleMatchedAgent || defaultAgent;
|
|
3952
|
+
const route = routeAgent
|
|
3953
|
+
? {
|
|
3954
|
+
agentId: routeAgent.id,
|
|
3955
|
+
agentName: routeAgent.name,
|
|
3956
|
+
provider: routeAgent.provider,
|
|
3957
|
+
model: routeAgent.model,
|
|
3958
|
+
reasoning: namedAgent
|
|
3959
|
+
? 'Matched the explicitly named bot.'
|
|
3960
|
+
: roleMatchedAgent
|
|
3961
|
+
? 'Matched the prompt to the configured bot role.'
|
|
3962
|
+
: 'Fell back to the configured default bot.',
|
|
3963
|
+
}
|
|
3964
|
+
: { routing: 'default', reason: 'No bot configured' };
|
|
3850
3965
|
res.json(route);
|
|
3851
3966
|
}
|
|
3852
3967
|
catch (err) {
|
|
@@ -4752,6 +4867,13 @@ function runtimeModeLabel(mode, runtimeSource) {
|
|
|
4752
4867
|
return (0, subscription_runtime_1.claudeSubscriptionRuntimeLabel)(runtimeSource);
|
|
4753
4868
|
return 'API Key';
|
|
4754
4869
|
}
|
|
4870
|
+
function resolveDirectCliSessionTransport(providerName, enableCliSessionEpoch, localStorageMode) {
|
|
4871
|
+
if (!enableCliSessionEpoch)
|
|
4872
|
+
return 'none';
|
|
4873
|
+
if (providerName === 'codex-cli' && localStorageMode)
|
|
4874
|
+
return 'codex-app-server';
|
|
4875
|
+
return 'pty';
|
|
4876
|
+
}
|
|
4755
4877
|
function runtimePayloadForDisplay(providerName, model, runtimeMode, runtimeSource) {
|
|
4756
4878
|
return {
|
|
4757
4879
|
mode: runtimeMode,
|
|
@@ -4783,6 +4905,9 @@ function configuredRuntimeLabelForProfile(profile) {
|
|
|
4783
4905
|
async function buildChatRuntimeForTest(profile) {
|
|
4784
4906
|
return buildChatRuntime(profile);
|
|
4785
4907
|
}
|
|
4908
|
+
function resolveDirectCliSessionTransportForTest(providerName, enableCliSessionEpoch, localStorageMode) {
|
|
4909
|
+
return resolveDirectCliSessionTransport(providerName, enableCliSessionEpoch, localStorageMode);
|
|
4910
|
+
}
|
|
4786
4911
|
function buildConfiguredMessageModel(profile) {
|
|
4787
4912
|
if (!profile)
|
|
4788
4913
|
return '';
|
|
@@ -4808,100 +4933,231 @@ function hydrateMessageDisplayMetadata(message) {
|
|
|
4808
4933
|
model: [modelBase, runtimeLabel].filter(Boolean).join(' | '),
|
|
4809
4934
|
};
|
|
4810
4935
|
}
|
|
4811
|
-
function
|
|
4812
|
-
const
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
const
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4936
|
+
function buildLocalDesktopDirectPrompt(input) {
|
|
4937
|
+
const isCliProvider = index_1.CLI_PROVIDERS.has(input.currentProvider);
|
|
4938
|
+
const useCompletionSentinel = !!input.useCompletionSentinel;
|
|
4939
|
+
const useCliHistoryBootstrap = isCliProvider && !input.isCliRecurring;
|
|
4940
|
+
const crossBotReplies = isCliProvider && input.isCliRecurring
|
|
4941
|
+
? getLatestOtherBotReplies(input.conversationId, input.currentBotId, input.currentBotName)
|
|
4942
|
+
: [];
|
|
4943
|
+
const contract = !isCliProvider || !input.isCliRecurring
|
|
4944
|
+
? 'api_or_fresh_cli'
|
|
4945
|
+
: crossBotReplies.length > 0
|
|
4946
|
+
? 'cli_recurring_multibot'
|
|
4947
|
+
: 'cli_recurring_single';
|
|
4948
|
+
const lines = [
|
|
4949
|
+
'[Bot Identity]',
|
|
4950
|
+
input.soulMd.trim(),
|
|
4951
|
+
'',
|
|
4952
|
+
'If the provided context is not enough, use available tools to get what you need.',
|
|
4953
|
+
'',
|
|
4954
|
+
'[Response Style]',
|
|
4955
|
+
'Write in short readable paragraphs.',
|
|
4956
|
+
'Put a blank line between distinct ideas.',
|
|
4957
|
+
'Use bullets when listing findings, steps, or issues.',
|
|
4958
|
+
'Do not return one dense wall of text.',
|
|
4959
|
+
'Keep progress updates compact and factual.',
|
|
4960
|
+
'Do not mention bootstrap files, history files, file paths, or that you loaded context unless the user explicitly asks about them.',
|
|
4961
|
+
];
|
|
4962
|
+
if (useCompletionSentinel) {
|
|
4963
|
+
lines.push(completion_marker_1.CLI_COMPLETION_INSTRUCTION);
|
|
4821
4964
|
}
|
|
4822
|
-
const
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4965
|
+
const projectLines = [];
|
|
4966
|
+
if (input.projectName)
|
|
4967
|
+
projectLines.push(`Project: ${input.projectName}`);
|
|
4968
|
+
if (input.topicTitle)
|
|
4969
|
+
projectLines.push(`Topic: ${input.topicTitle}`);
|
|
4970
|
+
projectLines.push(`Workspace: ${input.workspacePath || '(project folder not configured)'}`);
|
|
4971
|
+
if (input.timezone)
|
|
4972
|
+
projectLines.push(`Timezone: ${input.timezone}`);
|
|
4973
|
+
lines.push('', '[Project Overview]', ...projectLines);
|
|
4974
|
+
const toolManifest = isCliProvider ? '' : buildLocalDesktopToolManifest(input.availableTools, contract);
|
|
4975
|
+
if (toolManifest) {
|
|
4976
|
+
lines.push('', '[Available Tools]', toolManifest);
|
|
4977
|
+
if (/\bsearch_local_memory\b|\bsearch_memory\b/i.test(toolManifest)) {
|
|
4978
|
+
lines.push('If the user refers to prior project or conversation context that is not included here, search with these tools first.');
|
|
4827
4979
|
}
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4980
|
+
}
|
|
4981
|
+
if (contract === 'api_or_fresh_cli' && !useCliHistoryBootstrap) {
|
|
4982
|
+
const summaryWindow = (0, context_window_1.getPromptContextWindow)(input.conversationId, safeguards_1.SAFEGUARDS.CONTEXT_WINDOW_TURNS);
|
|
4983
|
+
if (summaryWindow.summary?.summary_text?.trim()) {
|
|
4984
|
+
lines.push('', '[Context Summary]', summaryWindow.summary.summary_text.trim());
|
|
4831
4985
|
}
|
|
4832
|
-
const
|
|
4833
|
-
|
|
4834
|
-
|
|
4986
|
+
const recentTurnsWindow = (0, context_window_1.getPromptContextWindow)(input.conversationId, summaryWindow.summary?.summary_text?.trim()
|
|
4987
|
+
? safeguards_1.SAFEGUARDS.CONTEXT_WINDOW_TURNS
|
|
4988
|
+
: safeguards_1.SAFEGUARDS.NO_SUMMARY_CONTEXT_WINDOW_TURNS);
|
|
4989
|
+
if (recentTurnsWindow.turns.length > 0) {
|
|
4990
|
+
lines.push('', '[Recent Messages]', (0, context_window_1.formatTurnsForPrompt)(recentTurnsWindow.turns));
|
|
4835
4991
|
}
|
|
4836
|
-
return {};
|
|
4837
4992
|
}
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
return {};
|
|
4993
|
+
let effectiveUserPrompt = input.userPrompt;
|
|
4994
|
+
if (useCliHistoryBootstrap && input.cliHistoryFilePath) {
|
|
4995
|
+
effectiveUserPrompt = [
|
|
4996
|
+
`Please read the history file at: ${input.cliHistoryFilePath}`,
|
|
4997
|
+
'Use it for context only.',
|
|
4998
|
+
'There is no need to mention the file, its path, or that you loaded context unless the user explicitly asks about it.',
|
|
4999
|
+
'Then respond to the user request below.',
|
|
5000
|
+
'',
|
|
5001
|
+
'Current user request:',
|
|
5002
|
+
input.userPrompt,
|
|
5003
|
+
].join('\n');
|
|
4850
5004
|
}
|
|
4851
|
-
if (
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
5005
|
+
if (contract === 'cli_recurring_multibot' && crossBotReplies.length > 0) {
|
|
5006
|
+
effectiveUserPrompt = [
|
|
5007
|
+
'[Cross-Bot Context]',
|
|
5008
|
+
...crossBotReplies.map((line) => `- ${line}`),
|
|
5009
|
+
'',
|
|
5010
|
+
'Current user request:',
|
|
5011
|
+
input.userPrompt,
|
|
5012
|
+
].join('\n');
|
|
4857
5013
|
}
|
|
4858
|
-
if (
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
5014
|
+
if (useCompletionSentinel) {
|
|
5015
|
+
effectiveUserPrompt = [
|
|
5016
|
+
effectiveUserPrompt,
|
|
5017
|
+
'',
|
|
5018
|
+
`Required final line when the response is fully complete: ${completion_marker_1.CLI_COMPLETION_SENTINEL}`,
|
|
5019
|
+
'Do not mention or explain the tag.',
|
|
5020
|
+
].join('\n');
|
|
4862
5021
|
}
|
|
4863
|
-
|
|
4864
|
-
|
|
5022
|
+
return {
|
|
5023
|
+
systemPrompt: lines.join('\n').trim(),
|
|
5024
|
+
userPrompt: effectiveUserPrompt,
|
|
5025
|
+
};
|
|
5026
|
+
}
|
|
5027
|
+
function buildLocalDesktopDirectPromptForTest(input) {
|
|
5028
|
+
return buildLocalDesktopDirectPrompt(input);
|
|
5029
|
+
}
|
|
5030
|
+
function writeCliBootstrapHistoryFile(input) {
|
|
5031
|
+
const historyDir = path.join(input.projectPath, 'history');
|
|
5032
|
+
const filename = `${input.conversationId}--${input.botId}.txt`;
|
|
5033
|
+
const historyFilePath = path.join(historyDir, filename);
|
|
5034
|
+
const historyContent = buildCliBootstrapHistoryContent(input.conversationId, input.topicId);
|
|
5035
|
+
try {
|
|
5036
|
+
fs.mkdirSync(historyDir, { recursive: true });
|
|
5037
|
+
cleanupStaleCliHistoryFiles(historyDir);
|
|
5038
|
+
fs.writeFileSync(historyFilePath, historyContent, 'utf8');
|
|
5039
|
+
return historyFilePath;
|
|
5040
|
+
}
|
|
5041
|
+
catch (err) {
|
|
5042
|
+
console.warn(chalk_1.default.yellow(` [cli-history] Failed to write bootstrap history file: ${err instanceof Error ? err.message : String(err)}`));
|
|
5043
|
+
return null;
|
|
4865
5044
|
}
|
|
4866
|
-
return {};
|
|
4867
5045
|
}
|
|
4868
|
-
|
|
4869
|
-
|
|
5046
|
+
function buildCliBootstrapHistoryContent(conversationId, topicId) {
|
|
5047
|
+
const lines = [];
|
|
5048
|
+
const previousConversationId = topicId ? data.getPreviousConversationInTopic(conversationId)?.id || null : null;
|
|
5049
|
+
lines.push('[Bootstrap History]');
|
|
5050
|
+
if (!topicId) {
|
|
5051
|
+
lines.push('No prior topic history is available for this conversation.');
|
|
5052
|
+
return lines.join('\n').trim();
|
|
5053
|
+
}
|
|
5054
|
+
if (!previousConversationId) {
|
|
5055
|
+
lines.push('No prior topic history is available for this conversation.');
|
|
5056
|
+
return lines.join('\n').trim();
|
|
5057
|
+
}
|
|
5058
|
+
const rollingSummary = (0, context_window_1.getLatestRollingSummary)(previousConversationId);
|
|
5059
|
+
const recentTurns = (0, context_window_1.getRecentTurns)(previousConversationId, 5);
|
|
5060
|
+
if (rollingSummary?.summary_text?.trim()) {
|
|
5061
|
+
lines.push('', '[Running Summary]', rollingSummary.summary_text.trim());
|
|
5062
|
+
}
|
|
5063
|
+
if (recentTurns.length > 0) {
|
|
5064
|
+
lines.push('', '[Last 5 Turns]', (0, context_window_1.formatTurnsForPrompt)(recentTurns));
|
|
5065
|
+
}
|
|
5066
|
+
else {
|
|
5067
|
+
lines.push('', '[Last 5 Turns]', '(no recent topic turns)');
|
|
5068
|
+
}
|
|
5069
|
+
return lines.join('\n').trim();
|
|
5070
|
+
}
|
|
5071
|
+
function cleanupStaleCliHistoryFiles(historyDir) {
|
|
5072
|
+
const maxAgeMs = 14 * 24 * 60 * 60 * 1000;
|
|
5073
|
+
const now = Date.now();
|
|
5074
|
+
try {
|
|
5075
|
+
const files = fs.readdirSync(historyDir);
|
|
5076
|
+
for (const file of files) {
|
|
5077
|
+
if (!file.toLowerCase().endsWith('.txt'))
|
|
5078
|
+
continue;
|
|
5079
|
+
const fullPath = path.join(historyDir, file);
|
|
5080
|
+
try {
|
|
5081
|
+
const stat = fs.statSync(fullPath);
|
|
5082
|
+
if (now - stat.mtimeMs > maxAgeMs) {
|
|
5083
|
+
fs.unlinkSync(fullPath);
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
catch {
|
|
5087
|
+
// best effort cleanup only
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
}
|
|
5091
|
+
catch {
|
|
5092
|
+
// best effort cleanup only
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
5095
|
+
function buildLocalDesktopToolManifest(availableTools, contract) {
|
|
5096
|
+
const selectedTools = contract === 'api_or_fresh_cli'
|
|
5097
|
+
? availableTools
|
|
5098
|
+
: availableTools.filter((tool) => ['search_local_memory', 'search_memory'].includes(tool.name));
|
|
5099
|
+
return selectedTools
|
|
5100
|
+
.map((tool) => `- ${tool.name}: ${tool.description}`)
|
|
5101
|
+
.join('\n')
|
|
5102
|
+
.trim();
|
|
5103
|
+
}
|
|
5104
|
+
function getLatestOtherBotReplies(conversationId, currentBotId, currentBotName) {
|
|
5105
|
+
const turns = (0, context_window_1.getPromptContextWindow)(conversationId, safeguards_1.SAFEGUARDS.NO_SUMMARY_CONTEXT_WINDOW_TURNS).turns;
|
|
5106
|
+
const seen = new Set();
|
|
5107
|
+
const replies = [];
|
|
5108
|
+
for (let turnIndex = turns.length - 1; turnIndex >= 0; turnIndex -= 1) {
|
|
5109
|
+
const turn = turns[turnIndex];
|
|
5110
|
+
for (let responseIndex = turn.responses.length - 1; responseIndex >= 0; responseIndex -= 1) {
|
|
5111
|
+
const response = turn.responses[responseIndex];
|
|
5112
|
+
if (response.role !== 'assistant')
|
|
5113
|
+
continue;
|
|
5114
|
+
const identity = String(response.bot_id || response.agent_name || '').trim();
|
|
5115
|
+
if (!identity)
|
|
5116
|
+
continue;
|
|
5117
|
+
const sameBot = (response.bot_id && response.bot_id === currentBotId)
|
|
5118
|
+
|| (response.agent_name && response.agent_name === currentBotName);
|
|
5119
|
+
if (sameBot || seen.has(identity))
|
|
5120
|
+
continue;
|
|
5121
|
+
const content = String(response.content || '').trim();
|
|
5122
|
+
if (!content)
|
|
5123
|
+
continue;
|
|
5124
|
+
seen.add(identity);
|
|
5125
|
+
replies.unshift(`${response.agent_name || 'Assistant'} (${response.created_at}): ${content}`);
|
|
5126
|
+
}
|
|
5127
|
+
}
|
|
5128
|
+
return replies;
|
|
5129
|
+
}
|
|
5130
|
+
function classifyOrchestratorProgress(status) {
|
|
4870
5131
|
const trimmed = String(status || '').trim();
|
|
5132
|
+
if (!trimmed)
|
|
5133
|
+
return {};
|
|
4871
5134
|
const match = trimmed.match(/^([a-z][a-z0-9_ -]{1,48})::\s*(.+)$/i);
|
|
4872
5135
|
if (!match)
|
|
4873
|
-
return
|
|
5136
|
+
return {};
|
|
4874
5137
|
const roleName = match[1].trim().toLowerCase();
|
|
4875
5138
|
const detail = match[2].trim();
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
if (/classif/i.test(detail))
|
|
4879
|
-
return null; // skip classification (too early)
|
|
4880
|
-
if (/decomposed request into (\d+)/i.test(detail)) {
|
|
4881
|
-
const m = detail.match(/(\d+)/);
|
|
4882
|
-
return `I've broken this down into ${m?.[1] || 'multiple'} steps. Let me work through them...`;
|
|
4883
|
-
}
|
|
4884
|
-
if (/routed single intent/i.test(detail))
|
|
4885
|
-
return null; // handled by dispatch
|
|
5139
|
+
if (/(intent_classifier|orchestration_planner|dispatch_controller|policy_interpreter|verifier)/i.test(roleName)) {
|
|
5140
|
+
return {};
|
|
4886
5141
|
}
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
5142
|
+
if (roleName === 'orchestrator') {
|
|
5143
|
+
if (/^understanding request$/i.test(detail)
|
|
5144
|
+
|| /^still understanding the request$/i.test(detail)
|
|
5145
|
+
|| /is working on the request/i.test(detail)
|
|
5146
|
+
|| /completed the request/i.test(detail)) {
|
|
5147
|
+
return {};
|
|
4892
5148
|
}
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
return `Connecting to ${connectMatch[1]}...`;
|
|
5149
|
+
if (/hit an issue while working/i.test(detail)) {
|
|
5150
|
+
return { activityText: detail };
|
|
4896
5151
|
}
|
|
4897
5152
|
}
|
|
5153
|
+
return {};
|
|
5154
|
+
}
|
|
5155
|
+
/** Derive user-visible interim messages from orchestrator progress for key transitions */
|
|
5156
|
+
function deriveOrchestratorInterimMessage(status) {
|
|
5157
|
+
void status;
|
|
5158
|
+
// Intent classification → "I'll analyze this request..."
|
|
5159
|
+
// Dispatch → "I'm sending this to [Bot Name]..."
|
|
4898
5160
|
// Orchestration planner → workflow planning
|
|
4899
|
-
if (roleName === 'orchestration_planner') {
|
|
4900
|
-
const stepsMatch = detail.match(/prepared (\d+) workflow steps/i);
|
|
4901
|
-
if (stepsMatch) {
|
|
4902
|
-
return `I've prepared a ${stepsMatch[1]}-step workflow. Starting execution...`;
|
|
4903
|
-
}
|
|
4904
|
-
}
|
|
4905
5161
|
return null;
|
|
4906
5162
|
}
|
|
4907
5163
|
function resolveCliNameForProvider(providerName) {
|
|
@@ -4931,6 +5187,11 @@ function detectInteractiveAuthFailure(text, activeProviderName, configuredProvid
|
|
|
4931
5187
|
};
|
|
4932
5188
|
}
|
|
4933
5189
|
const LOCAL_RUNTIME_RETRY_LIMIT = 2;
|
|
5190
|
+
function isClaudeFreshSessionStartupFailure(err) {
|
|
5191
|
+
return err?.code === 'CLAUDE_FRESH_SESSION_STARTUP_TIMEOUT'
|
|
5192
|
+
|| err?.name === 'ClaudeFreshSessionStartupTimeoutError'
|
|
5193
|
+
|| /fresh session startup timed out/i.test(String(err?.message || err || ''));
|
|
5194
|
+
}
|
|
4934
5195
|
function shouldRetrySelectedLocalRuntime(err) {
|
|
4935
5196
|
const text = String(err?.message || err || '').toLowerCase();
|
|
4936
5197
|
if (!text)
|