Clarify agent hibernation controls (#5426)
This commit is contained in:
parent
f3f41e3e2b
commit
3a87a9e572
|
|
@ -20,6 +20,7 @@ afterEach(() => {
|
|||
|
||||
async function renderExperimentalPane(args: {
|
||||
updateSettings: (settings: Partial<GlobalSettings>) => void
|
||||
settings?: GlobalSettings
|
||||
}): Promise<{ root: Root; container: HTMLDivElement }> {
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
|
|
@ -27,7 +28,7 @@ async function renderExperimentalPane(args: {
|
|||
await act(async () => {
|
||||
root.render(
|
||||
<ExperimentalPane
|
||||
settings={getDefaultSettings('/tmp')}
|
||||
settings={args.settings ?? getDefaultSettings('/tmp')}
|
||||
updateSettings={args.updateSettings}
|
||||
/>
|
||||
)
|
||||
|
|
@ -56,8 +57,7 @@ describe('ExperimentalPane', () => {
|
|||
expect(settings.experimentalAgentHibernation).toBe(false)
|
||||
expect(settings.agentHibernationIdleMs).toBe(30 * 60 * 1000)
|
||||
expect(markup).toContain('Agent hibernation')
|
||||
expect(markup).toContain('Hibernate after')
|
||||
expect(markup).toContain('minutes')
|
||||
expect(markup).not.toContain('Hibernate after')
|
||||
expect(markup).toContain('aria-checked="false"')
|
||||
expect(getExperimentalPaneSearchEntries().map((entry) => entry.title)).toContain(
|
||||
'Agent hibernation'
|
||||
|
|
@ -66,7 +66,11 @@ describe('ExperimentalPane', () => {
|
|||
|
||||
it('renders the agent hibernation idle duration as configurable minutes', async () => {
|
||||
const updateSettings = vi.fn()
|
||||
const { root, container } = await renderExperimentalPane({ updateSettings })
|
||||
const settings = {
|
||||
...getDefaultSettings('/tmp'),
|
||||
experimentalAgentHibernation: true
|
||||
}
|
||||
const { root, container } = await renderExperimentalPane({ updateSettings, settings })
|
||||
|
||||
const idleInput = container.querySelector<HTMLInputElement>(
|
||||
'#experimental-agent-hibernation input[type="number"]'
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { HiddenExperimentalGroup } from './HiddenExperimentalGroup'
|
|||
import { NumberField, SettingsSwitch } from './SettingsFormControls'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import {
|
||||
DEFAULT_AGENT_HIBERNATION_IDLE_MS,
|
||||
MAX_AGENT_HIBERNATION_IDLE_MS,
|
||||
MIN_AGENT_HIBERNATION_IDLE_MS,
|
||||
getEffectiveAgentHibernationIdleMs
|
||||
|
|
@ -236,31 +235,32 @@ export function ExperimentalPane({
|
|||
}
|
||||
/>
|
||||
</div>
|
||||
<NumberField
|
||||
label={translate(
|
||||
'auto.components.settings.ExperimentalPane.agentHibernation.idleMinutesLabel',
|
||||
'Hibernate after'
|
||||
)}
|
||||
description={translate(
|
||||
'auto.components.settings.ExperimentalPane.agentHibernation.idleMinutesDescription',
|
||||
'How many idle minutes a completed background agent must wait before Orca can hibernate it.'
|
||||
)}
|
||||
value={agentHibernationIdleMinutes}
|
||||
defaultValue={DEFAULT_AGENT_HIBERNATION_IDLE_MS / MS_PER_MINUTE}
|
||||
min={MIN_AGENT_HIBERNATION_IDLE_MS / MS_PER_MINUTE}
|
||||
max={MAX_AGENT_HIBERNATION_IDLE_MS / MS_PER_MINUTE}
|
||||
step={1}
|
||||
suffix={translate(
|
||||
'auto.components.settings.ExperimentalPane.agentHibernation.idleMinutesSuffix',
|
||||
'minutes'
|
||||
)}
|
||||
onChange={(minutes) =>
|
||||
updateSettings({
|
||||
// Why: settings persist the planner contract, not the display unit.
|
||||
agentHibernationIdleMs: minutes * MS_PER_MINUTE
|
||||
})
|
||||
}
|
||||
/>
|
||||
{agentHibernationEnabled ? (
|
||||
<NumberField
|
||||
label={translate(
|
||||
'auto.components.settings.ExperimentalPane.agentHibernation.idleMinutesLabel',
|
||||
'Hibernate after'
|
||||
)}
|
||||
description={translate(
|
||||
'auto.components.settings.ExperimentalPane.agentHibernation.idleMinutesDescription',
|
||||
'How many idle minutes a completed background agent must wait before Orca can hibernate it.'
|
||||
)}
|
||||
value={agentHibernationIdleMinutes}
|
||||
min={MIN_AGENT_HIBERNATION_IDLE_MS / MS_PER_MINUTE}
|
||||
max={MAX_AGENT_HIBERNATION_IDLE_MS / MS_PER_MINUTE}
|
||||
step={1}
|
||||
suffix={translate(
|
||||
'auto.components.settings.ExperimentalPane.agentHibernation.idleMinutesSuffix',
|
||||
'minutes'
|
||||
)}
|
||||
onChange={(minutes) =>
|
||||
updateSettings({
|
||||
// Why: settings persist the planner contract, not the display unit.
|
||||
agentHibernationIdleMs: minutes * MS_PER_MINUTE
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</SearchableSetting>
|
||||
) : null}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ vi.mock('react', async () => {
|
|||
memo: <T>(component: T) => component,
|
||||
useEffect: () => {},
|
||||
useLayoutEffect: () => {},
|
||||
useCallback: <T extends (...args: never[]) => unknown>(callback: T) => callback,
|
||||
useMemo: <T>(factory: () => T) => factory(),
|
||||
useRef: <T>(current: T) => ({ current }),
|
||||
useState: <T>(initial: T | (() => T)) => {
|
||||
|
|
|
|||
|
|
@ -4640,7 +4640,16 @@
|
|||
"f63ea281e3": "Feed integrado en la barra lateral izquierda para completar agentes y estados de bloqueo.",
|
||||
"ca2219fe5e": "Muestra una pequeña mascota animada fijada en la esquina inferior derecha. Elige un personaje (Claudino, OpenCode, Gremlin) o carga tu propio PNG, APNG, GIF, WebP, JPG o SVG desde el menú de mascotas de la barra de estado. Ocultarlo en cualquier momento desde el mismo menú sin desactivar esta configuración.",
|
||||
"dd6f0a1d45": "Mascota",
|
||||
"0e89a574ae": "Mascota animada flotante en la esquina inferior derecha."
|
||||
"0e89a574ae": "Mascota animada flotante en la esquina inferior derecha.",
|
||||
"agentHibernation": {
|
||||
"copy": "Detiene los terminales de agentes en segundo plano que estén inactivos después del intervalo configurado y reanuda las sesiones compatibles cuando las vuelves a abrir. Experimental mientras ajustamos el modelo de seguridad.",
|
||||
"description": "Detiene los terminales de agentes en segundo plano que estén inactivos después del intervalo configurado y reanuda las sesiones compatibles cuando las vuelves a abrir.",
|
||||
"idleMinutesDescription": "Cuántos minutos inactivo debe esperar un agente en segundo plano completado antes de que Orca pueda hibernarlo.",
|
||||
"idleMinutesLabel": "Hibernar después de",
|
||||
"idleMinutesSuffix": "minutos",
|
||||
"title": "Hibernación de agentes",
|
||||
"toggleLabel": "Alternar hibernación de agentes"
|
||||
}
|
||||
},
|
||||
"FloatingWorkspacePane": {
|
||||
"aeaf76fda9": "Barra de estado",
|
||||
|
|
@ -6692,7 +6701,17 @@
|
|||
"b54cea709b": "compañero",
|
||||
"051203d37c": "mascota",
|
||||
"6b5a56ac35": "Mascota animada flotante en la esquina inferior derecha.",
|
||||
"87d99e634b": "Mascota"
|
||||
"87d99e634b": "Mascota",
|
||||
"agentHibernation": {
|
||||
"agent": "agente",
|
||||
"agents": "agentes",
|
||||
"description": "Detiene los terminales de agentes en segundo plano que estén inactivos después del intervalo configurado y reanuda las sesiones compatibles cuando se vuelven a abrir.",
|
||||
"hibernate": "hibernar",
|
||||
"minutes": "minutos",
|
||||
"sleep": "suspender",
|
||||
"terminal": "terminal",
|
||||
"title": "Hibernación de agentes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"floating": {
|
||||
|
|
|
|||
|
|
@ -4625,7 +4625,16 @@
|
|||
"f63ea281e3": "agent の完了とブロック状態を示すスレッド化された左側のサイドバー フィード。",
|
||||
"ca2219fe5e": "右下隅に固定された小さなアニメーションのペットを表示します。キャラクター (Claudino、OpenCode、Gremlin) を選択するか、ステータス バーのペット メニューから独自の PNG、APNG、GIF、WebP、JPG、または SVG をアップロードします。この設定を無効にしなくても、同じメニューからいつでも非表示にできます。",
|
||||
"dd6f0a1d45": "ペット",
|
||||
"0e89a574ae": "右下隅に浮かぶアニメーションのペット。"
|
||||
"0e89a574ae": "右下隅に浮かぶアニメーションのペット。",
|
||||
"agentHibernation": {
|
||||
"copy": "設定したアイドル時間が経過したバックグラウンド agent ターミナルを停止し、対応しているセッションは再度開いたときに再開します。安全モデルを調整中の実験的機能です。",
|
||||
"description": "設定したアイドル時間が経過したバックグラウンド agent ターミナルを停止し、対応しているセッションは再度開いたときに再開します。",
|
||||
"idleMinutesDescription": "完了したバックグラウンド agent を Orca が休止状態にできるまで待つアイドル時間(分)。",
|
||||
"idleMinutesLabel": "休止まで",
|
||||
"idleMinutesSuffix": "分",
|
||||
"title": "Agent の休止",
|
||||
"toggleLabel": "Agent の休止を切り替え"
|
||||
}
|
||||
},
|
||||
"FloatingWorkspacePane": {
|
||||
"aeaf76fda9": "ステータスバー",
|
||||
|
|
@ -6714,7 +6723,17 @@
|
|||
"b54cea709b": "相棒",
|
||||
"051203d37c": "ペット",
|
||||
"6b5a56ac35": "右下隅に浮かぶアニメーションのペット。",
|
||||
"87d99e634b": "ペット"
|
||||
"87d99e634b": "ペット",
|
||||
"agentHibernation": {
|
||||
"agent": "agent",
|
||||
"agents": "agents",
|
||||
"description": "設定したアイドル時間が経過したバックグラウンド agent ターミナルを停止し、再度開いたときに対応セッションを再開します。",
|
||||
"hibernate": "休止",
|
||||
"minutes": "分",
|
||||
"sleep": "スリープ",
|
||||
"terminal": "ターミナル",
|
||||
"title": "Agent の休止"
|
||||
}
|
||||
}
|
||||
},
|
||||
"floating": {
|
||||
|
|
|
|||
|
|
@ -4625,7 +4625,16 @@
|
|||
"f63ea281e3": "agent 완료 및 차단 상태에 대한 스레드 왼쪽 사이드바 피드입니다.",
|
||||
"ca2219fe5e": "오른쪽 하단에 고정된 작은 애니메이션 애완동물을 표시합니다. 캐릭터(Claudino, OpenCode, Gremlin)를 선택하거나 상태 표시줄 애완동물 메뉴에서 자신만의 PNG, APNG, GIF, WebP, JPG 또는 SVG를 업로드하세요. 이 설정을 비활성화하지 않고 동일한 메뉴에서 언제든지 숨길 수 있습니다.",
|
||||
"dd6f0a1d45": "애완 동물",
|
||||
"0e89a574ae": "오른쪽 하단에 떠 있는 애니메이션 애완동물."
|
||||
"0e89a574ae": "오른쪽 하단에 떠 있는 애니메이션 애완동물.",
|
||||
"agentHibernation": {
|
||||
"copy": "설정된 유휴 시간이 지난 백그라운드 agent 터미널을 중지하고, 다시 열 때 지원되는 세션을 재개합니다. 안전 모델을 조정하는 동안 제공되는 실험적 기능입니다.",
|
||||
"description": "설정된 유휴 시간이 지난 백그라운드 agent 터미널을 중지하고, 다시 열 때 지원되는 세션을 재개합니다.",
|
||||
"idleMinutesDescription": "완료된 백그라운드 agent를 Orca가 최대 절전 모드로 전환하기 전에 기다릴 유휴 시간(분)입니다.",
|
||||
"idleMinutesLabel": "최대 절전까지",
|
||||
"idleMinutesSuffix": "분",
|
||||
"title": "Agent 최대 절전",
|
||||
"toggleLabel": "Agent 최대 절전 전환"
|
||||
}
|
||||
},
|
||||
"FloatingWorkspacePane": {
|
||||
"aeaf76fda9": "상태 표시줄",
|
||||
|
|
@ -6677,7 +6686,17 @@
|
|||
"b54cea709b": "친구",
|
||||
"051203d37c": "애완 동물",
|
||||
"6b5a56ac35": "오른쪽 하단에 떠 있는 애니메이션 애완동물.",
|
||||
"87d99e634b": "애완 동물"
|
||||
"87d99e634b": "애완 동물",
|
||||
"agentHibernation": {
|
||||
"agent": "agent",
|
||||
"agents": "agents",
|
||||
"description": "설정된 유휴 시간이 지난 백그라운드 agent 터미널을 중지하고, 다시 열 때 지원되는 세션을 재개합니다.",
|
||||
"hibernate": "최대 절전",
|
||||
"minutes": "분",
|
||||
"sleep": "절전",
|
||||
"terminal": "터미널",
|
||||
"title": "Agent 최대 절전"
|
||||
}
|
||||
}
|
||||
},
|
||||
"floating": {
|
||||
|
|
|
|||
|
|
@ -4625,7 +4625,16 @@
|
|||
"f63ea281e3": "用于 Agent 完成和阻塞状态的螺纹左侧边栏提要。",
|
||||
"ca2219fe5e": "显示固定在右下角的小动画宠物。从状态栏宠物菜单中选择一个角色(Claudino、OpenCode、Gremlin)或上传您自己的 PNG、APNG、GIF、WebP、JPG 或 SVG。随时从同一菜单隐藏它,而不禁用此设置。",
|
||||
"dd6f0a1d45": "宠物",
|
||||
"0e89a574ae": "右下角漂浮的动画宠物。"
|
||||
"0e89a574ae": "右下角漂浮的动画宠物。",
|
||||
"agentHibernation": {
|
||||
"copy": "在配置的空闲时间后停止后台 agent 终端,并在你再次打开时恢复受支持的会话。我们仍在调校安全模型,此功能为实验性功能。",
|
||||
"description": "在配置的空闲时间后停止后台 agent 终端,并在你再次打开时恢复受支持的会话。",
|
||||
"idleMinutesDescription": "已完成的后台 agent 在 Orca 可以将其休眠前需要等待的空闲分钟数。",
|
||||
"idleMinutesLabel": "休眠等待时间",
|
||||
"idleMinutesSuffix": "分钟",
|
||||
"title": "Agent 休眠",
|
||||
"toggleLabel": "切换 agent 休眠"
|
||||
}
|
||||
},
|
||||
"FloatingWorkspacePane": {
|
||||
"aeaf76fda9": "状态栏",
|
||||
|
|
@ -6677,7 +6686,17 @@
|
|||
"b54cea709b": "伙伴",
|
||||
"051203d37c": "宠物",
|
||||
"6b5a56ac35": "右下角漂浮的动画宠物。",
|
||||
"87d99e634b": "宠物"
|
||||
"87d99e634b": "宠物",
|
||||
"agentHibernation": {
|
||||
"agent": "agent",
|
||||
"agents": "agents",
|
||||
"description": "在配置的空闲时间后停止后台 agent 终端,并在再次打开时恢复受支持的会话。",
|
||||
"hibernate": "休眠",
|
||||
"minutes": "分钟",
|
||||
"sleep": "睡眠",
|
||||
"terminal": "终端",
|
||||
"title": "Agent 休眠"
|
||||
}
|
||||
}
|
||||
},
|
||||
"floating": {
|
||||
|
|
|
|||
|
|
@ -147,6 +147,28 @@ function signatureFor(worktreeId: string, panes: EligiblePane[]): string {
|
|||
return `${worktreeId}|${parts.join('|')}`
|
||||
}
|
||||
|
||||
function getAgentEntriesByTabId(
|
||||
agentStatusByPaneKey: AgentHibernationPlannerSnapshot['agentStatusByPaneKey']
|
||||
): Map<string, AgentStatusEntry[]> {
|
||||
const entriesByTabId = new Map<string, AgentStatusEntry[]>()
|
||||
for (const entry of Object.values(agentStatusByPaneKey)) {
|
||||
if (!entry) {
|
||||
continue
|
||||
}
|
||||
const tabId = getEntryTabId(entry)
|
||||
if (!tabId) {
|
||||
continue
|
||||
}
|
||||
const entries = entriesByTabId.get(tabId)
|
||||
if (entries) {
|
||||
entries.push(entry)
|
||||
} else {
|
||||
entriesByTabId.set(tabId, [entry])
|
||||
}
|
||||
}
|
||||
return entriesByTabId
|
||||
}
|
||||
|
||||
export function planAgentHibernationCandidates(
|
||||
snapshot: AgentHibernationPlannerSnapshot
|
||||
): AgentHibernationCandidate[] {
|
||||
|
|
@ -156,6 +178,7 @@ export function planAgentHibernationCandidates(
|
|||
const idleMs = getEffectiveAgentHibernationIdleMs(snapshot.settings.agentHibernationIdleMs)
|
||||
const mobileLockedPtyIds = new Set(snapshot.mobileLockedPtyIds)
|
||||
const foregroundWorktreeIds = new Set(snapshot.foregroundWorktreeIds)
|
||||
const agentEntriesByTabId = getAgentEntriesByTabId(snapshot.agentStatusByPaneKey)
|
||||
const candidates: AgentHibernationCandidate[] = []
|
||||
for (const [worktreeId, tabs] of Object.entries(snapshot.tabsByWorktree)) {
|
||||
if (
|
||||
|
|
@ -181,10 +204,7 @@ export function planAgentHibernationCandidates(
|
|||
continue
|
||||
}
|
||||
const layout = snapshot.terminalLayoutsByTabId[tab.id]
|
||||
for (const entry of Object.values(snapshot.agentStatusByPaneKey)) {
|
||||
if (!entry || getEntryTabId(entry) !== tab.id) {
|
||||
continue
|
||||
}
|
||||
for (const entry of agentEntriesByTabId.get(tab.id) ?? []) {
|
||||
const eligible = getEligiblePane({
|
||||
entry,
|
||||
tab,
|
||||
|
|
|
|||
Loading…
Reference in New Issue