Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2263b8c
feat(server): wire Grok Build effort, auth, rewind, and usage
t3dotgg Aug 12, 2026
d3dcbf5
fix(server): allow undefined Grok effort on session/set_model
t3dotgg Aug 12, 2026
cf18ba5
fix(server): keep Grok effort and rewind aligned with the live session
t3dotgg Aug 12, 2026
fe46af8
feat(server): ingest Grok Build workflows and real PromptUsage
maslinedwin Aug 15, 2026
1d45fdb
fix(grok): align workflow ingest with Claude and Codex
maslinedwin Aug 15, 2026
c99fc87
fix(grok): close remaining Macroscope and Bugbot findings
maslinedwin Aug 15, 2026
615135e
fix(grok): cancel in-flight prompts before rewind and keep subagent t…
maslinedwin Aug 15, 2026
5c8bd7c
fix(grok): emit token usage from the sendTurn success fallback
maslinedwin Aug 15, 2026
b0079ed
fix(grok): close reported model, login, permission, and tool-update i…
maslinedwin Aug 16, 2026
5f29710
fix(grok): address Macroscope and Bugbot findings on probe, tools, an…
maslinedwin Aug 16, 2026
180f6b4
fix(grok): surface rewind errors, serialize startup notifications, ch…
maslinedwin Aug 16, 2026
3dac3f6
fix(grok): reject oversized rewind and snapshot startup notifications…
maslinedwin Aug 16, 2026
7288e6a
fix(grok): classify ACP auth failures from tagged errors
maslinedwin Aug 16, 2026
1570598
Merge branch 'main' into feat/grok-build-parity
maslinedwin Aug 22, 2026
8fc08fd
fix(grok): rewind from remaining history and resolve effort menus thr…
maslinedwin Aug 22, 2026
8b86f83
feat(usage): scan Grok session transcripts on the usage page
maslinedwin Aug 22, 2026
29016bb
feat(grok): map session extras onto existing runtime events
maslinedwin Aug 22, 2026
935cd6c
feat(grok): honor plan mode through session/set_mode
maslinedwin Aug 22, 2026
103affa
feat(grok): advertise workflow slash commands from disk scripts
maslinedwin Aug 22, 2026
fdd8765
feat(grok): project prompt queue onto session state
maslinedwin Aug 22, 2026
ef5cc54
fix(grok): close Macroscope and Bugbot findings
maslinedwin Aug 22, 2026
2ec14f6
fix(grok): include project workflows on the initial snapshot
maslinedwin Aug 22, 2026
110efe8
fix(grok): satisfy exactOptionalPropertyTypes and usage chart bands
maslinedwin Aug 22, 2026
4f0eb5d
Merge branch 'main' into feat/grok-build-parity
maslinedwin Aug 23, 2026
899ffa2
fix(grok): discover workflow scripts through Effect FileSystem
maslinedwin Aug 23, 2026
af3cc51
fix(grok): keep context max, rewind extras, and compact session honest
maslinedwin Aug 23, 2026
56d7255
fix(grok): bound workflow reads and resolve home without HOME
maslinedwin Aug 23, 2026
1a3608f
fix(grok): provide FileSystem to snapshot effects and decode utf-8
maslinedwin Aug 23, 2026
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
4 changes: 3 additions & 1 deletion apps/mobile/src/features/usage/usageProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { useAppearancePreferences } from "../settings/appearance/AppearancePrefe
* Series and table order. The chart stacks providers from the bottom in this
* order, so it also fixes which band sits on top of the bars.
*/
export const PROVIDER_ORDER: readonly UsageProviderKind[] = ["codex", "claude"];
export const PROVIDER_ORDER: readonly UsageProviderKind[] = ["codex", "claude", "grok"];

export const PROVIDER_LABEL: Record<UsageProviderKind, string> = {
claude: "Claude Code",
codex: "Codex",
grok: "Grok Build",
};

