feat: 任务管理页二级开关「有新后台任务时自动展开」(autoOpenTasks)
- 新 pref autoOpenTasks(默认开):当前会话出现新后台任务(任意新任务 id, 非仅首个)时自动展开侧边栏并打开任务管理页,与 autoOpenSubagent 并列 - 注册为 subagent tab 的声明式二级开关(设置页齿轮弹窗,key 在 PrefsSchema) - Sidebar 新增独立基线 effect(镜像子代理自动展开;刷新不触发、开关关闭/类型 禁用不触发);detectNewTask 纯函数(settling/同 id/他会话不触发) - 测试:detectNewTask 用例、prefs 解析断言、builtins 开关清单、schema 默认值 断言;全量 371 通过
This commit is contained in:
@@ -44,6 +44,7 @@ import type { TabDragPayload } from './TabBar.tsx'
|
||||
import { relativeTo } from './paths.ts'
|
||||
import { OrphanedTab } from './OrphanedTab.tsx'
|
||||
import { detectNewDirectSubagent } from './subagent-detect.ts'
|
||||
import { detectNewTask } from './subagent-tasks.ts'
|
||||
import { t } from './locales.ts'
|
||||
import { api, type SessionScope } from './api.ts'
|
||||
import css from './sidebar.module.css'
|
||||
@@ -241,6 +242,28 @@ export function Sidebar(props: { ctx: Context; store: SidebarStore }) {
|
||||
ctx.betterSidebar?.openTab({ type: 'subagent', title: t('subagent') })
|
||||
}, [sessionList, sessionId, store, ctx])
|
||||
|
||||
/**
|
||||
* Task auto-activation: the moment a NEW background task appears for the
|
||||
* current conversation (a task id the previous snapshot lacked), the
|
||||
* auto-open pref is on, and the Tasks tab type is enabled, open the panel
|
||||
* (if collapsed) and focus the Tasks page. Unlike the subagent trigger
|
||||
* (0 → N only), ANY new task id triggers: the agent may start several
|
||||
* tasks in one session, and each should surface. A fresh page load never
|
||||
* triggers — its baseline starts at the current snapshot.
|
||||
*/
|
||||
const taskBaselineRef = useRef<SidebarSessionList | undefined>(undefined)
|
||||
useEffect(() => {
|
||||
const prev = taskBaselineRef.current
|
||||
taskBaselineRef.current = sessionList
|
||||
if (sessionId === undefined || prev === undefined) return
|
||||
if (!detectNewTask(prev, sessionList, sessionId)) return
|
||||
if (!store.getPrefs().autoOpenTasks) return
|
||||
if (ctx.betterSidebar?.isTabEnabled('subagent') === false) return
|
||||
store.reduce(s => s.panelOpen ? s : togglePanel(s))
|
||||
store.reduce(s => ({ ...s, activePane: firstLeaf(s.splits).id }))
|
||||
ctx.betterSidebar?.openTab({ type: 'subagent', title: t('subagent') })
|
||||
}, [sessionList, sessionId, store, ctx])
|
||||
|
||||
/**
|
||||
* Topology jump-back: clicking a subagent node on the Subagent page calls
|
||||
* the official `openSubagent`, which switches the sidebar to that child
|
||||
|
||||
@@ -109,13 +109,17 @@ export function builtinTabs(ctx: Context): readonly TabDescriptor[] {
|
||||
icon: (size: number) => <IconThinkOutline16 size={size} />,
|
||||
order: 30,
|
||||
single: true,
|
||||
// Declarative settings: the auto-open switch renders under this row in
|
||||
// the Side card settings page (the Subagent page's own related setting).
|
||||
// Declarative settings: the auto-open switches render under this row in
|
||||
// the Side card settings page (the Tasks page's own related settings).
|
||||
settings: {
|
||||
toggles: [{
|
||||
key: 'autoOpenSubagent',
|
||||
title: () => t('settingsSubagentTitle'),
|
||||
desc: () => t('settingsSubagentDesc'),
|
||||
}, {
|
||||
key: 'autoOpenTasks',
|
||||
title: () => t('settingsTasksTitle'),
|
||||
desc: () => t('settingsTasksDesc'),
|
||||
}],
|
||||
},
|
||||
component: ({ ctx, scope, visible, onSubagentJump }) => (
|
||||
|
||||
@@ -121,6 +121,8 @@ const zh = {
|
||||
zoomHint: 'Alt + 滚轮',
|
||||
settingsSubagentTitle: '检测到子代理时自动展开任务管理页',
|
||||
settingsSubagentDesc: '当前会话产生新的子代理时,自动展开侧边栏并打开任务管理页;关闭后需手动打开',
|
||||
settingsTasksTitle: '有新后台任务时自动展开任务管理页',
|
||||
settingsTasksDesc: '当前会话出现新的后台任务时,自动展开侧边栏并打开任务管理页(每个新任务都会触发);关闭后需手动打开',
|
||||
settingsToolsTitle: '为模型注入终端工具',
|
||||
settingsToolsDesc: '开启后,模型可通过 terminal_create 等 8 个工具创建并操作侧边栏终端(默认关闭)',
|
||||
settingsBottomTerminalTitle: '底部面板首次展开自动开终端',
|
||||
@@ -194,7 +196,7 @@ const zh = {
|
||||
taskViewOutput: '查看输出',
|
||||
taskHideOutput: '收起输出',
|
||||
taskNoOutput: '暂无输出',
|
||||
taskNotReadYet: '模型尚未读取该任务的输出,读取后会显示在这里',
|
||||
taskNotReadYet: '等待模型读取该任务的输出(模型执行 task_output 后,输出会显示在这里)',
|
||||
taskOutputTruncated: '输出过长,已截断显示',
|
||||
taskOutputError: '输出读取失败',
|
||||
taskKill: '终止',
|
||||
@@ -318,6 +320,8 @@ const en: Record<keyof typeof zh, string> = {
|
||||
zoomHint: 'Alt + wheel',
|
||||
settingsSubagentTitle: 'Auto-open the Tasks page when a subagent appears',
|
||||
settingsSubagentDesc: 'Expand the side card and open the Tasks page when the current conversation spawns a new subagent; turn off to open it manually',
|
||||
settingsTasksTitle: 'Auto-open the Tasks page on a new background task',
|
||||
settingsTasksDesc: 'Expand the side card and open the Tasks page whenever a new background task appears for the current conversation (every new task triggers); turn off to open it manually',
|
||||
settingsToolsTitle: 'Inject terminal tools for the model',
|
||||
settingsToolsDesc: 'When enabled, the model can create and drive sidebar terminals through the 8 terminal_* tools (off by default)',
|
||||
settingsBottomTerminalTitle: 'Auto-open a terminal on the bottom panel\'s first expansion',
|
||||
@@ -391,7 +395,7 @@ const en: Record<keyof typeof zh, string> = {
|
||||
taskViewOutput: 'View output',
|
||||
taskHideOutput: 'Hide output',
|
||||
taskNoOutput: 'No output yet',
|
||||
taskNotReadYet: 'The model has not read this task\'s output yet; it will appear here once read',
|
||||
taskNotReadYet: 'Waiting for the model to read this task; its output appears here once the model runs task_output',
|
||||
taskOutputTruncated: 'Output truncated',
|
||||
taskOutputError: 'Failed to read output',
|
||||
taskKill: 'Kill',
|
||||
|
||||
@@ -40,6 +40,9 @@ export function parsePrefs(value: unknown): SidebarPrefs {
|
||||
autoOpenSubagent: typeof record.autoOpenSubagent === 'boolean'
|
||||
? record.autoOpenSubagent
|
||||
: SIDEBAR_PREFS_DEFAULTS.autoOpenSubagent,
|
||||
autoOpenTasks: typeof record.autoOpenTasks === 'boolean'
|
||||
? record.autoOpenTasks
|
||||
: SIDEBAR_PREFS_DEFAULTS.autoOpenTasks,
|
||||
agentTerminalTools: typeof record.agentTerminalTools === 'boolean'
|
||||
? record.agentTerminalTools
|
||||
: SIDEBAR_PREFS_DEFAULTS.agentTerminalTools,
|
||||
|
||||
@@ -57,6 +57,23 @@ export function treeSessionIds(
|
||||
return ids
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a NEW background task appeared for one session between two
|
||||
* consecutive list snapshots (a task id the previous snapshot lacked).
|
||||
* Unlike the subagent auto-open (0 → N only), ANY new task id triggers: the
|
||||
* agent may start several tasks over a session, and each new one should
|
||||
* surface the Tasks page (a fresh page load never triggers — its baseline
|
||||
* starts at the current snapshot).
|
||||
*/
|
||||
export function detectNewTask(
|
||||
prev: SidebarSessionList,
|
||||
next: SidebarSessionList,
|
||||
sessionId: string,
|
||||
): boolean {
|
||||
const prevIds = new Set((prev.tasksBySession?.[sessionId] ?? []).map(task => task.id))
|
||||
return (next.tasksBySession?.[sessionId] ?? []).some(task => !prevIds.has(task.id))
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the background tasks of the whole current tree, owner-labeled.
|
||||
* Sessions without a mirror entry contribute nothing; an absent mirror
|
||||
|
||||
@@ -79,6 +79,7 @@ export const PrefsSchema: z<SidebarPrefs> = z.object({
|
||||
openByDefault: z.boolean().default(true),
|
||||
defaultWidthPercent: z.number().step(1).min(WIDTH_PERCENT_MIN).max(WIDTH_PERCENT_MAX).default(WIDTH_PERCENT_DEFAULT),
|
||||
autoOpenSubagent: z.boolean().default(true),
|
||||
autoOpenTasks: z.boolean().default(true),
|
||||
agentTerminalTools: z.boolean().default(false),
|
||||
bottomPanelAutoTerminal: z.boolean().default(true),
|
||||
interceptOpenPath: z.boolean().default(true),
|
||||
|
||||
@@ -20,6 +20,12 @@ export interface SidebarPrefs {
|
||||
* Subagent page when the current conversation spawns a new subagent.
|
||||
*/
|
||||
autoOpenSubagent: boolean
|
||||
/**
|
||||
* Whether the sidebar auto-activates (opens the panel) and expands the
|
||||
* Tasks page when a NEW background task appears for the current
|
||||
* conversation (any new task id, not just the first one).
|
||||
*/
|
||||
autoOpenTasks: boolean
|
||||
/**
|
||||
* Whether the model-facing agent terminal tools (terminal_create / list /
|
||||
* send / read / wait_for / resize / signal / close) are injected into the
|
||||
@@ -103,6 +109,7 @@ export const SIDEBAR_PREFS_DEFAULTS: SidebarPrefs = {
|
||||
openByDefault: true,
|
||||
defaultWidthPercent: WIDTH_PERCENT_DEFAULT,
|
||||
autoOpenSubagent: true,
|
||||
autoOpenTasks: true,
|
||||
agentTerminalTools: false,
|
||||
bottomPanelAutoTerminal: true,
|
||||
interceptOpenPath: true,
|
||||
|
||||
Reference in New Issue
Block a user