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
75 changes: 75 additions & 0 deletions apps/server/scripts/acp-mock-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const emitInterleavedAssistantToolCalls =
const emitGenericToolPlaceholders = process.env.T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS === "1";
const emitAskQuestion = process.env.T3_ACP_EMIT_ASK_QUESTION === "1";
const emitXAiAskUserQuestion = process.env.T3_ACP_EMIT_XAI_ASK_USER_QUESTION === "1";
const emitXAiExitPlanMode = process.env.T3_ACP_EMIT_XAI_EXIT_PLAN_MODE === "1";
const emitXAiPlanMdWrite = process.env.T3_ACP_EMIT_XAI_PLAN_MD_WRITE === "1";
const emitXAiPromptCompleteThenHang = process.env.T3_ACP_EMIT_XAI_PROMPT_COMPLETE_THEN_HANG === "1";
const emitForeignSessionUpdates = process.env.T3_ACP_EMIT_FOREIGN_SESSION_UPDATES === "1";
const hangPromptForever = process.env.T3_ACP_HANG_PROMPT_FOREVER === "1";
Expand Down Expand Up @@ -810,6 +812,79 @@ const program = Effect.gen(function* () {
return { stopReason: "end_turn" };
}

if (emitXAiPlanMdWrite) {
// Match Grok's real session layout so isGrokPlanMarkdownPath accepts it.
const planPath = `/tmp/mock-home/.grok/sessions/${requestedSessionId}/plan.md`;
const planBody = "# Mock plan\n\n- Write the feature\n- Add a test\n- Ship it\n";
// enter_plan_mode first so the adapter arms planModeActive.
yield* agent.client.sessionUpdate({
sessionId: requestedSessionId,
update: {
sessionUpdate: "tool_call",
toolCallId: "enter-plan-mode-1",
title: "enter_plan_mode",
kind: "other",
status: "completed",
rawInput: { variant: "EnterPlanMode" },
},
});
yield* agent.client.sessionUpdate({
sessionId: requestedSessionId,
update: {
sessionUpdate: "tool_call",
toolCallId: "plan-md-write-1",
title: "write",
kind: "edit",
status: "pending",
rawInput: { file_path: planPath, content: planBody },
},
});
yield* agent.client.sessionUpdate({
sessionId: requestedSessionId,
update: {
sessionUpdate: "tool_call_update",
toolCallId: "plan-md-write-1",
kind: "edit",
status: "completed",
title: `Write \`${planPath}\``,
rawInput: { file_path: planPath, content: planBody },
content: [
{
type: "diff",
path: planPath,
oldText: "",
newText: planBody,
},
],
},
});
return { stopReason: "end_turn" };
}

if (emitXAiExitPlanMode) {
const result = yield* agent.client.extRequest("_x.ai/exit_plan_mode", {
method: "x.ai/exit_plan_mode",
params: {
sessionId: requestedSessionId,
toolCallId: "exit-plan-mode-tool-call-1",
planContent: "# Exit plan\n\n- Step one\n- Step two\n",
},
});
if (typeof result !== "object" || result === null || !("outcome" in result)) {
throw new Error("Expected _x.ai/exit_plan_mode response outcome.");
}
if (
result.outcome !== "abandoned" &&
result.outcome !== "approved" &&
result.outcome !== "request_changes"
) {
throw new Error(
`Expected exit_plan_mode outcome abandoned|approved|request_changes, got ${String(result.outcome)}`,
);
}
return { stopReason: "end_turn" };
}

if (emitForeignSessionUpdates) {
yield* agent.client.sessionUpdate({
sessionId: requestedSessionId,
Expand Down
112 changes: 111 additions & 1 deletion apps/server/src/provider/Layers/GrokAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import {
} from "@t3tools/contracts";

import { ServerConfig } from "../../config.ts";
import { grokPromptSettlementBelongsToContext, makeGrokAdapter } from "./GrokAdapter.ts";
import {
grokPromptSettlementBelongsToContext,
isGrokEnterPlanModeToolCall,
makeGrokAdapter,
} from "./GrokAdapter.ts";
const decodeGrokSettings = Schema.decodeSync(GrokSettings);

const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
Expand Down Expand Up @@ -89,6 +93,27 @@ const grokAdapterTestLayer = ServerConfig.layerTest(process.cwd(), {
const makeTestAdapter = (binaryPath: string, options?: Parameters<typeof makeGrokAdapter>[1]) =>
makeGrokAdapter(decodeGrokSettings({ binaryPath }), options).pipe(Effect.orDie);

it("detects enter_plan_mode tool calls from title and rawInput", () => {
assert.isTrue(
isGrokEnterPlanModeToolCall({
title: "enter_plan_mode",
data: { toolCallId: "1" },
}),
);
assert.isTrue(
isGrokEnterPlanModeToolCall({
title: "Plan mode entered",
data: { toolCallId: "1", rawInput: { variant: "EnterPlanMode" } },
}),
);
assert.isFalse(
isGrokEnterPlanModeToolCall({
title: "write",
data: { toolCallId: "1", rawInput: { file_path: "/tmp/x", content: "y" } },
}),
);
});

it("requires a settlement to match the live Grok turn", () => {
const staleTurnId = TurnId.make("stale-turn");
const replacementTurnId = TurnId.make("replacement-turn");
Expand Down Expand Up @@ -1097,6 +1122,91 @@ it.layer(grokAdapterTestLayer)("GrokAdapterLive", (it) => {
}),
);

it.effect("captures xAI exit_plan_mode as a proposed plan and unblocks the turn", () =>
Effect.gen(function* () {
const threadId = ThreadId.make("grok-xai-exit-plan-mode");
const wrapperPath = yield* Effect.promise(() =>
makeMockGrokWrapper({ T3_ACP_EMIT_XAI_EXIT_PLAN_MODE: "1" }),
);
const adapter = yield* makeTestAdapter(wrapperPath);
const proposed =
yield* Deferred.make<Extract<ProviderRuntimeEvent, { type: "turn.proposed.completed" }>>();
const turnCompleted = yield* Deferred.make<void>();

const eventsFiber = yield* Stream.runForEach(adapter.streamEvents, (event) => {
if (String(event.threadId) !== String(threadId)) {
return Effect.void;
}
if (event.type === "turn.proposed.completed") {
return Deferred.succeed(proposed, event).pipe(Effect.ignore);
}
if (event.type === "turn.completed") {
return Deferred.succeed(turnCompleted, undefined).pipe(Effect.ignore);
}
return Effect.void;
}).pipe(Effect.forkChild);

yield* adapter.startSession({
threadId,
provider: ProviderDriverKind.make("grok"),
cwd: process.cwd(),
runtimeMode: "full-access",
});

yield* adapter.sendTurn({ threadId, input: "present the plan", attachments: [] });

const proposedEvent = yield* Deferred.await(proposed);
assert.equal(proposedEvent.type, "turn.proposed.completed");
assert.equal(proposedEvent.payload.planMarkdown, "# Exit plan\n\n- Step one\n- Step two");
assert.equal(proposedEvent.raw?.method, "_x.ai/exit_plan_mode");
yield* Deferred.await(turnCompleted);

yield* Fiber.interrupt(eventsFiber);
yield* adapter.stopSession(threadId);
}),
);

it.effect("surfaces plan.md writes as a proposed plan while plan mode is active", () =>
Effect.gen(function* () {
const threadId = ThreadId.make("grok-xai-plan-md-write");
const wrapperPath = yield* Effect.promise(() =>
makeMockGrokWrapper({ T3_ACP_EMIT_XAI_PLAN_MD_WRITE: "1" }),
);
const adapter = yield* makeTestAdapter(wrapperPath);
const proposed =
yield* Deferred.make<Extract<ProviderRuntimeEvent, { type: "turn.proposed.completed" }>>();

const eventsFiber = yield* Stream.runForEach(adapter.streamEvents, (event) => {
if (String(event.threadId) !== String(threadId)) {
return Effect.void;
}
if (event.type === "turn.proposed.completed") {
return Deferred.succeed(proposed, event).pipe(Effect.ignore);
}
return Effect.void;
}).pipe(Effect.forkChild);

yield* adapter.startSession({
threadId,
provider: ProviderDriverKind.make("grok"),
cwd: process.cwd(),
runtimeMode: "full-access",
});

yield* adapter.sendTurn({ threadId, input: "write the plan", attachments: [] });

const proposedEvent = yield* Deferred.await(proposed);
assert.equal(
proposedEvent.payload.planMarkdown,
"# Mock plan\n\n- Write the feature\n- Add a test\n- Ship it",
);
assert.equal(proposedEvent.raw?.method, "session/update");

yield* Fiber.interrupt(eventsFiber);
yield* adapter.stopSession(threadId);
}),
);

it.effect("handles xAI ask_user_question extension requests", () =>
Effect.gen(function* () {
const threadId = ThreadId.make("grok-xai-ask-user-question");
Expand Down
Loading
Loading