Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/server/src/claudeModelOptions.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it } from "vite-plus/test";

import { ProviderInstanceId, type ModelSelection } from "@t3tools/contracts";

Expand Down
300 changes: 269 additions & 31 deletions apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts
Comment thread
macroscopeapp[bot] marked this conversation as resolved.

Large diffs are not rendered by default.

168 changes: 167 additions & 1 deletion apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@ import { assert, describe, it } from "@effect/vitest";
import type * as EffectAcpSchema from "effect-acp/schema";

import { ProviderAdapterV2RuntimePolicy } from "../ProviderAdapter.ts";
import { AcpProviderCapabilitiesV2, acpPermissionDisposition } from "./AcpAdapterV2.ts";
import {
AcpProviderCapabilitiesV2,
acpPermissionDisposition,
acpRootSessionUpdateIngestsOutput,
acpRootTurnCompletionDrainMs,
acpRootTurnHasIngestedOutput,
acpRootTurnIsIdle,
acpRootTurnLivenessWatchdogDefersForPending,
acpRootTurnPromptLivenessMs,
acpRootTurnSettleDebounceMs,
acpRootTurnShouldRearmRecoveryTimers,
} from "./AcpAdapterV2.ts";
import { GrokProviderCapabilitiesV2 } from "./GrokAdapterV2.ts";

function permissionRequest(
Expand Down Expand Up @@ -37,6 +48,161 @@ function runtimePolicy(input: {
});
}

describe("acpRootTurnSettleDebounceMs", () => {
it("allows tool calls to land after a brief assistant preamble", () => {
assert.equal(acpRootTurnSettleDebounceMs, 2_000);
});
});

describe("acpRootTurnPromptLivenessMs", () => {
it("fails silent post-prompt hangs before the user waits hours", () => {
assert.equal(acpRootTurnPromptLivenessMs, 30_000);
});
});

describe("acpRootTurnCompletionDrainMs", () => {
it("gives trailing root chunks a short landing window", () => {
assert.equal(acpRootTurnCompletionDrainMs, 100);
});
});

describe("acpRootSessionUpdateIngestsOutput", () => {
const sessionId = "session-1";

it("ignores empty assistant chunks used as Grok keepalives", () => {
assert.isFalse(
acpRootSessionUpdateIngestsOutput({
sessionId,
update: {
sessionUpdate: "agent_message_chunk",
content: { type: "text", text: "" },
},
}),
);
});

it("accepts non-empty assistant and reasoning chunks", () => {
assert.isTrue(
acpRootSessionUpdateIngestsOutput({
sessionId,
update: {
sessionUpdate: "agent_message_chunk",
content: { type: "text", text: "hello" },
},
}),
);
assert.isTrue(
acpRootSessionUpdateIngestsOutput({
sessionId,
update: {
sessionUpdate: "agent_thought_chunk",
content: { type: "text", text: "thinking" },
},
}),
);
});

it("accepts tool and plan updates", () => {
assert.isTrue(
acpRootSessionUpdateIngestsOutput({
sessionId,
update: {
sessionUpdate: "tool_call",
toolCallId: "tool-1",
title: "Read",
kind: "read",
status: "pending",
},
}),
);
assert.isTrue(
acpRootSessionUpdateIngestsOutput({
sessionId,
update: {
sessionUpdate: "plan",
entries: [{ content: "Step 1", status: "pending", priority: "medium" }],
},
}),
);
});
});

describe("acpRootTurnHasIngestedOutput", () => {
const empty = {
assistant: { current: null, nextSegment: 0 },
reasoning: { current: null, nextSegment: 0 },
tools: new Map(),
plan: null,
} as const;

it("is false before any root turn items land", () => {
assert.isFalse(acpRootTurnHasIngestedOutput(empty));
});

it("is true once assistant segments have streamed", () => {
assert.isTrue(
acpRootTurnHasIngestedOutput({
...empty,
assistant: { current: null, nextSegment: 1 },
}),
);
});
});

