@sema-agent/core
Version:
Stateless, task-oriented AI agent core
111 lines (110 loc) • 7.08 kB
TypeScript
/** 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[];
}
/**
* 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;