/** * `tm_ledger` — the LEDGER rule's landing spot on a host that has no `todowrite`. * * The lead's prompt mandates it: every new ask becomes a list item BEFORE the * work, an interruption is an insertion rather than a replacement, `blocked` is a * state and not an exit, and after a compaction the list is re-read before * anything else. On v1 that list is the host's own `todowrite`. On v2 a plugin * cannot create that tool (`V2_ONLY_ACTIONS` names `todowrite` as a key with no * v2 counterpart), so the mandate had no落点 — and the goal tripwire in `tm_join` * had nothing to check, which it now says out loud (`goal_unchecked reason=no_seam`). * * This module is the other answer. Storage is `ctx.storage` — the host's own * key/value domain, proven by a boot self-check that writes a marker and reads it * back. That choice beats the alternative (a file under the run store), which is * the user's work-in-progress sitting in a temp directory with no owner. Two facts * from a live 2.0.16 probe (`docs/research/agent-data-exchange.md`) shape this file: * · the domain is shared across sessions, agents AND projects — the only namespace * is the plugin id — so the session id is part of the key and nothing may be * written without one; * · there is no TTL and no quota, so a list that only ever grows is a leak with a * friendly name. That is why `LEDGER_MAX_ITEMS` refuses instead of promising a * cleanup mechanism this host does not offer. * * The honesty rule this module exists to obey (product goal #6): a reply about * the list says whether the list REACHED storage. An in-memory-only "已记录" * would be exactly the unverified "done" the rest of this plugin treats as a bug. */ import type { ToolDefinition } from "../types.js"; export declare const LEDGER_STATUSES: readonly ["open", "doing", "done", "blocked"]; export type LedgerStatus = (typeof LEDGER_STATUSES)[number]; export interface LedgerItem { id: number; text: string; status: LedgerStatus; /** epoch ms of the last change to THIS item */ at: number; /** why it is blocked / how it was closed — the part a later round cannot infer */ note?: string; } export interface Ledger { sessionID: string; items: LedgerItem[]; updated: number; } /** The persistence seam. v2 supplies the `ctx.storage` adapter; a test supplies * a Map. Nothing here may assume a shape the host did not answer with, so * `load` returns null for "nothing stored yet" and throws only for a real * failure — which the tool reports as a failure. */ export interface LedgerStore { readonly kind: string; available(): boolean; load(sessionID: string): Promise; save(ledger: Ledger): Promise; /** the last transport problem, in words — never a value the user stored */ detail?(): string; } export declare function createStorageLedgerStore(storage: unknown): LedgerStore; /** Defensive read: a value that is not a ledger is treated as absent rather than * half-trusted, because a silently-mangled list is worse than an empty one — * the lead would wrap up believing the work was accounted for. */ export declare function normalizeLedger(raw: unknown): Ledger | null; /** The dedup key. The LEDGER rule says an interruption INSERTS, and the same ask * arriving twice (a re-stated instruction, a screenshot of the same question) * must not grow a second line the lead then has to close twice — so the key is * the text with case/whitespace/trailing punctuation removed, which is the same * "same question" notion tm_memory's dedupKey uses. */ export declare function ledgerDedupKey(text: string): string; export declare function emptyLedger(sessionID: string): Ledger; /** `ctx.storage` never expires anything (measured), so the list needs a ceiling or * it grows for the life of the install. Refusing is honest; silently truncating * would be the overstated claim — the lead would believe its oldest asks were gone * when they were only hidden. */ export declare const LEDGER_MAX_ITEMS_DEFAULT = 200; export declare function ledgerMaxItems(config?: { ledgerMaxItems: number; }): number; /** A merge is reported, not hidden: the reply says "这条已经在清单上(#2)" so the * lead learns the insertion was a repeat instead of wondering why the count did * not move. */ export declare function addItem(ledger: Ledger, text: string, now?: number): { ledger: Ledger; item: LedgerItem; merged: boolean; }; export declare function openItems(ledger: Ledger | null | undefined): LedgerItem[]; /** Resolve `#4` / `4` / a substring, and refuse an ambiguous match by NAMING the * candidates — a list tool that silently marks the wrong line destroys the one * thing it is for. */ export declare function findItem(ledger: Ledger, ref: string | number): { item?: LedgerItem; candidates: LedgerItem[]; }; export declare function markItem(ledger: Ledger, ref: string | number, status: LedgerStatus, note?: string, now?: number): { ledger: Ledger; item: LedgerItem; } | { error: string; }; export declare function renderLedger(ledger: Ledger): string; /** The line `tm_join` appends when the round settled but the LEDGER did not — * the goal tripwire's shape applied to a list the plugin actually owns. */ export declare function ledgerGoalLine(ledger: Ledger | null | undefined, max?: number): string | null; export interface LedgerToolDeps { store: LedgerStore; /** The LEDGER is the lead's instrument (a specialist reports STATUS and answers * to the lead). This is the SECOND lock, and on the real host it is * UNREACHABLE: the request layer already removes `tm_ledger` from every * non-lead surface (`v2-session.ts` `toolsToRemove` pushes it whenever the * matrix denies `tm_join`, which every specialist's does), and the v2 * permission layer adds an explicit `tm_ledger: deny` triple for them * (`v2-permissions.ts`). A specialist therefore never sees the tool, and the * host answers `Unknown tool` if one is hallucinated — this branch only fires * in a unit test that calls `execute` directly. It stays as defence-in-depth * for a host that stops honouring the request-layer removal. */ onlyAgent?: string; /** Resolved from the personality's own config, so v1 and v2 can differ. */ config?: { ledgerMaxItems: number; }; /** Called after a successful write so the host (index.ts) can log the trajectory. */ onWrite?: (sessionID: string, event: string) => void; now?: () => number; } export declare function buildLedgerTool(deps: LedgerToolDeps): ToolDefinition; //# sourceMappingURL=ledger.d.ts.map