flowseeker 0.1.7
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/CHANGELOG.md +111 -0
- package/COMPATIBILITY.md +281 -0
- package/LICENSE +21 -0
- package/README.md +375 -0
- package/dist/adapters/frameworkEdges.js +586 -0
- package/dist/agent/approvalPolicy.js +81 -0
- package/dist/agent/commandRunner.js +166 -0
- package/dist/agent/flowCommandRunner.js +124 -0
- package/dist/agent/mcpToolRunner.js +167 -0
- package/dist/auth/githubAuth.js +71 -0
- package/dist/auth/modelList.js +127 -0
- package/dist/auth/oauthHandler.js +377 -0
- package/dist/chat/nativeChatParticipant.js +616 -0
- package/dist/cli/mcpServer.js +383 -0
- package/dist/cli/runEvaluation.js +789 -0
- package/dist/cli/runReplay.js +481 -0
- package/dist/commands/checkHostCompatibility.js +149 -0
- package/dist/commands/copyAgentPrompt.js +52 -0
- package/dist/commands/copyContext.js +54 -0
- package/dist/commands/explainSelectionRelevance.js +57 -0
- package/dist/commands/findRelevantContext.js +127 -0
- package/dist/commands/openEvidence.js +49 -0
- package/dist/commands/openMcpConfig.js +81 -0
- package/dist/commands/openRelatedTests.js +54 -0
- package/dist/commands/rebuildIndex.js +45 -0
- package/dist/commands/runEvaluationSuite.js +323 -0
- package/dist/commands/runReplaySuite.js +228 -0
- package/dist/config/defaultConfig.js +72 -0
- package/dist/config/loadConfig.js +84 -0
- package/dist/config/loadConfigFromPath.js +60 -0
- package/dist/extension.js +513 -0
- package/dist/gateway/agentPrompts.js +176 -0
- package/dist/gateway/aiGateway.js +1255 -0
- package/dist/gateway/aiProviders.js +901 -0
- package/dist/gateway/contextExpansion.js +331 -0
- package/dist/gateway/editProposalStore.js +238 -0
- package/dist/gateway/planProposalStore.js +28 -0
- package/dist/index/cacheStore.js +51 -0
- package/dist/index/chunker.js +45 -0
- package/dist/index/dependencyExtractor.js +107 -0
- package/dist/index/fileDiscovery.js +177 -0
- package/dist/index/structuredExtractor.js +256 -0
- package/dist/index/workspaceIndex.js +518 -0
- package/dist/mcp/mcpConfig.js +154 -0
- package/dist/mcp/mcpProvider.js +109 -0
- package/dist/mcp/mcpTools.js +215 -0
- package/dist/pipeline/agentPrompt.js +79 -0
- package/dist/pipeline/agentPromptHeadless.js +85 -0
- package/dist/pipeline/contextBlueprint.js +346 -0
- package/dist/pipeline/contextPack.js +80 -0
- package/dist/pipeline/diffPreview.js +79 -0
- package/dist/pipeline/evaluationMetrics.js +154 -0
- package/dist/pipeline/evidenceGraph.js +389 -0
- package/dist/pipeline/feedback.js +215 -0
- package/dist/pipeline/fileGroups.js +84 -0
- package/dist/pipeline/fileScanner.js +866 -0
- package/dist/pipeline/nodeScan.js +219 -0
- package/dist/pipeline/ranker.js +563 -0
- package/dist/pipeline/responseLanguage.js +39 -0
- package/dist/pipeline/retrievalPlan.js +163 -0
- package/dist/pipeline/runHeadless.js +54 -0
- package/dist/pipeline/runPipeline.js +114 -0
- package/dist/pipeline/solvePacket.js +382 -0
- package/dist/pipeline/subsystem.js +257 -0
- package/dist/pipeline/taskUnderstanding.js +453 -0
- package/dist/pipeline/tokenSavings.js +146 -0
- package/dist/pipeline/universalScan.js +216 -0
- package/dist/pipeline/verifyAfterApply.js +233 -0
- package/dist/runtime/capabilities.js +71 -0
- package/dist/runtime/hostDetect.js +98 -0
- package/dist/runtime/hostTier.js +68 -0
- package/dist/runtime/statusBar.js +80 -0
- package/dist/skills/skillRegistry.js +208 -0
- package/dist/types.js +3 -0
- package/dist/ui/chatViewProvider.js +3899 -0
- package/dist/ui/resultTreeProvider.js +174 -0
- package/dist/usage/quotaTracker.js +358 -0
- package/dist/utils/async.js +30 -0
- package/dist/utils/logger.js +64 -0
- package/dist/utils/text.js +364 -0
- package/dist/utils/updateChecker.js +140 -0
- package/package.json +561 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.registerNativeChatParticipant = registerNativeChatParticipant;
|
|
37
|
+
const vscode = __importStar(require("vscode"));
|
|
38
|
+
const aiGateway_1 = require("../gateway/aiGateway");
|
|
39
|
+
const agentPrompts_1 = require("../gateway/agentPrompts");
|
|
40
|
+
const editProposalStore_1 = require("../gateway/editProposalStore");
|
|
41
|
+
const fileGroups_1 = require("../pipeline/fileGroups");
|
|
42
|
+
const runPipeline_1 = require("../pipeline/runPipeline");
|
|
43
|
+
const diffPreview_1 = require("../pipeline/diffPreview");
|
|
44
|
+
const tokenSavings_1 = require("../pipeline/tokenSavings");
|
|
45
|
+
const logger_1 = require("../utils/logger");
|
|
46
|
+
const contextExpansion_1 = require("../gateway/contextExpansion");
|
|
47
|
+
const participantId = "flowseeker.participant";
|
|
48
|
+
function registerNativeChatParticipant(context, resultProvider, gateway, editStore, planStore, skillRegistry) {
|
|
49
|
+
const handler = async (request, _chatContext, stream, token) => {
|
|
50
|
+
let task = request.prompt.trim();
|
|
51
|
+
if (request.command === "apply") {
|
|
52
|
+
return handleApprovedPlan(request, stream, token, gateway, editStore, planStore);
|
|
53
|
+
}
|
|
54
|
+
// Skill commands: /skillName → prepend skill prompt to user request
|
|
55
|
+
const skill = skillRegistry.getSkill(request.command ?? "");
|
|
56
|
+
if (skill && !task) {
|
|
57
|
+
stream.markdown(`**${skill.name}** — ${skill.description}\n\nDescribe what you want to ${skill.name}.`);
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
if (skill && task) {
|
|
61
|
+
task = `${skill.prompt}\n\nUser request: ${task}`;
|
|
62
|
+
}
|
|
63
|
+
if (!task) {
|
|
64
|
+
stream.markdown("Describe the codebase task you want FlowSeeker to solve.");
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
const mode = skill?.mode === "auto" ? "auto" : commandToMode(request.command);
|
|
68
|
+
try {
|
|
69
|
+
(0, logger_1.logInfo)(`Native chat scan started for task: ${task}`);
|
|
70
|
+
const result = await runWithCompactProgress(stream, "FlowSeeker", "Finding relevant files before asking the agent...", async (progress) => (0, runPipeline_1.runFlowSeeker)(task, { token, progress }));
|
|
71
|
+
resultProvider.setResult(result);
|
|
72
|
+
streamRetrievalSummary(stream, result);
|
|
73
|
+
const connection = await gateway.getConnection();
|
|
74
|
+
if (connection.connected && connection.mode === "direct") {
|
|
75
|
+
try {
|
|
76
|
+
stream.progress(`Asking ${connection.providerLabel ?? "configured provider"}${connection.model ? `: ${connection.model}` : ""}...`);
|
|
77
|
+
const answer = await gateway.ask(result, mode);
|
|
78
|
+
if (mode === "auto") {
|
|
79
|
+
const plan = await maybeExpandGatewayPlan(gateway, result, answer.message, stream);
|
|
80
|
+
renderPlan(stream, result, plan, planStore);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
renderAgentResponse(stream, answer, editStore);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
metadata: {
|
|
87
|
+
mode,
|
|
88
|
+
provider: connection.provider,
|
|
89
|
+
model: connection.model
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch (providerError) {
|
|
94
|
+
(0, logger_1.logError)("Configured AI provider request failed.", providerError);
|
|
95
|
+
const message = providerError instanceof Error ? providerError.message : String(providerError);
|
|
96
|
+
stream.markdown(`\nConfigured AI provider could not answer: ${message}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const modelAvailable = await isLanguageModelAvailable(request.model, token);
|
|
100
|
+
if (modelAvailable) {
|
|
101
|
+
try {
|
|
102
|
+
stream.progress(`Asking selected VSCode model: ${request.model.name}...`);
|
|
103
|
+
if (mode === "auto") {
|
|
104
|
+
const initialPlan = await askVsCodeModelForPlan(request.model, result, token, (tokens) => streamModelPromptTokens(stream, request.model, result, tokens));
|
|
105
|
+
const plan = await maybeExpandVsCodePlan(request.model, result, initialPlan, token, stream);
|
|
106
|
+
renderPlan(stream, result, plan, planStore);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const answer = await askVsCodeModelForGuidance(request.model, result, token, (tokens) => streamModelPromptTokens(stream, request.model, result, tokens));
|
|
110
|
+
renderAgentResponse(stream, answer, editStore);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
metadata: {
|
|
114
|
+
mode,
|
|
115
|
+
provider: "vscode-language-model",
|
|
116
|
+
model: request.model.name,
|
|
117
|
+
vendor: request.model.vendor
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (modelError) {
|
|
122
|
+
(0, logger_1.logError)("Selected VSCode model request failed.", modelError);
|
|
123
|
+
const message = modelError instanceof Error ? modelError.message : String(modelError);
|
|
124
|
+
stream.markdown(`\nSelected VSCode model could not answer: ${message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (connection.connected && connection.mode === "customGateway") {
|
|
128
|
+
try {
|
|
129
|
+
stream.progress("Asking custom AI Gateway...");
|
|
130
|
+
const answer = await gateway.ask(result, mode);
|
|
131
|
+
if (mode === "auto") {
|
|
132
|
+
const plan = await maybeExpandGatewayPlan(gateway, result, answer.message, stream);
|
|
133
|
+
renderPlan(stream, result, plan, planStore);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
renderAgentResponse(stream, answer, editStore);
|
|
137
|
+
}
|
|
138
|
+
return { metadata: { mode, provider: "customGateway", connected: true } };
|
|
139
|
+
}
|
|
140
|
+
catch (gatewayError) {
|
|
141
|
+
(0, logger_1.logError)("Custom AI Gateway request failed.", gatewayError);
|
|
142
|
+
const message = gatewayError instanceof Error ? gatewayError.message : String(gatewayError);
|
|
143
|
+
stream.markdown(`\nCustom AI Gateway could not answer: ${message}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
{
|
|
147
|
+
stream.markdown("\n## No AI provider available");
|
|
148
|
+
stream.markdown([
|
|
149
|
+
"",
|
|
150
|
+
"FlowSeeker retrieved the relevant context, but no AI model is available to reason over it.",
|
|
151
|
+
"",
|
|
152
|
+
"**To fix this, choose one:**",
|
|
153
|
+
"",
|
|
154
|
+
"- **Configure AI Provider** - connect OpenAI, Anthropic, Gemini, Ollama, OpenRouter, Mistral, Groq, xAI, or DeepSeek.",
|
|
155
|
+
"- **Install GitHub Copilot** - use a VS Code Chat model directly.",
|
|
156
|
+
"- **Use another AI tool** - click **Copy Agent Prompt**, then paste into ChatGPT, Claude, or another AI assistant.",
|
|
157
|
+
""
|
|
158
|
+
].join("\n"));
|
|
159
|
+
stream.button({ command: "flowseeker.copyAgentPrompt", title: "Copy Agent Prompt" });
|
|
160
|
+
stream.button({ command: "flowseeker.connectGateway", title: "Configure AI Provider" });
|
|
161
|
+
stream.button({ command: "flowseeker.checkHostCompatibility", title: "Check Host Compatibility" });
|
|
162
|
+
return { metadata: { mode, connected: false, modelAvailable } };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
(0, logger_1.logError)("FlowSeeker native chat request failed.", error);
|
|
167
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
168
|
+
stream.markdown(`FlowSeeker failed: ${message}`);
|
|
169
|
+
return { errorDetails: { message } };
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const participant = vscode.chat.createChatParticipant(participantId, handler);
|
|
173
|
+
participant.iconPath = vscode.Uri.joinPath(context.extensionUri, "resources", "flowseeker-logo-vector-v2.svg");
|
|
174
|
+
participant.followupProvider = {
|
|
175
|
+
provideFollowups(result) {
|
|
176
|
+
const metadata = result.metadata;
|
|
177
|
+
return [
|
|
178
|
+
{ prompt: "/guide Explain why these files are relevant", label: "Explain retrieved files" },
|
|
179
|
+
{ prompt: "/auto Propose a reviewable fix plan", label: "Propose a plan" },
|
|
180
|
+
{ prompt: "/guide What can FlowSeeker do in this editor?", label: "Check editor support" },
|
|
181
|
+
...(metadata?.connected === false ? [{ prompt: "/guide What should I paste into my current AI agent?", label: "Use without gateway" }] : [])
|
|
182
|
+
];
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
return participant;
|
|
186
|
+
}
|
|
187
|
+
async function runWithCompactProgress(stream, title, chatMessage, work) {
|
|
188
|
+
const transcript = createTranscriptProgress(stream);
|
|
189
|
+
transcript.push("start", chatMessage);
|
|
190
|
+
let lastMessage = "";
|
|
191
|
+
let lastReportAt = 0;
|
|
192
|
+
let lastIndexed;
|
|
193
|
+
let lastScanned;
|
|
194
|
+
return vscode.window.withProgress({
|
|
195
|
+
location: vscode.ProgressLocation.Window,
|
|
196
|
+
title,
|
|
197
|
+
cancellable: false
|
|
198
|
+
}, async (windowProgress) => {
|
|
199
|
+
const compactProgress = {
|
|
200
|
+
report: (value) => {
|
|
201
|
+
if (!value.message) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const message = compactProgressMessage(value.message);
|
|
205
|
+
const now = Date.now();
|
|
206
|
+
updateTranscriptProgress(value.message);
|
|
207
|
+
if (message === lastMessage || now - lastReportAt < 150) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
lastMessage = message;
|
|
211
|
+
lastReportAt = now;
|
|
212
|
+
windowProgress.report({ message, increment: value.increment });
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
return work(compactProgress);
|
|
216
|
+
});
|
|
217
|
+
function updateTranscriptProgress(message) {
|
|
218
|
+
const indexed = message.match(/^Indexing\s+(\d+)\/(\d+)(?:\s+files)?(?::\s*(.+))?/i);
|
|
219
|
+
if (indexed) {
|
|
220
|
+
lastIndexed = { current: Number(indexed[1]), total: Number(indexed[2]) };
|
|
221
|
+
transcript.push("indexing", "Indexing workspace files...");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const scanning = message.match(/^Scanning\s+(\d+)\/(\d+):/i);
|
|
225
|
+
if (scanning) {
|
|
226
|
+
if (lastIndexed) {
|
|
227
|
+
transcript.push("scanning", `Indexed ${lastIndexed.current}/${lastIndexed.total} files. Scanning candidate files...`);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
transcript.push("scanning", "Scanning candidate files...");
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const scanned = message.match(/^Scanned\s+(\d+)\/(\d+)\s+files,\s+(\d+)\s+evidence/i);
|
|
235
|
+
if (scanned) {
|
|
236
|
+
lastScanned = { current: Number(scanned[1]), total: Number(scanned[2]), evidence: Number(scanned[3]) };
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (/^Ranking\b/i.test(message)) {
|
|
240
|
+
const summary = lastScanned
|
|
241
|
+
? `Scanned ${lastScanned.current}/${lastScanned.total} files, ${lastScanned.evidence} evidence.`
|
|
242
|
+
: "Candidate scan complete.";
|
|
243
|
+
transcript.push("ranking", `${summary} Ranking evidence...`);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (/^Building evidence graph/i.test(message)) {
|
|
247
|
+
transcript.push("graph", "Building evidence graph...");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function compactProgressMessage(message) {
|
|
253
|
+
const indexing = message.match(/^Indexing\s+(\d+)\/(\d+)(?:\s+files)?(?::\s*(.+))?/i);
|
|
254
|
+
if (indexing) {
|
|
255
|
+
const suffix = indexing[3] ? ` - ${shortenPath(indexing[3])}` : "";
|
|
256
|
+
return `Indexing ${indexing[1]}/${indexing[2]} (${percent(Number(indexing[1]), Number(indexing[2]))})${suffix}`;
|
|
257
|
+
}
|
|
258
|
+
const indexed = message.match(/^Indexed\s+(\d+)\/(\d+)\s+files/i);
|
|
259
|
+
if (indexed) {
|
|
260
|
+
return `Indexed ${indexed[1]}/${indexed[2]} (${percent(Number(indexed[1]), Number(indexed[2]))})`;
|
|
261
|
+
}
|
|
262
|
+
const scanning = message.match(/^Scanning\s+(\d+\/\d+):\s+(.+)$/);
|
|
263
|
+
if (scanning) {
|
|
264
|
+
const [current, total] = scanning[1].split("/").map(Number);
|
|
265
|
+
return `Scanning ${scanning[1]} (${percent(current, total)}) - ${shortenPath(scanning[2])}`;
|
|
266
|
+
}
|
|
267
|
+
return shortenProgress(message);
|
|
268
|
+
}
|
|
269
|
+
function createTranscriptProgress(stream) {
|
|
270
|
+
const seen = new Set();
|
|
271
|
+
let lineCount = 0;
|
|
272
|
+
const maxLines = 5;
|
|
273
|
+
return {
|
|
274
|
+
push: (key, message) => {
|
|
275
|
+
if (seen.has(key) || lineCount >= maxLines) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
seen.add(key);
|
|
279
|
+
lineCount += 1;
|
|
280
|
+
stream.progress(message);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function percent(current, total) {
|
|
285
|
+
if (!Number.isFinite(current) || !Number.isFinite(total) || total <= 0) {
|
|
286
|
+
return "0%";
|
|
287
|
+
}
|
|
288
|
+
return `${Math.min(100, Math.max(0, Math.round((current / total) * 100)))}%`;
|
|
289
|
+
}
|
|
290
|
+
function shortenPath(value) {
|
|
291
|
+
const normalized = value.replace(/\\/g, "/");
|
|
292
|
+
if (normalized.length <= 60) {
|
|
293
|
+
return normalized;
|
|
294
|
+
}
|
|
295
|
+
const parts = normalized.split("/");
|
|
296
|
+
const fileName = parts.at(-1) ?? normalized;
|
|
297
|
+
const parent = parts.at(-2);
|
|
298
|
+
const shortPath = parent ? `${parent}/${fileName}` : fileName;
|
|
299
|
+
return shortPath.length <= 60 ? shortPath : `...${shortPath.slice(-57)}`;
|
|
300
|
+
}
|
|
301
|
+
function shortenProgress(value) {
|
|
302
|
+
return value.length <= 96 ? value : `${value.slice(0, 93)}...`;
|
|
303
|
+
}
|
|
304
|
+
function commandToMode(command) {
|
|
305
|
+
return command === "auto" ? "auto" : "guide";
|
|
306
|
+
}
|
|
307
|
+
async function isLanguageModelAvailable(model, token) {
|
|
308
|
+
try {
|
|
309
|
+
const models = await vscode.lm.selectChatModels({ vendor: model.vendor, family: model.family });
|
|
310
|
+
return models.length > 0;
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async function handleApprovedPlan(request, stream, token, gateway, editStore, planStore) {
|
|
317
|
+
const planId = request.prompt.trim().split(/\s+/)[0];
|
|
318
|
+
if (!planId) {
|
|
319
|
+
stream.markdown("No approved plan id was provided. Run `/auto <task>` first, review the plan, then use the approve button.");
|
|
320
|
+
return { errorDetails: { message: "Missing plan id." } };
|
|
321
|
+
}
|
|
322
|
+
const proposal = planStore.get(planId);
|
|
323
|
+
if (!proposal) {
|
|
324
|
+
stream.markdown("This plan is no longer available. Run `/auto <task>` again to regenerate a reviewable plan.");
|
|
325
|
+
return { errorDetails: { message: "Plan not found." } };
|
|
326
|
+
}
|
|
327
|
+
const allowedFiles = proposal.allowedFiles;
|
|
328
|
+
stream.markdown("Plan approved. FlowSeeker will now ask for structured edits. No files will be written until you press **Apply Proposed Edits**.");
|
|
329
|
+
if (allowedFiles.length > 0) {
|
|
330
|
+
stream.markdown(`\nScope locked to ${allowedFiles.length} approved file(s): ${allowedFiles.map((file) => `\`${file}\``).join(", ")}`);
|
|
331
|
+
}
|
|
332
|
+
const connection = await gateway.getConnection();
|
|
333
|
+
if (connection.connected && connection.mode === "direct") {
|
|
334
|
+
try {
|
|
335
|
+
stream.progress(`Asking ${connection.providerLabel ?? "configured provider"} for edits${connection.model ? `: ${connection.model}` : ""}...`);
|
|
336
|
+
let answer = await gateway.askWithInstruction(proposal.result, "auto", (0, agentPrompts_1.createApprovedEditInstruction)(proposal.plan), { stage: "approved_edits", approvedPlan: proposal.plan });
|
|
337
|
+
answer = await maybeExpandGatewayApprovedEdits(gateway, proposal.result, proposal.plan, answer, stream);
|
|
338
|
+
renderApprovedEditResponse(stream, answer, editStore, allowedFiles);
|
|
339
|
+
return {
|
|
340
|
+
metadata: {
|
|
341
|
+
mode: "auto",
|
|
342
|
+
stage: "approved_edits",
|
|
343
|
+
provider: connection.provider,
|
|
344
|
+
model: connection.model
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
catch (providerError) {
|
|
349
|
+
(0, logger_1.logError)("Configured AI provider failed to generate approved edits.", providerError);
|
|
350
|
+
const message = providerError instanceof Error ? providerError.message : String(providerError);
|
|
351
|
+
stream.markdown(`\nConfigured AI provider could not generate edits: ${message}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
stream.progress(`Asking selected VSCode model for edits: ${request.model.name}...`);
|
|
356
|
+
let answer = await askVsCodeModelForApprovedEdits(request.model, proposal.result, proposal.plan, token, (tokens) => streamModelPromptTokens(stream, request.model, proposal.result, tokens));
|
|
357
|
+
answer = await maybeExpandVsCodeApprovedEdits(request.model, proposal.result, proposal.plan, answer, token, stream);
|
|
358
|
+
renderApprovedEditResponse(stream, answer, editStore, allowedFiles);
|
|
359
|
+
return {
|
|
360
|
+
metadata: {
|
|
361
|
+
mode: "auto",
|
|
362
|
+
stage: "approved_edits",
|
|
363
|
+
provider: "vscode-language-model",
|
|
364
|
+
model: request.model.name,
|
|
365
|
+
vendor: request.model.vendor
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
catch (modelError) {
|
|
370
|
+
(0, logger_1.logError)("Selected VSCode model failed to generate approved edits.", modelError);
|
|
371
|
+
const message = modelError instanceof Error ? modelError.message : String(modelError);
|
|
372
|
+
stream.markdown(`\nSelected VSCode model could not generate edits: ${message}`);
|
|
373
|
+
}
|
|
374
|
+
if (!connection.connected) {
|
|
375
|
+
stream.markdown("\nNo AI provider fallback is connected, so FlowSeeker cannot generate edits from the approved plan.");
|
|
376
|
+
stream.button({ command: "flowseeker.connectGateway", title: "Configure AI Provider" });
|
|
377
|
+
return { metadata: { mode: "auto", stage: "approved_edits", connected: false } };
|
|
378
|
+
}
|
|
379
|
+
stream.progress("Asking custom AI Gateway for edits from the approved plan...");
|
|
380
|
+
let answer = await gateway.askWithInstruction(proposal.result, "auto", (0, agentPrompts_1.createApprovedEditInstruction)(proposal.plan), { stage: "approved_edits", approvedPlan: proposal.plan });
|
|
381
|
+
answer = await maybeExpandGatewayApprovedEdits(gateway, proposal.result, proposal.plan, answer, stream);
|
|
382
|
+
renderApprovedEditResponse(stream, answer, editStore, allowedFiles);
|
|
383
|
+
return { metadata: { mode: "auto", stage: "approved_edits", connected: true } };
|
|
384
|
+
}
|
|
385
|
+
async function askVsCodeModelForGuidance(model, result, token, onPromptTokens) {
|
|
386
|
+
const prompt = (0, agentPrompts_1.createGuidancePrompt)(result);
|
|
387
|
+
onPromptTokens?.(await countModelPromptTokens(model, prompt, token));
|
|
388
|
+
const text = await sendModelPrompt(model, prompt, token);
|
|
389
|
+
return { message: text.trim() || "The selected model returned an empty response." };
|
|
390
|
+
}
|
|
391
|
+
async function askVsCodeModelForPlan(model, result, token, onPromptTokens) {
|
|
392
|
+
const prompt = (0, agentPrompts_1.createPlanPrompt)(result);
|
|
393
|
+
onPromptTokens?.(await countModelPromptTokens(model, prompt, token));
|
|
394
|
+
const text = await sendModelPrompt(model, prompt, token);
|
|
395
|
+
return text.trim() || "The selected model returned an empty plan.";
|
|
396
|
+
}
|
|
397
|
+
async function askVsCodeModelForApprovedEdits(model, result, approvedPlan, token, onPromptTokens) {
|
|
398
|
+
const prompt = (0, agentPrompts_1.createApprovedEditPrompt)(result, approvedPlan);
|
|
399
|
+
onPromptTokens?.(await countModelPromptTokens(model, prompt, token));
|
|
400
|
+
const text = await sendModelPrompt(model, prompt, token);
|
|
401
|
+
return (0, aiGateway_1.parseAgentResponse)(text);
|
|
402
|
+
}
|
|
403
|
+
async function countModelPromptTokens(model, prompt, token) {
|
|
404
|
+
try {
|
|
405
|
+
return await model.countTokens(vscode.LanguageModelChatMessage.User(prompt), token);
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
(0, logger_1.logError)("Selected VSCode model token counting failed.", error);
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function streamModelPromptTokens(stream, model, result, promptTokens) {
|
|
413
|
+
if (promptTokens === undefined) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const workspaceTokens = result.tokenSavings?.workspaceTokensEstimate;
|
|
417
|
+
const reduction = workspaceTokens && workspaceTokens > 0 ? `; vs workspace baseline: **${(0, tokenSavings_1.formatReductionPercent)(((workspaceTokens - promptTokens) / workspaceTokens) * 100)}** reduction` : "";
|
|
418
|
+
stream.markdown(`\nSelected model input measured by \`${model.name}\`: **${(0, tokenSavings_1.formatTokenCount)(promptTokens)}** tokens${reduction}.`);
|
|
419
|
+
}
|
|
420
|
+
async function sendModelPrompt(model, prompt, token) {
|
|
421
|
+
const response = await model.sendRequest([vscode.LanguageModelChatMessage.User(prompt)], {
|
|
422
|
+
justification: "FlowSeeker uses the selected VSCode chat model to reason over the relevant codebase context it retrieved."
|
|
423
|
+
}, token);
|
|
424
|
+
let text = "";
|
|
425
|
+
for await (const chunk of response.text) {
|
|
426
|
+
text += chunk;
|
|
427
|
+
}
|
|
428
|
+
return text;
|
|
429
|
+
}
|
|
430
|
+
function renderApprovedEditResponse(stream, answer, editStore, scope) {
|
|
431
|
+
if (!answer.edits?.length) {
|
|
432
|
+
stream.markdown([
|
|
433
|
+
"\nThe AI provider did not return structured edits FlowSeeker can apply.",
|
|
434
|
+
"",
|
|
435
|
+
"Expected JSON shape: `{\"message\":\"...\",\"edits\":[{\"file\":\"relative/path\",\"range\":{\"startLine\":1,\"endLine\":1},\"text\":\"replacement\"}],\"commands\":[],\"mcpTools\":[]}`.",
|
|
436
|
+
"",
|
|
437
|
+
"Provider response preview:",
|
|
438
|
+
"",
|
|
439
|
+
(0, contextExpansion_1.truncateForAgent)(answer.message, 1200),
|
|
440
|
+
"",
|
|
441
|
+
"No workspace files were changed."
|
|
442
|
+
].join("\n"));
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
renderAgentResponse(stream, answer, editStore, scope);
|
|
446
|
+
}
|
|
447
|
+
function renderAgentResponse(stream, answer, editStore, scope) {
|
|
448
|
+
stream.markdown(`\n${answer.message}`);
|
|
449
|
+
if (answer.patch) {
|
|
450
|
+
stream.markdown(["", "```diff", answer.patch, "```"].join("\n"));
|
|
451
|
+
}
|
|
452
|
+
if (answer.edits?.length) {
|
|
453
|
+
stream.markdown(`\n${(0, diffPreview_1.renderDiffPreviewMarkdown)(answer.edits)}`);
|
|
454
|
+
const proposalId = editStore.store(answer.edits, scope);
|
|
455
|
+
stream.markdown(`\nProposed ${answer.edits.length} structured edit(s). Review the target files before applying.`);
|
|
456
|
+
stream.button({
|
|
457
|
+
command: "flowseeker.openGatewayDiff",
|
|
458
|
+
title: "Open Diff",
|
|
459
|
+
arguments: [proposalId]
|
|
460
|
+
});
|
|
461
|
+
stream.button({
|
|
462
|
+
command: "flowseeker.applyGatewayEdits",
|
|
463
|
+
title: "Apply Proposed Edits",
|
|
464
|
+
arguments: [proposalId]
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
if (answer.commands?.length) {
|
|
468
|
+
stream.markdown(`\nSuggested ${answer.commands.length} verification command(s). FlowSeeker will ask before running commands unless your approval policy allows it.`);
|
|
469
|
+
for (const command of answer.commands.slice(0, 4)) {
|
|
470
|
+
stream.button({
|
|
471
|
+
command: "flowseeker.runAgentCommand",
|
|
472
|
+
title: `Run: ${shortenCommand(command.command)}`,
|
|
473
|
+
arguments: [command]
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (answer.mcpTools?.length) {
|
|
478
|
+
stream.markdown(`\nSuggested ${answer.mcpTools.length} MCP/tool invocation(s). FlowSeeker will ask before invoking tools unless your approval policy allows it.`);
|
|
479
|
+
for (const tool of answer.mcpTools.slice(0, 4)) {
|
|
480
|
+
stream.button({
|
|
481
|
+
command: "flowseeker.runMcpTool",
|
|
482
|
+
title: `Invoke: ${shortenCommand(tool.tool)}`,
|
|
483
|
+
arguments: [tool]
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function shortenCommand(command) {
|
|
489
|
+
return command.length <= 42 ? command : `${command.slice(0, 39)}...`;
|
|
490
|
+
}
|
|
491
|
+
function renderPlan(stream, result, plan, planStore) {
|
|
492
|
+
if (!(0, contextExpansion_1.isReviewablePlan)(plan)) {
|
|
493
|
+
stream.markdown(`\n${plan}`);
|
|
494
|
+
stream.markdown("\nFlowSeeker did not receive a reviewable implementation plan, so approval is disabled for this response.");
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const planId = planStore.store(result, plan);
|
|
498
|
+
stream.markdown(`\n${plan}`);
|
|
499
|
+
stream.markdown("\nNo files were changed. Review this plan first. If it looks right, approve it to generate structured edits.");
|
|
500
|
+
stream.button({
|
|
501
|
+
command: "flowseeker.approvePlan",
|
|
502
|
+
title: "Approve Plan: Generate Edits",
|
|
503
|
+
arguments: [planId]
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
async function maybeExpandGatewayPlan(gateway, result, plan, stream) {
|
|
507
|
+
if (!(0, contextExpansion_1.isMissingContextRequest)(plan)) {
|
|
508
|
+
return plan;
|
|
509
|
+
}
|
|
510
|
+
const paths = (0, contextExpansion_1.selectContextExpansionPaths)(plan, result);
|
|
511
|
+
if (paths.length === 0) {
|
|
512
|
+
stream.markdown("\nThe AI requested more context, but FlowSeeker could not identify a bounded file set to read.");
|
|
513
|
+
return plan;
|
|
514
|
+
}
|
|
515
|
+
stream.progress(`AI requested more context. Reading ${paths.length} bounded file(s) and retrying the plan...`);
|
|
516
|
+
const expandedContext = await (0, contextExpansion_1.readWorkspaceContextFiles)(paths);
|
|
517
|
+
if (!expandedContext.trim()) {
|
|
518
|
+
stream.markdown("\nFlowSeeker could not read the requested context files safely.");
|
|
519
|
+
return plan;
|
|
520
|
+
}
|
|
521
|
+
const answer = await gateway.askWithInstruction(result, "auto", (0, contextExpansion_1.createExpandedPlanInstruction)(plan, expandedContext), { stage: "expanded_plan", requestedFiles: paths });
|
|
522
|
+
return answer.message;
|
|
523
|
+
}
|
|
524
|
+
async function maybeExpandGatewayApprovedEdits(gateway, result, approvedPlan, answer, stream) {
|
|
525
|
+
if (answer.edits?.length || !(0, contextExpansion_1.isMissingContextRequest)(answer.message)) {
|
|
526
|
+
return answer;
|
|
527
|
+
}
|
|
528
|
+
const paths = (0, contextExpansion_1.selectContextExpansionPaths)(answer.message, result);
|
|
529
|
+
if (paths.length === 0) {
|
|
530
|
+
stream.markdown("\nThe AI requested more edit context, but FlowSeeker could not identify a bounded file set to read.");
|
|
531
|
+
return answer;
|
|
532
|
+
}
|
|
533
|
+
stream.progress(`AI requested more edit context. Reading ${paths.length} bounded file(s) and retrying structured edits...`);
|
|
534
|
+
const expandedContext = await (0, contextExpansion_1.readWorkspaceContextFiles)(paths);
|
|
535
|
+
if (!expandedContext.trim()) {
|
|
536
|
+
stream.markdown("\nFlowSeeker could not read the requested edit context files safely.");
|
|
537
|
+
return answer;
|
|
538
|
+
}
|
|
539
|
+
return gateway.askWithInstruction(result, "auto", (0, contextExpansion_1.createExpandedApprovedEditInstruction)(approvedPlan, answer.message, expandedContext), { stage: "expanded_approved_edits", requestedFiles: paths, approvedPlan });
|
|
540
|
+
}
|
|
541
|
+
async function maybeExpandVsCodePlan(model, result, plan, token, stream) {
|
|
542
|
+
if (!(0, contextExpansion_1.isMissingContextRequest)(plan)) {
|
|
543
|
+
return plan;
|
|
544
|
+
}
|
|
545
|
+
const paths = (0, contextExpansion_1.selectContextExpansionPaths)(plan, result);
|
|
546
|
+
if (paths.length === 0) {
|
|
547
|
+
stream.markdown("\nThe AI requested more context, but FlowSeeker could not identify a bounded file set to read.");
|
|
548
|
+
return plan;
|
|
549
|
+
}
|
|
550
|
+
stream.progress(`AI requested more context. Reading ${paths.length} bounded file(s) and retrying the plan...`);
|
|
551
|
+
const expandedContext = await (0, contextExpansion_1.readWorkspaceContextFiles)(paths);
|
|
552
|
+
if (!expandedContext.trim()) {
|
|
553
|
+
stream.markdown("\nFlowSeeker could not read the requested context files safely.");
|
|
554
|
+
return plan;
|
|
555
|
+
}
|
|
556
|
+
return sendModelPrompt(model, (0, agentPrompts_1.createPromptWithInstruction)(result, (0, contextExpansion_1.createExpandedPlanInstruction)(plan, expandedContext)), token);
|
|
557
|
+
}
|
|
558
|
+
async function maybeExpandVsCodeApprovedEdits(model, result, approvedPlan, answer, token, stream) {
|
|
559
|
+
if (answer.edits?.length || !(0, contextExpansion_1.isMissingContextRequest)(answer.message)) {
|
|
560
|
+
return answer;
|
|
561
|
+
}
|
|
562
|
+
const paths = (0, contextExpansion_1.selectContextExpansionPaths)(answer.message, result);
|
|
563
|
+
if (paths.length === 0) {
|
|
564
|
+
stream.markdown("\nThe AI requested more edit context, but FlowSeeker could not identify a bounded file set to read.");
|
|
565
|
+
return answer;
|
|
566
|
+
}
|
|
567
|
+
stream.progress(`AI requested more edit context. Reading ${paths.length} bounded file(s) and retrying structured edits...`);
|
|
568
|
+
const expandedContext = await (0, contextExpansion_1.readWorkspaceContextFiles)(paths);
|
|
569
|
+
if (!expandedContext.trim()) {
|
|
570
|
+
stream.markdown("\nFlowSeeker could not read the requested edit context files safely.");
|
|
571
|
+
return answer;
|
|
572
|
+
}
|
|
573
|
+
const text = await sendModelPrompt(model, (0, agentPrompts_1.createPromptWithInstruction)(result, (0, contextExpansion_1.createExpandedApprovedEditInstruction)(approvedPlan, answer.message, expandedContext)), token);
|
|
574
|
+
return (0, aiGateway_1.parseAgentResponse)(text);
|
|
575
|
+
}
|
|
576
|
+
function streamRetrievalSummary(stream, result) {
|
|
577
|
+
const groups = (0, fileGroups_1.aggregateEvidenceFiles)(result.units).slice(0, 10);
|
|
578
|
+
const stats = result.stats;
|
|
579
|
+
const tokenSavings = result.tokenSavings;
|
|
580
|
+
stream.markdown([
|
|
581
|
+
`Found **${result.units.length}** evidence unit(s) from **${stats.scannedFiles}** scanned file(s).`,
|
|
582
|
+
...(tokenSavings && tokenSavings.status !== "unavailable"
|
|
583
|
+
? [
|
|
584
|
+
`Measured token savings: Solve Packet **${(0, tokenSavings_1.formatTokenCount)(tokenSavings.solvePacketTokensEstimate)}** / workspace **${(0, tokenSavings_1.formatTokenCount)(tokenSavings.workspaceTokensEstimate)}** tokens by \`${tokenSavings.tokenizer}\` (**${(0, tokenSavings_1.formatReductionPercent)(tokenSavings.reductionPercent)}** reduction).`,
|
|
585
|
+
tokenSavings.status === "partial" ? `Estimate note: ${tokenSavings.reason ?? "partial scan basis"}.` : "",
|
|
586
|
+
tokenSavings.proofWarnings.length ? `Proof warning: ${tokenSavings.proofWarnings.join("; ")}.` : ""
|
|
587
|
+
].filter(Boolean)
|
|
588
|
+
: []),
|
|
589
|
+
result.contextCoverage
|
|
590
|
+
? `Context coverage: **${result.contextCoverage.foundRequiredSlots.length}/${result.contextCoverage.requiredSlots.length}** required slot(s) found${result.contextCoverage.missingRequiredSlots.length ? `; missing ${result.contextCoverage.missingRequiredSlots.join(", ")}` : ""}.`
|
|
591
|
+
: "",
|
|
592
|
+
`Intent: \`${result.profile.intent}\``,
|
|
593
|
+
result.profile.keywords.length ? `Keywords: ${result.profile.keywords.map((keyword) => `\`${keyword}\``).join(", ")}` : "Keywords: none",
|
|
594
|
+
"",
|
|
595
|
+
"Top files:"
|
|
596
|
+
].join("\n"));
|
|
597
|
+
const root = vscode.workspace.workspaceFolders?.[0]?.uri;
|
|
598
|
+
for (const [index, group] of groups.entries()) {
|
|
599
|
+
const range = group.ranges[0];
|
|
600
|
+
const lineSuffix = range ? `:${range.startLine}-${range.endLine}` : "";
|
|
601
|
+
stream.markdown(`\n${index + 1}. \`${group.relativePath}${lineSuffix}\` - ${group.role}/${group.slot}, score ${group.score.toFixed(1)}, evidence ${group.unitCount}`);
|
|
602
|
+
if (root) {
|
|
603
|
+
try {
|
|
604
|
+
const uri = (0, editProposalStore_1.resolveWorkspaceFile)(root, group.relativePath);
|
|
605
|
+
const location = range
|
|
606
|
+
? new vscode.Location(uri, new vscode.Range(new vscode.Position(range.startLine - 1, 0), new vscode.Position(Math.max(range.endLine - 1, range.startLine - 1), 0)))
|
|
607
|
+
: uri;
|
|
608
|
+
stream.anchor(location, group.relativePath);
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
// The markdown path is still useful even when an anchor cannot be built.
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
//# sourceMappingURL=nativeChatParticipant.js.map
|