mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-13 21:02:08 +00:00
Compare commits
3 Commits
v0.106.1-p
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e857d59ec | ||
|
|
c5c120dba9 | ||
|
|
4f474d655b |
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.106.0"
|
||||
".": "0.107.0"
|
||||
}
|
||||
|
||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,5 +1,26 @@
|
||||
# Changelog
|
||||
|
||||
## [0.107.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.106.0...v0.107.0) (2026-11-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* report the turn's rate limit in rate-limit failure errors ([#1610](https://github.com/agentclientprotocol/claude-agent-acp/issues/1610)) ([4f474d6](https://github.com/agentclientprotocol/claude-agent-acp/commit/4f474d655b88a075ec7d0124d45cafa0f5565af7))
|
||||
* report unknown subagent state at a conversation reset and reference children from tool calls ([#1608](https://github.com/agentclientprotocol/claude-agent-acp/issues/1608)) ([4804917](https://github.com/agentclientprotocol/claude-agent-acp/commit/4804917a6fa7050490d1d62c6689e78c231c752a))
|
||||
* say the cause of an api retry in the AIR warning details ([#1594](https://github.com/agentclientprotocol/claude-agent-acp/issues/1594)) ([4f690e1](https://github.com/agentclientprotocol/claude-agent-acp/commit/4f690e129c01f22e2c2a87e0e0a3c5b503241f15))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* assume a 1M context window until Claude Code reports it ([#1613](https://github.com/agentclientprotocol/claude-agent-acp/issues/1613)) ([9619805](https://github.com/agentclientprotocol/claude-agent-acp/commit/96198053067cf4d7b7440c52036174ad4f54183e))
|
||||
* end the tool calls of a response that Claude Code took back ([#1616](https://github.com/agentclientprotocol/claude-agent-acp/issues/1616)) ([21f3e04](https://github.com/agentclientprotocol/claude-agent-acp/commit/21f3e0430b693e6b04750ab3018e1eaf726af173))
|
||||
* explain why Claude Code turns Fast mode off ([#1602](https://github.com/agentclientprotocol/claude-agent-acp/issues/1602)) ([c331e17](https://github.com/agentclientprotocol/claude-agent-acp/commit/c331e178a5df47448be1a3ed2f71270c3aad2433))
|
||||
* follow Claude Code's rule for managed deniedModels in the model picker ([#1615](https://github.com/agentclientprotocol/claude-agent-acp/issues/1615)) ([c78bcc7](https://github.com/agentclientprotocol/claude-agent-acp/commit/c78bcc7f417fc8d3da66f51021b43f4528826ec0))
|
||||
* log a managed policy source that did not load ([#1614](https://github.com/agentclientprotocol/claude-agent-acp/issues/1614)) ([ca46b41](https://github.com/agentclientprotocol/claude-agent-acp/commit/ca46b412f51cdb7b5a303c751f5d48c216700887))
|
||||
* report a mid-turn API error notice where the response broke off ([#1599](https://github.com/agentclientprotocol/claude-agent-acp/issues/1599)) ([4df6999](https://github.com/agentclientprotocol/claude-agent-acp/commit/4df69996ca74adff6c42fc60e4b9b742f2d3fdf5))
|
||||
* say why the login failed in the v2 authRequired error ([#1598](https://github.com/agentclientprotocol/claude-agent-acp/issues/1598)) ([ecd066c](https://github.com/agentclientprotocol/claude-agent-acp/commit/ecd066c8700955e3456dd9387e6b8317f024e26c))
|
||||
* update to @anthropic-ai/claude-agent-sdk 0.3.323 ([#1612](https://github.com/agentclientprotocol/claude-agent-acp/issues/1612)) ([ddee24e](https://github.com/agentclientprotocol/claude-agent-acp/commit/ddee24ef8fd0abc0401fb32d567914030fbf6944))
|
||||
|
||||
## [0.106.0](https://github.com/agentclientprotocol/claude-agent-acp/compare/v0.105.0...v0.106.0) (2026-11-10)
|
||||
|
||||
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.106.0",
|
||||
"version": "0.107.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@agentclientprotocol/claude-agent-acp",
|
||||
"version": "0.106.0",
|
||||
"version": "0.107.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "2.6.0",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"version": "0.106.0",
|
||||
"version": "0.107.0",
|
||||
"description": "An ACP-compatible coding agent powered by the Claude Agent SDK (TypeScript)",
|
||||
"main": "dist/lib.js",
|
||||
"types": "dist/lib.d.ts",
|
||||
|
||||
@@ -8,9 +8,26 @@
|
||||
import type {
|
||||
SDKMessage,
|
||||
SDKMessageOrigin,
|
||||
SDKRateLimitInfo,
|
||||
SDKResultMessage,
|
||||
} from "@anthropic-ai/claude-agent-sdk";
|
||||
|
||||
/**
|
||||
* The `data` of the error that fails a turn, for clients to dispatch on
|
||||
* without matching its text: the assistant message's `errorKind`, and for a
|
||||
* `rate_limit` failure the turn's latest `rate_limit_event` info as
|
||||
* `rateLimit`, whose `resetsAt` says when the account can run again. A turn
|
||||
* refused before its first usage sends no `usage_update` to carry that info.
|
||||
* `undefined` without an error kind.
|
||||
*/
|
||||
export function turnErrorData<K extends string>(
|
||||
errorKind: K | undefined,
|
||||
rateLimit: SDKRateLimitInfo | undefined,
|
||||
): { errorKind: K; rateLimit?: SDKRateLimitInfo } | undefined {
|
||||
if (!errorKind) return undefined;
|
||||
return errorKind === "rate_limit" && rateLimit ? { errorKind, rateLimit } : { errorKind };
|
||||
}
|
||||
|
||||
export type ResultEnd =
|
||||
| { stopReason: "refusal" | "max_tokens" | "end_turn" | "max_turn_requests" }
|
||||
/**
|
||||
|
||||
@@ -13406,6 +13406,30 @@ describe("stop reason propagation", () => {
|
||||
expect((err as { data: unknown }).data).toEqual({ errorKind: "rate_limit" });
|
||||
});
|
||||
|
||||
it("adds the turn's rate limit to a rate-limit failure's data", async () => {
|
||||
const agent = createMockAgent();
|
||||
const rateLimit = { status: "rejected", resetsAt: 1700000000, rateLimitType: "five_hour" };
|
||||
injectSession(agent, [
|
||||
{
|
||||
type: "rate_limit_event",
|
||||
rate_limit_info: rateLimit,
|
||||
uuid: randomUUID(),
|
||||
session_id: "test-session",
|
||||
},
|
||||
createAssistantError("rate_limit"),
|
||||
createResultMessage({
|
||||
subtype: "success",
|
||||
stop_reason: "end_turn",
|
||||
is_error: true,
|
||||
result: "You've hit your 5-hour limit · resets 5:30pm (America/Los_Angeles)",
|
||||
}),
|
||||
]);
|
||||
|
||||
await expect(
|
||||
agent.prompt({ sessionId: "test-session", prompt: [{ type: "text", text: "test" }] }),
|
||||
).rejects.toMatchObject({ data: { errorKind: "rate_limit", rateLimit } });
|
||||
});
|
||||
|
||||
it.each(
|
||||
[false, true].flatMap((typedFailures) =>
|
||||
[undefined, "authentication_failed" as const].map((staleError) => ({
|
||||
|
||||
@@ -143,6 +143,32 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("v2 core with Claude Code",
|
||||
]);
|
||||
}, 180_000);
|
||||
|
||||
it("ends the work once when Claude Code folds a prompt into it", async () => {
|
||||
const sessionId = await newSession("bypassPermissions");
|
||||
await prompt(
|
||||
sessionId,
|
||||
"Use the Bash tool in the foreground (not run_in_background) to run exactly: ping -c 8 127.0.0.1 > /dev/null . Then reply with just: woke",
|
||||
);
|
||||
await until(() =>
|
||||
updates.some(
|
||||
({ update }) =>
|
||||
update.sessionUpdate === "terminal_update" &&
|
||||
/ping/.test((update as v2.TerminalUpdate).command ?? ""),
|
||||
),
|
||||
);
|
||||
// Claude Code takes this prompt in after the command, into the same work.
|
||||
// It sends the prompt's `completed` frame before the work's result.
|
||||
await prompt(sessionId, "Reply with exactly the single word: pong");
|
||||
await until(() => states(sessionId).some((update) => update.state === "idle"));
|
||||
// A turn that the result opened would follow the idle at once.
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
expect(states(sessionId).map((update) => update.state)).toEqual(["running", "idle"]);
|
||||
// The idle is the result's: it carries what the work spent.
|
||||
const idle = states(sessionId).at(-1) as { stopReason?: string; usage?: v2.Usage };
|
||||
expect(idle.stopReason).toBe("end_turn");
|
||||
expect(idle.usage?.totalTokens).toBeGreaterThan(0);
|
||||
}, 240_000);
|
||||
|
||||
it("fails a queued prompt with -32800 at a cancel and goes idle with cancelled", async () => {
|
||||
const sessionId = await newSession("bypassPermissions");
|
||||
await prompt(
|
||||
|
||||
@@ -1706,6 +1706,62 @@ describe("ACP v2 core", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["the turn of another prompt", true],
|
||||
["a cycle that Claude Code started on its own", false],
|
||||
])("ends the work once, at its result, when a prompt is folded into %s", async (_, prompted) => {
|
||||
const client = v2Client();
|
||||
await client.connect(async (agent) => {
|
||||
const { response, claude } = await openSession(agent);
|
||||
const ask = () =>
|
||||
agent.request(v2.methods.agent.session.prompt, {
|
||||
sessionId: response.sessionId,
|
||||
prompt: PROMPT,
|
||||
});
|
||||
let opener: SDKUserMessage | undefined;
|
||||
if (prompted) {
|
||||
const first = ask();
|
||||
opener = await claude.prompt(0);
|
||||
claude.send(lifecycle(opener, "started"));
|
||||
await first;
|
||||
}
|
||||
claude.send(cycleStart());
|
||||
await vi.waitFor(() => expect(client.states()).toEqual(["running"]));
|
||||
// As the bundled CLI sends it: a folded prompt's echo comes before its
|
||||
// `started`, and its `completed` before the result of the work.
|
||||
const second = ask();
|
||||
const folded = await claude.prompt(prompted ? 1 : 0);
|
||||
claude.send(echo(folded));
|
||||
claude.send(lifecycle(folded, "started"));
|
||||
await second;
|
||||
for (const event of textDelta("msg_pong", "pong")) claude.send(event);
|
||||
claude.send(assistant("msg_pong", [{ type: "text", text: "pong" }]));
|
||||
claude.send(lifecycle(folded, "completed"));
|
||||
claude.send(
|
||||
result({
|
||||
result: "pong",
|
||||
origin: { kind: prompted ? "human" : "task-notification" },
|
||||
user_message_uuids: [...(opener ? [opener.uuid] : []), folded.uuid],
|
||||
}),
|
||||
);
|
||||
if (opener) claude.send(lifecycle(opener, "completed"));
|
||||
claude.send(sessionState("idle"));
|
||||
await vi.waitFor(() => expect(client.states()).toContain("idle"));
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
});
|
||||
|
||||
expect(client.states()).toEqual(["running", "idle"]);
|
||||
expect(
|
||||
client.sessionUpdates.filter((update) => update.sessionUpdate === "agent_message_chunk"),
|
||||
).toMatchObject([{ messageId: "msg_pong" }]);
|
||||
// The idle carries the result's usage: the result ended the work.
|
||||
expect(client.sessionUpdates.at(-1)).toMatchObject({
|
||||
state: "idle",
|
||||
stopReason: "end_turn",
|
||||
usage: { totalTokens: 6 },
|
||||
});
|
||||
});
|
||||
|
||||
it("reports a tool call that Claude dropped before it ran as cancelled at the turn's end", async () => {
|
||||
const client = v2Client();
|
||||
await client.connect(async (agent) => {
|
||||
@@ -2467,6 +2523,29 @@ describe("ACP v2 core", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("adds the turn's rate limit to a rate-limit failure's error data", async () => {
|
||||
const rateLimit = { status: "rejected", resetsAt: 1700000000, rateLimitType: "five_hour" };
|
||||
const client = v2Client();
|
||||
await client.connect(async (agent) => {
|
||||
const { claude } = await openSession(agent);
|
||||
claude.send(cycleStart());
|
||||
claude.send({
|
||||
type: "rate_limit_event",
|
||||
rate_limit_info: rateLimit,
|
||||
uuid: "00000000-0000-4000-8000-00000000e002",
|
||||
session_id: "session",
|
||||
});
|
||||
claude.send({ ...assistant("msg_limited", []), error: "rate_limit" });
|
||||
claude.send(result({ is_error: true, result: "You've hit your 5-hour limit" }));
|
||||
await vi.waitFor(() => expect(client.states()).toEqual(["running", "idle"]));
|
||||
});
|
||||
|
||||
expect(client.sessionUpdates.at(-1)).toMatchObject({
|
||||
stopReason: "error",
|
||||
error: { data: { errorKind: "rate_limit", rateLimit } },
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["a notice when the turn goes on to end", { result: "" }, "end_turn", 1],
|
||||
["a notice once when the result repeats its text", {}, "end_turn", 1],
|
||||
|
||||
@@ -76,6 +76,7 @@ import {
|
||||
Query,
|
||||
query,
|
||||
SDKAssistantMessageError,
|
||||
SDKRateLimitInfo,
|
||||
SDKActiveGoalMessage,
|
||||
SDKMessage,
|
||||
SDKMessageOrigin,
|
||||
@@ -421,7 +422,7 @@ import {
|
||||
providerCacheKeyFor,
|
||||
readContextWindow,
|
||||
} from "../context-window.js";
|
||||
import { isLoginRequiredText, resultEnd } from "../result-end.js";
|
||||
import { isLoginRequiredText, resultEnd, turnErrorData } from "../result-end.js";
|
||||
import { STARTUP_FAILURE_RESULTS_ENV, startupFailureError } from "../startup-failure.js";
|
||||
import { recordStreamedDelta, unstreamedRemainder, type StreamedBlock } from "../streamed-text.js";
|
||||
import { subagentHookOptions } from "../subagent-configuration.js";
|
||||
@@ -3094,6 +3095,8 @@ export class ClaudeAcpAgent {
|
||||
let lastAssistantModel: string | null = null;
|
||||
// A rate_limit_event that came before the turn had any usage to report.
|
||||
let pendingRateLimit: unknown = null;
|
||||
// The turn's latest rate_limit_event info, for its failure (see `turnErrorData`).
|
||||
let turnRateLimit: SDKRateLimitInfo | undefined;
|
||||
// When the Claude SDK classifies a turn as failed (e.g. rate limit, auth
|
||||
// problem, billing), it sets a categorical `error` field on the
|
||||
// `SDKAssistantMessage` that precedes the final `result` message. We capture
|
||||
@@ -3461,6 +3464,7 @@ export class ClaudeAcpAgent {
|
||||
lastAssistantUsage = null;
|
||||
lastAssistantModel = null;
|
||||
lastAssistantError = undefined;
|
||||
turnRateLimit = undefined;
|
||||
lastAssistantWasUsageLimit = false;
|
||||
lastAssistantFailureTitle = undefined;
|
||||
lastRefusalExplanation = null;
|
||||
@@ -6079,7 +6083,10 @@ export class ClaudeAcpAgent {
|
||||
}
|
||||
await failActiveWithSessionFailure(
|
||||
providerFailureCategory(lastAssistantError, lastAssistantWasUsageLimit),
|
||||
internalErrorForClient(errorKindData(lastAssistantError), ended.error),
|
||||
internalErrorForClient(
|
||||
turnErrorData(lastAssistantError, turnRateLimit),
|
||||
ended.error,
|
||||
),
|
||||
lastAssistantFailureTitle ?? ended.error,
|
||||
);
|
||||
break;
|
||||
@@ -6139,7 +6146,10 @@ export class ClaudeAcpAgent {
|
||||
if ("error" in ended) {
|
||||
await failActiveWithSessionFailure(
|
||||
providerFailureCategory(lastAssistantError, lastAssistantWasUsageLimit),
|
||||
internalErrorForClient(errorKindData(lastAssistantError), ended.error),
|
||||
internalErrorForClient(
|
||||
turnErrorData(lastAssistantError, turnRateLimit),
|
||||
ended.error,
|
||||
),
|
||||
lastAssistantFailureTitle ?? ended.error,
|
||||
);
|
||||
break;
|
||||
@@ -6152,7 +6162,10 @@ export class ClaudeAcpAgent {
|
||||
if ("error" in ended) {
|
||||
await failActiveWithSessionFailure(
|
||||
LIMIT_FAILURE_CATEGORY[message.subtype],
|
||||
internalErrorForClient(errorKindData(lastAssistantError), ended.error),
|
||||
internalErrorForClient(
|
||||
turnErrorData(lastAssistantError, turnRateLimit),
|
||||
ended.error,
|
||||
),
|
||||
ended.error,
|
||||
);
|
||||
break;
|
||||
@@ -6941,6 +6954,7 @@ export class ClaudeAcpAgent {
|
||||
}
|
||||
case "rate_limit_event": {
|
||||
session.idleCompaction?.noteRateLimit(message.rate_limit_info);
|
||||
turnRateLimit = message.rate_limit_info;
|
||||
// With partial messages on, the SDK sends this before the turn's
|
||||
// first assistant frame, while there is no usage yet to send it with.
|
||||
if (lastAssistantTotalUsage === null) {
|
||||
|
||||
@@ -12,11 +12,12 @@ import type * as v2 from "@agentclientprotocol/sdk/experimental/v2/unstable";
|
||||
import type {
|
||||
SDKAssistantMessage,
|
||||
SDKAssistantMessageError,
|
||||
SDKRateLimitInfo,
|
||||
SDKResultMessage,
|
||||
} from "@anthropic-ai/claude-agent-sdk";
|
||||
import { AUTONOMOUS_RESULT_ORIGINS } from "../result-end.js";
|
||||
import { isSyntheticLoginMessage } from "../message-filters.js";
|
||||
import { resultEnd } from "../result-end.js";
|
||||
import { resultEnd, turnErrorData } from "../result-end.js";
|
||||
import { assistantMessageText, providerFailureCategory } from "../session-failure-extension.js";
|
||||
import {
|
||||
getMatchingModelUsage,
|
||||
@@ -96,7 +97,7 @@ export function idleOutcome(
|
||||
// authenticate: this login reached its time limit and cannot be
|
||||
// renewed."
|
||||
RequestError.authRequired(undefined, ended.loginRequired ? undefined : ended.error)
|
||||
: RequestError.internalError(kind ? { errorKind: kind } : undefined, ended.error),
|
||||
: RequestError.internalError(turnErrorData(kind, report?.rateLimit), ended.error),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,6 +119,8 @@ function failed(error: RequestError): IdleOutcome {
|
||||
*/
|
||||
export class TurnReport {
|
||||
errorKind: SDKAssistantMessageError | undefined;
|
||||
/** The turn's latest `rate_limit_event` info (see `turnErrorData`). */
|
||||
rateLimit: SDKRateLimitInfo | undefined;
|
||||
/** Claude Code showed its sign-in message ("Not logged in · Please run /login"). */
|
||||
loginRequired = false;
|
||||
/** The explanation of the turn's refusal. */
|
||||
@@ -181,6 +184,7 @@ export class TurnReport {
|
||||
|
||||
reset(): void {
|
||||
this.errorKind = undefined;
|
||||
this.rateLimit = undefined;
|
||||
this.loginRequired = false;
|
||||
this.refusalExplanation = undefined;
|
||||
this.apiError = undefined;
|
||||
|
||||
@@ -141,6 +141,14 @@ export class V2Session implements RequestTarget {
|
||||
private foreground = false;
|
||||
/** The prompt whose `started` frame came last in the open turn. */
|
||||
private turnCommand: string | undefined;
|
||||
/**
|
||||
* Whether a cycle of Claude Code runs in the open turn: one started (see
|
||||
* {@link openTurn}) and its result has not come. Claude Code folds a prompt
|
||||
* that starts meanwhile into that cycle.
|
||||
*/
|
||||
private cycleOpen = false;
|
||||
/** The prompt that started the running cycle, if one did. */
|
||||
private cycleCommand: string | undefined;
|
||||
/** The state the client was told last. */
|
||||
private reportedState: ForegroundState = "idle";
|
||||
/** The open permission requests and elicitations of the session. */
|
||||
@@ -1044,6 +1052,7 @@ export class V2Session implements RequestTarget {
|
||||
* output (see {@link SessionReports}).
|
||||
*/
|
||||
private report(message: SDKMessage): void {
|
||||
if (message.type === "rate_limit_event") this.turnReport.rateLimit = message.rate_limit_info;
|
||||
if (message.type === "tool_progress") {
|
||||
this.output.toolProgress(message);
|
||||
return;
|
||||
@@ -1087,9 +1096,13 @@ export class V2Session implements RequestTarget {
|
||||
);
|
||||
return;
|
||||
case "completed":
|
||||
// Comes after the turn's result. A turn that ended without one ends here.
|
||||
// Claude Code sends it after the result for the prompt that started
|
||||
// the cycle, so a cycle that ended without a result ends here. For a
|
||||
// prompt folded into the cycle it sends it before the result, which
|
||||
// then ends the turn. Nothing but Claude Code's order tells the two
|
||||
// apart.
|
||||
this.stopped.promptEnded(uuid);
|
||||
if (uuid === this.turnCommand) this.closeTurn();
|
||||
if (uuid === this.cycleCommand) this.closeTurn();
|
||||
return;
|
||||
default:
|
||||
// `queued`: the prompt waits in Claude Code's queue.
|
||||
@@ -1152,6 +1165,10 @@ export class V2Session implements RequestTarget {
|
||||
if (interrupted) this.cancelledWork = true;
|
||||
// A result with no open turn: a turn that sent nothing before it.
|
||||
this.openTurn(undefined);
|
||||
// The result ends the cycle. A prompt that starts next in the open turn,
|
||||
// such as the `/clear` of a fresh context, starts its own.
|
||||
this.cycleOpen = false;
|
||||
this.cycleCommand = undefined;
|
||||
// A cycle that Claude Code started on its own leaves its Fast mode state
|
||||
// to the next result of a prompt.
|
||||
if (!(message.origin && AUTONOMOUS_RESULT_ORIGINS.has(message.origin.kind))) {
|
||||
@@ -1308,8 +1325,8 @@ export class V2Session implements RequestTarget {
|
||||
// --- State ---------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Opens a turn, if none is open. `command` is the prompt that started it,
|
||||
* if one did.
|
||||
* Opens a turn, if none is open, and starts a cycle in it, if none runs
|
||||
* there. `command` is the prompt that started it, if one did.
|
||||
*/
|
||||
private openTurn(command: string | undefined): void {
|
||||
if (command !== undefined) {
|
||||
@@ -1318,6 +1335,10 @@ export class V2Session implements RequestTarget {
|
||||
this.localCommands.delete(command);
|
||||
if (local) this.turnLocalCommand = { command: local, abort: new AbortController() };
|
||||
}
|
||||
if (!this.cycleOpen) {
|
||||
this.cycleOpen = true;
|
||||
this.cycleCommand = command;
|
||||
}
|
||||
if (this.foreground) return;
|
||||
this.foreground = true;
|
||||
this.lastEnd = undefined;
|
||||
@@ -1355,6 +1376,8 @@ export class V2Session implements RequestTarget {
|
||||
this.output.cancelled = false;
|
||||
this.lastEnd = undefined;
|
||||
this.turnCommand = undefined;
|
||||
this.cycleOpen = false;
|
||||
this.cycleCommand = undefined;
|
||||
this.turnReport.reset();
|
||||
const ownCommandDone = this.ownCommandDone;
|
||||
this.ownCommandDone = undefined;
|
||||
|
||||
Reference in New Issue
Block a user