Skip to content

Allow renaming and delete of (almost) all sessions.#222

Open
cyan-ember wants to merge 2 commits intodaggerhashimoto:masterfrom
cyan-ember:review/agent-session-renaming-clean
Open

Allow renaming and delete of (almost) all sessions.#222
cyan-ember wants to merge 2 commits intodaggerhashimoto:masterfrom
cyan-ember:review/agent-session-renaming-clean

Conversation

@cyan-ember
Copy link
Copy Markdown
Contributor

@cyan-ember cyan-ember commented Apr 2, 2026

What

Makes rename and delete controls consistently available for all sessions in the Agent panel, while blocking deletion of main agent sessions with a clearer explanatory modal.

Why

Agents panel currently hides rename/delete for some session types even though the gateway broadly supports session labeling and deletion. This makes direct-channel sessions and ACP sessions feel inconsistent in the agent panel. Main agent sessions are still not deletable, but that exception is better handled with an explicit explanation than by hiding the control.

How

Extends the existing session action visibility so all session rows can show rename/delete controls in the agent panel, including direct-channel, ACP, and cron-backed sessions.
Intercepts delete clicks for main agent sessions and shows a modal explaining that:

Adds focused regression tests covering:

  • direct child sessions show rename and delete
  • ACP child sessions show rename and delete
  • deleting a main agent session shows the explanatory modal

Manual testing

  • Try renaming channel sessions from Telegram or Discord
    • those should now be renameable from the agent panel
  • Try renaming ACP sessions
    • those should now be renameable as well
  • Try deleting a main agent session
    • the delete button should still be visible
    • clicking it should open a modal explaining why deletion isn’t allowed
    • the modal should point to Reset for clearing context

Summary by CodeRabbit

  • Bug Fixes

    • Prevented deletion of main agent sessions; attempting deletion now displays an informational dialog explaining why the session cannot be deleted, with a documentation link for additional guidance.
  • Tests

    • Added test suite for session action controls, verifying rename and delete functionality across different session types including direct child sessions, ACP child sessions, and main agent sessions.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

📝 Walkthrough

Walkthrough

This change blocks deletion of top-level main agent sessions by introducing a new deleteBlockedTarget state in SessionList and displaying a warning dialog. SessionNode's rename/delete action rendering is refactored to account for callback prop existence alongside permission flags.

Changes

Cohort / File(s) Summary
Session Deletion Blocking
src/features/sessions/SessionList.tsx
Added deleteBlockedTarget state and dedicated Dialog to prevent deletion of main agent sessions; modified handleSetDeleteTarget to detect top-level agent keys and block deletion with warning; simplified delete-confirmation dialog by removing root-agent-specific messaging.
Session Actions UI Refactoring
src/features/sessions/SessionNode.tsx
Updated canRenameDelete logic to incorporate callback prop existence (onStartRename, onDelete) alongside permission flags; refactored hover-action rendering to conditionally render rename/delete buttons based on hasRenameAction and hasDeleteAction instead of outer canRenameDelete guard.
Session Actions Test Coverage
src/features/sessions/SessionList.test.tsx
Added three test cases covering rename/delete action exposure for direct and ACP child sessions, and validation of "Cannot Delete Main Agent Session" dialog when attempting to delete a top-level main agent session.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PR #111: Introduces the session key and top-level-agent detection logic (isTopLevelAgentSessionKey) that forms the foundation for the deletion-blocking feature added in this PR.

Poem

🐰 A rabbit hops with careful grace,
Protecting agents in their place,
"Delete? Oh no!" the dialog cries,
Main sessions safe—a wise disguise,
Callbacks dance where actions play! 🌿

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: enabling rename and delete for all sessions except main agent sessions, which aligns with the core objective of the PR.
Description check ✅ Passed The description covers all required template sections (What, Why, How) with concrete details and provides manual testing steps, though the Type of Change checklist and build/test verification checkboxes are not completed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/features/sessions/SessionNode.tsx (1)

61-82: ⚠️ Potential issue | 🟠 Major

Memoization comparator must check handler presence changes.

The arePropsEqual function ignores the optional handler props (onAbort, onDelete, onStartRename), but lines 146–149 derive action button visibility directly from whether these handlers exist. When SessionList conditionally passes handlers based on parent configuration (lines 228–229), toggling handler availability won't trigger a re-render, leaving stale buttons or missing actions until an unrelated prop changes.

