funolio-agent 1.0.7 → 1.0.47
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 -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/backfill.d.ts.map +1 -1
- package/dist/backfill.js +31 -28
- package/dist/backfill.js.map +1 -1
- package/dist/bot-manager.d.ts +16 -4
- package/dist/bot-manager.d.ts.map +1 -1
- package/dist/bot-manager.js +280 -73
- package/dist/bot-manager.js.map +1 -1
- package/dist/clerk-model.d.ts +10 -6
- package/dist/clerk-model.d.ts.map +1 -1
- package/dist/clerk-model.js +39 -16
- package/dist/clerk-model.js.map +1 -1
- 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/start.d.ts.map +1 -1
- package/dist/commands/start.js +57 -225
- package/dist/commands/start.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +13 -2
- package/dist/config.js.map +1 -1
- package/dist/context-window.js +1 -1
- package/dist/context-window.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/local-data.d.ts +59 -6
- package/dist/local-data.d.ts.map +1 -1
- package/dist/local-data.js +544 -62
- package/dist/local-data.js.map +1 -1
- package/dist/local-db.d.ts.map +1 -1
- package/dist/local-db.js +21 -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 +19 -28
- 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.map +1 -1
- package/dist/local-memory-search.js +6 -3
- package/dist/local-memory-search.js.map +1 -1
- package/dist/local-server.d.ts +37 -0
- package/dist/local-server.d.ts.map +1 -1
- package/dist/local-server.js +731 -227
- package/dist/local-server.js.map +1 -1
- package/dist/mcp/local-memory-server.d.ts +1 -1
- package/dist/mcp/local-memory-server.d.ts.map +1 -1
- package/dist/mcp/local-memory-server.js +23 -11
- package/dist/mcp/local-memory-server.js.map +1 -1
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +86 -18
- package/dist/mcp/manager.js.map +1 -1
- package/dist/message-loop.d.ts +5 -10
- package/dist/message-loop.d.ts.map +1 -1
- package/dist/message-loop.js +393 -289
- package/dist/message-loop.js.map +1 -1
- package/dist/mqtt-client.d.ts +4 -1
- package/dist/mqtt-client.d.ts.map +1 -1
- package/dist/mqtt-client.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 +11 -0
- 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 +8 -6
- package/dist/orchestration/orchestrator-final-response-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 +39 -0
- package/dist/orchestration/worker-operating-prompt.js.map +1 -1
- package/dist/orchestrator.d.ts +13 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +235 -133
- package/dist/orchestrator.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.d.ts.map +1 -1
- package/dist/providers/claude-cli.js +16 -140
- 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 +78 -59
- 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 +2 -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/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/summarization-pipeline.d.ts +9 -0
- package/dist/summarization-pipeline.d.ts.map +1 -1
- package/dist/summarization-pipeline.js +61 -17
- package/dist/summarization-pipeline.js.map +1 -1
- 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.map +1 -1
- package/dist/tools/index.js +36 -0
- 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/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 +2 -0
- package/dist/types.d.ts.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 +80 -93
- package/dist/wizard-support.js.map +1 -1
- package/dist/workflow-engine.d.ts +3 -1
- package/dist/workflow-engine.d.ts.map +1 -1
- package/dist/workflow-engine.js +131 -12
- package/dist/workflow-engine.js.map +1 -1
- package/package.json +1 -1
package/dist/message-loop.js
CHANGED
|
@@ -48,16 +48,14 @@ 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
50
|
const clerk_model_1 = require("./clerk-model");
|
|
51
|
-
const local_funnel_1 = require("./local-funnel");
|
|
52
51
|
const auto_organizer_1 = require("./auto-organizer");
|
|
53
52
|
const orchestrator_profile_1 = require("./orchestrator-profile");
|
|
54
53
|
const response_guard_1 = require("./response-guard");
|
|
55
54
|
const context_compressor_1 = require("./context-compressor");
|
|
56
55
|
const crypto = __importStar(require("crypto"));
|
|
57
56
|
const data = __importStar(require("./local-data"));
|
|
58
|
-
const agent_config_1 = require("./agent-config");
|
|
59
|
-
const subscription_runtime_1 = require("./auth/subscription-runtime");
|
|
60
57
|
const prompt_template_1 = require("./prompt-template");
|
|
58
|
+
const local_funnel_1 = require("./local-funnel");
|
|
61
59
|
/** Determine priority from an AgentCommand */
|
|
62
60
|
function getCommandPriority(command) {
|
|
63
61
|
if (command.priority)
|
|
@@ -72,11 +70,53 @@ function getCommandPriority(command) {
|
|
|
72
70
|
return 'medium';
|
|
73
71
|
}
|
|
74
72
|
const PRIORITY_ORDER = { high: 0, medium: 1, low: 2 };
|
|
73
|
+
function classifyToolName(toolName) {
|
|
74
|
+
switch (toolName) {
|
|
75
|
+
case 'read_file':
|
|
76
|
+
return 'file_read';
|
|
77
|
+
case 'list_directory':
|
|
78
|
+
return 'directory_scan';
|
|
79
|
+
case 'write_file':
|
|
80
|
+
case 'edit_file':
|
|
81
|
+
return 'file_write';
|
|
82
|
+
case 'run_command':
|
|
83
|
+
case 'git_status':
|
|
84
|
+
case 'git_diff':
|
|
85
|
+
case 'git_commit':
|
|
86
|
+
return 'command_run';
|
|
87
|
+
case 'web_fetch':
|
|
88
|
+
return 'web_fetch';
|
|
89
|
+
default:
|
|
90
|
+
return 'workspace_hint';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function extractToolLocation(toolName, args, projectDir) {
|
|
94
|
+
const path = typeof args.path === 'string'
|
|
95
|
+
? args.path
|
|
96
|
+
: typeof args.filePath === 'string'
|
|
97
|
+
? args.filePath
|
|
98
|
+
: typeof args.directory === 'string'
|
|
99
|
+
? args.directory
|
|
100
|
+
: undefined;
|
|
101
|
+
const cwd = typeof args.cwd === 'string'
|
|
102
|
+
? args.cwd
|
|
103
|
+
: toolName === 'run_command' || toolName.startsWith('git_')
|
|
104
|
+
? projectDir
|
|
105
|
+
: undefined;
|
|
106
|
+
const url = typeof args.url === 'string' ? args.url : undefined;
|
|
107
|
+
return { path, cwd, url };
|
|
108
|
+
}
|
|
109
|
+
function summarizeToolResult(output, isError) {
|
|
110
|
+
const prefix = isError ? 'Tool failed: ' : 'Tool completed: ';
|
|
111
|
+
const compact = output.replace(/\s+/g, ' ').trim();
|
|
112
|
+
return `${prefix}${compact}`.slice(0, 240);
|
|
113
|
+
}
|
|
75
114
|
class MessageLoop {
|
|
76
115
|
options;
|
|
77
116
|
llmProvider;
|
|
78
117
|
activeCommandId = null;
|
|
79
118
|
activeCommandSource = null;
|
|
119
|
+
chunkSeq = 0;
|
|
80
120
|
toolContext;
|
|
81
121
|
toolDefinitions;
|
|
82
122
|
builtinToolDefinitions;
|
|
@@ -97,34 +137,31 @@ class MessageLoop {
|
|
|
97
137
|
static SCHEDULED_TASK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
98
138
|
scheduledTaskTimer = null;
|
|
99
139
|
static IDLE_THRESHOLD_MS = 15 * 60 * 1000; // 15 minutes
|
|
100
|
-
static SERVER_SYNC_RETRY_MAX = 1;
|
|
101
140
|
constructor(options) {
|
|
102
141
|
this.options = options;
|
|
103
|
-
|
|
104
|
-
// without explicit user approval.
|
|
105
|
-
const effectiveKey = options.oauthToken || options.apiKey || '';
|
|
142
|
+
const effectiveKey = options.apiKey || '';
|
|
106
143
|
const effectiveProvider = options.provider;
|
|
107
|
-
const
|
|
144
|
+
const llmAuthMode = options.authMode || (effectiveKey.startsWith('sk-ant-oat') ? 'oauth-bearer' : 'api-key');
|
|
108
145
|
this.llmProvider = (0, index_1.createProvider)(effectiveProvider, {
|
|
109
146
|
apiKey: effectiveKey,
|
|
110
147
|
model: options.model,
|
|
111
|
-
|
|
148
|
+
authMode: llmAuthMode,
|
|
149
|
+
...(options.baseUrl ? { baseUrl: options.baseUrl } : {}),
|
|
150
|
+
...(options.apiStyle ? { apiStyle: options.apiStyle } : {}),
|
|
112
151
|
});
|
|
113
|
-
// Async: attempt to resolve Anthropic subscription auth (create_api_key exchange)
|
|
114
|
-
if (isOAuthToken && effectiveProvider === 'anthropic') {
|
|
115
|
-
this.resolveAndUpgradeAuth(effectiveKey, options.model);
|
|
116
|
-
}
|
|
117
152
|
// Resolve Funolio API credentials for bot status reporting
|
|
118
153
|
const cfg = (0, config_1.loadConfig)();
|
|
119
154
|
const funoliApiKey = cfg.auth?.token || process.env.FUNOLIO_API_KEY || '';
|
|
120
155
|
const funoliApiBaseUrl = config_1.FUNOLIO_API_URL;
|
|
156
|
+
const unrestrictedCliProvider = index_1.CLI_PROVIDERS.has(effectiveProvider);
|
|
121
157
|
this.toolContext = (0, index_2.createToolContext)(options.projectDir, {
|
|
122
158
|
actorType: 'llm',
|
|
123
159
|
actorId: options.agentName || options.userId,
|
|
124
160
|
llmProvider: effectiveProvider,
|
|
125
161
|
llmModel: options.model,
|
|
126
162
|
llmApiKey: effectiveKey,
|
|
127
|
-
llmAuthMode
|
|
163
|
+
llmAuthMode,
|
|
164
|
+
restrictFileAccessToProject: unrestrictedCliProvider ? false : undefined,
|
|
128
165
|
apiKey: funoliApiKey,
|
|
129
166
|
apiBaseUrl: funoliApiBaseUrl,
|
|
130
167
|
botName: options.agentName || 'funolio-agent',
|
|
@@ -132,7 +169,7 @@ class MessageLoop {
|
|
|
132
169
|
let allTools = (0, index_2.getAllToolDefinitions)(options.mcpManager);
|
|
133
170
|
let builtinTools = (0, index_2.getToolDefinitions)();
|
|
134
171
|
// Filter tools by enabledTools whitelist if specified
|
|
135
|
-
if (options.enabledTools && options.enabledTools.length > 0) {
|
|
172
|
+
if (!unrestrictedCliProvider && options.enabledTools && options.enabledTools.length > 0) {
|
|
136
173
|
const allowed = new Set(options.enabledTools);
|
|
137
174
|
allTools = allTools.filter((t) => allowed.has(t.name));
|
|
138
175
|
builtinTools = builtinTools.filter((t) => allowed.has(t.name));
|
|
@@ -161,147 +198,6 @@ class MessageLoop {
|
|
|
161
198
|
// Session idle detection timer (checks every 60s)
|
|
162
199
|
this.idleTimer = setInterval(() => this.checkIdleSession(), 60_000);
|
|
163
200
|
}
|
|
164
|
-
/**
|
|
165
|
-
* Attempt to resolve Anthropic subscription auth via create_api_key exchange.
|
|
166
|
-
* If successful, recreates the LLM provider with the exchanged API key (which
|
|
167
|
-
* works with standard x-api-key auth and unlocks all models on the subscription).
|
|
168
|
-
* Falls back to bearer mode silently if exchange fails.
|
|
169
|
-
*/
|
|
170
|
-
async resolveAndUpgradeAuth(oauthToken, model) {
|
|
171
|
-
try {
|
|
172
|
-
const runtime = await (0, subscription_runtime_1.resolveClaudeSubscriptionRuntime)({
|
|
173
|
-
preferredModel: model,
|
|
174
|
-
inputCredential: {
|
|
175
|
-
provider: 'anthropic',
|
|
176
|
-
accessToken: oauthToken,
|
|
177
|
-
refreshToken: this.options.resolvedAuth?.credential?.refreshToken || '',
|
|
178
|
-
expiresAt: this.options.resolvedAuth?.credential?.expiresAt || 0,
|
|
179
|
-
},
|
|
180
|
-
inputSource: 'request:anthropic',
|
|
181
|
-
persistInputCredential: true,
|
|
182
|
-
});
|
|
183
|
-
if (!runtime)
|
|
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') {
|
|
193
|
-
// Successfully exchanged OAuth token for a temporary API key
|
|
194
|
-
console.log('[message-loop] Anthropic subscription auth: using exchanged API key');
|
|
195
|
-
this.llmProvider = (0, index_1.createProvider)('anthropic', {
|
|
196
|
-
apiKey: runtime.apiKey,
|
|
197
|
-
model,
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
// Bearer fallback — recreate provider with the (possibly refreshed) token
|
|
202
|
-
const currentToken = runtime.apiKey || this.options.resolvedAuth?.credential?.accessToken || oauthToken;
|
|
203
|
-
console.log('[message-loop] Anthropic subscription auth: using bearer fallback' +
|
|
204
|
-
(currentToken !== oauthToken ? ' (with refreshed token)' : ''));
|
|
205
|
-
this.llmProvider = (0, index_1.createProvider)('anthropic', {
|
|
206
|
-
apiKey: currentToken,
|
|
207
|
-
model,
|
|
208
|
-
authMode: 'oauth-bearer',
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
catch (err) {
|
|
213
|
-
console.warn('[message-loop] Anthropic subscription auth resolution failed, keeping bearer mode:', err?.message || err);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
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
201
|
/** Check if a session has gone idle and trigger auto-organize */
|
|
306
202
|
async checkIdleSession() {
|
|
307
203
|
if (!this.lastMessageAt || !(0, auto_organizer_1.isAutoOrganizeEnabled)())
|
|
@@ -419,48 +315,19 @@ class MessageLoop {
|
|
|
419
315
|
const overrideModel = command.model?.model;
|
|
420
316
|
let provider = overrideProvider || this.options.provider;
|
|
421
317
|
const model = overrideModel || this.options.model;
|
|
422
|
-
const apiKey = this.options.
|
|
318
|
+
const apiKey = this.options.apiKey;
|
|
423
319
|
// Only create a new provider if provider or model actually changed (ignore apiKey overrides from server)
|
|
424
320
|
const hasOverride = (overrideProvider && overrideProvider !== this.options.provider) ||
|
|
425
321
|
(overrideModel && overrideModel !== this.options.model);
|
|
426
322
|
let llm;
|
|
427
323
|
if (hasOverride) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
accessToken: apiKey,
|
|
436
|
-
refreshToken: this.options.resolvedAuth?.credential?.refreshToken || '',
|
|
437
|
-
expiresAt: this.options.resolvedAuth?.credential?.expiresAt || 0,
|
|
438
|
-
},
|
|
439
|
-
inputSource: 'request:anthropic',
|
|
440
|
-
persistInputCredential: true,
|
|
441
|
-
});
|
|
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
|
-
llm = (0, index_1.createProvider)(provider, {
|
|
452
|
-
apiKey: resolvedKey,
|
|
453
|
-
model,
|
|
454
|
-
...(authMode === 'oauth-bearer' ? { authMode: 'oauth-bearer' } : {}),
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
catch {
|
|
458
|
-
llm = (0, index_1.createProvider)(provider, { apiKey, model, authMode: 'oauth-bearer' });
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
else {
|
|
462
|
-
llm = (0, index_1.createProvider)(provider, { apiKey, model });
|
|
463
|
-
}
|
|
324
|
+
llm = (0, index_1.createProvider)(provider, {
|
|
325
|
+
apiKey,
|
|
326
|
+
model,
|
|
327
|
+
...(provider === this.options.provider && this.options.authMode ? { authMode: this.options.authMode } : {}),
|
|
328
|
+
...(provider === this.options.provider && this.options.baseUrl ? { baseUrl: this.options.baseUrl } : {}),
|
|
329
|
+
...(provider === this.options.provider && this.options.apiStyle ? { apiStyle: this.options.apiStyle } : {}),
|
|
330
|
+
});
|
|
464
331
|
}
|
|
465
332
|
else {
|
|
466
333
|
llm = this.llmProvider;
|
|
@@ -478,6 +345,7 @@ class MessageLoop {
|
|
|
478
345
|
}
|
|
479
346
|
this.activeCommandId = command.id;
|
|
480
347
|
this.activeCommandSource = command.source || 'user';
|
|
348
|
+
this.chunkSeq = 0;
|
|
481
349
|
this.processing = true;
|
|
482
350
|
this.lastMessageAt = Date.now();
|
|
483
351
|
const commandStartMs = Date.now();
|
|
@@ -545,6 +413,8 @@ class MessageLoop {
|
|
|
545
413
|
...this.toolContext,
|
|
546
414
|
projectId: effectiveProjectId ?? null,
|
|
547
415
|
abortSignal: commandAbortController.signal,
|
|
416
|
+
// Use project folder as working directory if available
|
|
417
|
+
...(effectiveProject?.folder ? { projectDir: effectiveProject.folder } : {}),
|
|
548
418
|
};
|
|
549
419
|
// ─── Orchestrator Mode Branch ─────────────────────────
|
|
550
420
|
// Resolve the selected bot profile — from command.bot, conversation bot_id, or default
|
|
@@ -583,27 +453,33 @@ class MessageLoop {
|
|
|
583
453
|
await this.options.mqttClient.publishResult({
|
|
584
454
|
commandId: command.id,
|
|
585
455
|
type: 'chunk',
|
|
456
|
+
seq: this.chunkSeq++,
|
|
586
457
|
content: status + '\n',
|
|
587
458
|
timestamp: Date.now(),
|
|
588
459
|
});
|
|
589
460
|
},
|
|
590
461
|
mqttPublish: (result) => this.options.mqttClient.publishResult(result),
|
|
591
462
|
});
|
|
592
|
-
|
|
463
|
+
const responseMeta = orchestrator.getLastResponseMeta();
|
|
464
|
+
const finalAgentName = responseMeta?.agentName || 'Orchestrator';
|
|
465
|
+
const finalBotId = responseMeta?.botId || (finalAgentName === 'Orchestrator' ? activeProfile?.id : undefined);
|
|
466
|
+
const finalModelLabel = responseMeta?.modelLabel || undefined;
|
|
467
|
+
// Save final response to local DB
|
|
593
468
|
if (localConvId && localData && activeProfile) {
|
|
594
|
-
localData.addMessage(localConvId, 'assistant', response,
|
|
469
|
+
localData.addMessage(localConvId, 'assistant', response, finalModelLabel, undefined, finalBotId, finalAgentName);
|
|
595
470
|
}
|
|
596
471
|
// Publish final response and complete
|
|
597
472
|
await this.options.mqttClient.publishResult({
|
|
598
473
|
commandId: command.id,
|
|
599
474
|
type: 'chunk',
|
|
475
|
+
seq: this.chunkSeq++,
|
|
600
476
|
content: response,
|
|
601
477
|
timestamp: Date.now(),
|
|
602
478
|
});
|
|
603
479
|
await this.options.mqttClient.publishResult({
|
|
604
480
|
commandId: command.id,
|
|
605
481
|
type: 'complete',
|
|
606
|
-
content: '',
|
|
482
|
+
content: response || '',
|
|
607
483
|
timestamp: Date.now(),
|
|
608
484
|
});
|
|
609
485
|
}
|
|
@@ -648,7 +524,7 @@ class MessageLoop {
|
|
|
648
524
|
includeKeyDecisions: false,
|
|
649
525
|
});
|
|
650
526
|
systemPrompt = built.systemPrompt;
|
|
651
|
-
console.log(chalk_1.default.gray(` [clerk] Context: ${built.
|
|
527
|
+
console.log(chalk_1.default.gray(` [clerk] Context: ${built.injectedSummaries} summaries (${built.contextTokensUsed} tokens)`));
|
|
652
528
|
}
|
|
653
529
|
else {
|
|
654
530
|
systemPrompt = this.buildFallbackSystemPrompt(command);
|
|
@@ -667,12 +543,12 @@ class MessageLoop {
|
|
|
667
543
|
systemPrompt += deadServerNote;
|
|
668
544
|
}
|
|
669
545
|
systemPrompt += `\n\nCONTEXT PRIORITY RULES:
|
|
670
|
-
1. ALWAYS use the context provided above (
|
|
546
|
+
1. ALWAYS use the context provided above (summaries, workflow history, and 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
547
|
2. Do NOT browse local files or run shell commands unless:
|
|
672
548
|
a. The provided context does not contain the answer, OR
|
|
673
549
|
b. The user explicitly asks you to look at local files or run a command, OR
|
|
674
550
|
c. The user asks about current runtime state (logs, running processes, file contents right now)
|
|
675
|
-
3. When you use provided context, briefly cite the source (e.g. "Based on your stored
|
|
551
|
+
3. When you use provided context, briefly cite the source (e.g. "Based on your stored summary..." or "From your conversation history...").
|
|
676
552
|
4. If no relevant context was provided AND the question is about past work/decisions, say "I don't have stored context about that topic yet" — don't guess or hallucinate.
|
|
677
553
|
|
|
678
554
|
TOOL EFFICIENCY RULES:
|
|
@@ -680,7 +556,7 @@ TOOL EFFICIENCY RULES:
|
|
|
680
556
|
- PREFER one broad command over many narrow ones (e.g. "find . -name '*.json' -exec grep 'error' {} +" instead of checking files one by one)
|
|
681
557
|
- PLAN your approach before starting — outline what you need to find, then execute with minimal tool calls`;
|
|
682
558
|
// Count context sections injected into system prompt
|
|
683
|
-
const contextSectionCount = ['[
|
|
559
|
+
const contextSectionCount = ['[Relevant Conversations]', '[Code References]', '[Relevant Files]', '[Bot Memory]', '[Retrieved Context]', '[Summaries]']
|
|
684
560
|
.filter(header => systemPrompt.includes(header)).length;
|
|
685
561
|
// Add the current prompt (with image attachments if present)
|
|
686
562
|
if (command.attachments && command.attachments.length > 0) {
|
|
@@ -717,15 +593,40 @@ TOOL EFFICIENCY RULES:
|
|
|
717
593
|
const str = JSON.stringify(args || {}).slice(0, 200);
|
|
718
594
|
return crypto.createHash('md5').update(str).digest('hex').slice(0, 12);
|
|
719
595
|
}
|
|
596
|
+
function getRetryKey(name, args) {
|
|
597
|
+
// Bug 6: For meta-tools, include action in retry key so different actions don't share budget
|
|
598
|
+
if (args?.action)
|
|
599
|
+
return `${name}:${args.action}`;
|
|
600
|
+
return name;
|
|
601
|
+
}
|
|
720
602
|
function trackToolFailure(name, args) {
|
|
721
|
-
const key = `${name}:${getArgsHash(args)}`;
|
|
603
|
+
const key = `${getRetryKey(name, args)}:${getArgsHash(args)}`;
|
|
604
|
+
const retryKey = getRetryKey(name, args);
|
|
722
605
|
toolFailuresByKey.set(key, (toolFailuresByKey.get(key) || 0) + 1);
|
|
723
|
-
toolFailuresByName.set(
|
|
606
|
+
toolFailuresByName.set(retryKey, (toolFailuresByName.get(retryKey) || 0) + 1);
|
|
607
|
+
}
|
|
608
|
+
// Futility detection: track consecutive low-value tool results
|
|
609
|
+
let consecutiveEmptyResults = 0;
|
|
610
|
+
const FUTILITY_THRESHOLD = 6; // after 6 consecutive empty/low-value results, nudge the LLM
|
|
611
|
+
let futilityNudgeInjected = false;
|
|
612
|
+
function isLowValueResult(output) {
|
|
613
|
+
if (!output || output.length < 30)
|
|
614
|
+
return true;
|
|
615
|
+
const lower = output.toLowerCase();
|
|
616
|
+
if (lower.includes('no matches found') || lower.includes('no results'))
|
|
617
|
+
return true;
|
|
618
|
+
if (lower.includes('0 bytes') || lower.includes('empty'))
|
|
619
|
+
return true;
|
|
620
|
+
// exit code 1 with no meaningful stdout
|
|
621
|
+
if (/\[exit code: [^0]/.test(lower) && output.replace(/\[exit code:.*?\]/g, '').trim().length < 20)
|
|
622
|
+
return true;
|
|
623
|
+
return false;
|
|
724
624
|
}
|
|
725
625
|
function checkRetryBudget(name, args) {
|
|
726
|
-
const key = `${name}:${getArgsHash(args)}`;
|
|
626
|
+
const key = `${getRetryKey(name, args)}:${getArgsHash(args)}`;
|
|
627
|
+
const retryKey = getRetryKey(name, args);
|
|
727
628
|
const keyCount = toolFailuresByKey.get(key) || 0;
|
|
728
|
-
const nameCount = toolFailuresByName.get(
|
|
629
|
+
const nameCount = toolFailuresByName.get(retryKey) || 0;
|
|
729
630
|
if (keyCount >= 2) {
|
|
730
631
|
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
632
|
}
|
|
@@ -736,15 +637,20 @@ TOOL EFFICIENCY RULES:
|
|
|
736
637
|
}
|
|
737
638
|
// Agentic loop - keep calling LLM until no more tool calls
|
|
738
639
|
let iteration = 0;
|
|
739
|
-
|
|
740
|
-
|
|
640
|
+
// Phase 1d: hard caps on iterations, tokens, and tool calls
|
|
641
|
+
// Web/MQTT commands (source undefined or 'user') get tighter cap; scheduled/system tasks get more room
|
|
642
|
+
const MAX_ITERATIONS = (command.source === 'scheduled' || command.source === 'system') ? 10 : 5;
|
|
643
|
+
const MAX_INPUT_TOKENS = 150_000; // 150K cumulative input token budget
|
|
644
|
+
const MAX_TOOL_CALLS = 20; // hard cap on tool calls per turn
|
|
645
|
+
const INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes of no LLM/tool activity
|
|
646
|
+
let lastActivityMs = Date.now();
|
|
647
|
+
let lastProgressMs = Date.now();
|
|
741
648
|
let totalTokensUsed = 0;
|
|
742
649
|
let totalInputTokens = 0;
|
|
743
650
|
let totalOutputTokens = 0;
|
|
744
651
|
let totalCacheReadTokens = 0;
|
|
745
652
|
let totalCacheCreationTokens = 0;
|
|
746
653
|
let totalToolCallDurationMs = 0;
|
|
747
|
-
let serverSyncRetries = 0;
|
|
748
654
|
const deniedTools = new Set(); // Track tools denied by user — don't re-ask
|
|
749
655
|
const commandStartMs = Date.now();
|
|
750
656
|
// Publish prompt context for "View Prompt" panel
|
|
@@ -765,16 +671,93 @@ TOOL EFFICIENCY RULES:
|
|
|
765
671
|
catch { }
|
|
766
672
|
while (iteration < MAX_ITERATIONS && this.activeCommandId === command.id) {
|
|
767
673
|
iteration++;
|
|
768
|
-
//
|
|
769
|
-
if (Date.now() -
|
|
770
|
-
|
|
674
|
+
// Emit progress every 60 seconds
|
|
675
|
+
if (Date.now() - lastProgressMs >= 60_000) {
|
|
676
|
+
lastProgressMs = Date.now();
|
|
771
677
|
await this.options.mqttClient.publishResult({
|
|
772
678
|
commandId: command.id,
|
|
773
|
-
type: '
|
|
774
|
-
|
|
679
|
+
type: 'progress',
|
|
680
|
+
elapsed: Math.round((Date.now() - commandStartMs) / 1000),
|
|
681
|
+
iteration,
|
|
682
|
+
toolsUsed: totalToolCalls,
|
|
775
683
|
timestamp: Date.now(),
|
|
776
684
|
});
|
|
777
|
-
|
|
685
|
+
}
|
|
686
|
+
// Inactivity timeout check (5 min of no LLM response or tool completion)
|
|
687
|
+
if (Date.now() - lastActivityMs > INACTIVITY_TIMEOUT_MS) {
|
|
688
|
+
console.log(chalk_1.default.red(` ⏱ Inactivity timeout exceeded (${INACTIVITY_TIMEOUT_MS / 1000}s) — attempting graceful wind-down`));
|
|
689
|
+
// Inject wind-down system message and do one final LLM call
|
|
690
|
+
messages.push({
|
|
691
|
+
role: 'user',
|
|
692
|
+
content: '[System] You are running out of time. Stop using tools immediately. Summarize everything you have so far and provide your best answer now.',
|
|
693
|
+
});
|
|
694
|
+
let windDownSucceeded = false;
|
|
695
|
+
let windDownResponse = null;
|
|
696
|
+
try {
|
|
697
|
+
windDownResponse = await llm.chat({
|
|
698
|
+
messages,
|
|
699
|
+
system: systemPrompt,
|
|
700
|
+
stream: true,
|
|
701
|
+
onChunk: async (chunk) => {
|
|
702
|
+
if (this.activeCommandId !== command.id)
|
|
703
|
+
return;
|
|
704
|
+
await this.options.mqttClient.publishResult({
|
|
705
|
+
commandId: command.id,
|
|
706
|
+
type: 'chunk',
|
|
707
|
+
seq: this.chunkSeq++,
|
|
708
|
+
content: chunk,
|
|
709
|
+
timestamp: Date.now(),
|
|
710
|
+
});
|
|
711
|
+
},
|
|
712
|
+
});
|
|
713
|
+
if (windDownResponse.usage) {
|
|
714
|
+
totalTokensUsed += windDownResponse.usage.inputTokens + windDownResponse.usage.outputTokens;
|
|
715
|
+
totalInputTokens += windDownResponse.usage.inputTokens;
|
|
716
|
+
totalOutputTokens += windDownResponse.usage.outputTokens;
|
|
717
|
+
}
|
|
718
|
+
windDownSucceeded = true;
|
|
719
|
+
}
|
|
720
|
+
catch (windDownErr) {
|
|
721
|
+
console.error(chalk_1.default.red(` Wind-down LLM call failed: ${windDownErr.message}`));
|
|
722
|
+
commandAbortController.abort();
|
|
723
|
+
}
|
|
724
|
+
// Emit stats
|
|
725
|
+
await this.options.mqttClient.publishResult({
|
|
726
|
+
commandId: command.id,
|
|
727
|
+
type: 'stats',
|
|
728
|
+
llmCalls: iteration,
|
|
729
|
+
toolsUsed: totalToolCalls,
|
|
730
|
+
inputTokens: totalInputTokens,
|
|
731
|
+
outputTokens: totalOutputTokens,
|
|
732
|
+
cacheReadTokens: totalCacheReadTokens || undefined,
|
|
733
|
+
cacheCreationTokens: totalCacheCreationTokens || undefined,
|
|
734
|
+
totalTokens: totalTokensUsed,
|
|
735
|
+
latencyMs: Date.now() - commandStartMs,
|
|
736
|
+
toolCallDurationMs: totalToolCallDurationMs || undefined,
|
|
737
|
+
contextSections: contextSectionCount || undefined,
|
|
738
|
+
model,
|
|
739
|
+
timestamp: Date.now(),
|
|
740
|
+
});
|
|
741
|
+
if (windDownSucceeded) {
|
|
742
|
+
// Wind-down produced a summary — treat as completed
|
|
743
|
+
await this.options.mqttClient.publishResult({
|
|
744
|
+
commandId: command.id,
|
|
745
|
+
type: 'complete',
|
|
746
|
+
content: windDownResponse?.content || '',
|
|
747
|
+
timestamp: Date.now(),
|
|
748
|
+
});
|
|
749
|
+
console.log(chalk_1.default.green(` Command completed (inactivity wind-down): ${iteration} iterations, ${totalToolCalls} tool calls`));
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
// Wind-down failed — emit error so the job is marked failed, not completed
|
|
753
|
+
await this.options.mqttClient.publishResult({
|
|
754
|
+
commandId: command.id,
|
|
755
|
+
type: 'error',
|
|
756
|
+
error: `Command timed out after ${Math.round((Date.now() - commandStartMs) / 60000)} minutes of inactivity and wind-down failed.`,
|
|
757
|
+
timestamp: Date.now(),
|
|
758
|
+
});
|
|
759
|
+
console.log(chalk_1.default.red(` Command failed (wind-down failed): ${iteration} iterations, ${totalToolCalls} tool calls`));
|
|
760
|
+
}
|
|
778
761
|
break;
|
|
779
762
|
}
|
|
780
763
|
// Send a separator between agentic loop iterations so streamed text
|
|
@@ -783,41 +766,24 @@ TOOL EFFICIENCY RULES:
|
|
|
783
766
|
await this.options.mqttClient.publishResult({
|
|
784
767
|
commandId: command.id,
|
|
785
768
|
type: 'chunk',
|
|
769
|
+
seq: this.chunkSeq++,
|
|
786
770
|
content: '\n\n',
|
|
787
771
|
timestamp: Date.now(),
|
|
788
772
|
});
|
|
789
773
|
}
|
|
790
|
-
//
|
|
774
|
+
// API keys don't expire — ensureFreshToken is a no-op for api-key source
|
|
791
775
|
if (this.resolvedAuth) {
|
|
792
776
|
const refreshed = await (0, auto_detect_1.ensureFreshToken)(this.resolvedAuth);
|
|
793
|
-
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
|
-
console.error(chalk_1.default.red(` [auth] Token refresh failed: ${refreshed.error}`));
|
|
802
|
-
await this.options.mqttClient.publishResult({
|
|
803
|
-
commandId: command.id,
|
|
804
|
-
type: 'error',
|
|
805
|
-
error: 'Your authentication token has expired or been revoked. Please re-authenticate by running `funolio-agent login` or `funolio-agent configure`.',
|
|
806
|
-
timestamp: Date.now(),
|
|
807
|
-
});
|
|
808
|
-
break;
|
|
809
|
-
}
|
|
810
777
|
if (refreshed.apiKey !== this.resolvedAuth.apiKey) {
|
|
811
778
|
this.resolvedAuth = refreshed;
|
|
812
779
|
this.updateToken(refreshed.apiKey);
|
|
813
780
|
}
|
|
814
781
|
}
|
|
815
|
-
// With OAuth auto-detection, all providers now use direct API calls
|
|
816
|
-
// with full tool support. The old CLI_PROVIDERS check is kept only
|
|
817
|
-
// for logging/diagnostics but no longer gates tool availability.
|
|
818
782
|
// Smart tool filtering: only send relevant tools to reduce token usage
|
|
819
|
-
const filteredTools =
|
|
820
|
-
|
|
783
|
+
const filteredTools = index_1.CLI_PROVIDERS.has(this.options.provider)
|
|
784
|
+
? this.toolDefinitions
|
|
785
|
+
: (0, tool_filter_1.filterToolsForMessage)(this.toolDefinitions, this.builtinToolDefinitions, command.prompt);
|
|
786
|
+
if (!index_1.CLI_PROVIDERS.has(this.options.provider) && filteredTools.length < this.toolDefinitions.length) {
|
|
821
787
|
console.log(chalk_1.default.gray(` [tool-filter] ${filteredTools.length}/${this.toolDefinitions.length} tools selected`));
|
|
822
788
|
}
|
|
823
789
|
// Compress context before sending to LLM (Optimizations 3+4)
|
|
@@ -838,6 +804,7 @@ TOOL EFFICIENCY RULES:
|
|
|
838
804
|
await this.options.mqttClient.publishResult({
|
|
839
805
|
commandId: command.id,
|
|
840
806
|
type: 'chunk',
|
|
807
|
+
seq: this.chunkSeq++,
|
|
841
808
|
content: chunk,
|
|
842
809
|
timestamp: Date.now(),
|
|
843
810
|
});
|
|
@@ -848,26 +815,19 @@ TOOL EFFICIENCY RULES:
|
|
|
848
815
|
const msg = llmError?.message || String(llmError);
|
|
849
816
|
const isAuthError = /401|403|unauthorized|forbidden|authentication/i.test(msg);
|
|
850
817
|
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
818
|
console.error(chalk_1.default.red(` [auth] LLM API authentication failed: ${msg}`));
|
|
859
819
|
await this.options.mqttClient.publishResult({
|
|
860
820
|
commandId: command.id,
|
|
861
821
|
type: 'error',
|
|
862
|
-
error: 'API authentication failed.
|
|
822
|
+
error: 'API authentication failed. Check your API key or run `funolio-agent configure` to update it.',
|
|
863
823
|
timestamp: Date.now(),
|
|
864
824
|
});
|
|
865
825
|
break;
|
|
866
826
|
}
|
|
867
827
|
throw llmError;
|
|
868
828
|
}
|
|
869
|
-
//
|
|
870
|
-
|
|
829
|
+
// Reset inactivity timer after LLM response
|
|
830
|
+
lastActivityMs = Date.now();
|
|
871
831
|
// Track token usage across iterations
|
|
872
832
|
if (response.usage) {
|
|
873
833
|
totalTokensUsed += response.usage.inputTokens + response.usage.outputTokens;
|
|
@@ -876,6 +836,60 @@ TOOL EFFICIENCY RULES:
|
|
|
876
836
|
totalCacheReadTokens += response.usage.cacheReadTokens || 0;
|
|
877
837
|
totalCacheCreationTokens += response.usage.cacheCreationTokens || 0;
|
|
878
838
|
}
|
|
839
|
+
// Phase 1d: Check cumulative input token budget
|
|
840
|
+
if (totalInputTokens >= MAX_INPUT_TOKENS) {
|
|
841
|
+
console.log(`[token-cap] Input token budget exceeded: ${totalInputTokens}/${MAX_INPUT_TOKENS} — triggering wind-down`);
|
|
842
|
+
messages.push({
|
|
843
|
+
role: 'user',
|
|
844
|
+
content: '[System] Token budget exceeded. Stop using tools immediately. Summarize everything you have so far and provide your best answer now.',
|
|
845
|
+
});
|
|
846
|
+
try {
|
|
847
|
+
const capResponse = await llm.chat({
|
|
848
|
+
messages, system: systemPrompt, stream: true,
|
|
849
|
+
onChunk: async (chunk) => {
|
|
850
|
+
if (this.activeCommandId !== command.id)
|
|
851
|
+
return;
|
|
852
|
+
await this.options.mqttClient.publishResult({ commandId: command.id, type: 'chunk', seq: this.chunkSeq++, content: chunk, timestamp: Date.now() });
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
if (capResponse.usage) {
|
|
856
|
+
totalTokensUsed += capResponse.usage.inputTokens + capResponse.usage.outputTokens;
|
|
857
|
+
totalInputTokens += capResponse.usage.inputTokens;
|
|
858
|
+
totalOutputTokens += capResponse.usage.outputTokens;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
catch (e) {
|
|
862
|
+
console.error(`[token-cap] Wind-down LLM call failed: ${e.message}`);
|
|
863
|
+
}
|
|
864
|
+
break;
|
|
865
|
+
}
|
|
866
|
+
// Phase 1d: Check tool call hard cap
|
|
867
|
+
if (totalToolCalls >= MAX_TOOL_CALLS) {
|
|
868
|
+
console.log(`[token-cap] Tool call cap exceeded: ${totalToolCalls}/${MAX_TOOL_CALLS} — triggering wind-down`);
|
|
869
|
+
messages.push({
|
|
870
|
+
role: 'user',
|
|
871
|
+
content: '[System] Tool call limit reached. Stop using tools immediately. Summarize everything you have so far and provide your best answer now.',
|
|
872
|
+
});
|
|
873
|
+
try {
|
|
874
|
+
const capResponse = await llm.chat({
|
|
875
|
+
messages, system: systemPrompt, stream: true,
|
|
876
|
+
onChunk: async (chunk) => {
|
|
877
|
+
if (this.activeCommandId !== command.id)
|
|
878
|
+
return;
|
|
879
|
+
await this.options.mqttClient.publishResult({ commandId: command.id, type: 'chunk', seq: this.chunkSeq++, content: chunk, timestamp: Date.now() });
|
|
880
|
+
},
|
|
881
|
+
});
|
|
882
|
+
if (capResponse.usage) {
|
|
883
|
+
totalTokensUsed += capResponse.usage.inputTokens + capResponse.usage.outputTokens;
|
|
884
|
+
totalInputTokens += capResponse.usage.inputTokens;
|
|
885
|
+
totalOutputTokens += capResponse.usage.outputTokens;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
catch (e) {
|
|
889
|
+
console.error(`[token-cap] Wind-down LLM call failed: ${e.message}`);
|
|
890
|
+
}
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
879
893
|
// If there are tool calls, publish them and continue the loop
|
|
880
894
|
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
881
895
|
// Add assistant message with tool calls to history
|
|
@@ -887,9 +901,13 @@ TOOL EFFICIENCY RULES:
|
|
|
887
901
|
for (const toolCall of response.toolCalls) {
|
|
888
902
|
totalToolCalls++;
|
|
889
903
|
console.log(chalk_1.default.cyan(` 🔧 Tool: ${toolCall.name}(${JSON.stringify(toolCall.arguments).slice(0, 60)}...)`));
|
|
904
|
+
const toolSummary = (0, approval_1.generateToolSummary)(toolCall.name, toolCall.arguments);
|
|
905
|
+
const toolLocation = extractToolLocation(toolCall.name, toolCall.arguments, commandToolContext.projectDir);
|
|
890
906
|
// Check permission before execution
|
|
891
|
-
const permMode = this.options.
|
|
892
|
-
|
|
907
|
+
const permMode = index_1.CLI_PROVIDERS.has(this.options.provider)
|
|
908
|
+
? 'autopilot'
|
|
909
|
+
: (this.options.permissionMode || 'autopilot');
|
|
910
|
+
let approval = (0, approval_1.checkPermission)(toolCall.name, permMode, index_1.CLI_PROVIDERS.has(this.options.provider) ? undefined : this.options.enabledTools);
|
|
893
911
|
// If tool was already denied once in this command, auto-deny without re-asking
|
|
894
912
|
if (!approval.approved && deniedTools.has(toolCall.name)) {
|
|
895
913
|
approval = { approved: false, reason: `Tool "${toolCall.name}" was already denied in this session.` };
|
|
@@ -897,20 +915,29 @@ TOOL EFFICIENCY RULES:
|
|
|
897
915
|
// If not auto-approved, request interactive approval via MQTT
|
|
898
916
|
if (!approval.approved && permMode !== 'autopilot' && !deniedTools.has(toolCall.name)) {
|
|
899
917
|
console.log(chalk_1.default.yellow(` 🔐 Requesting user approval for: ${toolCall.name}`));
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
918
|
+
approval = await this.approvalManager.requestApproval(command.id, toolCall.name, toolCall.arguments, {
|
|
919
|
+
onRequest: async (request) => {
|
|
920
|
+
await this.options.mqttClient.publishResult({
|
|
921
|
+
commandId: command.id,
|
|
922
|
+
type: 'approval_request',
|
|
923
|
+
requestId: request.requestId,
|
|
924
|
+
riskLevel: request.riskLevel,
|
|
925
|
+
summary: request.summary,
|
|
926
|
+
toolCall: {
|
|
927
|
+
id: toolCall.id,
|
|
928
|
+
name: toolCall.name,
|
|
929
|
+
arguments: toolCall.arguments,
|
|
930
|
+
classification: classifyToolName(toolCall.name),
|
|
931
|
+
summary: toolSummary,
|
|
932
|
+
path: toolLocation.path,
|
|
933
|
+
cwd: toolLocation.cwd,
|
|
934
|
+
url: toolLocation.url,
|
|
935
|
+
importance: request.riskLevel === 'destructive' ? 'high' : 'medium',
|
|
936
|
+
},
|
|
937
|
+
timestamp: Date.now(),
|
|
938
|
+
});
|
|
910
939
|
},
|
|
911
|
-
timestamp: Date.now(),
|
|
912
940
|
});
|
|
913
|
-
approval = await this.approvalManager.requestApproval(command.id, toolCall.name, toolCall.arguments);
|
|
914
941
|
// If user chose "always allow", persist it
|
|
915
942
|
if (approval.approved && approval.remember) {
|
|
916
943
|
(0, approval_1.rememberTool)(toolCall.name);
|
|
@@ -920,19 +947,26 @@ TOOL EFFICIENCY RULES:
|
|
|
920
947
|
if (!approval.approved) {
|
|
921
948
|
deniedTools.add(toolCall.name);
|
|
922
949
|
console.log(chalk_1.default.yellow(` ⚠ Tool denied: ${approval.reason}`));
|
|
950
|
+
const deniedOutput = `PERMISSION_DENIED: ${approval.reason}`;
|
|
923
951
|
await this.options.mqttClient.publishResult({
|
|
924
952
|
commandId: command.id,
|
|
925
953
|
type: 'tool_result',
|
|
926
954
|
toolResult: {
|
|
927
955
|
callId: toolCall.id,
|
|
928
|
-
output:
|
|
956
|
+
output: deniedOutput,
|
|
929
957
|
isError: true,
|
|
958
|
+
success: false,
|
|
959
|
+
summary: summarizeToolResult(deniedOutput, true),
|
|
960
|
+
path: toolLocation.path,
|
|
961
|
+
cwd: toolLocation.cwd,
|
|
962
|
+
url: toolLocation.url,
|
|
963
|
+
pathsTouched: toolLocation.path ? [toolLocation.path] : [],
|
|
930
964
|
},
|
|
931
965
|
timestamp: Date.now(),
|
|
932
966
|
});
|
|
933
967
|
messages.push({
|
|
934
968
|
role: 'tool',
|
|
935
|
-
content:
|
|
969
|
+
content: deniedOutput,
|
|
936
970
|
toolCallId: toolCall.id,
|
|
937
971
|
toolName: toolCall.name,
|
|
938
972
|
});
|
|
@@ -945,6 +979,12 @@ TOOL EFFICIENCY RULES:
|
|
|
945
979
|
id: toolCall.id,
|
|
946
980
|
name: toolCall.name,
|
|
947
981
|
arguments: toolCall.arguments,
|
|
982
|
+
classification: classifyToolName(toolCall.name),
|
|
983
|
+
summary: toolSummary,
|
|
984
|
+
path: toolLocation.path,
|
|
985
|
+
cwd: toolLocation.cwd,
|
|
986
|
+
url: toolLocation.url,
|
|
987
|
+
importance: (0, approval_1.getRiskLevel)(toolCall.name) === 'destructive' ? 'high' : 'medium',
|
|
948
988
|
},
|
|
949
989
|
timestamp: Date.now(),
|
|
950
990
|
});
|
|
@@ -977,9 +1017,16 @@ TOOL EFFICIENCY RULES:
|
|
|
977
1017
|
const toolStartMs = Date.now();
|
|
978
1018
|
const rawResult = await (0, tools_1.executeToolWithMCP)({ id: toolCall.id, name: toolCall.name, arguments: toolCall.arguments }, commandToolContext, this.options.mcpManager);
|
|
979
1019
|
const toolResult = await (0, verification_1.verifyToolResult)(rawResult, toolCall.arguments, commandToolContext);
|
|
980
|
-
|
|
1020
|
+
let resultOutput = toolResult.success
|
|
981
1021
|
? toolResult.output
|
|
982
1022
|
: `ERROR: ${toolResult.error || 'Unknown error'}`;
|
|
1023
|
+
// Bug 2 & 7: Cap tool result size to prevent context blowup
|
|
1024
|
+
const MAX_TOOL_RESULT_CHARS = 50_000;
|
|
1025
|
+
if (resultOutput.length > MAX_TOOL_RESULT_CHARS) {
|
|
1026
|
+
const originalLength = resultOutput.length;
|
|
1027
|
+
resultOutput = resultOutput.slice(0, MAX_TOOL_RESULT_CHARS) +
|
|
1028
|
+
`\n\n[OUTPUT TRUNCATED — was ${originalLength} chars. Use more specific commands to read smaller portions.]`;
|
|
1029
|
+
}
|
|
983
1030
|
// Track failures for retry budget (Optimization 2)
|
|
984
1031
|
if (!toolResult.success) {
|
|
985
1032
|
trackToolFailure(toolCall.name, toolCall.arguments);
|
|
@@ -987,6 +1034,7 @@ TOOL EFFICIENCY RULES:
|
|
|
987
1034
|
console.log(chalk_1.default.gray(` Result: ${toolResult.success ? '✓' : '✗'} ${resultOutput.slice(0, 100)}${resultOutput.length > 100 ? '...' : ''}`));
|
|
988
1035
|
const toolDurationMs = Date.now() - toolStartMs;
|
|
989
1036
|
totalToolCallDurationMs += toolDurationMs;
|
|
1037
|
+
lastActivityMs = Date.now(); // Reset inactivity timer after tool completion
|
|
990
1038
|
await this.options.mqttClient.publishResult({
|
|
991
1039
|
commandId: command.id,
|
|
992
1040
|
type: 'tool_result',
|
|
@@ -996,6 +1044,13 @@ TOOL EFFICIENCY RULES:
|
|
|
996
1044
|
output: resultOutput,
|
|
997
1045
|
isError: !toolResult.success,
|
|
998
1046
|
durationMs: toolDurationMs,
|
|
1047
|
+
success: toolResult.success,
|
|
1048
|
+
summary: summarizeToolResult(resultOutput, !toolResult.success),
|
|
1049
|
+
path: toolLocation.path,
|
|
1050
|
+
cwd: toolLocation.cwd,
|
|
1051
|
+
url: toolLocation.url,
|
|
1052
|
+
pathsTouched: toolLocation.path ? [toolLocation.path] : [],
|
|
1053
|
+
exitCode: typeof toolResult.exit_code === 'number' ? toolResult.exit_code : undefined,
|
|
999
1054
|
},
|
|
1000
1055
|
timestamp: Date.now(),
|
|
1001
1056
|
});
|
|
@@ -1006,6 +1061,22 @@ TOOL EFFICIENCY RULES:
|
|
|
1006
1061
|
toolCallId: toolCall.id,
|
|
1007
1062
|
toolName: toolCall.name,
|
|
1008
1063
|
});
|
|
1064
|
+
// Futility detection: track consecutive low-value results
|
|
1065
|
+
if (isLowValueResult(resultOutput)) {
|
|
1066
|
+
consecutiveEmptyResults++;
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
consecutiveEmptyResults = 0; // reset on any meaningful result
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
// Futility check: if too many consecutive empty results, nudge the LLM to stop spinning
|
|
1073
|
+
if (consecutiveEmptyResults >= FUTILITY_THRESHOLD && !futilityNudgeInjected) {
|
|
1074
|
+
futilityNudgeInjected = true;
|
|
1075
|
+
console.log(chalk_1.default.yellow(` ⚠ Futility detected: ${consecutiveEmptyResults} consecutive low-value tool results — nudging LLM`));
|
|
1076
|
+
messages.push({
|
|
1077
|
+
role: 'user',
|
|
1078
|
+
content: '[System] Your last several tool calls returned empty or no-match results. Stop searching and work with what you have. Summarize what you found (or could not find) and suggest next steps to the user. Do not make more search attempts.',
|
|
1079
|
+
});
|
|
1009
1080
|
}
|
|
1010
1081
|
// --- Rate limit checks ---
|
|
1011
1082
|
// Warn at 80% of tool call limit
|
|
@@ -1032,6 +1103,7 @@ TOOL EFFICIENCY RULES:
|
|
|
1032
1103
|
await this.options.mqttClient.publishResult({
|
|
1033
1104
|
commandId: command.id,
|
|
1034
1105
|
type: 'chunk',
|
|
1106
|
+
seq: this.chunkSeq++,
|
|
1035
1107
|
content: chunk,
|
|
1036
1108
|
timestamp: Date.now(),
|
|
1037
1109
|
});
|
|
@@ -1059,7 +1131,7 @@ TOOL EFFICIENCY RULES:
|
|
|
1059
1131
|
await this.options.mqttClient.publishResult({
|
|
1060
1132
|
commandId: command.id,
|
|
1061
1133
|
type: 'complete',
|
|
1062
|
-
content: '',
|
|
1134
|
+
content: response.content || '',
|
|
1063
1135
|
timestamp: Date.now(),
|
|
1064
1136
|
});
|
|
1065
1137
|
console.log(chalk_1.default.green(` Command completed: ${iteration} iterations, ${totalToolCalls} tool calls, ~${totalTokensUsed} tokens`));
|
|
@@ -1072,9 +1144,9 @@ TOOL EFFICIENCY RULES:
|
|
|
1072
1144
|
continue;
|
|
1073
1145
|
}
|
|
1074
1146
|
// No tool calls - final response.
|
|
1075
|
-
//
|
|
1076
|
-
// The
|
|
1077
|
-
//
|
|
1147
|
+
// Phase 2a: The 'complete' message includes the full response content.
|
|
1148
|
+
// The server-side relay (mqtt-agent-relay.ts) prefers this over
|
|
1149
|
+
// reassembled chunks for higher-fidelity persistence.
|
|
1078
1150
|
// Fix 1: Detect phantom tool use — LLM claims it did something but made 0 tool calls
|
|
1079
1151
|
const phantom = (0, response_guard_1.detectPhantomToolUse)(response.content || '', totalToolCalls);
|
|
1080
1152
|
if (phantom.detected) {
|
|
@@ -1083,6 +1155,7 @@ TOOL EFFICIENCY RULES:
|
|
|
1083
1155
|
await this.options.mqttClient.publishResult({
|
|
1084
1156
|
commandId: command.id,
|
|
1085
1157
|
type: 'chunk',
|
|
1158
|
+
seq: this.chunkSeq++,
|
|
1086
1159
|
content: '\n\n---\n⚠️ ' + phantom.correction,
|
|
1087
1160
|
timestamp: Date.now(),
|
|
1088
1161
|
});
|
|
@@ -1108,7 +1181,7 @@ TOOL EFFICIENCY RULES:
|
|
|
1108
1181
|
await this.options.mqttClient.publishResult({
|
|
1109
1182
|
commandId: command.id,
|
|
1110
1183
|
type: 'complete',
|
|
1111
|
-
content: '',
|
|
1184
|
+
content: response.content || '',
|
|
1112
1185
|
timestamp: Date.now(),
|
|
1113
1186
|
});
|
|
1114
1187
|
// Local-first: save assistant response to SQLite + trigger funnel
|
|
@@ -1116,7 +1189,6 @@ TOOL EFFICIENCY RULES:
|
|
|
1116
1189
|
try {
|
|
1117
1190
|
const localData = await Promise.resolve().then(() => __importStar(require('./local-data')));
|
|
1118
1191
|
localData.addMessage(localConvId, 'assistant', response.content || '', model);
|
|
1119
|
-
// Trigger background memory extraction
|
|
1120
1192
|
(0, local_funnel_1.scheduleFunnelProcessing)(localConvId);
|
|
1121
1193
|
// Track for session idle auto-organize
|
|
1122
1194
|
this.lastOrganizedConvId = localConvId;
|
|
@@ -1137,7 +1209,22 @@ TOOL EFFICIENCY RULES:
|
|
|
1137
1209
|
}
|
|
1138
1210
|
}
|
|
1139
1211
|
catch (error) {
|
|
1140
|
-
|
|
1212
|
+
// Capture detailed provider error info for diagnosis
|
|
1213
|
+
const details = [` ✗ Error: ${error.message}`];
|
|
1214
|
+
if (error.status)
|
|
1215
|
+
details.push(` ✗ HTTP status: ${error.status}`);
|
|
1216
|
+
if (error.type)
|
|
1217
|
+
details.push(` ✗ Error type: ${error.type}`);
|
|
1218
|
+
if (error.response?.body)
|
|
1219
|
+
details.push(` ✗ Response body: ${JSON.stringify(error.response.body).slice(0, 500)}`);
|
|
1220
|
+
if (error.error?.type)
|
|
1221
|
+
details.push(` ✗ Provider error type: ${error.error.type}`);
|
|
1222
|
+
if (error.cause)
|
|
1223
|
+
details.push(` ✗ Cause: ${error.cause}`);
|
|
1224
|
+
// For Anthropic "terminated" / stop_reason issues
|
|
1225
|
+
if (error.stop_reason)
|
|
1226
|
+
details.push(` ✗ Stop reason: ${error.stop_reason}`);
|
|
1227
|
+
details.forEach(d => console.error(chalk_1.default.red(d)));
|
|
1141
1228
|
await this.publishError(command.id, error.message);
|
|
1142
1229
|
}
|
|
1143
1230
|
finally {
|
|
@@ -1183,7 +1270,8 @@ TOOL EFFICIENCY RULES:
|
|
|
1183
1270
|
- If a matching MCP tool server exists, use \`request_mcp_install\` to request permission to install it.
|
|
1184
1271
|
- You can discover and gain new capabilities on-the-fly through the marketplace.
|
|
1185
1272
|
- **NEVER tell the user to "do it manually" or "upload it yourself"** — always check the marketplace first and offer to install the right tool.`;
|
|
1186
|
-
systemPrompt += '\n\nIMPORTANT: When the user references a project, topic, or past work, use the relevant
|
|
1273
|
+
systemPrompt += '\n\nIMPORTANT: When the user references a project, topic, or past work, use the relevant summaries and conversation history provided below. If no relevant context is available, say so honestly rather than guessing. Use your tools (file browsing, commands) to find project files on the local machine.';
|
|
1274
|
+
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
1275
|
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
1276
|
systemPrompt += '\n\n' + (0, clerk_model_1.buildTodoInstructions)(this.options.agentName || 'LLM');
|
|
1189
1277
|
// Inject model self-switch info
|
|
@@ -1205,13 +1293,7 @@ When a user asks to "use Opus" or "switch to GPT-4o", identify the right model I
|
|
|
1205
1293
|
systemPrompt += '\n\n[Bot Memory]\n' + command.context.memoryMd;
|
|
1206
1294
|
}
|
|
1207
1295
|
const sc = command.context?.structuredContext;
|
|
1208
|
-
if (sc && (sc.
|
|
1209
|
-
if (sc.decisions?.length) {
|
|
1210
|
-
systemPrompt += '\n\n[Key Decisions]\n' + sc.decisions.map(d => `- ${d}`).join('\n');
|
|
1211
|
-
}
|
|
1212
|
-
if (sc.memoryFacts?.length) {
|
|
1213
|
-
systemPrompt += '\n\n[Memory Facts]\n' + sc.memoryFacts.map(f => `- ${f}`).join('\n');
|
|
1214
|
-
}
|
|
1296
|
+
if (sc && (sc.codeRefs?.length || sc.conversations?.length)) {
|
|
1215
1297
|
if (sc.conversations?.length) {
|
|
1216
1298
|
systemPrompt += '\n\n[Relevant Conversations]\n' + sc.conversations.map(c => `- ${c}`).join('\n');
|
|
1217
1299
|
}
|
|
@@ -1219,15 +1301,38 @@ When a user asks to "use Opus" or "switch to GPT-4o", identify the right model I
|
|
|
1219
1301
|
systemPrompt += '\n\n[Code References]\n' + sc.codeRefs.map(r => `- ${r}`).join('\n');
|
|
1220
1302
|
}
|
|
1221
1303
|
}
|
|
1222
|
-
else if (command.context?.facts?.length) {
|
|
1223
|
-
systemPrompt += '\n\nRelevant memory/facts from the user\'s stored conversations:\n' + command.context.facts.map(f => `- ${f}`).join('\n');
|
|
1224
|
-
}
|
|
1225
1304
|
else {
|
|
1226
|
-
systemPrompt += '\n\nNo relevant
|
|
1305
|
+
systemPrompt += '\n\nNo relevant stored summaries were found for this query. If the user is asking about a specific project or past work, let them know you don\'t have stored context about it and offer to search their local files instead.';
|
|
1227
1306
|
}
|
|
1228
1307
|
if (command.context?.files?.length) {
|
|
1229
1308
|
systemPrompt += '\n\nRelevant files: ' + command.context.files.join(', ');
|
|
1230
1309
|
}
|
|
1310
|
+
if (command.context?.stateContext?.summaryText) {
|
|
1311
|
+
systemPrompt += '\n\n[Recent Operational State]\n' + command.context.stateContext.summaryText;
|
|
1312
|
+
}
|
|
1313
|
+
if (command.context?.workspaceManifest) {
|
|
1314
|
+
const manifestLines = [];
|
|
1315
|
+
const manifest = command.context.workspaceManifest;
|
|
1316
|
+
if (manifest.projectRoot)
|
|
1317
|
+
manifestLines.push(`- Project root: ${manifest.projectRoot}`);
|
|
1318
|
+
if (manifest.likelyWorkingDirectory)
|
|
1319
|
+
manifestLines.push(`- Likely working directory: ${manifest.likelyWorkingDirectory}`);
|
|
1320
|
+
if (manifest.recentlyReadFiles?.length)
|
|
1321
|
+
manifestLines.push(`- Recently read files: ${manifest.recentlyReadFiles.join(', ')}`);
|
|
1322
|
+
if (manifest.recentlyWrittenFiles?.length)
|
|
1323
|
+
manifestLines.push(`- Recently written files: ${manifest.recentlyWrittenFiles.join(', ')}`);
|
|
1324
|
+
if (manifest.recentlyScannedDirectories?.length)
|
|
1325
|
+
manifestLines.push(`- Recently scanned directories: ${manifest.recentlyScannedDirectories.join(', ')}`);
|
|
1326
|
+
if (manifest.recentDownloads?.length)
|
|
1327
|
+
manifestLines.push(`- Recent downloads: ${manifest.recentDownloads.join(', ')}`);
|
|
1328
|
+
if (manifest.recentFailures?.length)
|
|
1329
|
+
manifestLines.push(`- Recent failures: ${manifest.recentFailures.join(' | ')}`);
|
|
1330
|
+
if (manifest.startHereHints?.length)
|
|
1331
|
+
manifestLines.push(`- Start here: ${manifest.startHereHints.join(', ')}`);
|
|
1332
|
+
if (manifestLines.length) {
|
|
1333
|
+
systemPrompt += '\n\n[Workspace Manifest]\n' + manifestLines.join('\n');
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1231
1336
|
return systemPrompt;
|
|
1232
1337
|
}
|
|
1233
1338
|
async publishError(commandId, error) {
|
|
@@ -1240,19 +1345,18 @@ When a user asks to "use Opus" or "switch to GPT-4o", identify the right model I
|
|
|
1240
1345
|
timestamp: Date.now(),
|
|
1241
1346
|
});
|
|
1242
1347
|
}
|
|
1243
|
-
/** Update the
|
|
1348
|
+
/** Update the API key used for requests */
|
|
1244
1349
|
updateToken(token) {
|
|
1245
|
-
this.options.
|
|
1246
|
-
// Recreate the default provider with the new token
|
|
1350
|
+
this.options.apiKey = token;
|
|
1247
1351
|
const effectiveProvider = this.options.provider;
|
|
1352
|
+
const authMode = this.options.authMode || (token.startsWith('sk-ant-oat') ? 'oauth-bearer' : 'api-key');
|
|
1248
1353
|
this.llmProvider = (0, index_1.createProvider)(effectiveProvider, {
|
|
1249
1354
|
apiKey: token,
|
|
1250
1355
|
model: this.options.model,
|
|
1251
|
-
|
|
1356
|
+
authMode,
|
|
1357
|
+
...(this.options.baseUrl ? { baseUrl: this.options.baseUrl } : {}),
|
|
1358
|
+
...(this.options.apiStyle ? { apiStyle: this.options.apiStyle } : {}),
|
|
1252
1359
|
});
|
|
1253
|
-
if (token.startsWith('sk-ant-oat01-') && effectiveProvider === 'anthropic') {
|
|
1254
|
-
this.resolveAndUpgradeAuth(token, this.options.model);
|
|
1255
|
-
}
|
|
1256
1360
|
}
|
|
1257
1361
|
}
|
|
1258
1362
|
exports.MessageLoop = MessageLoop;
|