show compaction as it happens and open tool calls while they run
Compaction was the one thing the harness does that emitted nothing at all while it ran, and it can run for minutes — silence that reads as a hung turn, which costs a server restart to discover it wasn't. The sidecar now reports both ends: the start from the PreCompact hook, the finish from the compact_boundary message with the token count, both durable so a reload or a reconnect still sees them. Tool rows open themselves while they run and hold for five seconds after their result, so the inputs are on screen at the moment the call is made rather than after the fact. The clock lives outside React, keyed by tool call id: rows are virtualised, so unmounting is not the call ending, and a fast call can render its start and its result together — a row that only opens when it catches the pending state never opens for exactly the quickest calls. A click outranks the clock for as long as the row lives. A failure behaves identically and differs only in colour, so it stays findable by scanning and nameable in conversation. Shell logs move off the green-on-black pre onto the shared code surface, which is the one block that had no copy button and the one you most often want to hand to someone else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -179,6 +179,12 @@ type PersistentSession = {
|
||||
* side that called `interrupt()` knows better, so it says so here.
|
||||
*/
|
||||
interrupted: boolean;
|
||||
/**
|
||||
* When the `PreCompact` hook fired, so the `compact_boundary` that closes it can carry how long the
|
||||
* silence lasted. The harness reports the boundary but not the duration, and the duration is the part
|
||||
* that explains the wait.
|
||||
*/
|
||||
compactStartedAt?: number;
|
||||
idleTimer?: ReturnType<typeof setTimeout>;
|
||||
};
|
||||
|
||||
@@ -259,6 +265,25 @@ function createSession(params: ClaudeSpawnStreamingParams, onEvent: (event: Chat
|
||||
permissionMode: 'bypassPermissions',
|
||||
allowDangerouslySkipPermissions: true,
|
||||
includePartialMessages: true,
|
||||
// The only warning that compaction is about to happen. Everything else the harness does narrates
|
||||
// itself through the message stream; compaction goes silent for as long as it takes — 2.5 minutes
|
||||
// in the worst case on disk here — and the stream resumes with no explanation of the gap. The hook
|
||||
// returns immediately and never throws: it is a notification, and it must not be able to stall or
|
||||
// fail the compaction it is announcing.
|
||||
hooks: {
|
||||
PreCompact: [
|
||||
{
|
||||
hooks: [
|
||||
async (input) => {
|
||||
session.compactStartedAt = Date.now();
|
||||
const trigger = 'trigger' in input && input.trigger === 'manual' ? 'manual' : 'auto';
|
||||
session.emit({ type: 'compact:start', trigger });
|
||||
return { continue: true };
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
abortController: abort,
|
||||
pathToClaudeCodeExecutable: CLAUDE_BIN,
|
||||
settingSources: ['user', 'project', 'local'],
|
||||
@@ -304,7 +329,15 @@ function createSession(params: ClaudeSpawnStreamingParams, onEvent: (event: Chat
|
||||
const state = createParseState();
|
||||
const emit = (raw: ChatEvent) => {
|
||||
// A turn we interrupted ends in a failed `result`. That is the stop landing, not a fault.
|
||||
const event: ChatEvent = raw.type === 'error' && session.interrupted ? { type: 'stopped' } : raw;
|
||||
const event: ChatEvent =
|
||||
raw.type === 'error' && session.interrupted
|
||||
? { type: 'stopped' }
|
||||
: // The boundary knows what it dropped; only this side knows how long it took, because the start
|
||||
// came from a hook rather than from the stream.
|
||||
raw.type === 'compact:done' && session.compactStartedAt
|
||||
? { ...raw, durationMs: Date.now() - session.compactStartedAt }
|
||||
: raw;
|
||||
if (event.type === 'compact:done') session.compactStartedAt = undefined;
|
||||
if (event.type === 'task:started') {
|
||||
// Work is running — hold off idle-GC until it finishes.
|
||||
session.pendingTasks.add(event.taskId);
|
||||
|
||||
Reference in New Issue
Block a user