fix(tui): skip tool_execution_start bookkeeping entries in ask recovery walk

#recoverAskReanswerQuestions previously bailed on any non-message-typed
ancestor, but #recordToolExecutionStart() appends a custom
tool_execution_start entry between the assistant message and every
toolResult in real persisted sessions. The walk now generically skips
any ancestor that isn't the target assistant message (or a turn-
boundary user message), so the normal single-tool-call ask case
recovers correctly instead of always falling back to a plain leaf
move.
This commit is contained in:
Mathews-Tom
2026-07-18 00:56:48 +05:30
parent 0a1013122f
commit 70990e31e8
2 changed files with 52 additions and 18 deletions
@@ -16993,30 +16993,34 @@ export class AgentSession {
/**
* Look up the `ask` toolCall's persisted `arguments` and validate them
* back into `questions`, for `/tree` `ask` re-answer (issue #5642). Walks
* up from the toolResult's parent past any interleaved sibling toolResults
* — `ask` runs `exclusive` (serialized execution) but a multi-tool-call
* assistant turn can still emit other tool calls first, so the persisted
* *parent* of the `ask` toolResult may be one of those toolResults rather
* than the assistant message itself (roboomp review on #5895) — until it
* finds the assistant entry that actually emitted `toolCallId`. Returns
* `undefined` when no ancestor entry holds a matching `ask` toolCall, or
* the arguments can't be resolved — the caller falls back to a plain leaf
* move rather than opening a picker with bad data.
* up from the toolResult's parent past any interleaved ancestor entries
* — sibling toolResults from other tool calls in the same turn (`ask`
* runs `exclusive`, which only serializes *execution*, not persistence
* order — roboomp review on #5895), and bookkeeping entries such as the
* `tool_execution_start` custom entry `#recordToolExecutionStart()`
* appends before every toolResult in real persisted sessions (chatgpt-codex
* review on #5895) — until it finds the assistant entry that actually
* emitted `toolCallId`. Stops at a `user` message (turn boundary) or a
* dead end. Returns `undefined` when no ancestor entry holds a matching
* `ask` toolCall, or the arguments can't be resolved — the caller falls
* back to a plain leaf move rather than opening a picker with bad data.
*/
#recoverAskReanswerQuestions(parentId: string | null, toolCallId: string): AskToolInput["questions"] | undefined {
let current = parentId;
while (current !== null) {
const entry = this.sessionManager.getEntry(current);
if (entry?.type !== "message") return undefined;
if (entry.message.role === "assistant") {
const toolCall = entry.message.content.find(
(block): block is AgentToolCall => block.type === "toolCall" && block.id === toolCallId,
);
if (!toolCall) return undefined;
if (toolCall.name !== "ask") return undefined;
return recoverAskQuestions(toolCall.arguments);
if (!entry) return undefined;
if (entry.type === "message") {
if (entry.message.role === "assistant") {
const toolCall = entry.message.content.find(
(block): block is AgentToolCall => block.type === "toolCall" && block.id === toolCallId,
);
if (!toolCall) return undefined;
if (toolCall.name !== "ask") return undefined;
return recoverAskQuestions(toolCall.arguments);
}
if (entry.message.role === "user") return undefined;
}
if (entry.message.role !== "toolResult") return undefined;
current = entry.parentId;
}
return undefined;