/**
Expand All @@ -21,5 +22,6 @@ export function useProviderColors(): Record<UsageProviderKind, string> {
return {
claude: "#d97757",
codex: scheme === "dark" ? "#e6e6e6" : "#3c3c43",
grok: "#8884d8",
};
}
208 changes: 204 additions & 4 deletions apps/server/scripts/acp-mock-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ 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 xAiSessionNotificationMethod =
process.env.T3_ACP_XAI_SESSION_METHOD ?? "x.ai/session_notification";
const emitSessionExtras = process.env.T3_ACP_EMIT_SESSION_EXTRAS === "1";
const emitQueueChanged = process.env.T3_ACP_EMIT_QUEUE === "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 @@ -279,9 +283,29 @@ function modeState(): AcpSchema.SessionModeState {
}

const grokAcpModels: ReadonlyArray<AcpSchema.ModelInfo> = [
{ modelId: "grok-build", name: "Grok Build" },
{
modelId: "grok-build",
name: "Grok Build",
_meta: {
supportsReasoningEffort: true,
reasoningEffort: "high",
totalContextTokens: 500000,
reasoningEfforts: [
{ id: "xhigh", value: "xhigh", label: "Extra High Effort" },
{ id: "high", value: "high", label: "High Effort", default: true },
{ id: "medium", value: "medium", label: "Medium Effort" },
{ id: "low", value: "low", label: "Low Effort" },
],
},
},
{ modelId: "grok-mock-alt", name: "Grok Mock Alt" },
];
const enableRewind = process.env.T3_ACP_ENABLE_REWIND === "1";
const ghostRewindOnCancel = process.env.T3_ACP_REWIND_GHOST_ON_CANCEL === "1";
const emitUsage = process.env.T3_ACP_EMIT_USAGE === "1";
const emitWorkflow = process.env.T3_ACP_EMIT_WORKFLOW === "1";
const emitSubagent = process.env.T3_ACP_EMIT_SUBAGENT === "1";
let rewindPoints: Array<{ prompt_index: number; prompt_preview: string }> = [];

function modelState(): AcpSchema.SessionModelState {
const modelId = grokAcpModels.some((model) => model.modelId === currentModelId)
Expand Down Expand Up @@ -392,7 +416,12 @@ const program = Effect.gen(function* () {
);
}
currentModelId = request.modelId;
return {};
return {
_meta: {
model: { Ok: request.modelId },
...(request._meta ?? {}),
},
};
}),
);

Expand Down Expand Up @@ -437,6 +466,12 @@ const program = Effect.gen(function* () {
Effect.gen(function* () {
const cancelledSessionId = String(sessionId ?? "mock-session-1");
cancelledSessions.add(cancelledSessionId);
if (enableRewind && ghostRewindOnCancel) {
rewindPoints.push({
prompt_index: rewindPoints.length,
prompt_preview: "cancelled-ghost",
});
}
if (emitLateUpdateAfterCancel) {
yield* Effect.sleep("50 millis");
yield* Effect.sync(() => {
Expand Down Expand Up @@ -545,7 +580,13 @@ const program = Effect.gen(function* () {
sessionId: requestedSessionId,
promptId: promptIdFromRequestMeta(request) ?? "mock-xai-prompt-1",
...(omitXAiPromptCompleteStopReason ? {} : { stopReason: "end_turn" }),
agentResult: null,
agentResult: emitUsage
? {
input_tokens: 10,
output_tokens: 4,
reasoning_tokens: 3,
}
: null,
});

if (emitForeignSessionUpdates) {
Expand Down Expand Up @@ -873,11 +914,170 @@ const program = Effect.gen(function* () {
},
});

return { stopReason: "end_turn" };
if (enableRewind && !cancelledSessions.has(requestedSessionId)) {
const preview =
request.prompt.find((block) => block.type === "text" && "text" in block)?.text ?? "";
rewindPoints.push({
prompt_index: rewindPoints.length,
prompt_preview: typeof preview === "string" ? preview : "",
});
}

if (emitSubagent) {
writeJsonRpcNotification(xAiSessionNotificationMethod, {
sessionId: requestedSessionId,
update: {
sessionUpdate: "subagent_spawned",
subagent_id: "sa_explore_1",
parent_session_id: requestedSessionId,
child_session_id: "child-explore-1",
subagent_type: "explore",
},
});
}

if (emitWorkflow) {
writeJsonRpcNotification(xAiSessionNotificationMethod, {
sessionId: requestedSessionId,
update: {
sessionUpdate: "workflow_updated",
run_id: "wf_review_1",
revision: 1,
name: "review-changes",
objective: "Review the latest diff",
status: "active",
phases: [
{ title: "Plan", state: "done" },
{ title: "Execute", state: "active" },
],
current_phase: "Execute",
elapsed_ms: 1200,
active_agents: 1,
agents: [
{
agent_id: "agent_reviewer",
label: "Reviewer",
phase: "Execute",
model: "grok-4.6",
state: "running",
tokens_used: 42,
duration_ms: 800,
},
],
},
});
}

if (emitSessionExtras) {
writeJsonRpcNotification(xAiSessionNotificationMethod, {
sessionId: requestedSessionId,
update: {
sessionUpdate: "hook_execution",
event_name: "user_prompt_submit",
runs: [
{
name: "global/settings:user_prompt_submit[0].hooks[0]",
status: { status: "success", elapsed_ms: 12 },
},
],
},
});
writeJsonRpcNotification(xAiSessionNotificationMethod, {
sessionId: requestedSessionId,
update: {
sessionUpdate: "auto_compact_started",
tokens_used: 402_072,
context_window: 500_000,
percentage: 80,
reason: "Context window 80% full",
},
});
writeJsonRpcNotification(xAiSessionNotificationMethod, {
sessionId: requestedSessionId,
update: {
sessionUpdate: "auto_compact_completed",
tokens_before: 402_072,
tokens_after: 42_380,
elapsed_ms: 80,
},
});
writeJsonRpcNotification(xAiSessionNotificationMethod, {
sessionId: requestedSessionId,
update: {
sessionUpdate: "session_recap",
summary: "Mapped Grok extras onto T3 runtime events.",
auto: true,
},
});
writeJsonRpcNotification(xAiSessionNotificationMethod, {
sessionId: requestedSessionId,
update: {
sessionUpdate: "turn_completed",
prompt_id: "prompt-extras-1",
stop_reason: "end_turn",
usage: {
inputTokens: 100,
outputTokens: 20,
costUsdTicks: 1_626_488_800,
},
},
});
writeJsonRpcNotification(xAiSessionNotificationMethod, {
sessionId: requestedSessionId,
update: {
sessionUpdate: "task_backgrounded",
task_id: "call-bg-1",
command: "sleep 1",
description: "Background wait",
output_file: "/tmp/grok-bg.log",
},
});
}

if (emitQueueChanged) {
writeJsonRpcNotification("_x.ai/queue/changed", {
sessionId: requestedSessionId,
entries: [{ prompt: "follow up" }],
});
}

return {
stopReason: "end_turn",
...(emitUsage
? {
_meta: {
usage: {
input_tokens: 10,
output_tokens: 4,
reasoning_tokens: 3,
},
},
}
: {}),
};
}),
);

yield* agent.handleUnknownExtRequest((method, params) => {
if (method === "_x.ai/rewind/points") {
return Effect.succeed({ rewind_points: rewindPoints });
}
if (method === "_x.ai/rewind/execute") {
const record = typeof params === "object" && params !== null ? params : {};
const target =
"targetPromptIndex" in record && typeof record.targetPromptIndex === "number"
? record.targetPromptIndex
: undefined;
if (target === undefined) {
return Effect.succeed({ success: false, error: "missing targetPromptIndex" });
}
rewindPoints = rewindPoints.filter((point) => point.prompt_index < target);
return Effect.succeed({
success: true,
target_prompt_index: target,
mode: "conversation_only",
});
}
if (method === "cursor/list_available_models") {
return Effect.succeed({
models: availableModels(),
Expand Down
16 changes: 14 additions & 2 deletions apps/server/src/provider/Drivers/GrokDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = {
Effect.gen(function* () {
const crypto = yield* Crypto.Crypto;
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const httpClient = yield* HttpClient.HttpClient;
const serverSettings = yield* ServerSettingsService;
const eventLoggers = yield* ProviderEventLoggers;
Expand Down Expand Up @@ -113,10 +115,13 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = {
});
const textGeneration = yield* makeGrokTextGeneration(effectiveConfig, processEnv);

const checkProvider = checkGrokProviderStatus(effectiveConfig, processEnv).pipe(
const { cwd: projectRoot } = yield* ServerConfig;
const checkProvider = checkGrokProviderStatus(effectiveConfig, processEnv, projectRoot).pipe(
Effect.map(stampIdentity),
Effect.provideService(Crypto.Crypto, crypto),
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
Effect.provideService(FileSystem.FileSystem, fileSystem),
Effect.provideService(Path.Path, path),
);

const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings);
Expand All @@ -126,7 +131,14 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = {
streamSettings: snapshotSettings.streamSettings,
haveSettingsChanged: haveProviderSnapshotSettingsChanged,
initialSnapshot: (settings) =>
buildInitialGrokProviderSnapshot(settings.provider).pipe(Effect.map(stampIdentity)),
buildInitialGrokProviderSnapshot(settings.provider, {
environment: processEnv,
projectRoot,
}).pipe(
Effect.map(stampIdentity),
Effect.provideService(FileSystem.FileSystem, fileSystem),
Effect.provideService(Path.Path, path),
),
checkProvider,
enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) =>
enrichGrokSnapshot({
Expand Down
Loading
Loading