fix: ACP both live and load share synthetic pending status preceeding… (#14916)
This commit is contained in:
@@ -270,25 +270,7 @@ export namespace ACP {
|
|||||||
const sessionId = session.id
|
const sessionId = session.id
|
||||||
|
|
||||||
if (part.type === "tool") {
|
if (part.type === "tool") {
|
||||||
if (!this.toolStarts.has(part.callID)) {
|
await this.toolStart(sessionId, part)
|
||||||
this.toolStarts.add(part.callID)
|
|
||||||
await this.connection
|
|
||||||
.sessionUpdate({
|
|
||||||
sessionId,
|
|
||||||
update: {
|
|
||||||
sessionUpdate: "tool_call",
|
|
||||||
toolCallId: part.callID,
|
|
||||||
title: part.tool,
|
|
||||||
kind: toToolKind(part.tool),
|
|
||||||
status: "pending",
|
|
||||||
locations: [],
|
|
||||||
rawInput: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
log.error("failed to send tool pending to ACP", { error })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (part.state.status) {
|
switch (part.state.status) {
|
||||||
case "pending":
|
case "pending":
|
||||||
@@ -829,25 +811,10 @@ export namespace ACP {
|
|||||||
|
|
||||||
for (const part of message.parts) {
|
for (const part of message.parts) {
|
||||||
if (part.type === "tool") {
|
if (part.type === "tool") {
|
||||||
|
await this.toolStart(sessionId, part)
|
||||||
switch (part.state.status) {
|
switch (part.state.status) {
|
||||||
case "pending":
|
case "pending":
|
||||||
this.bashSnapshots.delete(part.callID)
|
this.bashSnapshots.delete(part.callID)
|
||||||
await this.connection
|
|
||||||
.sessionUpdate({
|
|
||||||
sessionId,
|
|
||||||
update: {
|
|
||||||
sessionUpdate: "tool_call",
|
|
||||||
toolCallId: part.callID,
|
|
||||||
title: part.tool,
|
|
||||||
kind: toToolKind(part.tool),
|
|
||||||
status: "pending",
|
|
||||||
locations: [],
|
|
||||||
rawInput: {},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
log.error("failed to send tool pending to ACP", { error: err })
|
|
||||||
})
|
|
||||||
break
|
break
|
||||||
case "running":
|
case "running":
|
||||||
const output = this.bashOutput(part)
|
const output = this.bashOutput(part)
|
||||||
@@ -880,6 +847,7 @@ export namespace ACP {
|
|||||||
})
|
})
|
||||||
break
|
break
|
||||||
case "completed":
|
case "completed":
|
||||||
|
this.toolStarts.delete(part.callID)
|
||||||
this.bashSnapshots.delete(part.callID)
|
this.bashSnapshots.delete(part.callID)
|
||||||
const kind = toToolKind(part.tool)
|
const kind = toToolKind(part.tool)
|
||||||
const content: ToolCallContent[] = [
|
const content: ToolCallContent[] = [
|
||||||
@@ -959,6 +927,7 @@ export namespace ACP {
|
|||||||
})
|
})
|
||||||
break
|
break
|
||||||
case "error":
|
case "error":
|
||||||
|
this.toolStarts.delete(part.callID)
|
||||||
this.bashSnapshots.delete(part.callID)
|
this.bashSnapshots.delete(part.callID)
|
||||||
await this.connection
|
await this.connection
|
||||||
.sessionUpdate({
|
.sessionUpdate({
|
||||||
@@ -1116,6 +1085,27 @@ export namespace ACP {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async toolStart(sessionId: string, part: ToolPart) {
|
||||||
|
if (this.toolStarts.has(part.callID)) return
|
||||||
|
this.toolStarts.add(part.callID)
|
||||||
|
await this.connection
|
||||||
|
.sessionUpdate({
|
||||||
|
sessionId,
|
||||||
|
update: {
|
||||||
|
sessionUpdate: "tool_call",
|
||||||
|
toolCallId: part.callID,
|
||||||
|
title: part.tool,
|
||||||
|
kind: toToolKind(part.tool),
|
||||||
|
status: "pending",
|
||||||
|
locations: [],
|
||||||
|
rawInput: {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
log.error("failed to send tool pending to ACP", { error })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private async loadAvailableModes(directory: string): Promise<ModeOption[]> {
|
private async loadAvailableModes(directory: string): Promise<ModeOption[]> {
|
||||||
const agents = await this.config.sdk.app
|
const agents = await this.config.sdk.app
|
||||||
.agents(
|
.agents(
|
||||||
|
|||||||
@@ -572,6 +572,65 @@ describe("acp.agent event subscription", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("does not emit duplicate synthetic pending after replayed running tool", async () => {
|
||||||
|
await using tmp = await tmpdir()
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const { agent, controller, sessionUpdates, stop, sdk } = createFakeAgent()
|
||||||
|
const cwd = "/tmp/opencode-acp-test"
|
||||||
|
const sessionId = await agent.newSession({ cwd, mcpServers: [] } as any).then((x) => x.sessionId)
|
||||||
|
const input = { command: "echo hi", description: "run command" }
|
||||||
|
|
||||||
|
sdk.session.messages = async () => ({
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
role: "assistant",
|
||||||
|
sessionID: sessionId,
|
||||||
|
},
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
type: "tool",
|
||||||
|
callID: "call_1",
|
||||||
|
tool: "bash",
|
||||||
|
state: {
|
||||||
|
status: "running",
|
||||||
|
input,
|
||||||
|
metadata: { output: "hi\n" },
|
||||||
|
time: { start: Date.now() },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
await agent.loadSession({ sessionId, cwd, mcpServers: [] } as any)
|
||||||
|
controller.push(
|
||||||
|
toolEvent(sessionId, cwd, {
|
||||||
|
callID: "call_1",
|
||||||
|
tool: "bash",
|
||||||
|
status: "running",
|
||||||
|
input,
|
||||||
|
metadata: { output: "hi\nthere\n" },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
await new Promise((r) => setTimeout(r, 20))
|
||||||
|
|
||||||
|
const types = sessionUpdates
|
||||||
|
.filter((u) => u.sessionId === sessionId)
|
||||||
|
.map((u) => u.update)
|
||||||
|
.filter((u) => "toolCallId" in u && u.toolCallId === "call_1")
|
||||||
|
.map((u) => u.sessionUpdate)
|
||||||
|
.filter((u) => u === "tool_call" || u === "tool_call_update")
|
||||||
|
|
||||||
|
expect(types).toEqual(["tool_call", "tool_call_update", "tool_call_update"])
|
||||||
|
stop()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("clears bash snapshot marker on pending state", async () => {
|
test("clears bash snapshot marker on pending state", async () => {
|
||||||
await using tmp = await tmpdir()
|
await using tmp = await tmpdir()
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
|
|||||||
Reference in New Issue
Block a user