Add handler presence comparisons to the memo predicate:

Suggested fix
     prev.renameValue === next.renameValue &&
     prev.granularStatus === next.granularStatus &&
-    prev.compact === next.compact
+    prev.compact === next.compact &&
+    Boolean(prev.onAbort) === Boolean(next.onAbort) &&
+    Boolean(prev.onDelete) === Boolean(next.onDelete) &&
+    Boolean(prev.onStartRename) === Boolean(next.onStartRename)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/sessions/SessionNode.tsx` around lines 61 - 82, arePropsEqual
currently omits optional handler props so changes to onAbort, onDelete, or
onStartRename won't trigger re-renders and action button visibility stays stale;
update arePropsEqual (which compares SessionNodeProps) to also compare presence
(and/or reference) of prev.onAbort vs next.onAbort, prev.onDelete vs
next.onDelete, and prev.onStartRename vs next.onStartRename so toggling those
handlers in SessionList updates SessionNode rendering.
🧹 Nitpick comments (2)
src/features/sessions/SessionList.tsx (1)

56-57: Drop the now-dead isRootAgent field from deleteTarget.

After the Line 141 early return, this state is never populated for top-level agent sessions, so the field is always false and no longer read. Removing it will make the delete-dialog state contract easier to follow.

Suggested cleanup
-  const [deleteTarget, setDeleteTarget] = useState<{ key: string; label: string; descendantCount: number; isRootAgent: boolean } | null>(null);
+  const [deleteTarget, setDeleteTarget] = useState<{ key: string; label: string; descendantCount: number } | null>(null);
     setDeleteTarget({
       key,
       label,
       descendantCount: targetNode ? countDescendants(targetNode) : 0,
-      isRootAgent: false,
     });

Also applies to: 147-151

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/sessions/SessionList.tsx` around lines 56 - 57, The state type
for deleteTarget contains a now-unused isRootAgent field; remove isRootAgent
from the useState declaration for deleteTarget and from any places that set or
read it (e.g., calls to setDeleteTarget, delete-dialog props/state consumers and
any destructuring that expects isRootAgent). Update the type to { key: string;
label: string; descendantCount: number } | null, remove any logic branches that
reference isRootAgent, and ensure deleteBlockedTarget usage remains unchanged.
src/features/sessions/SessionList.test.tsx (1)

78-90: Also assert that deletion stays blocked.

This currently proves the informational dialog opens, but not that the destructive callback stayed untouched. Please keep the onDelete mock in a variable and assert it is not called after the click sequence, otherwise a regression that both opens the modal and still deletes would pass.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/sessions/SessionList.test.tsx` around lines 78 - 90, The test
opens the informational dialog but doesn't assert the delete callback wasn't
invoked; change the inline onDelete mock to a named mock variable (e.g., const
onDelete = vi.fn()) passed into renderSessionList, then after firing the actions
assert that onDelete was not called (expect(onDelete).not.toHaveBeenCalled()) to
ensure deletion remains blocked; update references in this test to use the named
onDelete mock and keep the existing assertions that the dialog and link are
present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/features/sessions/SessionNode.tsx`:
- Around line 61-82: arePropsEqual currently omits optional handler props so
changes to onAbort, onDelete, or onStartRename won't trigger re-renders and
action button visibility stays stale; update arePropsEqual (which compares
SessionNodeProps) to also compare presence (and/or reference) of prev.onAbort vs
next.onAbort, prev.onDelete vs next.onDelete, and prev.onStartRename vs
next.onStartRename so toggling those handlers in SessionList updates SessionNode
rendering.

---

Nitpick comments:
In `@src/features/sessions/SessionList.test.tsx`:
- Around line 78-90: The test opens the informational dialog but doesn't assert
the delete callback wasn't invoked; change the inline onDelete mock to a named
mock variable (e.g., const onDelete = vi.fn()) passed into renderSessionList,
then after firing the actions assert that onDelete was not called
(expect(onDelete).not.toHaveBeenCalled()) to ensure deletion remains blocked;
update references in this test to use the named onDelete mock and keep the
existing assertions that the dialog and link are present.

