Repository navigation
fix: make busy-session inputs durable - #304
Conversation
|
Too many files changed for review. ( Bypass the limit by tagging |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis change replaces transient session steering and interruption flows with durable, turn-fenced session-input queue operations. It adds queue listing, replacement, cancellation, and promotion across storage, APIs, CLI, tools, SDK contracts, and the web interface. ChangesSession input lifecycle
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
internal/daemon/native_session_mutation.go (1)
99-121: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winForward the trimmed
expected_turn_id.The guard rejects an
ExpectedTurnIDthat is empty afterstrings.TrimSpace. Line 120 then forwards the rawinput.ExpectedTurnID. A value such as" turn-1 "passes the guard and reachesSendPromptwith the surrounding whitespace. If the session manager compares the fence with the active turn identifier by exact equality, the fence does not match and the prompt is rejected. Trim once and reuse the trimmed value.🐛 Proposed fix
mode := session.BusyInputMode(strings.TrimSpace(input.Mode)) - if (mode == session.BusyInputModeSteer || mode == session.BusyInputModeInterrupt) && - strings.TrimSpace(input.ExpectedTurnID) == "" { + expectedTurnID := strings.TrimSpace(input.ExpectedTurnID) + if (mode == session.BusyInputModeSteer || mode == session.BusyInputModeInterrupt) && + expectedTurnID == "" { return toolspkg.ToolResult{}, nativeNetworkInputError( req.ToolID, errors.New("expected_turn_id is required for steer and interrupt mode"), ) }result, err := n.deps.Sessions.SendPrompt(ctx, sessionID, session.SendPromptOpts{ Message: message, MessageID: messageID, IdempotencyKey: idempotencyKey, - Mode: mode, ExpectedTurnID: input.ExpectedTurnID, + Mode: mode, ExpectedTurnID: expectedTurnID, Runtime: core.PromptRuntimeSelectionFromPayload(input.Runtime), })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/daemon/native_session_mutation.go` around lines 99 - 121, Trim input.ExpectedTurnID once before validation, reuse that trimmed value for the empty-check, and pass it as ExpectedTurnID in the session.SendPrompt call within the native session mutation flow. Preserve the existing validation and error behavior for blank values.internal/tools/builtin/builtin_test.go (1)
1407-1420: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winFix the contradictory failure message for the identity fields.
Line 1412 asserts
identity.MinLength != 0, so the test requires nominLengthconstraint. This matches internal/tools/builtin/sessions.go lines 342-343, wheremessage_idandidempotency_keyare declared as plain strings. The failure message at line 1414 says "want non-empty string", which describes the opposite condition.If a later change adds
minLength: 1to those properties, this test fails and reports "want non-empty string" for a schema that is already a non-empty string. Correct the message so the diagnostic matches the assertion.💚 Proposed fix
if identity.Type != "string" || identity.MinLength != 0 { t.Fatalf( - "%s prompt.%s schema = %#v, want non-empty string", + "%s prompt.%s schema = %#v, want string without a minLength constraint", owner, field, identity, ) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/tools/builtin/builtin_test.go` around lines 1407 - 1420, Update the failure message in the identity-field assertion within the builtin schema test to describe a string with no minLength constraint, matching the existing checks on identity.Type and identity.MinLength. Keep the assertion logic unchanged.internal/store/globaldb/global_db_session_input_queue_test.go (1)
129-165: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAlign the test name with the behaviour the body verifies.
The name says "Should dispatch current-generation steer before queued input". The body stages two steers,
steer-oldat generation 0 andsteer-newat the advanced generation, and never enqueues a queued input. The assertions at lines 166-182 verify generation fencing between the two steers and the one-shot lease. They do not verify priority between a steer and a queued entry.A reader who trusts this name would believe steer-before-queue ordering is covered. Either rename the test to describe generation fencing, or enqueue a queued input so the stated priority is actually asserted.
As per path instructions: "Verify tests can fail when business logic changes" and "Ensure tests verify behavior outcomes, not just function calls".
💚 Option 1: rename to match the body
- t.Run("Should dispatch current-generation steer before queued input", func(t *testing.T) { + t.Run("Should dispatch only the current-generation steer and lease it once", func(t *testing.T) {💚 Option 2: enqueue a queued input so the name holds
Insert before the first claim at line 166:
if _, _, err := globalDB.EnqueueSessionInput(ctx, store.SessionInputQueueInsert{ ID: "inq-after-steer", SessionID: sessionID, Mode: store.SessionInputQueueModeQueue, Text: "queued input", SessionGeneration: generation, QueueCap: 10, Now: now.Add(2500 * time.Millisecond), }); err != nil { t.Fatalf("EnqueueSessionInput() error = %v", err) }The existing assertion that
steer-newis claimed first then proves the steer outranks the queued entry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/store/globaldb/global_db_session_input_queue_test.go` around lines 129 - 165, Rename the test case around the visible generation-fencing assertions to describe that it dispatches the current-generation steer while excluding the stale steer; do not claim steer-before-queued-input priority unless the test also enqueues and asserts a queued entry is dispatched afterward.Source: Path instructions
internal/session/manager_busy_input.go (1)
306-319: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winActivation failures leave durable queue entries behind in three paths. Each path persists or promotes a queue entry, then activates it. When activation returns an error, the code returns the error without removing the entry, so the daemon keeps an
interrupt_then_promptentry whose target turn was never interrupted.cancelUndeliverableInputalready exists for this purpose and detaches the cancellation withcontext.WithoutCancel.
internal/session/manager_busy_input.go#L306-L319: wrap theactivateInterruptingInputerror fromStageSteerwithm.cancelUndeliverableInput(ctx, session.ID, entry.ID, err).internal/session/manager_busy_input.go#L346-L358: wrap theactivateInterruptingInputerror fromEnqueueInterruptwith the same call.internal/session/manager_pending_input.go#L121-L123: wrap theensureInterruptingInputActivatederror fromPromoteToSteerwith the same call.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/session/manager_busy_input.go` around lines 306 - 319, Activation failures leave durable interrupting-input entries behind; update all three activation paths to cancel the undeliverable entry before returning the error. In internal/session/manager_busy_input.go lines 306-319, wrap the activateInterruptingInput error after StageSteer with m.cancelUndeliverableInput using session.ID and entry.ID; apply the same change at lines 346-358 after EnqueueInterrupt, and in internal/session/manager_pending_input.go lines 121-123 after PromoteToSteer wraps ensureInterruptingInputActivated. Preserve cancellation’s detached-context behavior through the existing helper.internal/session/manager_prompt_admission.go (1)
282-299: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
interruptAdmittedPromptdoes not guard a nil input queue.
enqueueAdmittedBusyPromptat line 205 andstageAdmittedSteerPromptat line 244 both returnErrPromptInProgresswhenm.inputQueueis nil. The interrupt path callsm.inputQueue.EnqueueAdmittedInterruptdirectly.EnqueueAdmittedInterruptreads service configuration throughprepareAdmittedEntry, so a nil service dereferences a nil pointer and panics inside the daemon request path.Add the same guard that the two sibling paths use.
🛡️ Proposed guard
) (SendPromptResult, error) { + if m.inputQueue == nil { + return SendPromptResult{}, ErrPromptInProgress + } previousTurnID, err := requireExpectedActiveTurn(session, req.expectedTurnID) if err != nil { return SendPromptResult{}, err }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/session/manager_prompt_admission.go` around lines 282 - 299, Update interruptAdmittedPrompt to check m.inputQueue for nil before calling EnqueueAdmittedInterrupt, matching the guards in enqueueAdmittedBusyPrompt and stageAdmittedSteerPrompt; return ErrPromptInProgress through the same existing error path when the queue is unavailable.
🧹 Nitpick comments (17)
web/src/hooks/routes/__tests__/use-session-page-controls.test.tsx (1)
224-246: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a negative test for the missing turn fence.
This test proves the positive path: an active turn ID reaches the steer and interrupt mutations. The new guards in
use-session-page-controls.ts(Lines 137 and 164) also block both operations whenactiveTurnIdis empty. No test covers that branch, so a regression that drops the guard would still pass.Add a case that renders with
makeSession("active")(no turn ID) and asserts thatsteerPromptMutation.mutateAsyncandinterruptPromptMutation.mutateAsyncare not called.💚 Suggested test
+ it("Should block steer and interrupt when no active turn fence exists", async () => { + routeHookMocks.auiState.thread.isRunning = true; + const { result } = renderControls(makeSession("active")); + + await act(async () => { + await result.current.handleSteerPrompt("new constraint"); + await result.current.handleInterruptPrompt("replace the work"); + }); + + expect(routeHookMocks.steerPromptMutation.mutateAsync).not.toHaveBeenCalled(); + expect(routeHookMocks.interruptPromptMutation.mutateAsync).not.toHaveBeenCalled(); + });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/hooks/routes/__tests__/use-session-page-controls.test.tsx` around lines 224 - 246, Add a negative test alongside the existing active-turn test that renders controls with makeSession("active") and no activeTurnId, invokes handleSteerPrompt and handleInterruptPrompt, and asserts both steerPromptMutation.mutateAsync and interruptPromptMutation.mutateAsync are not called.web/src/components/assistant-ui/session-composer.tsx (1)
223-237: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider gating the queued-row Steer control with the same fence.
Lines 224 and 237 disable the direct Steer and Interrupt buttons when
busyInputFenceAvailableis false. The queued-row Steer button inSessionComposerQueuedPromptsis not gated by this flag.handleSteerQueuedPromptinweb/src/hooks/routes/use-session-page-controls.ts(Line 190) returns early whenactiveTurnIdis empty, so a user click on an enabled row control produces no result and no message.Pass the fence state into the queued-prompt strip so the row control reflects the same precondition.
♻️ Suggested wiring
<SessionComposerQueuedPrompts prompts={queuedPrompts} onSteer={onSteerQueuedPrompt!} onEdit={handleEditQueuedPrompt} onRemove={handleRemoveQueuedPrompt} - disabled={isBusyInputPending} + disabled={isBusyInputPending} + steerDisabled={!busyInputFenceAvailable} />
SessionComposerQueuedPromptsthen appliessteerDisabledonly to the Steer control.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/components/assistant-ui/session-composer.tsx` around lines 223 - 237, Pass busyInputFenceAvailable from SessionComposer into SessionComposerQueuedPrompts, and have the queued-row Steer control use it as steerDisabled. Keep the existing gating for direct Steer and Interrupt buttons unchanged, and apply the new disabled state only to the queued Steer action.web/src/hooks/routes/use-session-page-controls.ts (1)
79-85: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winMemoize the derived
queuedPromptsarray.Lines 79-85 build a new array on every render. The hook returns this array, and
SessionThreadforwards it toSessionComposeras thequeuedPromptsprop. Every render of the session page therefore produces a new prop identity for the composer and foruseSessionBusyInputActions. The previous store-owned value kept a stable identity between updates.Derive the projection with
useMemokeyed on the query data.♻️ Suggested change
- const queuedPrompts: QueuedPrompt[] = - sessionInputs.data?.inputs.map(input => ({ - id: input.id, - mode: input.mode, - status: input.status, - text: input.text, - })) ?? []; + const queuedPrompts: QueuedPrompt[] = useMemo( + () => + sessionInputs.data?.inputs.map(input => ({ + id: input.id, + mode: input.mode, + status: input.status, + text: input.text, + })) ?? EMPTY_QUEUED_PROMPTS, + [sessionInputs.data] + );Add
import { useMemo } from "react";and a module-levelconst EMPTY_QUEUED_PROMPTS: QueuedPrompt[] = [];.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/hooks/routes/use-session-page-controls.ts` around lines 79 - 85, Memoize the derived queuedPrompts projection in the useSessionPageControls hook using useMemo, keyed on sessionInputs.data, and reuse a module-level EMPTY_QUEUED_PROMPTS fallback so the returned array identity remains stable when no inputs exist. Add the required React import and preserve the existing field mapping.internal/api/httpapi/handlers_test.go (1)
2735-2736: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd
t.Parallel()to the outer test function.
TestSessionInputHandlersExposeAuthoritativeQueueMutationsdoes not callt.Parallel(). The subtests share the mutable capturesreplaceOptsandpromoteOpts, so the subtests must stay sequential. The outer function can still run in parallel with other tests.♻️ Proposed change
func TestSessionInputHandlersExposeAuthoritativeQueueMutations(t *testing.T) { + t.Parallel() now := time.Date(2026, 8, 3, 18, 0, 0, 0, time.UTC)As per coding guidelines: "use
t.Run("Should …"), defaultt.Parallel".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/api/httpapi/handlers_test.go` around lines 2735 - 2736, Add t.Parallel() at the beginning of TestSessionInputHandlersExposeAuthoritativeQueueMutations, while leaving its subtests sequential because they share the mutable replaceOpts and promoteOpts captures.Source: Coding guidelines
internal/daemon/native_session_input.go (2)
79-99: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueValidate the body fields before the workspace and session lookup.
sessionInputReplaceresolves the workspace and verifies session ownership first, then validatestext,message_id, andidempotency_key.sessionPromptininternal/daemon/native_session_mutation.govalidates the required strings first and resolves the workspace afterwards. Aligning the order removes a workspace resolution and a session lookup for malformed requests.sessionInputPromotehas the same ordering.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/daemon/native_session_input.go` around lines 79 - 99, In the sessionInputReplace flow, validate text, message_id, and idempotency_key with requiredNativeString before calling nativeSessionInputMutationScope; then perform the workspace/session lookup and replacement unchanged. Apply the same validation-before-lookup ordering to sessionInputPromote, matching sessionPrompt.
60-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRename the loop variable to avoid shadowing
input.The loop variable
inputshadows the decoded request structinputdeclared at line 48. The code is correct, but the two values have different types and meanings.♻️ Proposed change
payloads := make([]contract.SessionInputPayload, 0, len(inputs)) - for _, input := range inputs { - payloads = append(payloads, core.SessionInputPayloadFromSession(input)) + for _, pending := range inputs { + payloads = append(payloads, core.SessionInputPayloadFromSession(pending)) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/daemon/native_session_input.go` around lines 60 - 63, Rename the range variable in the payload-building loop within the native session input handler so it no longer shadows the decoded request struct input; update the corresponding SessionInputPayloadFromSession argument while preserving the existing behavior.internal/daemon/native_tools_test.go (1)
5603-5634: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a case for the missing
expected_turn_idguard.This test covers the success path for steer mode with
expected_turn_idset.sessionPromptininternal/daemon/native_session_mutation.golines 99-106 rejects steer and interrupt mode whenexpected_turn_idis blank. No test exercises that rejection. Add a case that callsToolIDSessionPromptwith"mode":"steer"and noexpected_turn_id, and assert the invalid-input reason plus zeroSendPromptcalls.Do you want me to generate that test case?
As per path instructions: "Verify tests can fail when business logic changes".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/daemon/native_tools_test.go` around lines 5603 - 5634, Add a test case alongside the existing session_prompt steer-mode success test that calls ToolIDSessionPrompt with mode "steer" but omits expected_turn_id, then assert the invalid-input error reason and verify SendPrompt is not called. Ensure the test fails if the expected_turn_id validation in sessionPrompt is removed or bypassed.Source: Path instructions
internal/api/core/session_prompt.go (1)
24-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse shared status constants or define them with
SendPromptResult.
internal/sessiondefinesBusyInputModeQueue,BusyInputModeInterrupt, andBusyInputModeSteerconstants, butSendPromptResult.Statususes the string literals"queued","steering", and"interrupting"directly. Add exported status constants and use them frominternal/session/manager_busy_input.goandinternal/api/core/session_prompt.goso a rename cannot silently return 200 instead of 202.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/api/core/session_prompt.go` around lines 24 - 27, Define exported status constants alongside SendPromptResult for the queued, steering, and interrupting values, then replace the corresponding string literals in internal/session/manager_busy_input.go and the status check in session_prompt.go with those constants. Preserve the existing 202 behavior for these statuses and the Replayed condition.internal/extension/contract/host_api.go (1)
161-164: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider one shared mode type across request and result.
SessionsPromptParams.Modeusesapicontract.PromptMode.SessionPromptResult.ModeandSessionInput.Modeusesession.BusyInputMode(seeinternal/extension/contract/host_api_results.golines 56 and 78). Extension authors then see two different Go types for the same wire concept, andinternal/extension/host_api_sessions.goline 123 converts throughstring. Align both sides on one contract type if the wire values are identical.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/extension/contract/host_api.go` around lines 161 - 164, Use one shared mode type for the request and result contracts: update SessionPromptResult.Mode and SessionInput.Mode to use apicontract.PromptMode, matching SessionsPromptParams.Mode, and remove the unnecessary string conversion in the related session handling flow.internal/extension/host_api_sessions.go (1)
292-314: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd a forward runtime-payload conversion helper.
internal/api/contract/prompt_runtime_selection.goonly exposesPromptRuntimeSelectionFromPayload, whileinternal/extension/host_api_sessions.gostill initializesPromptRuntimeSelectionPayloadfield by field. Add an exported forward conversion in the contract package and call it here so future payload changes stay in one place.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/extension/host_api_sessions.go` around lines 292 - 314, Add an exported conversion helper in prompt_runtime_selection.go that converts the contract’s PromptRuntimeSelectionPayload into the internal runtime selection type, complementing PromptRuntimeSelectionFromPayload. Update hostAPISessionInputFromPending to use this helper instead of constructing PromptRuntimeSelectionPayload field by field, while preserving nil handling.internal/store/globaldb/global_db_session_prompt_admission_queue.go (1)
284-312: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the admission status strings into named constants.
"queued","interrupting", and"steering"are inline literals here. They form a wire-visible vocabulary that the HTTP, UDS, CLI, and web layers project. The output schema in internal/tools/builtin/sessions.go line 339 declaresstatusas a plain string with no enum, so nothing pins these values. A typo or a divergent literal in another layer would not fail a build or a test.Declare the values next to the other session input constants in
internal/store/session_input_queue.goand reference them from every producer and consumer.♻️ Proposed refactor
Add the constants in
internal/store/session_input_queue.go:const ( SessionPromptAdmissionStatusQueued = "queued" SessionPromptAdmissionStatusInterrupting = "interrupting" SessionPromptAdmissionStatusSteering = "steering" )Then use them here:
- status := "queued" + status := store.SessionPromptAdmissionStatusQueued if entry.Mode == store.SessionInputQueueModeInterrupt { - status = "interrupting" + status = store.SessionPromptAdmissionStatusInterrupting }return store.SessionPromptAdmissionResult{ - Status: "steering", + Status: store.SessionPromptAdmissionStatusSteering, Mode: store.SessionInputQueueModeSteer,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/store/globaldb/global_db_session_prompt_admission_queue.go` around lines 284 - 312, The admission status strings are inline literals and need a shared wire-level vocabulary. Define SessionPromptAdmissionStatusQueued, SessionPromptAdmissionStatusInterrupting, and SessionPromptAdmissionStatusSteering alongside the session input constants in session_input_queue.go, then replace the literals in queuedPromptAdmissionResult and stagedPromptAdmissionResult and update every other producer and consumer to reference these constants.internal/tools/builtin/builtin_test.go (1)
1492-1529: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSplit the
requiresExpectedTurnflag from the output-schema choice.
requiresExpectedTurncontrols two unrelated things. At line 1517 it selects theexpected_turn_idassertion. At line 1526 it also selects which output envelope the helper verifies. Because of that coupling,assertSessionInputPromoteSchemamust callassertSessionPromptMutationOutputSchemaitself at line 1489, whileassertSessionInputReplaceSchemadepends on the helper's implicit branch. The responsibility split between caller and helper is asymmetric and is not visible from the parameter name.A future mutation tool that requires
expected_turn_idand returns theinputenvelope cannot be expressed with this signature.Keep the input assertions in the helper and move the output assertion to each caller.
♻️ Proposed refactor
func assertSessionInputReplaceSchema(t *testing.T, descriptor toolspkg.Descriptor) { t.Helper() assertSessionInputMutationSchema(t, descriptor, []string{ "idempotency_key", "message_id", "queue_entry_id", "session_id", "text", "workspace", }, []string{"session_id", "queue_entry_id", "text", "message_id", "idempotency_key"}, false) + assertSessionInputOutputSchema(t, descriptor.ID.String()+" output", descriptor.OutputSchema) }if requiresExpectedTurn { var expectedTurn nativeObjectSchema if err := json.Unmarshal(input.Properties["expected_turn_id"], &expectedTurn); err != nil { t.Fatalf("%s expected_turn_id schema unmarshal error = %v", descriptor.ID, err) } if expectedTurn.Type != "string" || expectedTurn.MinLength != 1 { t.Fatalf("%s expected_turn_id schema = %#v, want non-empty string", descriptor.ID, expectedTurn) } } - if !requiresExpectedTurn { - assertSessionInputOutputSchema(t, descriptor.ID.String()+" output", descriptor.OutputSchema) - } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/tools/builtin/builtin_test.go` around lines 1492 - 1529, Remove the output-schema branching from assertSessionInputMutationSchema, leaving it responsible only for input and expected_turn_id assertions. Update each caller, including assertSessionInputPromoteSchema and assertSessionInputReplaceSchema, to explicitly invoke the appropriate output-schema assertion so expected_turn_id validation is independent of the returned envelope.internal/session/inputqueue/queue.go (1)
243-248: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider one insert-parameter struct instead of adjacent positional strings.
newInsertnow takessessionID,text,mode,delivery, andtargetTurnIDas five adjacent strings, andprepareAdmittedEntryforwards four of them. A swapped argument compiles without error and writes wrong queue metadata. A small parameter struct removes that risk and keeps the call sites readable.♻️ Sketch of the parameter struct
+type insertSpec struct { + SessionID string + Text string + Mode string + Delivery string + TargetTurnID string + Generation int64 + Runtime store.SessionInputRuntime +} + -func (s *Service) newInsert( - sessionID string, - text string, - mode string, - delivery string, - targetTurnID string, - generation int64, - runtime store.SessionInputRuntime, -) (store.SessionInputQueueInsert, error) { +func (s *Service) newInsert(spec insertSpec) (store.SessionInputQueueInsert, error) {Also applies to: 358-366
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/session/inputqueue/queue.go` around lines 243 - 248, Replace the adjacent string parameters for newInsert and prepareAdmittedEntry with a single named parameter struct containing sessionID, text, mode, delivery, and targetTurnID. Update prepareAdmittedEntry and all newInsert call sites to populate and consume the struct by field, preserving the existing queue metadata values and behavior.internal/session/manager_busy_input_test.go (1)
1812-1814: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd
t.Parallel()to the new top-level test function.
TestManagerPendingInputPromotionReplaymarks only the subtest as parallel. The neighboring suites, such asTestManagerPromptAdmissionReplayat line 222 andTestManagerBusyInputPromptAdmissionReplayat line 330, callt.Parallel()on the outer function too.As per coding guidelines: "use
t.Run("Should …"), defaultt.Parallel".♻️ Proposed change
func TestManagerPendingInputPromotionReplay(t *testing.T) { + t.Parallel() + t.Run("Should replay promotion after the replacement turn starts without interrupting it again", func(t *testing.T) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/session/manager_busy_input_test.go` around lines 1812 - 1814, Add t.Parallel() at the start of TestManagerPendingInputPromotionReplay, before its subtest declaration, matching the parallel execution pattern used by the neighboring top-level test functions.Source: Coding guidelines
internal/session/inputqueue/mutation.go (1)
15-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider one shared mutation helper for
ReplaceandPromoteToSteer.Both methods repeat the same nine steps and differ only in mode, delivery, target turn, and the store call. A single private helper keeps the replay, insert-building, and identity assignment in one place, and it reduces the seven-parameter signatures.
♻️ Sketch of a shared helper
type mutationSpec struct { sessionID string entryID string text string targetTurnID string messageID string idempotencyKey string mode string delivery string } func (s *Service) mutate( ctx context.Context, spec mutationSpec, apply func(context.Context, string, string, store.SessionInputQueueInsert) (store.SessionInputQueueEntry, bool, error), ) (store.SessionInputQueueEntry, bool, error) { // trim, replay, build insert, then call apply }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/session/inputqueue/mutation.go` around lines 15 - 112, Extract the shared mutation flow from Replace and PromoteToSteer into a private mutate helper, using a mutationSpec to carry their differing session, entry, text, target-turn, identity, mode, and delivery values. Keep trimming, replayMutation, Get, newInsert, replacement identity assignment, and error propagation in the helper, while passing the operation-specific store behavior through the apply callback. Update both methods to construct the appropriate spec and delegate to mutate.internal/store/globaldb/queries/session_input.sql (1)
15-15: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAnchor dispatch order to the store constant.
internal/store/globaldb/queries/session_input.sqlhas the same'interrupt_then_prompt'literal inGetNextDispatchableSessionInput,PeekNextSessionInput, andListPendingSessionInputs. Add one store test that inserts an older queued input and a newerSessionInputDeliveryInterruptThenPromptinput, then checks that the interrupting input is the one dispatched. If the literal andstore.SessionInputDeliveryInterruptThenPromptdrift later, the test fails before runtime priority is lost.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/store/globaldb/queries/session_input.sql` at line 15, Replace the duplicated 'interrupt_then_prompt' SQL literal in GetNextDispatchableSessionInput, PeekNextSessionInput, and ListPendingSessionInputs with the store constant SessionInputDeliveryInterruptThenPrompt, and add one store test inserting an older queued input plus a newer interrupt-then-prompt input that verifies the interrupting input is dispatched first.internal/store/globaldb/schema/definitions/20_sessions.sql (1)
183-183: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winMake the pending index match the queue ordering.
idx_session_input_queue_pendingis created as(session_id, status, delivery, enqueued_at, id), while dispatch queries order byCASE delivery ...,enqueued_at, andid. A plaindeliverycolumn does not match thatCASE deliveryordering, so add the expression to the covered query/index chain or switch the queues todelivery DESCordering so the index can serve dispatch.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/store/globaldb/schema/definitions/20_sessions.sql` at line 183, Update idx_session_input_queue_pending and the corresponding dispatch query ordering so they use the same delivery ordering semantics: either index the CASE delivery expression used by dispatch, or change the queue ordering to delivery DESC consistently. Preserve the existing enqueued_at and id ordering and ensure the index column/order matches the final query exactly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/cli/client_test.go`:
- Around line 238-250: Update internal/cli/client_test.go lines 238-250 in the
promote request fixture to return the interrupt_then_prompt delivery value and
assert the decoded response delivery matches
contract.PromptDeliveryInterruptThenPrompt. Update internal/cli/session_test.go
lines 2055-2058 to return contract.PromptDeliveryInterruptThenPrompt for the
promoted result and assert that delivery in the command output.
In `@internal/cli/session_test.go`:
- Around line 1849-1852: Replace the raw err.Error()/strings.Contains checks
with the project’s ErrorContains or ErrorAs helper in
internal/cli/session_test.go at lines 1849-1852, 1918-1920, 2107-2108, and
2135-2137. Keep each assertion specific to the missing interrupt fence, missing
steer fence, missing promotion fence, and removed --cancel flag error text
respectively.
In `@internal/extension/contract/host_api_results.go`:
- Line 57: Update the delivery field in the host API result contract to use the
concrete prompt delivery/source enum represented by PromptTurnSourceUser,
PromptTurnSourceNetwork, or PromptTurnSourceSynthetic, rather than the opaque
PromptDelivery domain type; ensure validation accepts and serialization
preserves those concrete values.
In `@internal/session/manager_busy_input_test.go`:
- Around line 206-208: The two assertions access the concurrently updated cancel
counter without synchronization. In internal/session/manager_busy_input_test.go
at lines 206-208 and 1888-1890, add a managerCancelCalls(h) helper that locks
h.driver.mu while reading cancelCalls, then use it for both the zero-cancel and
one-cancel assertions.
In `@internal/session/manager_prompt_lifetime_test.go`:
- Around line 207-221: Wrap the existing test logic in
TestPromptTranscriptMarkerSuppressesExpectedCancellation with a t.Run subtest
named using the “Should …” pattern, and move t.Parallel() into that subtest
while preserving the current cancellation assertions.
In `@internal/store/globaldb/global_db_session_input_queue_mutation.go`:
- Around line 164-175: Update the replay handling around
getSessionInputQueueEntry and sameSessionInputMutation so a replacement is
acknowledged only when the stored entry remains queued. Reject canceled,
dispatching, or sent entries by returning
store.ErrSessionInputQueueEntryNotQueued, while preserving the existing conflict
error for mismatched mutations and the normal replay path for queued entries.
In `@internal/store/globaldb/schema/migrations/00043_schema.sql`:
- Line 7: Update migration 00043 around the new_session_input_queue row-copy so
legacy session_input_queue fields, including delivery and target_turn_id, are
migrated without loss; prefer altering the existing schema rather than
rebuilding it, or explicitly map every legacy column and preserve its per-row
value instead of relying on defaults.
In `@web/src/components/assistant-ui/hooks/use-session-busy-input-actions.ts`:
- Around line 63-70: Update the action flow around handler and
describeComposerActionError so synchronous throws from
handler(trimmedComposerText) are converted into the same promise chain before
the existing catch, ensuring they trigger the failure toast while preserving
abort handling and success behavior.
- Around line 75-87: Update the edit flow around editingQueuedPrompt and
onReplaceQueuedPrompt so an existing queued prompt never falls through to
onQueuePrompt when replacement is unavailable. Require onReplaceQueuedPrompt
before entering edit mode, or return after reporting an error if it is absent;
preserve the existing atomic replacement path when the handler exists.
In `@web/src/hooks/routes/use-session-page-controls.ts`:
- Around line 189-220: The queued-prompt handlers generate IDs directly through
globalThis.crypto.randomUUID(), which can throw in non-secure contexts. Update
handleSteerQueuedPrompt and handleReplaceQueuedPrompt to use the existing shared
ID helper that provides a secure-context fallback for both idempotency_key and
message_id, preserving the current mutation and error-handling behavior.
In `@web/src/systems/session/hooks/use-session-actions.ts`:
- Line 270: Update the interrupt and steer request flows in the session actions
hook to validate that expectedTurnId is present and non-empty before making
either request. Reject or return early when the fence is missing, and only
include the validated value as expected_turn_id; never omit it or substitute an
empty string. Apply the change to both request construction sites referenced
near the interrupt and steer actions.
---
Outside diff comments:
In `@internal/daemon/native_session_mutation.go`:
- Around line 99-121: Trim input.ExpectedTurnID once before validation, reuse
that trimmed value for the empty-check, and pass it as ExpectedTurnID in the
session.SendPrompt call within the native session mutation flow. Preserve the
existing validation and error behavior for blank values.
In `@internal/session/manager_busy_input.go`:
- Around line 306-319: Activation failures leave durable interrupting-input
entries behind; update all three activation paths to cancel the undeliverable
entry before returning the error. In internal/session/manager_busy_input.go
lines 306-319, wrap the activateInterruptingInput error after StageSteer with
m.cancelUndeliverableInput using session.ID and entry.ID; apply the same change
at lines 346-358 after EnqueueInterrupt, and in
internal/session/manager_pending_input.go lines 121-123 after PromoteToSteer
wraps ensureInterruptingInputActivated. Preserve cancellation’s detached-context
behavior through the existing helper.
In `@internal/session/manager_prompt_admission.go`:
- Around line 282-299: Update interruptAdmittedPrompt to check m.inputQueue for
nil before calling EnqueueAdmittedInterrupt, matching the guards in
enqueueAdmittedBusyPrompt and stageAdmittedSteerPrompt; return
ErrPromptInProgress through the same existing error path when the queue is
unavailable.
In `@internal/store/globaldb/global_db_session_input_queue_test.go`:
- Around line 129-165: Rename the test case around the visible
generation-fencing assertions to describe that it dispatches the
current-generation steer while excluding the stale steer; do not claim
steer-before-queued-input priority unless the test also enqueues and asserts a
queued entry is dispatched afterward.
In `@internal/tools/builtin/builtin_test.go`:
- Around line 1407-1420: Update the failure message in the identity-field
assertion within the builtin schema test to describe a string with no minLength
constraint, matching the existing checks on identity.Type and
identity.MinLength. Keep the assertion logic unchanged.
---
Nitpick comments:
In `@internal/api/core/session_prompt.go`:
- Around line 24-27: Define exported status constants alongside SendPromptResult
for the queued, steering, and interrupting values, then replace the
corresponding string literals in internal/session/manager_busy_input.go and the
status check in session_prompt.go with those constants. Preserve the existing
202 behavior for these statuses and the Replayed condition.
In `@internal/api/httpapi/handlers_test.go`:
- Around line 2735-2736: Add t.Parallel() at the beginning of
TestSessionInputHandlersExposeAuthoritativeQueueMutations, while leaving its
subtests sequential because they share the mutable replaceOpts and promoteOpts
captures.
In `@internal/daemon/native_session_input.go`:
- Around line 79-99: In the sessionInputReplace flow, validate text, message_id,
and idempotency_key with requiredNativeString before calling
nativeSessionInputMutationScope; then perform the workspace/session lookup and
replacement unchanged. Apply the same validation-before-lookup ordering to
sessionInputPromote, matching sessionPrompt.
- Around line 60-63: Rename the range variable in the payload-building loop
within the native session input handler so it no longer shadows the decoded
request struct input; update the corresponding SessionInputPayloadFromSession
argument while preserving the existing behavior.
In `@internal/daemon/native_tools_test.go`:
- Around line 5603-5634: Add a test case alongside the existing session_prompt
steer-mode success test that calls ToolIDSessionPrompt with mode "steer" but
omits expected_turn_id, then assert the invalid-input error reason and verify
SendPrompt is not called. Ensure the test fails if the expected_turn_id
validation in sessionPrompt is removed or bypassed.
In `@internal/extension/contract/host_api.go`:
- Around line 161-164: Use one shared mode type for the request and result
contracts: update SessionPromptResult.Mode and SessionInput.Mode to use
apicontract.PromptMode, matching SessionsPromptParams.Mode, and remove the
unnecessary string conversion in the related session handling flow.
In `@internal/extension/host_api_sessions.go`:
- Around line 292-314: Add an exported conversion helper in
prompt_runtime_selection.go that converts the contract’s
PromptRuntimeSelectionPayload into the internal runtime selection type,
complementing PromptRuntimeSelectionFromPayload. Update
hostAPISessionInputFromPending to use this helper instead of constructing
PromptRuntimeSelectionPayload field by field, while preserving nil handling.
In `@internal/session/inputqueue/mutation.go`:
- Around line 15-112: Extract the shared mutation flow from Replace and
PromoteToSteer into a private mutate helper, using a mutationSpec to carry their
differing session, entry, text, target-turn, identity, mode, and delivery
values. Keep trimming, replayMutation, Get, newInsert, replacement identity
assignment, and error propagation in the helper, while passing the
operation-specific store behavior through the apply callback. Update both
methods to construct the appropriate spec and delegate to mutate.
In `@internal/session/inputqueue/queue.go`:
- Around line 243-248: Replace the adjacent string parameters for newInsert and
prepareAdmittedEntry with a single named parameter struct containing sessionID,
text, mode, delivery, and targetTurnID. Update prepareAdmittedEntry and all
newInsert call sites to populate and consume the struct by field, preserving the
existing queue metadata values and behavior.
In `@internal/session/manager_busy_input_test.go`:
- Around line 1812-1814: Add t.Parallel() at the start of
TestManagerPendingInputPromotionReplay, before its subtest declaration, matching
the parallel execution pattern used by the neighboring top-level test functions.
In `@internal/store/globaldb/global_db_session_prompt_admission_queue.go`:
- Around line 284-312: The admission status strings are inline literals and need
a shared wire-level vocabulary. Define SessionPromptAdmissionStatusQueued,
SessionPromptAdmissionStatusInterrupting, and
SessionPromptAdmissionStatusSteering alongside the session input constants in
session_input_queue.go, then replace the literals in queuedPromptAdmissionResult
and stagedPromptAdmissionResult and update every other producer and consumer to
reference these constants.
In `@internal/store/globaldb/queries/session_input.sql`:
- Line 15: Replace the duplicated 'interrupt_then_prompt' SQL literal in
GetNextDispatchableSessionInput, PeekNextSessionInput, and
ListPendingSessionInputs with the store constant
SessionInputDeliveryInterruptThenPrompt, and add one store test inserting an
older queued input plus a newer interrupt-then-prompt input that verifies the
interrupting input is dispatched first.
In `@internal/store/globaldb/schema/definitions/20_sessions.sql`:
- Line 183: Update idx_session_input_queue_pending and the corresponding
dispatch query ordering so they use the same delivery ordering semantics: either
index the CASE delivery expression used by dispatch, or change the queue
ordering to delivery DESC consistently. Preserve the existing enqueued_at and id
ordering and ensure the index column/order matches the final query exactly.
In `@internal/tools/builtin/builtin_test.go`:
- Around line 1492-1529: Remove the output-schema branching from
assertSessionInputMutationSchema, leaving it responsible only for input and
expected_turn_id assertions. Update each caller, including
assertSessionInputPromoteSchema and assertSessionInputReplaceSchema, to
explicitly invoke the appropriate output-schema assertion so expected_turn_id
validation is independent of the returned envelope.
In `@web/src/components/assistant-ui/session-composer.tsx`:
- Around line 223-237: Pass busyInputFenceAvailable from SessionComposer into
SessionComposerQueuedPrompts, and have the queued-row Steer control use it as
steerDisabled. Keep the existing gating for direct Steer and Interrupt buttons
unchanged, and apply the new disabled state only to the queued Steer action.
In `@web/src/hooks/routes/__tests__/use-session-page-controls.test.tsx`:
- Around line 224-246: Add a negative test alongside the existing active-turn
test that renders controls with makeSession("active") and no activeTurnId,
invokes handleSteerPrompt and handleInterruptPrompt, and asserts both
steerPromptMutation.mutateAsync and interruptPromptMutation.mutateAsync are not
called.
In `@web/src/hooks/routes/use-session-page-controls.ts`:
- Around line 79-85: Memoize the derived queuedPrompts projection in the
useSessionPageControls hook using useMemo, keyed on sessionInputs.data, and
reuse a module-level EMPTY_QUEUED_PROMPTS fallback so the returned array
identity remains stable when no inputs exist. Add the required React import and
preserve the existing field mapping.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c4d6e386-bd32-4efa-88f0-6bed098a48b9
⛔ Files ignored due to path filters (26)
docs/qa/_seeds/feature-stories/01_analysis_runtime-sessions.mdis excluded by!**/*.mddocs/qa/_seeds/feature-stories/feature-status.csvis excluded by!**/*.csvdocs/qa/_seeds/final-qa/_children/11-api-cli-parity.mdis excluded by!**/*.mddocs/qa/reports/2026-08-03-session-input-controls.mdis excluded by!**/*.mddocs/qa/scenarios/ET-web-session-transcript-calm-grammar.mdis excluded by!**/*.mddocs/qa/scenarios/RT-019.mdis excluded by!**/*.mddocs/qa/scenarios/RT-059.mdis excluded by!**/*.mddocs/qa/scenarios/RT-session-lifecycle-affordances.mdis excluded by!**/*.mdinternal/store/globaldb/schema/migrations/atlas.sumis excluded by!**/*.sum,!**/*.suminternal/tools/builtin/testdata/native-tool-catalog.jsonis excluded by!**/*.jsonopenapi/compozy.jsonis excluded by!**/*.jsonpackages/site/content/docs/cli/session/index.mdxis excluded by!**/*.mdxpackages/site/content/docs/cli/session/input/cancel.mdxis excluded by!**/*.mdxpackages/site/content/docs/cli/session/input/edit.mdxis excluded by!**/*.mdxpackages/site/content/docs/cli/session/input/index.mdxis excluded by!**/*.mdxpackages/site/content/docs/cli/session/input/list.mdxis excluded by!**/*.mdxpackages/site/content/docs/cli/session/input/meta.jsonis excluded by!**/*.jsonpackages/site/content/docs/cli/session/input/steer.mdxis excluded by!**/*.mdxpackages/site/content/docs/cli/session/meta.jsonis excluded by!**/*.jsonpackages/site/content/docs/cli/session/prompt.mdxis excluded by!**/*.mdxpackages/site/content/docs/sessions/events.mdxis excluded by!**/*.mdxpackages/site/content/docs/sessions/lifecycle.mdxis excluded by!**/*.mdxsdk/typescript/src/generated/contracts.tsis excluded by!**/generated/**,!**/generated/**skills/compozy/references/extension-authoring.mdis excluded by!**/*.mdskills/compozy/references/runtime-operations.mdis excluded by!**/*.mdweb/src/generated/compozy-openapi.d.tsis excluded by!**/generated/**,!**/generated/**,!**/*.d.ts
📒 Files selected for processing (142)
internal/acp/agent_process.gointernal/acp/client.gointernal/acp/handlers.gointernal/acp/handlers_session_state.gointernal/acp/handlers_steer.gointernal/acp/handlers_steer_test.gointernal/acp/handlers_test.gointernal/acp/start_process.gointernal/acp/types.gointernal/api/contract/prompt.gointernal/api/contract/prompt_input.gointernal/api/core/interfaces.gointernal/api/core/session_input.gointernal/api/core/session_manager_stub_test.gointernal/api/core/session_prompt.gointernal/api/core/session_prompt_dispatch.gointernal/api/core/session_workspace.gointernal/api/httpapi/handlers_test.gointernal/api/httpapi/prompt.gointernal/api/httpapi/session_routes.gointernal/api/spec/registry_sessions.gointernal/api/testutil/session_stub.gointernal/api/udsapi/handlers_test.gointernal/api/udsapi/prompt.gointernal/api/udsapi/session_routes.gointernal/cli/client_session_api.gointernal/cli/client_session_input.gointernal/cli/client_session_types.gointernal/cli/client_sessions.gointernal/cli/client_test.gointernal/cli/helpers_test.gointernal/cli/session_command.gointernal/cli/session_input.gointernal/cli/session_input_output.gointernal/cli/session_prompt_events.gointernal/cli/session_prompt_output.gointernal/cli/session_test.gointernal/daemon/daemon_test.gointernal/daemon/native_session_input.gointernal/daemon/native_session_mutation.gointernal/daemon/native_tool_binding_groups.gointernal/daemon/native_tools_test.gointernal/daemon/session_manager_factory.gointernal/daemon/session_steer_source.gointernal/extension/contract/host_api.gointernal/extension/contract/host_api_method_registry.gointernal/extension/contract/host_api_results.gointernal/extension/contract/host_api_test.gointernal/extension/contract/permissions.gointernal/extension/host_api.gointernal/extension/host_api_contract_types.gointernal/extension/host_api_methods.gointernal/extension/host_api_prompt_projection.gointernal/extension/host_api_sessions.gointernal/extension/host_api_test.gointernal/extension/host_api_workspace_binding.gointernal/extensionprotocol/host_api.gointernal/extensionprotocol/host_api_test.gointernal/session/inputqueue/mutation.gointernal/session/inputqueue/queue.gointernal/session/manager.gointernal/session/manager_busy_input.gointernal/session/manager_busy_input_activation.gointernal/session/manager_busy_input_test.gointernal/session/manager_busy_input_types.gointernal/session/manager_input_dispatch.gointernal/session/manager_interrupt_salvage.gointernal/session/manager_pending_input.gointernal/session/manager_prompt_admission.gointernal/session/manager_prompt_admission_identity.gointernal/session/manager_prompt_event_observation.gointernal/session/manager_prompt_input_event.gointernal/session/manager_prompt_lifetime_test.gointernal/session/manager_types.gointernal/session/session.gointernal/session/synthetic_prompt.gointernal/store/globaldb/global_db_session_input_queue.gointernal/store/globaldb/global_db_session_input_queue_mutation.gointernal/store/globaldb/global_db_session_input_queue_scan.gointernal/store/globaldb/global_db_session_input_queue_terminal.gointernal/store/globaldb/global_db_session_input_queue_test.gointernal/store/globaldb/global_db_session_prompt_admission.gointernal/store/globaldb/global_db_session_prompt_admission_queue.gointernal/store/globaldb/queries/session_input.sqlinternal/store/globaldb/schema/definitions/20_sessions.sqlinternal/store/globaldb/schema/migrations/00043_schema.sqlinternal/store/globaldb/sqlcgen/models.gointernal/store/globaldb/sqlcgen/session_input.sql.gointernal/store/session_input_queue.gointernal/store/session_prompt_admission.gointernal/toolmeta/native_entries.gointernal/toolmeta/registry_test.gointernal/tools/builtin/builtin_test.gointernal/tools/builtin/sessions.gointernal/tools/builtin/toolsets.gointernal/tools/builtin_ids.gosdk/go/contracts/host_contracts_gen.gosdk/go/contracts/host_methods_gen.gosdk/go/contracts/types_015_gen.gosdk/go/contracts/types_016_gen.gosdk/go/contracts/types_017_gen.gosdk/go/contracts/types_018_gen.gosdk/go/contracts/types_019_gen.gosdk/go/contracts/types_020_gen.gosdk/go/contracts/types_021_gen.gosdk/go/contracts/types_022_gen.gosdk/go/contracts/types_023_gen.gosdk/go/contracts/types_024_gen.gosdk/go/contracts/types_025_gen.gosdk/go/contracts/types_026_gen.gosdk/go/contracts/types_027_gen.goweb/src/components/assistant-ui/__tests__/session-thread.test.tsxweb/src/components/assistant-ui/hooks/use-session-busy-input-actions.tsweb/src/components/assistant-ui/session-composer-queued-prompts.tsxweb/src/components/assistant-ui/session-composer.tsxweb/src/components/assistant-ui/session-thread-states.tsxweb/src/components/assistant-ui/session-thread-viewport.tsxweb/src/components/assistant-ui/session-thread.tsxweb/src/components/assistant-ui/session-working-row.tsxweb/src/hooks/routes/__tests__/session-page-controls-store.test.tsweb/src/hooks/routes/__tests__/use-session-page-controls.test.tsxweb/src/hooks/routes/session-page-controls-effects.tsweb/src/hooks/routes/session-page-controls-store.tsweb/src/hooks/routes/use-session-page-controls.tsweb/src/systems/os/apps/session/session-window-content.tsxweb/src/systems/session/adapters/__tests__/session-api.test.tsweb/src/systems/session/adapters/session-api.tsweb/src/systems/session/adapters/session-input-api.tsweb/src/systems/session/components/__tests__/runtime-activity-notice.test.tsxweb/src/systems/session/components/runtime-activity-notice.logic.tsweb/src/systems/session/components/runtime-activity-notice.tsxweb/src/systems/session/components/session-prompt-runtime-selector.tsxweb/src/systems/session/hooks/__tests__/use-session-actions.test.tsxweb/src/systems/session/hooks/__tests__/use-session-live-tail.test.tsxweb/src/systems/session/hooks/use-session-actions.tsweb/src/systems/session/hooks/use-session-inputs.tsweb/src/systems/session/index.tsweb/src/systems/session/lib/query-keys.tsweb/src/systems/session/lib/query-options.tsweb/src/systems/session/lib/queued-prompt.tsweb/src/systems/session/lib/session-query-invalidation.tsweb/src/systems/session/types.ts
💤 Files with no reviewable changes (16)
- internal/acp/handlers.go
- internal/daemon/session_manager_factory.go
- internal/acp/types.go
- internal/session/manager_types.go
- internal/cli/client_sessions.go
- internal/acp/start_process.go
- internal/daemon/session_steer_source.go
- internal/acp/agent_process.go
- internal/session/manager_interrupt_salvage.go
- internal/acp/handlers_session_state.go
- internal/acp/handlers_test.go
- internal/session/manager.go
- internal/acp/handlers_steer_test.go
- internal/acp/client.go
- internal/acp/handlers_steer.go
- web/src/components/assistant-ui/session-thread-viewport.tsx
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
web/src/components/assistant-ui/session-composer.tsx (1)
64-64: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse a fail-closed fence default.
Both default fallbacks for
busyInputFenceAvailabletreat missing active-turn state as available. Change the default tofalseor require the prop; otherwise Steer and Interrupt can render enabled when the caller or component receives no active-turn state.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/components/assistant-ui/session-composer.tsx` at line 64, Change both fallback defaults for busyInputFenceAvailable in the session composer configuration to false, including the visible default assignment, so missing active-turn state disables Steer and Interrupt. Preserve explicitly provided prop values.Source: Coding guidelines
internal/store/globaldb/global_db_session_input_queue_test.go (1)
346-408: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winVerify replacement preserves FIFO position.
This test has no later queued entry. It passes if replacement moves behind a later input because it only checks that one entry remains. Enqueue a follower before replacement, then assert that
ClaimNextSessionInputclaims the replacement before the follower.As per coding guidelines, tests must focus on critical workflow behavior and verify behavior outcomes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/store/globaldb/global_db_session_input_queue_test.go` around lines 346 - 408, The replacement test should verify FIFO position, not only replacement identity and pending count. In the test around ReplaceSessionInput, enqueue a follower after the original but before replacement, then use ClaimNextSessionInput to assert the replacement is claimed first and the follower remains next; preserve the existing replay and conflict assertions.Sources: Coding guidelines, Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@internal/store/globaldb/global_db_session_input_queue_test.go`:
- Around line 346-408: The replacement test should verify FIFO position, not
only replacement identity and pending count. In the test around
ReplaceSessionInput, enqueue a follower after the original but before
replacement, then use ClaimNextSessionInput to assert the replacement is claimed
first and the follower remains next; preserve the existing replay and conflict
assertions.
In `@web/src/components/assistant-ui/session-composer.tsx`:
- Line 64: Change both fallback defaults for busyInputFenceAvailable in the
session composer configuration to false, including the visible default
assignment, so missing active-turn state disables Steer and Interrupt. Preserve
explicitly provided prop values.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: cc46325c-892a-41fb-b2d8-adcb4a7053f8
⛔ Files ignored due to path filters (8)
.codex/scripts/coderabbit-review-graphql.shis excluded by!.codex/**docs/qa/bugs/BUG-20260803-prompt-cancel-warning-noise.mdis excluded by!**/*.mddocs/qa/reports/2026-08-03-session-input-coderabbit.mdis excluded by!**/*.mddocs/qa/scenarios/ET-web-session-transcript-calm-grammar.mdis excluded by!**/*.mddocs/qa/scenarios/RT-019.mdis excluded by!**/*.mddocs/qa/scenarios/RT-059.mdis excluded by!**/*.mdinternal/store/globaldb/schema/migrations/atlas.sumis excluded by!**/*.sum,!**/*.sumsdk/typescript/src/generated/contracts.tsis excluded by!**/generated/**,!**/generated/**
📒 Files selected for processing (57)
internal/api/contract/contract_test.gointernal/api/contract/prompt_runtime_selection.gointernal/api/core/session_input.gointernal/api/core/session_prompt.gointernal/api/httpapi/handlers_test.gointernal/cli/client_test.gointernal/cli/session_test.gointernal/daemon/native_session_input.gointernal/daemon/native_session_mutation.gointernal/daemon/native_tools_test.gointernal/extension/contract/host_api_results.gointernal/extension/host_api_sessions.gointernal/extension/host_api_test.gointernal/session/inputqueue/mutation.gointernal/session/inputqueue/queue.gointernal/session/manager_busy_input.gointernal/session/manager_busy_input_activation.gointernal/session/manager_busy_input_test.gointernal/session/manager_pending_input.gointernal/session/manager_prompt_admission.gointernal/session/manager_prompt_lifetime_test.gointernal/store/globaldb/global_db_session_input_queue_test.gointernal/store/globaldb/global_db_session_prompt_admission.gointernal/store/globaldb/global_db_session_prompt_admission_queue.gointernal/store/globaldb/queries/session_input.sqlinternal/store/globaldb/queries/session_prompt_admission.sqlinternal/store/globaldb/schema/definitions/20_sessions.sqlinternal/store/globaldb/schema/migrations/00044_schema.sqlinternal/store/globaldb/sqlcgen/session_input.sql.gointernal/store/globaldb/sqlcgen/session_prompt_admission.sql.gointernal/store/session_prompt_admission.gointernal/tools/builtin/builtin_test.gosdk/go/contracts/types_005_gen.gosdk/go/contracts/types_006_gen.gosdk/go/contracts/types_007_gen.gosdk/go/contracts/types_008_gen.gosdk/go/contracts/types_009_gen.gosdk/go/contracts/types_010_gen.gosdk/go/contracts/types_011_gen.gosdk/go/contracts/types_012_gen.gosdk/go/contracts/types_013_gen.gosdk/go/contracts/types_014_gen.gosdk/go/contracts/types_015_gen.gosdk/go/contracts/types_019_gen.goweb/src/components/assistant-ui/__tests__/session-thread.test.tsxweb/src/components/assistant-ui/hooks/use-session-busy-input-actions.tsweb/src/components/assistant-ui/session-composer-queued-prompts.tsxweb/src/components/assistant-ui/session-composer.tsxweb/src/hooks/routes/__tests__/use-session-page-controls.test.tsxweb/src/hooks/routes/use-session-page-controls.tsweb/src/lib/client-id.tsweb/src/systems/network/hooks/network-message-builders.tsweb/src/systems/session/components/__tests__/runtime-activity-notice.test.tsxweb/src/systems/session/components/runtime-activity-notice.tsxweb/src/systems/session/hooks/__tests__/use-session-actions.test.tsxweb/src/systems/session/hooks/use-session-actions.tsweb/src/systems/session/lib/session-prompt-chat-transport.ts
🚧 Files skipped from review as they are similar to previous changes (28)
- web/src/systems/session/components/runtime-activity-notice.tsx
- internal/daemon/native_session_mutation.go
- internal/session/manager_busy_input_activation.go
- web/src/systems/session/hooks/tests/use-session-actions.test.tsx
- internal/store/globaldb/global_db_session_prompt_admission_queue.go
- internal/session/manager_prompt_lifetime_test.go
- internal/extension/contract/host_api_results.go
- internal/cli/client_test.go
- web/src/components/assistant-ui/hooks/use-session-busy-input-actions.ts
- internal/store/globaldb/schema/definitions/20_sessions.sql
- internal/api/core/session_input.go
- internal/session/manager_pending_input.go
- internal/api/core/session_prompt.go
- web/src/components/assistant-ui/session-composer-queued-prompts.tsx
- internal/session/manager_prompt_admission.go
- internal/tools/builtin/builtin_test.go
- internal/store/globaldb/queries/session_input.sql
- internal/api/httpapi/handlers_test.go
- internal/extension/host_api_sessions.go
- internal/cli/session_test.go
- internal/session/inputqueue/mutation.go
- internal/daemon/native_tools_test.go
- internal/session/manager_busy_input.go
- web/src/hooks/routes/use-session-page-controls.ts
- internal/daemon/native_session_input.go
- web/src/systems/session/components/tests/runtime-activity-notice.test.tsx
- internal/extension/host_api_test.go
- internal/store/globaldb/sqlcgen/session_input.sql.go
Summary
This PR repairs busy-session input end to end. Queue, Steer, and Interrupt are now daemon-owned, durable operations instead of transient client-side intent. The web composer projects the daemon's authoritative state, keeps drafts until acknowledgement, and presents a single calm working signal without duplicate lifecycle warnings.
The change is intentionally a hard cut. It removes the old ACP steer handler, the runtime steer source, interrupt-salvage behavior, and the web's local queued-prompt mirror rather than maintaining two competing models.
Problem
While a session was running:
The root cause was split ownership. The web held a local queue while the runtime had separate prompt, steer, and cancellation paths. A client-side acknowledgement did not prove durable admission, edit/cancel/promote mutations were not one atomic model, and expected ACP cancellation leaked into the transcript as a provider failure.
Runtime and storage changes
session_input_queuepersistence through append-only migration00043_schema.sql.after_turn.interrupt_then_promptwith an expected active-turn fence.FailureCanceledpath.Public surfaces
The same authoritative behavior is available across every supported management surface:
GET /api/workspaces/{workspace_id}/sessions/{session_id}/prompt/queuePUT /api/workspaces/{workspace_id}/sessions/{session_id}/prompt/queue/{queue_entry_id}POST /api/workspaces/{workspace_id}/sessions/{session_id}/prompt/queue/{queue_entry_id}/steerDELETE /api/workspaces/{workspace_id}/sessions/{session_id}/prompt/queue/{queue_entry_id}compozy session prompt --queue|--steer|--interruptcompozy session input list|edit|steer|cancelcompozy__session_promptcompozy__session_inputs_listcompozy__session_input_replacecompozy__session_input_cancelcompozy__session_input_promoteThe old dedicated interrupt endpoint and ACP steer path were removed. There are no aliases, compatibility fallbacks, polling shims, or salvage bridges.
Web behavior
SessionComposer, leaving the component inside the repository's complexity limit.Reference research
The design was checked against the working session implementations in
.resources/codex,.resources/synara, and.resources/t3code. The adopted common pattern is durable/server-owned admission plus a client projection, rather than optimistic local ownership. TanStack Query v5 documentation was also checked through Context7 for immutable authoritative cache updates after mutation acknowledgement.Tests and QA
Automated coverage
session inputlist/edit/steer/cancel commands.TestPromptTranscriptMarkerSuppressesExpectedCancellationfor the provider-warning regression found during QA.Verification completed
make bun-lint— pass, zero warnings.session-thread.test.tsx— 42/42 pass.The final
make gate-fullattempt passed codegen, installer checks, and source-size checks, then exposed oneSessionComposercomplexity error at BunLint. That error was fixed by extracting the handlers, and its owning lint, typecheck, and canonical test suite all passed. Per explicit operator direction, the full gate was not repeated because this PR is entering a review/remediation cycle and the full gate will run again after that cycle.Real-user QA
QA used a fresh isolated lab with both a deterministic ACP provider and a real Codex provider:
sess-8a3ded8e5a329240.clean: true; no daemon, browser, Storybook, watcher, or tmux process survived.Tracked report:
docs/qa/reports/2026-08-03-session-input-controls.md.Migration and compatibility
This is an alpha hard cut:
00043and updates the declarative schema, Atlas checksum, sqlc queries, and generated code.Compozy Impact Audit
compozy__session_prompt; added list/replace/cancel/promote input tools; regenerated descriptors, catalog snapshots, schema digests, toolsets, metadata, capability bindings, and tests.config.tomlkey or lifecycle change is required because admission is session runtime state.skills/compozy/references/runtime-operations.mdandextension-authoring.mdwith the new commands, tools, delivery modes, and host methods.Release notes
🐛 Bug Fixes
Review guide
Suggested review order:
internal/store/session_input_queue.goandinternal/session/inputqueue/for the durable model and mutation invariants.internal/session/manager_busy_input*.goandmanager_pending_input.gofor fencing, cancellation, activation, and dispatch.internal/api/core/session_input.go, route registries, CLI, native tools, and extension host for public parity.web/src/systems/session/hooks/use-session-inputs.tsand the session composer/thread changes for authoritative UI behavior.Summary by CodeRabbit