ACP: session rejects a new prompt as busy after answering the cancelled prompt with 'cancelled'

Đang mở
#569 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
53/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Lĩnh vực
backend-api-design, cli

Hướng nghiên cứu

Start at the mcode acp session prompt and cancellation handling, then trace how an in-flight bash tool call is torn down and when the session's active-turn state is cleared. Compare the tool-call reproduction with the control run without a tool call in flight. Add or locate a regression test for sending a prompt immediately after a cancelled response; done when that prompt is accepted without waiting for a delay.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Product or interface

CLI — ACP (mcode acp), version 0.7.1 (macOS arm64)

Use case and problem

An ACP client that cancels a prompt turn and then sends a new prompt on the same session gets the new prompt rejected as busy, even though the agent already answered the cancelled prompt with stopReason: "cancelled".

Sequence that reproduces it:

  1. session/prompt starts a turn that runs a bash tool call (sleep 45).
  2. While the tool call is in_progress, the client sends session/cancel.
  3. The agent answers the original session/prompt with stopReason: "cancelled" (~40 ms later).
  4. The client immediately sends another session/prompt on the same session.
  5. The agent rejects it:
Internal error: MiniMax Code Runtime failed: Session already has an active Turn. Use queue send to deliver the message after it.
code: -32603

Sending the same prompt ~110 ms later succeeds.

Expected: once the agent has answered the cancelled prompt with the cancelled stop reason, the completed turn no longer prevents a new prompt on that session. Per the ACP cancellation spec the response marks "Agent completes prompt cancellation", so a client that waits for that response — as the spec explicitly allows — still cannot use the session.

Reproduction

Raw log (timestamps relative to process start, mcode acp over stdio via @agentclientprotocol/sdk 0.17.1):

[  8707ms] tool_call: bash
[  8808ms] tool call is running - cancelling
[ 10312ms] session/cancel sent (returned after 2 ms - it is a notification)
[ 10351ms] prompt#1: stopReason=cancelled after 41 ms
[ 10357ms] prompt#2 (sent 0ms after the cancelled answer): ERROR -32603 Internal error: MiniMax Code Runtime failed: Session already has an active Turn. Use queue send to deliver the message after it. after 6 ms
[ 10412ms] update: session_info_update {"updatedAt":"2026-10-30T15:39:56.835Z","sessionUpdate":"session_info_update"}
[ 10676ms] update: session_info_update {"updatedAt":"2026-10-30T15:39:57.099Z","sessionUpdate":"session_info_update"}
[ 11293ms] update: session_info_update {"updatedAt":"2026-10-30T15:39:57.715Z","sessionUpdate":"session_info_update"}
[ 11299ms] update: usage_update {"cost":{"amount":0,"currency":"USD"},"size":512000,"used":16743,"sessionUpdate":"usage_update"}
[ 11302ms] update: usage_update {"cost":{"amount":0,"currency":"USD"},"size":512000,"used":16743,"sessionUpdate":"usage_update"}
[ 11302ms] prompt#2 (sent 109ms after the cancelled answer): OK stopReason=end_turn after 842 ms
[ 12334ms] prompt#2 (sent 1253ms after the cancelled answer): OK stopReason=end_turn after 730 ms
[ 14017ms] prompt#2 (sent 2985ms after the cancelled answer): OK stopReason=end_turn after 681 ms

Control run without a tool call in flight — there the next prompt succeeds immediately after the cancelled answer, so the problem is tied to the tool call being torn down:

[  7931ms] session/cancel sent (returned after 0 ms - it is a notification)
[  8204ms] prompt#1 answered: {"stopReason":"cancelled"} after 273 ms
[ 10731ms] prompt#2: OK ({"stopReason":"end_turn"}) after 2527 ms

Note also that no terminal tool_call_update is emitted for the cancelled tool call — the only signal that the turn ended is the cancelled answer to the prompt.

