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).
This commit is contained in:
@@ -13,7 +13,9 @@ const isStatus = (v: unknown): v is TodoStatus => (STATUSES as readonly string[]
|
||||
|
||||
function parseArray(value: unknown[]): TodoItem[] {
|
||||
return value.flatMap(item => {
|
||||
if (!isRecord(item) || !isStatus(item.status)) {return []}
|
||||
if (!isRecord(item) || !isStatus(item.status)) {
|
||||
return []
|
||||
}
|
||||
const id = String(item.id ?? '').trim()
|
||||
const content = String(item.content ?? '').trim()
|
||||
|
||||
@@ -22,15 +24,25 @@ function parseArray(value: unknown[]): TodoItem[] {
|
||||
}
|
||||
|
||||
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 (depth > 2) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (isRecord(value) && Object.hasOwn(value, 'todos')) {return parse(value.todos, depth + 1)}
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user