/** * tm_stats — read the plugin's own trajectory back (2026-09-19). * * Why: the project's whole justification is throughput ("if Team isn't * faster, Team has no point"), and until now every claim about it was * unfalsifiable — the run store and `steps.jsonl` were append-only outputs * nothing read back. This module turns that log into the two numbers that * actually decide the argument: * * 1. TOKENS kept out of the context window (offload events, measured by * the same CJK-aware estimator the offload decision used, net of the * preview that DID come back); * 2. SECONDS saved by overlap (dispatch children's own * durations summed against the wall-clock window they actually * occupied — serial cost minus parallel cost). * * Plus the governance and degrade counts a user needs to see whether the * guardrails are biting (clamped bash timeouts, web-cache hits, offload * degradation). Counters whose producer was deleted with the v1 personality * or the self-built browser are RETIRED rather than left rendering a 0 that * reads as "never blocked" — see the `governance` block below. * * Honesty rules baked in: the numbers are an ESTIMATE over the retained * trajectory (TTL-swept, run-per-process), the window is stated alongside * them, and the host-capability matrix rides the same reply so "what broke * after the upgrade" is answerable in one call. Read-only, no network, no * new surface: it reads files this plugin wrote. */ import type { ToolDefinition } from "../types.js"; import { type CapabilityRow } from "../capabilities.js"; import type { RunStore } from "./store.js"; /** One `steps.jsonl` line; unknown fields stay reachable. */ export interface TrajEvent { ts?: string; run_id?: string; tool?: string; step_id?: string; event?: string; [k: string]: unknown; } /** Torn/crashed tail lines are normal in an append-only log — skip, never throw. */ export declare function parseTrajectoryJsonl(text: string): TrajEvent[]; export interface RunFile { runId: string; file: string; mtimeMs: number; bytes: number; } /** Newest-first trajectory runs (one dir per plugin process). */ export declare function listTrajectoryRuns(trajectoryRoot: string, limit?: number): RunFile[]; export interface ToolStat { tool: string; calls: number; results: number; offloaded: number; tokens: number; savedTokens: number; } export interface TmStats { window: { runs: number; events: number; from?: number; to?: number; wallMs: number; }; tools: ToolStat[]; dispatch: { starts: number; settled: number; failed: number; adopted: number; /** Host `task` children tm_join claimed by explicit id (parentage checked). */ claims: number; cancelled: number; sumMs: number; maxMs: number; /** serial cost minus the wall-clock window the children really used. */ overlapSavedMs: number; /** Σ ms the LEAD spent blocked inside tm_join (the price of waiting). */ waitMs: number; waits: number; /** waits that followed a wait that settled nothing — the anti-pattern. */ repeatWaits: number; }; governance: { clampedTimeouts: number; clampSavedMs: number; offloadDegraded: number; /** URL-cache hits — every one is a fetch this round did NOT pay for. */ webCacheHits: number; }; /** `v2-cache` rows: the prompt-cache reading of the newest usage in that request. * `samples` counts only rows whose rate was computable — a row carrying `input` * but no `cache` object says nothing about caching and must not dilute the ratio * (it lands in `unavailable` instead, so the omission is visible). */ cache: { samples: number; unavailable: number; read: number; write: number; input: number; }; /** `tool:"host"` lines — which personality booted, what it could not do. * Without this the v2 boot record is written and never read back, so "the * plugin loaded" stays a claim the user cannot check from a session. */ boot: Array>; } export declare function summarizeEvents(events: readonly TrajEvent[]): TmStats; /** Newest-first host lines worth showing. A run appends several `v2-surface` * snapshots of itself (one at attach, then one whenever the counters move), and * the early ones are legitimately all-zero — showing them buries the line that * says what actually happened, which is how a working native offload read as a * dead tool. Per (run, step_id) the LAST snapshot supersedes the earlier ones. */ export declare function bootSnapshots(lines: ReadonlyArray>, limit?: number): Array>; /** A2: the sentence about roles the editor snapshot did not list — ONE definition, * printed by both places that used to paste "配置里缺角色". That phrase was a false * assertion: measured on 2.0.20 all six role files exist and the host runs them * (`--agent team` resolves, child sessions start), while `ctx.agent.transform` hands * the agent set from BEFORE the config directory merged. * * "已装" is a PER-ID observation, so the line is built per id. The first version only * required `agents_resolved_in_request` to be non-empty and then asserted "installed, * snapshot blind" for the WHOLE unseen list — but on a cold 2.0.20 boot the pre-merge * snapshot misses all six while the request has resolved just `team`, so five roles * nobody had ever observed were written up as a conclusion. Two strengths, never mixed * inside one sentence; when both sets are non-empty the line says them as two segments: * · unseen AND resolved in a request ⇒ installed, snapshot blind; * · unseen and never resolved anywhere ⇒ this process cannot tell the two apart, so * the segment names the deciding observation (that id appearing in * agents_resolved_in_request) instead of asserting either half; * · a legacy row (pre-A2 build, still inside the window) carrying `agents_missing` * ⇒ printed as what that field actually measured, not as a claim about files. */ export declare function agentVisibilityLine(line: Record): string; /** #49 Context Pruning: the counters the prune layer writes on the boot / surface / * shutdown rows. Rendered here because a counter that is recorded but never printed * is the exact defect this section exists to remove — and because the two zeroes a * reader must not confuse are "we looked and it was under the threshold" (没裁) and * "we could not even compute the threshold" (没得裁, `no_limit`). `pruned_tokens` is * OUR estimate (`estimateTokens`), never a number the host reported, and the line * says so out loud. */ export declare function pruneLine(line: Record): string; /** Markdown, in the shapes the host renders fastest (tables, not prose). */ export declare function renderStats(stats: TmStats, opts: { runDirs: number; roots: string[]; matrix?: CapabilityRow[]; now?: number; }): string; /** One row of the current run's offload index (`index.jsonl`). */ export interface OffloadRow { ref: string; /** `/.md`, relative to the run's steps dir. */ file: string; preview: string; tokens: number; expire_at: number; } export declare function parseOffloadIndex(text: string): OffloadRow[]; /** One governed call, newest-first, for the "what actually came back" recap. */ export interface RecentCall { ts: string; tool: string; stepId: string; tokens: number; offloaded: boolean; ref?: string; /** tokens that DID reach the context window (an offloaded call's preview) */ previewTokens?: number; /** absolute path of the payload file — the only place a human can read it */ file?: string; preview?: string; } /** Newest-first over `event:"result"` lines; `index` resolves refs to files. */ export declare function recentCalls(events: readonly TrajEvent[], limit: number, index: readonly OffloadRow[], stepsRoot: string): RecentCall[]; /** The recap the UI cannot give: every card a plugin tool renders is a one-line * row (the desktop's ToolRegistry holds only its own built-in names), so the * payload paths and previews have to be said out loud in the reply. */ export declare function renderRecent(calls: readonly RecentCall[]): string[]; export interface StatsDeps { store: RunStore; /** Live host-capability matrix (index.ts owns the probe). */ capabilities?: () => CapabilityRow[]; /** Injected for tests; defaults to reading the real trajectory store. */ readEvents?: (runs: readonly RunFile[]) => TrajEvent[]; } export declare function buildStatsTool(deps: StatsDeps): ToolDefinition; //# sourceMappingURL=stats.d.ts.map