Merge PR #8608: fix(tui): wait for wire-aliased edit previews (@roboomp)

This commit is contained in:
can1357
2026-08-16 02:43:32 +02:00
3 changed files with 63 additions and 47 deletions
+3
View File
@@ -72,6 +72,9 @@
### Fixed
- Fixed the `auto` thinking classifier failing every turn on Anthropic models served through LiteLLM/Vertex with `max_tokens must be greater than thinking.budget_tokens`. The classifier's disabled-reasoning request is downgraded to the lowest reasoning effort on the `openai-completions` transport, which the proxy translates to an Anthropic thinking budget of at least 1024 tokens; the classifier now reserves enough output room (4096) to clear that budget instead of capping at exactly 1024 ([#8610](https://github.com/can1357/oh-my-pi/issues/8610)).
### Fixed
- Fixed `always-ask` approval prompts bypassing edit preview readiness when a built-in tool executes under its wire-level alias, such as `edit` running as `apply_patch` ([#8607](https://github.com/can1357/oh-my-pi/issues/8607)).
## [17.3.4] - 2026-08-14
@@ -265,7 +265,10 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
if (approvalCheck.required) {
const scheduledCall = context?.toolCall?.toolCalls[context.toolCall.index];
if (scheduledCall?.id === toolCallId && scheduledCall.name === this.tool.name) {
if (
scheduledCall?.id === toolCallId &&
(scheduledCall.name === this.tool.name || scheduledCall.name === this.tool.customWireName)
) {
await untilAborted(signal, () => this.runner.waitForToolApprovalPreview(toolCallId));
}
@@ -1988,54 +1988,64 @@ describe("ExtensionRunner", () => {
delete globalState.__approvalEvents;
});
it("does not present approval before the tool preview is ready", async () => {
const result = await loadTestExtensions();
const runner = new ExtensionRunner(
result.extensions,
result.runtime,
tempDir.path(),
sessionManager,
modelRegistry,
);
const preview = Promise.withResolvers<void>();
const order: string[] = [];
runner.setToolApprovalPreviewWaiter(async toolCallId => {
order.push(`preview_wait:${toolCallId}`);
await preview.promise;
order.push("preview_ready");
});
initializeRunner(
runner,
vi.fn(async () => {
order.push("ui_select");
return "Approve";
}),
);
const wrapper = new ExtensionToolWrapper(approvalTool, runner);
const execution = (wrapper as ExtensionToolWrapper<any>).execute("call-preview", {}, undefined, undefined, {
sessionManager,
modelRegistry,
model: undefined,
isIdle: () => true,
hasQueuedMessages: () => false,
abort: () => {},
settings: {
get: (key: string) => (key === "tools.approvalMode" ? "always-ask" : {}),
} as never,
toolCall: {
batchId: "batch-preview",
index: 0,
total: 1,
toolCalls: [{ id: "call-preview", name: "dangerous_tool" }],
it("does not present approval before canonical or wire-aliased tool previews are ready", async () => {
const cases = [
{ tool: approvalTool, wireName: "dangerous_tool", toolCallId: "call-preview" },
{
tool: { ...approvalTool, name: "edit", customWireName: "apply_patch" },
wireName: "apply_patch",
toolCallId: "call-aliased-preview",
},
});
await Promise.resolve();
expect(order).toEqual(["preview_wait:call-preview"]);
];
for (const { tool, wireName, toolCallId } of cases) {
const result = await loadTestExtensions();
const runner = new ExtensionRunner(
result.extensions,
result.runtime,
tempDir.path(),
sessionManager,
modelRegistry,
);
const preview = Promise.withResolvers<void>();
const order: string[] = [];
runner.setToolApprovalPreviewWaiter(async waitedToolCallId => {
order.push(`preview_wait:${waitedToolCallId}`);
await preview.promise;
order.push("preview_ready");
});
initializeRunner(
runner,
vi.fn(async () => {
order.push("ui_select");
return "Approve";
}),
);
preview.resolve();
await execution;
expect(order).toEqual(["preview_wait:call-preview", "preview_ready", "ui_select"]);
const wrapper = new ExtensionToolWrapper(tool, runner);
const execution = (wrapper as ExtensionToolWrapper<any>).execute(toolCallId, {}, undefined, undefined, {
sessionManager,
modelRegistry,
model: undefined,
isIdle: () => true,
hasQueuedMessages: () => false,
abort: () => {},
settings: {
get: (key: string) => (key === "tools.approvalMode" ? "always-ask" : {}),
} as never,
toolCall: {
batchId: `batch-${toolCallId}`,
index: 0,
total: 1,
toolCalls: [{ id: toolCallId, name: wireName }],
},
});
await Promise.resolve();
expect(order).toEqual([`preview_wait:${toolCallId}`]);
preview.resolve();
await execution;
expect(order).toEqual([`preview_wait:${toolCallId}`, "preview_ready", "ui_select"]);
}
});
it("emits resolved false when approval is denied", async () => {