Files
goodbuddy/docs/features/task-and-job/technical-design.md
T
mesalogo f7541203e4 fix: restore cached reply duration when switching conversations
Keep execution statistics by conversation and project so returning to a viewed conversation immediately restores its reply duration while refreshing in the background. Preserve late-result isolation and clear failed scopes.

Validation: 15 focused tests, typecheck and lint passed. Full suite: 4878 passed, 9 navigation settings assertion failures and 1 Agent package timeout outside this change.

Release note: Switching back to a viewed conversation now immediately displays its cached cumulative Agent reply duration while refreshing the statistics.
2026-09-23 01:00:18 +08:00

11 KiB

Execution Statistics Query

Runtime Read Path

WAL、连接所有权、内存缓存和后台读取的通用规则见 运行时存储架构。

tasks:execution-stats 在 Main 校验来源、范围和当前回复租约,然后经 AssistantDatabase.getExecutionStatsAsync 调用独立的 execution-stats-worker。 worker 使用只读连接和短读事务执行下文查询,不运行数据库初始化或任务恢复。 查询错误返回调用方,关闭数据库时终止 worker 并拒绝未完成请求。

worker 内按项目或会话范围缓存结果,最多 8 项、30 秒;每次读取先检查 SQLite 连接版本。 活动回复绕过缓存,事务内读取清空并绕过缓存,关闭连接清空缓存。写入提交后的下一次读取 会重新计算,覆盖本地消息、远程事件、任务删除和恢复。统计数据仍以查询快照时刻为准。

Renderer 按会话及所属项目、项目分别保留内存统计快照,切回已查询范围时同步显示缓存, 同时后台刷新;首次打开未缓存范围仍等待查询,缓存不跨应用重启保留,也不在前端推算时长。 保留可见时 5 秒刷新以及 revision、focus、visibility 触发刷新;失败的范围清除缓存并显示 不可用,旧范围的迟到结果丢弃。重复缓存结果保留对象引用,更新通过 startTransition 提交,避免无变化的轮询反复更新 App 的任务时长映射。任务面板使用 React Activity 保留状态并降低隐藏时的渲染优先级;面板工厂在该边界内部调用。浏览器和终端沿用原有挂载 与 effect 生命周期。

Contract

window.goodbuddy.tasks.getExecutionStats(input) invokes tasks:execution-stats. ExecutionStatsInput accepts exactly one UUID scope: { conversationId } or { projectId }. Main validates the scope and trusted sender. The database returns:

interface ExecutionStats {
  durationMs: number
  requestCount: number
  incompleteRequestCount: number
  activeRequestCount: number
  asOf: number // epoch milliseconds at query time
  taskDurations: Array<{
    id: string
    durationMs: number
    incompleteRequestCount: number
  }>
}

The project scope sums request evidence across its conversations and returns card totals in taskDurations. A scheduled execution uses requestId = schedule_runs.id; join its schedule_id to the stable task's schedule_id to aggregate repeated runs. Request creation also saves that proven relation in parent_task_id. Before plan deletion cascades its run records, bind older requests that still lack this link. Statistics can then use the retained link to a visible origin = schedule parent, even after the parent's schedule_id has been cleared. A shared conversation alone does not establish ownership. Other identified requests retain their own task ID. Visible tasks with no executions have a zero entry. Conversation queries return an empty taskDurations array. Message counts used to detect missing history stay in Main; no bodies are returned.

