Merge PR #8608: fix(tui): wait for wire-aliased edit previews (@roboomp)
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user