@aexol/spectral 0.9.46 → 0.9.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +15 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +77 -4
- package/dist/extensions/browser/browser-service.d.ts.map +1 -1
- package/dist/extensions/browser/browser-service.js +13 -3
- package/dist/relay/auto-optimizer.d.ts +41 -0
- package/dist/relay/auto-optimizer.d.ts.map +1 -0
- package/dist/relay/auto-optimizer.js +488 -0
- package/dist/relay/client.d.ts.map +1 -1
- package/dist/relay/client.js +10 -0
- package/dist/relay/dispatcher.d.ts +20 -2
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +27 -0
- package/dist/sdk/coding-agent/core/agent-session.d.ts +6 -0
- package/dist/sdk/coding-agent/core/agent-session.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/agent-session.js +37 -0
- package/dist/sdk/coding-agent/core/settings-manager.d.ts +12 -0
- package/dist/sdk/coding-agent/core/settings-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/settings-manager.js +33 -0
- package/dist/sdk/coding-agent/core/system-prompt-mutator.d.ts +17 -0
- package/dist/sdk/coding-agent/core/system-prompt-mutator.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/system-prompt-mutator.js +138 -0
- package/dist/server/agent-bridge.d.ts +1 -0
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +32 -10
- package/dist/server/handlers/prompt-mutation-settings.d.ts +34 -0
- package/dist/server/handlers/prompt-mutation-settings.d.ts.map +1 -0
- package/dist/server/handlers/prompt-mutation-settings.js +58 -0
- package/dist/server/shutdown.d.ts +2 -0
- package/dist/server/shutdown.d.ts.map +1 -1
- package/dist/server/shutdown.js +13 -0
- package/dist/server/wire.d.ts +41 -1
- package/dist/server/wire.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Optimizer (CLI side)
|
|
3
|
+
*
|
|
4
|
+
* Two-phase pipeline for project file optimization, modeled after auto-research.
|
|
5
|
+
*
|
|
6
|
+
* Phase 1 — Analyze (action: "analyze"):
|
|
7
|
+
* 1. Scan project filesystem for optimization candidates (size, duplication, complexity).
|
|
8
|
+
* 2. Build an analysis prompt for the agent.
|
|
9
|
+
* 3. Send prompt to the agent session via manager.prompt() with skipPersistence.
|
|
10
|
+
* 4. Parse the agent's response into structured AutoOptimizeRecommendation[].
|
|
11
|
+
* 5. Emit auto_optimize_analysis_complete via relay with recommendations.
|
|
12
|
+
*
|
|
13
|
+
* Phase 2 — Execute (action: "execute"):
|
|
14
|
+
* 1. Receive approvedItemIds from the frame.
|
|
15
|
+
* 2. Apply each approved recommendation via the agent session.
|
|
16
|
+
* 3. Run quality gates (type-check, lint, tests) on modified files.
|
|
17
|
+
* 4. Emit auto_optimize_execution_complete with per-item results and gate status.
|
|
18
|
+
*
|
|
19
|
+
* All errors are surfaced as auto_optimize_error events on the wire.
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from "node:fs";
|
|
22
|
+
import * as path from "node:path";
|
|
23
|
+
import { execSync } from "node:child_process";
|
|
24
|
+
const activeAutoOptimizeSessions = new Set();
|
|
25
|
+
const OPTIMIZER_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Helpers
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
function sendEvent(relay, sessionId, event) {
|
|
30
|
+
relay.send({ kind: "ws_event", sessionId, event });
|
|
31
|
+
}
|
|
32
|
+
function makeRelaySubscriber(sessionId, relay) {
|
|
33
|
+
return {
|
|
34
|
+
send(event) {
|
|
35
|
+
relay.send({ kind: "ws_event", sessionId, event });
|
|
36
|
+
},
|
|
37
|
+
isOpen() {
|
|
38
|
+
return true;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Walk the project directory and collect file statistics.
|
|
44
|
+
* Skips node_modules, .git, .next, dist, build, .spectral, coverage.
|
|
45
|
+
* Respects MAX_FILES_SCAN limit.
|
|
46
|
+
*/
|
|
47
|
+
const MAX_FILES_SCAN = 2000;
|
|
48
|
+
function scanProjectFiles(projectPath) {
|
|
49
|
+
const stats = [];
|
|
50
|
+
const skipDirs = new Set([
|
|
51
|
+
"node_modules",
|
|
52
|
+
".git",
|
|
53
|
+
".next",
|
|
54
|
+
"dist",
|
|
55
|
+
"build",
|
|
56
|
+
".spectral",
|
|
57
|
+
"coverage",
|
|
58
|
+
"__pycache__",
|
|
59
|
+
".turbo",
|
|
60
|
+
]);
|
|
61
|
+
const stack = [projectPath];
|
|
62
|
+
while (stack.length > 0 && stats.length < MAX_FILES_SCAN) {
|
|
63
|
+
const dir = stack.pop();
|
|
64
|
+
let entries;
|
|
65
|
+
try {
|
|
66
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (entry.isDirectory()) {
|
|
73
|
+
if (!skipDirs.has(entry.name) && !entry.name.startsWith(".")) {
|
|
74
|
+
stack.push(path.join(dir, entry.name));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (entry.isFile()) {
|
|
78
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
79
|
+
// Only collect source files
|
|
80
|
+
if ([".ts", ".tsx", ".js", ".jsx", ".css", ".scss", ".less"].includes(ext)) {
|
|
81
|
+
const fullPath = path.join(dir, entry.name);
|
|
82
|
+
try {
|
|
83
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
84
|
+
const lines = content.split("\n").length;
|
|
85
|
+
stats.push({
|
|
86
|
+
path: path.relative(projectPath, fullPath),
|
|
87
|
+
lines,
|
|
88
|
+
ext,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// skip unreadable files
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (stats.length >= MAX_FILES_SCAN)
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return stats;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Build the analyze-phase prompt sent to the agent.
|
|
104
|
+
* Uses adaptive heuristics: top N% by line count, growth signals,
|
|
105
|
+
* and DRY hints. No hard line limits — the agent decides.
|
|
106
|
+
*/
|
|
107
|
+
function buildAnalyzePrompt(fileStats, projectName) {
|
|
108
|
+
const sorted = [...fileStats].sort((a, b) => b.lines - a.lines);
|
|
109
|
+
const topFiles = sorted.slice(0, 20);
|
|
110
|
+
const topList = topFiles
|
|
111
|
+
.map((f, i) => `${i + 1}. \`${f.path}\` — ${f.lines} lines (${f.ext})`)
|
|
112
|
+
.join("\n");
|
|
113
|
+
const totalLines = fileStats.reduce((s, f) => s + f.lines, 0);
|
|
114
|
+
const fileCount = fileStats.length;
|
|
115
|
+
const avgLines = fileCount > 0 ? Math.round(totalLines / fileCount) : 0;
|
|
116
|
+
const medianLines = fileCount > 0
|
|
117
|
+
? sorted[Math.floor(fileCount / 2)]?.lines ?? 0
|
|
118
|
+
: 0;
|
|
119
|
+
return [
|
|
120
|
+
`You are the Auto-Optimizer for the project "${projectName}".`,
|
|
121
|
+
``,
|
|
122
|
+
`## Project File Stats`,
|
|
123
|
+
`- Total source files scanned: ${fileCount}`,
|
|
124
|
+
`- Total lines of code: ${totalLines}`,
|
|
125
|
+
`- Average lines per file: ${avgLines}`,
|
|
126
|
+
`- Median lines per file: ${medianLines}`,
|
|
127
|
+
``,
|
|
128
|
+
`## Top Files by Line Count`,
|
|
129
|
+
topList,
|
|
130
|
+
``,
|
|
131
|
+
`## Instructions`,
|
|
132
|
+
`Analyze the top files above and identify optimization opportunities.`,
|
|
133
|
+
`For each opportunity, provide a recommendation with:`,
|
|
134
|
+
`- **filePath**: relative path to the file`,
|
|
135
|
+
`- **issue**: what's wrong (too long, duplicated logic, mixed concerns, etc.)`,
|
|
136
|
+
`- **suggestion**: concrete refactoring to apply (extract hook/component, split file, deduplicate, etc.)`,
|
|
137
|
+
`- **severity**: "low", "medium", or "high"`,
|
|
138
|
+
`- **estimatedLineReduction** (optional): how many lines could be saved`,
|
|
139
|
+
``,
|
|
140
|
+
`### Rules`,
|
|
141
|
+
`1. DO NOT use hard line limits. Use adaptive thresholds: files significantly above the project median or average.`,
|
|
142
|
+
`2. Focus on structural issues: component decomposition, hook extraction, DRY violations, mixed concerns.`,
|
|
143
|
+
`3. Avoid cosmetic-only changes (renames, formatting). Each recommendation must have a functional benefit.`,
|
|
144
|
+
`4. Be conservative — only recommend changes you are confident are correct and safe.`,
|
|
145
|
+
`5. NEVER recommend deleting functionality or changing public APIs without strong justification.`,
|
|
146
|
+
``,
|
|
147
|
+
`Output your analysis as a JSON array of recommendation objects. Do NOT include markdown code fences — output raw JSON only.`,
|
|
148
|
+
].join("\n");
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Build the execute-phase prompt — applies approved recommendations.
|
|
152
|
+
*/
|
|
153
|
+
function buildExecutePrompt(approvedIds) {
|
|
154
|
+
return [
|
|
155
|
+
`You are the Auto-Optimizer. Execute the approved optimization recommendations.`,
|
|
156
|
+
``,
|
|
157
|
+
`## Approved Recommendation IDs`,
|
|
158
|
+
approvedIds.map((id) => `- ${id}`).join("\n"),
|
|
159
|
+
``,
|
|
160
|
+
`## Instructions`,
|
|
161
|
+
`Read each recommendation from the previous analysis, then:`,
|
|
162
|
+
`1. Apply the suggested refactoring to the target file(s).`,
|
|
163
|
+
`2. Ensure all imports, exports, and type references remain correct.`,
|
|
164
|
+
`3. After all changes: run type-check, lint, and tests on modified files.`,
|
|
165
|
+
`4. Report results per recommendation: success/fail, before/after line counts, and any errors.`,
|
|
166
|
+
``,
|
|
167
|
+
`Output a JSON object with \`results\` (array) and \`gates\` (object with passed: boolean and results array).`,
|
|
168
|
+
`Do NOT include markdown code fences — output raw JSON only.`,
|
|
169
|
+
].join("\n");
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Parse the agent's response into structured recommendations.
|
|
173
|
+
* Handles both raw JSON and JSON-in-markdown-fences.
|
|
174
|
+
*/
|
|
175
|
+
function parseRecommendations(text) {
|
|
176
|
+
// Try extracting JSON from code fences first
|
|
177
|
+
const fenceMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
178
|
+
const jsonText = fenceMatch ? fenceMatch[1].trim() : text.trim();
|
|
179
|
+
try {
|
|
180
|
+
const parsed = JSON.parse(jsonText);
|
|
181
|
+
if (Array.isArray(parsed))
|
|
182
|
+
return parsed;
|
|
183
|
+
// Some models wrap in an object
|
|
184
|
+
if (parsed.recommendations && Array.isArray(parsed.recommendations)) {
|
|
185
|
+
return parsed.recommendations;
|
|
186
|
+
}
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function parseExecuteResults(text) {
|
|
194
|
+
const fenceMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
195
|
+
const jsonText = fenceMatch ? fenceMatch[1].trim() : text.trim();
|
|
196
|
+
try {
|
|
197
|
+
const parsed = JSON.parse(jsonText);
|
|
198
|
+
if (parsed.results && Array.isArray(parsed.results)) {
|
|
199
|
+
return { results: parsed.results };
|
|
200
|
+
}
|
|
201
|
+
if (Array.isArray(parsed)) {
|
|
202
|
+
return { results: parsed };
|
|
203
|
+
}
|
|
204
|
+
return { results: [] };
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return { results: [] };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function runQualityGates(projectPath) {
|
|
211
|
+
const gates = [];
|
|
212
|
+
// Gate 1: TypeScript type-check
|
|
213
|
+
const hasTsConfig = fs.existsSync(path.join(projectPath, "tsconfig.json"));
|
|
214
|
+
if (hasTsConfig) {
|
|
215
|
+
try {
|
|
216
|
+
execSync("npx tsc --noEmit", {
|
|
217
|
+
cwd: projectPath,
|
|
218
|
+
timeout: 60_000,
|
|
219
|
+
stdio: "pipe",
|
|
220
|
+
});
|
|
221
|
+
gates.push({ gate: "tsc", passed: true, message: "TypeScript type-check passed." });
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
const e = err;
|
|
225
|
+
const raw = e.stderr?.toString() || e.message || "Unknown error";
|
|
226
|
+
const short = raw.length > 500 ? raw.slice(0, 500) + "..." : raw;
|
|
227
|
+
gates.push({ gate: "tsc", passed: false, message: `TypeScript errors:\n${short}` });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Gate 2: Next.js lint (if Next.js project)
|
|
231
|
+
const hasNextConfig = fs.existsSync(path.join(projectPath, "next.config.ts")) ||
|
|
232
|
+
fs.existsSync(path.join(projectPath, "next.config.js")) ||
|
|
233
|
+
fs.existsSync(path.join(projectPath, "next.config.mjs"));
|
|
234
|
+
if (hasNextConfig) {
|
|
235
|
+
try {
|
|
236
|
+
execSync("npx next lint --no-cache", {
|
|
237
|
+
cwd: projectPath,
|
|
238
|
+
timeout: 120_000,
|
|
239
|
+
stdio: "pipe",
|
|
240
|
+
});
|
|
241
|
+
gates.push({ gate: "next lint", passed: true, message: "Next.js lint passed." });
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
const e = err;
|
|
245
|
+
const raw = e.stderr?.toString() || e.message || "Unknown error";
|
|
246
|
+
const short = raw.length > 500 ? raw.slice(0, 500) + "..." : raw;
|
|
247
|
+
gates.push({ gate: "next lint", passed: false, message: `Lint errors:\n${short}` });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Gate 3: Deno lint (if Deno project and not already Next.js)
|
|
251
|
+
const hasDenoJson = fs.existsSync(path.join(projectPath, "deno.json"));
|
|
252
|
+
if (hasDenoJson && !hasNextConfig) {
|
|
253
|
+
try {
|
|
254
|
+
execSync("deno lint", {
|
|
255
|
+
cwd: projectPath,
|
|
256
|
+
timeout: 60_000,
|
|
257
|
+
stdio: "pipe",
|
|
258
|
+
});
|
|
259
|
+
gates.push({ gate: "deno lint", passed: true, message: "Deno lint passed." });
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
const e = err;
|
|
263
|
+
const raw = e.stderr?.toString() || e.message || "Unknown error";
|
|
264
|
+
const short = raw.length > 500 ? raw.slice(0, 500) + "..." : raw;
|
|
265
|
+
gates.push({ gate: "deno lint", passed: false, message: `Lint errors:\n${short}` });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// If no gates ran, return informational note
|
|
269
|
+
if (gates.length === 0) {
|
|
270
|
+
gates.push({
|
|
271
|
+
gate: "info",
|
|
272
|
+
passed: true,
|
|
273
|
+
message: "No quality gates configured for this project type.",
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return gates;
|
|
277
|
+
}
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
// Main handler
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
export function handleAutoOptimize(input, deps) {
|
|
282
|
+
const { projectId, sessionId, action, approvedItemIds } = input;
|
|
283
|
+
const { store, manager, relay, subscribers } = deps;
|
|
284
|
+
const logger = deps.logger ?? console;
|
|
285
|
+
if (activeAutoOptimizeSessions.has(sessionId)) {
|
|
286
|
+
sendEvent(relay, sessionId, {
|
|
287
|
+
type: "auto_optimize_error",
|
|
288
|
+
projectId,
|
|
289
|
+
message: "Auto-optimizer is already running for this session.",
|
|
290
|
+
});
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const project = store.getProject(projectId);
|
|
294
|
+
if (!project) {
|
|
295
|
+
sendEvent(relay, sessionId, {
|
|
296
|
+
type: "auto_optimize_error",
|
|
297
|
+
projectId,
|
|
298
|
+
message: `Project not found: ${projectId}`,
|
|
299
|
+
});
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
// Validate action-specific requirements
|
|
303
|
+
if (action === "execute" && (!approvedItemIds || approvedItemIds.length === 0)) {
|
|
304
|
+
sendEvent(relay, sessionId, {
|
|
305
|
+
type: "auto_optimize_error",
|
|
306
|
+
projectId,
|
|
307
|
+
message: "Execute action requires approvedItemIds.",
|
|
308
|
+
});
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
sendEvent(relay, sessionId, {
|
|
312
|
+
type: "auto_optimize_start",
|
|
313
|
+
projectId,
|
|
314
|
+
action,
|
|
315
|
+
});
|
|
316
|
+
// Ensure a subscriber exists for this session
|
|
317
|
+
let subscriber = subscribers.get(sessionId);
|
|
318
|
+
if (!subscriber) {
|
|
319
|
+
subscriber = makeRelaySubscriber(sessionId, relay);
|
|
320
|
+
try {
|
|
321
|
+
manager.attach(sessionId, subscriber);
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
325
|
+
sendEvent(relay, sessionId, {
|
|
326
|
+
type: "auto_optimize_error",
|
|
327
|
+
projectId,
|
|
328
|
+
message: `Failed to attach session subscriber: ${msg}`,
|
|
329
|
+
});
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
subscribers.set(sessionId, subscriber);
|
|
333
|
+
}
|
|
334
|
+
// Build task content based on phase
|
|
335
|
+
let taskContent;
|
|
336
|
+
if (action === "analyze") {
|
|
337
|
+
const projectPath = project.path;
|
|
338
|
+
const fileStats = scanProjectFiles(projectPath);
|
|
339
|
+
if (fileStats.length === 0) {
|
|
340
|
+
sendEvent(relay, sessionId, {
|
|
341
|
+
type: "auto_optimize_error",
|
|
342
|
+
projectId,
|
|
343
|
+
message: "No source files found in project.",
|
|
344
|
+
});
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
taskContent = buildAnalyzePrompt(fileStats, project.name);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
taskContent = buildExecutePrompt(approvedItemIds);
|
|
351
|
+
}
|
|
352
|
+
const storedModelId = store.getSessionModel(sessionId) ?? undefined;
|
|
353
|
+
activeAutoOptimizeSessions.add(sessionId);
|
|
354
|
+
// Phase 3: buffer text_delta events to parse the agent's structured output
|
|
355
|
+
let textBuffer = "";
|
|
356
|
+
let watcherFired = false;
|
|
357
|
+
const doCleanupAndSend = (event) => {
|
|
358
|
+
activeAutoOptimizeSessions.delete(sessionId);
|
|
359
|
+
try {
|
|
360
|
+
manager.detach(sessionId, watcher);
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// already detached
|
|
364
|
+
}
|
|
365
|
+
sendEvent(relay, sessionId, event);
|
|
366
|
+
};
|
|
367
|
+
let retryCount = 0;
|
|
368
|
+
const MAX_PROMPT_RETRIES = 3;
|
|
369
|
+
const isCompactingError = (msg) => /compacting|compacted|wait.*moment|try again/i.test(msg);
|
|
370
|
+
const sendPrompt = () => {
|
|
371
|
+
manager
|
|
372
|
+
.prompt(sessionId, taskContent, storedModelId, undefined, undefined, {
|
|
373
|
+
skipPersistence: true,
|
|
374
|
+
})
|
|
375
|
+
.catch((err) => {
|
|
376
|
+
if (watcherFired)
|
|
377
|
+
return;
|
|
378
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
379
|
+
logger.error?.(`[auto-optimizer] manager.prompt failed for ${sessionId}:`, msg);
|
|
380
|
+
clearTimeout(timeout);
|
|
381
|
+
watcherFired = true;
|
|
382
|
+
doCleanupAndSend({
|
|
383
|
+
type: "auto_optimize_error",
|
|
384
|
+
projectId,
|
|
385
|
+
message: `Auto-optimizer prompt failed: ${msg}`,
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
};
|
|
389
|
+
const watcher = {
|
|
390
|
+
send(event) {
|
|
391
|
+
if (watcherFired)
|
|
392
|
+
return;
|
|
393
|
+
if (event.type === "text_delta") {
|
|
394
|
+
textBuffer += event.delta;
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (event.type === "agent_end") {
|
|
398
|
+
watcherFired = true;
|
|
399
|
+
clearTimeout(timeout);
|
|
400
|
+
if (action === "analyze") {
|
|
401
|
+
const recommendations = parseRecommendations(textBuffer);
|
|
402
|
+
logger.error?.(`[auto-optimizer] analyze complete: ${recommendations.length} recommendations from ${textBuffer.length} chars`);
|
|
403
|
+
doCleanupAndSend({
|
|
404
|
+
type: "auto_optimize_analysis_complete",
|
|
405
|
+
projectId,
|
|
406
|
+
recommendations,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
// execute — parse results then run quality gates (Phase 5)
|
|
411
|
+
const { results } = parseExecuteResults(textBuffer);
|
|
412
|
+
logger.error?.(`[auto-optimizer] execute complete: ${results.length} results, running quality gates...`);
|
|
413
|
+
runQualityGates(project.path)
|
|
414
|
+
.then((gateResults) => {
|
|
415
|
+
const gatesPassed = gateResults.every((g) => g.passed);
|
|
416
|
+
doCleanupAndSend({
|
|
417
|
+
type: "auto_optimize_execution_complete",
|
|
418
|
+
projectId,
|
|
419
|
+
results,
|
|
420
|
+
gatesPassed,
|
|
421
|
+
gateResults,
|
|
422
|
+
});
|
|
423
|
+
})
|
|
424
|
+
.catch((err) => {
|
|
425
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
426
|
+
logger.error?.("[auto-optimizer] quality gates crashed:", msg);
|
|
427
|
+
doCleanupAndSend({
|
|
428
|
+
type: "auto_optimize_error",
|
|
429
|
+
projectId,
|
|
430
|
+
message: `Quality gates failed: ${msg}`,
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
else if (event.type === "error") {
|
|
436
|
+
const errorMsg = typeof event.message === "string" ? event.message : "";
|
|
437
|
+
// If the session is compacting, retry prompt with backoff
|
|
438
|
+
// instead of immediately failing
|
|
439
|
+
if (isCompactingError(errorMsg) &&
|
|
440
|
+
retryCount < MAX_PROMPT_RETRIES) {
|
|
441
|
+
retryCount++;
|
|
442
|
+
const delay = 2000 * Math.pow(2, retryCount - 1); // 2s, 4s, 8s
|
|
443
|
+
logger.error?.(`[auto-optimizer] session compacting, retrying prompt ${retryCount}/${MAX_PROMPT_RETRIES} in ${delay}ms`);
|
|
444
|
+
setTimeout(() => {
|
|
445
|
+
if (watcherFired)
|
|
446
|
+
return;
|
|
447
|
+
sendPrompt();
|
|
448
|
+
}, delay);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
watcherFired = true;
|
|
452
|
+
clearTimeout(timeout);
|
|
453
|
+
doCleanupAndSend({
|
|
454
|
+
type: "auto_optimize_error",
|
|
455
|
+
projectId,
|
|
456
|
+
message: event.message,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
isOpen() {
|
|
461
|
+
return true;
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
try {
|
|
465
|
+
manager.attach(sessionId, watcher);
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
469
|
+
watcherFired = true;
|
|
470
|
+
doCleanupAndSend({
|
|
471
|
+
type: "auto_optimize_error",
|
|
472
|
+
projectId,
|
|
473
|
+
message: `Failed to attach auto-optimizer watcher: ${msg}`,
|
|
474
|
+
});
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const timeout = setTimeout(() => {
|
|
478
|
+
if (watcherFired)
|
|
479
|
+
return;
|
|
480
|
+
watcherFired = true;
|
|
481
|
+
doCleanupAndSend({
|
|
482
|
+
type: "auto_optimize_error",
|
|
483
|
+
projectId,
|
|
484
|
+
message: "Auto-optimizer timed out after 10 minutes.",
|
|
485
|
+
});
|
|
486
|
+
}, OPTIMIZER_TIMEOUT_MS);
|
|
487
|
+
sendPrompt();
|
|
488
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/relay/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,SAAS,MAAM,IAAI,CAAC;AAqB3B,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,SAAS,CAAC;IACjC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;OAEG;IACH,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACjD;;;;OAIG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;CAChC;AAED,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,WAAY,SAAQ,YAAY;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAmB;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IACjE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0B;IAE/C,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,SAAS,CAAgB;IACjC,iFAAiF;IACjF,OAAO,CAAC,UAAU,CAAS;IAE3B,qEAAqE;IACrE,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,aAAa,CAA+B;gBAExC,IAAI,EAAE,kBAAkB;IAWpC,kEAAkE;IAClE,OAAO,IAAI,IAAI;IAMf;;;;;;;OAOG;IACH,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO;IAqBzC;;;OAGG;IACH,OAAO,IAAI,IAAI;IA2Bf,OAAO,CAAC,UAAU;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/relay/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,SAAS,MAAM,IAAI,CAAC;AAqB3B,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,SAAS,CAAC;IACjC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;OAEG;IACH,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACjD;;;;OAIG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;CAChC;AAED,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,WAAY,SAAQ,YAAY;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAmB;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IACjE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0B;IAE/C,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,SAAS,CAAgB;IACjC,iFAAiF;IACjF,OAAO,CAAC,UAAU,CAAS;IAE3B,qEAAqE;IACrE,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,aAAa,CAA+B;gBAExC,IAAI,EAAE,kBAAkB;IAWpC,kEAAkE;IAClE,OAAO,IAAI,IAAI;IAMf;;;;;;;OAOG;IACH,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO;IAqBzC;;;OAGG;IACH,OAAO,IAAI,IAAI;IA2Bf,OAAO,CAAC,UAAU;IAgJlB,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,YAAY;IAOpB;;;;;OAKG;YACW,WAAW;YA8BX,iBAAiB;CAmDhC"}
|
package/dist/relay/client.js
CHANGED
|
@@ -258,6 +258,16 @@ export class RelayClient extends EventEmitter {
|
|
|
258
258
|
this.exit(1);
|
|
259
259
|
return;
|
|
260
260
|
}
|
|
261
|
+
if (reasonStr === "team-changed") {
|
|
262
|
+
// Team was changed via Studio — the machine_team_changed frame handler
|
|
263
|
+
// in serve.ts already saved the new JWT. Just reconnect normally.
|
|
264
|
+
if (this.authFailed) {
|
|
265
|
+
this.emit("auth-failed");
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
this.scheduleReconnect();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
261
271
|
// Auth failure at the transport layer (HTTP 401/403 on WS upgrade).
|
|
262
272
|
// The machine JWT is invalid (expired, malformed, or revoked by key
|
|
263
273
|
// rotation) but the backend didn't send a structured close reason.
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* or `code:"machine_offline"` (CLI socket dropped). Both are
|
|
39
39
|
* transparent to this layer — we just respond when we can.
|
|
40
40
|
*/
|
|
41
|
-
import type { AutoResearchFrame, CancelTurnFrame, ClientMessageFrame, MetaEvent, RestRequestFrame, RestResponseFrame, SubscribeFrame } from "@aexol/relay-protocol";
|
|
41
|
+
import type { AutoOptimizeFrame, AutoResearchFrame, CancelTurnFrame, ClientMessageFrame, MetaEvent, RestRequestFrame, RestResponseFrame, SubscribeFrame } from "@aexol/relay-protocol";
|
|
42
42
|
import type { McpExtensionState } from "../mcp/state.js";
|
|
43
43
|
import type { SessionStreamManager, Subscriber } from "../server/session-stream.js";
|
|
44
44
|
import type { SessionStore } from "../server/storage.js";
|
|
@@ -48,7 +48,7 @@ import type { RelayClient } from "./client.js";
|
|
|
48
48
|
* `id` is populated when the route had an `:id` placeholder.
|
|
49
49
|
*/
|
|
50
50
|
interface RouteMatch {
|
|
51
|
-
route: "list_projects" | "create_project" | "update_project" | "delete_project" | "bind_studio" | "unbind_studio" | "get_project_observations" | "list_project_sessions" | "cleanup_project_sessions" | "create_session" | "get_session" | "get_session_messages" | "update_session" | "delete_session" | "get_session_memory" | "get_session_memory_details" | "compact_session" | "remember_and_delete_session" | "fork_session" | "list_path_autocomplete" | "pick_directory" | "search_project_files" | "enqueue_prompt" | "get_prompt_queue" | "remove_prompt" | "clear_prompt_queue" | "list_mcp_status" | "reload_mcp_config" | "refresh_native_extensions" | "get_settings" | "put_settings" | "get_agent_settings" | "put_agent_settings" | "list_agents";
|
|
51
|
+
route: "list_projects" | "create_project" | "update_project" | "delete_project" | "bind_studio" | "unbind_studio" | "get_project_observations" | "list_project_sessions" | "cleanup_project_sessions" | "create_session" | "get_session" | "get_session_messages" | "update_session" | "delete_session" | "get_session_memory" | "get_session_memory_details" | "compact_session" | "remember_and_delete_session" | "fork_session" | "list_path_autocomplete" | "pick_directory" | "search_project_files" | "enqueue_prompt" | "get_prompt_queue" | "remove_prompt" | "clear_prompt_queue" | "list_mcp_status" | "reload_mcp_config" | "refresh_native_extensions" | "get_settings" | "put_settings" | "get_agent_settings" | "put_agent_settings" | "get_prompt_mutation_settings" | "put_prompt_mutation_settings" | "list_agents";
|
|
52
52
|
id?: string;
|
|
53
53
|
/** Parsed query params, if the path carried a `?...` suffix. */
|
|
54
54
|
query?: URLSearchParams;
|
|
@@ -215,5 +215,23 @@ export interface AutoResearchDeps {
|
|
|
215
215
|
* Errors are surfaced as `auto_research_error` events.
|
|
216
216
|
*/
|
|
217
217
|
export declare function handleAutoResearchFrame(frame: AutoResearchFrame, deps: AutoResearchDeps): void;
|
|
218
|
+
/** Dependencies for auto-optimizer — mirror of AutoResearchDeps. */
|
|
219
|
+
export interface AutoOptimizeDeps {
|
|
220
|
+
store: SessionStore;
|
|
221
|
+
manager: SessionStreamManager;
|
|
222
|
+
relay: Pick<RelayClient, "send">;
|
|
223
|
+
subscribers: Map<string, Subscriber>;
|
|
224
|
+
cwd: string;
|
|
225
|
+
logger?: {
|
|
226
|
+
error: (...args: unknown[]) => void;
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Dispatch an `auto_optimize` frame. Two-phase flow controlled by `action`:
|
|
231
|
+
* - "analyze" scans the project and streams back `auto_optimize_analysis_complete`.
|
|
232
|
+
* - "execute" applies approved recommendations and runs gate checks.
|
|
233
|
+
* Progress/results are streamed as `auto_optimize_*` ws_events on `sessionId`.
|
|
234
|
+
*/
|
|
235
|
+
export declare function handleAutoOptimizeFrame(frame: AutoOptimizeFrame, deps: AutoOptimizeDeps): void;
|
|
218
236
|
export {};
|
|
219
237
|
//# sourceMappingURL=dispatcher.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/relay/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAIH,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACf,MAAM,uBAAuB,CAAC;AAK/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/relay/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAIH,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACf,MAAM,uBAAuB,CAAC;AAK/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AA8CzD,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACX,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQ/C;;;GAGG;AACH,UAAU,UAAU;IAClB,KAAK,EACD,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,gBAAgB,GAChB,aAAa,GACb,eAAe,GACf,0BAA0B,GAC1B,uBAAuB,GACvB,0BAA0B,GAC1B,gBAAgB,GAChB,aAAa,GACb,sBAAsB,GACtB,gBAAgB,GAChB,gBAAgB,GAChB,oBAAoB,GACpB,4BAA4B,GAC5B,iBAAiB,GACjB,6BAA6B,GAC7B,cAAc,GACd,wBAAwB,GACxB,gBAAgB,GAChB,sBAAsB,GACtB,gBAAgB,GAChB,kBAAkB,GAClB,eAAe,GACf,oBAAoB,GACpB,iBAAiB,GACjB,mBAAmB,GACnB,2BAA2B,GAC3B,cAAc,GACd,cAAc,GACd,oBAAoB,GACpB,oBAAoB,GACpB,8BAA8B,GAC9B,8BAA8B,GAC9B,aAAa,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,UAAU,GAAG,IAAI,CAqLnB;AAMD;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,6FAA6F;IAC7F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uGAAuG;IACvG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;IACjD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,uBAAuB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,iBAAiB,CAAC,CAiD5B;AAoYD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,oBAAoB,CAAC;IAC9B,2DAA2D;IAC3D,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACjC;;;;;;;;;OASG;IACH,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,yEAAyE;IACzE,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAClD;AA8BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,kBAAkB,EACzB,IAAI,EAAE,iBAAiB,GACtB,IAAI,CAyIN;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,iBAAiB,GACtB,IAAI,CA0DN;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE;IAAE,OAAO,EAAE,oBAAoB,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;KAAE,CAAA;CAAE,GACxF,IAAI,CAEN;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,oBAAoB,EAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GACnC,IAAI,CASN;AAMD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACjC,sDAAsD;IACtD,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,iCAAiC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,kDAAkD;IAClD,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAClD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,iBAAiB,EACxB,IAAI,EAAE,gBAAgB,GACrB,IAAI,CAQN;AAED,oEAAoE;AACpE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAClD;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,iBAAiB,EACxB,IAAI,EAAE,gBAAgB,GACrB,IAAI,CAUN"}
|
package/dist/relay/dispatcher.js
CHANGED
|
@@ -50,8 +50,10 @@ import { handleCompactSession, handleCreateSession, handleDeleteSession, handleF
|
|
|
50
50
|
import { handleClearPromptQueue, handleEnqueuePrompt, handleGetPromptQueue, handleRemovePrompt, } from "../server/handlers/queue.js";
|
|
51
51
|
import { handleGetAgentSettings, handleListAgents, handlePutAgentSettings, } from "../server/handlers/agent-settings.js";
|
|
52
52
|
import { handleGetSettings, handlePutSettings, } from "../server/handlers/settings.js";
|
|
53
|
+
import { handleGetPromptMutationSettings, handlePutPromptMutationSettings, } from "../server/handlers/prompt-mutation-settings.js";
|
|
53
54
|
import { shutdownState } from "../server/shutdown.js";
|
|
54
55
|
import { handleAutoResearch } from "./auto-research.js";
|
|
56
|
+
import { handleAutoOptimize } from "./auto-optimizer.js";
|
|
55
57
|
/**
|
|
56
58
|
* Inline path matcher. Returns `null` for any path/method combination we
|
|
57
59
|
* don't recognise; the caller turns that into a `404 Unknown route`.
|
|
@@ -96,6 +98,13 @@ export function matchRoute(method, path) {
|
|
|
96
98
|
return { route: "put_agent_settings" };
|
|
97
99
|
return null;
|
|
98
100
|
}
|
|
101
|
+
if (cleanPath === "/api/prompt-mutation-settings") {
|
|
102
|
+
if (method === "GET")
|
|
103
|
+
return { route: "get_prompt_mutation_settings" };
|
|
104
|
+
if (method === "PUT")
|
|
105
|
+
return { route: "put_prompt_mutation_settings" };
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
99
108
|
// /api/agents
|
|
100
109
|
if (cleanPath === "/api/agents") {
|
|
101
110
|
if (method === "GET")
|
|
@@ -554,6 +563,10 @@ async function dispatchRoute(match, body, deps) {
|
|
|
554
563
|
return handleGetAgentSettings(deps.cwd ?? homedir(), deps.agentDir);
|
|
555
564
|
case "put_agent_settings":
|
|
556
565
|
return handlePutAgentSettings(deps.cwd ?? homedir(), asObject(body), deps.agentDir);
|
|
566
|
+
case "get_prompt_mutation_settings":
|
|
567
|
+
return handleGetPromptMutationSettings(deps.cwd ?? homedir(), deps.agentDir);
|
|
568
|
+
case "put_prompt_mutation_settings":
|
|
569
|
+
return handlePutPromptMutationSettings(deps.cwd ?? homedir(), asObject(body), deps.agentDir);
|
|
557
570
|
case "list_agents": {
|
|
558
571
|
const agentCwd = match.query?.get("cwd") || deps.cwd || homedir();
|
|
559
572
|
return handleListAgents(agentCwd, deps.agentDir);
|
|
@@ -899,3 +912,17 @@ export function handleAutoResearchFrame(frame, deps) {
|
|
|
899
912
|
sessionId: frame.sessionId,
|
|
900
913
|
}, deps);
|
|
901
914
|
}
|
|
915
|
+
/**
|
|
916
|
+
* Dispatch an `auto_optimize` frame. Two-phase flow controlled by `action`:
|
|
917
|
+
* - "analyze" scans the project and streams back `auto_optimize_analysis_complete`.
|
|
918
|
+
* - "execute" applies approved recommendations and runs gate checks.
|
|
919
|
+
* Progress/results are streamed as `auto_optimize_*` ws_events on `sessionId`.
|
|
920
|
+
*/
|
|
921
|
+
export function handleAutoOptimizeFrame(frame, deps) {
|
|
922
|
+
handleAutoOptimize({
|
|
923
|
+
projectId: frame.projectId,
|
|
924
|
+
sessionId: frame.sessionId,
|
|
925
|
+
action: frame.action,
|
|
926
|
+
approvedItemIds: frame.approvedItemIds,
|
|
927
|
+
}, deps);
|
|
928
|
+
}
|
|
@@ -78,6 +78,10 @@ export type AgentSessionEvent = Exclude<AgentEvent, {
|
|
|
78
78
|
type: "compaction_delta";
|
|
79
79
|
delta: string;
|
|
80
80
|
content: string;
|
|
81
|
+
} | {
|
|
82
|
+
type: "prompt_mutation_status";
|
|
83
|
+
phase: "mutating" | "done";
|
|
84
|
+
message?: string;
|
|
81
85
|
};
|
|
82
86
|
/** Listener function for agent session events */
|
|
83
87
|
export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
|
|
@@ -198,6 +202,7 @@ export declare class AgentSession {
|
|
|
198
202
|
private _extensionCommandContextActions?;
|
|
199
203
|
private _extensionAbortHandler?;
|
|
200
204
|
private _extensionShutdownHandler?;
|
|
205
|
+
private _promptMutationApplied;
|
|
201
206
|
private _extensionErrorListener?;
|
|
202
207
|
private _extensionErrorUnsubscriber?;
|
|
203
208
|
private _modelRegistry;
|
|
@@ -316,6 +321,7 @@ export declare class AgentSession {
|
|
|
316
321
|
private _normalizePromptGuidelines;
|
|
317
322
|
private _rebuildSystemPrompt;
|
|
318
323
|
private _runAgentPrompt;
|
|
324
|
+
private _applyPromptMutationIfNeeded;
|
|
319
325
|
private _handlePostAgentRun;
|
|
320
326
|
/**
|
|
321
327
|
* Send a prompt to the agent.
|