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/message-loop.d.ts
CHANGED
|
@@ -41,6 +41,10 @@ export declare class MessageLoop {
|
|
|
41
41
|
/** Priority queue for commands when agent is busy */
|
|
42
42
|
private commandQueue;
|
|
43
43
|
private processing;
|
|
44
|
+
/** Idempotency guardrails */
|
|
45
|
+
private commandState;
|
|
46
|
+
private commandCompletedAt;
|
|
47
|
+
private static readonly COMMAND_DEDUPE_TTL_MS;
|
|
44
48
|
/** Timeout for scheduled tasks (5 minutes) */
|
|
45
49
|
private static readonly SCHEDULED_TASK_TIMEOUT_MS;
|
|
46
50
|
private scheduledTaskTimer;
|
|
@@ -58,6 +62,8 @@ export declare class MessageLoop {
|
|
|
58
62
|
/** Stop the idle detection timer */
|
|
59
63
|
destroy(): void;
|
|
60
64
|
isBusy(): boolean;
|
|
65
|
+
private pruneCommandDedupeCache;
|
|
66
|
+
private publishDuplicateIgnored;
|
|
61
67
|
/**
|
|
62
68
|
* Public entry point: enqueue or execute a command based on priority.
|
|
63
69
|
* High-priority (user) commands execute immediately.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-loop.d.ts","sourceRoot":"","sources":["../src/message-loop.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAe,MAAM,eAAe,CAAC;AAM3E,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,EAAmB,cAAc,
|
|
1
|
+
{"version":3,"file":"message-loop.d.ts","sourceRoot":"","sources":["../src/message-loop.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAe,MAAM,eAAe,CAAC;AAM3E,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C,OAAO,EAAmB,cAAc,EAAiC,MAAM,YAAY,CAAC;AAG5F,OAAO,EAAoB,YAAY,EAAE,MAAM,oBAAoB,CAAC;AASpE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,eAAe,CAAC;IAC5B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqFD,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,sBAAsB,CAAsB;IACpD,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,SAAS,CAA+C;IAChE,OAAO,CAAC,eAAe,CAAkB;IAEzC,sCAAsC;IACtC,OAAO,CAAC,sBAAsB,CAAS;IACvC,OAAO,CAAC,mBAAmB,CAAS;IAEpC,qDAAqD;IACrD,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,UAAU,CAAS;IAE3B,6BAA6B;IAC7B,OAAO,CAAC,YAAY,CAAoE;IACxF,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAkB;IAE/D,8CAA8C;IAC9C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAiB;IAClE,OAAO,CAAC,kBAAkB,CAA8C;IAExE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAkB;gBAE/C,OAAO,EAAE,kBAAkB;IAkFvC;;;;;OAKG;YACW,qBAAqB;IA2CnC,iEAAiE;YACnD,gBAAgB;IAiB9B,oCAAoC;IACpC,OAAO,IAAI,IAAI;IAOf,MAAM,IAAI,OAAO;IAIjB,OAAO,CAAC,uBAAuB;YAYjB,uBAAuB;IAWrC;;;;OAIG;IACG,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IA2CzD,4CAA4C;YAC9B,UAAU;IAOxB,iEAAiE;YACnD,iBAAiB;YAejB,eAAe;IAozB7B,oEAAoE;IACpE,OAAO,CAAC,yBAAyB;YAuDnB,YAAY;IAW1B,yEAAyE;IACzE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;CAkBjC"}
|
package/dist/message-loop.js
CHANGED
|
@@ -52,6 +52,7 @@ const clerk_model_1 = require("./clerk-model");
|
|
|
52
52
|
const local_funnel_1 = require("./local-funnel");
|
|
53
53
|
const auto_organizer_1 = require("./auto-organizer");
|
|
54
54
|
const tool_permissions_1 = require("./tool-permissions");
|
|
55
|
+
const response_guard_1 = require("./response-guard");
|
|
55
56
|
const data = __importStar(require("./local-data"));
|
|
56
57
|
/** Determine priority from an AgentCommand */
|
|
57
58
|
function getCommandPriority(command) {
|
|
@@ -117,6 +118,15 @@ function normalizeMcpToolNames(toolNames) {
|
|
|
117
118
|
.filter(Boolean);
|
|
118
119
|
return [...new Set(trimmed)];
|
|
119
120
|
}
|
|
121
|
+
function isFastPathEnvKeyCheck(prompt) {
|
|
122
|
+
const text = String(prompt || '').toLowerCase();
|
|
123
|
+
if (!text)
|
|
124
|
+
return false;
|
|
125
|
+
const asksOpenAiKey = /(openai[_\s-]*api[_\s-]*key|openai key)/i.test(text);
|
|
126
|
+
const asksPresence = /(is|check|whether|exists|set|available|configured|have access)/i.test(text);
|
|
127
|
+
const asksEnv = /(env|environment|variable|local)/i.test(text);
|
|
128
|
+
return asksOpenAiKey && (asksPresence || asksEnv);
|
|
129
|
+
}
|
|
120
130
|
class MessageLoop {
|
|
121
131
|
options;
|
|
122
132
|
llmProvider;
|
|
@@ -137,6 +147,10 @@ class MessageLoop {
|
|
|
137
147
|
/** Priority queue for commands when agent is busy */
|
|
138
148
|
commandQueue = [];
|
|
139
149
|
processing = false;
|
|
150
|
+
/** Idempotency guardrails */
|
|
151
|
+
commandState = new Map();
|
|
152
|
+
commandCompletedAt = new Map();
|
|
153
|
+
static COMMAND_DEDUPE_TTL_MS = 30 * 60 * 1000;
|
|
140
154
|
/** Timeout for scheduled tasks (5 minutes) */
|
|
141
155
|
static SCHEDULED_TASK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
142
156
|
scheduledTaskTimer = null;
|
|
@@ -284,6 +298,27 @@ class MessageLoop {
|
|
|
284
298
|
isBusy() {
|
|
285
299
|
return this.processing || this.activeCommandId !== null;
|
|
286
300
|
}
|
|
301
|
+
pruneCommandDedupeCache() {
|
|
302
|
+
const cutoff = Date.now() - MessageLoop.COMMAND_DEDUPE_TTL_MS;
|
|
303
|
+
for (const [commandId, completedAt] of this.commandCompletedAt.entries()) {
|
|
304
|
+
if (completedAt < cutoff) {
|
|
305
|
+
this.commandCompletedAt.delete(commandId);
|
|
306
|
+
if (this.commandState.get(commandId) === 'completed') {
|
|
307
|
+
this.commandState.delete(commandId);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async publishDuplicateIgnored(command, reason) {
|
|
313
|
+
await this.options.mqttClient.publishResult({
|
|
314
|
+
commandId: command.id,
|
|
315
|
+
type: 'status',
|
|
316
|
+
phase: 'duplicate_ignored',
|
|
317
|
+
detail: reason,
|
|
318
|
+
status: 'duplicate_ignored',
|
|
319
|
+
timestamp: Date.now(),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
287
322
|
/**
|
|
288
323
|
* Public entry point: enqueue or execute a command based on priority.
|
|
289
324
|
* High-priority (user) commands execute immediately.
|
|
@@ -302,6 +337,18 @@ class MessageLoop {
|
|
|
302
337
|
if (command.type === 'ping' || command.type === 'cancel') {
|
|
303
338
|
return this._executeCommand(command);
|
|
304
339
|
}
|
|
340
|
+
this.pruneCommandDedupeCache();
|
|
341
|
+
const existingState = this.commandState.get(command.id);
|
|
342
|
+
if (existingState === 'queued' || existingState === 'running') {
|
|
343
|
+
console.log(chalk_1.default.yellow(` ↺ Duplicate command ignored ${command.id.slice(0, 8)}... (state=${existingState})`));
|
|
344
|
+
await this.publishDuplicateIgnored(command, `Command already ${existingState}`);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (existingState === 'completed' || existingState === 'failed') {
|
|
348
|
+
console.log(chalk_1.default.yellow(` ↺ Duplicate command ignored ${command.id.slice(0, 8)}... (recently ${existingState})`));
|
|
349
|
+
await this.publishDuplicateIgnored(command, `Command already ${existingState}`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
305
352
|
const priority = getCommandPriority(command);
|
|
306
353
|
// If not busy, execute directly
|
|
307
354
|
if (!this.processing) {
|
|
@@ -310,6 +357,7 @@ class MessageLoop {
|
|
|
310
357
|
// If busy but incoming is high priority and current is not high, queue current? No — just queue incoming.
|
|
311
358
|
// Simple approach: queue everything when busy, drain by priority when done.
|
|
312
359
|
console.log(chalk_1.default.yellow(` ⏳ Agent busy, queuing command ${command.id.slice(0, 8)}... (priority: ${priority})`));
|
|
360
|
+
this.commandState.set(command.id, 'queued');
|
|
313
361
|
this.commandQueue.push({ command, priority, queuedAt: Date.now() });
|
|
314
362
|
// Sort: high first, then medium, then low
|
|
315
363
|
this.commandQueue.sort((a, b) => PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]);
|
|
@@ -359,6 +407,8 @@ class MessageLoop {
|
|
|
359
407
|
return;
|
|
360
408
|
}
|
|
361
409
|
if (!command.prompt) {
|
|
410
|
+
this.commandState.set(command.id, 'failed');
|
|
411
|
+
this.commandCompletedAt.set(command.id, Date.now());
|
|
362
412
|
await this.publishError(command.id, 'No prompt provided');
|
|
363
413
|
return;
|
|
364
414
|
}
|
|
@@ -424,14 +474,19 @@ class MessageLoop {
|
|
|
424
474
|
this.toolDefinitions = (0, index_2.getAllToolDefinitions)(this._mcpManager);
|
|
425
475
|
this.activeCommandId = command.id;
|
|
426
476
|
this.activeCommandSource = command.source || 'user';
|
|
477
|
+
this.commandState.set(command.id, 'running');
|
|
427
478
|
this.processing = true;
|
|
428
479
|
this.lastMessageAt = Date.now();
|
|
429
480
|
const commandStartMs = Date.now();
|
|
430
481
|
let totalToolCalls = 0;
|
|
482
|
+
let totalToolErrors = 0; // Phase 3.3: track errors for step_progress events
|
|
483
|
+
let commandFailed = false;
|
|
431
484
|
// Set timeout for scheduled tasks (5 minutes)
|
|
432
485
|
if (command.source === 'scheduled') {
|
|
433
486
|
this.scheduledTaskTimer = setTimeout(() => {
|
|
434
487
|
console.log(chalk_1.default.red(` ⏰ Scheduled task ${command.id.slice(0, 8)} timed out after 5 minutes`));
|
|
488
|
+
this.commandState.set(command.id, 'failed');
|
|
489
|
+
this.commandCompletedAt.set(command.id, Date.now());
|
|
435
490
|
this.activeCommandId = null;
|
|
436
491
|
this.processing = false;
|
|
437
492
|
this.publishTaskResult(command, 'Task timed out after 5 minutes', false);
|
|
@@ -492,21 +547,23 @@ class MessageLoop {
|
|
|
492
547
|
// Resolve the selected bot profile — from command.bot, conversation bot_id, or default
|
|
493
548
|
const selectedBotId = command.bot?.id || activeConversation?.bot_id || localAgentId;
|
|
494
549
|
const activeProfile = selectedBotId ? localData?.getAgentProfile(selectedBotId) : null;
|
|
495
|
-
if (activeProfile?.role_class === 'orchestrator'
|
|
496
|
-
const clerk = (0, clerk_model_1.getClerk)();
|
|
497
|
-
if (!clerk) {
|
|
498
|
-
// Do not silently fall through to direct chat — report error
|
|
499
|
-
await this.publishError(command.id, 'Orchestrator mode requires a clerk model. Please add a provider connection in Settings.');
|
|
500
|
-
this.activeCommandId = null;
|
|
501
|
-
this.processing = false;
|
|
502
|
-
this.drainQueue();
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
550
|
+
if (localFirst && (activeProfile?.role_class === 'orchestrator' || localData?.isClerkOrchestratorEnabled?.())) {
|
|
505
551
|
{
|
|
506
|
-
const { OrchestratorAgent } = await Promise.resolve().then(() => __importStar(require('./orchestrator')));
|
|
552
|
+
const { OrchestratorAgent, buildLocalDesktopOrchestratorRuntime } = await Promise.resolve().then(() => __importStar(require('./orchestrator')));
|
|
507
553
|
const { getWorkflowEngine } = await Promise.resolve().then(() => __importStar(require('./workflow-engine')));
|
|
508
554
|
const workflowEngine = getWorkflowEngine(this.options.projectDir);
|
|
509
|
-
|
|
555
|
+
let orchestratorRuntime;
|
|
556
|
+
try {
|
|
557
|
+
orchestratorRuntime = buildLocalDesktopOrchestratorRuntime(activeProfile || undefined);
|
|
558
|
+
}
|
|
559
|
+
catch (runtimeErr) {
|
|
560
|
+
await this.publishError(command.id, runtimeErr?.message || 'Orchestrator mode is not configured correctly.');
|
|
561
|
+
this.activeCommandId = null;
|
|
562
|
+
this.processing = false;
|
|
563
|
+
this.drainQueue();
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const orchestrator = new OrchestratorAgent(orchestratorRuntime, workflowEngine);
|
|
510
567
|
try {
|
|
511
568
|
const response = await orchestrator.handleUserMessage(command.prompt, localConvId || '', {
|
|
512
569
|
projectDir: this.options.projectDir,
|
|
@@ -524,7 +581,7 @@ class MessageLoop {
|
|
|
524
581
|
});
|
|
525
582
|
// Save O's response to local DB
|
|
526
583
|
if (localConvId && localData) {
|
|
527
|
-
localData.addMessage(localConvId, 'assistant', response, undefined, undefined, activeProfile
|
|
584
|
+
localData.addMessage(localConvId, 'assistant', response, undefined, undefined, activeProfile?.id, 'Project Manager');
|
|
528
585
|
}
|
|
529
586
|
// Publish final response and complete
|
|
530
587
|
await this.options.mqttClient.publishResult({
|
|
@@ -555,6 +612,81 @@ class MessageLoop {
|
|
|
555
612
|
return;
|
|
556
613
|
}
|
|
557
614
|
}
|
|
615
|
+
// Fast-path: simple environment API-key presence checks for Claude CLI.
|
|
616
|
+
// This avoids full LLM/tool orchestration latency for common operational asks.
|
|
617
|
+
if (provider === 'claude-cli' && isFastPathEnvKeyCheck(command.prompt)) {
|
|
618
|
+
await this.options.mqttClient.publishResult({
|
|
619
|
+
commandId: command.id,
|
|
620
|
+
type: 'status',
|
|
621
|
+
phase: 'thinking',
|
|
622
|
+
detail: 'Checking local environment…',
|
|
623
|
+
timestamp: Date.now(),
|
|
624
|
+
});
|
|
625
|
+
const checkCommand = `if [ -n "${'$'}OPENAI_API_KEY" ]; then echo "OPENAI_API_KEY=set"; else echo "OPENAI_API_KEY=not_set"; fi; if [ -f .env ]; then if grep -q '^OPENAI_API_KEY=' .env; then echo ".env_OPENAI_API_KEY=present"; else echo ".env_OPENAI_API_KEY=absent"; fi; else echo ".env=missing"; fi`;
|
|
626
|
+
const rawResult = await (0, tools_1.executeToolWithMCP)({ id: `fastpath-${Date.now()}`, name: 'run_command', arguments: { command: checkCommand, cwd: this.options.projectDir } }, commandToolContext, this.options.mcpManager);
|
|
627
|
+
const verified = await (0, verification_1.verifyToolResult)(rawResult, { command: checkCommand, cwd: this.options.projectDir }, commandToolContext);
|
|
628
|
+
const output = verified.success ? verified.output : `ERROR: ${verified.error || 'Unknown error'}`;
|
|
629
|
+
const keySet = /OPENAI_API_KEY=set/.test(output);
|
|
630
|
+
const envFilePresent = /\.env_OPENAI_API_KEY=present/.test(output);
|
|
631
|
+
const final = keySet || envFilePresent
|
|
632
|
+
? 'OPENAI_API_KEY is set (environment and/or .env).'
|
|
633
|
+
: 'No OPENAI_API_KEY is set in the current environment or .env file.';
|
|
634
|
+
await this.options.mqttClient.publishResult({
|
|
635
|
+
commandId: command.id,
|
|
636
|
+
type: 'tool_call',
|
|
637
|
+
toolCall: {
|
|
638
|
+
id: `fastpath-${Date.now()}`,
|
|
639
|
+
name: 'run_command',
|
|
640
|
+
arguments: { command: 'check OPENAI_API_KEY presence' },
|
|
641
|
+
classification: 'command_run',
|
|
642
|
+
summary: 'Fast-path env key presence check',
|
|
643
|
+
cwd: this.options.projectDir,
|
|
644
|
+
importance: 'low',
|
|
645
|
+
},
|
|
646
|
+
timestamp: Date.now(),
|
|
647
|
+
});
|
|
648
|
+
await this.options.mqttClient.publishResult({
|
|
649
|
+
commandId: command.id,
|
|
650
|
+
type: 'tool_result',
|
|
651
|
+
toolResult: {
|
|
652
|
+
callId: `fastpath-${Date.now()}`,
|
|
653
|
+
output: output.slice(0, 500),
|
|
654
|
+
isError: !verified.success,
|
|
655
|
+
success: verified.success,
|
|
656
|
+
summary: summarizeToolResult(output, !verified.success),
|
|
657
|
+
cwd: this.options.projectDir,
|
|
658
|
+
},
|
|
659
|
+
timestamp: Date.now(),
|
|
660
|
+
});
|
|
661
|
+
await this.options.mqttClient.publishResult({
|
|
662
|
+
commandId: command.id,
|
|
663
|
+
type: 'chunk',
|
|
664
|
+
content: final,
|
|
665
|
+
timestamp: Date.now(),
|
|
666
|
+
});
|
|
667
|
+
await this.options.mqttClient.publishResult({
|
|
668
|
+
commandId: command.id,
|
|
669
|
+
type: 'complete',
|
|
670
|
+
content: '',
|
|
671
|
+
finalContent: final,
|
|
672
|
+
finalContentLength: final.length,
|
|
673
|
+
timestamp: Date.now(),
|
|
674
|
+
});
|
|
675
|
+
if (localFirst && localConvId && localData) {
|
|
676
|
+
try {
|
|
677
|
+
localData.addMessage(localConvId, 'assistant', final, model);
|
|
678
|
+
}
|
|
679
|
+
catch { }
|
|
680
|
+
}
|
|
681
|
+
this.activeCommandId = null;
|
|
682
|
+
this.processing = false;
|
|
683
|
+
if (this.scheduledTaskTimer) {
|
|
684
|
+
clearTimeout(this.scheduledTaskTimer);
|
|
685
|
+
this.scheduledTaskTimer = null;
|
|
686
|
+
}
|
|
687
|
+
this.drainQueue();
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
558
690
|
// Build messages from context
|
|
559
691
|
const messages = [];
|
|
560
692
|
// Add previous conversation context
|
|
@@ -617,6 +749,7 @@ TOOL EFFICIENCY RULES:
|
|
|
617
749
|
let iteration = 0;
|
|
618
750
|
const MAX_ITERATIONS = Infinity;
|
|
619
751
|
let totalTokensUsed = 0;
|
|
752
|
+
let forcedFinalizationPass = false;
|
|
620
753
|
const llmTimingSamples = [];
|
|
621
754
|
while (iteration < MAX_ITERATIONS && this.activeCommandId === command.id) {
|
|
622
755
|
iteration++;
|
|
@@ -659,6 +792,26 @@ TOOL EFFICIENCY RULES:
|
|
|
659
792
|
let response;
|
|
660
793
|
const llmCallStartMs = Date.now();
|
|
661
794
|
let firstChunkLatencyMs = null;
|
|
795
|
+
await this.options.mqttClient.publishResult({
|
|
796
|
+
commandId: command.id,
|
|
797
|
+
type: 'status',
|
|
798
|
+
phase: 'thinking',
|
|
799
|
+
detail: iteration === 1 ? 'Working on it…' : `Continuing… (pass ${iteration})`,
|
|
800
|
+
timestamp: Date.now(),
|
|
801
|
+
});
|
|
802
|
+
let emittedSlowStatus = false;
|
|
803
|
+
const slowStatusTimer = setTimeout(() => {
|
|
804
|
+
if (this.activeCommandId !== command.id || firstChunkLatencyMs !== null)
|
|
805
|
+
return;
|
|
806
|
+
emittedSlowStatus = true;
|
|
807
|
+
this.options.mqttClient.publishResult({
|
|
808
|
+
commandId: command.id,
|
|
809
|
+
type: 'status',
|
|
810
|
+
phase: 'thinking',
|
|
811
|
+
detail: 'Still working…',
|
|
812
|
+
timestamp: Date.now(),
|
|
813
|
+
}).catch(() => { });
|
|
814
|
+
}, 2000);
|
|
662
815
|
try {
|
|
663
816
|
response = await llm.chat({
|
|
664
817
|
messages,
|
|
@@ -679,10 +832,12 @@ TOOL EFFICIENCY RULES:
|
|
|
679
832
|
});
|
|
680
833
|
},
|
|
681
834
|
});
|
|
835
|
+
clearTimeout(slowStatusTimer);
|
|
682
836
|
const llmTotalMs = Date.now() - llmCallStartMs;
|
|
683
|
-
llmTimingSamples.push(`iter${iteration}:firstChunkMs=${firstChunkLatencyMs ?? llmTotalMs},totalMs=${llmTotalMs},selectedTools=${filteredTools.length}`);
|
|
837
|
+
llmTimingSamples.push(`iter${iteration}:firstChunkMs=${firstChunkLatencyMs ?? llmTotalMs},totalMs=${llmTotalMs},selectedTools=${filteredTools.length},slowStatus=${emittedSlowStatus ? 1 : 0}`);
|
|
684
838
|
}
|
|
685
839
|
catch (llmError) {
|
|
840
|
+
clearTimeout(slowStatusTimer);
|
|
686
841
|
const msg = llmError?.message || String(llmError);
|
|
687
842
|
const isAuthError = /401|403|unauthorized|forbidden|authentication/i.test(msg);
|
|
688
843
|
if (isAuthError) {
|
|
@@ -711,9 +866,14 @@ TOOL EFFICIENCY RULES:
|
|
|
711
866
|
});
|
|
712
867
|
for (const toolCall of response.toolCalls) {
|
|
713
868
|
totalToolCalls++;
|
|
869
|
+
await this.options.mqttClient.publishResult({
|
|
870
|
+
commandId: command.id,
|
|
871
|
+
type: 'status',
|
|
872
|
+
phase: 'calling_tool',
|
|
873
|
+
detail: `Running ${toolCall.name}...`,
|
|
874
|
+
timestamp: Date.now(),
|
|
875
|
+
});
|
|
714
876
|
console.log(chalk_1.default.cyan(` 🔧 Tool: ${toolCall.name}(${JSON.stringify(toolCall.arguments).slice(0, 60)}...)`));
|
|
715
|
-
const toolSummary = (0, approval_1.generateToolSummary)(toolCall.name, toolCall.arguments);
|
|
716
|
-
const toolLocation = extractToolLocation(toolCall.name, toolCall.arguments, commandToolContext.projectDir);
|
|
717
877
|
// Check permission before execution
|
|
718
878
|
const permMode = this.options.permissionMode || 'autopilot';
|
|
719
879
|
const allowedToolNames = this.options.enabledTools !== undefined || this.options.enabledMcpTools !== undefined
|
|
@@ -723,29 +883,17 @@ TOOL EFFICIENCY RULES:
|
|
|
723
883
|
// If not auto-approved, request interactive approval via MQTT
|
|
724
884
|
if (!approval.approved && permMode !== 'autopilot') {
|
|
725
885
|
console.log(chalk_1.default.yellow(` 🔐 Requesting user approval for: ${toolCall.name}`));
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
summary: request.summary,
|
|
734
|
-
toolCall: {
|
|
735
|
-
id: toolCall.id,
|
|
736
|
-
name: toolCall.name,
|
|
737
|
-
arguments: toolCall.arguments,
|
|
738
|
-
classification: classifyToolName(toolCall.name),
|
|
739
|
-
summary: toolSummary,
|
|
740
|
-
path: toolLocation.path,
|
|
741
|
-
cwd: toolLocation.cwd,
|
|
742
|
-
url: toolLocation.url,
|
|
743
|
-
importance: request.riskLevel === 'destructive' ? 'high' : 'medium',
|
|
744
|
-
},
|
|
745
|
-
timestamp: Date.now(),
|
|
746
|
-
});
|
|
886
|
+
await this.options.mqttClient.publishResult({
|
|
887
|
+
commandId: command.id,
|
|
888
|
+
type: 'approval_request',
|
|
889
|
+
toolCall: {
|
|
890
|
+
id: toolCall.id,
|
|
891
|
+
name: toolCall.name,
|
|
892
|
+
arguments: toolCall.arguments,
|
|
747
893
|
},
|
|
894
|
+
timestamp: Date.now(),
|
|
748
895
|
});
|
|
896
|
+
approval = await this.approvalManager.requestApproval(command.id, toolCall.name, toolCall.arguments);
|
|
749
897
|
// If user chose "always allow", persist it
|
|
750
898
|
if (approval.approved && approval.remember) {
|
|
751
899
|
(0, approval_1.rememberTool)(toolCall.name);
|
|
@@ -754,26 +902,19 @@ TOOL EFFICIENCY RULES:
|
|
|
754
902
|
}
|
|
755
903
|
if (!approval.approved) {
|
|
756
904
|
console.log(chalk_1.default.yellow(` ⚠ Tool denied: ${approval.reason}`));
|
|
757
|
-
const deniedOutput = `PERMISSION_DENIED: ${approval.reason}`;
|
|
758
905
|
await this.options.mqttClient.publishResult({
|
|
759
906
|
commandId: command.id,
|
|
760
907
|
type: 'tool_result',
|
|
761
908
|
toolResult: {
|
|
762
909
|
callId: toolCall.id,
|
|
763
|
-
output:
|
|
910
|
+
output: `PERMISSION_DENIED: ${approval.reason}`,
|
|
764
911
|
isError: true,
|
|
765
|
-
success: false,
|
|
766
|
-
summary: summarizeToolResult(deniedOutput, true),
|
|
767
|
-
path: toolLocation.path,
|
|
768
|
-
cwd: toolLocation.cwd,
|
|
769
|
-
url: toolLocation.url,
|
|
770
|
-
pathsTouched: toolLocation.path ? [toolLocation.path] : [],
|
|
771
912
|
},
|
|
772
913
|
timestamp: Date.now(),
|
|
773
914
|
});
|
|
774
915
|
messages.push({
|
|
775
916
|
role: 'tool',
|
|
776
|
-
content:
|
|
917
|
+
content: `PERMISSION_DENIED: ${approval.reason}`,
|
|
777
918
|
toolCallId: toolCall.id,
|
|
778
919
|
toolName: toolCall.name,
|
|
779
920
|
});
|
|
@@ -786,12 +927,6 @@ TOOL EFFICIENCY RULES:
|
|
|
786
927
|
id: toolCall.id,
|
|
787
928
|
name: toolCall.name,
|
|
788
929
|
arguments: toolCall.arguments,
|
|
789
|
-
classification: classifyToolName(toolCall.name),
|
|
790
|
-
summary: toolSummary,
|
|
791
|
-
path: toolLocation.path,
|
|
792
|
-
cwd: toolLocation.cwd,
|
|
793
|
-
url: toolLocation.url,
|
|
794
|
-
importance: (0, approval_1.getRiskLevel)(toolCall.name) === 'destructive' ? 'high' : 'medium',
|
|
795
930
|
},
|
|
796
931
|
timestamp: Date.now(),
|
|
797
932
|
});
|
|
@@ -801,6 +936,9 @@ TOOL EFFICIENCY RULES:
|
|
|
801
936
|
const resultOutput = toolResult.success
|
|
802
937
|
? toolResult.output
|
|
803
938
|
: `ERROR: ${toolResult.error || 'Unknown error'}`;
|
|
939
|
+
// Phase 3.3: Track tool errors
|
|
940
|
+
if (!toolResult.success)
|
|
941
|
+
totalToolErrors++;
|
|
804
942
|
console.log(chalk_1.default.gray(` Result: ${toolResult.success ? '✓' : '✗'} ${resultOutput.slice(0, 100)}${resultOutput.length > 100 ? '...' : ''}`));
|
|
805
943
|
await this.options.mqttClient.publishResult({
|
|
806
944
|
commandId: command.id,
|
|
@@ -809,16 +947,37 @@ TOOL EFFICIENCY RULES:
|
|
|
809
947
|
callId: toolCall.id,
|
|
810
948
|
output: resultOutput,
|
|
811
949
|
isError: !toolResult.success,
|
|
812
|
-
success: toolResult.success,
|
|
813
|
-
summary: summarizeToolResult(resultOutput, !toolResult.success),
|
|
814
|
-
path: toolLocation.path,
|
|
815
|
-
cwd: toolLocation.cwd,
|
|
816
|
-
url: toolLocation.url,
|
|
817
|
-
pathsTouched: toolLocation.path ? [toolLocation.path] : [],
|
|
818
|
-
exitCode: typeof toolResult.exit_code === 'number' ? toolResult.exit_code : undefined,
|
|
819
950
|
},
|
|
820
951
|
timestamp: Date.now(),
|
|
821
952
|
});
|
|
953
|
+
// Phase 3.3: Emit structured step_progress event for watchdog consumption
|
|
954
|
+
if (command.taskId) {
|
|
955
|
+
try {
|
|
956
|
+
await this.options.mqttClient.publishResult({
|
|
957
|
+
commandId: command.id,
|
|
958
|
+
type: 'tool_result',
|
|
959
|
+
toolResult: {
|
|
960
|
+
callId: `step_progress_${toolCall.id}`,
|
|
961
|
+
output: JSON.stringify({
|
|
962
|
+
type: 'step_progress',
|
|
963
|
+
taskId: command.taskId,
|
|
964
|
+
toolName: toolCall.name,
|
|
965
|
+
arguments: toolCall.arguments,
|
|
966
|
+
success: toolResult.success,
|
|
967
|
+
errorMessage: toolResult.success ? undefined : (toolResult.error || 'Unknown error'),
|
|
968
|
+
totalToolCalls,
|
|
969
|
+
errorCount: totalToolErrors,
|
|
970
|
+
timestamp: Date.now(),
|
|
971
|
+
}),
|
|
972
|
+
isError: false,
|
|
973
|
+
},
|
|
974
|
+
timestamp: Date.now(),
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
catch {
|
|
978
|
+
// Best effort — don't break the main loop for progress reporting
|
|
979
|
+
}
|
|
980
|
+
}
|
|
822
981
|
// Add tool result to messages using proper 'tool' role for provider formatting
|
|
823
982
|
messages.push({
|
|
824
983
|
role: 'tool',
|
|
@@ -903,13 +1062,27 @@ TOOL EFFICIENCY RULES:
|
|
|
903
1062
|
continue;
|
|
904
1063
|
}
|
|
905
1064
|
// No tool calls - final response.
|
|
1065
|
+
const candidate = (response.content || '').trim();
|
|
1066
|
+
if (!forcedFinalizationPass && (0, response_guard_1.isLikelyDeferredReply)(candidate)) {
|
|
1067
|
+
forcedFinalizationPass = true;
|
|
1068
|
+
messages.push({ role: 'assistant', content: candidate });
|
|
1069
|
+
messages.push({
|
|
1070
|
+
role: 'user',
|
|
1071
|
+
content: 'Provide the final answer now. Do not say you will check later. Either provide concrete results or explicitly say what is unavailable.',
|
|
1072
|
+
});
|
|
1073
|
+
console.log(chalk_1.default.yellow(' [response-guard] Deferred-style reply detected; forcing finalization pass'));
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
906
1076
|
// DEDUP FIX: Content was already streamed via onChunk callbacks above.
|
|
907
|
-
// The 'complete' message signals end-of-response;
|
|
908
|
-
//
|
|
1077
|
+
// The 'complete' message signals end-of-response; include finalContent
|
|
1078
|
+
// metadata so clients can reconcile if stream chunks were truncated.
|
|
1079
|
+
const finalResponseText = (response.content || '').trim();
|
|
909
1080
|
await this.options.mqttClient.publishResult({
|
|
910
1081
|
commandId: command.id,
|
|
911
1082
|
type: 'complete',
|
|
912
|
-
content:
|
|
1083
|
+
content: finalResponseText,
|
|
1084
|
+
finalContent: finalResponseText,
|
|
1085
|
+
finalContentLength: finalResponseText.length,
|
|
913
1086
|
timestamp: Date.now(),
|
|
914
1087
|
});
|
|
915
1088
|
// Local-first: save assistant response to SQLite + trigger funnel
|
|
@@ -935,10 +1108,12 @@ TOOL EFFICIENCY RULES:
|
|
|
935
1108
|
break;
|
|
936
1109
|
}
|
|
937
1110
|
if (iteration >= MAX_ITERATIONS) {
|
|
1111
|
+
commandFailed = true;
|
|
938
1112
|
await this.publishError(command.id, `Max iterations (${MAX_ITERATIONS}) reached`);
|
|
939
1113
|
}
|
|
940
1114
|
}
|
|
941
1115
|
catch (error) {
|
|
1116
|
+
commandFailed = true;
|
|
942
1117
|
console.error(chalk_1.default.red(` ✗ Error: ${error.message}`));
|
|
943
1118
|
await this.publishError(command.id, error.message);
|
|
944
1119
|
}
|
|
@@ -946,6 +1121,8 @@ TOOL EFFICIENCY RULES:
|
|
|
946
1121
|
this.activeCommandId = null;
|
|
947
1122
|
this.activeCommandSource = null;
|
|
948
1123
|
this.processing = false;
|
|
1124
|
+
this.commandState.set(command.id, commandFailed ? 'failed' : 'completed');
|
|
1125
|
+
this.commandCompletedAt.set(command.id, Date.now());
|
|
949
1126
|
// Clear scheduled task timeout
|
|
950
1127
|
if (this.scheduledTaskTimer) {
|
|
951
1128
|
clearTimeout(this.scheduledTaskTimer);
|
|
@@ -965,7 +1142,6 @@ TOOL EFFICIENCY RULES:
|
|
|
965
1142
|
|| this.options.systemPrompt
|
|
966
1143
|
|| 'You are a Funolio AI agent running locally on the user\'s machine. You have access to their project files and can execute code.';
|
|
967
1144
|
systemPrompt += '\n\nIMPORTANT: When the user references a project, topic, or past work, use the relevant memory/facts provided below. If no relevant facts are available, say so honestly rather than guessing. Use your tools (file browsing, commands) to find project files on the local machine.';
|
|
968
|
-
systemPrompt += '\n\nIMPORTANT: If a Workspace Manifest or Recent Operational State section is present, use it before repeating discovery work. Check known local files and directories before fetching from external sources. Do not re-fetch from Drive, GitHub, or the web if the state context shows the artifact is already local unless the user explicitly asks for a fresh external copy.';
|
|
969
1145
|
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.';
|
|
970
1146
|
systemPrompt += '\n\n' + (0, clerk_model_1.buildTodoInstructions)(this.options.agentName || 'LLM');
|
|
971
1147
|
// Inject model self-switch info
|
|
@@ -1010,32 +1186,6 @@ When a user asks to "use Opus" or "switch to GPT-4o", identify the right model I
|
|
|
1010
1186
|
if (command.context?.files?.length) {
|
|
1011
1187
|
systemPrompt += '\n\nRelevant files: ' + command.context.files.join(', ');
|
|
1012
1188
|
}
|
|
1013
|
-
if (command.context?.stateContext?.summaryText) {
|
|
1014
|
-
systemPrompt += '\n\n[Recent Operational State]\n' + command.context.stateContext.summaryText;
|
|
1015
|
-
}
|
|
1016
|
-
if (command.context?.workspaceManifest) {
|
|
1017
|
-
const manifestLines = [];
|
|
1018
|
-
const manifest = command.context.workspaceManifest;
|
|
1019
|
-
if (manifest.projectRoot)
|
|
1020
|
-
manifestLines.push(`- Project root: ${manifest.projectRoot}`);
|
|
1021
|
-
if (manifest.likelyWorkingDirectory)
|
|
1022
|
-
manifestLines.push(`- Likely working directory: ${manifest.likelyWorkingDirectory}`);
|
|
1023
|
-
if (manifest.recentlyReadFiles?.length)
|
|
1024
|
-
manifestLines.push(`- Recently read files: ${manifest.recentlyReadFiles.join(', ')}`);
|
|
1025
|
-
if (manifest.recentlyWrittenFiles?.length)
|
|
1026
|
-
manifestLines.push(`- Recently written files: ${manifest.recentlyWrittenFiles.join(', ')}`);
|
|
1027
|
-
if (manifest.recentlyScannedDirectories?.length)
|
|
1028
|
-
manifestLines.push(`- Recently scanned directories: ${manifest.recentlyScannedDirectories.join(', ')}`);
|
|
1029
|
-
if (manifest.recentDownloads?.length)
|
|
1030
|
-
manifestLines.push(`- Recent downloads: ${manifest.recentDownloads.join(', ')}`);
|
|
1031
|
-
if (manifest.recentFailures?.length)
|
|
1032
|
-
manifestLines.push(`- Recent failures: ${manifest.recentFailures.join(' | ')}`);
|
|
1033
|
-
if (manifest.startHereHints?.length)
|
|
1034
|
-
manifestLines.push(`- Start here: ${manifest.startHereHints.join(', ')}`);
|
|
1035
|
-
if (manifestLines.length) {
|
|
1036
|
-
systemPrompt += '\n\n[Workspace Manifest]\n' + manifestLines.join('\n');
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
1189
|
return systemPrompt;
|
|
1040
1190
|
}
|
|
1041
1191
|
async publishError(commandId, error) {
|