@towles/tool 0.0.130 → 0.0.132
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createSignal, For, Show, onCleanup } from "solid-js";
|
|
2
2
|
import type { Accessor } from "solid-js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { SessionData, Theme } from "@tt-agentboard/runtime";
|
|
4
4
|
import { truncate } from "@tt-agentboard/runtime";
|
|
5
5
|
import { UNSEEN_ICON, BOLD, DIM, toneColor } from "../constants";
|
|
6
6
|
import { DiffStats } from "./DiffStats";
|
|
@@ -9,16 +9,6 @@ import { formatElapsed } from "./elapsed";
|
|
|
9
9
|
import { liveStatusIcon, unseenTerminalColor } from "./status-visuals";
|
|
10
10
|
import { familyColor } from "./family-color";
|
|
11
11
|
|
|
12
|
-
const STATUS_TEXT: Record<AgentStatus, string> = {
|
|
13
|
-
idle: "",
|
|
14
|
-
running: "running",
|
|
15
|
-
done: "done",
|
|
16
|
-
error: "error",
|
|
17
|
-
waiting: "waiting",
|
|
18
|
-
question: "question",
|
|
19
|
-
interrupted: "stopped",
|
|
20
|
-
};
|
|
21
|
-
|
|
22
12
|
export interface SessionCardProps {
|
|
23
13
|
session: SessionData;
|
|
24
14
|
isFocused: boolean;
|
|
@@ -77,7 +67,7 @@ export function SessionCard(props: SessionCardProps) {
|
|
|
77
67
|
};
|
|
78
68
|
|
|
79
69
|
const truncName = () => truncate(props.session.name, 18);
|
|
80
|
-
const truncBranch = () => (props.session.branch ? truncate(props.session.branch,
|
|
70
|
+
const truncBranch = () => (props.session.branch ? truncate(props.session.branch, 45) : "");
|
|
81
71
|
|
|
82
72
|
const hasDiff = () => {
|
|
83
73
|
const { linesAdded, linesRemoved, commitsDelta, filesChanged } = props.session;
|
|
@@ -128,7 +118,7 @@ export function SessionCard(props: SessionCardProps) {
|
|
|
128
118
|
</Show>
|
|
129
119
|
|
|
130
120
|
<box flexDirection="column" flexGrow={1} paddingRight={1}>
|
|
131
|
-
<box flexDirection="row">
|
|
121
|
+
<box flexDirection="row" height={1}>
|
|
132
122
|
<text truncate flexGrow={1}>
|
|
133
123
|
<span
|
|
134
124
|
style={{
|
|
@@ -139,25 +129,32 @@ export function SessionCard(props: SessionCardProps) {
|
|
|
139
129
|
{truncName()}
|
|
140
130
|
</span>
|
|
141
131
|
</text>
|
|
142
|
-
<Show when={
|
|
143
|
-
<
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
{statusIcon()}
|
|
147
|
-
{runningAgents() > 1 ? String(runningAgents()) : ""}
|
|
148
|
-
</span>
|
|
149
|
-
</text>
|
|
132
|
+
<Show when={hasDiff()}>
|
|
133
|
+
<box flexShrink={0} paddingLeft={1}>
|
|
134
|
+
<DiffStats session={props.session} palette={() => P()} />
|
|
135
|
+
</box>
|
|
150
136
|
</Show>
|
|
137
|
+
<box width={3} flexShrink={0}>
|
|
138
|
+
<Show when={statusIcon()}>
|
|
139
|
+
<text>
|
|
140
|
+
<span style={{ fg: statusColor() }}>
|
|
141
|
+
{" "}
|
|
142
|
+
{statusIcon()}
|
|
143
|
+
{runningAgents() > 1 ? String(runningAgents()) : ""}
|
|
144
|
+
</span>
|
|
145
|
+
</text>
|
|
146
|
+
</Show>
|
|
147
|
+
</box>
|
|
151
148
|
</box>
|
|
152
149
|
|
|
153
150
|
<Show when={props.session.branch}>
|
|
154
|
-
<
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
151
|
+
<box flexDirection="row" height={1}>
|
|
152
|
+
<text truncate flexShrink={1}>
|
|
153
|
+
<span style={{ fg: props.isFocused ? P().pink : P().overlay0 }}>
|
|
154
|
+
{truncBranch()}
|
|
155
|
+
</span>
|
|
156
|
+
</text>
|
|
157
|
+
</box>
|
|
161
158
|
</Show>
|
|
162
159
|
|
|
163
160
|
<Show when={metaSummary()}>
|
|
@@ -184,8 +181,6 @@ export function SessionCard(props: SessionCardProps) {
|
|
|
184
181
|
</For>
|
|
185
182
|
</box>
|
|
186
183
|
</box>
|
|
187
|
-
|
|
188
|
-
<box height={1} />
|
|
189
184
|
</box>
|
|
190
185
|
);
|
|
191
186
|
}
|
|
@@ -230,15 +225,15 @@ function AgentRow(props: AgentRowProps) {
|
|
|
230
225
|
return SC()[props.agent.status];
|
|
231
226
|
};
|
|
232
227
|
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
const isCacheExpired = () => {
|
|
228
|
+
const cacheLabel = () => {
|
|
236
229
|
const details = props.agent.details;
|
|
237
|
-
if (!details) return
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return
|
|
230
|
+
if (!details) return null;
|
|
231
|
+
const expiresAt =
|
|
232
|
+
details.cacheExpiresAt ??
|
|
233
|
+
(details.lastActivityAt != null ? details.lastActivityAt + 60 * 60 * 1000 : null);
|
|
234
|
+
if (expiresAt == null) return null;
|
|
235
|
+
const minutesLeft = Math.ceil((expiresAt - props.now()) / 60_000);
|
|
236
|
+
return minutesLeft <= 0 ? "cache expired" : `cache ${minutesLeft}m`;
|
|
242
237
|
};
|
|
243
238
|
|
|
244
239
|
let flashTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -264,14 +259,27 @@ function AgentRow(props: AgentRowProps) {
|
|
|
264
259
|
backgroundColor={bgColor()}
|
|
265
260
|
onMouseDown={(event) => {
|
|
266
261
|
if (event.target?.id === "dismiss") return;
|
|
262
|
+
// Stop the click bubbling to SessionCard's onSelect, which would fire
|
|
263
|
+
// switch-session and clobber the precise pane that onFocusPane selects.
|
|
264
|
+
event.stopPropagation();
|
|
267
265
|
triggerFlash();
|
|
268
266
|
props.onFocusPane();
|
|
269
267
|
}}
|
|
270
268
|
>
|
|
271
|
-
<box flexDirection="row">
|
|
272
|
-
<text
|
|
269
|
+
<box flexDirection="row" height={1}>
|
|
270
|
+
<text flexShrink={0}>
|
|
273
271
|
<span style={{ fg: color() }}>{icon()}</span>
|
|
274
|
-
|
|
272
|
+
</text>
|
|
273
|
+
<text flexGrow={1} flexShrink={1} truncate>
|
|
274
|
+
<Show when={props.agent.threadName}>
|
|
275
|
+
<span style={{ fg: isUnseen() ? color() : P().overlay0 }}>
|
|
276
|
+
{" "}
|
|
277
|
+
{truncate(props.agent.threadName!.replace(/\s+/g, " ").trim(), 40)}
|
|
278
|
+
</span>
|
|
279
|
+
</Show>
|
|
280
|
+
</text>
|
|
281
|
+
<Show when={props.agent.status === "running" && props.agent.details?.lastActivityAt}>
|
|
282
|
+
<text flexShrink={0}>
|
|
275
283
|
<span
|
|
276
284
|
style={{
|
|
277
285
|
fg: props.isKeyboardFocused ? P().subtext0 : P().overlay1,
|
|
@@ -281,11 +289,6 @@ function AgentRow(props: AgentRowProps) {
|
|
|
281
289
|
{" "}
|
|
282
290
|
{formatElapsed(props.now() - (props.agent.details?.lastActivityAt ?? props.now()))}
|
|
283
291
|
</span>
|
|
284
|
-
</Show>
|
|
285
|
-
</text>
|
|
286
|
-
<Show when={!isUnseen()}>
|
|
287
|
-
<text flexShrink={0}>
|
|
288
|
-
<span style={{ fg: color(), attributes: DIM }}>{statusText()}</span>
|
|
289
292
|
</text>
|
|
290
293
|
</Show>
|
|
291
294
|
<text
|
|
@@ -302,16 +305,6 @@ function AgentRow(props: AgentRowProps) {
|
|
|
302
305
|
</text>
|
|
303
306
|
</box>
|
|
304
307
|
|
|
305
|
-
<Show when={props.agent.threadName}>
|
|
306
|
-
<box height={2} flexShrink={0}>
|
|
307
|
-
<text>
|
|
308
|
-
<span style={{ fg: isUnseen() ? color() : P().overlay0 }}>
|
|
309
|
-
{truncate(props.agent.threadName!.replace(/\s+/g, " ").trim(), 60)}
|
|
310
|
-
</span>
|
|
311
|
-
</text>
|
|
312
|
-
</box>
|
|
313
|
-
</Show>
|
|
314
|
-
|
|
315
308
|
<Show when={props.agent.status === "running" && props.agent.details}>
|
|
316
309
|
{(d) => {
|
|
317
310
|
const details = d();
|
|
@@ -334,9 +327,9 @@ function AgentRow(props: AgentRowProps) {
|
|
|
334
327
|
}}
|
|
335
328
|
</Show>
|
|
336
329
|
|
|
337
|
-
<Show when={
|
|
330
|
+
<Show when={cacheLabel()}>
|
|
338
331
|
<text truncate>
|
|
339
|
-
<span style={{ fg: P().overlay0, attributes: DIM }}>
|
|
332
|
+
<span style={{ fg: P().overlay0, attributes: DIM }}>{cacheLabel()}</span>
|
|
340
333
|
</text>
|
|
341
334
|
</Show>
|
|
342
335
|
</box>
|
|
@@ -555,43 +555,53 @@ function App() {
|
|
|
555
555
|
/>
|
|
556
556
|
|
|
557
557
|
{/* Session list */}
|
|
558
|
-
<scrollbox flexGrow={1} flexShrink={1} paddingTop={
|
|
558
|
+
<scrollbox flexGrow={1} flexShrink={1} paddingTop={0}>
|
|
559
|
+
<box height={1}>
|
|
560
|
+
<text style={{ fg: P().overlay0 }}>{DIVIDER}</text>
|
|
561
|
+
</box>
|
|
559
562
|
<For each={sessions}>
|
|
560
563
|
{(session, i) => (
|
|
561
|
-
<
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
session: session.name
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
564
|
+
<box flexDirection="column" flexShrink={0}>
|
|
565
|
+
<Show when={i() > 0}>
|
|
566
|
+
<box height={1}>
|
|
567
|
+
<text style={{ fg: P().surface2 }}>{DIVIDER}</text>
|
|
568
|
+
</box>
|
|
569
|
+
</Show>
|
|
570
|
+
<SessionCard
|
|
571
|
+
session={session}
|
|
572
|
+
isFocused={isFocused(session.name)}
|
|
573
|
+
isCurrent={session.name === currentSession()}
|
|
574
|
+
spinIdx={spinIdx}
|
|
575
|
+
now={now}
|
|
576
|
+
theme={theme}
|
|
577
|
+
statusColors={S}
|
|
578
|
+
focusedAgentIdx={
|
|
579
|
+
isFocused(session.name) && panelFocus() === "agents" ? focusedAgentIdx() : -1
|
|
580
|
+
}
|
|
581
|
+
onSelect={() => {
|
|
582
|
+
setFocusedSession(session.name);
|
|
583
|
+
send({ type: "focus-session", name: session.name });
|
|
584
|
+
switchToSession(session.name);
|
|
585
|
+
}}
|
|
586
|
+
onDismissAgent={(agent) => {
|
|
587
|
+
send({
|
|
588
|
+
type: "dismiss-agent",
|
|
589
|
+
session: session.name,
|
|
590
|
+
agent: agent.agent,
|
|
591
|
+
threadId: agent.threadId,
|
|
592
|
+
});
|
|
593
|
+
}}
|
|
594
|
+
onFocusAgentPane={(agent) => {
|
|
595
|
+
send({
|
|
596
|
+
type: "focus-agent-pane",
|
|
597
|
+
session: session.name,
|
|
598
|
+
agent: agent.agent,
|
|
599
|
+
threadId: agent.threadId,
|
|
600
|
+
threadName: agent.threadName,
|
|
601
|
+
});
|
|
602
|
+
}}
|
|
603
|
+
/>
|
|
604
|
+
</box>
|
|
595
605
|
)}
|
|
596
606
|
</For>
|
|
597
607
|
</scrollbox>
|
|
@@ -931,6 +931,7 @@ export function startServer(
|
|
|
931
931
|
const raw = shell([
|
|
932
932
|
"tmux",
|
|
933
933
|
"list-panes",
|
|
934
|
+
"-s",
|
|
934
935
|
"-t",
|
|
935
936
|
sessionName,
|
|
936
937
|
"-F",
|
|
@@ -999,6 +1000,15 @@ export function startServer(
|
|
|
999
1000
|
if (!targetPaneId) return;
|
|
1000
1001
|
|
|
1001
1002
|
log("focus-agent-pane", "focusing", { sessionName, agentName, paneId: targetPaneId });
|
|
1003
|
+
// The agent's pane may live in a different session/window than the one the
|
|
1004
|
+
// client is attached to. switch-client moves the active client to the
|
|
1005
|
+
// agent's session, select-window to its window, select-pane to the pane.
|
|
1006
|
+
// We deliberately omit `-c <tty>`: clients can share a tty name (e.g. a
|
|
1007
|
+
// stale suspended duplicate), making `-c` match the wrong client and
|
|
1008
|
+
// silently no-op. Without `-c`, tmux targets the most-recently-active
|
|
1009
|
+
// client, which is the real interactive one.
|
|
1010
|
+
shell(["tmux", "switch-client", "-t", sessionName]);
|
|
1011
|
+
shell(["tmux", "select-window", "-t", targetPaneId]);
|
|
1002
1012
|
shell(["tmux", "select-pane", "-t", targetPaneId]);
|
|
1003
1013
|
|
|
1004
1014
|
const existing = pendingHighlightResets.get(targetPaneId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tt",
|
|
3
3
|
"description": "Core dev workflow commands and skills: interview-me, write-prd, prd-to-issues, tdd, improve-architecture, refine-text, task, parallel-slots, towles-tool.",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.132",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Chris Towles"
|
|
7
7
|
}
|