pi-straw-boss 0.5.1 → 0.5.2

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
  {
2
2
  "name": "pi-straw-boss",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Straw Boss coordination for pi in Herdr: sessions join a tab's net by choice, the first joiner is its main agent, a workspace tower opens on demand with a live monitoring dashboard, and boss_broadcast reaches the rest",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/dashboard.ts CHANGED
@@ -18,10 +18,10 @@ import { headOf, type HerdrRun, isSub, type Pane, panes, parentOf, type Session,
18
18
  export type Mark = "working" | "waiting" | "idle" | "done" | "error" | "unknown";
19
19
  /** One line of the tree. `net` rows are tab (0) > session (1) > subagent (2); `towers` rows are flat. */
20
20
  export interface Row { group: "net" | "towers"; depth: number; mark: Mark; text: string; kind?: string; note?: string; workspace?: string; id?: string; file?: string; pane?: string }
21
- export interface Snapshot { title: string; mark: Mark; head: boolean; rows: Row[] }
21
+ export interface Snapshot { title: string; mark: Mark; head: boolean; rows: Row[]; gui?: string }
22
22
  export type Tone = "accent" | "warning" | "muted" | "success" | "dim" | "error" | "border" | "title";
23
23
  export type Paint = (tone: Tone, text: string) => string;
24
- export interface Sources { run: HerdrRun; sessions(): Promise<Session[]>; workspaceId: string; now?: number }
24
+ export interface Sources { run: HerdrRun; sessions(): Promise<Session[]>; workspaceId: string; now?: number; gui?(): Promise<string | undefined> }
25
25
 
26
26
  const KEY = "straw-boss-dashboard";
27
27
  const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
@@ -43,7 +43,7 @@ export function sessionMark(s: Session, pane?: Pane): Mark {
43
43
  const pad = (text: string, width: number) => text + " ".repeat(Math.max(0, width - visibleWidth(text)));
44
44
 
45
45
  /** The pure half: a snapshot in, a framed tree out. One shared spinner frame animates every working row. */
46
- export function render({ title, mark, head, rows }: Snapshot, frame: number, width: number, paint: Paint): string[] {
46
+ export function render({ title, mark, head, rows, gui }: Snapshot, frame: number, width: number, paint: Paint): string[] {
47
47
  const total = Math.min(width, MAX_WIDTH), inner = Math.max(8, total - 4);
48
48
  const glyph = (m: Mark) => paint(TONE[m], m === "working" ? SPINNER[frame % SPINNER.length]! : GLYPH[m as Exclude<Mark, "working">]);
49
49
  const shown = rows.slice(0, MAX_ROWS);
@@ -58,7 +58,9 @@ export function render({ title, mark, head, rows }: Snapshot, frame: number, wid
58
58
  const at = shown.flatMap((r, i) => (r.group === group ? [i] : []));
59
59
  return at.length || empty ? [paint("muted", name), ...(at.length ? at.map(line) : [paint("dim", ` ${empty}`)])] : [];
60
60
  };
61
- const body = [...section("net", "this net", "nothing has joined yet"), ...section("towers", "other towers"), ...(rows.length > MAX_ROWS ? [paint("muted", `… ${rows.length - MAX_ROWS} more`)] : [])];
61
+ // What this pane is, and where the browser view lives, come before the tree.
62
+ const intro = [paint("dim", "Control tower: main agents talk here (call_tower)."), gui ? `${paint("muted", "GUI")} ${paint("accent", gui)}` : paint("dim", "GUI not running; /boss gui opens it"), ""];
63
+ const body = [...intro, ...section("net", "this net", "nothing has joined yet"), ...section("towers", "other towers"), ...(rows.length > MAX_ROWS ? [paint("muted", `… ${rows.length - MAX_ROWS} more`)] : [])];
62
64
  const [name, tag] = [` ${glyph(mark)} ${paint("title", `tower · ${title}`)} `, head ? ` ${paint("accent", "head tower")} ` : ""];
63
65
  const dashes = "─".repeat(Math.max(1, total - 4 - visibleWidth(name) - visibleWidth(tag)));
64
66
  const frameLine = (text: string) => paint("border", "│ ") + pad(truncateToWidth(text, inner), inner) + paint("border", " │");
@@ -114,7 +116,7 @@ export function subagents(parent: Pane | undefined, all: Pane[], now: number): R
114
116
  * One reading of the world as rows: this tower's own net in detail (each tab's joined sessions with their subagents, then its
115
117
  * sub workspaces), and one summary line for every other tower. Other workspaces' sessions, and sessions that did not join, are not listed.
116
118
  */
117
- export async function collect({ run, sessions, workspaceId, now = Date.now() }: Sources): Promise<Snapshot> {
119
+ export async function collect({ run, sessions, workspaceId, now = Date.now(), gui }: Sources): Promise<Snapshot> {
118
120
  const [live, ps, all] = [await sessions(), await panes(run), await workspaces(run)];
119
121
  const head = headOf(all);
120
122
  const mine = all.find((w) => w.workspace_id === workspaceId);
@@ -139,7 +141,7 @@ export async function collect({ run, sessions, workspaceId, now = Date.now() }:
139
141
  for (const w of all.filter((x) => x !== mine && !isSub(x) && live.some((s) => s.id === x.tokens?.tower))) rows.push({ group: "towers", depth: 0, mark: markOf(w.agent_status), text: w.label, kind: w === head ? "head tower" : "tower" });
140
142
  // The title shows what needs attention most: a waiting row, then a working one, else the workspace's own status.
141
143
  const attention = (["waiting", "working"] as const).find((m) => rows.some((r) => r.mark === m));
142
- return { title: mine?.label ?? workspaceId, mark: attention ?? markOf(mine?.agent_status), head: Boolean(mine) && mine === head, rows };
144
+ return { title: mine?.label ?? workspaceId, mark: attention ?? markOf(mine?.agent_status), head: Boolean(mine) && mine === head, rows, gui: await gui?.().catch(() => undefined) };
143
145
  }
144
146
 
145
147
  export interface Dashboard { poke(): void; stop(): void }
package/src/gui.ts CHANGED
@@ -150,7 +150,7 @@ export async function startGui({ read, net, call, dir = BUNDLE, port = 0, token
150
150
  // instead of starting one. A dead server's port and token are reused, so a page left open reconnects to its successor.
151
151
  export interface GuiRecord { pid: number; port: number; token: string; csrf?: string; owner: string }
152
152
  export interface Shared { url: string; owner: string; mine?: Gui }
153
- const urlOf = (r: GuiRecord) => `http://127.0.0.1:${r.port}/${r.token}/`;
153
+ export const urlOf = (r: GuiRecord) => `http://127.0.0.1:${r.port}/${r.token}/`;
154
154
 
155
155
  export function readRecord(dir: string): GuiRecord | undefined {
156
156
  try {
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url";
5
5
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
6
6
  import { Type, type Static, type TSchema } from "typebox";
7
7
  import { collect, startDashboard } from "./dashboard.ts";
8
- import { answers, type Gui, openBrowser, readRecord, sharedGui, startGui } from "./gui.ts";
8
+ import { answers, type Gui, openBrowser, readRecord, sharedGui, startGui, urlOf } from "./gui.ts";
9
9
  import { netSource } from "./net.ts";
10
10
  import { copyText, pairingKeys, showLines } from "./pairing.ts";
11
11
  import { type Run, switchRemote, tailscale } from "./remote.ts";
@@ -316,7 +316,7 @@ export function createStrawBoss({ run = herdrRun, api = herdrApi(), call = herdr
316
316
  keepFresh();
317
317
  await publish();
318
318
  // The dashboard lives exactly as long as this session holds the tower role.
319
- if (hasUI) dashboard = startDashboard({ ui: ui!, collect: () => collect({ run, sessions: () => c.listSessions(), workspaceId: at.workspaceId }) });
319
+ if (hasUI) dashboard = startDashboard({ ui: ui!, collect: () => collect({ run, sessions: () => c.listSessions(), workspaceId: at.workspaceId, gui: async () => { const r = readRecord(guiDir); return r && (await answers(r)) ? urlOf(r) : undefined; } }) });
320
320
  };
321
321
 
322
322
  // A session in a sub workspace (a linked git worktree) acts like a subagent: it never joins. Failing to tell bars it too.