mirror of
https://github.com/stablyai/orca.git
synced 2026-10-28 16:02:55 +00:00
* feat(agents): distinguish Claude background monitoring Adds an optional `workingMode: 'monitoring'` discriminator for a Claude session whose lead turn finished but which still has background shell tasks or session crons registered. The wire state stays `working`, so older peers that never read the field keep rendering Working. (cherry picked from commitd5d54b4bdd) Rebased onto current main (554 commits of drift) by Brennan Benson; conflicts resolved by keeping both sides where main and this branch made independent additions to the same construct. * fix(sidebar): keep monitoring status visible (cherry picked from commitfd6b38654d) * test(agents): cover Claude monitoring drain (cherry picked from commitce4d61ebf8) * test(mobile): avoid unresolved renderer test type (cherry picked from commitbbcfa35ff9) * feat(agents): render Claude monitoring as a static turquoise dot Replaces the yellow Radio glyph from #14205 with a static dot in a new --agent-monitoring token (#8abeb7), defined once for light and once for dark like --workspace-status-done, so the status keeps its identity when the theme flips. Deliberately a fixed UI value: it never reads terminal theme state at runtime. Adds the turn-boundary notification pins. The monitoring predicate and the turnCompletedAt stamp are computed from the same "lead said done but the pane resolves to working" expression, so a rename can silently drop the stamp and kill a completion notification that works today with nothing else going red. * revert(agents): restore the yellow Radio glyph for monitoring Brennan chose #14205's original treatment over the turquoise dot, so the visual goes back to nwparker's: lucide Radio in text-yellow-500 across the sidebar, dashboard dot, cmd-j palette and agent-map ring. Reverts only the visual surface. The turn-boundary notification pins stay — the monitoring predicate and the turnCompletedAt stamp share an expression, so a rename can silently drop the stamp and kill a completion that works today with nothing else going red. The --agent-monitoring token is removed with its last consumer rather than left dead in main.css. --------- Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
184 lines
5.9 KiB
TypeScript
184 lines
5.9 KiB
TypeScript
import { join } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
import type { RuntimeWorktreeAgentRow } from '../../../src/shared/runtime-types'
|
|
import type { Worktree } from './workspace-list-sections'
|
|
import { areWorktreeListsEqual } from './worktree-list-snapshot'
|
|
|
|
function agent(overrides: Partial<RuntimeWorktreeAgentRow> = {}): RuntimeWorktreeAgentRow {
|
|
return {
|
|
paneKey: 'agent-1',
|
|
parentPaneKey: null,
|
|
state: 'working',
|
|
agentType: 'codex',
|
|
prompt: 'fix mobile lag',
|
|
lastAssistantMessage: null,
|
|
toolName: null,
|
|
toolInput: null,
|
|
interrupted: false,
|
|
stateStartedAt: 100,
|
|
updatedAt: 200,
|
|
...overrides
|
|
}
|
|
}
|
|
|
|
function worktree(overrides: Partial<Worktree> = {}): Worktree {
|
|
const worktreePath = join('/tmp', 'orca', 'worktrees', 'manta')
|
|
return {
|
|
worktreeId: `repo-1::${worktreePath}`,
|
|
repoId: 'repo-1',
|
|
repo: 'orca',
|
|
branch: 'feature/mobile-lag',
|
|
displayName: 'manta',
|
|
workspaceStatus: 'in-progress',
|
|
path: worktreePath,
|
|
liveTerminalCount: 1,
|
|
hasAttachedPty: true,
|
|
preview: '$ codex',
|
|
unread: false,
|
|
lastOutputAt: 1234,
|
|
isPinned: false,
|
|
isActive: false,
|
|
linkedPR: null,
|
|
linkedIssue: null,
|
|
linkedLinearIssue: null,
|
|
linkedGitLabMR: null,
|
|
linkedGitLabIssue: null,
|
|
comment: '',
|
|
status: 'active',
|
|
agents: [],
|
|
...overrides
|
|
}
|
|
}
|
|
|
|
describe('areWorktreeListsEqual', () => {
|
|
it('treats cloned snapshots with the same visible fields as equal', () => {
|
|
const first = [worktree({ agents: [agent()] })]
|
|
const second = [worktree({ agents: [agent()] })]
|
|
|
|
expect(areWorktreeListsEqual(first, second)).toBe(true)
|
|
})
|
|
|
|
it('detects order and field changes that affect the host list', () => {
|
|
const first = [worktree({ worktreeId: 'a' }), worktree({ worktreeId: 'b' })]
|
|
const reordered = [worktree({ worktreeId: 'b' }), worktree({ worktreeId: 'a' })]
|
|
const renamed = [
|
|
worktree({ worktreeId: 'a', displayName: 'renamed' }),
|
|
worktree({ worktreeId: 'b' })
|
|
]
|
|
|
|
expect(areWorktreeListsEqual(first, reordered)).toBe(false)
|
|
expect(areWorktreeListsEqual(first, renamed)).toBe(false)
|
|
})
|
|
|
|
it('detects host visibility field changes', () => {
|
|
expect(areWorktreeListsEqual([worktree()], [worktree({ isArchived: true })])).toBe(false)
|
|
expect(areWorktreeListsEqual([worktree()], [worktree({ isMainWorktree: true })])).toBe(false)
|
|
expect(areWorktreeListsEqual([worktree()], [worktree({ hasHostSidebarActivity: true })])).toBe(
|
|
false
|
|
)
|
|
})
|
|
|
|
it('detects workspace status changes', () => {
|
|
expect(
|
|
areWorktreeListsEqual(
|
|
[worktree({ workspaceStatus: 'in-progress' })],
|
|
[worktree({ workspaceStatus: 'in-review' })]
|
|
)
|
|
).toBe(false)
|
|
})
|
|
|
|
it('detects manual order changes', () => {
|
|
expect(
|
|
areWorktreeListsEqual(
|
|
[worktree({ manualOrder: 10, sortOrder: 1 })],
|
|
[worktree({ manualOrder: 20, sortOrder: 1 })]
|
|
)
|
|
).toBe(false)
|
|
})
|
|
|
|
it('detects desktop ordering field changes', () => {
|
|
expect(
|
|
areWorktreeListsEqual(
|
|
[worktree({ lastActivityAt: 10, createdAt: 1 })],
|
|
[worktree({ lastActivityAt: 20, createdAt: 1 })]
|
|
)
|
|
).toBe(false)
|
|
expect(
|
|
areWorktreeListsEqual(
|
|
[worktree({ lastActivityAt: 10, createdAt: 1 })],
|
|
[worktree({ lastActivityAt: 10, createdAt: 2 })]
|
|
)
|
|
).toBe(false)
|
|
})
|
|
|
|
it('detects resume host and terminal platform changes', () => {
|
|
expect(areWorktreeListsEqual([worktree()], [worktree({ hostId: 'ssh:box' })])).toBe(false)
|
|
expect(
|
|
areWorktreeListsEqual(
|
|
[worktree({ terminalPlatform: 'win32' })],
|
|
[worktree({ terminalPlatform: 'linux' })]
|
|
)
|
|
).toBe(false)
|
|
})
|
|
|
|
it('detects lineage changes', () => {
|
|
const base = worktree({ worktreeId: 'child', parentWorktreeId: 'parent-a' })
|
|
const changedParent = worktree({ worktreeId: 'child', parentWorktreeId: 'parent-b' })
|
|
const changedChildren = worktree({
|
|
worktreeId: 'child',
|
|
parentWorktreeId: 'parent-a',
|
|
childWorktreeIds: ['grandchild']
|
|
})
|
|
|
|
expect(areWorktreeListsEqual([base], [changedParent])).toBe(false)
|
|
expect(areWorktreeListsEqual([base], [changedChildren])).toBe(false)
|
|
})
|
|
|
|
it('detects lineage instance changes', () => {
|
|
const base = worktree({
|
|
worktreeId: 'child',
|
|
parentWorktreeId: 'parent',
|
|
worktreeInstanceId: 'child-instance',
|
|
lineageWorktreeInstanceId: 'child-instance',
|
|
parentWorktreeInstanceId: 'parent-instance'
|
|
})
|
|
const changedParentInstance = worktree({
|
|
worktreeId: 'child',
|
|
parentWorktreeId: 'parent',
|
|
worktreeInstanceId: 'child-instance',
|
|
lineageWorktreeInstanceId: 'child-instance',
|
|
parentWorktreeInstanceId: 'new-parent-instance'
|
|
})
|
|
|
|
expect(areWorktreeListsEqual([base], [changedParentInstance])).toBe(false)
|
|
})
|
|
|
|
it('detects agent status changes', () => {
|
|
const first = [worktree({ agents: [agent({ state: 'working' })] })]
|
|
const second = [worktree({ agents: [agent({ state: 'waiting' })] })]
|
|
|
|
expect(areWorktreeListsEqual(first, second)).toBe(false)
|
|
})
|
|
|
|
it('detects monitoring mode changes within working', () => {
|
|
const first = [worktree({ agents: [agent({ state: 'working' })] })]
|
|
const second = [worktree({ agents: [agent({ state: 'working', workingMode: 'monitoring' })] })]
|
|
|
|
expect(areWorktreeListsEqual(first, second)).toBe(false)
|
|
})
|
|
|
|
it('detects workspace monitoring mode changes within working', () => {
|
|
const first = [worktree({ status: 'working' })]
|
|
const second = [worktree({ status: 'working', workingMode: 'monitoring' })]
|
|
|
|
expect(areWorktreeListsEqual(first, second)).toBe(false)
|
|
})
|
|
|
|
it('treats missing and empty agent arrays as equivalent for rendering', () => {
|
|
const first = [worktree({ agents: undefined })]
|
|
const second = [worktree({ agents: [] })]
|
|
|
|
expect(areWorktreeListsEqual(first, second)).toBe(true)
|
|
})
|
|
})
|