vault backup: 2026-06-09 23:15:17

This commit is contained in:
2026-06-09 23:15:17 +08:00
parent 31fd89aafe
commit e92c0327d9
111 changed files with 7276 additions and 8846 deletions
+106 -252
View File
@@ -1,314 +1,168 @@
---
tags: [autonomy, jira, claude-code, 生命周期, bug修复, daemon]
create time: 2026-06-09 22:30
---
# Autonomy Reliability Jira Drafts
These tickets are based on the call-chain audit of `/autonomy`, proactive
ticks, HEARTBEAT managed flows, cron scheduling, command queue consumption,
and daemon process supervision.
## 概述
## AUT-001: Preserve autonomy lifecycle when queued commands are consumed mid-turn
基于 `/autonomy`、proactive ticks、HEARTBEAT managed flows、cron scheduling、command queue consumption 和 daemon process supervision 的调用链审计,整理出 12 个可靠性修复 Tickets。覆盖 autonomy 生命周期状态机、proactive/cron 异步失败可见性、daemon 重启定时器泄漏、持久化锁累积等关键问题。
Type: Bug
Priority: P0
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
## 正文
Problem:
`query.ts` can drain queued prompt/task-notification commands as attachments
during an active turn. Autonomy prompts consumed this way were removed from the
in-memory queue without marking the persisted run as running/completed/failed,
so managed flows could stay stuck in `queued` and never advance.
### AUT-001: Preserve autonomy lifecycle when queued commands are consumed mid-turn
Evidence:
- `src/query.ts` drains queued commands via `getCommandsByMaxPriority()`.
- `src/query.ts` removes consumed commands from the queue.
- Lifecycle updates existed only in the normal queued-submit path
`src/utils/handlePromptSubmit.ts` and headless `src/cli/print.ts`.
- Type: Bug | Priority: P0 | Status: Draft
- Patch status: Implemented in `fix/autonomy-lifecycle`.
Acceptance criteria:
- Mid-turn consumed autonomy commands mark runs `running`.
- Normal query completion finalizes consumed runs and queues next managed-flow
steps.
- Query errors or abort terminal reasons mark consumed runs failed.
- Stale/cancelled autonomy commands are removed from the in-memory queue
without being sent to the model.
- Regression tests cover stale command filtering and managed-flow advancement.
**Problem**: `query.ts` 可以在 active turn 期间将 queued prompt/task-notification commands 作为 attachments 消费。这种方式消费的 Autonomy prompts 从内存队列中移除,但没有标记持久化的 run 为 running/completed/failed,导致 managed flows 可能卡在 `queued` 状态无法推进。
## AUT-002: Make autonomy run lifecycle transitions terminal-safe
**Evidence**: `src/query.ts` 通过 `getCommandsByMaxPriority()` 消费 queued commands 并从队列中移除。生命周期更新仅存在于正常的 queued-submit 路径 `src/utils/handlePromptSubmit.ts` 和 headless `src/cli/print.ts`。
Type: Bug
Priority: P0
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
**Acceptance criteria**: Mid-turn consumed autonomy commands 标记 runs 为 `running`。正常 query completion 完成 consumed runs 并排队下一个 managed-flow steps。Query errors 或 abort terminal reasons 标记 consumed runs 为 failed。Stale/cancelled autonomy commands 从内存队列移除但不发送给模型。
Problem:
Run lifecycle helpers rewrote status unconditionally. A stale in-memory command
could mark a cancelled/completed/failed run back to `running`, causing a
cancelled flow to execute or a terminal flow to be rewritten.
---
Evidence:
- `markAutonomyRunRunning`, `markAutonomyRunCompleted`,
`markAutonomyRunFailed`, and `markAutonomyRunCancelled` updated records
without checking current status.
- External CLI cancel cannot remove queued commands living inside another
process, so stale commands are a realistic input.
### AUT-002: Make autonomy run lifecycle transitions terminal-safe
Acceptance criteria:
- `queued -> running/completed/failed/cancelled` remains allowed.
- `running -> completed/failed/cancelled` remains allowed.
- Any terminal status rejects later lifecycle updates.
- Rejected transitions do not update managed-flow step state.
- Regression tests cover stale lifecycle calls after cancellation.
- Type: Bug | Priority: P0 | Status: Draft
- Patch status: Implemented in `fix/autonomy-lifecycle`.
## AUT-003: Prevent proactive and scheduled-task async fire failures from becoming invisible
**Problem**: Run lifecycle helpers 无条件覆写状态。Stale 的内存命令可能将 cancelled/completed/failed 的 run 重新标记为 `running`,导致 cancelled flow 执行或 terminal flow 被重写。
Type: Bug
Priority: P1
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
**Evidence**: `markAutonomyRunRunning`、`markAutonomyRunCompleted`、`markAutonomyRunFailed`、`markAutonomyRunCancelled` 更新记录时未检查当前状态。外部 CLI cancel 无法移除另一个进程中的 queued commands,所以 stale commands 是现实存在的输入。
Problem:
Proactive tick and cron fire callbacks launch detached async work. Failures in
prompt preparation or queue insertion could surface as unhandled rejections or
be lost from diagnostics. In one-shot cron paths, the scheduler has already
decided the task fired.
**Acceptance criteria**: `queued -> running/completed/failed/cancelled` 保持允许。`running -> completed/failed/cancelled` 保持允许。任何 terminal status 拒绝后续 lifecycle 更新。Rejected transitions 不更新 managed-flow step state。
Evidence:
- `src/proactive/useProactive.ts` used a detached async IIFE without catch.
- `src/cli/print.ts` proactive and cron paths also detached async work.
- `src/hooks/useScheduledTasks.ts` cron callbacks detached async work.
---
Acceptance criteria:
- Detached proactive/cron fire work has explicit error logging.
- REPL proactive tick generation is non-reentrant.
- Tick generation stops queueing after hook unmount.
### AUT-003: Prevent proactive and scheduled-task async fire failures from becoming invisible
## AUT-004: Bound long-running daemon restart timers during shutdown
- Type: Bug | Priority: P1 | Status: Draft
- Patch status: Implemented in `fix/autonomy-lifecycle`.
Type: Bug
Priority: P1
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
**Problem**: Proactive tick 和 cron fire callbacks 启动 detached async work。prompt 准备或队列插入的失败可能表现为 unhandled rejections 或从诊断中丢失。在 one-shot cron 路径中,scheduler 已经决定 task 已触发。
Problem:
The daemon supervisor scheduled worker restarts with `setTimeout()` but did
not store, clear, or `unref()` the timer. Shutdown during backoff could keep
the supervisor alive until the timer fired, forcing the stop path toward
SIGKILL.
**Evidence**: `src/proactive/useProactive.ts` 使用了无 catch 的 detached async IIFE。`src/cli/print.ts` 的 proactive 和 cron 路径也使用 detached async work。`src/hooks/useScheduledTasks.ts` cron callbacks 使用 detached async work。
Evidence:
- `src/daemon/main.ts` scheduled restart timers directly in the worker exit
handler.
- Shutdown only signaled child processes and did not clear restart timers.
**Acceptance criteria**: Detached proactive/cron fire work 有显式错误日志。REPL proactive tick generation 是 non-reentrant 的。Hook unmount 后 Tick generation 停止排队。
Acceptance criteria:
- Worker restart timers are tracked per worker.
- Shutdown clears any pending restart timers.
- Restart and force-kill grace timers do not keep the supervisor alive alone.
---
## AUT-005: Release autonomy persistence lock bookkeeping after each chain
### AUT-004: Bound long-running daemon restart timers during shutdown
Type: Bug
Priority: P1
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
- Type: Bug | Priority: P1 | Status: Draft
- Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
`withAutonomyPersistenceLock` stored a chained promise in its map but compared
the map value against the raw current promise during cleanup. That condition
never matched, so root-level lock bookkeeping could accumulate in long-lived
processes that touch many workspaces.
**Problem**: daemon supervisor 使用 `setTimeout()` 调度 worker 重启,但没有存储、清除或 `unref()` timer。在 backoff 期间 shutdown 可能让 supervisor 保持存活直到 timer 触发,迫使 stop 路径走向 SIGKILL。
Evidence:
- `src/utils/autonomyPersistence.ts` stored `previous.then(() => current)`.
- Cleanup compared `persistenceLocks.get(key) === current`.
**Evidence**: `src/daemon/main.ts` 在 worker exit handler 中直接调度 restart timers。Shutdown 仅通知子进程,未清除 restart timers。
Acceptance criteria:
- The stored chained promise is the value used for cleanup comparison.
- Existing serialization behavior for same-root calls remains unchanged.
- Tests directly assert same-root lock bookkeeping returns to zero after both
success and failure.
**Acceptance criteria**: Worker restart timers 按 worker 跟踪。Shutdown 清除所有 pending restart timers。Restart 和 force-kill grace timers 不单独保持 supervisor 存活。
## AUT-006: Add active-record protection before persistence truncation
---
Type: Reliability
Priority: P2
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
### AUT-005: Release autonomy persistence lock bookkeeping after each chain
Problem:
Autonomy runs and flows are capped by latest-created/updated order only.
Under high churn, active `queued` or `running` records can be truncated before
completion, which removes recovery evidence and can break managed-flow
advancement.
- Type: Bug | Priority: P1 | Status: Draft
- Patch status: Implemented in `fix/autonomy-lifecycle`.
Evidence:
- `src/utils/autonomyRuns.ts` keeps the latest 200 runs by `createdAt`.
- `src/utils/autonomyFlows.ts` keeps the latest 100 flows by `updatedAt`.
**Problem**: `withAutonomyPersistenceLock` 在 map 中存储了 chained promise,但在 cleanup 时将 map 值与 raw current promise 比较。条件永远不匹配,导致 root-level lock bookkeeping 在长期运行的进程中累积。
Acceptance criteria:
- Active records are retained before completed historical records are trimmed.
- Tests cover trimming with more than the configured cap and active records
near the tail.
**Evidence**: `src/utils/autonomyPersistence.ts` 存储 `previous.then(() => current)`。Cleanup 比较 `persistenceLocks.get(key) === current`。
## AUT-007: Treat provider API-error responses as failed autonomy turns
**Acceptance criteria**: 存储的 chained promise 是 cleanup 比较使用的值。Same-root calls 的序列化行为不变。测试直接断言 success 和 failure 后 same-root lock bookkeeping 归零。
Type: Bug
Priority: P0
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
---
Problem:
Third-party provider adapters can convert provider failures into synthetic
assistant API-error messages instead of throwing. `query.ts` treated
`isApiErrorMessage` terminal responses as `completed`, so an autonomy command
that had already been consumed as a queued attachment could be marked
completed and advance its managed flow even though the provider call failed.
### AUT-006: Add active-record protection before persistence truncation
Evidence:
- `src/services/api/openai/index.ts`, `src/services/api/gemini/index.ts`, and
`src/services/api/grok/index.ts` yield `createAssistantAPIErrorMessage()` on
adapter errors.
- `src/query.ts` skipped stop hooks for API-error assistant messages but
returned `reason: 'completed'`.
- Top-level autonomy finalization used terminal completion to decide whether
to mark consumed runs completed or failed.
- Type: Reliability | Priority: P2 | Status: Draft
- Patch status: Implemented in `fix/autonomy-lifecycle`.
Acceptance criteria:
- Provider API-error assistant messages terminate the query with
`reason: 'model_error'`.
- Any consumed autonomy run is marked failed rather than completed.
- Managed flows do not advance to the next step after provider API errors.
- A regression test simulates provider error after a queued autonomy attachment
has been consumed.
**Problem**: Autonomy runs 和 flows 仅按 latest-created/updated order 截断。高 churn 场景下,active 的 `queued` 或 `running` 记录可能在完成前被截断,移除恢复证据并可能中断 managed-flow advancement。
## AUT-008: Finalize consumed autonomy runs on async-generator close
**Evidence**: `src/utils/autonomyRuns.ts` 保留最新 200 条 runs(按 `createdAt`)。`src/utils/autonomyFlows.ts` 保留最新 100 条 flows(按 `updatedAt`)。
Type: Bug
Priority: P0
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
**Acceptance criteria**: Active records 在 completed historical records 被 trim 之前保留。测试覆盖超出配置上限且 active records 在尾部附近的 trim 场景。
Problem:
`query()` is an async generator. When its consumer calls `.return()` or breaks
out of iteration, JavaScript executes `finally` blocks and skips code after the
`try/finally`. The previous autonomy finalization ran after the `finally`, so
queued autonomy commands that had already been claimed as `running` could stay
persisted as `running` forever if the REPL/SDK consumer closed the generator.
---
Evidence:
- Claimed run IDs were collected during queued attachment injection.
- Completion/failure finalization happened only after `yield* queryLoop(...)`
returned normally or threw.
- Claude cross-validation flagged this as a durable run/flow leak.
### AUT-007: Treat provider API-error responses as failed autonomy turns
Acceptance criteria:
- Consumed autonomy runs are finalized from a `finally` path.
- Normal completion marks consumed runs completed and enqueues next managed
flow steps.
- Provider/model errors mark consumed runs failed.
- Generator close and user abort terminals mark consumed runs cancelled.
- A regression test closes the generator after a queued autonomy attachment and
verifies the run/flow are cancelled, not left running.
- Type: Bug | Priority: P0 | Status: Draft
- Patch status: Implemented in `fix/autonomy-lifecycle`.
## AUT-009: Claim queued autonomy runs before attachment injection
**Problem**: 第三方 provider adapters 可以将 provider failures 转换为合成的 assistant API-error 消息而非抛出异常。`query.ts` 将 `isApiErrorMessage` terminal responses 视为 `completed`,导致已消费的 autonomy command 被标记为 completed 并推进 managed flow,即使 provider 调用实际失败。
Type: Bug
Priority: P0
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
**Evidence**: `src/services/api/openai/index.ts`、`src/services/api/gemini/index.ts`、`src/services/api/grok/index.ts` 在 adapter errors 时 yield `createAssistantAPIErrorMessage()`。`src/query.ts` 跳过 stop hooks 但返回 `reason: 'completed'`。
Problem:
The query loop filtered stale queued autonomy commands before attachment
generation, but it did not claim runs as `running` until after attachments were
already yielded. A concurrent cancellation between those steps could still send
a cancelled prompt into the model context.
**Acceptance criteria**: Provider API-error assistant messages 以 `reason: 'model_error'` 终止 query。已消费的 autonomy run 标记为 failed 而非 completed。Provider API errors 后 managed flows 不推进到下一步。
Evidence:
- `partitionConsumableQueuedAutonomyCommands()` only checked persisted status.
- `markAutonomyRunRunning()` previously ran after `getAttachmentMessages()`.
- Reviewer cross-validation identified the check-then-act race.
---
Acceptance criteria:
- Query claims queued autonomy runs before passing commands to attachment
generation.
- Only successfully claimed commands are injected as queued-command
attachments.
- Failed claims are treated as stale and removed from the in-memory queue.
- Claiming reads persisted run state once per turn rather than once per
command.
### AUT-008: Finalize consumed autonomy runs on async-generator close
## AUT-010: Cancel proactive and cron runs dropped before enqueue
- Type: Bug | Priority: P0 | Status: Draft
- Patch status: Implemented in `fix/autonomy-lifecycle`.
Type: Bug
Priority: P1
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
**Problem**: `query()` 是一个 async generator。当 consumer 调用 `.return()` 或跳出迭代时,JavaScript 执行 `finally` 块并跳过 `try/finally` 之后的代码。之前的 autonomy finalization 在 `finally` 之后运行,导致已 claimed 为 `running` 的 queued autonomy commands 在 REPL/SDK consumer 关闭 generator 后永远保持 `running` 状态。
Problem:
`/proactive` and scheduled-task producers persist autonomy runs before
returning queue commands. If the component is disposed or headless input closes
after persistence but before enqueue, the queued run is left on disk with no
in-memory command to consume it.
**Evidence**: Claimed run IDs 在 queued attachment 注入期间收集。Completion/failure finalization 仅在 `yield* queryLoop(...)` 正常返回或抛出后执行。
Evidence:
- `createProactiveAutonomyCommands()` commits runs before returning commands.
- `commitAutonomyQueuedPrompt()` persists scheduled-task runs before callers
enqueue them.
- Callers checked `disposed` / `inputClosed` after command creation and could
return without terminalizing the run.
**Acceptance criteria**: Consumed autonomy runs 从 `finally` 路径 finalize。Normal completion 标记 consumed runs 为 completed 并入队下一步 managed flow。Generator close 和 user abort terminals 标记 consumed runs 为 cancelled。
Acceptance criteria:
- Proactive hook cancellation checks run both before commit and after command
creation.
- Headless proactive and cron paths cancel any already-created command that is
dropped due to input close.
- REPL scheduled-task cleanup cancels already-created commands when unmounted.
- A regression test verifies a proactive command created but dropped before
enqueue is marked cancelled.
---
## AUT-011: Replace query transition `any` stubs with typed contracts
### AUT-009: Claim queued autonomy runs before attachment injection
Type: Test/Type Safety
Priority: P2
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
- Type: Bug | Priority: P0 | Status: Draft
- Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
`src/query/transitions.ts` defined both `Terminal` and `Continue` as `any`.
That allowed new terminal reasons such as `model_error` and continuation
reasons such as `collapse_drain_retry` to drift without compiler checks.
**Problem**: Query loop 在 attachment generation 之前过滤了 stale queued autonomy commands,但直到 attachments 已经 yield 后才将 runs 标记为 `running`。在这两步之间的并发 cancel 仍然可以将 cancelled prompt 发送到模型上下文中。
Evidence:
- Claude cross-validation flagged the `Terminal = any` contract as a remaining
issue.
- Tightening the type immediately caught that
`collapse_drain_retry.committed` is a `number`, not a `boolean`.
**Evidence**: `partitionConsumableQueuedAutonomyCommands()` 仅检查持久化状态。`markAutonomyRunRunning()` 之前在 `getAttachmentMessages()` 之后运行。
Acceptance criteria:
- `Terminal` is a concrete union of query terminal reasons.
- `Continue` is a concrete union of continuation reasons and payloads.
- `bun run typecheck` validates all query return sites against that contract.
**Acceptance criteria**: Query 在将 commands 传递给 attachment generation 之前 claim queued autonomy runs。只有成功 claimed 的 commands 被注入为 queued-command attachments。Failed claims 视为 stale 并从内存队列移除。
## AUT-012: Avoid provider test settings-module mock pollution
---
Type: Test Reliability
Priority: P2
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
### AUT-010: Cancel proactive and cron runs dropped before enqueue
Problem:
The provider tests previously mocked `settings.js`. A minimal mock broke other
tests that imported additional settings exports in the same Bun process; the
expanded mock avoided the failure but over-coupled the provider test to
unrelated settings internals.
- Type: Bug | Priority: P1 | Status: Draft
- Patch status: Implemented in `fix/autonomy-lifecycle`.
Evidence:
- Full test runs observed cross-file settings mock pollution.
- `src/utils/model/providers.ts` only needs the real `getInitialSettings()`
behavior.
**Problem**: `/proactive` 和 scheduled-task producers 在返回 queue commands 之前持久化 autonomy runs。如果组件在持久化之后、enqueue 之前被 dispose 或 headless input 关闭,queued run 留在磁盘上但没有内存 command 来消费它。
Acceptance criteria:
- Provider tests do not mock `settings.js`.
- `modelType` precedence is exercised through an injected settings snapshot,
leaving global bootstrap state untouched.
- Provider tests pass when run alongside permissions tests and the provider
matrix.
**Evidence**: `createProactiveAutonomyCommands()` 在返回 commands 之前提交 runs。`commitAutonomyQueuedPrompt()` 在 callers enqueue 之前持久化 scheduled-task runs。
**Acceptance criteria**: Proactive hook cancellation 在 commit 前和 command creation 后都检查。Headless proactive 和 cron 路径在 input close 导致 command 被丢弃时 cancel 已创建的 command。REPL scheduled-task cleanup 在 unmount 时 cancel 已创建的 commands。
---
### AUT-011: Replace query transition `any` stubs with typed contracts
- Type: Test/Type Safety | Priority: P2 | Status: Draft
- Patch status: Implemented in `fix/autonomy-lifecycle`.
**Problem**: `src/query/transitions.ts` 将 `Terminal` 和 `Continue` 都定义为 `any`。这允许新的 terminal reasons(如 `model_error`)和 continuation reasons(如 `collapse_drain_retry`)在没有编译器检查的情况下漂移。
**Acceptance criteria**: `Terminal` 是 query terminal reasons 的具体 union。`Continue` 是 continuation reasons 和 payloads 的具体 union。`bun run typecheck` 验证所有 query return sites。
---
### AUT-012: Avoid provider test settings-module mock pollution
- Type: Test Reliability | Priority: P2 | Status: Draft
- Patch status: Implemented in `fix/autonomy-lifecycle`.
**Problem**: Provider tests 之前 mock 了 `settings.js`。Minimal mock 在同一 Bun 进程中破坏了导入额外 settings exports 的其他测试;扩展后的 mock 避免了失败但过度耦合了 provider test 到无关的 settings 内部实现。
**Acceptance criteria**: Provider tests 不 mock `settings.js`。`modelType` precedence 通过注入的 settings snapshot 测试,不触碰全局 bootstrap state。Provider tests 在与 permissions tests 和 provider matrix 一起运行时通过。
## 关联笔记
- [[agent-comm-fix-jira-tasks]] - Agent 通讯修复 Jira Task
- [[agent-comm-fix-questions]] - Agent 通讯修复问题文档
- [[three-tier-gating]] - 三层门禁系统