chore(telemetry): add path-confusion loop breaker and strict blank-preview diagnostic protocol
This commit is contained in:
@@ -310,6 +310,15 @@ Each project has a persistent \`vibn-dev\` container. Edit files via \`fs_*\` an
|
||||
- After \`dev_server_start\` returns a \`previewUrl\` AND \`healthCheck.status === 200\`, call \`browser_console { url: previewUrl }\` to capture frontend console errors.
|
||||
- **CRITICAL:** Next.js HMR overlay syntax errors do NOT crash the \`dev_server_start\` command. Even if \`dev_server_start\` returns \`Status: success\`, you MUST call \`browser_console\` to verify that there are no red syntax error overlays on the screen. If \`browser_console\` returns errors, fix them with \`fs_edit\` before declaring done. A green \`healthCheck\` plus a clean console is the real "done" signal for UI work.
|
||||
|
||||
**BLANK PREVIEW / NOT LOADING PROTOCOL:**
|
||||
If the user tells you the preview is blank, not loading, or shows nothing:
|
||||
1. **DO NOT GUESS OR EDIT CODE YET.**
|
||||
2. Run \`dev_server_list\` to check if the server is actually running.
|
||||
3. If it is not running, run \`dev_server_start\`.
|
||||
4. If it is running, run \`dev_server_logs\` on its port to check for compilation hangs (e.g. Turbopack slow filesystem hangs) or fatal errors.
|
||||
5. Run \`browser_console\` on the previewUrl.
|
||||
6. ONLY edit code or configuration once the logs/console explicitly identify the source file or error.
|
||||
|
||||
**HMR through the proxy (apply when scaffolding):**
|
||||
- **Vite (verified working):** in \`vite.config\` set \`server: { host: '0.0.0.0', port: <3000-3009>, strictPort: true, hmr: { clientPort: 443, protocol: 'wss', host: '<the previewUrl host, no protocol>' } }\`. The \`hmr.host\` is REQUIRED — without it Vite's HMR client can guess the wrong host and the WS handshake fails through Traefik. Default localhost binding looks fine locally but breaks HMR through the proxy.
|
||||
- **Next dev:** \`next dev -p 3000 -H 0.0.0.0\` (WSS HMR works automatically through the proxy without extra config).
|
||||
@@ -822,6 +831,7 @@ export async function POST(request: Request) {
|
||||
|
||||
let lastVerifySig: string | null = null;
|
||||
let lastRoundToolSig: string | null = null;
|
||||
let lastRoundResults: any[] = [];
|
||||
let fileHashes = new Map<string, string>();
|
||||
let stallRounds = 0;
|
||||
|
||||
@@ -1064,7 +1074,16 @@ export async function POST(request: Request) {
|
||||
"Stalled (Repeated the exact same tool calls twice without advancing)";
|
||||
}
|
||||
|
||||
const pathConfusion = detectPathConfusion(
|
||||
currentRoundResults,
|
||||
lastRoundResults,
|
||||
);
|
||||
if (pathConfusion) {
|
||||
loopBreakReason = `PATH_CONFUSION: ${pathConfusion}`;
|
||||
}
|
||||
|
||||
lastRoundToolSig = currentRoundToolSig;
|
||||
lastRoundResults = currentRoundResults;
|
||||
|
||||
if (loopBreakReason) break;
|
||||
}
|
||||
@@ -1546,3 +1565,44 @@ function checkRoundProgress(
|
||||
|
||||
return { progressed, nextHashes };
|
||||
}
|
||||
|
||||
function detectPathConfusion(current: any[], last: any[]): string | null {
|
||||
const missingFiles = new Set<string>();
|
||||
|
||||
const extractMissingFiles = (results: any[]) => {
|
||||
for (const tr of results) {
|
||||
if (!tr.content) continue;
|
||||
try {
|
||||
const parsed = JSON.parse(tr.content);
|
||||
const resultStr =
|
||||
typeof parsed === "string" ? parsed : JSON.stringify(parsed);
|
||||
const argsStr = typeof tr.toolName === "string" ? tr.toolName : "";
|
||||
const allStr = resultStr + argsStr;
|
||||
|
||||
if (
|
||||
allStr.includes("not a file or missing") ||
|
||||
allStr.includes("No such file or directory")
|
||||
) {
|
||||
const match = allStr.match(/([a-zA-Z0-9_\-\.]+\.[a-zA-Z0-9]+)/);
|
||||
if (match && match[1]) {
|
||||
missingFiles.add(match[1]);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
extractMissingFiles(current);
|
||||
extractMissingFiles(last);
|
||||
|
||||
// If the agent hit a "Not a file" error on the same basename across two different rounds
|
||||
if (missingFiles.size > 0 && current.length > 0 && last.length > 0) {
|
||||
for (const file of missingFiles) {
|
||||
// Very basic heuristic: if it complains about the same file twice, break the loop
|
||||
// and instruct it to use \`find\`
|
||||
return `You are trying to read ${file} but the path is wrong. Stop guessing paths. Run 'shell_exec { command: "find . -name ${file}" }' to discover the exact path to this file, then use it exactly once.`;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user