/** * Graph Execution Engine v2 — Pure `graph_status` Filter/Query Helpers * * Version: 2.0 * Date: 2026-07-25 * * PURE, dependency-free filtering of an engine node set. These functions do * NOT read or mutate {@link EngineState} — they take a read-only node set * (`ReadonlyMap`) and return a filtered array of * {@link NodeRuntimeState}. They are wired into the `graph_status` renderers * in `graph-tools.ts` (which calls this module) and are independently unit * tested in `tests/graph/graph-status-filters.test.ts`. * * ## Filters (AND-combined when more than one is supplied) * * - `query` — case-insensitive substring match on nodeId / prompt / agent. * - `status` — exact {@link NodeStatus} match (canonical lowercase values * from `src/constants.ts` `NodeStatus`). * - `agent` — exact agent match. * - `from_date` / `to_date` — ISO-8601 window on node timestamps. * * ## Date-window semantics (honest, never fabricated) * * - `from_date` (ISO) — include a node when `startedAt >= from` (epoch ms). * - `to_date` (ISO) — include a node ONLY when it has a `completedAt` and * `completedAt <= to`. A node with no `completedAt` (still * pending/ready/running) does NOT match a `to_date` bound — * there is no completion timestamp to compare, so claiming * it "completed within the window" would fabricate data. * - When both bounds are given, a node matches only if it has BOTH a * `startedAt >= from` AND a `completedAt <= to` (i.e. it completed within * the window). Invalid ISO strings throw a descriptive {@link Error} — a * garbage date can never silently broaden or narrow a result set. * * All filtering here is pure: `filterNodes` returns an honest subset of the * input; a no-match filter yields an empty array, never invented rows. * * ## View flags (subtask 3 — appended additively, nothing above is rewritten) * * - `limitNodes` — cap a node-row list at `limit` rows (summary/json). * - `groupCompletedNodes` — bucket COMPLETED nodes over their `completedAt` * by `hour` / `day` / `agent`. Uncompleted nodes (no `completedAt`, or a * status other than completed) are excluded honestly — never bucketed into * an invented slot. Each bucket carries a genuine `nodes` id list. * - `depth` — a pure tree concern (cutoff at N levels); the render * wiring lives in `graph-tools.ts`. `limitNodes` returns the input array * unchanged when `limit` is unset, and `groupCompletedNodes` is a distinct * view mode — so default summary/json/tree output is byte-identical when * the new flags are unset. * * Data source: {@link NodeRuntimeState} (`src/types.engine-v2.ts:72-132`) * carries nodeId / prompt / agent / status / startedAt / completedAt directly. */ import { NodeStatus } from "../../constants.ts"; import type { EngineState, NodeRuntimeState } from "../../types.engine-v2.ts"; /** * The `graph_status` filter surface. Every field is optional — a filter is * active only when its value is supplied (`undefined` = not applied). */ export interface StatusQuery { /** Case-insensitive substring match on nodeId / prompt / agent. */ query?: string; /** Exact {@link NodeStatus} match (canonical lowercase value). */ status?: NodeStatus; /** Exact agent match. */ agent?: string; /** ISO-8601 window lower bound (node.startedAt >= from). */ from_date?: string; /** ISO-8601 window upper bound (node.completedAt <= to, when completed). */ to_date?: string; } /** * A `signalsObserved` ledger entry that is present. The shared `getSignal` * seam (contract C2) takes a type predicate, and a stored `undefined` is * indistinguishable from an absent key — so this is exactly the "recorded at * all" test the ad-hoc `signalsObserved?.[key]` reads used to spell out (Y8), * now shared by every tools-layer ledger read. */ export declare function isRecordedSignal(value: unknown): value is unknown; /** Parse an ISO-8601 string to epoch ms, or throw on an invalid value. */ export declare function toEpochMs(iso: string): number; /** Case-insensitive substring match on nodeId / prompt / agent. */ export declare function filterByQuery(nodes: ReadonlyMap, query: string): NodeRuntimeState[]; /** Exact {@link NodeStatus} match. */ export declare function filterByStatus(nodes: ReadonlyMap, status: NodeStatus): NodeRuntimeState[]; /** Exact agent match. */ export declare function filterByAgent(nodes: ReadonlyMap, agent: string): NodeRuntimeState[]; /** * Date-window filter. `from_date` / `to_date` are ISO strings; either may be * omitted. A node matches only when it has the data to answer the bound (see * the module header for the honest semantics). */ export declare function filterByDateWindow(nodes: ReadonlyMap, from_date?: string, to_date?: string): NodeRuntimeState[]; /** * Apply every supplied filter to the node set (AND-combined). Returns an * honest subset of the input — an empty array when nothing matches, never * fabricated rows. When no filter field is present, returns all nodes. * * Each active predicate is evaluated exactly once per node and the working set * is narrowed in place; the cheapest predicate (the substring query) is * composed first so `every` short-circuits on it. The old implementation * recomputed every enabled filter over the FULL node set, built an id `Set` * per filter, and intersected — semantically identical, needlessly quadratic. */ export declare function filterNodes(nodes: ReadonlyMap, query: StatusQuery): NodeRuntimeState[]; /** The `group_by` aggregation mode for completed-node bucketing. */ export type GroupByMode = "hour" | "day" | "agent"; /** A completed-node bucket produced by {@link groupCompletedNodes}. */ export interface GroupBucket { /** Bucket key: an ISO hour (`YYYY-MM-DDTHH:00:00.000Z`), an ISO date * (`YYYY-MM-DD`), or the agent id for the `agent` mode. */ key: string; /** Number of completed nodes in this bucket. */ count: number; /** Node ids in this bucket — genuine, derived from real `completedAt` data. */ nodes: string[]; } /** * Cap a node-row list at `limit` rows for summary/json rendering. A `limit` of * `undefined` or `<= 0` leaves the list untouched (unbounded — the default), so * existing output is byte-identical when `limit` is unset. Never reorders. */ export declare function limitNodes(nodes: ReadonlyArray, limit?: number): NodeRuntimeState[]; /** * Bucket COMPLETED nodes by `hour` / `day` / `agent` over their `completedAt` * timestamp, returning the bucket list with counts. Honesty contract: * * - Only nodes with `status === Completed` **and** a `completedAt` timestamp * are included — an uncompleted node (pending/ready/running/blocked, or any * node with no completion timestamp) is excluded, never bucketed into an * invented slot. * - Buckets are sorted by key (ISO strings sort lexicographically in UTC; the * `agent` mode sorts by agent id). When no completed node exists, an empty * bucket list is returned — never a fabricated row. */ export declare function groupCompletedNodes(nodes: ReadonlyMap, mode: GroupByMode): GroupBucket[]; /** * A single node awaiting a human approval decision. Backs the `graph_status` * `pending_approvals` view flag (and is independently importable for subtasks * that need to enumerate gated nodes — e.g. a batch-approve round). */ export interface PendingApprovalEntry { /** Owning graph id. */ graphId: string; /** The `needs_approval` node currently `blocked`. */ nodeId: string; /** Agent bound to the node. */ agent: string; /** * Epoch ms when the node entered the blocked state. Sourced from the stashed * `approval_payload.timestamp` (ISO → ms) when present; otherwise falls back * to the node's `startedAt`. Absent only when neither timestamp exists. */ blockedSince?: number; /** * Truncated summary of the node's stashed `signalsObserved["approval_payload"]` * (JSON-serialized, then capped at `summaryLimit` chars). Absent when the node * has no approval_payload stash. Never a fabricated value. */ approvalPayloadSummary?: string; /** A paste-ready `graph_approve` call to accumulate the approval. */ approveCall: string; } /** Options for {@link listPendingApprovals}. */ export interface ListPendingApprovalsOptions { /** Truncation limit for the approval_payload summary (`approvalPayloadSummary`). * Default 200 chars. */ summaryLimit?: number; } /** * Enumerate every node currently awaiting a human approval decision across the * supplied engine states. * * A "pending approval" is a node with BOTH `status === Blocked` AND * `needsApproval === true` — the exact gate the `graph_approve` tool resolves. * Pure function over a read-only state set: registry states, persisted states, * or a merged set can all be passed in. Honesty contract: * * - Every field is sourced from real recorded state — never fabricated. * - An empty input yields an empty list, never an invented row. * - Iteration order follows the input array order (caller controls * registry-first / persisted-first merge via the array it builds). * * @param states Read-only engine states to scan. * @param opts Optional summary-length cap. * @returns The pending-approval entries, in input order. */ export declare function listPendingApprovals(states: ReadonlyArray, opts?: ListPendingApprovalsOptions): PendingApprovalEntry[]; //# sourceMappingURL=status-queries.d.ts.map