{"version":3,"file":"ActivityOverlay.d.ts","sourceRoot":"","sources":["../../src/components/ActivityOverlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,KAAK,CAAC;AAC7E,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAErE;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;CACd;AAED,4DAA4D;AAC5D,MAAM,WAAW,uBAAuB;IACvC,8EAA8E;IAC9E,IAAI,IAAI,gBAAgB,EAAE,CAAC;IAC3B,6DAA6D;IAC7D,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC5C;AAED,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,EAAE,uBAGpC,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACjC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACjC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAChC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACjC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAChC;AAWD,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAuE;IACvE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAMD,qBAAa,eAAgB,YAAW,SAAS;IAChD,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,OAAO,CAAC,MAAM,CAAC,CAAa;IAE5B,YAAY,OAAO,GAAE,sBAA2B,EAK/C;IAED,0EAA0E;IAC1E,WAAW,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI,CAInD;IAED,yEAAyE;IACzE,UAAU,CAAC,MAAM,EAAE,MAAM,IAAI,GAAG,IAAI,CAKnC;IAED,OAAO,IAAI,IAAI,CAId;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA+B9B;IAED,OAAO,CAAC,UAAU;IAQlB;;;OAGG;IACH,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,KAAK;IAab,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,QAAQ;CAKhB","sourcesContent":["/**\n * Activity monitor overlay (F2).\n *\n * Renders a flat, live list of the session's running/queued/finished\n * activities — model calls, tools, foreground subagents — with elapsed time\n * and stalled detection. The blocking leaf (the row that is actually holding\n * up the reply) is marked so the user can see *why* a turn is slow.\n *\n * This package is LOW-level and must not import from `coding-agent`. The\n * overlay therefore defines its OWN structural snapshot + registry interfaces.\n * The real `ActivityRegistry` (in coding-agent) returns objects that are\n * structurally compatible with {@link ActivitySnapshot}; interactive-mode\n * adapts them.\n */\nimport { formatElapsed } from \"../format-elapsed.js\";\nimport type { Component } from \"../tui.js\";\nimport { truncateToWidth, visibleWidth } from \"../utils.js\";\n\nexport { formatElapsed };\n\nexport type ActivityKind = \"model\" | \"tool\" | \"subagent\" | \"process\" | \"mcp\";\nexport type ActivityStatus = \"running\" | \"queued\" | \"done\" | \"error\";\n\n/**\n * Structural snapshot of a single activity. Mirrors the shape returned by the\n * coding-agent `ActivityRegistry.list()` so the two never share a type import.\n */\nexport interface ActivitySnapshot {\n\tid: string;\n\tkind: ActivityKind;\n\tlabel: string;\n\tdetail?: string;\n\tstatus: ActivityStatus;\n\tstartedAt: number;\n\t/** now - startedAt (or endedAt - startedAt once finished). */\n\telapsedMs: number;\n\t/** now - lastProgressAt; 0 when never progress-tracked or just progressed. */\n\tstalledMs: number;\n\t/** Nesting depth for subagent/tool trees. */\n\tdepth: number;\n}\n\n/** Structural registry interface the overlay reads from. */\nexport interface ActivityOverlayRegistry {\n\t/** Current snapshot of every tracked activity, pre-sorted by the registry. */\n\tlist(): ActivitySnapshot[];\n\t/** Subscribe to changes. Returns an unsubscribe function. */\n\tsubscribe(listener: () => void): () => void;\n}\n\n/** Empty registry used before the session wires a real one. */\nexport const NULL_ACTIVITY_REGISTRY: ActivityOverlayRegistry = {\n\tlist: () => [],\n\tsubscribe: () => () => {},\n};\n\nexport interface ActivityOverlayTheme {\n\tborder: (text: string) => string;\n\theader: (text: string) => string;\n\trow: (text: string) => string;\n\tmuted: (text: string) => string;\n\taccent: (text: string) => string;\n\terror: (text: string) => string;\n}\n\nconst IDENTITY: ActivityOverlayTheme = {\n\tborder: (s) => s,\n\theader: (s) => s,\n\trow: (s) => s,\n\tmuted: (s) => s,\n\taccent: (s) => s,\n\terror: (s) => s,\n};\n\nexport interface ActivityOverlayOptions {\n\tregistry?: ActivityOverlayRegistry;\n\ttheme?: ActivityOverlayTheme;\n\tmaxRows?: number;\n\t/** Stalled rows past this many ms render the `· stalled Ns` marker. */\n\tstallThresholdMs?: number;\n}\n\nconst EMPTY_STATE = \"No activity — session idle.\";\nconst DEFAULT_STALL_THRESHOLD_MS = 10_000;\nconst BLOCKER_MARK = \"▸\";\n\nexport class ActivityOverlay implements Component {\n\tprivate registry: ActivityOverlayRegistry;\n\tprivate theme: ActivityOverlayTheme;\n\tprivate maxRows: number;\n\tprivate stallThresholdMs: number;\n\tprivate unsubscribe?: () => void;\n\tprivate redraw?: () => void;\n\n\tconstructor(options: ActivityOverlayOptions = {}) {\n\t\tthis.registry = options.registry ?? NULL_ACTIVITY_REGISTRY;\n\t\tthis.theme = options.theme ?? IDENTITY;\n\t\tthis.maxRows = options.maxRows ?? 12;\n\t\tthis.stallThresholdMs = options.stallThresholdMs ?? DEFAULT_STALL_THRESHOLD_MS;\n\t}\n\n\t/** Wire a new registry (e.g. when the session registers the real one). */\n\tsetRegistry(registry: ActivityOverlayRegistry): void {\n\t\tif (this.unsubscribe) this.unsubscribe();\n\t\tthis.registry = registry;\n\t\tthis.unsubscribe = registry.subscribe(() => this.redraw?.());\n\t}\n\n\t/** Bind a redraw callback so registry events trigger a TUI re-render. */\n\tbindRedraw(redraw: () => void): void {\n\t\tthis.redraw = redraw;\n\t\tif (!this.unsubscribe) {\n\t\t\tthis.unsubscribe = this.registry.subscribe(() => this.redraw?.());\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tif (this.unsubscribe) this.unsubscribe();\n\t\tthis.unsubscribe = undefined;\n\t\tthis.redraw = undefined;\n\t}\n\n\tinvalidate(): void {\n\t\t// Stateless render — nothing to clear.\n\t}\n\n\trender(width: number): string[] {\n\t\tconst snapshot = this.registry.list();\n\t\tif (snapshot.length === 0) {\n\t\t\treturn [\n\t\t\t\tthis.theme.header(this.padRight(\"Activity\", width)),\n\t\t\t\tthis.theme.muted(this.padRight(EMPTY_STATE, width)),\n\t\t\t];\n\t\t}\n\n\t\tconst blockerId = this.blockerId(snapshot);\n\t\tconst rows: string[] = [this.theme.header(this.padRight(this.headerText(snapshot), width))];\n\n\t\t// Cap to maxRows, but ALWAYS include the blocker even if it would overflow.\n\t\tconst visible = snapshot.slice(0, this.maxRows);\n\t\tif (blockerId && !visible.some((s) => s.id === blockerId)) {\n\t\t\tconst blocker = snapshot.find((s) => s.id === blockerId);\n\t\t\tif (blocker) {\n\t\t\t\t// Drop the last visible row to make room for the blocker.\n\t\t\t\tif (visible.length >= this.maxRows) visible.pop();\n\t\t\t\tvisible.push(blocker);\n\t\t\t}\n\t\t}\n\t\tconst hiddenCount = snapshot.length - visible.length;\n\n\t\tfor (const activity of visible) {\n\t\t\trows.push(this.formatRow(activity, width, activity.id === blockerId));\n\t\t}\n\t\tif (hiddenCount > 0) {\n\t\t\trows.push(this.theme.muted(this.padRight(`… +${hiddenCount} more`, width)));\n\t\t}\n\t\treturn rows;\n\t}\n\n\tprivate headerText(snapshot: ActivitySnapshot[]): string {\n\t\tconst running = snapshot.filter((s) => s.status === \"running\").length;\n\t\tconst queued = snapshot.filter((s) => s.status === \"queued\").length;\n\t\tlet header = `Activity (${running} running`;\n\t\tif (queued > 0) header += ` · ${queued} queued`;\n\t\treturn `${header})`;\n\t}\n\n\t/**\n\t * Blocker = the first running non-`model` row in the (already-sorted) list,\n\t * else the first running `model` row. Mirrors registry `blockingLeaf()`.\n\t */\n\tprivate blockerId(snapshot: ActivitySnapshot[]): string | undefined {\n\t\tconst running = snapshot.filter((s) => s.status === \"running\");\n\t\tconst leaf = running.find((s) => s.kind !== \"model\");\n\t\treturn (leaf ?? running.find((s) => s.kind === \"model\"))?.id;\n\t}\n\n\tprivate formatRow(activity: ActivitySnapshot, width: number, isBlocker: boolean): string {\n\t\tconst glyph = this.glyph(activity);\n\t\tconst mark = isBlocker ? `${BLOCKER_MARK} ` : \"\";\n\t\tconst indent = \"  \".repeat(Math.max(0, activity.depth));\n\t\tconst labelText = activity.detail ? `${activity.label}: ${activity.detail}` : activity.label;\n\t\tconst left = `${mark}${indent}${glyph} ${labelText}`;\n\t\tconst right = this.rightColumn(activity);\n\t\treturn this.theme.row(this.layoutRow(left, right, width));\n\t}\n\n\tprivate rightColumn(activity: ActivitySnapshot): string {\n\t\tlet right = formatElapsed(activity.elapsedMs);\n\t\tif (activity.status === \"running\" && activity.stalledMs > this.stallThresholdMs) {\n\t\t\tright += ` · stalled ${formatElapsed(activity.stalledMs)}`;\n\t\t}\n\t\treturn right;\n\t}\n\n\tprivate glyph(activity: ActivitySnapshot): string {\n\t\tswitch (activity.status) {\n\t\t\tcase \"running\":\n\t\t\t\treturn activity.stalledMs > this.stallThresholdMs ? this.theme.accent(\"◍\") : this.theme.accent(\"●\");\n\t\t\tcase \"queued\":\n\t\t\t\treturn this.theme.muted(\"◌\");\n\t\t\tcase \"done\":\n\t\t\t\treturn this.theme.muted(\"○\");\n\t\t\tcase \"error\":\n\t\t\t\treturn this.theme.error(\"✗\");\n\t\t}\n\t}\n\n\tprivate layoutRow(left: string, right: string, width: number): string {\n\t\tconst leftW = visibleWidth(left);\n\t\tconst rightW = visibleWidth(right);\n\t\tconst gap = Math.max(1, width - leftW - rightW);\n\t\tif (leftW + rightW + 1 > width) {\n\t\t\t// Truncate left to make room for the right column.\n\t\t\treturn `${this.truncateVisible(left, Math.max(0, width - rightW - 1))} ${right}`;\n\t\t}\n\t\treturn `${left}${\" \".repeat(gap)}${right}`;\n\t}\n\n\tprivate truncateVisible(s: string, max: number): string {\n\t\t// Width-aware (handles ANSI escapes + wide chars); plain slice would\n\t\t// corrupt colored labels by cutting mid-escape-sequence.\n\t\treturn truncateToWidth(s, max, \"…\");\n\t}\n\n\tprivate padRight(s: string, width: number): string {\n\t\tconst w = visibleWidth(s);\n\t\tif (w >= width) return s;\n\t\treturn s + \" \".repeat(width - w);\n\t}\n}\n"]}