One-pass local response cleanup without agent retries

hooks/register.ts 68 lines1import type { Register, TurnStepChunk } from "claude-code";
2
3export const register: Register = (on) => {
4 on("turn.step", async function* ($, e, next) {
5 if (e.agentId) return yield* next(e);
6 const pending: TurnStepChunk[] = [];
7 let passthrough = false;
8 let size = 0;
9 try {
10 for await (const chunk of next(e)) {
11 if (passthrough) {
12 yield chunk;
13 continue;
14 }
15 pending.push(chunk);
16 if (chunk.kind === "text") size += chunk.text.length;
17 if (chunk.kind === "tool" || size > 24000) {
18 passthrough = true;
19 yield* pending;
20 pending.length = 0;
21 }
22 }
23 } catch (error) {
24 yield* pending;
25 throw error;
26 }
27 if (passthrough) return;
28 const stop = pending.findLast((chunk) => chunk.kind === "stop");
29 if (stop?.kind === "stop" && stop.stopReason === "end_turn") {
30 const texts = pending.filter((chunk) => chunk.kind === "text");
31 // Keep separate content blocks and all engine handles in their original order.
32 const indices = new Set(texts.map((chunk) => chunk.index));
33 if (indices.size === 1) {
34 const original = texts.map((chunk) => chunk.text).join("");
35 try {
36 const result = await $.process.run(["@rewriter@"], {
37 stdin: JSON.stringify({ text: original }),
38 timeoutMs: 16000,
39 });
40 if (result.exitCode === 0) {
41 const reply: unknown = JSON.parse(result.stdout);
42 if (
43 typeof reply === "object" &&
44 reply !== null &&
45 "text" in reply &&
46 typeof reply.text === "string"
47 ) {
48 let first = true;
49 for (const chunk of pending) {
50 if (chunk.kind === "text") {
51 yield { ...chunk, text: first ? reply.text : "" };
52 first = false;
53 } else yield chunk;
54 }
55 return;
56 }
57 }
58 } catch {
59 $.ui.log("Output cleanup unavailable; kept the original response.", {
60 to: "debug",
61 });
62 }
63 }
64 }
65 yield* pending;
66 });
67};
68