Skip to content

Commit 021287e

Browse files
committed
fix: gate initialPrompt on feed readiness, not just auth resolution
Move prompt send back into useSessionFeed where it waits for the event stream. Add canSendPrompt accessor so session-controller gates on auth. Fixes premature send/drop race identified in counsel review. Remove authGatePending from public SessionController interface.
1 parent baa0cd7 commit 021287e

2 files changed

Lines changed: 13 additions & 15 deletions

File tree

‎apps/tui/src/hooks/use-session-feed.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ export function useSessionFeed(
259259
cast: <A, E>(effect: Effect.Effect<A, E, never>) => void,
260260
callbacks: SessionFeedCallbacks,
261261
initialPrompt?: string,
262+
canSendPrompt?: () => boolean,
262263
): SessionFeed {
263264
const [store, setStore] = createStore<{ messages: Message[]; events: SessionEvent[] }>({
264265
messages: [],
@@ -380,8 +381,16 @@ export function useSessionFeed(
380381
Effect.forkScoped,
381382
)
382383

383-
// Send the prompt only after the event stream is established.
384+
// Send the prompt only after the event stream is established
385+
// and the caller signals readiness (e.g. auth gate resolved).
384386
if (initialPrompt !== undefined && initialPrompt !== "" && !sentPrompts.has(key)) {
387+
// Wait for canSendPrompt if provided (polls via short sleep)
388+
if (canSendPrompt !== undefined) {
389+
while (!canSendPrompt()) {
390+
yield* Effect.sleep("50 millis")
391+
if (currentKey !== key) return yield* Fiber.join(eventsFiber)
392+
}
393+
}
385394
sentPrompts.add(key)
386395
client.log.info("feed.sendInitialPrompt", {
387396
sessionId: session,

‎apps/tui/src/routes/session-controller.ts‎

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ export interface SessionController {
8989
onSessionTreeSelect: (sessionId: SessionId) => void
9090
onForkSelect: (messageId: MessageId) => void
9191
onPromptSearchEvent: (event: Extract<SessionUiEvent, { _tag: "PromptSearch" }>["event"]) => void
92-
authGatePending: () => boolean
9392
}
9493

9594
const SPINNER_FRAMES = ["·", "•", "*", "⁑", "⁂"]
@@ -273,20 +272,11 @@ export function createSessionController(props: {
273272
router.navigateToSession(sessionId, branchId)
274273
},
275274
},
276-
// Never pass initialPrompt to feed — we control prompt sending through the auth gate.
277-
undefined,
275+
props.initialPrompt,
276+
// Gate prompt send on auth resolution — feed waits for stream + this signal
277+
() => !authGatePending(),
278278
)
279279

280-
// Send initial prompt once auth resolves and feed is ready
281-
if (props.initialPrompt !== undefined && props.initialPrompt !== "") {
282-
let promptSent = false
283-
createEffect(() => {
284-
if (promptSent || authGatePending()) return
285-
promptSent = true
286-
client.sendMessage(props.initialPrompt ?? "")
287-
})
288-
}
289-
290280
const items = createMemo<SessionItem[]>(() => feed.items())
291281
const promptSearch = createPromptSearchController({
292282
state: () => getPromptSearchState(uiState()),
@@ -627,7 +617,6 @@ export function createSessionController(props: {
627617
onSessionTreeSelect,
628618
onForkSelect,
629619
onPromptSearchEvent: (event) => promptSearch.onEvent(event),
630-
authGatePending,
631620
}
632621
}
633622

0 commit comments

Comments
 (0)