@tryinget/pi-activity-strip 0.5.0 → 0.6.0
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/README.md +41 -6
- package/bin/pi-activity-strip-claude-hook.mjs +71 -0
- package/bin/pi-activity-strip.mjs +22 -1
- package/native/bin/linux-x64-gnu/artifact.json +2 -2
- package/native/bin/linux-x64-gnu/pi-activity-strip-panel +0 -0
- package/native/panel/src/app.rs +20 -20
- package/native/panel/src/card_view.rs +308 -9
- package/native/panel/src/protocol.rs +63 -0
- package/native/panel/src/runtime.rs +50 -9
- package/native/panel/src/style.css +134 -60
- package/package.json +4 -4
- package/src/common/agent-identity.mjs +163 -0
- package/src/common/ak-tasks.mjs +354 -0
- package/src/common/claude-events.mjs +190 -0
- package/src/common/claude-hook-config.mjs +56 -0
- package/src/common/claude-transcript.mjs +163 -0
- package/src/common/codex-transcript.mjs +153 -0
- package/src/common/compatibility.mjs +13 -0
- package/src/common/contracts.d.ts +26 -0
- package/src/common/contracts.ts +64 -1
- package/src/common/ghostty-present.mjs +100 -0
- package/src/common/ghostty-theme.mjs +149 -0
- package/src/common/niri-focus.mjs +179 -110
- package/src/common/session-cards.mjs +5 -2
- package/src/common/status-report.mjs +51 -0
- package/src/common/surface-bindings.mjs +234 -0
- package/src/common/terminal-identity.mjs +75 -12
- package/src/common/window-placement.mjs +59 -0
- package/src/common/workspace-view.mjs +161 -0
- package/src/native/agent-discovery.mjs +294 -0
- package/src/native/ak-runtime.mjs +164 -0
- package/src/native/codex-discovery.mjs +128 -0
- package/src/native/ghostty-tab-inventory.py +99 -0
- package/src/native/height-repair.mjs +154 -0
- package/src/native/main.mjs +120 -14
- package/src/native/panel-projection.mjs +67 -5
- package/src/native/placement.mjs +225 -0
- package/src/native/tab-inventory.mjs +188 -0
- package/src/native/theme-runtime.mjs +72 -0
- package/src/native/theme-source.mjs +197 -0
- package/src/native/workspace-events.mjs +16 -1
|
@@ -1,56 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Every colour here derives from the handful of names the controller defines from Ghostty's own
|
|
3
|
+
* theme, so the ribbon follows the terminal into light and dark without a second palette to keep
|
|
4
|
+
* in sync. mix() against the background and alpha() against the foreground both invert correctly
|
|
5
|
+
* between schemes: a raised surface is lighter on a dark theme and darker on a light one.
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
/* biome-ignore lint/correctness/noUnknownTypeSelector: GTK CSS uses widget node names. */
|
|
2
9
|
window {
|
|
3
10
|
background: transparent;
|
|
4
|
-
color:
|
|
5
|
-
font-family: "IBM Plex Sans", "
|
|
11
|
+
color: @pi_foreground;
|
|
12
|
+
font-family: "IBM Plex Sans", "Inter", "Segoe UI", sans-serif;
|
|
6
13
|
}
|
|
7
14
|
|
|
8
|
-
.
|
|
9
|
-
.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
border
|
|
15
|
+
/* One surface on the same rhythm and radius as tiled windows, so it reads as a peer of them. */
|
|
16
|
+
.band {
|
|
17
|
+
/* Solid: the ribbon is the one surface that never dims, so it reads as the fixed ground. */
|
|
18
|
+
background: @pi_background;
|
|
19
|
+
border: 1px solid alpha(@pi_foreground, 0.16);
|
|
20
|
+
border-radius: 12px;
|
|
21
|
+
padding: 0 14px 0 16px;
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
.brand {
|
|
16
|
-
|
|
25
|
+
border-right: 1px solid alpha(@pi_foreground, 0.12);
|
|
26
|
+
margin-right: 12px;
|
|
27
|
+
padding: 11px 14px 11px 0;
|
|
17
28
|
}
|
|
18
29
|
|
|
19
30
|
.brand-eyebrow,
|
|
20
31
|
.meta,
|
|
21
32
|
.card-state,
|
|
22
33
|
.card-tool,
|
|
34
|
+
.card-elapsed,
|
|
23
35
|
.inspector-key {
|
|
24
36
|
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
|
25
37
|
}
|
|
26
38
|
|
|
27
39
|
.brand-eyebrow {
|
|
28
|
-
color:
|
|
40
|
+
color: alpha(@pi_accent, 0.9);
|
|
29
41
|
font-size: 9px;
|
|
30
|
-
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
letter-spacing: 1.4px;
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
.brand-title {
|
|
34
|
-
color:
|
|
35
|
-
font-size:
|
|
47
|
+
color: @pi_foreground;
|
|
48
|
+
font-size: 17px;
|
|
36
49
|
font-weight: 650;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.brand-subtitle {
|
|
40
|
-
color: rgba(194, 205, 224, 0.74);
|
|
41
|
-
font-size: 10px;
|
|
50
|
+
letter-spacing: -0.2px;
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
.meta {
|
|
45
|
-
color:
|
|
54
|
+
color: alpha(@pi_foreground, 0.56);
|
|
46
55
|
font-size: 9px;
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
/* biome-ignore lint/correctness/noUnknownTypeSelector: GTK CSS uses widget node names. */
|
|
59
|
+
.cards-panel,
|
|
50
60
|
.cards-panel > viewport {
|
|
51
|
-
background:
|
|
52
|
-
|
|
53
|
-
padding: 6px 8px 8px;
|
|
61
|
+
background: transparent;
|
|
62
|
+
padding: 0;
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
/* biome-ignore lint/correctness/noUnknownTypeSelector: GTK CSS uses widget node names. */
|
|
@@ -61,32 +70,42 @@ window {
|
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
.card {
|
|
64
|
-
color:
|
|
65
|
-
background:
|
|
66
|
-
border: 1px solid
|
|
67
|
-
border-left: 3px solid
|
|
68
|
-
border-radius:
|
|
69
|
-
padding:
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
color: @pi_foreground;
|
|
74
|
+
background: mix(@pi_background, @pi_foreground, 0.07);
|
|
75
|
+
border: 1px solid alpha(@pi_foreground, 0.1);
|
|
76
|
+
border-left: 3px solid alpha(@pi_foreground, 0.35);
|
|
77
|
+
border-radius: 12px;
|
|
78
|
+
padding: 7px 11px 7px 10px;
|
|
79
|
+
transition:
|
|
80
|
+
background 120ms ease,
|
|
81
|
+
border-color 120ms ease;
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
.card:hover,
|
|
75
85
|
.card:focus-visible,
|
|
76
86
|
.card.open {
|
|
77
|
-
background:
|
|
78
|
-
border-color:
|
|
87
|
+
background: mix(@pi_background, @pi_foreground, 0.14);
|
|
88
|
+
border-color: alpha(@pi_accent, 0.5);
|
|
79
89
|
}
|
|
80
90
|
|
|
91
|
+
/* The tab the compositor is actually showing, marked by weight rather than another border. */
|
|
81
92
|
.card.current {
|
|
82
|
-
|
|
83
|
-
border-color:
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
background: mix(@pi_background, @pi_foreground, 0.16);
|
|
94
|
+
border-color: alpha(@pi_accent, 0.42);
|
|
95
|
+
box-shadow: inset 0 0 0 1px alpha(@pi_accent, 0.22);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.card.hidden-tab {
|
|
99
|
+
background: alpha(@pi_background, 0.55);
|
|
100
|
+
border-style: dashed;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.card.hidden-tab .card-repo {
|
|
104
|
+
color: alpha(@pi_foreground, 0.78);
|
|
86
105
|
}
|
|
87
106
|
|
|
88
107
|
.card.stalled {
|
|
89
|
-
opacity: 0.
|
|
108
|
+
opacity: 0.62;
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
.card.stalled .card-phase {
|
|
@@ -94,83 +113,138 @@ window {
|
|
|
94
113
|
}
|
|
95
114
|
|
|
96
115
|
.card.state-thinking {
|
|
97
|
-
border-left-color:
|
|
116
|
+
border-left-color: @pi_thinking;
|
|
98
117
|
}
|
|
99
118
|
.card.state-tool {
|
|
100
|
-
border-left-color:
|
|
119
|
+
border-left-color: @pi_tool;
|
|
101
120
|
}
|
|
102
121
|
.card.state-waiting {
|
|
103
|
-
border-left-color:
|
|
122
|
+
border-left-color: @pi_waiting;
|
|
104
123
|
}
|
|
105
124
|
.card.state-success {
|
|
106
|
-
border-left-color:
|
|
125
|
+
border-left-color: @pi_success;
|
|
107
126
|
}
|
|
108
127
|
.card.state-error {
|
|
109
|
-
border-left-color:
|
|
128
|
+
border-left-color: @pi_error;
|
|
110
129
|
}
|
|
111
130
|
|
|
112
131
|
.card-repo {
|
|
113
|
-
color:
|
|
132
|
+
color: @pi_foreground;
|
|
114
133
|
font-size: 12px;
|
|
115
134
|
font-weight: 650;
|
|
116
135
|
}
|
|
117
136
|
|
|
118
137
|
.card-phase,
|
|
119
138
|
.inspector-value {
|
|
120
|
-
color:
|
|
139
|
+
color: alpha(@pi_foreground, 0.74);
|
|
121
140
|
font-size: 10px;
|
|
122
141
|
}
|
|
123
142
|
|
|
143
|
+
/* A quiet pill: the state is carried mostly by the card's left edge. */
|
|
124
144
|
.card-state {
|
|
125
|
-
|
|
145
|
+
background: alpha(@pi_foreground, 0.07);
|
|
146
|
+
border: 1px solid alpha(@pi_foreground, 0.16);
|
|
126
147
|
border-radius: 999px;
|
|
127
|
-
color:
|
|
148
|
+
color: alpha(@pi_foreground, 0.68);
|
|
128
149
|
font-size: 8px;
|
|
129
|
-
|
|
150
|
+
letter-spacing: 0.4px;
|
|
151
|
+
padding: 3px 7px;
|
|
130
152
|
}
|
|
131
153
|
|
|
154
|
+
.state-thinking .card-state {
|
|
155
|
+
background: alpha(@pi_thinking, 0.14);
|
|
156
|
+
border-color: alpha(@pi_thinking, 0.38);
|
|
157
|
+
color: @pi_thinking;
|
|
158
|
+
}
|
|
132
159
|
.state-tool .card-state {
|
|
133
|
-
|
|
134
|
-
border-color:
|
|
160
|
+
background: alpha(@pi_tool, 0.14);
|
|
161
|
+
border-color: alpha(@pi_tool, 0.38);
|
|
162
|
+
color: @pi_tool;
|
|
135
163
|
}
|
|
136
164
|
.state-waiting .card-state {
|
|
137
|
-
|
|
138
|
-
border-color:
|
|
165
|
+
background: alpha(@pi_waiting, 0.16);
|
|
166
|
+
border-color: alpha(@pi_waiting, 0.42);
|
|
167
|
+
color: @pi_waiting;
|
|
139
168
|
}
|
|
140
169
|
.state-success .card-state {
|
|
141
|
-
|
|
142
|
-
border-color:
|
|
170
|
+
background: alpha(@pi_success, 0.14);
|
|
171
|
+
border-color: alpha(@pi_success, 0.38);
|
|
172
|
+
color: @pi_success;
|
|
143
173
|
}
|
|
144
174
|
.state-error .card-state {
|
|
145
|
-
|
|
146
|
-
border-color:
|
|
175
|
+
background: alpha(@pi_error, 0.16);
|
|
176
|
+
border-color: alpha(@pi_error, 0.44);
|
|
177
|
+
color: @pi_error;
|
|
147
178
|
}
|
|
148
179
|
|
|
149
180
|
.card-tool,
|
|
150
181
|
.card-elapsed,
|
|
151
182
|
.inspector-key {
|
|
152
|
-
color:
|
|
183
|
+
color: alpha(@pi_foreground, 0.55);
|
|
153
184
|
font-size: 8px;
|
|
154
185
|
}
|
|
155
186
|
|
|
156
187
|
.inspector {
|
|
157
|
-
border-top: 1px solid
|
|
188
|
+
border-top: 1px solid alpha(@pi_foreground, 0.12);
|
|
158
189
|
padding-top: 9px;
|
|
159
190
|
}
|
|
160
191
|
|
|
161
192
|
.inspector-value {
|
|
162
|
-
line-height: 1.
|
|
193
|
+
line-height: 1.3;
|
|
163
194
|
}
|
|
164
195
|
|
|
165
196
|
.activation {
|
|
166
|
-
color:
|
|
197
|
+
color: alpha(@pi_foreground, 0.7);
|
|
167
198
|
font-size: 9px;
|
|
168
199
|
}
|
|
169
200
|
|
|
170
201
|
.activation.success {
|
|
171
|
-
color:
|
|
202
|
+
color: @pi_success;
|
|
172
203
|
}
|
|
173
204
|
|
|
174
205
|
.activation.error {
|
|
175
|
-
color:
|
|
206
|
+
color: @pi_error;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/*
|
|
210
|
+
* AK task references. An active chip is the one clickable element inside a card besides the card
|
|
211
|
+
* itself: it reuses the card's activation, so it carries the accent colour and a pointer cursor.
|
|
212
|
+
* Badge states (a claim that outlives its session, a deferred task) are deliberately inert pills.
|
|
213
|
+
*/
|
|
214
|
+
.ak-task {
|
|
215
|
+
background: alpha(@pi_foreground, 0.07);
|
|
216
|
+
border: 1px solid alpha(@pi_foreground, 0.16);
|
|
217
|
+
border-radius: 999px;
|
|
218
|
+
color: alpha(@pi_foreground, 0.68);
|
|
219
|
+
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
|
220
|
+
font-size: 8px;
|
|
221
|
+
padding: 3px 8px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.ak-task.ak-active {
|
|
225
|
+
background: alpha(@pi_accent, 0.14);
|
|
226
|
+
border-color: alpha(@pi_accent, 0.42);
|
|
227
|
+
color: @pi_accent;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.ak-task.ak-active:hover {
|
|
231
|
+
background: alpha(@pi_accent, 0.26);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.ak-task.ak-orphaned {
|
|
235
|
+
background: alpha(@pi_error, 0.12);
|
|
236
|
+
border: 1px dashed alpha(@pi_error, 0.4);
|
|
237
|
+
color: alpha(@pi_error, 0.92);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.ak-task.ak-deferred {
|
|
241
|
+
background: alpha(@pi_tool, 0.12);
|
|
242
|
+
border: 1px dashed alpha(@pi_tool, 0.4);
|
|
243
|
+
color: alpha(@pi_tool, 0.92);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.ak-overflow {
|
|
247
|
+
color: alpha(@pi_foreground, 0.5);
|
|
248
|
+
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
|
249
|
+
font-size: 8px;
|
|
176
250
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tryinget/pi-activity-strip",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "pi extension package for activity-strip workflows in monorepo runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -88,9 +88,9 @@
|
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
90
|
"@biomejs/biome": "2.4.4",
|
|
91
|
-
"@earendil-works/pi-agent-core": "0.84.
|
|
92
|
-
"@earendil-works/pi-ai": "0.84.
|
|
93
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
91
|
+
"@earendil-works/pi-agent-core": "0.84.4",
|
|
92
|
+
"@earendil-works/pi-ai": "0.84.4",
|
|
93
|
+
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
94
94
|
"@types/node": "22.14.0",
|
|
95
95
|
"@typescript/native-preview": "7.0.0-dev.20260417.1",
|
|
96
96
|
"typebox": "1.3.7",
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: "recognizes terminal-based coding agents and shapes them into activity-strip session records"
|
|
3
|
+
// read_when:
|
|
4
|
+
// - "adding an agent CLI, changing agent card fields, or changing agent title matching"
|
|
5
|
+
// ---
|
|
6
|
+
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Terminal-based coding agents that occupy a Ghostty tab the way Pi does. Recognition is by the
|
|
11
|
+
* executable name of the process that owns the terminal, never by a window title, so a tab is
|
|
12
|
+
* admitted on process evidence alone.
|
|
13
|
+
* @type {ReadonlyArray<{kind: string; label: string; commands: readonly string[]}>}
|
|
14
|
+
*/
|
|
15
|
+
export const AGENT_CATALOG = Object.freeze([
|
|
16
|
+
{ kind: "claude", label: "Claude Code", commands: Object.freeze(["claude"]) },
|
|
17
|
+
{ kind: "codex", label: "Codex", commands: Object.freeze(["codex"]) },
|
|
18
|
+
{ kind: "gemini", label: "Gemini", commands: Object.freeze(["gemini"]) },
|
|
19
|
+
{ kind: "amp", label: "Amp", commands: Object.freeze(["amp"]) },
|
|
20
|
+
{ kind: "aider", label: "Aider", commands: Object.freeze(["aider"]) },
|
|
21
|
+
{ kind: "opencode", label: "opencode", commands: Object.freeze(["opencode"]) },
|
|
22
|
+
{ kind: "crush", label: "Crush", commands: Object.freeze(["crush"]) },
|
|
23
|
+
{ kind: "goose", label: "Goose", commands: Object.freeze(["goose"]) },
|
|
24
|
+
{ kind: "cursor", label: "Cursor Agent", commands: Object.freeze(["cursor-agent"]) },
|
|
25
|
+
{ kind: "zcode", label: "zcode", commands: Object.freeze(["zcode"]) },
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
/** Pi publishes its own telemetry, so its processes are never discovered this way. */
|
|
29
|
+
const EXCLUDED_COMMANDS = new Set(["pi", "node", "bash", "sh", "zsh", "fish", "python3"]);
|
|
30
|
+
/**
|
|
31
|
+
* Executables that only ever host another program. When one of these is the process name, the
|
|
32
|
+
* command line names the real agent, so it is worth the extra read to look.
|
|
33
|
+
*/
|
|
34
|
+
const RUNTIME_COMMANDS = [/^node/i, /^python/i, /^(bash|sh|zsh|fish|dash|env)$/i, /^(deno|bun)$/i];
|
|
35
|
+
|
|
36
|
+
/** @param {unknown} command */
|
|
37
|
+
export function isRuntimeCommand(command) {
|
|
38
|
+
const name = path.basename(String(command ?? "").trim());
|
|
39
|
+
return name.length > 0 && RUNTIME_COMMANDS.some((pattern) => pattern.test(name));
|
|
40
|
+
}
|
|
41
|
+
export const AGENT_ACTIVE_WINDOW_MS = 45_000;
|
|
42
|
+
|
|
43
|
+
/** @param {NodeJS.ProcessEnv} [env] */
|
|
44
|
+
export function agentCatalogFor(env = process.env) {
|
|
45
|
+
const disabled = new Set(
|
|
46
|
+
String(env.PI_ACTIVITY_STRIP_AGENT_KINDS_DISABLED ?? "")
|
|
47
|
+
.split(",")
|
|
48
|
+
.map((value) => value.trim().toLowerCase())
|
|
49
|
+
.filter(Boolean),
|
|
50
|
+
);
|
|
51
|
+
return AGENT_CATALOG.filter((agent) => !disabled.has(agent.kind));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Classify one process by the executable it runs. `command` is the kernel's comm value, which the
|
|
56
|
+
* kernel truncates to 15 characters and which names the runtime rather than the agent when the
|
|
57
|
+
* agent is launched through one; the command line entries name the agent in that case.
|
|
58
|
+
* @param {{command?: unknown; argv0?: unknown; argv1?: unknown; env?: NodeJS.ProcessEnv}} process
|
|
59
|
+
*/
|
|
60
|
+
export function classifyAgentProcess({ command, argv0, argv1, env = process.env } = {}) {
|
|
61
|
+
const names = [command, argv0, argv1]
|
|
62
|
+
.map((value) => path.basename(String(value ?? "").trim()).toLowerCase())
|
|
63
|
+
.filter(Boolean);
|
|
64
|
+
if (names.length === 0 || names.every((name) => EXCLUDED_COMMANDS.has(name))) return null;
|
|
65
|
+
for (const agent of agentCatalogFor(env)) {
|
|
66
|
+
if (agent.commands.some((candidate) => names.includes(candidate))) return agent;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @param {unknown} cwd */
|
|
72
|
+
export function repoLabelFor(cwd) {
|
|
73
|
+
const normalized = String(cwd ?? "").trim();
|
|
74
|
+
if (!normalized || normalized === "/") return "";
|
|
75
|
+
return path.basename(normalized);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Shape one discovered agent tab into the record the card projection consumes. The record carries a
|
|
80
|
+
* terminal identity, so it groups and places exactly like a Pi terminal card, but its session id is
|
|
81
|
+
* namespaced by agent kind and can never be mistaken for a Pi session id.
|
|
82
|
+
* @param {{
|
|
83
|
+
* kind: string;
|
|
84
|
+
* label: string;
|
|
85
|
+
* processId: number;
|
|
86
|
+
* cwd: string;
|
|
87
|
+
* terminalKey: string;
|
|
88
|
+
* terminalFamily: string;
|
|
89
|
+
* terminalSurfaceId: string;
|
|
90
|
+
* sessionKey?: string;
|
|
91
|
+
* titleSuffix?: string;
|
|
92
|
+
* startedAt: number;
|
|
93
|
+
* lastEventAt?: number;
|
|
94
|
+
* now?: number;
|
|
95
|
+
* telemetry?: Partial<import("./claude-transcript.mjs").ClaudeTelemetry> | null;
|
|
96
|
+
* }} tab
|
|
97
|
+
*/
|
|
98
|
+
export function agentSessionRecord({
|
|
99
|
+
kind,
|
|
100
|
+
label,
|
|
101
|
+
processId,
|
|
102
|
+
cwd,
|
|
103
|
+
terminalKey,
|
|
104
|
+
terminalFamily,
|
|
105
|
+
terminalSurfaceId,
|
|
106
|
+
sessionKey = "",
|
|
107
|
+
titleSuffix = "",
|
|
108
|
+
startedAt,
|
|
109
|
+
lastEventAt = 0,
|
|
110
|
+
now = Date.now(),
|
|
111
|
+
telemetry = null,
|
|
112
|
+
}) {
|
|
113
|
+
// Process start times and file modification times are fractional milliseconds, while the panel
|
|
114
|
+
// protocol carries whole-millisecond integers, so every timestamp is rounded at the boundary.
|
|
115
|
+
const startedAtMs = Math.round(startedAt) || 0;
|
|
116
|
+
const lastEventMs = Math.round(lastEventAt) || 0;
|
|
117
|
+
const nowMs = Math.round(now) || 0;
|
|
118
|
+
const activity = lastEventMs > 0 ? lastEventMs : startedAtMs;
|
|
119
|
+
// Reported activity decides liveness. A published state only counts while the transcript is
|
|
120
|
+
// still moving; a stale "tool" record is not evidence that a tool is running right now.
|
|
121
|
+
const recent = lastEventMs > 0 && nowMs - lastEventMs <= AGENT_ACTIVE_WINDOW_MS;
|
|
122
|
+
const reported = String(telemetry?.state ?? "");
|
|
123
|
+
// "waiting" and "error" describe a session that is stopped on purpose, so they survive the
|
|
124
|
+
// liveness window: a permission prompt can sit unanswered for a long time and is still true.
|
|
125
|
+
const sticky = reported === "waiting" || reported === "error";
|
|
126
|
+
const busy = reported === "tool" || reported === "thinking";
|
|
127
|
+
const active = sticky || (recent && (busy || !reported));
|
|
128
|
+
const state = sticky ? reported : active ? (busy ? reported : "thinking") : "idle";
|
|
129
|
+
const toolName = active ? String(telemetry?.toolName ?? "") : "";
|
|
130
|
+
const toolTarget = active ? String(telemetry?.toolTarget ?? "") : "";
|
|
131
|
+
const detail = [toolTarget, String(telemetry?.assistantPreview ?? ""), cwd].find(Boolean) ?? "";
|
|
132
|
+
return {
|
|
133
|
+
sessionId: `agent:${kind}:${sessionKey || processId}`,
|
|
134
|
+
publisherId: `agent-scan:${processId}`,
|
|
135
|
+
publisherSequence: 0,
|
|
136
|
+
processId,
|
|
137
|
+
agentKind: kind,
|
|
138
|
+
agentSessionKey: sessionKey,
|
|
139
|
+
terminalKind: "ghostty-surface",
|
|
140
|
+
terminalKey,
|
|
141
|
+
terminalFamily,
|
|
142
|
+
terminalSurfaceId,
|
|
143
|
+
titleSuffix,
|
|
144
|
+
cwd,
|
|
145
|
+
repoLabel: repoLabelFor(cwd),
|
|
146
|
+
sessionName: "",
|
|
147
|
+
agentLabel: label,
|
|
148
|
+
phase: String(telemetry?.title ?? "").trim() || label,
|
|
149
|
+
detail,
|
|
150
|
+
assistantPreview: String(telemetry?.assistantPreview ?? ""),
|
|
151
|
+
toolName,
|
|
152
|
+
toolTarget,
|
|
153
|
+
state,
|
|
154
|
+
turnIndex: Number(telemetry?.turnIndex ?? 0) || 0,
|
|
155
|
+
updatedAt: nowMs,
|
|
156
|
+
lastEventAt: activity,
|
|
157
|
+
startedAt: startedAtMs,
|
|
158
|
+
agentStartedAt: active ? activity : null,
|
|
159
|
+
agentActive: active,
|
|
160
|
+
lastPromptPreview: String(telemetry?.lastPrompt ?? ""),
|
|
161
|
+
errorMessage: "",
|
|
162
|
+
};
|
|
163
|
+
}
|