{"version":3,"file":"taskOverlay.cjs","names":["resolveCollapseKey","visibleTasks","deriveTaskProjection","truncateToWidth","selectOverlayLayoutFromProjection","getMaxWidgetLines","allocateAgentIdentityColor","formatOverlayTaskLine"],"sources":["../../../../../../../../src/extensions/workspaces/sessions/(frontend)/overlay/_lib/taskOverlay.ts"],"sourcesContent":["import { allocateAgentIdentityColor } from '@agimon-ai/doompi-ui/theme';\nimport type { ExtensionAPI, ExtensionContext, ExtensionUIContext, Theme } from '@earendil-works/pi-coding-agent';\nimport type { KeyId } from '@earendil-works/pi-tui';\nimport { type TUI, truncateToWidth } from '@earendil-works/pi-tui';\n\nimport type { Task } from '../../../../../../models/task';\nimport { COLLAPSE_KEY_OFF, getMaxWidgetLines, resolveCollapseKey } from '../../../../../../services/config';\nimport type { DelegationManager } from '../../../../../../services/delegation';\nimport { formatOverlayTaskLine } from '../../_shared/format.cli';\nimport { deriveTaskProjection, selectOverlayLayoutFromProjection, visibleTasks } from './selectors';\n\nconst WIDGET_KEY = 'doom-tasks';\nconst OVERLAY_HEADING = 'Tasks';\nconst PROGRESS_TICK_MS = 1000;\n\nexport interface TaskOverlayOptions {\n  getTasks: () => readonly Task[];\n  delegation: DelegationManager;\n}\n\n/** The widget owns its collapse key, while the session root owns its lifetime. */\nexport function registerTaskCollapseShortcut(\n  pi: Pick<ExtensionAPI, 'registerShortcut'>,\n  overlay: TaskOverlay,\n  waitUntilReady: (context: ExtensionContext) => Promise<void>,\n  isActive: () => boolean,\n): void {\n  const key = resolveCollapseKey();\n  if (key === COLLAPSE_KEY_OFF) return;\n  pi.registerShortcut(key as KeyId, {\n    description: 'Collapse or expand the task overlay',\n    handler: async (context) => {\n      if (!isActive() || !context.hasUI) return;\n      await waitUntilReady(context);\n      if (!isActive() || !overlay.isRegistered()) return;\n      overlay.toggleCollapse();\n    },\n  });\n}\n\n/**\n * Persistent widget listing tasks above the editor.\n *\n * Registered once per UI context and refreshed with `requestRender()`; it\n * auto-hides when there is nothing to show so an idle session keeps a clean\n * prompt.\n */\nexport class TaskOverlay {\n  private readonly options: TaskOverlayOptions;\n  private uiCtx: ExtensionUIContext | undefined;\n  private widgetRegistered = false;\n  private tui: TUI | undefined;\n  private progressTimer: NodeJS.Timeout | undefined;\n  private collapsed = false;\n\n  constructor(options: TaskOverlayOptions) {\n    this.options = options;\n  }\n\n  /** Identity-compare so repeated session_start handlers stay idempotent. */\n  setUICtx(ctx: ExtensionUIContext): void {\n    if (ctx === this.uiCtx) return;\n    if (this.uiCtx && this.widgetRegistered) this.uiCtx.setWidget(WIDGET_KEY, undefined);\n    this.stopProgressTimer();\n    this.uiCtx = ctx;\n    this.widgetRegistered = false;\n    this.tui = undefined;\n  }\n\n  update(): void {\n    if (!this.uiCtx) {\n      this.stopProgressTimer();\n      return;\n    }\n    const tasks = visibleTasks(this.options.getTasks());\n\n    if (tasks.length === 0) {\n      if (this.widgetRegistered) {\n        this.uiCtx.setWidget(WIDGET_KEY, undefined);\n        this.widgetRegistered = false;\n        this.tui = undefined;\n      }\n      this.stopProgressTimer();\n      return;\n    }\n\n    if (this.widgetRegistered) {\n      this.syncProgressTimer(tasks);\n      this.tui?.requestRender();\n      return;\n    }\n\n    this.uiCtx.setWidget(\n      WIDGET_KEY,\n      (tui, factoryTheme) => {\n        this.tui = tui;\n        return {\n          render: (width: number) => this.renderWidget(this.uiCtx?.theme ?? factoryTheme, width),\n          invalidate: () => {\n            // Nothing is cached: the next render reads the live theme and store.\n          },\n        };\n      },\n      { placement: 'aboveEditor' },\n    );\n    this.widgetRegistered = true;\n    this.syncProgressTimer(tasks);\n  }\n\n  private syncProgressTimer(tasks: readonly Task[]): void {\n    const hasRunningDelegation = tasks.some(\n      (task) => task.status !== 'deleted' && task.delegation?.state === 'running',\n    );\n    if (this.collapsed || !this.widgetRegistered || !hasRunningDelegation) {\n      this.stopProgressTimer();\n      return;\n    }\n    if (this.progressTimer) return;\n    this.progressTimer = setInterval(() => this.tui?.requestRender(), PROGRESS_TICK_MS);\n    this.progressTimer.unref?.();\n  }\n\n  private stopProgressTimer(): void {\n    if (this.progressTimer) clearInterval(this.progressTimer);\n    this.progressTimer = undefined;\n  }\n\n  toggleCollapse(): void {\n    this.collapsed = !this.collapsed;\n    this.syncProgressTimer(this.options.getTasks());\n    // Forced redraw: collapsing changes the widget height, not just its content.\n    this.tui?.requestRender(true);\n  }\n\n  isRegistered(): boolean {\n    return this.widgetRegistered;\n  }\n\n  private renderWidget(theme: Theme, width: number): string[] {\n    const projection = deriveTaskProjection(this.options.getTasks());\n    if (projection.visible.length === 0) return [];\n\n    const truncate = (line: string): string => truncateToWidth(line, width, '…');\n    const headingColor = projection.hasActiveWork ? 'accent' : 'dim';\n    const headingIcon = projection.hasActiveWork ? '●' : '○';\n    let headingText = `${OVERLAY_HEADING} (${projection.counts.completed}/${projection.counts.total})`;\n    if (projection.counts.failed > 0) headingText += ` · ${projection.counts.failed} failed`;\n    const heading = truncate(`${theme.fg(headingColor, headingIcon)} ${theme.fg(headingColor, headingText)}`);\n\n    if (this.collapsed) {\n      const key = resolveCollapseKey();\n      const hint = key === COLLAPSE_KEY_OFF ? 'collapsed' : `${key} to expand`;\n      return this.withTrailingSpacer([heading, truncate(`${theme.fg('dim', '└─')} ${theme.fg('dim', hint)}`)]);\n    }\n\n    const layout = selectOverlayLayoutFromProjection(projection, getMaxWidgetLines() - 1);\n    const occupiedColors = new Set<ReturnType<typeof allocateAgentIdentityColor>>();\n    const agentColors = new Map<string, ReturnType<typeof allocateAgentIdentityColor>>();\n    for (const task of layout.visible) {\n      if (task.delegation?.state !== 'running') continue;\n      const identity = task.delegation.runId ?? task.delegation.requestId;\n      const color = allocateAgentIdentityColor(identity, occupiedColors);\n      occupiedColors.add(color);\n      agentColors.set(identity, color);\n    }\n\n    const nowMs = Date.now();\n    const lines = [heading];\n    for (const task of layout.visible) {\n      const progress = this.options.delegation.progressFor(task);\n      const identity = task.delegation?.runId ?? task.delegation?.requestId;\n      lines.push(\n        truncate(\n          `${theme.fg('dim', '├─')} ${formatOverlayTaskLine(\n            task,\n            theme,\n            projection.showIds,\n            progress,\n            nowMs,\n            identity ? agentColors.get(identity) : undefined,\n          )}`,\n        ),\n      );\n    }\n\n    const hidden = layout.hiddenCompleted + layout.truncatedTail;\n    if (hidden === 0) {\n      const last = lines.length - 1;\n      lines[last] = lines[last].replace('├─', '└─');\n      return this.withTrailingSpacer(lines);\n    }\n\n    const parts: string[] = [];\n    if (layout.hiddenCompleted > 0) parts.push(`${layout.hiddenCompleted} completed`);\n    if (layout.truncatedTail > 0) parts.push(`${layout.truncatedTail} pending`);\n    const summary = parts.length > 0 ? `+${hidden} more (${parts.join(', ')})` : `+${hidden} more`;\n    lines.push(truncate(`${theme.fg('dim', '└─')} ${theme.fg('dim', summary)}`));\n    return this.withTrailingSpacer(lines);\n  }\n\n  /** Pi adds a spacer above the widget but none below, which glues the last row to the input box. */\n  private withTrailingSpacer(lines: string[]): string[] {\n    if (lines.length === 0) return lines;\n    lines.push('');\n    return lines;\n  }\n\n  dispose(): void {\n    this.stopProgressTimer();\n    if (this.uiCtx) this.uiCtx.setWidget(WIDGET_KEY, undefined);\n    this.widgetRegistered = false;\n    this.tui = undefined;\n    this.uiCtx = undefined;\n    this.collapsed = false;\n  }\n}\n"],"mappings":"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"}