In `@src/features/sessions/SessionList.tsx`:
- Around line 56-57: The state type for deleteTarget contains a now-unused
isRootAgent field; remove isRootAgent from the useState declaration for
deleteTarget and from any places that set or read it (e.g., calls to
setDeleteTarget, delete-dialog props/state consumers and any destructuring that
expects isRootAgent). Update the type to { key: string; label: string;
descendantCount: number } | null, remove any logic branches that reference
isRootAgent, and ensure deleteBlockedTarget usage remains unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dfbc67c6-87ef-4014-8210-15ff50ccf864

📥 Commits

Reviewing files that changed from the base of the PR and between f0dbe68 and cdce39f.

📒 Files selected for processing (3)
  • src/features/sessions/SessionList.test.tsx
  • src/features/sessions/SessionList.tsx
  • src/features/sessions/SessionNode.tsx

@daggerhashimoto
Copy link
Copy Markdown
Owner

daggerhashimoto commented Apr 3, 2026

This addresses a real problem, thank you for introducing this. However there is a minor point I'd like to discuss before we go ahead with this:

  1. With these changes isTopLevelAgentSessionKey matches all top-level agent sessions like
  • agent:main:main
  • agent:reviewer:main
  • agent:designer:main etc.

In my view the ideal behaviour should be preventing deletes for only agent:main:main. These changes prevent all top-level roots from deletion.

so if non-main top-level roots should remain deletable (which I think is desirable for most users) this would be too broad for that. What do you think ?

@cyan-ember
Copy link
Copy Markdown
Contributor Author

cyan-ember commented Apr 3, 2026

Yeah, I was thinking about that too. Here's where I ended up.

The Agent panel is operating both as a session list and an Agent list (and I think it does that in an elegant way). But when you click on the agent::main session its a bit ambiguous on whether you want to do an agent-operation or a session-operation

if you click delete, you can interpret it either as an agent-delete or as a session-delete (of the agent::main session)

  • if its an agent-delete : deleting means removing the agent from the config and moving their workspace-agent/ to /.trash
  • if its a session-delete : deleting means removing the session store record and archiving the transcript, and importantly, the next time you message that agent a new session gets created

So here's where it gets a little weird. If you delete an agent::main session without deleting the agent, then the gateway will simply spawn a new session next time you talk to the agent. Which ends up having the same effect as if you used /reset initially rather than delete and then a re-message (although the mechanism is different).

The question is, how do you talk to that agent? If it's bound to a channel like telegram, you can message it through the channel to spawn a new session, but what should Nerve show in the Agent panel at that point (where there is an agent, but no session), and how would Nerve route a message to that session-less agent to spawn a new session (not actually sure on this point, but I assume it must be possible).

So here's where I ended up on how I think the Nerve UI can best abstract what's going on in the gateway when doing deletes. If you're deleting

  • normal session - delete session
  • agent::main session - use a modal to prompt user on two options (a) session reset or (b) agent delete
  • main:main - use a modal to prompt user to session reset (explaining agent delete of main:main is not allowed)

I wasn't ready to tackle deleting agents, so this PR only goes half way handles the session half, and then on *:main sessions pops a modal to educate on /reset (since there's already a button) and delete (by linking to documentation).

It felt like a good initial step, but I think the right full solution would be what I think you're suggesting and enable agent deletes as well. So perhaps the next PR after this (or along side it) should update that education modal and turn it into a two button modal with, "do yo want to (a) reset the session, or (b) delete the agent."

@daggerhashimoto
Copy link
Copy Markdown
Owner

daggerhashimoto commented Apr 6, 2026

You're right, my previous comment was from a narrow angle view. The current PR only really handles the session-deletion side, not the broader agent-deletion case and those are not the same thing.

I agree with your framing here: for agent:<name>:main, "delete" is ambiguous between deleting the session and deleting the agent itself. A full agent-deletion flow probably deserves its own explicit UX, maybe even a more deliberate github-style confirmation since it's pretty destructive.

If you don't want to blow-up the scope of this work, my suggestion would be to keep this PR scoped to the session/transcript side and merge it as the foundation without changing current behavior around root session deletion until that follow-up is properly designed. But if you want to keep pushing and build the full agent deletion path here, I'm fine with that too.

Let me know which direction you want to take.

Also, thank you for the work here. This was needed, and I appreciate you stepping in and thinking it through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants