feat: Seat Deck sidebar with parliament telemetry and motion (0.9.25)
Replace the OpenCode-style inspector with a seat/fleet/ctx/work telemetry rail, default-on for wide sessions, driven by 80ms ASCII micro-motion. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
@@ -3,7 +3,7 @@
|
||||
|
||||
#define CST_VERSION_MAJOR 0
|
||||
#define CST_VERSION_MINOR 9
|
||||
#define CST_VERSION_PATCH 24
|
||||
#define CST_VERSION_PATCH 25
|
||||
|
||||
#define CST_VERSION_STR_(x) #x
|
||||
#define CST_VERSION_STR(x) CST_VERSION_STR_(x)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "cst_ext.h"
|
||||
#include "cst_mcp.h"
|
||||
#include "cst_models.h"
|
||||
#include "cst_discover.h"
|
||||
#include "cst_parliament.h"
|
||||
#include "cst_plugins.h"
|
||||
#include "cst_realm.h"
|
||||
@@ -311,6 +312,7 @@ typedef struct {
|
||||
int todo_rows;
|
||||
int home; /* empty transcript: centered start screen */
|
||||
int show_side; /* /sidebar */
|
||||
int motion_on; /* /motion — Seat Deck ASCII micro-motion */
|
||||
int side_w;
|
||||
int in_x, in_y, in_w, in_h; /* input box, screen coords */
|
||||
int log_x, log_y; /* logw origin, screen coords */
|
||||
@@ -1976,6 +1978,67 @@ static const char *spinner_frame(const tui_t *t) {
|
||||
return g_ascii ? asc[t->spin % 4] : uni[t->spin % 10];
|
||||
}
|
||||
|
||||
/* Seat Deck motion: short glyph sequences driven by t->spin (80ms ticks). */
|
||||
static const char *motion_fleet_frame(const tui_t *t) {
|
||||
static const char *const uni[] = {"✦ · ·", "· ✦ ·", "· · ✦", "· ✦ ·"};
|
||||
static const char *const asc[] = {"* . .", ". * .", ". . *", ". * ."};
|
||||
int i = (t->spin / 2) % 4;
|
||||
return g_ascii ? asc[i] : uni[i];
|
||||
}
|
||||
|
||||
static const char *seat_pulse_dot(const tui_t *t) {
|
||||
int on = 1;
|
||||
if (t->motion_on) {
|
||||
if (t->busy)
|
||||
on = (t->spin % 2) == 0;
|
||||
else
|
||||
on = ((t->spin / 6) % 2) == 0;
|
||||
}
|
||||
if (g_ascii)
|
||||
return on ? "*" : ".";
|
||||
return on ? "●" : "○";
|
||||
}
|
||||
|
||||
static int realm_fp_short(char out[9]) {
|
||||
out[0] = 0;
|
||||
cst_peer_info_t peers[1];
|
||||
if (cst_discover_local_list(NULL, peers, 1) > 0 && peers[0].realm_fp[0]) {
|
||||
snprintf(out, 9, "%.8s", peers[0].realm_fp);
|
||||
return 0;
|
||||
}
|
||||
char dir[OS_PATH_MAX];
|
||||
cst_realm_keys_t k;
|
||||
char fp[33];
|
||||
if (cst_realm_dir(dir, sizeof(dir)) == 0 && cst_realm_load(dir, &k) == 0 &&
|
||||
cst_realm_fingerprint(k.pk, fp) == 0) {
|
||||
snprintf(out, 9, "%.8s", fp);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void draw_ctx_bar(char *out, size_t sz, int pct, int pulse_on) {
|
||||
int bar_w = 12;
|
||||
int fill = pct <= 0 ? 0 : pct >= 100 ? bar_w : (pct * bar_w) / 100;
|
||||
size_t o = 0;
|
||||
out[0] = 0;
|
||||
for (int i = 0; i < bar_w && o + 4 < sz; i++) {
|
||||
const char *cell;
|
||||
if (i < fill) {
|
||||
if (pulse_on && pct >= 80 && i == fill - 1)
|
||||
cell = g_ascii ? "*" : "▓";
|
||||
else
|
||||
cell = g_ascii ? "=" : "█";
|
||||
} else {
|
||||
cell = g_ascii ? "." : "░";
|
||||
}
|
||||
size_t n = strlen(cell);
|
||||
memcpy(out + o, cell, n);
|
||||
o += n;
|
||||
out[o] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void fmt_tokens(long n, char *out, size_t sz) {
|
||||
if (n >= 1000000)
|
||||
snprintf(out, sz, "%.1fM", (double)n / 1e6);
|
||||
@@ -2511,103 +2574,149 @@ static void draw_side(tui_t *t) {
|
||||
werase(s);
|
||||
int h, w;
|
||||
getmaxyx(s, h, w);
|
||||
int x = 2, mw = w - 4, y = 1;
|
||||
const char *rail = g_ascii ? "|" : "┃";
|
||||
for (int ry = 0; ry < h; ry++)
|
||||
draw_ansi_at(s, ry, 0, 1, rail, CP_SIDE_PRIMARY, 0, 0);
|
||||
int x = 2, mw = w - 3, y = 1;
|
||||
if (mw < 8)
|
||||
mw = w > 2 ? w - 2 : 1;
|
||||
char line[512];
|
||||
int bottom = h - 3; /* where + version */
|
||||
|
||||
const char *title = t->title[0] ? t->title : "New session";
|
||||
size_t fit = utf8_fit_cols(title, strlen(title), mw);
|
||||
draw_ansi_at(s, y++, x, mw, title, CP_SIDE_BRIGHT, 1, 0);
|
||||
if (fit < strlen(title) && y < h)
|
||||
draw_ansi_at(s, y++, x, mw, title + fit, CP_SIDE_BRIGHT, 1, 0);
|
||||
/* SEAT — local role + instance + short realm fp */
|
||||
draw_ansi_at(s, y++, x, mw, "seat", CP_SIDE_MUTED, 0, 0);
|
||||
{
|
||||
char sid[64] = {0};
|
||||
int sess = cst_parliament_session_ok(sid, sizeof(sid));
|
||||
int chair = sess && cst_parliament_is_chair();
|
||||
const char *role = !sess ? "logged-out" : chair ? "chair" : "member";
|
||||
int pair = !sess ? CP_SIDE_MUTED
|
||||
: chair ? CP_SIDE_SUCCESS : CP_SIDE_PRIMARY;
|
||||
int dx = x + draw_ansi_at(s, y, x, 2, seat_pulse_dot(t), pair, 0, 0);
|
||||
snprintf(line, sizeof(line), "%s", role);
|
||||
dx += draw_ansi_at(s, y, dx + 1, mw - (dx + 1 - x), line, pair, 1, 0);
|
||||
y++;
|
||||
if (!sid[0])
|
||||
cst_parliament_default_instance(sid, sizeof(sid));
|
||||
if (sid[0] && y < bottom) {
|
||||
size_t fit = utf8_fit_cols(sid, strlen(sid), mw);
|
||||
snprintf(line, sizeof(line), "%.*s", (int)fit, sid);
|
||||
draw_ansi_at(s, y++, x, mw, line, CP_SIDE_MUTED, 0, 0);
|
||||
}
|
||||
char fp[9];
|
||||
if (realm_fp_short(fp) == 0 && y < bottom) {
|
||||
snprintf(line, sizeof(line), "realm %.8s", fp);
|
||||
draw_ansi_at(s, y++, x, mw, line, CP_SIDE_MUTED, 0, 0);
|
||||
}
|
||||
}
|
||||
y++;
|
||||
|
||||
long used;
|
||||
int pct = ctx_percent(t, &used);
|
||||
draw_ansi_at(s, y++, x, mw, "Context", CP_SIDE_BRIGHT, 1, 0);
|
||||
snprintf(line, sizeof(line), "%ld tokens", used);
|
||||
draw_ansi_at(s, y++, x, mw, line, CP_SIDE_MUTED, 0, 0);
|
||||
snprintf(line, sizeof(line), "%d%% used", pct);
|
||||
draw_ansi_at(s, y++, x, mw, line, CP_SIDE_MUTED, 0, 0);
|
||||
y++;
|
||||
/* FLEET — peers / daemon / revision */
|
||||
if (y + 1 < bottom) {
|
||||
draw_ansi_at(s, y++, x, mw, "fleet", CP_SIDE_MUTED, 0, 0);
|
||||
int pid = 0, port = 0;
|
||||
char diid[64] = {0};
|
||||
int svc = cst_parliament_daemon_info(&pid, diid, sizeof(diid), &port) == 0;
|
||||
cst_parliament_meta_t meta;
|
||||
memset(&meta, 0, sizeof(meta));
|
||||
int have_meta = cst_parliament_load(&meta) == 0;
|
||||
cst_peer_info_t peers[16];
|
||||
int np = cst_discover_local_list(NULL, peers, 16);
|
||||
if (!svc) {
|
||||
draw_ansi_at(s, y++, x, mw, "offline", CP_SIDE_MUTED, 0, 0);
|
||||
} else {
|
||||
snprintf(line, sizeof(line), "port %d · %d peer%s", port, np,
|
||||
np == 1 ? "" : "s");
|
||||
draw_ansi_at(s, y++, x, mw, line, CP_SIDE, 0, 0);
|
||||
if (have_meta && y < bottom) {
|
||||
int dx = x;
|
||||
snprintf(line, sizeof(line), "rev=%lu", meta.revision);
|
||||
dx += draw_ansi_at(s, y, x, mw, line, CP_SIDE_BRIGHT, 0, 0);
|
||||
if (t->motion_on && np > 0 && dx + 8 < x + mw)
|
||||
draw_ansi_at(s, y, dx + 1, x + mw - (dx + 1), motion_fleet_frame(t),
|
||||
CP_SIDE_PRIMARY, 0, 0);
|
||||
y++;
|
||||
}
|
||||
if (np == 0 && y < bottom)
|
||||
draw_ansi_at(s, y++, x, mw, "no peers", CP_SIDE_MUTED, 0, 0);
|
||||
else {
|
||||
for (int i = 0; i < np && y < bottom && i < 4; i++) {
|
||||
int is_chair =
|
||||
have_meta && meta.chair_instance[0] &&
|
||||
!strcmp(peers[i].instance_id, meta.chair_instance);
|
||||
snprintf(line, sizeof(line), "%s%.28s", is_chair ? "+ " : " ",
|
||||
peers[i].instance_id);
|
||||
draw_ansi_at(s, y++, x, mw, line,
|
||||
is_chair ? CP_SIDE_SUCCESS : CP_SIDE_MUTED, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
y++;
|
||||
}
|
||||
|
||||
int bottom = h - 3; /* keep two rows for where + version */
|
||||
char appr[64];
|
||||
approval_label(t, appr, sizeof(appr));
|
||||
/* CTX — context meter */
|
||||
if (y + 2 < bottom) {
|
||||
draw_ansi_at(s, y++, x, mw, "Approval", CP_SIDE_BRIGHT, 1, 0);
|
||||
draw_ansi_at(s, y++, x, mw, appr,
|
||||
approval_mode(t) == CST_APPROVAL_YOLO ? CP_SIDE_PRIMARY
|
||||
: CP_SIDE_MUTED,
|
||||
draw_ansi_at(s, y++, x, mw, "ctx", CP_SIDE_MUTED, 0, 0);
|
||||
long used;
|
||||
int pct = ctx_percent(t, &used);
|
||||
char tok[32], bar[40];
|
||||
fmt_tokens(used, tok, sizeof(tok));
|
||||
int pulse = t->motion_on && pct >= 80 && (t->spin % 2) == 0;
|
||||
draw_ctx_bar(bar, sizeof(bar), pct, pulse);
|
||||
snprintf(line, sizeof(line), "%s %d%% %s%s", bar, pct, g_ascii ? "" : "· ",
|
||||
tok);
|
||||
draw_ansi_at(s, y++, x, mw, line, pct >= 80 ? CP_SIDE_PRIMARY : CP_SIDE,
|
||||
0, 0);
|
||||
y++;
|
||||
}
|
||||
|
||||
/* WORK — todo + modified (quiet) */
|
||||
int work_hdr = 0;
|
||||
int total = 0;
|
||||
int todo_n = 0;
|
||||
if (t->show_todo && t->todo && todo_open_items(t->todo, &total) >= 0 &&
|
||||
total > 0 && y + 2 < bottom) {
|
||||
draw_ansi_at(s, y++, x, mw, "Todo", CP_SIDE_BRIGHT, 1, 0);
|
||||
for (const char *p = t->todo; *p && y < bottom - 1;) {
|
||||
if (!work_hdr) {
|
||||
draw_ansi_at(s, y++, x, mw, "work", CP_SIDE_MUTED, 0, 0);
|
||||
work_hdr = 1;
|
||||
}
|
||||
for (const char *p = t->todo; *p && y < bottom - 1 && todo_n < 4;) {
|
||||
const char *nl = strchr(p, '\n');
|
||||
size_t len = nl ? (size_t)(nl - p) : strlen(p);
|
||||
if (len >= 3) {
|
||||
int pair = CP_SIDE;
|
||||
const char *mark = "[ ]";
|
||||
if (strncmp(p, "[x]", 3) == 0)
|
||||
pair = CP_SIDE_MUTED, mark = "[✓]";
|
||||
pair = CP_SIDE_MUTED, mark = g_ascii ? "[x]" : "[✓]";
|
||||
else if (strncmp(p, "[~]", 3) == 0)
|
||||
pair = CP_SIDE_PRIMARY, mark = "[•]";
|
||||
pair = CP_SIDE_PRIMARY, mark = g_ascii ? "[~]" : "[•]";
|
||||
const char *txt = p + 3;
|
||||
while (txt < p + len && *txt == ' ')
|
||||
txt++;
|
||||
snprintf(line, sizeof(line), "%s %.*s", mark, (int)(p + len - txt),
|
||||
txt);
|
||||
draw_ansi_at(s, y++, x, mw, line, pair, 0, 0);
|
||||
todo_n++;
|
||||
}
|
||||
p = nl ? nl + 1 : p + len;
|
||||
}
|
||||
y++;
|
||||
}
|
||||
|
||||
int mready = 0, mtotal = 0;
|
||||
cst_mcp_counts(&mready, &mtotal);
|
||||
if (mtotal > 0 && y + 2 < bottom) {
|
||||
cst_mcp_info_t mi[CST_MCP_MAX];
|
||||
int n = cst_mcp_info(mi, CST_MCP_MAX);
|
||||
snprintf(line, sizeof(line), "MCP %d/%d", mready, mtotal);
|
||||
draw_ansi_at(s, y++, x, mw, line, CP_SIDE_BRIGHT, 1, 0);
|
||||
for (int i = 0; i < n && y < bottom - 1; i++) {
|
||||
if (mi[i].state == CST_MCP_OFF)
|
||||
continue;
|
||||
int ok = mi[i].state == CST_MCP_READY;
|
||||
int pend = mi[i].state == CST_MCP_STARTING;
|
||||
int dx = x + draw_ansi_at(s, y, x, 2, g_ascii ? "*" : "●",
|
||||
ok ? CP_SIDE_SUCCESS
|
||||
: pend ? CP_SIDE_MUTED : CP_SIDE_PRIMARY,
|
||||
0, 0);
|
||||
if (ok)
|
||||
snprintf(line, sizeof(line), "%.63s · %d tools", mi[i].name,
|
||||
mi[i].ntools);
|
||||
else
|
||||
snprintf(line, sizeof(line), "%.63s · %s", mi[i].name,
|
||||
pend ? "starting" : "failed");
|
||||
draw_ansi_at(s, y++, dx + 1, mw - (dx + 1 - x), line, CP_SIDE_MUTED, 0,
|
||||
0);
|
||||
}
|
||||
y++;
|
||||
}
|
||||
|
||||
mod_file_t mods[16];
|
||||
int nm = collect_modified(t, mods, 16);
|
||||
if (nm > 0 && y + 2 < bottom) {
|
||||
draw_ansi_at(s, y++, x, mw, "Modified Files", CP_SIDE_BRIGHT, 1, 0);
|
||||
if (nm > 5)
|
||||
nm = 5;
|
||||
if (nm > 0 && y + 1 < bottom) {
|
||||
if (!work_hdr) {
|
||||
draw_ansi_at(s, y++, x, mw, "work", CP_SIDE_MUTED, 0, 0);
|
||||
work_hdr = 1;
|
||||
}
|
||||
for (int i = 0; i < nm && y < bottom; i++) {
|
||||
char stat[48];
|
||||
snprintf(stat, sizeof(stat), "+%d -%d", mods[i].add, mods[i].del);
|
||||
int sw = (int)strlen(stat);
|
||||
draw_ansi_at(s, y, x, mw - sw - 1, mods[i].path, CP_SIDE_MUTED, 0, 0);
|
||||
char pl[16], mi[16];
|
||||
snprintf(pl, sizeof(pl), "+%d", mods[i].add);
|
||||
snprintf(mi, sizeof(mi), "-%d", mods[i].del);
|
||||
int sw = (int)strlen(pl) + 1 + (int)strlen(mi);
|
||||
draw_ansi_at(s, y, x, mw - sw - 1, mods[i].path, CP_SIDE_MUTED, 0, 0);
|
||||
int px = x + mw - sw;
|
||||
px += draw_ansi_at(s, y, px, (int)strlen(pl), pl, CP_SIDE_SUCCESS, 0, 0);
|
||||
draw_ansi_at(s, y, px + 1, (int)strlen(mi), mi, CP_SIDE_PRIMARY, 0, 0);
|
||||
@@ -2617,10 +2726,8 @@ static void draw_side(tui_t *t) {
|
||||
|
||||
if (h >= 4) {
|
||||
draw_ansi_at(s, h - 3, x, mw, t->where, CP_SIDE_MUTED, 0, 0);
|
||||
int dx = x + draw_ansi_at(s, h - 2, x, 2, g_ascii ? "*" : "●",
|
||||
CP_SIDE_SUCCESS, 0, 0);
|
||||
dx += draw_ansi_at(s, h - 2, dx + 1, mw, "CST-Agent", CP_SIDE_BRIGHT, 1, 0);
|
||||
draw_ansi_at(s, h - 2, dx + 2, mw - dx, CST_VERSION, CP_SIDE_MUTED, 0, 0);
|
||||
snprintf(line, sizeof(line), "CST %s", CST_VERSION);
|
||||
draw_ansi_at(s, h - 2, x, mw, line, CP_SIDE_MUTED, 0, 0);
|
||||
}
|
||||
wrefresh(s);
|
||||
}
|
||||
@@ -3117,7 +3224,7 @@ static void layout(tui_t *t) {
|
||||
t->in_x = (t->cols - t->in_w) / 2;
|
||||
} else {
|
||||
if (t->show_side && t->cols >= 110)
|
||||
t->side_w = clampi(t->cols / 4, 30, 40);
|
||||
t->side_w = clampi(t->cols / 5, 28, 36);
|
||||
int mw = t->cols - t->side_w;
|
||||
t->in_x = 1;
|
||||
t->in_w = mw - 2;
|
||||
@@ -4715,9 +4822,27 @@ static void cmd_sidebar(tui_t *t, char *arg, int *quit) {
|
||||
(void)quit;
|
||||
t->show_side = !t->show_side;
|
||||
t->need_layout = 1;
|
||||
if (t->show_side && t->cols < 120)
|
||||
push_system_line(t, "(the sidebar shows when the terminal is at least 120 "
|
||||
"columns wide)");
|
||||
if (t->show_side && t->cols < 110)
|
||||
push_system_line(t, "(the Seat Deck shows when the terminal is at least "
|
||||
"110 columns wide)");
|
||||
}
|
||||
|
||||
static void cmd_motion(tui_t *t, char *arg, int *quit) {
|
||||
(void)quit;
|
||||
if (arg && arg[0]) {
|
||||
if (!strcasecmp(arg, "on") || !strcmp(arg, "1"))
|
||||
t->motion_on = 1;
|
||||
else if (!strcasecmp(arg, "off") || !strcmp(arg, "0"))
|
||||
t->motion_on = 0;
|
||||
else {
|
||||
push_system_line(t, "Usage: /motion [on|off]");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
t->motion_on = !t->motion_on;
|
||||
}
|
||||
push_system_line(t, t->motion_on ? "(Seat Deck motion on)"
|
||||
: "(Seat Deck motion off)");
|
||||
}
|
||||
|
||||
static void cmd_mouse(tui_t *t, char *arg, int *quit) {
|
||||
@@ -5828,7 +5953,8 @@ static const slash_cmd_t SLASH[] = {
|
||||
{"approval", "", "[ask|auto|yolo]", "how tools get approved (Shift+Tab)",
|
||||
1, cmd_approval},
|
||||
{"yolo", "", "", "toggle approving everything", 1, cmd_yolo},
|
||||
{"sidebar", "", "", "show or hide the session sidebar", 1, cmd_sidebar},
|
||||
{"sidebar", "", "", "show or hide the Seat Deck sidebar", 1, cmd_sidebar},
|
||||
{"motion", "", "[on|off]", "Seat Deck ASCII micro-motion", 1, cmd_motion},
|
||||
{"mouse", "", "", "toggle mouse support (drag copies)", 1,
|
||||
cmd_mouse},
|
||||
{"max-steps", "", "[N]", "tool steps allowed per turn", 1, cmd_max_steps},
|
||||
@@ -7202,7 +7328,11 @@ int cst_tui_run(cst_agent_t *agent) {
|
||||
t.focus_think_idx = -1;
|
||||
t.hist_browse = -1;
|
||||
t.show_todo = 1;
|
||||
t.show_side = 0; /* /sidebar to open; keeps the main column calm by default */
|
||||
t.show_side = 1; /* Seat Deck; narrow terminals auto-hide in layout() */
|
||||
{
|
||||
const char *mo = getenv("CST_ASCII_MOTION");
|
||||
t.motion_on = !(mo && mo[0] == '0');
|
||||
}
|
||||
{
|
||||
cst_settings_t st;
|
||||
cst_settings_load(&st);
|
||||
@@ -7307,6 +7437,8 @@ int cst_tui_run(cst_agent_t *agent) {
|
||||
t.spin++;
|
||||
draw_status(&t);
|
||||
draw_input(&t);
|
||||
if (t.motion_on && t.side)
|
||||
draw_side(&t);
|
||||
}
|
||||
|
||||
if (turn_done) {
|
||||
@@ -7336,8 +7468,13 @@ int cst_tui_run(cst_agent_t *agent) {
|
||||
|
||||
wint_t wch = 0;
|
||||
int rc = tui_wget_wch(t.input, &wch);
|
||||
if (rc == ERR)
|
||||
if (rc == ERR) {
|
||||
if (!busy && t.motion_on && t.side && !t.home) {
|
||||
t.spin++;
|
||||
draw_side(&t);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (t.sel_state == SEL_SHOWN &&
|
||||
!(rc == KEY_CODE_YES && (wch == KEY_MOUSE || wch == KEY_RESIZE)))
|
||||
sel_clear(&t);
|
||||
|
||||
Reference in New Issue
Block a user