mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 02:49:33 +01:00
fix(edit-history): bounded image probe, linear path scan (review of #693)
- probeLocalPaths: every agent-printed candidate goes through probePathKind() first (no pastCap, bulk scan), sequentially; only a path that answered 'file' gets realpath + stat. A path on a mount that stopped answering no longer hangs the sync, a queued sync or remove(). - PATH_TOKEN_RE: branch 4 starts only at a token boundary (`:` excluded so URLs stay ignored), branch 1 stops at `:`. Linear in the line length (30,000 `#` took ~3 s before). - Load older yields to the event loop between the files it parses. - GET /edits answers unsupported without loading a store. - CLAUDE.md paragraph trimmed to the rules; probe and regex rules added to architecture-invariants#edit-history-panel. Tests: stall test through the real sync and probe with a stat that never settles; time-budget tests with 30,000-character inputs and a 139-result base64 chunk; route test asserts no store load for unsupported sessions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bf2a705705
commit
de43657676
10 files changed
+229
-35
No files matched your search
@@ -436,8 +436,14 @@ export class EditHistorySync {
|
||||
this.dropMissing(store, requests, res);
|
||||
const wanted = new Set(requests.map((r) => r.relPath));
|
||||
const candidates: ImageCandidate[] = [];
|
||||
let parsed = false;
|
||||
for (const f of res.files) {
|
||||
if (!wanted.has(f.relPath)) continue;
|
||||
// Each file is up to one budget (8 MiB) of synchronous parsing: let the server breathe
|
||||
// between them. A store removed meanwhile ignores the rest (it is inert once removed).
|
||||
if (parsed) await new Promise<void>((resolve) => setImmediate(resolve));
|
||||
parsed = true;
|
||||
if (slot.gone) break;
|
||||
const r = store.applyOlder({
|
||||
transcriptId: res.transcriptId,
|
||||
source: f.relPath,
|
||||
|
||||
@@ -105,13 +105,16 @@ export function registerEditHistoryRoutes(app: FastifyInstance, ctx: SessionPort
|
||||
const { sync: doSync } = req.query as { sync?: string };
|
||||
const { session, view } = sessionOf(id, req);
|
||||
const why = unsupportedReason(view);
|
||||
// Never read: answer an empty list without loading (or creating) a store for it.
|
||||
if (why) {
|
||||
return {
|
||||
success: true,
|
||||
data: { state: 'unsupported', reason: why, backfillTruncated: false, agents: {}, entries: [] },
|
||||
};
|
||||
}
|
||||
// The sync starts in the same tick as the session check: a deletion right after it marks
|
||||
// the running sync gone (EditHistorySync.remove), so nothing writes a deleted session.
|
||||
const status: EditHistoryStatus = why
|
||||
? { state: 'unsupported', reason: why }
|
||||
: doSync === '1'
|
||||
? await sync.sync(view)
|
||||
: sync.getStatus(session.id);
|
||||
const status: EditHistoryStatus = doSync === '1' ? await sync.sync(view) : sync.getStatus(session.id);
|
||||
return { success: true, data: listOf(await sync.getStore(session.id), session.workingDir, status) };
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user