@sema-agent/core
Version:
Stateless, task-oriented AI agent core
200 lines (199 loc) • 12.3 kB
TypeScript
import type { AgentMessage, ExecutionEnv, GitAnnouncementKind, SessionTreeEntry } from "../../internal/harness-types.js";
/** Bound into the hash domain: bump when the frame's wording/structure changes so the upgraded
* renderer re-announces on its first leg instead of being suppressed by a pre-upgrade hash. */
export declare const GIT_STATUS_FRAME_FORMAT_VERSION = 1;
/**
* The frame's own first paragraph (replaces the CC "will not update during the conversation" head,
* which would be FALSE under the frame protocol). Wording obligations: self-declares the update
* semantics (latest frame supersedes earlier ones), promises re-send on VISIBLE-view change only
* (truncation-bound honesty), and "observed while preparing" does not claim an atomic instant (the
* probe is two shell round-trips and a write can land between them).
*/
export declare const GIT_STATUS_FRAME_PREAMBLE = "This is the git status observed while preparing this request. When the visible snapshot changes it will be re-sent in a later message \u2014 the most recent git status frame supersedes earlier ones.";
/** Tombstone body — the probes could not reach git at all while earlier frames announced a
* positive view (sent once, on the availability flip edge only). */
export declare const GIT_STATUS_UNAVAILABLE_BODY = "Git status is currently unavailable; the most recent git status frame above may be stale.";
/** Tombstone body — the directory stopped being a git repository while earlier frames announced a
* positive view (sent once, on the flip edge; the env block's static is-repo line flips the same leg). */
export declare const GIT_STATUS_NON_REPO_BODY = "The working directory is no longer a git repository; earlier git status frames no longer apply.";
/** The `steering_injected` echo previews for the frame — CONSTANT wordings on purpose: branch and
* status text are repo-controlled and must not enter the event telemetry plane through the echo. */
export declare const GIT_STATUS_ECHO_PREVIEW: Record<GitAnnouncementKind, string>;
/** The raw material of one probe cycle, as prepare-task resolved it (§4.3 unified ladder):
* - `full` — §E14 and the H4 snapshot both succeeded: `snapshot` = the pre-rendered
* {@link import("../../prompts/default.js").buildGitSnapshot} block (CC template,
* sanitized + bounded by its renderer);
* - `degraded` — §E14 yielded git facts but the H4 snapshot failed (exit 41/42, timeout, sentinel
* mis-split): branch + dirtiness are all that is honestly known;
* - `unavailable` — the §E14 probe itself failed (git facts unknowable this leg);
* - `non-repo` — the probe ran and the cwd is not a git repository. */
export type GitStatusProbeOutcome = {
kind: "full";
snapshot: string;
} | {
kind: "degraded";
branch?: string;
dirty?: boolean;
} | {
kind: "unavailable";
} | {
kind: "non-repo";
};
/** Render the frame BODY (the text inside the `<system-reminder>` shell) for one probe outcome. */
export declare function renderGitStatusFrameBody(outcome: GitStatusProbeOutcome): string;
/**
* Content hash of a rendered frame body — the hash half of the `(kind, hash)` comparison tuple.
* Digest domain = format version + canonical repo root + kind + body, NUL-separated (none of the
* inputs may contain NUL: the body is sanitized text, the root a canonical path). `canonicalRoot`
* is the ExecutionEnv-canonicalized worktree root (or cwd outside a repo) so identical bytes from
* two different checkouts never share an announcement.
*/
export declare function hashGitStatusFrame(kind: GitAnnouncementKind, body: string, canonicalRoot: string): string;
/** One resolved frame — outcome rendered and hashed, ready for the (kind, hash) compare. */
export interface ResolvedGitStatusFrame {
kind: GitAnnouncementKind;
body: string;
hash: string;
/** Pre-rendered DEGRADED body + hash for the same probe cycle (present only when kind is
* `full`): the one-shot deterministic shrink target of the irreducible-core over-budget arc —
* computed at prepare time because the request-build path must not re-run probes. */
shrunk?: {
body: string;
hash: string;
};
}
/** Render + hash one probe outcome (and its deterministic degraded shrink target when full). */
export declare function resolveGitStatusFrame(outcome: GitStatusProbeOutcome, canonicalRoot: string, degradedMaterial?: {
branch?: string;
dirty?: boolean;
}): ResolvedGitStatusFrame;
/**
* The run-local git-status lane state (one per run, on `Prepared`) — the coordination surface
* between prepare (probe resolution), the run loop (delivery + receipt + compaction re-assertion)
* and the request-build context handler (trim protection). Deliberately a plain mutable ref, the
* `announcedListingsRef` discipline.
*/
export interface GitStatusLaneRef {
/** This leg's resolved frame (undefined ⇒ hands-less leg: the lane is out of scope, announced
* state untouched — the env block equally carried no git facts for such a leg). */
frame?: ResolvedGitStatusFrame;
/** Canonical repo root the frame was hashed against. */
canonicalRoot?: string;
/** The announced state as THIS run knows it (seeded from the read ladder at leg start, advanced
* at each receipt) — mirrored onto the suspend checkpoint. `pending: true` ⇒ a re-announcement
* is owed (frame append failed / compaction restated pending); the next boundary retries. */
announced?: {
kind: GitAnnouncementKind;
hash: string;
entryId?: string;
pending?: true;
};
/** Exact WRAPPED text of the newest announced frame — the frame SEGMENT, on both carry forms
* (this moved off "the whole first-message text"; the contract here had
* kept the pre-r1 words). The context guard finds the carrier by CONTAINING this engine-held
* string (engine-region gated), protects that carrier as a replace-by-key slot — only the newest
* carrier is protected, older frames trim like ordinary history — and charges the irreducible
* core by THIS segment, never by the whole carrier (the objective riding the same message keeps
* its own overflow exit). */
protectedText?: string;
/** One-shot latch of the irreducible-core arc's DISCLOSURE: the request view substituted the
* degraded rendering at least once (the substitution itself is per-request deterministic; the
* disclosure + pending re-announcement fire only on the first). */
overBudgetShrunk?: boolean;
/** The exact WRAPPED frame segment of the protected carrier and its degraded substitution target
* (both `<system-reminder>`-shelled, exactly as delivered) — the irreducible-core shrink is a
* literal string replace of `find` with `replace` inside the carrier's text. Set at delivery
* alongside {@link GitStatusLaneRef.protectedText}; present only for a full-kind frame. */
wrappedShrink?: {
find: string;
replace: string;
};
/** Wired by the run loop once its queue exists: re-assert the current frame (compaction landing
* + boundary retry both call this). */
reassert?: () => Promise<void>;
/** F5 — the typed terminal's surface bridge: the loop converts a
* context-hook throw into a failure MESSAGE (text only), so the thrown `code` never reaches
* result assembly on its own. Set alongside the throw; the run loop lifts it into
* `TaskResult.errorCode`. */
terminalCode?: "irreducible_core_over_budget";
/** F4 — the receipt's mirror write, PARKED instead of fired: a
* fire-and-forget CAS append from inside the message_end walk can race the loop's own next
* transcript append and fail the CRITICAL write with a conflict. The run loop flushes this at
* serialization points only (turn boundary, prompt settle); an unflushed slot at suspend is
* covered by the checkpoint rung (the frame IS on the branch — visibility check passes). */
mirrorOwed?: {
kind: GitAnnouncementKind;
hash: string;
entryId: string;
};
}
/**
* prepare-side probe half of the lane (H4, extracted from prepareTask): run the CC-shape snapshot
* round-trip through the SAME ExecutionEnv seam when the
* §E14 probe confirmed a repo, resolve the outcome through the §4.3 unified kind ladder, and hash
* the rendered frame with the canonical repo root bound in. `envFacts` carries the §E14 results
* (read-only here). Every degrade names its reason through `onDegrade` — including the sentinel
* MIS-SPLIT arm (parts !== 4), which previously dropped the snapshot with ZERO telemetry (the
* loud-bad-value fix). Returns the run's lane ref (empty on a hands-less leg).
*
* H4 anchor notes carried from the prepareTask body: four sections split by a sentinel line —
* main-branch inference (CC qP symref → [inferred, main, master] each show-ref → renderer falls
* back "main"), `git config user.name`, `git --no-optional-locks status --short`, and
* `git --no-optional-locks log --oneline -n 5` (CC-exact commands). The two
* REQUIRED sections fail the WHOLE script with distinct exit codes (41 = status, 42 = log) so a
* failing status never half-renders as "(clean)"; both now DEGRADE the frame to the branch+dirty
* residual instead of silently skipping. The main-branch/user sections stay best-effort.
*/
export declare function probeGitStatusLane(args: {
executionEnv: Pick<ExecutionEnv, "exec" | "canonicalPath">;
envFacts: {
isGitRepo?: boolean;
gitBranch?: string;
gitDirty?: boolean;
gitWorktreeRoot?: string;
cwd?: string;
};
handsEnabled: boolean;
taskRoot: string;
onDegrade: (reason: string) => void;
}): Promise<GitStatusLaneRef>;
/**
* Request-build half of the trim protection (extracted from prepareTask's context handler under the
* same D-7 ratchet): wraps {@link protectGitFrame} with the lane ref's state — the substitution
* pair, the one-shot disclosure latch and the pending re-announcement on the first shrink — and
* converts the over-budget verdict into the LOUD typed terminal (`irreducible_core_over_budget`),
* never a pretended trim success. Returns the (possibly re-inserted / shrunk) request view.
*/
export declare function applyGitFrameGuard(args: {
before: AgentMessage[];
trimmed: AgentMessage[];
budgetTokens: number;
ref: GitStatusLaneRef;
charsPerToken?: number;
onDegrade: (message: string) => void;
}): AgentMessage[];
/**
* The transcript-side classifier of engine
* git frames. The mirror plane is a CACHE of "which frame is newest"; the TRANSCRIPT is the truth,
* and the two diverge exactly when a mirror write was lost. This scan finds the NEWEST engine git
* frame on the branch, with TOP-LEVEL WRAPPED-UNIT matching:
* - positive frame: the wrapped unit's head (open tag + newline + the frame preamble);
* - tombstone: the exact wrapped unit (open tag + newline + tombstone body + newline + close tag);
* - the occurrence must sit inside the message's ENGINE region (engineMinted / enginePrefixChars
* / engineSegments — metadata gates, never user-text shape-guessing), AND at TOP LEVEL (r4-2:
* start-of-text or right after a close tag — a hook relay that QUOTES the exact frame unit
* nests it inside its own shell with prose before the open tag, and is rejected);
* - within one message the LAST top-level unit wins (textual order = issue order).
*/
export declare function newestEngineGitFrame(branch: SessionTreeEntry[], mark?: string): {
entryId: string;
positive: boolean;
} | undefined;
export declare function stripGitStatusUnits(text: string, mark?: string): string;
/**
* The readable-absence tombstone question (R2-1), answered by the classifier above: does the
* branch carry a still-context-visible POSITIVE engine frame as its newest git frame? A newest
* TOMBSTONE means the disowning already happened (no re-spam); no frame at all means a genuinely
* fresh lane (no tombstone out of nowhere).
*/
export declare function branchCarriesVisiblePositiveGitFrame(branch: SessionTreeEntry[], mark?: string): boolean;