funolio-agent 1.0.52 → 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 +247 -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,8 @@ TOOL EFFICIENCY RULES:
|
|
|
617
749
|
let iteration = 0;
|
|
618
750
|
const MAX_ITERATIONS = Infinity;
|
|
619
751
|
let totalTokensUsed = 0;
|
|
752
|
+
let forcedFinalizationPass = false;
|
|
753
|
+
const llmTimingSamples = [];
|
|
620
754
|
while (iteration < MAX_ITERATIONS && this.activeCommandId === command.id) {
|
|
621
755
|
iteration++;
|
|
622
756
|
// Send a separator between agentic loop iterations so streamed text
|
|
@@ -656,6 +790,28 @@ TOOL EFFICIENCY RULES:
|
|
|
656
790
|
console.log(chalk_1.default.gray(` [tool-filter] ${filteredTools.length}/${this.toolDefinitions.length} tools selected`));
|
|
657
791
|
}
|
|
658
792
|
let response;
|
|
793
|
+
const llmCallStartMs = Date.now();
|
|
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);
|
|
659
815
|
try {
|
|
660
816
|
response = await llm.chat({
|
|
661
817
|
messages,
|
|
@@ -665,6 +821,9 @@ TOOL EFFICIENCY RULES:
|
|
|
665
821
|
onChunk: async (chunk) => {
|
|
666
822
|
if (this.activeCommandId !== command.id)
|
|
667
823
|
return;
|
|
824
|
+
if (firstChunkLatencyMs === null) {
|
|
825
|
+
firstChunkLatencyMs = Date.now() - llmCallStartMs;
|
|
826
|
+
}
|
|
668
827
|
await this.options.mqttClient.publishResult({
|
|
669
828
|
commandId: command.id,
|
|
670
829
|
type: 'chunk',
|
|
@@ -673,8 +832,12 @@ TOOL EFFICIENCY RULES:
|
|
|
673
832
|
});
|
|
674
833
|
},
|
|
675
834
|
});
|
|
835
|
+
clearTimeout(slowStatusTimer);
|
|
836
|
+
const llmTotalMs = Date.now() - llmCallStartMs;
|
|
837
|
+
llmTimingSamples.push(`iter${iteration}:firstChunkMs=${firstChunkLatencyMs ?? llmTotalMs},totalMs=${llmTotalMs},selectedTools=${filteredTools.length},slowStatus=${emittedSlowStatus ? 1 : 0}`);
|
|
676
838
|
}
|
|
677
839
|
catch (llmError) {
|
|
840
|
+
clearTimeout(slowStatusTimer);
|
|
678
841
|
const msg = llmError?.message || String(llmError);
|
|
679
842
|
const isAuthError = /401|403|unauthorized|forbidden|authentication/i.test(msg);
|
|
680
843
|
if (isAuthError) {
|
|
@@ -703,9 +866,14 @@ TOOL EFFICIENCY RULES:
|
|
|
703
866
|
});
|
|
704
867
|
for (const toolCall of response.toolCalls) {
|
|
705
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
|
+
});
|
|
706
876
|
console.log(chalk_1.default.cyan(` 🔧 Tool: ${toolCall.name}(${JSON.stringify(toolCall.arguments).slice(0, 60)}...)`));
|
|
707
|
-
const toolSummary = (0, approval_1.generateToolSummary)(toolCall.name, toolCall.arguments);
|
|
708
|
-
const toolLocation = extractToolLocation(toolCall.name, toolCall.arguments, commandToolContext.projectDir);
|
|
709
877
|
// Check permission before execution
|
|
710
878
|
const permMode = this.options.permissionMode || 'autopilot';
|
|
711
879
|
const allowedToolNames = this.options.enabledTools !== undefined || this.options.enabledMcpTools !== undefined
|
|
@@ -715,29 +883,17 @@ TOOL EFFICIENCY RULES:
|
|
|
715
883
|
// If not auto-approved, request interactive approval via MQTT
|
|
716
884
|
if (!approval.approved && permMode !== 'autopilot') {
|
|
717
885
|
console.log(chalk_1.default.yellow(` 🔐 Requesting user approval for: ${toolCall.name}`));
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
summary: request.summary,
|
|
726
|
-
toolCall: {
|
|
727
|
-
id: toolCall.id,
|
|
728
|
-
name: toolCall.name,
|
|
729
|
-
arguments: toolCall.arguments,
|
|
730
|
-
classification: classifyToolName(toolCall.name),
|
|
731
|
-
summary: toolSummary,
|
|
732
|
-
path: toolLocation.path,
|
|
733
|
-
cwd: toolLocation.cwd,
|
|
734
|
-
url: toolLocation.url,
|
|
735
|
-
importance: request.riskLevel === 'destructive' ? 'high' : 'medium',
|
|
736
|
-
},
|
|
737
|
-
timestamp: Date.now(),
|
|
738
|
-
});
|
|
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,
|
|
739
893
|
},
|
|
894
|
+
timestamp: Date.now(),
|
|
740
895
|
});
|
|
896
|
+
approval = await this.approvalManager.requestApproval(command.id, toolCall.name, toolCall.arguments);
|
|
741
897
|
// If user chose "always allow", persist it
|
|
742
898
|
if (approval.approved && approval.remember) {
|
|
743
899
|
(0, approval_1.rememberTool)(toolCall.name);
|
|
@@ -746,26 +902,19 @@ TOOL EFFICIENCY RULES:
|
|
|
746
902
|
}
|
|
747
903
|
if (!approval.approved) {
|
|
748
904
|
console.log(chalk_1.default.yellow(` ⚠ Tool denied: ${approval.reason}`));
|
|
749
|
-
const deniedOutput = `PERMISSION_DENIED: ${approval.reason}`;
|
|
750
905
|
await this.options.mqttClient.publishResult({
|
|
751
906
|
commandId: command.id,
|
|
752
907
|
type: 'tool_result',
|
|
753
908
|
toolResult: {
|
|
754
909
|
callId: toolCall.id,
|
|
755
|
-
output:
|
|
910
|
+
output: `PERMISSION_DENIED: ${approval.reason}`,
|
|
756
911
|
isError: true,
|
|
757
|
-
success: false,
|
|
758
|
-
summary: summarizeToolResult(deniedOutput, true),
|
|
759
|
-
path: toolLocation.path,
|
|
760
|
-
cwd: toolLocation.cwd,
|
|
761
|
-
url: toolLocation.url,
|
|
762
|
-
pathsTouched: toolLocation.path ? [toolLocation.path] : [],
|
|
763
912
|
},
|
|
764
913
|
timestamp: Date.now(),
|
|
765
914
|
});
|
|
766
915
|
messages.push({
|
|
767
916
|
role: 'tool',
|
|
768
|
-
content:
|
|
917
|
+
content: `PERMISSION_DENIED: ${approval.reason}`,
|
|
769
918
|
toolCallId: toolCall.id,
|
|
770
919
|
toolName: toolCall.name,
|
|
771
920
|
});
|
|
@@ -778,12 +927,6 @@ TOOL EFFICIENCY RULES:
|
|
|
778
927
|
id: toolCall.id,
|
|
779
928
|
name: toolCall.name,
|
|
780
929
|
arguments: toolCall.arguments,
|
|
781
|
-
classification: classifyToolName(toolCall.name),
|
|
782
|
-
summary: toolSummary,
|
|
783
|
-
path: toolLocation.path,
|
|
784
|
-
cwd: toolLocation.cwd,
|
|
785
|
-
url: toolLocation.url,
|
|
786
|
-
importance: (0, approval_1.getRiskLevel)(toolCall.name) === 'destructive' ? 'high' : 'medium',
|
|
787
930
|
},
|
|
788
931
|
timestamp: Date.now(),
|
|
789
932
|
});
|
|
@@ -793,6 +936,9 @@ TOOL EFFICIENCY RULES:
|
|
|
793
936
|
const resultOutput = toolResult.success
|
|
794
937
|
? toolResult.output
|
|
795
938
|
: `ERROR: ${toolResult.error || 'Unknown error'}`;
|
|
939
|
+
// Phase 3.3: Track tool errors
|
|
940
|
+
if (!toolResult.success)
|
|
941
|
+
totalToolErrors++;
|
|
796
942
|
console.log(chalk_1.default.gray(` Result: ${toolResult.success ? '✓' : '✗'} ${resultOutput.slice(0, 100)}${resultOutput.length > 100 ? '...' : ''}`));
|
|
797
943
|
await this.options.mqttClient.publishResult({
|
|
798
944
|
commandId: command.id,
|
|
@@ -801,16 +947,37 @@ TOOL EFFICIENCY RULES:
|
|
|
801
947
|
callId: toolCall.id,
|
|
802
948
|
output: resultOutput,
|
|
803
949
|
isError: !toolResult.success,
|
|
804
|
-
success: toolResult.success,
|
|
805
|
-
summary: summarizeToolResult(resultOutput, !toolResult.success),
|
|
806
|
-
path: toolLocation.path,
|
|
807
|
-
cwd: toolLocation.cwd,
|
|
808
|
-
url: toolLocation.url,
|
|
809
|
-
pathsTouched: toolLocation.path ? [toolLocation.path] : [],
|
|
810
|
-
exitCode: typeof toolResult.exit_code === 'number' ? toolResult.exit_code : undefined,
|
|
811
950
|
},
|
|
812
951
|
timestamp: Date.now(),
|
|
813
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
|
+
}
|
|
814
981
|
// Add tool result to messages using proper 'tool' role for provider formatting
|
|
815
982
|
messages.push({
|
|
816
983
|
role: 'tool',
|
|
@@ -895,13 +1062,27 @@ TOOL EFFICIENCY RULES:
|
|
|
895
1062
|
continue;
|
|
896
1063
|
}
|
|
897
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
|
+
}
|
|
898
1076
|
// DEDUP FIX: Content was already streamed via onChunk callbacks above.
|
|
899
|
-
// The 'complete' message signals end-of-response;
|
|
900
|
-
//
|
|
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();
|
|
901
1080
|
await this.options.mqttClient.publishResult({
|
|
902
1081
|
commandId: command.id,
|
|
903
1082
|
type: 'complete',
|
|
904
|
-
content:
|
|
1083
|
+
content: finalResponseText,
|
|
1084
|
+
finalContent: finalResponseText,
|
|
1085
|
+
finalContentLength: finalResponseText.length,
|
|
905
1086
|
timestamp: Date.now(),
|
|
906
1087
|
});
|
|
907
1088
|
// Local-first: save assistant response to SQLite + trigger funnel
|
|
@@ -922,15 +1103,17 @@ TOOL EFFICIENCY RULES:
|
|
|
922
1103
|
}
|
|
923
1104
|
console.log(chalk_1.default.white(` Response: ${response.content?.slice(0, 200)}${(response.content?.length || 0) > 200 ? '...' : ''}`));
|
|
924
1105
|
const latencyMs = Date.now() - commandStartMs;
|
|
925
|
-
console.log(`[agent-metrics] commandId=${command.id} contextSections=${contextSectionCount} toolCallsMade=${totalToolCalls} iterations=${iteration} totalTokens=${totalTokensUsed} latencyMs=${latencyMs}`);
|
|
1106
|
+
console.log(`[agent-metrics] commandId=${command.id} contextSections=${contextSectionCount} toolCallsMade=${totalToolCalls} iterations=${iteration} totalTokens=${totalTokensUsed} latencyMs=${latencyMs} llmTimings="${llmTimingSamples.join(' | ')}"`);
|
|
926
1107
|
console.log(chalk_1.default.green(` Command completed: ${iteration} iterations, ${totalToolCalls} tool calls, ~${totalTokensUsed} tokens`));
|
|
927
1108
|
break;
|
|
928
1109
|
}
|
|
929
1110
|
if (iteration >= MAX_ITERATIONS) {
|
|
1111
|
+
commandFailed = true;
|
|
930
1112
|
await this.publishError(command.id, `Max iterations (${MAX_ITERATIONS}) reached`);
|
|
931
1113
|
}
|
|
932
1114
|
}
|
|
933
1115
|
catch (error) {
|
|
1116
|
+
commandFailed = true;
|
|
934
1117
|
console.error(chalk_1.default.red(` ✗ Error: ${error.message}`));
|
|
935
1118
|
await this.publishError(command.id, error.message);
|
|
936
1119
|
}
|
|
@@ -938,6 +1121,8 @@ TOOL EFFICIENCY RULES:
|
|
|
938
1121
|
this.activeCommandId = null;
|
|
939
1122
|
this.activeCommandSource = null;
|
|
940
1123
|
this.processing = false;
|
|
1124
|
+
this.commandState.set(command.id, commandFailed ? 'failed' : 'completed');
|
|
1125
|
+
this.commandCompletedAt.set(command.id, Date.now());
|
|
941
1126
|
// Clear scheduled task timeout
|
|
942
1127
|
if (this.scheduledTaskTimer) {
|
|
943
1128
|
clearTimeout(this.scheduledTaskTimer);
|
|
@@ -957,7 +1142,6 @@ TOOL EFFICIENCY RULES:
|
|
|
957
1142
|
|| this.options.systemPrompt
|
|
958
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.';
|
|
959
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.';
|
|
960
|
-
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.';
|
|
961
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.';
|
|
962
1146
|
systemPrompt += '\n\n' + (0, clerk_model_1.buildTodoInstructions)(this.options.agentName || 'LLM');
|
|
963
1147
|
// Inject model self-switch info
|
|
@@ -1002,32 +1186,6 @@ When a user asks to "use Opus" or "switch to GPT-4o", identify the right model I
|
|
|
1002
1186
|
if (command.context?.files?.length) {
|
|
1003
1187
|
systemPrompt += '\n\nRelevant files: ' + command.context.files.join(', ');
|
|
1004
1188
|
}
|
|
1005
|
-
if (command.context?.stateContext?.summaryText) {
|
|
1006
|
-
systemPrompt += '\n\n[Recent Operational State]\n' + command.context.stateContext.summaryText;
|
|
1007
|
-
}
|
|
1008
|
-
if (command.context?.workspaceManifest) {
|
|
1009
|
-
const manifestLines = [];
|
|
1010
|
-
const manifest = command.context.workspaceManifest;
|
|
1011
|
-
if (manifest.projectRoot)
|
|
1012
|
-
manifestLines.push(`- Project root: ${manifest.projectRoot}`);
|
|
1013
|
-
if (manifest.likelyWorkingDirectory)
|
|
1014
|
-
manifestLines.push(`- Likely working directory: ${manifest.likelyWorkingDirectory}`);
|
|
1015
|
-
if (manifest.recentlyReadFiles?.length)
|
|
1016
|
-
manifestLines.push(`- Recently read files: ${manifest.recentlyReadFiles.join(', ')}`);
|
|
1017
|
-
if (manifest.recentlyWrittenFiles?.length)
|
|
1018
|
-
manifestLines.push(`- Recently written files: ${manifest.recentlyWrittenFiles.join(', ')}`);
|
|
1019
|
-
if (manifest.recentlyScannedDirectories?.length)
|
|
1020
|
-
manifestLines.push(`- Recently scanned directories: ${manifest.recentlyScannedDirectories.join(', ')}`);
|
|
1021
|
-
if (manifest.recentDownloads?.length)
|
|
1022
|
-
manifestLines.push(`- Recent downloads: ${manifest.recentDownloads.join(', ')}`);
|
|
1023
|
-
if (manifest.recentFailures?.length)
|
|
1024
|
-
manifestLines.push(`- Recent failures: ${manifest.recentFailures.join(' | ')}`);
|
|
1025
|
-
if (manifest.startHereHints?.length)
|
|
1026
|
-
manifestLines.push(`- Start here: ${manifest.startHereHints.join(', ')}`);
|
|
1027
|
-
if (manifestLines.length) {
|
|
1028
|
-
systemPrompt += '\n\n[Workspace Manifest]\n' + manifestLines.join('\n');
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
1189
|
return systemPrompt;
|
|
1032
1190
|
}
|
|
1033
1191
|
async publishError(commandId, error) {
|