Reproduction script
#!/usr/bin/env node
// Repro: mcode acp rejects the next prompt as busy even AFTER it answered the
// cancelled prompt with stopReason: "cancelled".
import { spawn } from "node:child_process";
import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
import { Writable, Readable } from "node:stream";

const t0 = Date.now();
const ts = () => String(Date.now() - t0).padStart(6, " ");
const log = (...a) => console.log(`[${ts()}ms]`, ...a);

const child = spawn("mcode", ["acp"], { stdio: ["pipe", "pipe", "pipe"] });
child.stderr.on("data", (d) => process.env.DEBUG_ACP && log("stderr:", d.toString().trim()));

const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));

let toolStarted = false;
const client = {
  sessionUpdate: async (params) => {
    const u = params.update ?? {};
    if (u.sessionUpdate === "tool_call") {
      toolStarted = true;
      log("tool_call:", u.title ?? u.toolCallId);
      return;
    }
    if (u.sessionUpdate === "tool_call_update") {
      if (u.status === "in_progress") log("tool_call_update in_progress:", u.title ?? "");
      return;
    }
    if (u.sessionUpdate === "agent_message_chunk") return;
    log("update:", u.sessionUpdate, JSON.stringify(u).slice(0, 200));
  },
  requestPermission: async () => ({ outcome: { outcome: "cancelled" } }),
};

const conn = new ClientSideConnection(() => client, stream);
await conn.initialize({ protocolVersion: 1, clientCapabilities: { fs: {} } });
const { sessionId } = await conn.newSession({ cwd: process.cwd(), mcpServers: [] });
log("session:", sessionId);

const prompt1 = conn
  .prompt({
    sessionId,
    messageId: "m1",
    prompt: [{ type: "text", text: "Выполни в bash команду `sleep 45` и дождись её завершения." }],
  })
  .then((r) => ({ ok: true, r }), (e) => ({ ok: false, e }));

const deadline = Date.now() + 30000;
while (!toolStarted && Date.now() < deadline) await new Promise((r) => setTimeout(r, 100));
log(toolStarted ? "tool call is running - cancelling" : "tool call did not start - cancelling anyway");
await new Promise((r) => setTimeout(r, 1500));

const tCancel = Date.now();
await conn.cancel({ sessionId });
log("session/cancel sent (returned after", Date.now() - tCancel, "ms - it is a notification)");

const res1 = await prompt1;
const tResponse = Date.now();
log(
  "prompt#1:",
  res1.ok ? `stopReason=${res1.r.stopReason}` : `ERROR ${res1.e?.code} ${String(res1.e?.message).slice(0, 150)}`,
  "after",
  tResponse - tCancel,
  "ms",
);

for (const waitMs of [0, 100, 300, 1000]) {
  if (waitMs) await new Promise((r) => setTimeout(r, waitMs));
  const sentAt = Date.now();
  const res = await conn
    .prompt({
      sessionId,
      messageId: `m2-${waitMs}`,
      prompt: [{ type: "text", text: "Reply with one word: ok" }],
    })
    .then((r) => ({ ok: true, r }), (e) => ({ ok: false, e }));
  log(
    `prompt#2 (sent ${sentAt - tResponse}ms after the cancelled answer): ${
      res.ok
        ? `OK stopReason=${res.r.stopReason}`
        : `ERROR ${res.e?.code} ${String(res.e?.message).slice(0, 180)}`
    }`,
    "after",
    Date.now() - sentAt,
    "ms",
  );
}

child.kill("SIGTERM");
process.exit(0);
Additional context

This is the provider side of a busy-session problem reported against the Paseo ACP client: https://github.com/getpaseo/paseo/issues/3964

The related request to queue prompts for a busy session instead of rejecting them is #567.

Ngôn ngữ chính
TypeScript
Star
2.1k
Fork
271
Merge trung bình
16 giờ 32 phút
Pull request đã merge (30 ngày)
32

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của MiniMax-AI/minimax-code

Tất cả issue của MiniMax-AI/minimax-code

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.