Skip to content

[Optimization] Cancel and settle active chat runs when deleting a conversation #90

Description

@aiis2

Problem

On authoritative origin/master@fb7b295, the sidebar allows deleting a conversation while its agent is streaming. deleteConversation removes the row from UI/DB but does not abort the conversation controller, resolve an outstanding ask_user, or remove the ID from streaming state.

A direct store probe deleting an active configured run shows:

{
  "immediate": {
    "signalAborted": false,
    "conversations": [],
    "activeConversationId": null,
    "isStreaming": true,
    "streamingConversationIds": ["conv-delete"]
  },
  "continuedAfterDelete": true
}

A second probe deleting while ask_user is awaiting input shows:

{
  "signalAborted": false,
  "sendSettled": false,
  "pendingQuestion": {
    "callId": "call-1",
    "question": "Choose?",
    "convId": "conv-question"
  },
  "isStreaming": true,
  "streamingConversationIds": ["conv-question"]
}

Consequences:

  1. A deleted conversation can keep running tools and commit side effects after the user believes it is gone.
  2. Deleting during ask_user leaves an unreachable promise and orphan question that never settles.
  3. The app can remain globally isStreaming: true with no conversation visible and no usable Stop target.

Expected

  1. Deleting a streaming conversation aborts its per-conversation controller immediately.
  2. If it is awaiting ask_user, deletion resolves the stored resolver with the existing __ABORT__ sentinel so the agent can unwind.
  3. The deleted ID is removed from streamingConversationIds; isStreaming is recomputed for the newly active conversation.
  4. pendingQuestion and agentTurnInfo are cleared when they belong to the deleted conversation.
  5. Deleting a background stream must not stop another active conversation or incorrectly mark that active conversation idle.
  6. The existing agent finalizer remains the owner of controller cleanup and settled tool handling; deletion must not resurrect or persist the deleted conversation.

Proposed plan

  1. Treat deleteConversation(id) as cancellation plus removal: resolve any per-conversation question, abort the controller, then update store state atomically.
  2. Remove only the deleted ID from streaming state and derive isStreaming from the remaining IDs and next active conversation.
  3. Leave the controller entry until runAgentWithModel finalizes so wasAborted remains true and memory consolidation stays skipped.
  4. Add product-level store regressions for an active stream, an ask_user wait, and preservation of another active stream.
  5. Run focused tests, full CJS suite, both TypeScript compilers, Vite build, Electron isolation/runtime smoke tests, and dependency audits.

Non-goals

  • Waiting synchronously for already-running non-cooperative tools before removing the conversation UI.
  • Adding a confirmation dialog or changing sidebar design.
  • Refactoring all chat lifecycle state into a new store.
  • Changing Stop semantics for conversations that are not deleted.

Mainline note

The repository has no origin/main; origin/HEAD points to origin/master. This cycle starts from authoritative origin/master@fb7b295.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions