Make a command a thing the app knows, and hold it until it can run
Typing "/" now suggests what this app understands -- `/compact` and `/rename <name>` -- with a line each about what they do, and anything else beginning with a slash is passed to whatever runs the session, because a dialect's own vocabulary grows without this list. None of them are messages, and that is the substance of the change. A line written into a running turn is read by the *model*, so a command sent mid-turn either does nothing or arrives as text somebody has to puzzle over. They now wait for the turn to end. The waiting is done once for every provider, in the pump that already watches every event for the boundary, rather than in each driver where a new provider could get it wrong by leaving it out. Waiting is a state, so it is on screen: the command sits at the reader's end of the conversation in blue, with a spinner and "waiting for this turn to end", and becomes an ordinary blue row when it goes. Blue because these are about the session rather than about the task -- the same blue a compaction already used, which is now one colour with one name rather than two. Renaming from the settings screen sends exactly this, so it waits and draws the same way. The name itself is not held: it is this server's own datum, so the list and the header change at once and only telling the session waits. Echo grew the same split, which is where the bug in it showed: its commands are its messages, so running one announced a `MessageTaken` as well, and the same line drew twice -- once blue, once purple. A command owes no announcement; the manager has already recorded that it was sent. Watched rather than reasoned about: `/compact` during a 25 second turn held with its bubble up, went out when the turn ended, and the compaction that followed reported what it recovered.
This commit is contained in:
1 parent
bebaae7a94
commit
749b2db287
13 files changed
+678
-93
No files matched your search
@@ -398,6 +398,29 @@ impl ClaudeDriver {
|
||||
let _ = self.to_child.send(line);
|
||||
}
|
||||
|
||||
/// Writes one of the CLI's own commands into the session.
|
||||
///
|
||||
/// Slash commands ride the normal user-message channel -- there is no
|
||||
/// control request for them; `set_session_name` is not a subtype the
|
||||
/// CLI knows, measured by asking. The turn they start is marked here
|
||||
/// because they produce a `result` like any other, so a message sent
|
||||
/// meanwhile belongs in the queue's "written, announce when read"
|
||||
/// path rather than being reported as read the moment it is typed.
|
||||
///
|
||||
/// Nothing is emitted about the command itself: the manager has
|
||||
/// already said it was sent, and the CLI announces what it does --
|
||||
/// saying so here would be this side's guess standing in for its
|
||||
/// measurement.
|
||||
fn local_command(&self, text: String) {
|
||||
self.queue.lock().unwrap().running = true;
|
||||
self.send_line(
|
||||
json!({"type": "user", "message": {"role": "user", "content": [
|
||||
{"type": "text", "text": text}
|
||||
]}})
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Sends a control request, remembering what it asked for.
|
||||
///
|
||||
/// `confirms` is the setting this request will have made if the CLI
|
||||
@@ -516,6 +539,15 @@ impl Driver for ClaudeDriver {
|
||||
);
|
||||
}
|
||||
|
||||
fn run_command(&self, text: &str) {
|
||||
// Whatever the CLI's own vocabulary holds -- `/context`, `/usage`,
|
||||
// a command added after this was written. It rides the same
|
||||
// channel as `/compact` and `/rename` and starts a turn the same
|
||||
// way, so the same bookkeeping applies; what it means is the
|
||||
// CLI's business, not this file's.
|
||||
self.local_command(text.to_string());
|
||||
}
|
||||
|
||||
fn set_title(&self, title: &str) {
|
||||
// The CLI's own mechanism, and a local command rather than a
|
||||
// control request -- `set_session_name` is not a subtype it
|
||||
@@ -530,32 +562,11 @@ impl Driver for ClaudeDriver {
|
||||
});
|
||||
return;
|
||||
}
|
||||
self.queue.lock().unwrap().running = true;
|
||||
self.send_line(
|
||||
json!({"type": "user", "message": {"role": "user", "content": [
|
||||
{"type": "text", "text": format!("/rename {title}")}
|
||||
]}})
|
||||
.to_string(),
|
||||
);
|
||||
self.local_command(format!("/rename {title}"));
|
||||
}
|
||||
|
||||
fn compact(&self) {
|
||||
// A turn is in flight from here. The CLI answers `/compact` like
|
||||
// any other message -- a status line, a boundary, then a `result`
|
||||
// -- so a message sent meanwhile belongs in the queue's "written,
|
||||
// announce when it has been read" path rather than being reported
|
||||
// as read the moment it is typed.
|
||||
self.queue.lock().unwrap().running = true;
|
||||
// Slash commands ride the normal user-message channel. Nothing is
|
||||
// emitted here: the CLI announces the compaction itself, and
|
||||
// saying so first would be this side's guess standing in for its
|
||||
// measurement.
|
||||
self.send_line(
|
||||
json!({"type": "user", "message": {"role": "user", "content": [
|
||||
{"type": "text", "text": "/compact"}
|
||||
]}})
|
||||
.to_string(),
|
||||
);
|
||||
self.local_command("/compact".to_string());
|
||||
}
|
||||
|
||||
fn detach(&self) {
|
||||
|
||||
Reference in new issue
Block a user