Repository navigation
fix(server): Grok monitor updates stay running until Grok says they finished #13557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -174,12 +174,15 @@ export interface AcpAdapterV2ExtensionContext { | |
| /** | ||
| * Session-scoped background-task lifecycle reported via extension | ||
| * notifications (e.g. Grok `x.ai/task_backgrounded`; older builds use the | ||
| * underscore alias). Mutations for non-root sessions are ignored. | ||
| * underscore alias). Mutations for non-root sessions are ignored. A terminal | ||
| * mutation also finishes the tool that registered the task in a settled turn | ||
| * still held open for it, with `output` as its final text when given. | ||
| */ | ||
| readonly applyBackgroundTaskMutation: (mutation: { | ||
| readonly sessionId: string; | ||
| readonly taskId: string; | ||
| readonly status: "running" | "completed" | "failed"; | ||
| readonly output?: string; | ||
| }) => Effect.Effect<void>; | ||
| readonly requestUserInput: ( | ||
| input: AcpAdapterV2UserInputRequest, | ||
|
|
@@ -3578,6 +3581,34 @@ export function makeAcpAdapterV2(options: AcpAdapterV2Options): ProviderAdapterV | |
| } | ||
| }); | ||
|
|
||
| // A structured task end (Grok `task_completed`) is authoritative for the | ||
| // tool that registered the task: its own updates only ever say running. | ||
| // Finish the row while deferred finalize holds a settled root turn open | ||
| // for it, so the turn can settle. While the prompt is still open the | ||
| // agent reports the end itself (TaskOutput hydration), and an unreported | ||
| // end must keep its post-finalize continuation offer. | ||
| const finishRegisteredBackgroundTool = Effect.fnUntraced(function* (mutation: { | ||
| readonly taskId: string; | ||
| readonly status: "completed" | "failed"; | ||
| readonly output?: string; | ||
| }) { | ||
| const context = yield* Ref.get(activeTurn); | ||
| if (context === null || context.finalized || !context.promptSettled) return; | ||
| const toolCallId = context.toolCallIdsByBackgroundTaskId.get(mutation.taskId); | ||
| const tool = toolCallId === undefined ? undefined : context.tools.get(toolCallId); | ||
| if (tool === undefined) return; | ||
| const status = toolStatus(tool.status); | ||
| if (status !== "pending" && status !== "running") return; | ||
| context.awaitingBackgroundHydration.delete(mutation.taskId); | ||
| const finished = { ...tool, status: mutation.status }; | ||
| yield* emitTool( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This changes how a settled, deferred turn finishes its registered monitor tool, but the tests do not assert that a Posted via Macroscope — Effect Service Conventions |
||
| context, | ||
| mutation.output === undefined ? finished : setToolOutputText(finished, mutation.output), | ||
| mutation.status, | ||
| ); | ||
| yield* rearmDeferredFinalize(context); | ||
| }); | ||
|
|
||
| const bufferPostSettleWake = Effect.fnUntraced(function* ( | ||
| notification: EffectAcpSchema.SessionNotification, | ||
| ) { | ||
|
|
@@ -5569,6 +5600,13 @@ export function makeAcpAdapterV2(options: AcpAdapterV2Options): ProviderAdapterV | |
| // its child session must not gate root wake machinery. | ||
| if ((yield* Ref.get(activeSessionId)) !== mutation.sessionId) return; | ||
| yield* applyLateBackgroundMutation(mutation.sessionId, mutation); | ||
| if (mutation.status !== "running") { | ||
| yield* finishRegisteredBackgroundTool({ | ||
| taskId: mutation.taskId, | ||
| status: mutation.status, | ||
| ...(mutation.output === undefined ? {} : { output: mutation.output }), | ||
| }); | ||
| } | ||
| }), | ||
| ).pipe(Effect.asVoid), | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -380,6 +380,8 @@ const XAiTaskLifecycleNotification = Schema.Struct({ | |
| task_snapshot: Schema.optional( | ||
| Schema.Struct({ | ||
| task_id: Schema.optional(Schema.String), | ||
| output: Schema.optional(Schema.String), | ||
| exit_code: Schema.optional(Schema.NullOr(Schema.Number)), | ||
| }), | ||
| ), | ||
| }), | ||
|
|
@@ -391,7 +393,9 @@ type XAiTaskLifecycleNotification = typeof XAiTaskLifecycleNotification.Type; | |
| export interface XAiBackgroundTaskLifecycleMutation { | ||
| readonly sessionId: string; | ||
| readonly taskId: string; | ||
| readonly status: "running" | "completed"; | ||
| readonly status: "running" | "completed" | "failed"; | ||
| /** Final output from `task_completed.task_snapshot`, when Grok sent one. */ | ||
| readonly output?: string; | ||
| } | ||
|
|
||
| export function xAiBackgroundTaskLifecycleMutation( | ||
|
|
@@ -404,7 +408,15 @@ export function xAiBackgroundTaskLifecycleMutation( | |
| nonEmptyString(update.task_id) ?? | ||
| nonEmptyString(update.tool_call_id); | ||
| if (taskId === undefined) return null; | ||
| return { sessionId: notification.sessionId, taskId, status }; | ||
| const exitCode = update.task_snapshot?.exit_code; | ||
| const output = update.task_snapshot?.output; | ||
| return { | ||
| sessionId: notification.sessionId, | ||
| taskId, | ||
| status: | ||
| status === "completed" && typeof exitCode === "number" && exitCode !== 0 ? "failed" : status, | ||
| ...(output === undefined ? {} : { output }), | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -480,10 +492,11 @@ export function resolveXAiAcpToolTitle(toolCall: AcpToolCallState): string | und | |
| * - replace generic titles ("Tool") with description / variant labels | ||
| * - structured Monitor start ACK stays running | ||
| * - text start ACK stays running | ||
| * - structured Bash results with exit_code are terminal (any tool: post-settle | ||
| * wake re-reports of a finished monitor arrive with empty rawInput and a | ||
| * generic title, so monitor detection cannot match; without this they replay | ||
| * as running and the timeline row spins forever) | ||
| * - structured Bash results with exit_code are terminal, unless Grok itself | ||
| * still reports the tool running. Grok 1.0.41 streams every monitor tick as | ||
| * `status: "in_progress"` with a Bash-shaped rawOutput whose exit_code is | ||
| * already 0, and never sends a completed status for the monitor; its end | ||
| * arrives as `_x.ai/task_completed`. | ||
| */ | ||
| export function normalizeXAiAcpToolCallState(toolCall: AcpToolCallState): AcpToolCallState { | ||
| const resolvedTitle = resolveXAiAcpToolTitle(toolCall); | ||
|
|
@@ -494,7 +507,8 @@ export function normalizeXAiAcpToolCallState(toolCall: AcpToolCallState): AcpToo | |
|
|
||
| const rawOutput = unknownRecord(withTitle.data.rawOutput); | ||
| const outputType = nonEmptyString(rawOutput?.type)?.toLowerCase(); | ||
| if (outputType === "bash") { | ||
| const reportedRunning = withTitle.status === "inProgress" || withTitle.status === "pending"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟠 High A status-less terminal Bash re-report after an earlier 🚀 Reply "fix it for me" or copy this AI Prompt for your agent: |
||
| if (outputType === "bash" && !reportedRunning) { | ||
| // Same key set as the adapter's command projection (commandExitCode). | ||
| const exitCode = ["exit_code", "exitCode", "code"] | ||
| .map((key) => rawOutput?.[key]) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟠 High
Adapters/AcpAdapterV2.ts:3596When
task_completedarrives beforepromptSettledis set, the registered tool row remainsinProgress, so deferred finalization stays blocked and the run never completes.finishRegisteredBackgroundToolreturns on!context.promptSettled, whileapplyLateBackgroundMutationhas already cleared the running-task set; allow this terminal mutation to finish the row before the completion callback settles the prompt.🚀 Reply "fix it for me" or copy this AI Prompt for your agent: