fix: show operation details in OpenCode tool records

Preserve OpenCode operation titles in main-session tool summaries, including permission waits, failures and interrupted calls. Keep title trimming and length limits aligned with subagent records.

Update tool display documentation and include the activity-menu wording in UI-DESIGN.md. Validation: 132 focused tests passed; main-process typecheck and changed-file lint passed. Full-suite checks were affected by concurrent activity-menu changes and a test timeout.

Release note: OpenCode main-session tool records now show file paths, commands or operation descriptions beside tool names, matching subagent records.
This commit is contained in:
mesalogo
2026-09-13 22:55:12 +08:00
parent 49377206cc
commit ba6f6b8f22
3 changed files with 81 additions and 7 deletions
+67 -2
View File
@@ -2517,7 +2517,11 @@ describe("OpenCodeRuntime embedded permission mediation", () => {
await runtime.dispose();
});
it("keeps non-Task tools with Task-shaped input as generic tools", async () => {
it.each([
[" Custom delegate ", "Custom delegate"],
["", "OpenCode 工具:custom_delegate"],
["x".repeat(300), "x".repeat(240)],
])("keeps generic tool titles bounded: %s", async (title, summary) => {
const setup = runClient([
{
id: "custom-tool-completed",
@@ -2536,7 +2540,7 @@ describe("OpenCodeRuntime embedded permission mediation", () => {
prompt: "Inspect the complete source tree.",
},
output: "Custom tool result.",
title: "Custom delegate",
title,
metadata: {},
time: { start: 1, end: 2 },
},
@@ -2559,12 +2563,73 @@ describe("OpenCodeRuntime embedded permission mediation", () => {
callId: "call-custom-1",
name: "custom_delegate",
state: "completed",
summary,
}),
);
expect(events.some((event) => event.type === "subagent")).toBe(false);
await runtime.dispose();
});
it.each(["error", "interrupted"])("retains the running tool title after %s", async (ending) => {
const part = {
id: "part-read-1",
callID: "call-read-1",
type: "tool" as const,
tool: "read",
};
const setup = runClient([
{
id: "read-running",
type: "message.part.updated",
properties: {
sessionID: "session-1",
part: {
...part,
state: {
status: "running",
input: { filePath: "README.md" },
title: "README.md",
time: { start: 1 },
},
},
},
},
...(ending === "error" ? [{
id: "read-error",
type: "message.part.updated" as const,
properties: {
sessionID: "session-1",
part: {
...part,
state: {
status: "error" as const,
input: { filePath: "README.md" },
error: "File not found",
time: { start: 1, end: 2 },
},
},
},
}] : []),
]);
const runtime = embeddedRuntime(setup.client);
const events: RuntimeEvent[] = [];
await expect((async () => {
for await (const event of runtime.run({
requestId: "3f496642-f47d-4e0a-8944-a32c77b0d6ef",
conversationId: "conversation-1",
prompt: "Read README.md",
workMode: "execute",
}, new AbortController().signal)) {
events.push(event);
}
})()).rejects.toThrow();
expect(events.filter((event) => event.type === "tool")).toEqual([
expect.objectContaining({ state: "running", summary: "README.md" }),
expect.objectContaining({ state: "failed", summary: "README.md" }),
]);
await runtime.dispose();
});
it("does not impose a default wall-clock deadline on OpenCode runs", async () => {
vi.useFakeTimers();
try {