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

Aperta
#569 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
53/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
javascript, node.js, typescript

Direzione di ricerca

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.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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.

Lingua principale
TypeScript
Stelle
2.1k
Fork
271
Merge medio
16h 6m
PR unite (30g)
32

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di MiniMax-AI/minimax-code

Tutte le issue di MiniMax-AI/minimax-code

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.