pi-straw-boss 0.9.0 → 0.9.1
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/herdr.ts +21 -7
- package/src/index.ts +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-straw-boss",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
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/herdr.ts
CHANGED
|
@@ -164,14 +164,24 @@ async function open({ run, sleep, live, moveFirst, model }: TowerEnv, workspace:
|
|
|
164
164
|
|
|
165
165
|
// A leftover tower tab is reused: a pane already running pi only needs its token awaited, a shell prompt gets pi started.
|
|
166
166
|
// The tab is told apart by the `sb_role=tower` pane token that only Straw Boss writes; a tab merely labelled "tower"
|
|
167
|
-
// (a pi-herdr-agents subagent can be named so) is someone else's.
|
|
167
|
+
// (a pi-herdr-agents subagent can be named so) is someone else's. A tab whose token was cleared (its tower quit) is still ours when it has
|
|
168
|
+
// exactly the tower label (the glyph), one pane, and no agent in it.
|
|
168
169
|
let paneId: string;
|
|
169
170
|
let running = false;
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
const here = await panes(run, ws);
|
|
172
|
+
const marked = here.find((p) => p.tokens?.sb_role === "tower");
|
|
173
|
+
const bare = marked ? [] : (await tabs(run, ws)).filter((t) => t.label === TOWER_LABEL).flatMap((t) => {
|
|
174
|
+
const inside = here.filter((p) => p.tab_id === t.tab_id);
|
|
175
|
+
return inside.length === 1 && !inside[0]!.agent && !inside[0]!.tokens?.sb_role ? [inside[0]!] : [];
|
|
176
|
+
});
|
|
177
|
+
const reused = marked ?? bare[0];
|
|
178
|
+
if (reused) {
|
|
179
|
+
paneId = reused.pane_id;
|
|
180
|
+
running = reused.agent === "pi";
|
|
181
|
+
if (!marked) await report(run, "pane", paneId, { sb_role: "tower" });
|
|
182
|
+
await moveFirst(reused.tab_id);
|
|
183
|
+
// Other shell-only leftovers are closed; failing to close one leaves a harmless tab, so it does not stop the tower.
|
|
184
|
+
for (const extra of bare.slice(1)) await run(["tab", "close", extra.tab_id]).catch(() => {});
|
|
175
185
|
} else {
|
|
176
186
|
const dir = workspace.worktree?.checkout_path ?? cwd;
|
|
177
187
|
const created = await createTab(run, ws, dir, ["STRAW_BOSS_ROLE=tower"], TOWER_LABEL);
|
|
@@ -182,7 +192,11 @@ async function open({ run, sleep, live, moveFirst, model }: TowerEnv, workspace:
|
|
|
182
192
|
}
|
|
183
193
|
if (!running) {
|
|
184
194
|
// Herdr agent names are unique across workspaces and lowercase only, so the name carries the lowercased workspace id.
|
|
185
|
-
|
|
195
|
+
// An adopted shell that turns out busy (the user runs something there) gives the token back, so the next open does not count it as the tower.
|
|
196
|
+
await startPi(run, sleep, `tower-${ws.toLowerCase()}`, paneId, ["--name", `tower · ${workspace.label}`, ...(model ? ["--model", model] : [])]).catch(async (error: unknown) => {
|
|
197
|
+
if (reused && !marked) await report(run, "pane", paneId, { sb_role: null }).catch(() => {});
|
|
198
|
+
throw error;
|
|
199
|
+
});
|
|
186
200
|
}
|
|
187
201
|
const id = await waitFor(sleep, held, TOWER_WAIT_MS, 1000);
|
|
188
202
|
if (!id) throw new Error(`The tower of ${workspace.label} did not publish its token within ${TOWER_WAIT_MS / 1000} s (pane ${paneId})`);
|
package/src/index.ts
CHANGED
|
@@ -441,7 +441,7 @@ export function createStrawBoss({ run = herdrRun, api = herdrApi(), call = herdr
|
|
|
441
441
|
if (["subagent", "subagent_resume"].includes(event.toolName) && !isTower && !barred && role()) for (const ms of KID_MARK_MS) setTimeout(() => void publish(), ms).unref();
|
|
442
442
|
});
|
|
443
443
|
|
|
444
|
-
pi.on("session_shutdown", async () => {
|
|
444
|
+
pi.on("session_shutdown", async (event) => {
|
|
445
445
|
if (gui) await switchRemote(gui, false, guiDir, ts).catch(() => {});
|
|
446
446
|
await gui?.stop();
|
|
447
447
|
gui = undefined;
|
|
@@ -459,6 +459,12 @@ export function createStrawBoss({ run = herdrRun, api = herdrApi(), call = herdr
|
|
|
459
459
|
await report(run, "pane", at.paneId, { sb_role: null, sb_opened_by: null, sb_order: null, sb_mark: null, sb_indent: null, sb_tab: null }).catch(() => {});
|
|
460
460
|
await clearOrder(run, [...kids]);
|
|
461
461
|
if (held) await report(run, "workspace", at.workspaceId, { tower: null }).catch(() => {});
|
|
462
|
+
// A tower that quits leaves its tab as a plain shell: close the tab, but only when this pane is all it holds. A reload, /new, resume or fork keeps the tab.
|
|
463
|
+
if (isTower && event.reason === "quit") {
|
|
464
|
+
const all = await panes(run, at.workspaceId).catch(() => []);
|
|
465
|
+
const tab = all.find((p) => p.pane_id === at.paneId)?.tab_id;
|
|
466
|
+
if (tab && all.every((p) => p.tab_id !== tab || p.pane_id === at.paneId)) await run(["tab", "close", tab]).catch(() => {});
|
|
467
|
+
}
|
|
462
468
|
});
|
|
463
469
|
|
|
464
470
|
// /boss gui is a read-only viewer: any session may open it, and it joins nothing and writes no token. A session outside the net
|