fix(coding-agent/session): cleared todo reminder and track assistant message synchronously
- Moved `#todoReminderAwaitingProgress` clearing into tool result handler for synchronous state update. - Moved `#lastAssistantMessage` tracking to message_end to prevent stale reads when events land in same tick.
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Browser tool selectors now accept bare snapshot refs (`tab.click("e501")`, `@e501`) everywhere `aria-ref=e501` works — previously the tab-worker backend fell through to a CSS tag selector that could never match, burning the 2s zero-match watchdog with a misleading "matches no elements" hint. `tab.select`, `tab.uploadFile`, `tab.press({ selector })`, `tab.screenshot({ selector })`, and `tab.drag` now resolve refs too. Unknown/stale refs fail immediately with the "refresh refs" error.
|
||||
- `tab.select` no longer double-reports the previously selected option of a single `<select>`: the returned selection is read back after the full assignment pass instead of mid-loop.
|
||||
- Fixed transcript blocks being visibly duplicated during streaming (whole tool boxes and assistant paragraphs recommitted below their first copy on the terminal tape) by removing transcript committed-prefix compaction entirely. Dropping committed rows from the transcript's local frame shifted the frame under the engine's committed-prefix ledger, so the audit re-anchored and recommitted rows the tape already held. The transcript now always keeps its full local frame; committed finalized blocks still skip `render()` via the segment reuse bypass. Reverts the compaction half of [#5930](https://github.com/can1357/oh-my-pi/issues/5930)'s fix (compose keeps the render bypass; the local frame is no longer truncated).
|
||||
- Fixed classifier refusals (e.g. Anthropic `stop_reason: "refusal"`) ending the turn with no visible error. Two independent regressions: (1) session events reached subscribers out of order when a turn's provider events landed in one tick — extension emits only await for event types with registered handlers, so the assistant `message_end` overtook its own `message_start` and the TUI skipped the error render entirely (no pinned banner, no inline `Error:` line); subscriber fan-out is now serialized in emission order. (2) Refusal turns are pruned from active context at settle (#3591), which also erased them from `state.messages` before `prompt()` resolved — print mode printed nothing and exited 0, and the task executor's `getLastAssistantMessage()` saw the previous turn. The pruned refusal is now retained until the next run starts, `getLastAssistantMessage()` reports it, and print mode reads the settled assistant via that accessor (exit 1 + refusal message on stderr).
|
||||
|
||||
- Fixed classifier refusals (e.g. Anthropic `stop_reason: "refusal"`) ending the turn with no visible error. Two independent regressions: (1) session events reached subscribers out of order when a turn's provider events landed in one tick — extension emits only await for event types with registered handlers, so the assistant `message_end` overtook its own `message_start` and the TUI skipped the error render entirely (no pinned banner, no inline `Error:` line); subscriber fan-out is now serialized in emission order. (2) Refusal turns are pruned from active context at settle (#3591), which also erased them from `state.messages` before `prompt()` resolved — print mode printed nothing and exited 0, and the task executor's `getLastAssistantMessage()` saw the previous turn. The pruned refusal is now retained until the next run starts, `getLastAssistantMessage()` reports it, and print mode reads the settled assistant via that accessor (exit 1 + refusal message on stderr). Additionally, `#lastAssistantMessage` is now set synchronously on `message_end` to prevent `agent_end` maintenance from reading a stale assistant turn when tool results and stops land in the same tick.
|
||||
|
||||
## [17.0.4] - 2026-07-18
|
||||
|
||||
|
||||
@@ -4497,6 +4497,19 @@ export class AgentSession {
|
||||
} else if (!isError && MID_RUN_TODO_NUDGE_MUTATING_TOOLS[toolName]) {
|
||||
this.#mutationsSinceLastTodoTouch++;
|
||||
}
|
||||
// A tool actually ran. Clear the post-reminder suppression synchronously
|
||||
// too: the settle check (`#checkTodoCompletion` in agent_end maintenance)
|
||||
// can otherwise read the stale flag when a tool result and the terminal
|
||||
// stop land in the same tick, swallowing the earned re-escalation.
|
||||
this.#todoReminderAwaitingProgress = false;
|
||||
}
|
||||
// Track the settled assistant turn synchronously as well: agent_end
|
||||
// maintenance reads `#lastAssistantMessage`, and when a turn's events all
|
||||
// land in one tick its handler can run before this handler's post-emit
|
||||
// bookkeeping — leaving maintenance looking at the previous (e.g.
|
||||
// toolUse) assistant message and skipping settle-only work.
|
||||
if (event.type === "message_end" && event.message.role === "assistant") {
|
||||
this.#lastAssistantMessage = event.message;
|
||||
}
|
||||
// Plan-mode internal transition: stamp `SILENT_ABORT_MARKER` on the
|
||||
// persisted message BEFORE the obfuscator's display-side copy below.
|
||||
@@ -4703,9 +4716,7 @@ export class AgentSession {
|
||||
}
|
||||
// Other message types (bashExecution, compactionSummary, branchSummary) are persisted elsewhere
|
||||
|
||||
// Track assistant message for auto-compaction (checked on agent_end)
|
||||
if (event.message.role === "assistant") {
|
||||
this.#lastAssistantMessage = event.message;
|
||||
const assistantMsg = event.message as AssistantMessage;
|
||||
// Fold this turn's timing into per-model perf aggregates (drives the
|
||||
// /models TPS/TTFT display). Errored turns measure nothing; aborted
|
||||
@@ -4775,10 +4786,6 @@ export class AgentSession {
|
||||
const details = isRecord(event.message.details) ? event.message.details : undefined;
|
||||
const semanticResult = semanticToolResult(toolName, event.message);
|
||||
const semanticDetails = isRecord(semanticResult?.details) ? semanticResult.details : undefined;
|
||||
// A tool actually ran. Clear the post-reminder suppression: the agent did
|
||||
// productive work in response to the prior nudge, so the next text-only stop
|
||||
// is allowed to escalate to the next reminder if todos remain incomplete.
|
||||
this.#todoReminderAwaitingProgress = false;
|
||||
// Invalidate streaming edit cache when edit tool completes to prevent stale data
|
||||
const editedPath = details ? getStringProperty(details, "path") : undefined;
|
||||
if (toolName === "edit" && editedPath) {
|
||||
|
||||
Reference in New Issue
Block a user