Evidence And Boundaries

  • Query all retained scoped task records, including hidden chat requests, without the visible-task list's pagination. Require a persisted message request_id link, a reply event (text, reasoning, tool, done, error, subagent, generated-image, or question) associated by task_events.task_id, a schedule run association, or a current reply lease. Local message snapshots do not persist request links, so local replies normally use event membership and kind. Do not parse each stream/tool payload merely to repeat its owning request ID.
  • Runtime status events also prove a reply when requestId matches the owning task, type is status, and message is a string. Maintenance status events such as { status: 'running' } do not. Status-only interrupted replies retain their last evidenced interval just like text/tool replies.
  • Exclude stable schedule parents (schedule_id is set) and expert tasks (origin = subagent). Scheduled reply requests remain eligible. Nested expert execution is already inside the parent's elapsed interval and is not added again.
  • Maintenance-only records do not establish reply identity. This excludes manual context compression, heartbeat summaries, and unexecuted suggestions. A record with no reply identity evidence cannot be classified; it is not counted.
  • Sum intervals from started_at or later running status events to terminal events/statuses. Pause/queue closes an interval; a later run starts another. Repeated approval/running transitions do not reset the start. Tools and approval waits inside a reply are included; idle gaps between replies/runs are excluded.
  • interrupted may be written at application restart. Count only through the preceding evidence, mark the request incomplete, and never use its recovery completed_at as an execution endpoint. Missing or invalid timing also marks the request incomplete; unknown time is not replaced with zero-length certainty.
  • A non-aborted reply lease confirms current execution: an open local interval with a reliable start contributes through asOf and remains complete while active. Manual context compression leases are excluded. Without a live lease, stop at the latest evidence and mark an open interval incomplete. Renderer must query again after asOf; it does not extrapolate.
  • Remote-recoverable records, tasks in persisted SSH project execution spaces, and events with remote operation provenance are incomplete and contribute no duration: their receipt timestamps cannot establish remote execution intervals, particularly after replay. No Runtime, Agent, or remote persistence behavior changes are required by this read-only query. The SSH project check applies before the first provenance event, including channel/delegation requests with remote_recoverable = false, so an unknown remote duration is not first reported as complete local execution time.
  • Completed, failed, or running schedule runs with no request history count as incomplete, including in their stable task card. Pending runs do not count.
  • Former schedule parents are never execution clocks, including after deletion. A removed plan without retained execution ownership is reported as incomplete, not a complete zero; its former lifetime cannot reconstruct missing runs.
  • Retained top-level assistant messages establish missing-history evidence. Explicit missing request links count as incomplete; unlinked snapshots use a count comparison within each conversation/task, after covering linked replies. Missing schedule runs already counted above cover their messages, avoiding a second missing count. Expert-task messages and nested subagent content are not counted again. A wholly missing reply contributes zero known duration and one incomplete request, not a complete zero.
  • Exclude the first local assistant message only when it has no request/task link and exactly matches a shipped Chinese/English default greeting. Persisted greetings have no separate marker. Other assistant-only history and identical text later in the conversation remain eligible; do not discard all messages before the first user turn.
  • These counts are a lower bound when local snapshots have no request links; fully deleted history cannot be reconstructed. requestCount includes replies inferred from missing-history evidence. Concurrent top-level requests may overlap in wall-clock time; their durations are summed independently.

The query materializes scoped task evidence once, then returns only status and terminal events. Only status payloads use JSON extraction. Request membership and remote provenance inspect scalar event columns; last-event and pre-interruption timestamps use (task_id, id) index seeks. This preserves a tool/delta event as the last reliable boundary without loading its body. Grouped assistant-message counts read task metadata from message rows; event rows are not scanned for that count. It adds no tables, columns, migration, or persisted timing totals. Stable task lifecycle timestamps are never duration endpoints.

Validation Coverage

The focused execution-statistics tests cover hidden requests, scope isolation, approval waits, repeated intervals, restart interruption, remote replay, missing start times, and history beyond visible-task pagination. IPC tests cover sender and scope validation; preload tests cover typed forwarding. Renderer integration must supply getExecutionStats in its DesktopApi.tasks mocks.

The large-payload regression inspects the production query plan, measures paired conversation/project polling on 3,000 events with approximately 94 MiB of payload, and replaces stream/tool bodies with invalid JSON to prove they are not parsed. Run it with npx vitest run src/main/assistant/assistant-execution-stats.test.ts -t seeks --reporter=verbose --disableConsoleIntercept to print timings. These are synthetic warm-query measurements, not a guarantee for arbitrarily large histories. Scalar event filtering and per-message metadata work still grow with retained history; if that becomes measurable in deployment, prefer polling only during active execution and refreshing on history/task changes.

Custom Task Creation

The dialog defaults to the current conversation and immediate execution. Content is required; an empty name uses the first 120 characters of whitespace-normalized content. App supplies other local conversations in the current project and also allows creating a new conversation. Scheduled creation retains once/daily/weekly recurrence and requires a future timestamp, checked by both App and Main IPC.

ScheduleCreateInput.runImmediately is an optional creation command, not a stored schedule field. It is valid only for recurrence: 'once'. createSchedule creates the conversation (if needed), task, disabled schedule, pending schedule run and conversation queue item in one SQLite transaction. Queue insertion failure rolls back all of them. Disabling automatic scheduling prevents timer ticks from adding another occurrence; pending/running queue state still determines task status.

The create IPC handler publishes the queue change and wakes the existing conversation queue pump. App does not follow creation with runNow. Execution uses the existing readiness and conversation serialization rules. A runtime failure belongs to the created task; it does not reject creation or create another task. Refresh failures after a successful creation are notifications, not a reason to retry creation. No schema migration or preload changes are required.

schedule-creation.test.ts verifies transaction rollback and timer non-duplication with real SQLite. IPC tests exercise that database through the production queue and ordinary run handler with a controlled runtime, including failed execution. App creation tests cover destination selection, time validation, and failure handling.