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 +1 -1
- package/src/dashboard.ts +8 -6
- package/src/gui.ts +1 -1
- package/src/index.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-straw-boss",
|
|
3
|
-
"version": "0.5.
|
|
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
|
-
|
|
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.
|