/** * Host half of the panel-snapshot channel: the `mstar/panelSnapshot` endpoint * on the host's SHARED `/api` typert gateway. * * WHY its own service: the panel's read is the store-backed one, while the * `mstar` engine-status gateway serves the last MODEL EMISSION (retired as a * read path). Two cordis services can share the `mstar` WIRE namespace — a * descriptor names its own service key — so this half is independently * installable and withdrawable, and neither retirement can take the other's * endpoint with it. * * VALIDATION CHAIN — mirrored from `engine-status-endpoint.ts` (the single * source of this order), answered entirely BEFORE any store read: * 1. argument shape (non-empty session id, an absolute traversal-free `cwd`, * and, when present, single-safe workflow/iteration/project ids); * 2. the session is resolved SERVER-side — the live `ctx.sessions.get(id)` * first, then the session controller's persisted `inspect(id)` fallback; * 3. the `{HARNESS_DIR}` comes from the SERVER-side session cwd (or the * boot-resolved config root) — never from client-asserted input; * 4. the client-asserted `cwd` must EQUAL the resolved session cwd. * Only then does `readPanelSnapshot` touch the store, and its own degradation * is returned as a value: this endpoint never throws a transport error for a * store that refuses. * * TARGETS ARE NOT THE CLIENT'S JOB: the browser holds `{ sessionId, cwd }` and * nothing else, so an omitted `workflowId` / `iterationId` / `projectId` is * RESOLVED server-side inside `readPanelSnapshot` (the session's durable pick, * then the harness catalog). An asserted id is still shape-validated here and * still read against the server-resolved harness root — a client claim can * never redirect the read to another workspace. * * A WORKSPACE WITH NO HARNESS ANSWERS `no-harness`: that is a state of the * workspace, not a failed read, so it travels as its own kind instead of an * `unavailable` token the panel would have to decode. * * @module @mstar-harness/dsh/panel/panel-snapshot-endpoint */ import type { Context } from '@deepseek-ai/cordis'; import type { TypertContribution } from '@deepseek-ai/dsh-typert-registry'; import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'; import type { HarnessResolver } from '../gates/_shared.ts'; import { type ReadEnvelope } from '@mstar-harness/engine'; import type { PanelSnapshotRead } from './panel-snapshot.ts'; import { type PanelRetainedReader } from './read-panel-snapshot.ts'; /** * One per-view read's answer. The engine's own envelope passes through * unmodified (`data` may be `null` for a detail view that resolves to nothing — * that is an answer, not a failure), and the two non-answers keep their own * kinds so the client never decodes a wire token. */ export type DashboardViewRead = { readonly kind: 'ok'; readonly envelope: ReadEnvelope; } | { readonly kind: 'no-harness'; } | { readonly kind: 'unavailable'; readonly reason: string; }; /** * The cordis service key of this half. Deliberately NOT the wire namespace: * the `mstar` key already belongs to the engine-status gateway, and a second * provider under the same key would collide. */ export declare const PANEL_SNAPSHOT_SERVICE = "mstarPanelSnapshot"; /** Options the endpoint needs from the plugin's apply scope. */ export interface PanelSnapshotEndpointOptions { /** The per-workspace `{HARNESS_DIR}` resolver (the same one the gates use). */ readonly resolver: Pick; /** The boot-resolved config root when an explicit `harnessDir` is configured. */ readonly bootHarnessDir: string | null; /** * The apply-scoped catalog reader for the `retained` facts. Optional: a host * without the catalog wiring answers `retained: null` and the snapshot stays * `ok`, so the panel never loses its workspace body to a missing catalog. */ readonly readRetained?: PanelRetainedReader; } /** * Host-side `mstar/panelSnapshot` service: `/api/mstar/panelSnapshot` serves the * composed store-backed snapshot of the session the caller asserts. */ export declare class MstarPanelSnapshotGateway extends TypertRemoteService { private readonly resolver; private readonly bootHarnessDir; private readonly readRetained; /** * @param ctx - owning Cordis context. * @param options - the apply-scoped resolver + boot root. */ constructor(ctx: Context, options: PanelSnapshotEndpointOptions); /** * Serve one session's composed panel snapshot after the validation chain * documented in the module header. * @param sessionId - the session whose snapshot is requested (client-asserted). * @param cwd - the session workspace the client believes it is reading (client-asserted). * @param workflowId - an asserted workflow id; resolved from the session's * durable pick when omitted (the browser never supplies one). * @param iterationId - an asserted iteration id; resolved from the harness * catalog when omitted. * @param projectId - an asserted project id; resolved from the harness catalog * (`_default`, else the single project) when omitted. * @returns the snapshot, the `no-harness` kind, or the explicit unavailable * state with a reason (`aborted` when the carrier cancelled). */ panelSnapshot(sessionId: string, cwd: string, workflowId?: string, iterationId?: string, projectId?: string): Promise; /** * Serve ONE engine dashboard view for a session — the per-view read path the * screens use (plan dsh-panel-unification Task 4a). It rides the same typert * contribution and the same server-side validation chain as * {@link panelSnapshot}: the client asserts its session and workspace, never * the ids or the store. * * @param sessionId - the session whose workspace is read (client-asserted). * @param cwd - the workspace the client believes it is reading (client-asserted, * and required to equal the server-resolved session cwd). * @param view - the engine `DashboardView` name (`workflow-detail`, `iterations`, …). * @param params - the view's own query parameters, strings only. * @param id - the detail record id, for the three detail views. * @returns the engine read envelope, the `no-harness` kind, or an explicit reason. */ dashboardView(sessionId: string, cwd: string, view: string, params?: Record, id?: string): Promise; /** * The shared argument chain: shape → server-resolved session → store root → * cwd equality. Both invocations take it, so a second read path cannot drift * from the first one's trust rules. */ private resolveTarget; /** * The carrier cancellation of THIS call, when the gateway is serving one. * `ctx.invocation` is set by the typert gateway for the duration of a Remote * call and absent otherwise (an in-process caller), so this reads the real * client timeout / socket close rather than a second timeout of our own. */ private carrierSignal; /** The validation chain itself (contained by {@link panelSnapshot}). */ private serve; /** * Resolve the authoritative cwd of one session: the LIVE session first * (`ctx.sessions.get(id)`), then the session controller's persisted * `inspect(id)` fallback for a session that is not currently attached. */ private resolveSessionCwd; } /** * The ONE invocation descriptor this unit contributes: `mstar/panelSnapshot` * with five plain-object argument wires. Optional ids legitimately absent * (`src-json` codecs accept a missing wire field), so the panel can ask for the * workspace body without a selection. * * The descriptor is CARRIED by `mstarEngineStatusContribution()`: typert admits * exactly ONE contribution per `#`, so the package's single host * contribution lists it beside its own — see * {@link installPanelSnapshotEndpoint} for the constraint. */ export declare function mstarPanelSnapshotInvocation(): TypertContribution['invocations'][number]; /** * The per-view invocation descriptor: `mstar/dashboardView` with the same * session/cwd assertion as the snapshot read, the view name, its query * parameters and the detail id. Carried by `mstarEngineStatusContribution()` * for the same one-contribution-per-face reason as the snapshot descriptor. */ export declare function mstarDashboardViewInvocation(): TypertContribution['invocations'][number]; /** * Install the host half of the channel on one apply scope: just the cordis * service. * * The DESCRIPTOR is not registered here. typert admits exactly ONE * contribution per `#`, and this package's `host` face belongs * to `mstarEngineStatusContribution()`, which carries * {@link mstarPanelSnapshotInvocation} in its own list (the engine-status * module imports it). A second contribution from this module would be refused * at boot and leave the endpoint silently unserved. * * The present-check is the dedupe: a sibling apply / HMR re-apply keeps the * FIRST instance with its own resolver and boot root, exactly like the * engine-status gateway beside it. * @param ctx - the plugin's apply context. * @param options - the apply-scoped resolver + boot root. */ export declare function installPanelSnapshotEndpoint(ctx: Context, options: PanelSnapshotEndpointOptions): void;