funolio-agent 1.0.7 → 1.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-config.d.ts +9 -1
- package/dist/agent-config.d.ts.map +1 -1
- package/dist/agent-config.js +4 -1
- package/dist/agent-config.js.map +1 -1
- package/dist/approval.d.ts +1 -0
- package/dist/approval.d.ts.map +1 -1
- package/dist/approval.js +12 -0
- package/dist/approval.js.map +1 -1
- package/dist/auth/auto-detect.d.ts +11 -3
- package/dist/auth/auto-detect.d.ts.map +1 -1
- package/dist/auth/auto-detect.js +136 -168
- package/dist/auth/auto-detect.js.map +1 -1
- package/dist/auth/subscription-runtime.js +1 -1
- package/dist/auth/subscription-runtime.js.map +1 -1
- package/dist/auto-organizer.d.ts.map +1 -1
- package/dist/auto-organizer.js +4 -3
- package/dist/auto-organizer.js.map +1 -1
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +34 -30
- package/dist/backfill.js.map +1 -1
- package/dist/bot-manager.d.ts +4 -8
- package/dist/bot-manager.d.ts.map +1 -1
- package/dist/bot-manager.js +31 -160
- package/dist/bot-manager.js.map +1 -1
- package/dist/clerk-model.d.ts +15 -7
- package/dist/clerk-model.d.ts.map +1 -1
- package/dist/clerk-model.js +78 -43
- package/dist/clerk-model.js.map +1 -1
- package/dist/cli-session-epoch.d.ts +10 -0
- package/dist/cli-session-epoch.d.ts.map +1 -0
- package/dist/cli-session-epoch.js +61 -0
- package/dist/cli-session-epoch.js.map +1 -0
- package/dist/cli.js +7 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/import-history.js +5 -1
- package/dist/commands/import-history.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +30 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pool.js +1 -1
- package/dist/commands/pool.js.map +1 -1
- package/dist/commands/setup.d.ts +37 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +146 -43
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +117 -255
- package/dist/commands/start.js.map +1 -1
- package/dist/config-cleanup.d.ts.map +1 -1
- package/dist/config-cleanup.js +2 -1
- package/dist/config-cleanup.js.map +1 -1
- package/dist/config.d.ts +6 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -18
- package/dist/config.js.map +1 -1
- package/dist/context-window.d.ts +33 -5
- package/dist/context-window.d.ts.map +1 -1
- package/dist/context-window.js +122 -21
- package/dist/context-window.js.map +1 -1
- package/dist/eval/orchestrator-front-door-replay.js +1 -1
- package/dist/eval/orchestrator-front-door-replay.js.map +1 -1
- package/dist/eval/policy-detection-replay.js +1 -1
- package/dist/eval/policy-detection-replay.js.map +1 -1
- package/dist/import-parser-core.d.ts.map +1 -1
- package/dist/import-parser-core.js +74 -8
- package/dist/import-parser-core.js.map +1 -1
- package/dist/integration-tokens.d.ts +1 -6
- package/dist/integration-tokens.d.ts.map +1 -1
- package/dist/integration-tokens.js +38 -40
- package/dist/integration-tokens.js.map +1 -1
- package/dist/local-cli-pty-manager.d.ts +50 -0
- package/dist/local-cli-pty-manager.d.ts.map +1 -0
- package/dist/local-cli-pty-manager.js +645 -0
- package/dist/local-cli-pty-manager.js.map +1 -0
- package/dist/local-data.d.ts +89 -6
- package/dist/local-data.d.ts.map +1 -1
- package/dist/local-data.js +600 -63
- package/dist/local-data.js.map +1 -1
- package/dist/local-db.d.ts.map +1 -1
- package/dist/local-db.js +74 -1
- package/dist/local-db.js.map +1 -1
- package/dist/local-funnel.d.ts +0 -7
- package/dist/local-funnel.d.ts.map +1 -1
- package/dist/local-funnel.js +22 -30
- package/dist/local-funnel.js.map +1 -1
- package/dist/local-import-worker.d.ts.map +1 -1
- package/dist/local-import-worker.js +49 -4
- package/dist/local-import-worker.js.map +1 -1
- package/dist/local-memory-search.d.ts +1 -0
- package/dist/local-memory-search.d.ts.map +1 -1
- package/dist/local-memory-search.js +107 -21
- package/dist/local-memory-search.js.map +1 -1
- package/dist/local-server.d.ts +21 -0
- package/dist/local-server.d.ts.map +1 -1
- package/dist/local-server.js +1057 -501
- package/dist/local-server.js.map +1 -1
- package/dist/mcp/bridge-server.d.ts.map +1 -1
- package/dist/mcp/bridge-server.js +2 -1
- package/dist/mcp/bridge-server.js.map +1 -1
- package/dist/mcp/local-memory-server.d.ts +6 -1
- package/dist/mcp/local-memory-server.d.ts.map +1 -1
- package/dist/mcp/local-memory-server.js +38 -13
- package/dist/mcp/local-memory-server.js.map +1 -1
- package/dist/mcp/manager.d.ts +3 -22
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +66 -320
- package/dist/mcp/manager.js.map +1 -1
- package/dist/memory-extraction.d.ts +2 -0
- package/dist/memory-extraction.d.ts.map +1 -1
- package/dist/memory-extraction.js +3 -1
- package/dist/memory-extraction.js.map +1 -1
- package/dist/message-loop.d.ts +1 -3
- package/dist/message-loop.d.ts.map +1 -1
- package/dist/message-loop.js +220 -437
- package/dist/message-loop.js.map +1 -1
- package/dist/mqtt-client.d.ts +2 -28
- package/dist/mqtt-client.d.ts.map +1 -1
- package/dist/mqtt-client.js +2 -2
- package/dist/mqtt-client.js.map +1 -1
- package/dist/oauth.d.ts +6 -0
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +91 -0
- package/dist/oauth.js.map +1 -1
- package/dist/orchestration/front-door-policy.d.ts +5 -2
- package/dist/orchestration/front-door-policy.d.ts.map +1 -1
- package/dist/orchestration/front-door-policy.js +25 -28
- package/dist/orchestration/front-door-policy.js.map +1 -1
- package/dist/orchestration/orchestrator-blocked-prompt.d.ts +2 -1
- package/dist/orchestration/orchestrator-blocked-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-blocked-prompt.js +12 -1
- package/dist/orchestration/orchestrator-blocked-prompt.js.map +1 -1
- package/dist/orchestration/orchestrator-final-response-prompt.d.ts +4 -1
- package/dist/orchestration/orchestrator-final-response-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-final-response-prompt.js +9 -7
- package/dist/orchestration/orchestrator-final-response-prompt.js.map +1 -1
- package/dist/orchestration/orchestrator-operating-prompt.d.ts +11 -0
- package/dist/orchestration/orchestrator-operating-prompt.d.ts.map +1 -1
- package/dist/orchestration/orchestrator-operating-prompt.js +67 -44
- package/dist/orchestration/orchestrator-operating-prompt.js.map +1 -1
- package/dist/orchestration/worker-operating-prompt.d.ts +2 -0
- package/dist/orchestration/worker-operating-prompt.d.ts.map +1 -1
- package/dist/orchestration/worker-operating-prompt.js +41 -2
- package/dist/orchestration/worker-operating-prompt.js.map +1 -1
- package/dist/orchestrator.d.ts +17 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +328 -166
- package/dist/orchestrator.js.map +1 -1
- package/dist/prompt-template.js +3 -3
- package/dist/prompt-template.js.map +1 -1
- package/dist/providers/anthropic.d.ts +0 -5
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +29 -75
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/claude-cli-prompt.d.ts.map +1 -1
- package/dist/providers/claude-cli-prompt.js +22 -6
- package/dist/providers/claude-cli-prompt.js.map +1 -1
- package/dist/providers/claude-cli.d.ts.map +1 -1
- package/dist/providers/claude-cli.js +36 -142
- 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 +148 -74
- package/dist/providers/codex-cli.js.map +1 -1
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +4 -2
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +27 -2
- package/dist/providers/openai.js.map +1 -1
- package/dist/runtime-context.d.ts +10 -0
- package/dist/runtime-context.d.ts.map +1 -0
- package/dist/runtime-context.js +30 -0
- package/dist/runtime-context.js.map +1 -0
- package/dist/storage-mode.d.ts +5 -0
- package/dist/storage-mode.d.ts.map +1 -0
- package/dist/storage-mode.js +21 -0
- package/dist/storage-mode.js.map +1 -0
- package/dist/subagent/queue.d.ts.map +1 -1
- package/dist/subagent/queue.js +1 -0
- package/dist/subagent/queue.js.map +1 -1
- package/dist/summarization-pipeline.d.ts +10 -0
- package/dist/summarization-pipeline.d.ts.map +1 -1
- package/dist/summarization-pipeline.js +147 -34
- package/dist/summarization-pipeline.js.map +1 -1
- package/dist/tool-permissions.d.ts +2 -0
- package/dist/tool-permissions.d.ts.map +1 -0
- package/dist/tool-permissions.js +25 -0
- package/dist/tool-permissions.js.map +1 -0
- package/dist/tools/analyze-image.js +2 -2
- package/dist/tools/analyze-image.js.map +1 -1
- package/dist/tools/edit-file.js +3 -3
- package/dist/tools/edit-file.js.map +1 -1
- package/dist/tools/index.d.ts +7 -8
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +106 -60
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-directory.js +7 -4
- package/dist/tools/list-directory.js.map +1 -1
- package/dist/tools/read-file.js +3 -3
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/run-command.js +3 -3
- package/dist/tools/run-command.js.map +1 -1
- package/dist/tools/sandbox.d.ts +10 -5
- package/dist/tools/sandbox.d.ts.map +1 -1
- package/dist/tools/sandbox.js +41 -13
- package/dist/tools/sandbox.js.map +1 -1
- package/dist/tools/search-codebase.js +2 -2
- package/dist/tools/search-codebase.js.map +1 -1
- package/dist/tools/search-local-memory.d.ts.map +1 -1
- package/dist/tools/search-local-memory.js +19 -8
- package/dist/tools/search-local-memory.js.map +1 -1
- package/dist/tools/search-memory.d.ts.map +1 -1
- package/dist/tools/search-memory.js +9 -3
- package/dist/tools/search-memory.js.map +1 -1
- package/dist/tools/spawn-subagent.d.ts.map +1 -1
- package/dist/tools/spawn-subagent.js +1 -0
- package/dist/tools/spawn-subagent.js.map +1 -1
- package/dist/tools/write-file.js +3 -3
- package/dist/tools/write-file.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +1 -1
- package/dist/verification/index.js +2 -2
- package/dist/verification/index.js.map +1 -1
- package/dist/wizard-state.d.ts.map +1 -1
- package/dist/wizard-state.js +16 -2
- package/dist/wizard-state.js.map +1 -1
- package/dist/wizard-support.d.ts +2 -2
- package/dist/wizard-support.d.ts.map +1 -1
- package/dist/wizard-support.js +88 -99
- package/dist/wizard-support.js.map +1 -1
- package/dist/workflow-engine.d.ts +9 -3
- package/dist/workflow-engine.d.ts.map +1 -1
- package/dist/workflow-engine.js +378 -82
- package/dist/workflow-engine.js.map +1 -1
- package/package.json +2 -1
package/dist/message-loop.js
CHANGED
|
@@ -47,17 +47,12 @@ const approval_1 = require("./approval");
|
|
|
47
47
|
const local_db_1 = require("./local-db");
|
|
48
48
|
const config_1 = require("./config");
|
|
49
49
|
const auto_detect_1 = require("./auth/auto-detect");
|
|
50
|
+
const anthropic_subscription_1 = require("./auth/anthropic-subscription");
|
|
50
51
|
const clerk_model_1 = require("./clerk-model");
|
|
51
52
|
const local_funnel_1 = require("./local-funnel");
|
|
52
53
|
const auto_organizer_1 = require("./auto-organizer");
|
|
53
|
-
const
|
|
54
|
-
const response_guard_1 = require("./response-guard");
|
|
55
|
-
const context_compressor_1 = require("./context-compressor");
|
|
56
|
-
const crypto = __importStar(require("crypto"));
|
|
54
|
+
const tool_permissions_1 = require("./tool-permissions");
|
|
57
55
|
const data = __importStar(require("./local-data"));
|
|
58
|
-
const agent_config_1 = require("./agent-config");
|
|
59
|
-
const subscription_runtime_1 = require("./auth/subscription-runtime");
|
|
60
|
-
const prompt_template_1 = require("./prompt-template");
|
|
61
56
|
/** Determine priority from an AgentCommand */
|
|
62
57
|
function getCommandPriority(command) {
|
|
63
58
|
if (command.priority)
|
|
@@ -72,6 +67,56 @@ function getCommandPriority(command) {
|
|
|
72
67
|
return 'medium';
|
|
73
68
|
}
|
|
74
69
|
const PRIORITY_ORDER = { high: 0, medium: 1, low: 2 };
|
|
70
|
+
function classifyToolName(toolName) {
|
|
71
|
+
switch (toolName) {
|
|
72
|
+
case 'read_file':
|
|
73
|
+
return 'file_read';
|
|
74
|
+
case 'list_directory':
|
|
75
|
+
return 'directory_scan';
|
|
76
|
+
case 'write_file':
|
|
77
|
+
case 'edit_file':
|
|
78
|
+
return 'file_write';
|
|
79
|
+
case 'run_command':
|
|
80
|
+
case 'git_status':
|
|
81
|
+
case 'git_diff':
|
|
82
|
+
case 'git_commit':
|
|
83
|
+
return 'command_run';
|
|
84
|
+
case 'web_fetch':
|
|
85
|
+
return 'web_fetch';
|
|
86
|
+
default:
|
|
87
|
+
return 'workspace_hint';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function extractToolLocation(toolName, args, projectDir) {
|
|
91
|
+
const path = typeof args.path === 'string'
|
|
92
|
+
? args.path
|
|
93
|
+
: typeof args.filePath === 'string'
|
|
94
|
+
? args.filePath
|
|
95
|
+
: typeof args.directory === 'string'
|
|
96
|
+
? args.directory
|
|
97
|
+
: undefined;
|
|
98
|
+
const cwd = typeof args.cwd === 'string'
|
|
99
|
+
? args.cwd
|
|
100
|
+
: toolName === 'run_command' || toolName.startsWith('git_')
|
|
101
|
+
? projectDir
|
|
102
|
+
: undefined;
|
|
103
|
+
const url = typeof args.url === 'string' ? args.url : undefined;
|
|
104
|
+
return { path, cwd, url };
|
|
105
|
+
}
|
|
106
|
+
function summarizeToolResult(output, isError) {
|
|
107
|
+
const prefix = isError ? 'Tool failed: ' : 'Tool completed: ';
|
|
108
|
+
const compact = output.replace(/\s+/g, ' ').trim();
|
|
109
|
+
return `${prefix}${compact}`.slice(0, 240);
|
|
110
|
+
}
|
|
111
|
+
function normalizeMcpToolNames(toolNames) {
|
|
112
|
+
if (!Array.isArray(toolNames))
|
|
113
|
+
return undefined;
|
|
114
|
+
const trimmed = toolNames
|
|
115
|
+
.filter((toolName) => typeof toolName === 'string')
|
|
116
|
+
.map((toolName) => toolName.trim())
|
|
117
|
+
.filter(Boolean);
|
|
118
|
+
return [...new Set(trimmed)];
|
|
119
|
+
}
|
|
75
120
|
class MessageLoop {
|
|
76
121
|
options;
|
|
77
122
|
llmProvider;
|
|
@@ -86,7 +131,6 @@ class MessageLoop {
|
|
|
86
131
|
resolvedAuth = null;
|
|
87
132
|
idleTimer = null;
|
|
88
133
|
approvalManager;
|
|
89
|
-
_activeAbortController = null;
|
|
90
134
|
/** Rate limiting / cost guardrails */
|
|
91
135
|
maxToolCallsPerMessage;
|
|
92
136
|
maxTokensPerMessage;
|
|
@@ -97,7 +141,6 @@ class MessageLoop {
|
|
|
97
141
|
static SCHEDULED_TASK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
98
142
|
scheduledTaskTimer = null;
|
|
99
143
|
static IDLE_THRESHOLD_MS = 15 * 60 * 1000; // 15 minutes
|
|
100
|
-
static SERVER_SYNC_RETRY_MAX = 1;
|
|
101
144
|
constructor(options) {
|
|
102
145
|
this.options = options;
|
|
103
146
|
// DO NOT remap Claude CLI OAuth sessions to the public Anthropic runtime
|
|
@@ -114,29 +157,28 @@ class MessageLoop {
|
|
|
114
157
|
if (isOAuthToken && effectiveProvider === 'anthropic') {
|
|
115
158
|
this.resolveAndUpgradeAuth(effectiveKey, options.model);
|
|
116
159
|
}
|
|
117
|
-
// Resolve Funolio API credentials for bot status reporting
|
|
118
|
-
const cfg = (0, config_1.loadConfig)();
|
|
119
|
-
const funoliApiKey = cfg.auth?.token || process.env.FUNOLIO_API_KEY || '';
|
|
120
|
-
const funoliApiBaseUrl = config_1.FUNOLIO_API_URL;
|
|
121
160
|
this.toolContext = (0, index_2.createToolContext)(options.projectDir, {
|
|
122
161
|
actorType: 'llm',
|
|
123
162
|
actorId: options.agentName || options.userId,
|
|
124
|
-
llmProvider: effectiveProvider,
|
|
125
|
-
llmModel: options.model,
|
|
126
|
-
llmApiKey: effectiveKey,
|
|
127
|
-
llmAuthMode: isOAuthToken ? 'oauth-bearer' : 'api-key',
|
|
128
|
-
apiKey: funoliApiKey,
|
|
129
|
-
apiBaseUrl: funoliApiBaseUrl,
|
|
130
|
-
botName: options.agentName || 'funolio-agent',
|
|
131
163
|
});
|
|
164
|
+
this.options.enabledTools = (0, tool_permissions_1.normalizeConfiguredToolNames)(options.enabledTools);
|
|
165
|
+
this.options.enabledMcpTools = normalizeMcpToolNames(options.enabledMcpTools);
|
|
132
166
|
let allTools = (0, index_2.getAllToolDefinitions)(options.mcpManager);
|
|
133
167
|
let builtinTools = (0, index_2.getToolDefinitions)();
|
|
134
|
-
// Filter
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
168
|
+
// Filter builtin and MCP tools independently.
|
|
169
|
+
// An explicit empty array means "deny all" for that category.
|
|
170
|
+
if (this.options.enabledTools !== undefined || this.options.enabledMcpTools !== undefined) {
|
|
171
|
+
const builtinNames = new Set(builtinTools.map((t) => t.name));
|
|
172
|
+
const allowedBuiltin = this.options.enabledTools !== undefined ? new Set(this.options.enabledTools) : null;
|
|
173
|
+
const allowedMcp = this.options.enabledMcpTools !== undefined ? new Set(this.options.enabledMcpTools) : null;
|
|
174
|
+
allTools = allTools.filter((t) => {
|
|
175
|
+
if (builtinNames.has(t.name)) {
|
|
176
|
+
return allowedBuiltin ? allowedBuiltin.has(t.name) : true;
|
|
177
|
+
}
|
|
178
|
+
return allowedMcp ? allowedMcp.has(t.name) : true;
|
|
179
|
+
});
|
|
180
|
+
builtinTools = builtinTools.filter((t) => (allowedBuiltin ? allowedBuiltin.has(t.name) : true));
|
|
181
|
+
console.log(chalk_1.default.gray(` [${options.agentName || 'agent'}] Tool filter: ${allTools.length} enabled (builtin=${this.options.enabledTools?.length ?? 'all'}, mcp=${this.options.enabledMcpTools?.length ?? 'all'})`));
|
|
140
182
|
}
|
|
141
183
|
this.toolDefinitions = allTools;
|
|
142
184
|
this.builtinToolDefinitions = builtinTools;
|
|
@@ -155,9 +197,7 @@ class MessageLoop {
|
|
|
155
197
|
});
|
|
156
198
|
// Rate limiting defaults (can be overridden via options from agent config)
|
|
157
199
|
this.maxToolCallsPerMessage = options.maxToolCallsPerMessage ?? 50;
|
|
158
|
-
|
|
159
|
-
// LLM providers handle their own context limits with proper error responses.
|
|
160
|
-
this.maxTokensPerMessage = options.maxTokensPerMessage ?? 1_000_000;
|
|
200
|
+
this.maxTokensPerMessage = options.maxTokensPerMessage ?? 200_000;
|
|
161
201
|
// Session idle detection timer (checks every 60s)
|
|
162
202
|
this.idleTimer = setInterval(() => this.checkIdleSession(), 60_000);
|
|
163
203
|
}
|
|
@@ -169,37 +209,34 @@ class MessageLoop {
|
|
|
169
209
|
*/
|
|
170
210
|
async resolveAndUpgradeAuth(oauthToken, model) {
|
|
171
211
|
try {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
212
|
+
const result = await (0, anthropic_subscription_1.resolveAnthropicSubscriptionAuth)({
|
|
213
|
+
accessToken: oauthToken,
|
|
214
|
+
refreshToken: this.options.resolvedAuth?.credential?.refreshToken || null,
|
|
215
|
+
expiresAt: this.options.resolvedAuth?.credential?.expiresAt || null,
|
|
216
|
+
onRefresh: (credential) => {
|
|
217
|
+
// Propagate refreshed credentials to BOTH auth stores
|
|
218
|
+
if (this.options.resolvedAuth) {
|
|
219
|
+
this.options.resolvedAuth.credential = credential;
|
|
220
|
+
}
|
|
221
|
+
if (this.resolvedAuth) {
|
|
222
|
+
this.resolvedAuth.credential = credential;
|
|
223
|
+
// Also update the apiKey field so ensureFreshToken() sees the new token
|
|
224
|
+
this.resolvedAuth.apiKey = credential.accessToken;
|
|
225
|
+
}
|
|
226
|
+
console.log('[message-loop] Anthropic OAuth token refreshed and propagated to runtime state');
|
|
179
227
|
},
|
|
180
|
-
inputSource: 'request:anthropic',
|
|
181
|
-
persistInputCredential: true,
|
|
182
228
|
});
|
|
183
|
-
if (
|
|
184
|
-
return;
|
|
185
|
-
if (this.options.resolvedAuth?.credential && runtime.authMode === 'oauth-bearer') {
|
|
186
|
-
this.options.resolvedAuth.credential.accessToken = runtime.apiKey;
|
|
187
|
-
}
|
|
188
|
-
if (this.resolvedAuth?.credential && runtime.authMode === 'oauth-bearer') {
|
|
189
|
-
this.resolvedAuth.credential.accessToken = runtime.apiKey;
|
|
190
|
-
this.resolvedAuth.apiKey = runtime.apiKey;
|
|
191
|
-
}
|
|
192
|
-
if (runtime.authMode !== 'oauth-bearer') {
|
|
229
|
+
if (result.source === 'api-key-exchange') {
|
|
193
230
|
// Successfully exchanged OAuth token for a temporary API key
|
|
194
231
|
console.log('[message-loop] Anthropic subscription auth: using exchanged API key');
|
|
195
232
|
this.llmProvider = (0, index_1.createProvider)('anthropic', {
|
|
196
|
-
apiKey:
|
|
233
|
+
apiKey: result.token,
|
|
197
234
|
model,
|
|
198
235
|
});
|
|
199
236
|
}
|
|
200
237
|
else {
|
|
201
238
|
// Bearer fallback — recreate provider with the (possibly refreshed) token
|
|
202
|
-
const currentToken =
|
|
239
|
+
const currentToken = this.options.resolvedAuth?.credential?.accessToken || oauthToken;
|
|
203
240
|
console.log('[message-loop] Anthropic subscription auth: using bearer fallback' +
|
|
204
241
|
(currentToken !== oauthToken ? ' (with refreshed token)' : ''));
|
|
205
242
|
this.llmProvider = (0, index_1.createProvider)('anthropic', {
|
|
@@ -213,95 +250,6 @@ class MessageLoop {
|
|
|
213
250
|
console.warn('[message-loop] Anthropic subscription auth resolution failed, keeping bearer mode:', err?.message || err);
|
|
214
251
|
}
|
|
215
252
|
}
|
|
216
|
-
async syncOAuthFromServer(reason) {
|
|
217
|
-
const providerId = this.options.provider;
|
|
218
|
-
if (!providerId || providerId.endsWith('-cli'))
|
|
219
|
-
return false;
|
|
220
|
-
const cfg = (0, config_1.loadConfig)();
|
|
221
|
-
const authToken = cfg.auth?.token;
|
|
222
|
-
if (!authToken)
|
|
223
|
-
return false;
|
|
224
|
-
try {
|
|
225
|
-
const res = await fetch(`${config_1.FUNOLIO_API_URL}/api/v1/agent/config`, {
|
|
226
|
-
headers: { Authorization: `Bearer ${authToken}` },
|
|
227
|
-
});
|
|
228
|
-
if (!res.ok)
|
|
229
|
-
return false;
|
|
230
|
-
const body = await res.json();
|
|
231
|
-
const provider = (body.providers || []).find((p) => p.id === providerId && p.connectionType === 'oauth');
|
|
232
|
-
if (!provider?.access_token)
|
|
233
|
-
return false;
|
|
234
|
-
const currentAccess = this.resolvedAuth?.credential?.accessToken || this.options.oauthToken || '';
|
|
235
|
-
const currentRefresh = this.resolvedAuth?.credential?.refreshToken || this.options.resolvedAuth?.credential?.refreshToken || '';
|
|
236
|
-
const currentExpiry = this.resolvedAuth?.credential?.expiresAt || this.options.resolvedAuth?.credential?.expiresAt || 0;
|
|
237
|
-
const nextExpiry = provider.expires_at || 0;
|
|
238
|
-
const changed = provider.access_token !== currentAccess
|
|
239
|
-
|| (provider.refresh_token || '') !== currentRefresh
|
|
240
|
-
|| nextExpiry > currentExpiry;
|
|
241
|
-
if (!changed)
|
|
242
|
-
return false;
|
|
243
|
-
if (this.resolvedAuth) {
|
|
244
|
-
this.resolvedAuth.apiKey = provider.access_token;
|
|
245
|
-
this.resolvedAuth.expired = false;
|
|
246
|
-
this.resolvedAuth.error = undefined;
|
|
247
|
-
if (this.resolvedAuth.credential) {
|
|
248
|
-
this.resolvedAuth.credential.accessToken = provider.access_token;
|
|
249
|
-
if (provider.refresh_token)
|
|
250
|
-
this.resolvedAuth.credential.refreshToken = provider.refresh_token;
|
|
251
|
-
if (provider.expires_at)
|
|
252
|
-
this.resolvedAuth.credential.expiresAt = provider.expires_at;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (this.options.resolvedAuth) {
|
|
256
|
-
this.options.resolvedAuth.apiKey = provider.access_token;
|
|
257
|
-
this.options.resolvedAuth.expired = false;
|
|
258
|
-
this.options.resolvedAuth.error = undefined;
|
|
259
|
-
if (this.options.resolvedAuth.credential) {
|
|
260
|
-
this.options.resolvedAuth.credential.accessToken = provider.access_token;
|
|
261
|
-
if (provider.refresh_token)
|
|
262
|
-
this.options.resolvedAuth.credential.refreshToken = provider.refresh_token;
|
|
263
|
-
if (provider.expires_at)
|
|
264
|
-
this.options.resolvedAuth.credential.expiresAt = provider.expires_at;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
this.options.oauthToken = provider.access_token;
|
|
268
|
-
const local = (0, config_1.loadConfig)();
|
|
269
|
-
if (!local.providers)
|
|
270
|
-
local.providers = [];
|
|
271
|
-
const idx = local.providers.findIndex((p) => p.id === providerId);
|
|
272
|
-
if (idx >= 0) {
|
|
273
|
-
local.providers[idx].authType = 'oauth';
|
|
274
|
-
local.providers[idx].oauthToken = provider.access_token;
|
|
275
|
-
if (provider.refresh_token)
|
|
276
|
-
local.providers[idx].oauthRefreshToken = provider.refresh_token;
|
|
277
|
-
if (provider.expires_at)
|
|
278
|
-
local.providers[idx].oauthExpiresAt = provider.expires_at;
|
|
279
|
-
if (provider.defaultModel)
|
|
280
|
-
local.providers[idx].defaultModel = provider.defaultModel;
|
|
281
|
-
}
|
|
282
|
-
(0, config_1.saveConfig)(local);
|
|
283
|
-
if (this.options.agentName) {
|
|
284
|
-
const agentLocal = (0, agent_config_1.loadAgentConfig)(this.options.agentName);
|
|
285
|
-
if (agentLocal && agentLocal.provider === providerId) {
|
|
286
|
-
agentLocal.oauthToken = provider.access_token;
|
|
287
|
-
if (provider.refresh_token)
|
|
288
|
-
agentLocal.oauthRefreshToken = provider.refresh_token;
|
|
289
|
-
if (provider.expires_at)
|
|
290
|
-
agentLocal.oauthExpiresAt = provider.expires_at;
|
|
291
|
-
if (provider.defaultModel)
|
|
292
|
-
agentLocal.model = provider.defaultModel;
|
|
293
|
-
(0, agent_config_1.saveAgentConfig)(this.options.agentName, agentLocal);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
this.updateToken(provider.access_token);
|
|
297
|
-
console.log(chalk_1.default.green(` [auth] Synced updated ${providerId} OAuth credentials from server (${reason})`));
|
|
298
|
-
return true;
|
|
299
|
-
}
|
|
300
|
-
catch (err) {
|
|
301
|
-
console.log(chalk_1.default.gray(` [auth] Server OAuth sync skipped (${reason}): ${err?.message || err}`));
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
253
|
/** Check if a session has gone idle and trigger auto-organize */
|
|
306
254
|
async checkIdleSession() {
|
|
307
255
|
if (!this.lastMessageAt || !(0, auto_organizer_1.isAutoOrganizeEnabled)())
|
|
@@ -402,11 +350,6 @@ class MessageLoop {
|
|
|
402
350
|
console.log(chalk_1.default.yellow(`Cancelling command ${command.id}`));
|
|
403
351
|
this.activeCommandId = null;
|
|
404
352
|
this.approvalManager.cancelAll();
|
|
405
|
-
// Abort any running tool execution
|
|
406
|
-
if (this._activeAbortController) {
|
|
407
|
-
this._activeAbortController.abort();
|
|
408
|
-
this._activeAbortController = null;
|
|
409
|
-
}
|
|
410
353
|
}
|
|
411
354
|
return;
|
|
412
355
|
}
|
|
@@ -428,30 +371,25 @@ class MessageLoop {
|
|
|
428
371
|
// For overrides with OAuth tokens, try subscription auth resolution
|
|
429
372
|
if ((apiKey || '').startsWith('sk-ant-oat01-') && provider === 'anthropic') {
|
|
430
373
|
try {
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
374
|
+
const authResult = await (0, anthropic_subscription_1.resolveAnthropicSubscriptionAuth)({
|
|
375
|
+
accessToken: apiKey,
|
|
376
|
+
refreshToken: this.options.resolvedAuth?.credential?.refreshToken || null,
|
|
377
|
+
expiresAt: this.options.resolvedAuth?.credential?.expiresAt || null,
|
|
378
|
+
onRefresh: (credential) => {
|
|
379
|
+
if (this.options.resolvedAuth) {
|
|
380
|
+
this.options.resolvedAuth.credential = credential;
|
|
381
|
+
}
|
|
382
|
+
if (this.resolvedAuth) {
|
|
383
|
+
this.resolvedAuth.credential = credential;
|
|
384
|
+
this.resolvedAuth.apiKey = credential.accessToken;
|
|
385
|
+
}
|
|
386
|
+
console.log('[message-loop] Anthropic OAuth token refreshed (override path)');
|
|
438
387
|
},
|
|
439
|
-
inputSource: 'request:anthropic',
|
|
440
|
-
persistInputCredential: true,
|
|
441
388
|
});
|
|
442
|
-
const resolvedKey = runtime?.apiKey || apiKey;
|
|
443
|
-
const authMode = runtime?.authMode;
|
|
444
|
-
if (this.options.resolvedAuth?.credential && authMode === 'oauth-bearer') {
|
|
445
|
-
this.options.resolvedAuth.credential.accessToken = resolvedKey;
|
|
446
|
-
}
|
|
447
|
-
if (this.resolvedAuth?.credential && authMode === 'oauth-bearer') {
|
|
448
|
-
this.resolvedAuth.credential.accessToken = resolvedKey;
|
|
449
|
-
this.resolvedAuth.apiKey = resolvedKey;
|
|
450
|
-
}
|
|
451
389
|
llm = (0, index_1.createProvider)(provider, {
|
|
452
|
-
apiKey:
|
|
390
|
+
apiKey: authResult.token,
|
|
453
391
|
model,
|
|
454
|
-
...(authMode === 'oauth-bearer' ? { authMode: 'oauth-bearer' } : {}),
|
|
392
|
+
...(authResult.authMode === 'oauth-bearer' ? { authMode: 'oauth-bearer' } : {}),
|
|
455
393
|
});
|
|
456
394
|
}
|
|
457
395
|
catch {
|
|
@@ -467,15 +405,6 @@ class MessageLoop {
|
|
|
467
405
|
}
|
|
468
406
|
// Refresh tool definitions each command so newly installed MCP tools are visible
|
|
469
407
|
this.toolDefinitions = (0, index_2.getAllToolDefinitions)(this._mcpManager);
|
|
470
|
-
// Fix 3: Check for dead MCP servers and note unavailable tools
|
|
471
|
-
let deadServerNote = '';
|
|
472
|
-
if (this._mcpManager) {
|
|
473
|
-
const dead = this._mcpManager.getDeadServers();
|
|
474
|
-
if (dead.length > 0) {
|
|
475
|
-
deadServerNote = `\n\n⚠️ UNAVAILABLE TOOL SERVERS: The following MCP servers have crashed and are no longer available: ${dead.join(', ')}. Do NOT attempt to use tools from these servers — they will fail. Inform the user if they ask for functionality from these servers.`;
|
|
476
|
-
console.log(chalk_1.default.yellow(` [MCP] Dead servers detected: ${dead.join(', ')}`));
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
408
|
this.activeCommandId = command.id;
|
|
480
409
|
this.activeCommandSource = command.source || 'user';
|
|
481
410
|
this.processing = true;
|
|
@@ -538,19 +467,15 @@ class MessageLoop {
|
|
|
538
467
|
: undefined;
|
|
539
468
|
const effectiveProjectId = command.context?.projectId || activeConversation?.project_id || undefined;
|
|
540
469
|
const effectiveProject = effectiveProjectId ? localData?.getProject(effectiveProjectId) : undefined;
|
|
541
|
-
// Create abort controller for this command execution
|
|
542
|
-
const commandAbortController = new AbortController();
|
|
543
|
-
this._activeAbortController = commandAbortController;
|
|
544
470
|
const commandToolContext = {
|
|
545
471
|
...this.toolContext,
|
|
546
472
|
projectId: effectiveProjectId ?? null,
|
|
547
|
-
abortSignal: commandAbortController.signal,
|
|
548
473
|
};
|
|
549
474
|
// ─── Orchestrator Mode Branch ─────────────────────────
|
|
550
475
|
// Resolve the selected bot profile — from command.bot, conversation bot_id, or default
|
|
551
476
|
const selectedBotId = command.bot?.id || activeConversation?.bot_id || localAgentId;
|
|
552
477
|
const activeProfile = selectedBotId ? localData?.getAgentProfile(selectedBotId) : null;
|
|
553
|
-
if (
|
|
478
|
+
if (activeProfile?.role_class === 'orchestrator' && localFirst) {
|
|
554
479
|
const clerk = (0, clerk_model_1.getClerk)();
|
|
555
480
|
if (!clerk) {
|
|
556
481
|
// Do not silently fall through to direct chat — report error
|
|
@@ -565,15 +490,6 @@ class MessageLoop {
|
|
|
565
490
|
const { getWorkflowEngine } = await Promise.resolve().then(() => __importStar(require('./workflow-engine')));
|
|
566
491
|
const workflowEngine = getWorkflowEngine(this.options.projectDir);
|
|
567
492
|
const orchestrator = new OrchestratorAgent(clerk, workflowEngine);
|
|
568
|
-
// Orchestrator work is conversation-scoped and can continue in parallel.
|
|
569
|
-
// Release the global message-loop lock here so another conversation can start.
|
|
570
|
-
this.activeCommandId = null;
|
|
571
|
-
this.processing = false;
|
|
572
|
-
if (this.scheduledTaskTimer) {
|
|
573
|
-
clearTimeout(this.scheduledTaskTimer);
|
|
574
|
-
this.scheduledTaskTimer = null;
|
|
575
|
-
}
|
|
576
|
-
void this.drainQueue();
|
|
577
493
|
try {
|
|
578
494
|
const response = await orchestrator.handleUserMessage(command.prompt, localConvId || '', {
|
|
579
495
|
projectDir: this.options.projectDir,
|
|
@@ -590,7 +506,7 @@ class MessageLoop {
|
|
|
590
506
|
mqttPublish: (result) => this.options.mqttClient.publishResult(result),
|
|
591
507
|
});
|
|
592
508
|
// Save O's response to local DB
|
|
593
|
-
if (localConvId && localData
|
|
509
|
+
if (localConvId && localData) {
|
|
594
510
|
localData.addMessage(localConvId, 'assistant', response, undefined, undefined, activeProfile.id, 'Project Manager');
|
|
595
511
|
}
|
|
596
512
|
// Publish final response and complete
|
|
@@ -617,7 +533,7 @@ class MessageLoop {
|
|
|
617
533
|
clearTimeout(this.scheduledTaskTimer);
|
|
618
534
|
this.scheduledTaskTimer = null;
|
|
619
535
|
}
|
|
620
|
-
|
|
536
|
+
this.drainQueue();
|
|
621
537
|
}
|
|
622
538
|
return;
|
|
623
539
|
}
|
|
@@ -662,10 +578,6 @@ class MessageLoop {
|
|
|
662
578
|
systemPrompt = this.buildFallbackSystemPrompt(command);
|
|
663
579
|
}
|
|
664
580
|
// --- Context priority rules & tool fallback (AFTER context) ---
|
|
665
|
-
// Fix 3: Inject dead server warnings into system prompt
|
|
666
|
-
if (deadServerNote) {
|
|
667
|
-
systemPrompt += deadServerNote;
|
|
668
|
-
}
|
|
669
581
|
systemPrompt += `\n\nCONTEXT PRIORITY RULES:
|
|
670
582
|
1. ALWAYS use the context provided above (decisions, memory facts, conversations) to answer questions about past work, decisions, or projects. This is your PRIMARY knowledge source — it comes from the user's stored conversation history.
|
|
671
583
|
2. Do NOT browse local files or run shell commands unless:
|
|
@@ -682,101 +594,14 @@ TOOL EFFICIENCY RULES:
|
|
|
682
594
|
// Count context sections injected into system prompt
|
|
683
595
|
const contextSectionCount = ['[Key Decisions]', '[Memory Facts]', '[Relevant Conversations]', '[Code References]', '[Relevant Files]', '[Bot Memory]', '[Retrieved Context]']
|
|
684
596
|
.filter(header => systemPrompt.includes(header)).length;
|
|
685
|
-
// Add the current prompt
|
|
686
|
-
|
|
687
|
-
const contentParts = [];
|
|
688
|
-
for (const att of command.attachments) {
|
|
689
|
-
if (att.fileType === 'image' && att.dataUrl) {
|
|
690
|
-
// Anthropic native format: source block with base64
|
|
691
|
-
const commaIdx = att.dataUrl.indexOf(',');
|
|
692
|
-
if (commaIdx >= 0) {
|
|
693
|
-
const mimeMatch = att.dataUrl.match(/^data:([^;]+);/);
|
|
694
|
-
const mediaType = mimeMatch?.[1] || att.mimeType || 'image/png';
|
|
695
|
-
const base64Data = att.dataUrl.slice(commaIdx + 1);
|
|
696
|
-
contentParts.push({ type: 'image', source: { type: 'base64', media_type: mediaType, data: base64Data } });
|
|
697
|
-
}
|
|
698
|
-
else {
|
|
699
|
-
// Fallback: treat as URL
|
|
700
|
-
contentParts.push({ type: 'image_url', image_url: { url: att.dataUrl, detail: 'auto' } });
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
if (att.extractedText) {
|
|
704
|
-
contentParts.push({ type: 'text', text: `[File: ${att.filename}]\n${att.extractedText}` });
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
contentParts.push({ type: 'text', text: command.prompt });
|
|
708
|
-
messages.push({ role: 'user', content: contentParts });
|
|
709
|
-
}
|
|
710
|
-
else {
|
|
711
|
-
messages.push({ role: 'user', content: command.prompt });
|
|
712
|
-
}
|
|
713
|
-
// Retry budget tracker (Optimization 2)
|
|
714
|
-
const toolFailuresByKey = new Map(); // toolName:argsHash → count
|
|
715
|
-
const toolFailuresByName = new Map(); // toolName → count
|
|
716
|
-
function getArgsHash(args) {
|
|
717
|
-
const str = JSON.stringify(args || {}).slice(0, 200);
|
|
718
|
-
return crypto.createHash('md5').update(str).digest('hex').slice(0, 12);
|
|
719
|
-
}
|
|
720
|
-
function trackToolFailure(name, args) {
|
|
721
|
-
const key = `${name}:${getArgsHash(args)}`;
|
|
722
|
-
toolFailuresByKey.set(key, (toolFailuresByKey.get(key) || 0) + 1);
|
|
723
|
-
toolFailuresByName.set(name, (toolFailuresByName.get(name) || 0) + 1);
|
|
724
|
-
}
|
|
725
|
-
function checkRetryBudget(name, args) {
|
|
726
|
-
const key = `${name}:${getArgsHash(args)}`;
|
|
727
|
-
const keyCount = toolFailuresByKey.get(key) || 0;
|
|
728
|
-
const nameCount = toolFailuresByName.get(name) || 0;
|
|
729
|
-
if (keyCount >= 2) {
|
|
730
|
-
return `This exact operation has failed ${keyCount} times. Stop retrying and inform the user what went wrong. Suggest an alternative approach or ask for help.`;
|
|
731
|
-
}
|
|
732
|
-
if (nameCount >= 3) {
|
|
733
|
-
return `The ${name} tool has failed ${nameCount} times in this conversation. Consider a completely different approach.`;
|
|
734
|
-
}
|
|
735
|
-
return null;
|
|
736
|
-
}
|
|
597
|
+
// Add the current prompt
|
|
598
|
+
messages.push({ role: 'user', content: command.prompt });
|
|
737
599
|
// Agentic loop - keep calling LLM until no more tool calls
|
|
738
600
|
let iteration = 0;
|
|
739
|
-
const MAX_ITERATIONS =
|
|
740
|
-
const GLOBAL_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes wall-clock timeout
|
|
601
|
+
const MAX_ITERATIONS = Infinity;
|
|
741
602
|
let totalTokensUsed = 0;
|
|
742
|
-
let totalInputTokens = 0;
|
|
743
|
-
let totalOutputTokens = 0;
|
|
744
|
-
let totalCacheReadTokens = 0;
|
|
745
|
-
let totalCacheCreationTokens = 0;
|
|
746
|
-
let totalToolCallDurationMs = 0;
|
|
747
|
-
let serverSyncRetries = 0;
|
|
748
|
-
const deniedTools = new Set(); // Track tools denied by user — don't re-ask
|
|
749
|
-
const commandStartMs = Date.now();
|
|
750
|
-
// Publish prompt context for "View Prompt" panel
|
|
751
|
-
try {
|
|
752
|
-
const promptSnapshot = [
|
|
753
|
-
{ role: 'system', content: systemPrompt.slice(0, 4000) + (systemPrompt.length > 4000 ? '\n...(truncated)' : '') },
|
|
754
|
-
...messages.map(m => ({ role: m.role, content: typeof m.content === 'string' ? m.content.slice(0, 2000) : '' })),
|
|
755
|
-
];
|
|
756
|
-
await this.options.mqttClient.publishResult({
|
|
757
|
-
commandId: command.id,
|
|
758
|
-
type: 'prompt_messages',
|
|
759
|
-
messages: promptSnapshot,
|
|
760
|
-
contextSections: contextSectionCount,
|
|
761
|
-
model,
|
|
762
|
-
timestamp: Date.now(),
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
catch { }
|
|
766
603
|
while (iteration < MAX_ITERATIONS && this.activeCommandId === command.id) {
|
|
767
604
|
iteration++;
|
|
768
|
-
// Global wall-clock timeout check
|
|
769
|
-
if (Date.now() - commandStartMs > GLOBAL_TIMEOUT_MS) {
|
|
770
|
-
console.log(chalk_1.default.red(` ⏱ Global timeout exceeded (${GLOBAL_TIMEOUT_MS / 1000}s) — breaking out of loop`));
|
|
771
|
-
await this.options.mqttClient.publishResult({
|
|
772
|
-
commandId: command.id,
|
|
773
|
-
type: 'error',
|
|
774
|
-
error: `Command execution exceeded the ${GLOBAL_TIMEOUT_MS / 60000} minute global timeout limit.`,
|
|
775
|
-
timestamp: Date.now(),
|
|
776
|
-
});
|
|
777
|
-
commandAbortController.abort();
|
|
778
|
-
break;
|
|
779
|
-
}
|
|
780
605
|
// Send a separator between agentic loop iterations so streamed text
|
|
781
606
|
// from different LLM calls doesn't run together in the UI.
|
|
782
607
|
if (iteration > 1) {
|
|
@@ -791,13 +616,6 @@ TOOL EFFICIENCY RULES:
|
|
|
791
616
|
if (this.resolvedAuth) {
|
|
792
617
|
const refreshed = await (0, auto_detect_1.ensureFreshToken)(this.resolvedAuth);
|
|
793
618
|
if (refreshed.expired) {
|
|
794
|
-
if (serverSyncRetries < MessageLoop.SERVER_SYNC_RETRY_MAX) {
|
|
795
|
-
const synced = await this.syncOAuthFromServer('refresh-failed');
|
|
796
|
-
if (synced) {
|
|
797
|
-
serverSyncRetries++;
|
|
798
|
-
continue;
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
619
|
console.error(chalk_1.default.red(` [auth] Token refresh failed: ${refreshed.error}`));
|
|
802
620
|
await this.options.mqttClient.publishResult({
|
|
803
621
|
commandId: command.id,
|
|
@@ -820,15 +638,10 @@ TOOL EFFICIENCY RULES:
|
|
|
820
638
|
if (filteredTools.length < this.toolDefinitions.length) {
|
|
821
639
|
console.log(chalk_1.default.gray(` [tool-filter] ${filteredTools.length}/${this.toolDefinitions.length} tools selected`));
|
|
822
640
|
}
|
|
823
|
-
// Compress context before sending to LLM (Optimizations 3+4)
|
|
824
|
-
const compressedMessages = (0, context_compressor_1.compressContext)(messages, 40, 3, 200);
|
|
825
|
-
if (compressedMessages.length < messages.length) {
|
|
826
|
-
console.log(chalk_1.default.gray(` [context] Compressed ${messages.length} → ${compressedMessages.length} messages`));
|
|
827
|
-
}
|
|
828
641
|
let response;
|
|
829
642
|
try {
|
|
830
643
|
response = await llm.chat({
|
|
831
|
-
messages
|
|
644
|
+
messages,
|
|
832
645
|
system: systemPrompt,
|
|
833
646
|
stream: true,
|
|
834
647
|
tools: filteredTools,
|
|
@@ -848,13 +661,6 @@ TOOL EFFICIENCY RULES:
|
|
|
848
661
|
const msg = llmError?.message || String(llmError);
|
|
849
662
|
const isAuthError = /401|403|unauthorized|forbidden|authentication/i.test(msg);
|
|
850
663
|
if (isAuthError) {
|
|
851
|
-
if (serverSyncRetries < MessageLoop.SERVER_SYNC_RETRY_MAX) {
|
|
852
|
-
const synced = await this.syncOAuthFromServer('llm-auth-error');
|
|
853
|
-
if (synced) {
|
|
854
|
-
serverSyncRetries++;
|
|
855
|
-
continue;
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
664
|
console.error(chalk_1.default.red(` [auth] LLM API authentication failed: ${msg}`));
|
|
859
665
|
await this.options.mqttClient.publishResult({
|
|
860
666
|
commandId: command.id,
|
|
@@ -866,15 +672,9 @@ TOOL EFFICIENCY RULES:
|
|
|
866
672
|
}
|
|
867
673
|
throw llmError;
|
|
868
674
|
}
|
|
869
|
-
// Any successful provider response clears one-shot sync retry budget
|
|
870
|
-
serverSyncRetries = 0;
|
|
871
675
|
// Track token usage across iterations
|
|
872
676
|
if (response.usage) {
|
|
873
677
|
totalTokensUsed += response.usage.inputTokens + response.usage.outputTokens;
|
|
874
|
-
totalInputTokens += response.usage.inputTokens;
|
|
875
|
-
totalOutputTokens += response.usage.outputTokens;
|
|
876
|
-
totalCacheReadTokens += response.usage.cacheReadTokens || 0;
|
|
877
|
-
totalCacheCreationTokens += response.usage.cacheCreationTokens || 0;
|
|
878
678
|
}
|
|
879
679
|
// If there are tool calls, publish them and continue the loop
|
|
880
680
|
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
@@ -887,30 +687,40 @@ TOOL EFFICIENCY RULES:
|
|
|
887
687
|
for (const toolCall of response.toolCalls) {
|
|
888
688
|
totalToolCalls++;
|
|
889
689
|
console.log(chalk_1.default.cyan(` 🔧 Tool: ${toolCall.name}(${JSON.stringify(toolCall.arguments).slice(0, 60)}...)`));
|
|
690
|
+
const toolSummary = (0, approval_1.generateToolSummary)(toolCall.name, toolCall.arguments);
|
|
691
|
+
const toolLocation = extractToolLocation(toolCall.name, toolCall.arguments, commandToolContext.projectDir);
|
|
890
692
|
// Check permission before execution
|
|
891
693
|
const permMode = this.options.permissionMode || 'autopilot';
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
}
|
|
694
|
+
const allowedToolNames = this.options.enabledTools !== undefined || this.options.enabledMcpTools !== undefined
|
|
695
|
+
? [...(this.options.enabledTools || []), ...(this.options.enabledMcpTools || [])]
|
|
696
|
+
: undefined;
|
|
697
|
+
let approval = (0, approval_1.checkPermission)(toolCall.name, permMode, allowedToolNames);
|
|
897
698
|
// If not auto-approved, request interactive approval via MQTT
|
|
898
|
-
if (!approval.approved && permMode !== 'autopilot'
|
|
699
|
+
if (!approval.approved && permMode !== 'autopilot') {
|
|
899
700
|
console.log(chalk_1.default.yellow(` 🔐 Requesting user approval for: ${toolCall.name}`));
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
701
|
+
approval = await this.approvalManager.requestApproval(command.id, toolCall.name, toolCall.arguments, {
|
|
702
|
+
onRequest: async (request) => {
|
|
703
|
+
await this.options.mqttClient.publishResult({
|
|
704
|
+
commandId: command.id,
|
|
705
|
+
type: 'approval_request',
|
|
706
|
+
requestId: request.requestId,
|
|
707
|
+
riskLevel: request.riskLevel,
|
|
708
|
+
summary: request.summary,
|
|
709
|
+
toolCall: {
|
|
710
|
+
id: toolCall.id,
|
|
711
|
+
name: toolCall.name,
|
|
712
|
+
arguments: toolCall.arguments,
|
|
713
|
+
classification: classifyToolName(toolCall.name),
|
|
714
|
+
summary: toolSummary,
|
|
715
|
+
path: toolLocation.path,
|
|
716
|
+
cwd: toolLocation.cwd,
|
|
717
|
+
url: toolLocation.url,
|
|
718
|
+
importance: request.riskLevel === 'destructive' ? 'high' : 'medium',
|
|
719
|
+
},
|
|
720
|
+
timestamp: Date.now(),
|
|
721
|
+
});
|
|
910
722
|
},
|
|
911
|
-
timestamp: Date.now(),
|
|
912
723
|
});
|
|
913
|
-
approval = await this.approvalManager.requestApproval(command.id, toolCall.name, toolCall.arguments);
|
|
914
724
|
// If user chose "always allow", persist it
|
|
915
725
|
if (approval.approved && approval.remember) {
|
|
916
726
|
(0, approval_1.rememberTool)(toolCall.name);
|
|
@@ -918,21 +728,27 @@ TOOL EFFICIENCY RULES:
|
|
|
918
728
|
}
|
|
919
729
|
}
|
|
920
730
|
if (!approval.approved) {
|
|
921
|
-
deniedTools.add(toolCall.name);
|
|
922
731
|
console.log(chalk_1.default.yellow(` ⚠ Tool denied: ${approval.reason}`));
|
|
732
|
+
const deniedOutput = `PERMISSION_DENIED: ${approval.reason}`;
|
|
923
733
|
await this.options.mqttClient.publishResult({
|
|
924
734
|
commandId: command.id,
|
|
925
735
|
type: 'tool_result',
|
|
926
736
|
toolResult: {
|
|
927
737
|
callId: toolCall.id,
|
|
928
|
-
output:
|
|
738
|
+
output: deniedOutput,
|
|
929
739
|
isError: true,
|
|
740
|
+
success: false,
|
|
741
|
+
summary: summarizeToolResult(deniedOutput, true),
|
|
742
|
+
path: toolLocation.path,
|
|
743
|
+
cwd: toolLocation.cwd,
|
|
744
|
+
url: toolLocation.url,
|
|
745
|
+
pathsTouched: toolLocation.path ? [toolLocation.path] : [],
|
|
930
746
|
},
|
|
931
747
|
timestamp: Date.now(),
|
|
932
748
|
});
|
|
933
749
|
messages.push({
|
|
934
750
|
role: 'tool',
|
|
935
|
-
content:
|
|
751
|
+
content: deniedOutput,
|
|
936
752
|
toolCallId: toolCall.id,
|
|
937
753
|
toolName: toolCall.name,
|
|
938
754
|
});
|
|
@@ -945,57 +761,36 @@ TOOL EFFICIENCY RULES:
|
|
|
945
761
|
id: toolCall.id,
|
|
946
762
|
name: toolCall.name,
|
|
947
763
|
arguments: toolCall.arguments,
|
|
764
|
+
classification: classifyToolName(toolCall.name),
|
|
765
|
+
summary: toolSummary,
|
|
766
|
+
path: toolLocation.path,
|
|
767
|
+
cwd: toolLocation.cwd,
|
|
768
|
+
url: toolLocation.url,
|
|
769
|
+
importance: (0, approval_1.getRiskLevel)(toolCall.name) === 'destructive' ? 'high' : 'medium',
|
|
948
770
|
},
|
|
949
771
|
timestamp: Date.now(),
|
|
950
772
|
});
|
|
951
|
-
// Check retry budget before execution (Optimization 2)
|
|
952
|
-
const retryBlock = checkRetryBudget(toolCall.name, toolCall.arguments);
|
|
953
|
-
if (retryBlock) {
|
|
954
|
-
console.log(chalk_1.default.yellow(` ⚠ Retry budget exceeded for ${toolCall.name}`));
|
|
955
|
-
const retryOutput = `ERROR: ${retryBlock}`;
|
|
956
|
-
await this.options.mqttClient.publishResult({
|
|
957
|
-
commandId: command.id,
|
|
958
|
-
type: 'tool_result',
|
|
959
|
-
toolResult: {
|
|
960
|
-
callId: toolCall.id,
|
|
961
|
-
name: toolCall.name,
|
|
962
|
-
output: retryOutput,
|
|
963
|
-
isError: true,
|
|
964
|
-
durationMs: 0,
|
|
965
|
-
},
|
|
966
|
-
timestamp: Date.now(),
|
|
967
|
-
});
|
|
968
|
-
messages.push({
|
|
969
|
-
role: 'tool',
|
|
970
|
-
content: retryOutput,
|
|
971
|
-
toolCallId: toolCall.id,
|
|
972
|
-
toolName: toolCall.name,
|
|
973
|
-
});
|
|
974
|
-
continue;
|
|
975
|
-
}
|
|
976
773
|
// Execute the tool (with MCP fallback)
|
|
977
|
-
const toolStartMs = Date.now();
|
|
978
774
|
const rawResult = await (0, tools_1.executeToolWithMCP)({ id: toolCall.id, name: toolCall.name, arguments: toolCall.arguments }, commandToolContext, this.options.mcpManager);
|
|
979
775
|
const toolResult = await (0, verification_1.verifyToolResult)(rawResult, toolCall.arguments, commandToolContext);
|
|
980
776
|
const resultOutput = toolResult.success
|
|
981
777
|
? toolResult.output
|
|
982
778
|
: `ERROR: ${toolResult.error || 'Unknown error'}`;
|
|
983
|
-
// Track failures for retry budget (Optimization 2)
|
|
984
|
-
if (!toolResult.success) {
|
|
985
|
-
trackToolFailure(toolCall.name, toolCall.arguments);
|
|
986
|
-
}
|
|
987
779
|
console.log(chalk_1.default.gray(` Result: ${toolResult.success ? '✓' : '✗'} ${resultOutput.slice(0, 100)}${resultOutput.length > 100 ? '...' : ''}`));
|
|
988
|
-
const toolDurationMs = Date.now() - toolStartMs;
|
|
989
|
-
totalToolCallDurationMs += toolDurationMs;
|
|
990
780
|
await this.options.mqttClient.publishResult({
|
|
991
781
|
commandId: command.id,
|
|
992
782
|
type: 'tool_result',
|
|
993
783
|
toolResult: {
|
|
994
784
|
callId: toolCall.id,
|
|
995
|
-
name: toolCall.name,
|
|
996
785
|
output: resultOutput,
|
|
997
786
|
isError: !toolResult.success,
|
|
998
|
-
|
|
787
|
+
success: toolResult.success,
|
|
788
|
+
summary: summarizeToolResult(resultOutput, !toolResult.success),
|
|
789
|
+
path: toolLocation.path,
|
|
790
|
+
cwd: toolLocation.cwd,
|
|
791
|
+
url: toolLocation.url,
|
|
792
|
+
pathsTouched: toolLocation.path ? [toolLocation.path] : [],
|
|
793
|
+
exitCode: typeof toolResult.exit_code === 'number' ? toolResult.exit_code : undefined,
|
|
999
794
|
},
|
|
1000
795
|
timestamp: Date.now(),
|
|
1001
796
|
});
|
|
@@ -1012,10 +807,9 @@ TOOL EFFICIENCY RULES:
|
|
|
1012
807
|
if (totalToolCalls >= this.maxToolCallsPerMessage * 0.8 && totalToolCalls < this.maxToolCallsPerMessage) {
|
|
1013
808
|
console.log(chalk_1.default.yellow(` ⚠ Approaching tool call limit: ${totalToolCalls}/${this.maxToolCallsPerMessage}`));
|
|
1014
809
|
}
|
|
1015
|
-
//
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
console.log(chalk_1.default.gray(` [tokens] iteration ${iteration}: ${response.usage.inputTokens} input, ${response.usage.outputTokens} output`));
|
|
810
|
+
// Warn at 80% of token limit
|
|
811
|
+
if (totalTokensUsed >= this.maxTokensPerMessage * 0.8 && totalTokensUsed < this.maxTokensPerMessage) {
|
|
812
|
+
console.log(chalk_1.default.yellow(` ⚠ Approaching token limit: ${totalTokensUsed}/${this.maxTokensPerMessage}`));
|
|
1019
813
|
}
|
|
1020
814
|
// Check tool call limit
|
|
1021
815
|
if (totalToolCalls >= this.maxToolCallsPerMessage) {
|
|
@@ -1042,20 +836,35 @@ TOOL EFFICIENCY RULES:
|
|
|
1042
836
|
}
|
|
1043
837
|
await this.options.mqttClient.publishResult({
|
|
1044
838
|
commandId: command.id,
|
|
1045
|
-
type: '
|
|
1046
|
-
|
|
1047
|
-
toolsUsed: totalToolCalls,
|
|
1048
|
-
inputTokens: totalInputTokens,
|
|
1049
|
-
outputTokens: totalOutputTokens,
|
|
1050
|
-
cacheReadTokens: totalCacheReadTokens || undefined,
|
|
1051
|
-
cacheCreationTokens: totalCacheCreationTokens || undefined,
|
|
1052
|
-
totalTokens: totalTokensUsed,
|
|
1053
|
-
latencyMs: Date.now() - commandStartMs,
|
|
1054
|
-
toolCallDurationMs: totalToolCallDurationMs || undefined,
|
|
1055
|
-
contextSections: contextSectionCount || undefined,
|
|
1056
|
-
model,
|
|
839
|
+
type: 'complete',
|
|
840
|
+
content: '',
|
|
1057
841
|
timestamp: Date.now(),
|
|
1058
842
|
});
|
|
843
|
+
console.log(chalk_1.default.green(` Command completed: ${iteration} iterations, ${totalToolCalls} tool calls, ~${totalTokensUsed} tokens`));
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
// Check token limit
|
|
847
|
+
if (totalTokensUsed >= this.maxTokensPerMessage) {
|
|
848
|
+
console.log(chalk_1.default.red(` 🛑 Token limit reached: ${totalTokensUsed}/${this.maxTokensPerMessage}`));
|
|
849
|
+
messages.push({ role: 'user', content: 'Token limit reached. Please summarize what you\'ve done so far.' });
|
|
850
|
+
const summaryResponse = await llm.chat({
|
|
851
|
+
messages,
|
|
852
|
+
system: systemPrompt,
|
|
853
|
+
stream: true,
|
|
854
|
+
onChunk: async (chunk) => {
|
|
855
|
+
if (this.activeCommandId !== command.id)
|
|
856
|
+
return;
|
|
857
|
+
await this.options.mqttClient.publishResult({
|
|
858
|
+
commandId: command.id,
|
|
859
|
+
type: 'chunk',
|
|
860
|
+
content: chunk,
|
|
861
|
+
timestamp: Date.now(),
|
|
862
|
+
});
|
|
863
|
+
},
|
|
864
|
+
});
|
|
865
|
+
if (summaryResponse.usage) {
|
|
866
|
+
totalTokensUsed += summaryResponse.usage.inputTokens + summaryResponse.usage.outputTokens;
|
|
867
|
+
}
|
|
1059
868
|
await this.options.mqttClient.publishResult({
|
|
1060
869
|
commandId: command.id,
|
|
1061
870
|
type: 'complete',
|
|
@@ -1065,9 +874,6 @@ TOOL EFFICIENCY RULES:
|
|
|
1065
874
|
console.log(chalk_1.default.green(` Command completed: ${iteration} iterations, ${totalToolCalls} tool calls, ~${totalTokensUsed} tokens`));
|
|
1066
875
|
break;
|
|
1067
876
|
}
|
|
1068
|
-
// No artificial token limit — LLM providers return proper errors (400/413)
|
|
1069
|
-
// when context exceeds the model's real limit, and those are caught by the
|
|
1070
|
-
// error handler above which surfaces them to the user.
|
|
1071
877
|
// Continue the loop for the next LLM call
|
|
1072
878
|
continue;
|
|
1073
879
|
}
|
|
@@ -1075,36 +881,6 @@ TOOL EFFICIENCY RULES:
|
|
|
1075
881
|
// DEDUP FIX: Content was already streamed via onChunk callbacks above.
|
|
1076
882
|
// The 'complete' message signals end-of-response; we send empty content
|
|
1077
883
|
// to avoid the receiver concatenating streamed chunks + full content.
|
|
1078
|
-
// Fix 1: Detect phantom tool use — LLM claims it did something but made 0 tool calls
|
|
1079
|
-
const phantom = (0, response_guard_1.detectPhantomToolUse)(response.content || '', totalToolCalls);
|
|
1080
|
-
if (phantom.detected) {
|
|
1081
|
-
console.log(chalk_1.default.yellow(` ⚠ Phantom tool use detected — injecting correction`));
|
|
1082
|
-
// Stream the correction to the user
|
|
1083
|
-
await this.options.mqttClient.publishResult({
|
|
1084
|
-
commandId: command.id,
|
|
1085
|
-
type: 'chunk',
|
|
1086
|
-
content: '\n\n---\n⚠️ ' + phantom.correction,
|
|
1087
|
-
timestamp: Date.now(),
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1090
|
-
// Publish stats before complete
|
|
1091
|
-
const latencyMs = Date.now() - commandStartMs;
|
|
1092
|
-
await this.options.mqttClient.publishResult({
|
|
1093
|
-
commandId: command.id,
|
|
1094
|
-
type: 'stats',
|
|
1095
|
-
llmCalls: iteration,
|
|
1096
|
-
toolsUsed: totalToolCalls,
|
|
1097
|
-
inputTokens: totalInputTokens,
|
|
1098
|
-
outputTokens: totalOutputTokens,
|
|
1099
|
-
cacheReadTokens: totalCacheReadTokens || undefined,
|
|
1100
|
-
cacheCreationTokens: totalCacheCreationTokens || undefined,
|
|
1101
|
-
totalTokens: totalTokensUsed,
|
|
1102
|
-
latencyMs,
|
|
1103
|
-
toolCallDurationMs: totalToolCallDurationMs || undefined,
|
|
1104
|
-
contextSections: contextSectionCount || undefined,
|
|
1105
|
-
model,
|
|
1106
|
-
timestamp: Date.now(),
|
|
1107
|
-
});
|
|
1108
884
|
await this.options.mqttClient.publishResult({
|
|
1109
885
|
commandId: command.id,
|
|
1110
886
|
type: 'complete',
|
|
@@ -1128,6 +904,7 @@ TOOL EFFICIENCY RULES:
|
|
|
1128
904
|
}
|
|
1129
905
|
}
|
|
1130
906
|
console.log(chalk_1.default.white(` Response: ${response.content?.slice(0, 200)}${(response.content?.length || 0) > 200 ? '...' : ''}`));
|
|
907
|
+
const latencyMs = Date.now() - commandStartMs;
|
|
1131
908
|
console.log(`[agent-metrics] commandId=${command.id} contextSections=${contextSectionCount} toolCallsMade=${totalToolCalls} iterations=${iteration} totalTokens=${totalTokensUsed} latencyMs=${latencyMs}`);
|
|
1132
909
|
console.log(chalk_1.default.green(` Command completed: ${iteration} iterations, ${totalToolCalls} tool calls, ~${totalTokensUsed} tokens`));
|
|
1133
910
|
break;
|
|
@@ -1159,31 +936,11 @@ TOOL EFFICIENCY RULES:
|
|
|
1159
936
|
}
|
|
1160
937
|
/** Build system prompt manually (without clerk) — original logic */
|
|
1161
938
|
buildFallbackSystemPrompt(command) {
|
|
1162
|
-
let systemPrompt =
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
soulMd: command.context?.soulMd || this.options.systemPrompt,
|
|
1166
|
-
});
|
|
1167
|
-
// Auto-enumerate available tools so the bot always knows its capabilities
|
|
1168
|
-
const toolDefs = (0, index_2.getToolDefinitions)();
|
|
1169
|
-
const toolList = toolDefs.map(t => `- **${t.name}**: ${t.description.slice(0, 120)}`).join('\n');
|
|
1170
|
-
systemPrompt += `\n\n## Your Available Tools\nYou have ${toolDefs.length} tools available. Use them proactively:\n${toolList}`;
|
|
1171
|
-
systemPrompt += `\n\n## Working Style
|
|
1172
|
-
- You are autonomous — take action, don't just describe what you could do.
|
|
1173
|
-
- For complex tasks, use spawn_subagent to parallelize work.
|
|
1174
|
-
- Read project files to understand context before making changes.
|
|
1175
|
-
- Commit your work with git_commit when you've made meaningful progress.
|
|
1176
|
-
- Use web_search and web_fetch to research unfamiliar topics.
|
|
1177
|
-
- Write important context to files so you remember it across sessions.
|
|
1178
|
-
- If you're unsure about something destructive, ask first. Otherwise, just do it.`;
|
|
1179
|
-
// Platform awareness — always present regardless of soulMd content
|
|
1180
|
-
systemPrompt += `\n\n## Extending Your Capabilities
|
|
1181
|
-
- You have built-in tools (file ops, shell, git, web search, web fetch, tasks, sub-agents, memory search).
|
|
1182
|
-
- If the user asks you to do something you don't have a tool for (e.g. Google Drive, GitHub issues, Slack, email, databases), search the Funolio marketplace with \`search_marketplace\`.
|
|
1183
|
-
- If a matching MCP tool server exists, use \`request_mcp_install\` to request permission to install it.
|
|
1184
|
-
- You can discover and gain new capabilities on-the-fly through the marketplace.
|
|
1185
|
-
- **NEVER tell the user to "do it manually" or "upload it yourself"** — always check the marketplace first and offer to install the right tool.`;
|
|
939
|
+
let systemPrompt = command.context?.soulMd
|
|
940
|
+
|| this.options.systemPrompt
|
|
941
|
+
|| 'You are a Funolio AI agent running locally on the user\'s machine. You have access to their project files and can execute code.';
|
|
1186
942
|
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.';
|
|
943
|
+
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.';
|
|
1187
944
|
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.';
|
|
1188
945
|
systemPrompt += '\n\n' + (0, clerk_model_1.buildTodoInstructions)(this.options.agentName || 'LLM');
|
|
1189
946
|
// Inject model self-switch info
|
|
@@ -1228,6 +985,32 @@ When a user asks to "use Opus" or "switch to GPT-4o", identify the right model I
|
|
|
1228
985
|
if (command.context?.files?.length) {
|
|
1229
986
|
systemPrompt += '\n\nRelevant files: ' + command.context.files.join(', ');
|
|
1230
987
|
}
|
|
988
|
+
if (command.context?.stateContext?.summaryText) {
|
|
989
|
+
systemPrompt += '\n\n[Recent Operational State]\n' + command.context.stateContext.summaryText;
|
|
990
|
+
}
|
|
991
|
+
if (command.context?.workspaceManifest) {
|
|
992
|
+
const manifestLines = [];
|
|
993
|
+
const manifest = command.context.workspaceManifest;
|
|
994
|
+
if (manifest.projectRoot)
|
|
995
|
+
manifestLines.push(`- Project root: ${manifest.projectRoot}`);
|
|
996
|
+
if (manifest.likelyWorkingDirectory)
|
|
997
|
+
manifestLines.push(`- Likely working directory: ${manifest.likelyWorkingDirectory}`);
|
|
998
|
+
if (manifest.recentlyReadFiles?.length)
|
|
999
|
+
manifestLines.push(`- Recently read files: ${manifest.recentlyReadFiles.join(', ')}`);
|
|
1000
|
+
if (manifest.recentlyWrittenFiles?.length)
|
|
1001
|
+
manifestLines.push(`- Recently written files: ${manifest.recentlyWrittenFiles.join(', ')}`);
|
|
1002
|
+
if (manifest.recentlyScannedDirectories?.length)
|
|
1003
|
+
manifestLines.push(`- Recently scanned directories: ${manifest.recentlyScannedDirectories.join(', ')}`);
|
|
1004
|
+
if (manifest.recentDownloads?.length)
|
|
1005
|
+
manifestLines.push(`- Recent downloads: ${manifest.recentDownloads.join(', ')}`);
|
|
1006
|
+
if (manifest.recentFailures?.length)
|
|
1007
|
+
manifestLines.push(`- Recent failures: ${manifest.recentFailures.join(' | ')}`);
|
|
1008
|
+
if (manifest.startHereHints?.length)
|
|
1009
|
+
manifestLines.push(`- Start here: ${manifest.startHereHints.join(', ')}`);
|
|
1010
|
+
if (manifestLines.length) {
|
|
1011
|
+
systemPrompt += '\n\n[Workspace Manifest]\n' + manifestLines.join('\n');
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1231
1014
|
return systemPrompt;
|
|
1232
1015
|
}
|
|
1233
1016
|
async publishError(commandId, error) {
|