1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import type { EngineInterface, Register } from "claude-code";
import {
done,
isPrimaryAgent,
permissionRequest,
questionRequest,
recordArguments,
stateScript,
working,
type StateRecord,
} from "./state.ts";
const sketchybarTrigger = [
"sketchybar_bin=$(command -v sketchybar) || sketchybar_bin=/opt/homebrew/opt/sketchybar/bin/sketchybar",
'[ -x "$sketchybar_bin" ] && "$sketchybar_bin" --trigger agent_state_change >/dev/null 2>&1 || true',
].join("; ");
async function cwd($: EngineInterface): Promise<string> {
return $.session.cwd();
}
async function triggerSketchybar($: EngineInterface): Promise<void> {
await $.process.run(["/bin/sh", "-c", sketchybarTrigger], { timeoutMs: 5000 });
}
async function publish($: EngineInterface, record: StateRecord): Promise<void> {
const [pane, home] = await Promise.all([$.env.get("TMUX_PANE"), $.env.get("HOME")]);
if (pane === undefined || pane === "" || home === undefined || home === "") return;
try {
await $.process.run([stateScript(home), ...recordArguments(record)], { timeoutMs: 5000 });
await triggerSketchybar($);
} catch {
// State tracking must not disrupt the Claude session.
}
}
async function clear($: EngineInterface): Promise<void> {
const [pane, home] = await Promise.all([$.env.get("TMUX_PANE"), $.env.get("HOME")]);
if (pane === undefined || pane === "" || home === undefined || home === "") return;
try {
await $.process.run([stateScript(home), "clear"], { timeoutMs: 5000 });
await triggerSketchybar($);
} catch {
// State tracking must not disrupt the Claude session.
}
}
export const register: Register = on => {
let writes: Promise<void> = Promise.resolve();
const enqueue = (operation: () => Promise<void>) => {
writes = writes.then(operation, operation).catch(() => undefined);
return writes;
};
// No event fires when a prompt is answered, so a blocked session goes back
// to working once the tool call it was blocked on resolves.
let blocked = false;
on("turn.start", async ($, event, next) => {
if (isPrimaryAgent(event.agentId)) {
blocked = false;
await enqueue(async () => publish($, working(await cwd($))));
}
return next(event);
});
on("turn.complete", async ($, event, next) => {
if (isPrimaryAgent(event.agentId)) {
blocked = false;
await enqueue(async () => publish($, done(await cwd($))));
}
return next(event);
});
on("tool.call", async ($, event, next) => {
if (!isPrimaryAgent(event.agentId)) return next(event);
if (event.tool === "AskUserQuestion") {
blocked = true;
await enqueue(async () => publish($, questionRequest(await cwd($))));
}
const result = await next(event);
if (blocked) {
blocked = false;
await enqueue(async () => publish($, working(await cwd($))));
}
return result;
}).catch(($, event, next) => next(event));
on("classic.PermissionRequest", async ($, event, next) => {
if (isPrimaryAgent(event.agent_id)) {
blocked = true;
await enqueue(async () => publish($, permissionRequest(event.tool_name, await cwd($))));
}
return next(event);
}).catch(($, event, next) => next(event));
on("classic.PermissionDenied", async ($, event, next) => {
if (isPrimaryAgent(event.agent_id)) await enqueue(async () => publish($, working(await cwd($))));
return next(event);
}).catch(($, event, next) => next(event));
on("session.end", async ($, event, next) => {
await enqueue(() => clear($));
return next(event);
});
};