perf: track execution duration by state and bound summary I/O
This commit is contained in:
@@ -9,9 +9,9 @@
|
||||
3. [Scheduled Task PRD](./scheduled-task-prd.md):时间或事件触发的 Task。
|
||||
4. [Goal Task PRD](./goal-task-prd.md):围绕可验证结果有界推进的 Task。
|
||||
5. [Job 与 Subjob PRD](./job-and-subjob-prd.md):Task 内部串行、并行和委派执行。
|
||||
6. [技术设计](./technical-design.md):统计查询契约、请求证据与不完整数据边界,以及立即创建的事务和入队规则。
|
||||
6. [技术设计](./technical-design.md):运行状态计时、快照订阅与不完整历史边界,以及立即创建的事务和入队规则。
|
||||
|
||||
统计读取与共享存储、worker 生命周期和 Renderer 调度的通用约束见[运行时存储架构](../../architecture/runtime-storage.md)。
|
||||
共享存储、后台读取和 Renderer 调度的通用约束见[运行时存储架构](../../architecture/runtime-storage.md)。
|
||||
|
||||
正式功能范围固定为当前项目:顶部显示当前会话消息总数和 Agent 累计回复时间,下方列出本项目全部会话的任务。创建默认立即执行并关联当前会话,可选择本项目其他会话、新会话或定时周期;产品与验收规则见 Task Center PRD 和 Scheduled Task PRD。
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
[当前会话统计 Demo](./hierarchy-demo.html) 用于评审任务中心中的紧凑统计区:上方显示当前会话的消息数与 Agent 累计回复时长,下方管理当前项目全部会话的任务及用时。切换会话只更新会话统计,任务范围固定为当前项目,没有全局或所有项目入口。支持状态筛选、定位所属会话,以及暂停、继续和取消任务的模拟操作。新建任务支持填写内容、关联本项目已有或新建会话、立即或定时执行;仅在页面内模拟,刷新后恢复样例。
|
||||
配套 `hierarchy-demo.css` 与 `hierarchy-demo.js` 仅服务于该静态原型;使用演示数据,未接入产品。
|
||||
应用中的项目任务标题右侧仅保留 `+` 新建按钮;展开列表后直接显示状态选项,默认选中“进行中”,收起后保留所选状态。静态 Demo 的筛选入口仍为旧交互。
|
||||
时间为固定样例,Demo 从每次回复的起止秒数计算。正式功能按请求有效区间累计,包含轮内工具和审批等待、不计轮间空闲;任务用时按 `schedule_runs` 关联请求汇总,不按父任务生命周期计算。缺失历史有提示,远程缺少可靠证据时不推算用时,数据来源与边界以技术设计为准。
|
||||
时间为固定样例,Demo 从每次回复的起止秒数计算。正式功能由 Main 累计回复请求处于 `running` 状态的时间,暂停、等待审批和轮间空闲不计时;任务用时汇总到所属任务卡。旧历史和中断时未闭合的区间标为不完整,不查询历史消息重建。远程请求只统计桌面观察到的运行区间,断连期间的用时未知;具体规则见技术设计。
|
||||
|
||||
先阅读统一领域模型。其他功能文档不得重新定义 Task、Conversation、Job、Run 或 Subagent。
|
||||
若实现与文档出现冲突,应先修正统一模型,再同步功能 PRD。
|
||||
|
||||
@@ -64,10 +64,11 @@ FR-TC1:当前项目是唯一范围,列表只显示 `projectId` 匹配的 Tas
|
||||
|
||||
### 4.1 当前会话统计
|
||||
|
||||
FR-TC2:顶部紧凑展示当前会话消息总数与 Agent 累计回复时间。累计时间按每轮请求的有效
|
||||
执行区间相加,包含轮内工具执行和审批等待,不计轮间空闲;不重复累加子专家用时。
|
||||
缺失历史或时间证据不完整时提示“部分历史用时不可用”,已知用时不能表示为完整总量。
|
||||
远程执行缺少可靠时间证据时显示不可用或不完整,不用事件接收时间虚构远程用时。
|
||||
FR-TC2:顶部紧凑展示当前会话消息总数与 Agent 累计回复时间。Main 累计每轮请求处于
|
||||
`running` 状态的时间,包含该状态下的工具执行;暂停、等待审批和轮间空闲不计时,
|
||||
子专家用时不重复累加。界面根据 Main 快照本地计时,切换会话或隐藏面板不影响累计。
|
||||
旧历史和中断时未闭合的区间提示“部分历史用时不可用”,不扫描历史消息重建时间。
|
||||
远程请求统计桌面观察到的运行区间,断连期间的用时标为不完整。
|
||||
没有当前会话时提示选择会话,项目任务列表仍保留。
|
||||
项目名称放在当前会话统计卡片内部的顶部。
|
||||
|
||||
@@ -156,7 +157,7 @@ FR-TC3:任务累计执行时间按其历次执行请求汇总,周期触发
|
||||
- [ ] 删除 Task 默认保留 Conversation 和既有消息。
|
||||
- [x] Smart Heartbeat 不进入 Task Center。
|
||||
- [ ] FR-TC1:仅显示当前项目任务,切换会话不缩小列表;无项目时显示选择项目提示。
|
||||
- [ ] FR-TC2:消息总数与累计回复时间随当前会话更新;轮内工具和审批计时,轮间空闲不计时,历史缺失及远程证据不足有提示。
|
||||
- [ ] FR-TC2:消息总数与累计回复时间随当前会话更新;仅运行状态计时,暂停、等待审批和轮间空闲不计时,旧历史与中断区间有不完整提示。
|
||||
- [ ] FR-TC3:多次执行汇总到同一任务,不按父 Task 生命周期计算用时。
|
||||
- [x] FR-TC4:标题右侧仅保留新建;展开列表直接显示状态选项,默认进行中,收起后保留所选条件。
|
||||
- [ ] FR-TC5:创建默认立即执行和当前会话,可选择本项目其他会话、新会话及定时周期,空名称从内容生成。
|
||||
|
||||
@@ -1,147 +1,95 @@
|
||||
# Execution Statistics Query
|
||||
# Execution Duration
|
||||
|
||||
## Runtime Read Path
|
||||
## Running State
|
||||
|
||||
WAL、连接所有权、内存缓存和后台读取的通用规则见
|
||||
[运行时存储架构](../../architecture/runtime-storage.md)。
|
||||
Main owns reply timing independently of the visible conversation or task panel.
|
||||
Acquiring a reply lease starts a segment after the request task has been created.
|
||||
Manual context compression uses a maintenance lease and creates no clock. Nested
|
||||
subagents and stable schedule parents are excluded; their work is already inside
|
||||
the top-level reply interval. Background and channel requests use the same lease.
|
||||
|
||||
`tasks:execution-stats` 在 Main 校验来源、范围和当前回复租约,然后经
|
||||
`AssistantDatabase.getExecutionStatsAsync` 调用独立的 `execution-stats-worker`。
|
||||
worker 使用只读连接和短读事务执行下文查询,不运行数据库初始化或任务恢复。
|
||||
查询错误返回调用方,关闭数据库时终止 worker 并拒绝未完成请求。
|
||||
Task transitions away from `running` close the segment, including `paused`,
|
||||
`queued`, `waiting_approval`, completion, failure and cancellation. Resuming a
|
||||
leased request opens another segment. Repeated `running` notifications leave the
|
||||
original segment intact. Abort closes timing immediately, even when runtime
|
||||
cleanup is still pending. Releasing a lease prevents subsequent task updates from
|
||||
restarting its clock. Concurrent replies contribute their durations independently.
|
||||
|
||||
worker 内按项目或会话范围缓存结果,最多 8 项、30 秒;每次读取先检查 SQLite 连接版本。
|
||||
活动回复绕过缓存,事务内读取清空并绕过缓存,关闭连接清空缓存。写入提交后的下一次读取
|
||||
会重新计算,覆盖本地消息、远程事件、任务删除和恢复。统计数据仍以查询快照时刻为准。
|
||||
Remote requests measure Main's observed running state. The existing committed
|
||||
terminal checkpoint closes their timing; event persistence, replay deduplication
|
||||
and Agent ACK order are unchanged. Disconnection/recovery gaps are incomplete.
|
||||
This duration does not claim to measure autonomous execution while the desktop
|
||||
is disconnected. Application exit closes observed detached remote intervals
|
||||
without cancelling the remote operation and marks the remaining duration unknown.
|
||||
|
||||
Renderer 按会话及所属项目、项目分别保留内存统计快照,切回已查询范围时同步显示缓存,
|
||||
同时后台刷新;首次打开未缓存范围仍等待查询,缓存不跨应用重启保留,也不在前端推算时长。
|
||||
保留可见时 5 秒刷新以及 revision、focus、visibility 触发刷新;失败的范围清除缓存并显示
|
||||
不可用,旧范围的迟到结果丢弃。重复缓存结果保留对象引用,更新通过 `startTransition`
|
||||
提交,避免无变化的轮询反复更新 App 的任务时长映射。任务面板使用 React `Activity`
|
||||
保留状态并降低隐藏时的渲染优先级;面板工厂在该边界内部调用。浏览器和终端沿用原有挂载
|
||||
与 effect 生命周期。
|
||||
## Storage
|
||||
|
||||
Schema 59 adds `execution_timing`: one row per timed request with its project,
|
||||
conversation, task-card owner, accumulated milliseconds, optional running start,
|
||||
and incomplete flag. Scope indexes keep snapshots separate from large task and
|
||||
message payloads. A schedule run copies the stable task association already
|
||||
stored in `parent_task_id`; repeated runs aggregate under that card. Task deletion
|
||||
cascades to its timing row.
|
||||
|
||||
Only state transitions write timing. Tokens, tools, display ticks and snapshot
|
||||
reads do not update it. Startup drops any unclosed segment and marks it incomplete,
|
||||
retaining previously accumulated time. It never counts application downtime or
|
||||
uses recovery timestamps as execution endpoints.
|
||||
|
||||
Migration marks existing conversations and visible task cards incomplete without
|
||||
reconstructing their past duration. Imported legacy conversation snapshots are
|
||||
also incomplete. No timing query reads historical messages, tools or task events.
|
||||
The same migration adds
|
||||
`messages_summary_idx ON messages(conversation_id, sequence, role, created_at)`
|
||||
for conversation summaries. It performs no history conversion or `VACUUM`.
|
||||
|
||||
## 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:
|
||||
Input accepts exactly one UUID scope: `{ conversationId }` or `{ projectId }`.
|
||||
Main validates the scope and trusted sender, then reads the small timing rows
|
||||
synchronously. The old evidence query, cache, reader and statistics worker are
|
||||
removed.
|
||||
|
||||
```ts
|
||||
interface ExecutionStats {
|
||||
durationMs: number
|
||||
requestCount: number
|
||||
incompleteRequestCount: number
|
||||
activeRequestCount: number
|
||||
asOf: number // epoch milliseconds at query time
|
||||
durationMs: number // closed segments plus open segments through asOf
|
||||
runningCount: number // open segments in this scope
|
||||
incomplete: boolean
|
||||
asOf: number // Main epoch milliseconds
|
||||
taskDurations: Array<{
|
||||
id: string
|
||||
durationMs: number
|
||||
incompleteRequestCount: number
|
||||
runningCount: number
|
||||
incomplete: boolean
|
||||
}>
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Conversation snapshots have an empty `taskDurations` array. Historical request
|
||||
counts are no longer part of this API.
|
||||
|
||||
## Evidence And Boundaries
|
||||
`onExecutionStatsChanged(listener)` returns an unsubscribe function. Main sends
|
||||
`tasks:execution-stats-changed` after timing transitions or history deletion;
|
||||
synchronous changes are coalesced into one notification. The renderer subscribes
|
||||
before fetching its current conversation and project snapshots and refreshes on
|
||||
notifications. It discards superseded responses and clears failed scopes.
|
||||
|
||||
- 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 UI adds `max(0, now - asOf) * runningCount` locally once per second while an
|
||||
open segment exists, including the corresponding per-card calculation. Hidden
|
||||
panels unsubscribe and fetch fresh snapshots when reopened. There is no five-second
|
||||
statistics polling, message-count revision trigger, or tick-time IPC. Main timing
|
||||
continues while the panel is hidden or another conversation is selected.
|
||||
|
||||
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
|
||||
|
||||
## 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.
|
||||
`assistant-execution-stats.test.ts` exercises real SQLite state transitions,
|
||||
restart interruption, remote checkpoints/replay, schedule ownership, scope
|
||||
isolation, deletion, schema migration and read-only snapshots. It verifies that
|
||||
snapshot SQL never reads messages or task events. IPC tests cover trusted scope
|
||||
validation and production lease completion, failure and abort; preload tests cover
|
||||
the subscription bridge. Hook tests verify local ticking without polling, hidden
|
||||
panels, stale responses, and failed reads.
|
||||
|
||||
## Custom Task Creation
|
||||
|
||||
@@ -163,7 +111,8 @@ 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.
|
||||
to retry creation. No schema migration or preload changes are required for this
|
||||
creation command.
|
||||
|
||||
`schedule-creation.test.ts` verifies transaction rollback and timer non-duplication
|
||||
with real SQLite. IPC tests exercise that database through the production queue
|
||||
|
||||
Reference in New Issue
Block a user