Files
hermes-agent/apps/desktop/src/lib/todos.ts
T
Brooklyn Nicholson d208f2c2c0 feat(desktop): reconcile live tool events, polish thread chrome, harden boot
- chat-messages: match tool rows by overlapping query/context/preview values
  so preview-first `tool.progress` rows reliably adopt later stable-id
  `tool.start` payloads instead of spawning ghost rows or mis-merging
  parallel same-name calls; preserve prior args/result across phases.
- tui_gateway: emit full args + parsed result on `tool.start` / `tool.complete`,
  drop redundant `tool.started` re-emit from `tool.progress`.
- electron/main: prefer SOURCE_REPO_ROOT before PATH `hermes` in dev so
  local backend edits actually run; split hardening helpers into
  `electron/hardening.cjs` with tests.
- thread/tool UI: one-shot enter animation keyed by stable ids, braille
  spinner for running rows, Cursor-like disclosure rows, drill-down +
  duration/count formatting via new tool-fallback-model.
- composer: extract `text-utils`, drop liquid-glass overrides.
- right-rail: split preview-pane into preview-console / preview-file.
- runtime: incremental external-store runtime + runtime-readiness gate;
  onboarding store + tests; route-resume hook test.
- regression tests for live tool reconciliation (parallel tools, id-less
  progress, preview-first rows, structured args/results).
2026-05-11 21:38:47 -04:00

51 lines
1.3 KiB
TypeScript

export type TodoStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled'
export interface TodoItem {
content: string
id: string
status: TodoStatus
}
const STATUSES: readonly TodoStatus[] = ['pending', 'in_progress', 'completed', 'cancelled']
const isRecord = (v: unknown): v is Record<string, unknown> => Boolean(v && typeof v === 'object' && !Array.isArray(v))
const isStatus = (v: unknown): v is TodoStatus => (STATUSES as readonly string[]).includes(v as string)
function parseArray(value: unknown[]): TodoItem[] {
return value.flatMap(item => {
if (!isRecord(item) || !isStatus(item.status)) {
return []
}
const id = String(item.id ?? '').trim()
const content = String(item.content ?? '').trim()
return id && content ? [{ content, id, status: item.status }] : []
})
}
function parse(value: unknown, depth: number): null | TodoItem[] {
if (depth > 2) {
return null
}
if (Array.isArray(value)) {
return parseArray(value)
}
if (typeof value === 'string' && value.trim()) {
try {
return parse(JSON.parse(value), depth + 1)
} catch {
return null
}
}
if (isRecord(value) && Object.hasOwn(value, 'todos')) {
return parse(value.todos, depth + 1)
}
return null
}
export const parseTodos = (value: unknown): null | TodoItem[] => parse(value, 0)