import type { SessionContext, TestRunStatus } from "@alwaysmeticulous/api"; import type { MeticulousClient } from "../types/client.types"; /** * The review decision recorded for a difference on its PR. `unreviewed` means no * decision (or no PR); `accepted`/`rejected`/`ignored` are the explicit verdicts. */ export type DiffDecisionState = "accepted" | "rejected" | "ignored" | "unreviewed"; export interface DiffsSummaryScreenshot { screenshotName: string; /** * Global 1-based rank across all differences. By default the selection-priority * order; with `orderByReplayDiffs` it's re-sequenced so a replay diff's * differences are consecutive. */ index: number; outcome: string; userVisibleOutcome: string; mismatchFraction: number | null; /** Present only when `includeDomDiffIds` is set. */ domDiffIds?: string; /** * Whether this screenshot is part of the selected representative subset. * Present only when `includeAllDiffs` is set — otherwise the response * already contains only selected screenshots. */ isSelected?: boolean; /** * The review decision for this difference on its PR. Present only when * `includeReviewDecisions` is set. `unreviewed` when there's no decision or no PR. */ decision?: DiffDecisionState; } export interface DiffsSummaryReplayDiff { replayDiffId: string; baseReplayId?: string; headReplayId?: string; screenshots: DiffsSummaryScreenshot[]; } export interface DiffsSummaryOptions { includeReplayIds?: boolean; /** Include the `domDiffIds` field on each screenshot. Default false. */ includeDomDiffIds?: boolean; /** * Return every diff rather than only the pre-selected representative subset. * When true, `isSelected` marks which screenshots are in the selected subset. */ includeAllDiffs?: boolean; /** * Assign the global `index` in replay-diff-grouped order (a replay diff's * differences get consecutive indices) instead of priority order. */ orderByReplayDiffs?: boolean; /** * Include the `decision` field (the PR review decision) on each screenshot. * Default false. Resolved against the test run's PR at request time. */ includeReviewDecisions?: boolean; /** * Return only the differences still awaiting review (decision `unreviewed`), * across every difference rather than just the selected representative subset — * i.e. everything a reviewer still has to act on. Default false. */ onlyUnreviewed?: boolean; /** * Request a fresh computation when the previous attempt failed. A normal poll * returns `status: "failed"` without restarting; pass this to start a clean * run over the failed one. Off by default. A no-op if no computation has ever * been triggered for this test run — that case already starts one regardless. */ retrigger?: boolean; } /** * Aggregate counts for a test run's diffs, computed live from the backend (no * diffs-summary computation needed). The decision buckets partition the * deduplicated differences: `numApproved + numIgnored + numRejected + * numUnreviewed === numDiffs`. */ export interface DiffsSummaryCountsResponse { /** Executed replay comparisons (excludes RSE-skipped / carried-forward and hidden). */ numReplays: number; /** Deduplicated user-visible differences (one per `effectiveDiffHash`). */ numDiffs: number; numApproved: number; numIgnored: number; numRejected: number; numUnreviewed: number; } /** The terminal Temporal workflow status that produced a `failed` response. */ export type DiffsSummaryFailureReason = "FAILED" | "TIMED_OUT" | "TERMINATED" | "CANCELLED"; export interface DiffsSummaryResponse { /** * `pending` / `processing` — poll again; `complete` — `data` is populated; * `failed` — the last computation failed and left no result (poll again with * `retrigger` to try a fresh run). */ status: "pending" | "processing" | "complete" | "failed"; data?: DiffsSummaryReplayDiff[]; /** Present only when `status` is `failed`. */ reason?: DiffsSummaryFailureReason; } /** * The agent diffs-summary API contract version this client speaks. Sent on * every request so the backend can apply version-appropriate defaults; older * backends ignore it. Bump when the client adopts a new default contract. * * - v1 (no clientVersion sent): behaves as if `--includeDomDiffIds` and * `--includeAllDiffs` were always on — the full set of diffs including * `domDiffIds`. This is the implicit behaviour for pre-versioning clients. * - v2: introduces `--includeDomDiffIds` / `--includeAllDiffs` as opt-in flags * (default off), so the response defaults to the curated selected subset * with `domDiffIds` omitted. */ export declare const DIFFS_SUMMARY_CLIENT_VERSION = 2; export interface ScreenshotDomDiffResponse { diffs: Array<{ index: number; content: string; }>; totalDiffs: number; } export type CompactRange = [startLineInc: number, endLineInc: number]; export type FileWithCompactRanges = [filePath: string, ranges: CompactRange[]]; export interface TestRunForCommitResponse { /** * The id of the most recent user-visible test run for the commit, including * one still in progress (`ExecutionError`/`Aborted` runs are skipped), or * `null` if the project has no such run. */ testRunId: string | null; /** * The matched run's status (`null` iff `testRunId` is null). An in-progress * status lets the caller decide whether to wait for the run to finish. */ status: TestRunStatus | null; } export interface TestRunJsCoverageResponse { /** * Executed line ranges per file across the whole test run, keyed by * repo-relative path (from the precomputed, repo-mapped coverage.json). */ files: FileWithCompactRanges[]; } /** * The agent test-run js-coverage API contract version this client speaks. Sent * on every request so the backend knows to serve the V2 per-file response * ({@link TestRunJsCoverageResponseV2}); older backends that don't * understand it fall back to the legacy {@link TestRunJsCoverageResponse}, and * pre-versioning clients (which send nothing) still get the legacy shape. * * - v1 (no clientVersion sent): legacy `files: [path, executedRanges][]`, * including files with no executed coverage. * - v2: per-file objects carrying only the requested columns * (executed/executable/uncovered ranges, coverage percentage), files with no * value in any requested column dropped unless `includeAllFiles`, plus * `prDiffOnly`/`globFilter`. At least one column must be requested. */ export declare const TESTRUN_JS_COVERAGE_CLIENT_VERSION = 2; /** Which columns/rows the V2 test-run coverage response should carry. */ export interface TestRunJsCoverageOptions { /** * Return every file regardless of the requested columns (otherwise a file is * dropped unless a requested column has a value for it). */ includeAllFiles?: boolean; /** Keep only repo file paths matching this gitignore-style glob. */ globFilter?: string; includeExecutedRanges?: boolean; includeExecutableRanges?: boolean; includeUncoveredRanges?: boolean; includeCoveragePercentage?: boolean; /** Scope coverage to the PR diff (coverage.pr.json) instead of the whole run. */ prDiffOnly?: boolean; /** * Additional test run IDs whose coverage is unioned with `testRunId`'s — * e.g. to show a run's normal coverage plus the coverage of a few extra * runs. Must belong to the same project as `testRunId`. */ unionTestRunIds?: string[]; } /** * A per-file row in the V2 test-run coverage response. `repoFilePath` is * always present; each other field is included only when the caller opted into * it, in this declaration order. Ranges are repo-relative and normalized. */ export interface TestRunCoverageFile { repoFilePath: string; executedRanges?: CompactRange[]; /** Statically-executable lines unioned with executed lines (executed ⊆ executable). */ executableRanges?: CompactRange[]; /** executable − executed. */ uncoveredRanges?: CompactRange[]; /** `100 × |executed| / |executable|`, in 0–100; `null` when no executable lines. */ coveragePercentage?: number | null; } export interface TestRunJsCoverageResponseV2 { files: TestRunCoverageFile[]; } /** * Whether `executedRanges` should be requested/printed when the caller didn't * explicitly ask for it — true unless another column flag was explicitly set, * preserving the historical default of executed ranges for a bare invocation. * Shared by {@link getTestRunJsCoverage}, {@link getProjectJsCoverage}, and the * CLI's `determineColumns` (`public_packages/cli/src/commands/agent/coverage-columns.util.ts`). */ export declare const shouldDefaultToExecutedRanges: (columnFlags: Pick) => boolean; /** * Which columns/rows the project coverage response should carry. A subset of * {@link TestRunJsCoverageOptions}: there is no `prDiffOnly` (a project has no * PR) and no `unionTestRunIds` (the run is resolved server-side). OAuth users * may pass `project` to override their configured default project; project API * tokens derive the project from the token. */ export interface ProjectJsCoverageOptions { project?: string; includeAllFiles?: boolean; globFilter?: string; includeExecutedRanges?: boolean; includeExecutableRanges?: boolean; includeUncoveredRanges?: boolean; includeCoveragePercentage?: boolean; } export interface ProjectJsCoverageResponse { /** * The test run the project's coverage was resolved to (the project's latest * successful run — the same one the webapp's project coverage view uses), or * `null` when the project has no such run. `files` is empty when `null`. */ testRunId: string | null; files: TestRunCoverageFile[]; } export interface ReplayJsCoverageResponse { /** * Executed line ranges for a single replay (whole replay, or one screenshot), * keyed by repo-relative path. Source-map paths that don't resolve to a repo * file are dropped. `null` only when a specific screenshot has no coverage. */ files: FileWithCompactRanges[] | null; } export interface CoverageFileDiff { /** Repo-relative file path. */ filePath: string; status: "added" | "removed" | "modified"; baseRanges: CompactRange[]; headRanges: CompactRange[]; } export interface ReplayDiffJsCoverageDiffResponse { /** * Base/head executed line ranges and their diff, all keyed by repo-relative * path. Source-map paths that don't resolve to a repo file (e.g. a file * deleted at head, or third-party code) are dropped. */ base: FileWithCompactRanges[] | null; head: FileWithCompactRanges[] | null; /** * Per-file coverage diff, computed over base/head *before* empty rows are * dropped, whereas the returned `base`/`head` arrays drop files with no * executed ranges unless `includeAllFiles`. So a `diff` entry can reference a * file absent from `base`/`head` (e.g. a file executed only on head is * `added` in `diff` but its empty base row is dropped from `base`); don't * assume every `diff.filePath` is present in both arrays. */ diff: CoverageFileDiff[]; } export interface ScreenshotUrlsResponse { outcome: string; before?: string; after?: string; /** * @deprecated Superseded by `before`/`after`. Still populated (mirroring * whichever of `before`/`after` is set) for `missing-base`/`missing-head` * outcomes only, so already-published CLI versions reading this field keep * working. New consumers should use `before`/`after` instead. */ screenshot?: string; diffImage?: string; } export interface TimelineDiffEntry { status: "identical" | "removed" | "added" | "changed"; timeMs: number; eventKind: string; description: string; mismatchFraction?: number | null; } export interface TimelineDiffResponse { baseReplayId: string; headReplayId: string; entries: TimelineDiffEntry[]; } export interface StructuredSessionSummary { sessionId: string; startUrl: string; viewport?: { width: number; height: number; }; eventCount: number; totalDurationMs: number; networkRequestCount: number; pageNavigations: string[]; } export interface StructuredUserEvent { index: number; type: string; selector: string; timestampMs: number; coordinates?: { x: number; y: number; }; } export interface NetworkRequestSummaryEntry { order: number; method: string; url: string; status: number; contentType: string | null; timeMs: number; } export interface NetworkRequestEntry { order: number; startedDateTime: string; request: { method: string; url: string; headers: Array<{ name: string; value: string; }>; queryString: Array<{ name: string; value: string; }>; postData?: { mimeType: string; text?: string; }; }; response: { status: number; headers: Array<{ name: string; value: string; }>; content: { mimeType: string; text?: string; encoding?: string; }; }; timeMs: number; } export interface SessionStorageSnapshot { cookies: Array<{ name: string; domain: string | null; path?: string; sameSite?: string; secure?: boolean; httpOnly?: boolean; }>; localStorage: Array<{ key: string; value: string; }>; sessionStorage?: Array<{ key: string; value: string; }>; indexedDb?: Array<{ databaseName: string; objectStoreName: string; entryCount: number; }>; } export interface UrlHistoryEntry { timestampMs: number; url: string; urlPattern?: string; } export interface WebSocketSummaryEntry { connectionId: number; url: string; eventCount: number; } export interface StructuredSessionDataResponse { summary: StructuredSessionSummary; userEvents: StructuredUserEvent[]; networkRequests: { summary: NetworkRequestSummaryEntry[]; entries: NetworkRequestEntry[]; }; storage: SessionStorageSnapshot; urlHistory: UrlHistoryEntry[]; context: SessionContext | null; webSockets?: { summary: WebSocketSummaryEntry[]; connections: Array<{ connectionId: number; url: string; events: unknown[]; }>; }; } export type AgentFeature = "debug-replay-diff" | "debug-replay"; export type AgentFeedbackOutcome = "helped" | "neutral" | "hindered"; export interface AgentFeedbackResponse { feedbackId: string; } export declare const trackAgentFeatureUsage: ({ client, feature, project, }: { client: MeticulousClient; feature: AgentFeature; project: string | undefined; }) => Promise; /** * Submit free-form feedback about Meticulous to the Meticulous team. Unlike * telemetry, errors propagate to the caller instead of being swallowed. */ export declare const submitAgentFeedback: ({ client, message, outcome, testRunId, skill, agentName, agentModel, project, }: { client: MeticulousClient; message: string; outcome?: AgentFeedbackOutcome | undefined; testRunId?: string | undefined; skill?: string | undefined; agentName?: string | undefined; agentModel?: string | undefined; project?: string | undefined; }) => Promise; export declare const getTestRunDiffsSummary: (client: MeticulousClient, testRunId: string, options?: DiffsSummaryOptions) => Promise; export declare const getTestRunDiffsSummaryCounts: (client: MeticulousClient, testRunId: string) => Promise; export declare const getScreenshotDomDiff: (client: MeticulousClient, replayDiffId: string, screenshotName: string, context?: string) => Promise; export declare const getTestRunForCommit: (client: MeticulousClient, commitSha: string, options?: { project?: string | undefined; }) => Promise; export declare const getTestRunJsCoverage: (client: MeticulousClient, testRunId: string, options?: TestRunJsCoverageOptions) => Promise; export declare const getProjectJsCoverage: (client: MeticulousClient, options?: ProjectJsCoverageOptions) => Promise; export declare const getReplayJsCoverage: (client: MeticulousClient, replayId: string, screenshotName?: string, options?: { testRunId?: string | undefined; includeAllFiles?: boolean | undefined; globFilter?: string | undefined; }) => Promise; export declare const getReplayDiffJsCoverage: (client: MeticulousClient, replayDiffId: string, screenshotName?: string, options?: { includeAllFiles?: boolean | undefined; globFilter?: string | undefined; }) => Promise; export declare const getScreenshotUrls: (client: MeticulousClient, replayDiffId: string, screenshotName: string) => Promise; export declare const getTimelineDiff: (client: MeticulousClient, replayDiffId: string) => Promise; export declare const getStructuredSessionData: (client: MeticulousClient, sessionId: string) => Promise; /** * How a session's row came to exist, derived from its id suffix. Mirrors * webapp-backend's `AgentSessionStatus` (agent.types.ts) — kept as a literal * union here rather than imported, since `public_packages` can't depend on * webapp-backend. */ export type SessionStatus = "original" | "patched" | "sliced" | "mutated"; /** * Why the recorder gave up on a session before it finished normally. Mirrors * `SessionAbandonmentReasonEnum` in `packages/session-payload-api` (a private * package `public_packages` can't depend on) — keep the two in sync. */ export type SessionAbandonmentReason = "payload_size" | "max_uploads" | "max_session_time" | "user_requested" | "error_creating_payload" | "superseded_by_native_recorder"; /** One row of the recent-sessions listing. */ export interface SessionListItem { id: string; /** * When this session entry was created, as an ISO-8601 string — the stored * row timestamp and the basis of the newest-first ordering. Equal to * `recordedAt` for an original session; for a non-original session it's * when that row itself was produced, so it can be later than `recordedAt`. */ createdAt: string; /** * When the session was originally recorded, as an ISO-8601 string. For a * non-original session (patched, sliced, or mutated) this is the root * session's recording time; otherwise it equals `createdAt`. */ recordedAt: string; /** * The identity that recorded the session (email, falling back to a user * id). Omitted if neither was set. */ recordedBy?: string; /** * How this session's row came to exist. Omitted when * `excludeSyntheticSessions` is set (every row is then `original`). */ status?: SessionStatus; /** The session's start URL. Included only when `includeStartUrl` is set. */ startUrl?: string; /** * The reason the recorder gave up on the session before it finished normally * (meaning the recording is incomplete). Included only when * `includeAbandonedReason` is set, and then only for abandoned sessions. */ abandonedReason?: SessionAbandonmentReason; } export interface SessionsResponse { /** The project's most recently recorded sessions, newest first. */ sessions: SessionListItem[]; } export declare const getSessions: (client: MeticulousClient, options?: { project?: string | undefined; createdSince?: string | undefined; createdUntil?: string | undefined; recordedSince?: string | undefined; recordedUntil?: string | undefined; recordedBy?: string | undefined; excludeSyntheticSessions?: boolean | undefined; visitedUrlFilter?: string | undefined; includeStartUrl?: boolean | undefined; includeAbandonedReason?: boolean | undefined; limit?: number | undefined; offset?: number | undefined; }) => Promise; //# sourceMappingURL=agent.api.d.ts.map