ข้ามไปยังเนื้อหา

Thread and State Lifecycle

This page documents the actual runtime lifecycle implemented in kobi-ai chat hooks/components.

StateMeaningMain Code Path
newNo materialized thread yetChatPane + useChatLifecycle
pending_threadThread is pre-created for upload flow but not bound to UI state yetChatPane.ensureThread()
thread_readyReal UUID thread exists and is bound to current route/sessionuseChatSender + saveSession
streamingAI response is in progressuseChatSender, useChatActions.readResumeStream
hitl_pendingAI proposed resumable actions waiting decisionActionProposal, actionResumeGuards
hitl_stagedBatch decision staged per action (staged_approved/staged_rejected)useChatActions + hitlBatchState
hitl_submittedBatch submitted to /api/ai/resume (submitted)useChatActions.handleBatchConfirm

Current implementation supports pre-create + silent URL update:

sequenceDiagram
  participant UI as ChatPane
  participant BFF as /api/chat/threads
  participant SENDER as useChatSender
  participant ROUTER as Next router/history

  alt file upload precreate
    UI->>BFF: createThread()
    BFF-->>UI: thread UUID
    UI->>UI: keep in pendingThreadRef only
  end

  UI->>SENDER: sendMessage(threadId override optional)
  alt no thread exists
    SENDER->>BFF: createThread()
    BFF-->>SENDER: thread UUID
  end
  SENDER->>ROUTER: navigate(..., { replace: true, silent: true })
  SENDER->>UI: continue send/stream on same mounted pane

The silent route update uses window.history.replaceState to avoid remount jitter.

useChatLifecycle enforces these rules:

  • If URL thread is new, reset to clean local state.
  • If URL thread is UUID and differs from current state, load messages for that thread.
  • If a UUID thread is not found, clear saved session and redirect to base route.
  • Quick chat empty-thread URL is treated as invalid and redirected to base route.
  • Project/team mode can treat empty thread as valid (when team-scoped context is provided).
  • Saved session restoration can rewrite URL when no explicit URL thread is present.

/api/ai/* proxy path requires materialized thread context:

  • Canonical field: threadId
  • Deprecated alias accepted: sessionId
  • Mismatched threadId/sessionId returns 400
  • Missing/new thread returns 400 (“Thread ID is required. Create thread via /api/chat/threads first”)

There is no server-side auto-generation fallback in the BFF proxy path.

Batch flow is dynamic, not fixed-size:

  1. Collect pending resumable actions for one message (collectHitlBatchCandidates).
  2. Stage per-action decision (staged_approved or staged_rejected).
  3. Allow undo/reset before submit.
  4. Submit one /api/ai/resume call with decisions[].
  5. Set temporary submitted, then finalize to approved/rejected after response.

Because mapping is by toolCallId/tool_call_id, batch size can be N=1..k depending on model output.

ActionContext avoids DB fetch for temporary client-only message IDs:

  • If messageId is UUID-like, it may fetch from DB.
  • If messageId is not UUID-like, it uses local message context directly.

This avoids invalid UUID errors like:

  • invalid input syntax for type uuid: "resume-bot-..."

After successful resume:

  • Strategy mode triggers useStrategyStore.loadItemsByThread immediately and with delayed retry.
  • Other modes trigger canvas refresh callback twice (immediate + delayed retry).

This double refresh is intentional to smooth eventual consistency between stream completion and persisted mutations.

  • Never assume one interrupt equals one action; handle decisions[] arrays.
  • Keep action->tool_call_id mapping stable when mutating message action lists.
  • Prefer staged UI states over immediate destructive state changes for better recovery and undo behavior.