UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

114 lines (113 loc) • 7.53 kB
/** One LSP diagnostic, the subset the model/shell needs (LSP `Diagnostic` narrowed). */ export interface LspDiagnostic { message: string; /** LSP severity: 1=Error 2=Warning 3=Information 4=Hint. Absent = unknown (sorted last). */ severity?: number; range?: { start: { line: number; character: number; }; end?: { line: number; character: number; }; }; code?: string | number; source?: string; } /** One file's new diagnostics, as drained for injection + the wire frame. */ export interface LspFileDiagnostics { uri: string; diagnostics: LspDiagnostic[]; } /** * The pending/delivered diagnostics registry. * * **Lifetime, stated honestly:** ONE registry is constructed per `NodeLspManager`, i.e. per DEPLOYMENT — * every task served through `RunnerDeps.lspManager` shares this object. Its two halves scope differently, * and deliberately: * - `pending` is the language server's own view of the workspace (whole-file replacement per the LSP * spec) and is therefore deployment-wide, like the servers that feed it; * - `delivered` — "this was already injected into the transcript" — is a per-RUN fact, so it is keyed by * the caller's `runIdent`. Without that scope a diagnostic delivered to one run was filtered out of * every later run's drain for the lifetime of the process, including runs that had never seen it and * (never having edited that file) had no way to make it resurface. Growth is bounded by the ACTIVE * runs, because {@link releaseRun} drops a finished run's keys. * * `publish` is called by an LSP session's publishDiagnostics subscription; `drain` is called at the turn * boundary and returns only diagnostics THAT RUN has not been shown, sorted and volume-capped. * * NOT durable: diagnostics regenerate from the language server on the next edit, and a resumed run is a * new run (fresh delivered scope), so a suspend/resume re-delivers rather than losing anything. */ export declare class LspDiagnosticsRegistry { /** uri → the LAST published full diagnostic set for that file (LSP replace semantics). */ private readonly pending; /** runIdent → the diagnostic keys already injected into THAT run. Cleared per file when that run edits * it (the problem may resurface against the new text), and wholesale when the run ends * ({@link releaseRun}). A Map of Sets rather than one flat prefixed set so that releasing/clearing a * run can never touch a run whose ident merely starts with the same characters. */ private readonly delivered; /** Record a `textDocument/publishDiagnostics` payload (already version-checked by the session). */ publish(uri: string, diagnostics: LspDiagnostic[]): void; /** The run `runIdent` edited `uri` — that run's delivered keys for the file reset, so a still-present * problem can resurface against the NEW text (CC clears per-file delivered on edit, 198 `Fjn`). Only * the editing run's view is cleared: another run never saw this edit, and re-showing it a diagnostic * it is already carrying in its transcript would be duplication, not news. */ fileEdited(runIdent: string, uri: string): void; /** The run ended — drop its delivered keys. This is what bounds the registry: without it the set grows * monotonically for the lifetime of the (deployment-scoped) manager. Idempotent; unknown idents are a * no-op, so a caller may release a run that never drained. */ releaseRun(runIdent: string): void; /** True when a drain would produce nothing (cheap pre-check for the turn-boundary hook). */ isEmpty(): boolean; /** * Take the diagnostics `runIdent` has NOT been shown: sorted Error>Warning>Info>Hint within each file, * capped at {@link MAX_PER_FILE}/{@link MAX_TOTAL}, marked delivered FOR THAT RUN. A file whose * diagnostics this run has all seen drops out (until its server publishes again or that run edits it). * "Delivered" is scoped to the run because it means "already in this transcript" — another run's * transcript does not have it, and must be able to see it (see the class note on lifetime). * * HRD-LSP-3: what the caps hold back is REQUEUED, not dropped. The old body capped the take and then * `pending.clear()`-ed unconditionally, so diagnostic #11 of a file — and every file after the 30-item * total ceiling — was neither returned nor marked delivered: it vanished, and no later drain could * recover it unless the server happened to re-publish. Errors ranked first inside a file made that worse, * not better: the highest-severity items were delivered and the REST were the ones destroyed. The * remainder now survives into the next drain (the next turn boundary), which is also what makes the * per-turn volume cap a PACING rule instead of a silent data cap. */ drain(runIdent: string): LspFileDiagnostics[]; } /** A drain with every file the run's read face withholds left out (`withheldOf` answers the batch: a withheld file is in * the map; a judgment that fails withholds them all). The run's drain already marked them delivered, so they do not come back. */ export declare function withholdListedDiagnostics(files: LspFileDiagnostics[], withheldOf: (uris: readonly string[]) => Promise<ReadonlyMap<string, unknown>>): Promise<LspFileDiagnostics[]>; /** * CC 198 `formatDiagnosticsSummary` (198:320480-320499), verbatim shape: * ``` * {basename}: * {symbol} [Line {line+1}:{col+1}] {message}[ [{code}]][ ({source})] * ``` * capped at 4000 chars with an honest `…[truncated]` tail. * * ENVELOPE CONTAINMENT (untrusted-text.ts `ENGINE_ENVELOPES`, the `new-diagnostics` row): the * per-diagnostic strings (`message`, `code`, `source`) and the file basename come from a LANGUAGE * SERVER — a process the engine launches over repository content, i.e. the same trust class as the * repository. They land inside the `<new-diagnostics>` envelope, and (since this block is now * delivered inside a marked reminder shell) inside an authority-marked reminder too, so a diagnostic * message carrying `</new-diagnostics>` closed the envelope and placed server text at the top level * of engine-authored authority. CC 223 does not neutralize here either — a deliberate, disclosed * hardening divergence: CC ships the same block inside the same reminder shell, so the hole exists on * both sides and only the fix is ours. Neutralization runs BEFORE the join, so the 4000-char cap * still bounds the FINAL text and can never cut inside a neutralized marker's own bytes. */ export declare function formatDiagnosticsSummary(files: LspFileDiagnostics[]): string; /** CC 198 model-facing injection block (198:320500-320503), verbatim framing. * * DELIVERY (design/319 sibling work): the runner shells this block in a MARKED `<system-reminder>` * before steering it — CC 223 `case "diagnostics"` does the same (`ih([Vr({content: …})])` → the * reminder wrap), and until that was matched this was the engine's only authority text reaching the * model as a bare block with no provenance mark at all. The block's own bytes are unchanged; the * shell is applied at the delivery site (runtask), not baked in here, so this exported renderer keeps * serving callers that place the block themselves. */ export declare function formatDiagnosticsBlock(files: LspFileDiagnostics[]): string;