diff --git a/src/editor/core/controller.ts b/src/editor/core/controller.ts index 3d66df09..552e5f26 100644 --- a/src/editor/core/controller.ts +++ b/src/editor/core/controller.ts @@ -1093,6 +1093,20 @@ export class EditorController implements SlotHost { this.apply((s) => closeKey(s, key)); } + activeInput(): EditorInput | null { + return this.activeTabOf()?.input ?? null; + } + + onActiveInputChange(fn: (input: EditorInput | null) => void): () => void { + let prev = this.activeInput()?.key; + return useTabs.subscribe(() => { + const input = this.activeInput(); + if (input?.key === prev) return; + prev = input?.key; + fn(input); + }); + } + activePath(): string | null { return this.activeSlot?.model.path ?? this.activeTabOf()?.input.path ?? null; } diff --git a/src/editor/core/index.ts b/src/editor/core/index.ts index c4e95281..e5bd81e4 100644 --- a/src/editor/core/index.ts +++ b/src/editor/core/index.ts @@ -43,6 +43,8 @@ export function activate(): void | (() => void) { focus: () => ctrl.focus(), openPaths: () => ctrl.openPaths(), openTabs: () => ctrl.openTabs(), + activeInput: () => ctrl.activeInput(), + onActiveInputChange: (fn) => ctrl.onActiveInputChange(fn), closePath: (path) => ctrl.closePath(path), closeInput: (key) => ctrl.closeInput(key), reformat: async (path) => { diff --git a/src/features/agents/AgentsView.tsx b/src/features/agents/AgentsView.tsx index d93d078a..e9283606 100644 --- a/src/features/agents/AgentsView.tsx +++ b/src/features/agents/AgentsView.tsx @@ -1,10 +1,11 @@ /** Agents tool window: tasks grouped by what they need from the user. */ -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import type { AgentStatus as Status, AgentTask } from "@/bindings"; import { executeAction } from "@/platform/actions"; +import { maybeEditorService, type EditorInput } from "@/workbench/contributions"; import { confirmChecked } from "@/workbench/messages"; import { EmptyState, Icon, IconButton, Spinner, T, Tree, showContextMenu, type MenuItem } from "@/ui"; -import { age, branchLabel, groupTasks, isBusy, type TaskGroup } from "./model"; +import { SESSION_KIND, age, branchLabel, groupTasks, isBusy, type TaskGroup } from "./model"; import { archiveTask, deleteTask, openHome, openTask, useAgents } from "./store"; import styles from "./Agents.module.css"; import { tx } from "@/platform/i18n"; @@ -17,6 +18,9 @@ type Node = { kind: "group"; group: TaskGroup } | { kind: "task"; task: AgentTas const nodeKey = (n: Node) => (n.kind === "group" ? `g:${n.group.status}` : n.task.id); +const sessionTask = (input: EditorInput | null | undefined) => + input?.kind === SESSION_KIND ? (input.data as { task?: string } | undefined)?.task : undefined; + export function TaskStatusIcon({ status }: { status: Status }) { switch (status) { case "needsInput": @@ -39,7 +43,23 @@ export function AgentsView() { const tasks = useAgents((s) => s.tasks); const providers = useAgents((s) => s.providers); const [collapsed, setCollapsed] = useState>(() => new Set(["g:archived"])); - const [selected, setSelected] = useState>(() => new Set()); + const [selected, setSelected] = useState>(() => { + const task = sessionTask(maybeEditorService()?.activeInput?.()); + return new Set(task ? [task] : []); + }); + // Follow the active session tab. + useEffect( + () => + maybeEditorService()?.onActiveInputChange?.((input) => { + const task = sessionTask(input); + if (!task) return; + setSelected(new Set([task])); + const group = groupTasks(useAgents.getState().tasks.values()).find((g) => g.tasks.some((t) => t.id === task)); + const key = group && `g:${group.status}`; + if (key) setCollapsed((c) => (c.has(key) ? new Set([...c].filter((k) => k !== key)) : c)); + }), + [], + ); const roots = useMemo(() => groupTasks(tasks.values()).map((group) => ({ kind: "group", group })), [tasks]); const expanded = useMemo(() => new Set(roots.map(nodeKey).filter((k) => !collapsed.has(k))), [roots, collapsed]); if (!loaded) diff --git a/src/workbench/contributions.ts b/src/workbench/contributions.ts index 6a5b7fb3..ff8d4e2f 100644 --- a/src/workbench/contributions.ts +++ b/src/workbench/contributions.ts @@ -409,6 +409,10 @@ export interface EditorService { openPaths?(): string[]; /** Every open tab, files or not, most recently active first (Switcher). */ openTabs?(): OpenEditorTab[]; + /** Input of the active tab (any kind), if any. */ + activeInput?(): EditorInput | null; + /** Calls `fn` whenever the active tab changes; returns the unsubscribe. */ + onActiveInputChange?(fn: (input: EditorInput | null) => void): () => void; /** Closes every tab showing `path`. */ closePath?(path: string): void; /** Closes the tab of the input with this `key` (e.g. a merge editor once applied). */