describe("acpRootTurn recovery timer re-arm", () => {
it("re-arms settle and watchdog after pending clears on active turns", () => {
assert.isTrue(acpRootTurnShouldRearmRecoveryTimers({ finalized: false, interrupted: false }));
});

it("skips re-arm when the turn is already terminal", () => {
assert.isFalse(acpRootTurnShouldRearmRecoveryTimers({ finalized: true, interrupted: false }));
assert.isFalse(acpRootTurnShouldRearmRecoveryTimers({ finalized: false, interrupted: true }));
});

it("defers the liveness watchdog tick while approval or user-input is pending", () => {
assert.isTrue(acpRootTurnLivenessWatchdogDefersForPending(true));
assert.isFalse(acpRootTurnLivenessWatchdogDefersForPending(false));
});
});

describe("acpRootTurnIsIdle", () => {
const idle = {
finalized: false,
interrupted: false,
assistantStreamOpen: false,
reasoningStreamOpen: false,
hasRunningTool: false,
hasPendingRuntimeRequest: false,
hasToolHistory: false,
hasRunningSubagent: false,
hasOutput: true,
} as const;

it("is false while assistant text is still streaming", () => {
assert.isFalse(acpRootTurnIsIdle({ ...idle, assistantStreamOpen: true }));
});

it("is false while a tool is running", () => {
assert.isFalse(acpRootTurnIsIdle({ ...idle, hasRunningTool: true }));
});

it("is false after tool history until the prompt RPC completes", () => {
assert.isFalse(acpRootTurnIsIdle({ ...idle, hasToolHistory: true }));
});

it("is false while a native subagent task is still running", () => {
assert.isFalse(acpRootTurnIsIdle({ ...idle, hasRunningSubagent: true }));
});

it("is false when only reasoning or tools have streamed", () => {
assert.isFalse(acpRootTurnIsIdle({ ...idle, hasOutput: false }));
});

it("is true when root assistant output is quiescent", () => {
assert.isTrue(acpRootTurnIsIdle(idle));
});
});

describe("GrokAdapterV2 capabilities", () => {
it("keeps optional protocol features conservative until a flavor or handshake confirms them", () => {
assert.isFalse(AcpProviderCapabilitiesV2.sessions.supportsModelSwitchInSession);
Expand Down
5 changes: 5 additions & 0 deletions apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
} from "../ProviderAdapterDriver.ts";
import {
AcpProviderCapabilitiesV2,
acpRootTurnPromptLivenessMs,
makeAcpAdapterV2,
type AcpAdapterV2Flavor,
type AcpAdapterV2RuntimeInput,
Expand Down Expand Up @@ -132,12 +133,16 @@ export function makeGrokAdapterV2(options: GrokAdapterV2Options) {
const flavor: AcpAdapterV2Flavor = {
driver: GROK_PROVIDER,
capabilities: GrokProviderCapabilitiesV2,
settleRootTurnWhenIdle: true,
interruptPromptOnCancel: false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High Adapters/GrokAdapterV2.ts:137

makeGrokAdapterV2 sets interruptPromptOnCancel: false, so cancelling a Grok turn calls session/cancel without first interrupting the local prompt fiber. The interruptPromptOnCancel option exists specifically to unblock Grok's wedged session/prompt calls, but leaving it false means a stuck prompt keeps the turn in running after Stop/cancel. Consider setting it to true.

Suggested change
interruptPromptOnCancel: false,
interruptPromptOnCancel: true,
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.ts around line 137:

`makeGrokAdapterV2` sets `interruptPromptOnCancel: false`, so cancelling a Grok turn calls `session/cancel` without first interrupting the local prompt fiber. The `interruptPromptOnCancel` option exists specifically to unblock Grok's wedged `session/prompt` calls, but leaving it `false` means a stuck prompt keeps the turn in `running` after Stop/cancel. Consider setting it to `true`.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional: interruptPromptOnCancel stays false because true breaks message_steering/grok replay. PR description updated under Out of scope; idle settle uses runtime.cancel (session/cancel).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment thread
cursor[bot] marked this conversation as resolved.
rootTurnPromptLivenessMs: acpRootTurnPromptLivenessMs,
resolveModelId: (selection) => resolveGrokAcpBaseModelId(selection.model),
makeRuntime:
options.makeRuntime ??
((input) =>
makeGrokAcpRuntime({
...input,
interruptPromptOnCancel: input.interruptPromptOnCancel ?? false,
grokSettings: options.settings,
environment: options.environment,
childProcessSpawner: options.childProcessSpawner,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it } from "vite-plus/test";

import {
type ModelSelection,
Expand Down
Loading
Loading