handle /clear in clide: reset to a fresh session instead of forwarding
Claude Code's /clear forks the conversation to a new session-id, which clide's transcript reader (pinned to the spawn --session-id) can't follow — so after /clear the pane froze on the old transcript and looked dead. clide now owns /clear: it's intercepted in the composer's send path (never forwarded to tmux), tears the pane's session down, and respawns a fresh empty one. A new session-id is forced even for the primary so it starts empty rather than resuming the old transcript; _spawn's self-heal kills the stale tmux session. The old transcript is left on disk. Known follow-up (T-156): /resume and /compact have the same forking problem but need different handling (a session picker, not a wipe). T-156. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -301,6 +301,13 @@ class _ClaudePaneState extends State<ClaudePane> {
|
||||
// The no-tmux fallback runs claude directly in our PTY, where pane.write
|
||||
// does reach it.
|
||||
void _send(String text) {
|
||||
// Commands clide owns (T-156) are handled here, never forwarded — Claude
|
||||
// Code's /clear forks the session to a new id our reader can't follow, so
|
||||
// we tear this session down and start a fresh one instead.
|
||||
if (clideOwnedCommand(text) == 'clear') {
|
||||
unawaited(_clearSession());
|
||||
return;
|
||||
}
|
||||
if (_usingTmux) {
|
||||
final session = _sessionName;
|
||||
if (session == null) return;
|
||||
@@ -321,6 +328,31 @@ class _ClaudePaneState extends State<ClaudePane> {
|
||||
unawaited(ipc.request('pane.write', args: {'id': id, 'text': encodeClaudeInput(text)}));
|
||||
}
|
||||
|
||||
/// clide-owned `/clear` (T-156): tear this pane's session down and respawn a
|
||||
/// brand-new, empty one. A fresh session id is forced — even for the primary,
|
||||
/// whose id is normally deterministic — so we start empty rather than resume
|
||||
/// the old transcript; _spawn's self-heal kills the now-stale tmux session
|
||||
/// because the new id has no transcript yet. The old transcript is left on
|
||||
/// disk (history preserved, just detached from this pane).
|
||||
Future<void> _clearSession() async {
|
||||
_conversation?.dispose();
|
||||
_conversation = null;
|
||||
unawaited(_feed?.dispose());
|
||||
_feed = null;
|
||||
_statusSub?.cancel();
|
||||
_statusSub = null;
|
||||
_eventSub?.cancel();
|
||||
_eventSub = null;
|
||||
_sessionId = freshSessionId();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_status = const SessionStatus();
|
||||
_statusLine = 'clearing…';
|
||||
});
|
||||
}
|
||||
await _spawn();
|
||||
}
|
||||
|
||||
void _subscribe() {
|
||||
final kernel = _kernel();
|
||||
if (kernel == null) return;
|
||||
|
||||
@@ -27,6 +27,18 @@ bool isKnownSlashCommand(String text, Iterable<String> known) {
|
||||
return token != null && known.contains(token);
|
||||
}
|
||||
|
||||
/// Slash commands clide handles itself instead of forwarding to Claude:
|
||||
/// Claude Code's own handling forks the session to a new id that clide's
|
||||
/// transcript reader can't follow, so clide owns the semantics (T-156).
|
||||
const Set<String> kClideOwnedCommands = {'clear'};
|
||||
|
||||
/// The clide-owned command in [text] (a single-line leading-slash token in
|
||||
/// [kClideOwnedCommands]), or null.
|
||||
String? clideOwnedCommand(String text) {
|
||||
final token = slashCommandToken(text);
|
||||
return token != null && kClideOwnedCommands.contains(token) ? token : null;
|
||||
}
|
||||
|
||||
bool _isWs(String c) => c == ' ' || c == '\t' || c == '\n';
|
||||
|
||||
/// An in-progress slash query at the cursor — the `/` position and the word
|
||||
|
||||
Reference in New Issue
Block a user