@tryinget/pi-activity-strip 0.5.0 → 0.7.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 +59 -7
- package/bin/pi-activity-strip-claude-hook.mjs +71 -0
- package/bin/pi-activity-strip.mjs +98 -64
- package/extensions/activity-strip.js +25 -21
- 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 +396 -9
- package/native/panel/src/main.rs +64 -1
- package/native/panel/src/protocol.rs +142 -0
- package/native/panel/src/runtime.rs +50 -9
- package/native/panel/src/style.css +147 -60
- package/package.json +4 -4
- package/src/client/runtime-lock.mjs +140 -0
- 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/constants.mjs +2 -0
- package/src/common/contracts.d.ts +35 -0
- package/src/common/contracts.ts +67 -1
- package/src/common/ghostty-present.mjs +100 -0
- package/src/common/ghostty-theme.mjs +149 -0
- package/src/common/niri-focus.mjs +179 -111
- 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 +122 -64
- package/src/native/panel-binary.mjs +56 -0
- package/src/native/panel-projection.mjs +70 -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 +22 -1
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
use serde::Deserialize;
|
|
2
2
|
use serde_json::{Value, json};
|
|
3
3
|
use std::io::{self, Write};
|
|
4
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
4
5
|
use std::sync::{Mutex, OnceLock};
|
|
5
6
|
|
|
7
|
+
#[derive(Clone, Debug, Deserialize)]
|
|
8
|
+
#[serde(rename_all = "camelCase")]
|
|
9
|
+
pub struct AkTask {
|
|
10
|
+
#[serde(default)]
|
|
11
|
+
pub id: i64,
|
|
12
|
+
#[serde(default)]
|
|
13
|
+
pub title: String,
|
|
14
|
+
#[serde(default)]
|
|
15
|
+
pub state: String,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl AkTask {
|
|
19
|
+
/// Only an `active` chip is a button: the card's own session holds a live claim, so clicking
|
|
20
|
+
/// it focuses that session's Ghostty window. Every other state is a badge and never fires.
|
|
21
|
+
pub fn is_active(&self) -> bool {
|
|
22
|
+
self.state == "active"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
6
26
|
#[derive(Clone, Debug, Deserialize)]
|
|
7
27
|
#[serde(rename_all = "camelCase")]
|
|
8
28
|
pub struct Card {
|
|
@@ -40,6 +60,36 @@ pub struct Card {
|
|
|
40
60
|
pub agent_active: bool,
|
|
41
61
|
#[serde(default, rename = "processId")]
|
|
42
62
|
pub pid: i64,
|
|
63
|
+
/// Set for tabs running an agent other than Pi, which publish no telemetry of their own.
|
|
64
|
+
#[serde(default)]
|
|
65
|
+
pub agent_label: String,
|
|
66
|
+
/// Read-only AK task references joined onto this card by the controller. `active` chips are
|
|
67
|
+
/// clickable and focus this card's terminal; the rest are inert badges.
|
|
68
|
+
#[serde(default)]
|
|
69
|
+
pub ak_tasks: Vec<AkTask>,
|
|
70
|
+
/// How many further AK references exist beyond the rendered chips.
|
|
71
|
+
#[serde(default)]
|
|
72
|
+
pub ak_task_overflow: u32,
|
|
73
|
+
/// `Some(false)` marks a tab hidden behind another tab of its Ghostty window; the controller
|
|
74
|
+
/// placed it through its host process, so activation presents the tab before focusing.
|
|
75
|
+
#[serde(default)]
|
|
76
|
+
pub surface_visible: Option<bool>,
|
|
77
|
+
/// Niri window id, so a window an agent or tool names by id can be found on the ribbon. A
|
|
78
|
+
/// hidden tab has no window of its own and carries the id of the window hosting it.
|
|
79
|
+
#[serde(default, deserialize_with = "lenient_integer")]
|
|
80
|
+
pub window_id: Option<i64>,
|
|
81
|
+
/// Niri workspace `idx`: the number the operator sees, not the internal workspace id.
|
|
82
|
+
#[serde(default, deserialize_with = "lenient_integer")]
|
|
83
|
+
pub workspace_idx: Option<i64>,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// An optional label is not worth a view: a value that is not an integer drops the field rather
|
|
87
|
+
/// than failing the whole message and every card in it.
|
|
88
|
+
fn lenient_integer<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
|
|
89
|
+
where
|
|
90
|
+
D: serde::Deserializer<'de>,
|
|
91
|
+
{
|
|
92
|
+
Ok(Value::deserialize(deserializer)?.as_i64())
|
|
43
93
|
}
|
|
44
94
|
|
|
45
95
|
impl Card {
|
|
@@ -51,6 +101,10 @@ impl Card {
|
|
|
51
101
|
}
|
|
52
102
|
}
|
|
53
103
|
|
|
104
|
+
pub fn hidden_tab(&self) -> bool {
|
|
105
|
+
self.surface_visible == Some(false)
|
|
106
|
+
}
|
|
107
|
+
|
|
54
108
|
pub fn active(&self) -> bool {
|
|
55
109
|
self.agent_active || matches!(self.state.as_str(), "thinking" | "tool" | "waiting")
|
|
56
110
|
}
|
|
@@ -93,6 +147,7 @@ fn protocol_version() -> u8 {
|
|
|
93
147
|
}
|
|
94
148
|
|
|
95
149
|
static OUTPUT: OnceLock<Mutex<io::Stdout>> = OnceLock::new();
|
|
150
|
+
static READY: AtomicBool = AtomicBool::new(false);
|
|
96
151
|
|
|
97
152
|
pub fn emit(event: Value) {
|
|
98
153
|
let stdout = OUTPUT.get_or_init(|| Mutex::new(io::stdout()));
|
|
@@ -103,7 +158,14 @@ pub fn emit(event: Value) {
|
|
|
103
158
|
}
|
|
104
159
|
}
|
|
105
160
|
|
|
161
|
+
/// Whether this panel ever came up. GTK returns from `run` without building anything when it hands
|
|
162
|
+
/// the activation to another panel on the same display.
|
|
163
|
+
pub fn was_ready() -> bool {
|
|
164
|
+
READY.load(Ordering::SeqCst)
|
|
165
|
+
}
|
|
166
|
+
|
|
106
167
|
pub fn emit_ready() {
|
|
168
|
+
READY.store(true, Ordering::SeqCst);
|
|
107
169
|
emit(json!({
|
|
108
170
|
"protocol": 1,
|
|
109
171
|
"type": "ready",
|
|
@@ -140,6 +202,8 @@ pub fn demo_view() -> ViewMessage {
|
|
|
140
202
|
started_at: now - 135_000,
|
|
141
203
|
last_event_at: now - 4_000,
|
|
142
204
|
updated_at: now,
|
|
205
|
+
window_id: Some(36),
|
|
206
|
+
workspace_idx: Some(2),
|
|
143
207
|
..empty_card()
|
|
144
208
|
},
|
|
145
209
|
Card {
|
|
@@ -159,6 +223,32 @@ pub fn demo_view() -> ViewMessage {
|
|
|
159
223
|
last_event_at: now - 1_000,
|
|
160
224
|
updated_at: now,
|
|
161
225
|
agent_active: true,
|
|
226
|
+
ak_tasks: vec![AkTask {
|
|
227
|
+
id: 5701,
|
|
228
|
+
title: "Show clickable AK-task references on the activity ribbon".into(),
|
|
229
|
+
state: "active".into(),
|
|
230
|
+
}],
|
|
231
|
+
ak_task_overflow: 1,
|
|
232
|
+
window_id: Some(43),
|
|
233
|
+
workspace_idx: Some(2),
|
|
234
|
+
..empty_card()
|
|
235
|
+
},
|
|
236
|
+
Card {
|
|
237
|
+
card_id: "demo-hidden".into(),
|
|
238
|
+
session_id: "demo-hidden".into(),
|
|
239
|
+
repo_label: "pi-extensions".into(),
|
|
240
|
+
phase: "Waiting behind another tab".into(),
|
|
241
|
+
state: "idle".into(),
|
|
242
|
+
detail: "Hidden tab placed through its Ghostty host window".into(),
|
|
243
|
+
cwd: "/home/tryinget/ai-society/softwareco/owned/pi-extensions".into(),
|
|
244
|
+
started_at: now - 610_000,
|
|
245
|
+
last_event_at: now - 95_000,
|
|
246
|
+
updated_at: now,
|
|
247
|
+
pid: 48213,
|
|
248
|
+
agent_label: "Claude Code".into(),
|
|
249
|
+
surface_visible: Some(false),
|
|
250
|
+
window_id: Some(43),
|
|
251
|
+
workspace_idx: Some(2),
|
|
162
252
|
..empty_card()
|
|
163
253
|
},
|
|
164
254
|
],
|
|
@@ -184,6 +274,12 @@ fn empty_card() -> Card {
|
|
|
184
274
|
updated_at: 0,
|
|
185
275
|
agent_active: false,
|
|
186
276
|
pid: 0,
|
|
277
|
+
agent_label: String::new(),
|
|
278
|
+
ak_tasks: Vec::new(),
|
|
279
|
+
ak_task_overflow: 0,
|
|
280
|
+
surface_visible: None,
|
|
281
|
+
window_id: None,
|
|
282
|
+
workspace_idx: None,
|
|
187
283
|
}
|
|
188
284
|
}
|
|
189
285
|
|
|
@@ -202,6 +298,52 @@ mod tests {
|
|
|
202
298
|
assert!(view.sessions[0].active());
|
|
203
299
|
assert_eq!(view.sessions[0].agent_started_at, None);
|
|
204
300
|
assert_eq!(view.sessions[0].pid, 4242);
|
|
301
|
+
assert!(view.sessions[0].ak_tasks.is_empty());
|
|
302
|
+
assert_eq!(view.sessions[0].ak_task_overflow, 0);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#[test]
|
|
306
|
+
fn window_and_workspace_numbers_are_optional_card_fields() {
|
|
307
|
+
let view: ViewMessage = serde_json::from_str(
|
|
308
|
+
r#"{"protocol":1,"type":"view","revision":9,"sessions":[{"cardId":"card-a","windowId":43,"workspaceIdx":2},{"cardId":"card-b","windowId":null,"workspaceIdx":null},{"cardId":"card-c"}]}"#,
|
|
309
|
+
)
|
|
310
|
+
.expect("view should parse");
|
|
311
|
+
assert_eq!(view.sessions[0].window_id, Some(43));
|
|
312
|
+
assert_eq!(view.sessions[0].workspace_idx, Some(2));
|
|
313
|
+
assert_eq!(view.sessions[1].window_id, None);
|
|
314
|
+
assert_eq!(view.sessions[1].workspace_idx, None);
|
|
315
|
+
assert_eq!(
|
|
316
|
+
view.sessions[2].window_id, None,
|
|
317
|
+
"controllers that send no window id still parse"
|
|
318
|
+
);
|
|
319
|
+
assert_eq!(view.sessions[2].workspace_idx, None);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
#[test]
|
|
323
|
+
fn a_malformed_window_or_workspace_number_drops_only_that_field() {
|
|
324
|
+
let view: ViewMessage = serde_json::from_str(
|
|
325
|
+
r#"{"protocol":1,"type":"view","revision":10,"sessions":[{"cardId":"card-a","windowId":"43","workspaceIdx":2.5},{"cardId":"card-b","windowId":44,"workspaceIdx":3}]}"#,
|
|
326
|
+
)
|
|
327
|
+
.expect("a bad optional field must not reject the whole view");
|
|
328
|
+
assert_eq!(view.sessions.len(), 2);
|
|
329
|
+
assert_eq!(view.sessions[0].window_id, None);
|
|
330
|
+
assert_eq!(view.sessions[0].workspace_idx, None);
|
|
331
|
+
assert_eq!(view.sessions[1].window_id, Some(44));
|
|
332
|
+
assert_eq!(view.sessions[1].workspace_idx, Some(3));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
#[test]
|
|
336
|
+
fn ak_task_chips_parse_with_states() {
|
|
337
|
+
let view: ViewMessage = serde_json::from_str(
|
|
338
|
+
r#"{"protocol":1,"type":"view","revision":8,"sessions":[{"cardId":"card-a","akTasks":[{"id":5701,"title":"Show clickable AK-task references","state":"active"},{"id":5432,"title":"Preserve safe NEXUS recovery reasons","state":"orphaned"},{"id":4220,"title":"Control Decision 77 epoch admissions","state":"deferred"}],"akTaskOverflow":3}]}"#,
|
|
339
|
+
)
|
|
340
|
+
.expect("view should parse");
|
|
341
|
+
let card = &view.sessions[0];
|
|
342
|
+
assert_eq!(card.ak_tasks.len(), 3);
|
|
343
|
+
assert!(card.ak_tasks[0].is_active());
|
|
344
|
+
assert!(!card.ak_tasks[1].is_active());
|
|
345
|
+
assert!(!card.ak_tasks[2].is_active());
|
|
346
|
+
assert_eq!(card.ak_task_overflow, 3);
|
|
205
347
|
}
|
|
206
348
|
|
|
207
349
|
#[test]
|
|
@@ -5,17 +5,44 @@ use std::collections::{HashMap, HashSet};
|
|
|
5
5
|
use std::io::{BufRead, BufReader};
|
|
6
6
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
7
7
|
|
|
8
|
+
/// Colour definitions used until the controller reports what Ghostty is showing.
|
|
9
|
+
const FALLBACK_DEFINITIONS: &str = concat!(
|
|
10
|
+
"@define-color pi_background #1e2326;\n",
|
|
11
|
+
"@define-color pi_foreground #d3c6aa;\n",
|
|
12
|
+
"@define-color pi_accent #7fbbb3;\n",
|
|
13
|
+
"@define-color pi_thinking #7fbbb3;\n",
|
|
14
|
+
"@define-color pi_tool #dbbc7f;\n",
|
|
15
|
+
"@define-color pi_waiting #e69875;\n",
|
|
16
|
+
"@define-color pi_success #a7c080;\n",
|
|
17
|
+
"@define-color pi_error #e67e80;\n",
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
thread_local! {
|
|
21
|
+
static THEME_PROVIDER: gtk::CssProvider = gtk::CssProvider::new();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// The stylesheet derives every shade from a handful of named colours, so following the terminal's
|
|
25
|
+
/// theme is a matter of redefining those names and reloading one provider.
|
|
8
26
|
#[allow(deprecated)]
|
|
9
27
|
pub fn install_css() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
apply_theme(FALLBACK_DEFINITIONS);
|
|
29
|
+
THEME_PROVIDER.with(|provider| {
|
|
30
|
+
if let Some(display) = gtk::gdk::Display::default() {
|
|
31
|
+
gtk::style_context_add_provider_for_display(
|
|
32
|
+
&display,
|
|
33
|
+
provider,
|
|
34
|
+
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Replace the active colour definitions. Definitions must sit in the same provider as the rules
|
|
41
|
+
/// that reference them, so both are loaded together.
|
|
42
|
+
#[allow(deprecated)]
|
|
43
|
+
pub fn apply_theme(definitions: &str) {
|
|
44
|
+
let css = format!("{definitions}\n{}", include_str!("style.css"));
|
|
45
|
+
THEME_PROVIDER.with(|provider| provider.load_from_data(&css));
|
|
19
46
|
}
|
|
20
47
|
|
|
21
48
|
pub fn start_input_reader(sender: relm4::Sender<AppMsg>) {
|
|
@@ -53,6 +80,20 @@ fn consume_line(sender: &relm4::Sender<AppMsg>, line: &str) {
|
|
|
53
80
|
let _ = sender.send(AppMsg::InputError(format!("invalid view message: {error}")));
|
|
54
81
|
}
|
|
55
82
|
},
|
|
83
|
+
Some("theme") => {
|
|
84
|
+
let definitions = value
|
|
85
|
+
.get("definitions")
|
|
86
|
+
.and_then(|item| item.as_str())
|
|
87
|
+
.unwrap_or_default()
|
|
88
|
+
.to_owned();
|
|
89
|
+
if definitions.is_empty() {
|
|
90
|
+
let _ = sender.send(AppMsg::InputError(
|
|
91
|
+
"theme message carried no colours".into(),
|
|
92
|
+
));
|
|
93
|
+
} else {
|
|
94
|
+
let _ = sender.send(AppMsg::Theme(definitions));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
56
97
|
Some("focus-strip") => {
|
|
57
98
|
let _ = sender.send(AppMsg::FocusStrip);
|
|
58
99
|
}
|
|
@@ -1,56 +1,66 @@
|
|
|
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,
|
|
33
|
+
.card-window,
|
|
22
34
|
.card-tool,
|
|
35
|
+
.card-elapsed,
|
|
23
36
|
.inspector-key {
|
|
24
37
|
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
|
25
38
|
}
|
|
26
39
|
|
|
27
40
|
.brand-eyebrow {
|
|
28
|
-
color:
|
|
41
|
+
color: alpha(@pi_accent, 0.9);
|
|
29
42
|
font-size: 9px;
|
|
30
|
-
|
|
43
|
+
font-weight: 600;
|
|
44
|
+
letter-spacing: 1.4px;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
.brand-title {
|
|
34
|
-
color:
|
|
35
|
-
font-size:
|
|
48
|
+
color: @pi_foreground;
|
|
49
|
+
font-size: 17px;
|
|
36
50
|
font-weight: 650;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.brand-subtitle {
|
|
40
|
-
color: rgba(194, 205, 224, 0.74);
|
|
41
|
-
font-size: 10px;
|
|
51
|
+
letter-spacing: -0.2px;
|
|
42
52
|
}
|
|
43
53
|
|
|
44
54
|
.meta {
|
|
45
|
-
color:
|
|
55
|
+
color: alpha(@pi_foreground, 0.56);
|
|
46
56
|
font-size: 9px;
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
/* biome-ignore lint/correctness/noUnknownTypeSelector: GTK CSS uses widget node names. */
|
|
60
|
+
.cards-panel,
|
|
50
61
|
.cards-panel > viewport {
|
|
51
|
-
background:
|
|
52
|
-
|
|
53
|
-
padding: 6px 8px 8px;
|
|
62
|
+
background: transparent;
|
|
63
|
+
padding: 0;
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
/* biome-ignore lint/correctness/noUnknownTypeSelector: GTK CSS uses widget node names. */
|
|
@@ -61,32 +71,42 @@ window {
|
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
.card {
|
|
64
|
-
color:
|
|
65
|
-
background:
|
|
66
|
-
border: 1px solid
|
|
67
|
-
border-left: 3px solid
|
|
68
|
-
border-radius:
|
|
69
|
-
padding:
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
color: @pi_foreground;
|
|
75
|
+
background: mix(@pi_background, @pi_foreground, 0.07);
|
|
76
|
+
border: 1px solid alpha(@pi_foreground, 0.1);
|
|
77
|
+
border-left: 3px solid alpha(@pi_foreground, 0.35);
|
|
78
|
+
border-radius: 12px;
|
|
79
|
+
padding: 7px 11px 7px 10px;
|
|
80
|
+
transition:
|
|
81
|
+
background 120ms ease,
|
|
82
|
+
border-color 120ms ease;
|
|
72
83
|
}
|
|
73
84
|
|
|
74
85
|
.card:hover,
|
|
75
86
|
.card:focus-visible,
|
|
76
87
|
.card.open {
|
|
77
|
-
background:
|
|
78
|
-
border-color:
|
|
88
|
+
background: mix(@pi_background, @pi_foreground, 0.14);
|
|
89
|
+
border-color: alpha(@pi_accent, 0.5);
|
|
79
90
|
}
|
|
80
91
|
|
|
92
|
+
/* The tab the compositor is actually showing, marked by weight rather than another border. */
|
|
81
93
|
.card.current {
|
|
82
|
-
|
|
83
|
-
border-color:
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
background: mix(@pi_background, @pi_foreground, 0.16);
|
|
95
|
+
border-color: alpha(@pi_accent, 0.42);
|
|
96
|
+
box-shadow: inset 0 0 0 1px alpha(@pi_accent, 0.22);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.card.hidden-tab {
|
|
100
|
+
background: alpha(@pi_background, 0.55);
|
|
101
|
+
border-style: dashed;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.card.hidden-tab .card-repo {
|
|
105
|
+
color: alpha(@pi_foreground, 0.78);
|
|
86
106
|
}
|
|
87
107
|
|
|
88
108
|
.card.stalled {
|
|
89
|
-
opacity: 0.
|
|
109
|
+
opacity: 0.62;
|
|
90
110
|
}
|
|
91
111
|
|
|
92
112
|
.card.stalled .card-phase {
|
|
@@ -94,83 +114,150 @@ window {
|
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
.card.state-thinking {
|
|
97
|
-
border-left-color:
|
|
117
|
+
border-left-color: @pi_thinking;
|
|
98
118
|
}
|
|
99
119
|
.card.state-tool {
|
|
100
|
-
border-left-color:
|
|
120
|
+
border-left-color: @pi_tool;
|
|
101
121
|
}
|
|
102
122
|
.card.state-waiting {
|
|
103
|
-
border-left-color:
|
|
123
|
+
border-left-color: @pi_waiting;
|
|
104
124
|
}
|
|
105
125
|
.card.state-success {
|
|
106
|
-
border-left-color:
|
|
126
|
+
border-left-color: @pi_success;
|
|
107
127
|
}
|
|
108
128
|
.card.state-error {
|
|
109
|
-
border-left-color:
|
|
129
|
+
border-left-color: @pi_error;
|
|
110
130
|
}
|
|
111
131
|
|
|
112
132
|
.card-repo {
|
|
113
|
-
color:
|
|
133
|
+
color: @pi_foreground;
|
|
114
134
|
font-size: 12px;
|
|
115
135
|
font-weight: 650;
|
|
116
136
|
}
|
|
117
137
|
|
|
118
138
|
.card-phase,
|
|
119
139
|
.inspector-value {
|
|
120
|
-
color:
|
|
140
|
+
color: alpha(@pi_foreground, 0.74);
|
|
121
141
|
font-size: 10px;
|
|
122
142
|
}
|
|
123
143
|
|
|
144
|
+
/* A quiet pill: the state is carried mostly by the card's left edge. */
|
|
124
145
|
.card-state {
|
|
125
|
-
|
|
146
|
+
background: alpha(@pi_foreground, 0.07);
|
|
147
|
+
border: 1px solid alpha(@pi_foreground, 0.16);
|
|
126
148
|
border-radius: 999px;
|
|
127
|
-
color:
|
|
149
|
+
color: alpha(@pi_foreground, 0.68);
|
|
128
150
|
font-size: 8px;
|
|
129
|
-
|
|
151
|
+
letter-spacing: 0.4px;
|
|
152
|
+
padding: 3px 7px;
|
|
130
153
|
}
|
|
131
154
|
|
|
155
|
+
.state-thinking .card-state {
|
|
156
|
+
background: alpha(@pi_thinking, 0.14);
|
|
157
|
+
border-color: alpha(@pi_thinking, 0.38);
|
|
158
|
+
color: @pi_thinking;
|
|
159
|
+
}
|
|
132
160
|
.state-tool .card-state {
|
|
133
|
-
|
|
134
|
-
border-color:
|
|
161
|
+
background: alpha(@pi_tool, 0.14);
|
|
162
|
+
border-color: alpha(@pi_tool, 0.38);
|
|
163
|
+
color: @pi_tool;
|
|
135
164
|
}
|
|
136
165
|
.state-waiting .card-state {
|
|
137
|
-
|
|
138
|
-
border-color:
|
|
166
|
+
background: alpha(@pi_waiting, 0.16);
|
|
167
|
+
border-color: alpha(@pi_waiting, 0.42);
|
|
168
|
+
color: @pi_waiting;
|
|
139
169
|
}
|
|
140
170
|
.state-success .card-state {
|
|
141
|
-
|
|
142
|
-
border-color:
|
|
171
|
+
background: alpha(@pi_success, 0.14);
|
|
172
|
+
border-color: alpha(@pi_success, 0.38);
|
|
173
|
+
color: @pi_success;
|
|
143
174
|
}
|
|
144
175
|
.state-error .card-state {
|
|
145
|
-
|
|
146
|
-
border-color:
|
|
176
|
+
background: alpha(@pi_error, 0.16);
|
|
177
|
+
border-color: alpha(@pi_error, 0.44);
|
|
178
|
+
color: @pi_error;
|
|
147
179
|
}
|
|
148
180
|
|
|
149
181
|
.card-tool,
|
|
150
182
|
.card-elapsed,
|
|
151
183
|
.inspector-key {
|
|
152
|
-
color:
|
|
184
|
+
color: alpha(@pi_foreground, 0.55);
|
|
185
|
+
font-size: 8px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/*
|
|
189
|
+
* The Niri window id, for finding a window an agent or tool names by id. A tint rather than a
|
|
190
|
+
* border and no vertical padding, so it reads as a chip without growing the footer line.
|
|
191
|
+
*/
|
|
192
|
+
.card-window {
|
|
193
|
+
background: alpha(@pi_foreground, 0.09);
|
|
194
|
+
border-radius: 999px;
|
|
195
|
+
color: alpha(@pi_foreground, 0.7);
|
|
153
196
|
font-size: 8px;
|
|
197
|
+
padding: 0 5px;
|
|
154
198
|
}
|
|
155
199
|
|
|
156
200
|
.inspector {
|
|
157
|
-
border-top: 1px solid
|
|
201
|
+
border-top: 1px solid alpha(@pi_foreground, 0.12);
|
|
158
202
|
padding-top: 9px;
|
|
159
203
|
}
|
|
160
204
|
|
|
161
205
|
.inspector-value {
|
|
162
|
-
line-height: 1.
|
|
206
|
+
line-height: 1.3;
|
|
163
207
|
}
|
|
164
208
|
|
|
165
209
|
.activation {
|
|
166
|
-
color:
|
|
210
|
+
color: alpha(@pi_foreground, 0.7);
|
|
167
211
|
font-size: 9px;
|
|
168
212
|
}
|
|
169
213
|
|
|
170
214
|
.activation.success {
|
|
171
|
-
color:
|
|
215
|
+
color: @pi_success;
|
|
172
216
|
}
|
|
173
217
|
|
|
174
218
|
.activation.error {
|
|
175
|
-
color:
|
|
219
|
+
color: @pi_error;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/*
|
|
223
|
+
* AK task references. An active chip is the one clickable element inside a card besides the card
|
|
224
|
+
* itself: it reuses the card's activation, so it carries the accent colour and a pointer cursor.
|
|
225
|
+
* Badge states (a claim that outlives its session, a deferred task) are deliberately inert pills.
|
|
226
|
+
*/
|
|
227
|
+
.ak-task {
|
|
228
|
+
background: alpha(@pi_foreground, 0.07);
|
|
229
|
+
border: 1px solid alpha(@pi_foreground, 0.16);
|
|
230
|
+
border-radius: 999px;
|
|
231
|
+
color: alpha(@pi_foreground, 0.68);
|
|
232
|
+
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
|
233
|
+
font-size: 8px;
|
|
234
|
+
padding: 3px 8px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.ak-task.ak-active {
|
|
238
|
+
background: alpha(@pi_accent, 0.14);
|
|
239
|
+
border-color: alpha(@pi_accent, 0.42);
|
|
240
|
+
color: @pi_accent;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.ak-task.ak-active:hover {
|
|
244
|
+
background: alpha(@pi_accent, 0.26);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.ak-task.ak-orphaned {
|
|
248
|
+
background: alpha(@pi_error, 0.12);
|
|
249
|
+
border: 1px dashed alpha(@pi_error, 0.4);
|
|
250
|
+
color: alpha(@pi_error, 0.92);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.ak-task.ak-deferred {
|
|
254
|
+
background: alpha(@pi_tool, 0.12);
|
|
255
|
+
border: 1px dashed alpha(@pi_tool, 0.4);
|
|
256
|
+
color: alpha(@pi_tool, 0.92);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.ak-overflow {
|
|
260
|
+
color: alpha(@pi_foreground, 0.5);
|
|
261
|
+
font-family: "IBM Plex Mono", "JetBrains Mono", monospace;
|
|
262
|
+
font-size: 8px;
|
|
176
263
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tryinget/pi-activity-strip",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|