mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 16:03:26 +00:00
feat(dashboard): add agent status search board (#11042)
* feat(dashboard): add agent status search board * fix(dashboard): keep idle controls reachable * chore: drop merge-only formatting drift * fix(dashboard): compare sparse subagent snapshots safely * fix(dashboard): satisfy settings handler lint * fix(dashboard): address review feedback * fix(dashboard): complete search and localized status copy * fix(dashboard): pad active filter row * fix(dashboard): keep idle control in board settings * fix(dashboard): source filters from workspace state * fix(dashboard): clarify PR and MR status filter * fix(dashboard): preserve review and board parity
This commit is contained in:
@@ -20,13 +20,24 @@ const SNAPSHOT = {
|
||||
leafId: 'leaf-1',
|
||||
repoName: 'Orca',
|
||||
worktreeName: 'Dashboard',
|
||||
workspaceStatusId: 'in-review',
|
||||
workspaceStatusLabel: 'In review',
|
||||
workspaceStatusColor: 'emerald',
|
||||
hasReview: true,
|
||||
review: { number: 11012, state: 'open' },
|
||||
subagents: [{ id: 'child-1', name: 'Review loop', dotState: 'working' }],
|
||||
startedAt: 1_699_999_000_000,
|
||||
finishedAt: null,
|
||||
stateChangedAt: 1_699_999_500_000,
|
||||
unseen: true,
|
||||
askSummary: '{"question":"Proceed?"}'
|
||||
}
|
||||
]
|
||||
],
|
||||
showIdle: false,
|
||||
filterOptions: {
|
||||
projects: [{ id: 'repo-1', label: 'Orca' }],
|
||||
workspaceStatuses: [{ id: 'in-review', label: 'In review', color: 'emerald' }]
|
||||
}
|
||||
} satisfies DashboardSnapshot
|
||||
|
||||
describe('dashboard payload validation', () => {
|
||||
@@ -48,6 +59,18 @@ describe('dashboard payload validation', () => {
|
||||
cards: [{ ...SNAPSHOT.cards[0], lastAgentMessage: 'x'.repeat(8_001) }]
|
||||
})
|
||||
).toBe(false)
|
||||
expect(
|
||||
isDashboardSnapshot({
|
||||
...SNAPSHOT,
|
||||
cards: [{ ...SNAPSHOT.cards[0], review: { number: 0, state: 'open' } }]
|
||||
})
|
||||
).toBe(false)
|
||||
expect(
|
||||
isDashboardSnapshot({
|
||||
...SNAPSHOT,
|
||||
cards: [{ ...SNAPSHOT.cards[0], subagents: [{ id: '', name: 'bad', dotState: 'idle' }] }]
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts repo icons a pop-out can safely render, and rejects the rest', () => {
|
||||
@@ -85,6 +108,29 @@ describe('dashboard payload validation', () => {
|
||||
expect(isDashboardSnapshot({ ...SNAPSHOT, repoIconsByRepoId: [] })).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts bounded filter options independently of cards', () => {
|
||||
expect(isDashboardSnapshot({ ...SNAPSHOT, cards: [] })).toBe(true)
|
||||
expect(isDashboardSnapshot({ ...SNAPSHOT, filterOptions: undefined })).toBe(true)
|
||||
expect(
|
||||
isDashboardSnapshot({
|
||||
...SNAPSHOT,
|
||||
filterOptions: {
|
||||
...SNAPSHOT.filterOptions,
|
||||
projects: [{ id: '', label: 'Invalid' }]
|
||||
}
|
||||
})
|
||||
).toBe(false)
|
||||
expect(
|
||||
isDashboardSnapshot({
|
||||
...SNAPSHOT,
|
||||
filterOptions: {
|
||||
...SNAPSHOT.filterOptions,
|
||||
workspaceStatuses: [{ id: 'todo', label: 'x'.repeat(1_025) }]
|
||||
}
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('bounds the conversation name', () => {
|
||||
expect(
|
||||
isDashboardSnapshot({
|
||||
|
||||
@@ -8,11 +8,14 @@ import {
|
||||
} from '../../shared/agent-status-types'
|
||||
|
||||
const MAX_DASHBOARD_CARDS = 1_000
|
||||
const MAX_DASHBOARD_SUBAGENTS = 100
|
||||
const MAX_DASHBOARD_REPO_ICONS = 500
|
||||
const MAX_DASHBOARD_FILTER_OPTIONS = 500
|
||||
const MAX_ID_LENGTH = 4_096
|
||||
const MAX_LABEL_LENGTH = 1_024
|
||||
const DASHBOARD_BUCKETS = new Set(['attention', 'working', 'idle'])
|
||||
const DASHBOARD_BUCKETS = new Set(['attention', 'working', 'done', 'idle'])
|
||||
const DASHBOARD_DOT_STATES = new Set(['working', 'blocked', 'waiting', 'done', 'idle'])
|
||||
const DASHBOARD_REVIEW_STATES = new Set(['open', 'closed', 'merged', 'draft'])
|
||||
|
||||
function isBoundedString(value: unknown, maxLength: number, allowEmpty = false): value is string {
|
||||
return typeof value === 'string' && value.length <= maxLength && (allowEmpty || value.length > 0)
|
||||
@@ -53,10 +56,44 @@ export function isDashboardSnapshot(value: unknown): value is DashboardSnapshot
|
||||
Array.isArray(snapshot.cards) &&
|
||||
snapshot.cards.length <= MAX_DASHBOARD_CARDS &&
|
||||
snapshot.cards.every(isDashboardCard) &&
|
||||
(snapshot.showIdle === undefined || typeof snapshot.showIdle === 'boolean') &&
|
||||
isDashboardFilterOptions(snapshot.filterOptions) &&
|
||||
isDashboardRepoIcons(snapshot.repoIconsByRepoId)
|
||||
)
|
||||
}
|
||||
|
||||
function isDashboardFilterOptions(value: unknown): boolean {
|
||||
if (value === undefined) {
|
||||
return true
|
||||
}
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return false
|
||||
}
|
||||
const options = value as Record<string, unknown>
|
||||
return (
|
||||
isDashboardFilterOptionList(options.projects) &&
|
||||
isDashboardFilterOptionList(options.workspaceStatuses)
|
||||
)
|
||||
}
|
||||
|
||||
function isDashboardFilterOptionList(value: unknown): boolean {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.length <= MAX_DASHBOARD_FILTER_OPTIONS &&
|
||||
value.every((entry) => {
|
||||
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
||||
return false
|
||||
}
|
||||
const option = entry as Record<string, unknown>
|
||||
return (
|
||||
isBoundedString(option.id, MAX_ID_LENGTH) &&
|
||||
isBoundedString(option.label, MAX_LABEL_LENGTH, true) &&
|
||||
isOptionalBoundedString(option.color, MAX_ID_LENGTH)
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/** Repo icons reach the pop-out's `<img src>`, so each one must survive the
|
||||
* same sanitizer the settings picker writes through. */
|
||||
function isDashboardRepoIcons(value: unknown): boolean {
|
||||
@@ -77,6 +114,44 @@ function isDashboardRepoIcons(value: unknown): boolean {
|
||||
)
|
||||
}
|
||||
|
||||
function isDashboardReview(value: unknown): boolean {
|
||||
if (value === undefined) {
|
||||
return true
|
||||
}
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return false
|
||||
}
|
||||
const review = value as Record<string, unknown>
|
||||
return (
|
||||
isFiniteNumber(review.number) &&
|
||||
review.number > 0 &&
|
||||
typeof review.state === 'string' &&
|
||||
DASHBOARD_REVIEW_STATES.has(review.state)
|
||||
)
|
||||
}
|
||||
|
||||
function isDashboardSubagents(value: unknown): boolean {
|
||||
if (value === undefined) {
|
||||
return true
|
||||
}
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.length <= MAX_DASHBOARD_SUBAGENTS &&
|
||||
value.every((entry) => {
|
||||
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
||||
return false
|
||||
}
|
||||
const subagent = entry as Record<string, unknown>
|
||||
return (
|
||||
isBoundedString(subagent.id, MAX_ID_LENGTH) &&
|
||||
isBoundedString(subagent.name, MAX_LABEL_LENGTH, true) &&
|
||||
typeof subagent.dotState === 'string' &&
|
||||
DASHBOARD_DOT_STATES.has(subagent.dotState)
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function isDashboardCard(value: unknown): boolean {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return false
|
||||
@@ -99,6 +174,12 @@ function isDashboardCard(value: unknown): boolean {
|
||||
(card.leafId === null || isBoundedString(card.leafId, MAX_ID_LENGTH)) &&
|
||||
isBoundedString(card.repoName, MAX_LABEL_LENGTH, true) &&
|
||||
isBoundedString(card.worktreeName, MAX_LABEL_LENGTH, true) &&
|
||||
isOptionalBoundedString(card.workspaceStatusId, MAX_ID_LENGTH) &&
|
||||
isOptionalBoundedString(card.workspaceStatusLabel, MAX_LABEL_LENGTH) &&
|
||||
isOptionalBoundedString(card.workspaceStatusColor, MAX_ID_LENGTH) &&
|
||||
(card.hasReview === undefined || typeof card.hasReview === 'boolean') &&
|
||||
isDashboardReview(card.review) &&
|
||||
isDashboardSubagents(card.subagents) &&
|
||||
isFiniteNumber(card.startedAt) &&
|
||||
(card.finishedAt === null || isFiniteNumber(card.finishedAt)) &&
|
||||
isFiniteNumber(card.stateChangedAt) &&
|
||||
|
||||
Reference in New Issue
Block a user