@huseynsnmz/pi-board 0.14.0 → 0.15.3

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/src/index.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * launch background workers and the detached runner, and keeps a small footer status with
6
6
  * the count of sessions needing attention. See docs/EXPLORATION.md for the design.
7
7
  */
8
+ import "./core/load-config.mjs";
8
9
  import { fileURLToPath } from "node:url";
9
10
  import { createConnection } from "node:net";
10
11
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
@@ -15,8 +16,10 @@ import { createService, envInt } from "./runtime/service.mjs";
15
16
  import { attachWarmHostSweeper } from "./core/warm-host-sweeper.mjs";
16
17
  import { openDashboard, registerAgentBoardCommand } from "./commands/agent-board.js";
17
18
  import { registerBgCommand } from "./commands/bg.js";
19
+ import { registerBoardModelCommand } from "./commands/board-model.ts";
18
20
  import { announceBoardClosedOn } from "./ui/chrome.ts";
19
21
  import { watchDoubleLeft } from "./ui/double-left.ts";
22
+ import { watchWaiting } from "./ui/waiting.ts";
20
23
 
21
24
  const RUNNER_SCRIPT = fileURLToPath(new URL("../runner/job-runner.mjs", import.meta.url));
22
25
  const PTY_RUNNER_SCRIPT = fileURLToPath(new URL("../runner/pty-runner.mjs", import.meta.url));
@@ -40,6 +43,7 @@ let hostedSessionEpoch = 0;
40
43
  export default function piAgentBoard(pi: ExtensionAPI): void {
41
44
  announceBoardClosedOn(pi.events);
42
45
  let stopDoubleLeft: (() => void) | null = null;
46
+ let stopWaiting: (() => void) | null = null;
43
47
  const root = defaultRoot();
44
48
  const { piCommand, piArgsPrefix } = resolvePiInvocation();
45
49
 
@@ -85,6 +89,7 @@ export default function piAgentBoard(pi: ExtensionAPI): void {
85
89
 
86
90
  registerAgentBoardCommand(pi, commandOpts);
87
91
  registerBgCommand(pi, commandOpts);
92
+ registerBoardModelCommand(pi);
88
93
  pi.registerFlag("agent-board", {
89
94
  description: "Open the agent-board dashboard on startup",
90
95
  type: "boolean",
@@ -193,11 +198,23 @@ export default function piAgentBoard(pi: ExtensionAPI): void {
193
198
  hostedFrameWitness = { stop: () => cleanup("session-shutdown") };
194
199
  }
195
200
 
201
+ // After this the context they hold is stale, and using one takes Pi down.
202
+ pi.on("session_shutdown", () => {
203
+ stopDoubleLeft?.();
204
+ stopDoubleLeft = null;
205
+ stopWaiting?.();
206
+ stopWaiting = null;
207
+ });
208
+
196
209
  pi.on("session_start", async (event, ctx) => {
197
210
  if (!isHostedChild && ctx.mode === "tui") {
198
211
  stopDoubleLeft?.();
199
212
  stopDoubleLeft = watchDoubleLeft(ctx);
200
213
  }
214
+ if (ctx.mode === "tui") {
215
+ stopWaiting?.();
216
+ stopWaiting = watchWaiting(ctx, (waiting) => syncForeground({ type: "agent_board_waiting", waiting }, ctx));
217
+ }
201
218
  if (isHostedChild && !hostedEditorReporter && typeof ctx.ui?.getEditorText === "function" && hostedViewId) {
202
219
  const { createEditorStateReporter } = await import("./core/editor-state-reporter.mjs");
203
220
  hostedEditorReporter = createEditorStateReporter({
@@ -1377,7 +1377,8 @@ export function createService(opts) {
1377
1377
  const pty = ptySupport();
1378
1378
  if (!pty.ok) return { ok: false, error: "PTY unavailable", fallbackReason: nodePtyFallbackMessage(pty) };
1379
1379
  if (isAgentBusy(row)) return { ok: false, error: "A non-live background run is active for this session" };
1380
- if (!existsSync(row.meta.sessionFile)) return { ok: false, error: "Session file isn't ready yet" };
1380
+ // A missing file is a session that was put on the board before its first message. Pi starts
1381
+ // it at that path, as it would have.
1381
1382
 
1382
1383
  const lock = tryAcquireLockImpl(root, viewId, "host-start", { identity: serviceIdentity() });
1383
1384
  if (!lock.acquired) {
@@ -1809,6 +1810,16 @@ export function createService(opts) {
1809
1810
  appendDiagnostic(root, row.meta.id, { source: "evidence", level: "warn", code: "evidence_reduce_failed", message: "Could not reduce hosted evidence", details: { error: err instanceof Error ? err.message : String(err) } });
1810
1811
  }
1811
1812
 
1813
+ if (event.type === "agent_board_waiting") {
1814
+ status.semanticState = event.waiting ? "needs_input" : "working";
1815
+ status.processState = "alive";
1816
+ status.question = event.waiting ? "Waiting on a prompt. Attach to answer it." : null;
1817
+ status.summary = event.waiting ? "Waiting on a prompt" : "Running…";
1818
+ status.lastActivityAt = now;
1819
+ void writeForegroundState(row, status);
1820
+ return true;
1821
+ }
1822
+
1812
1823
  if (event.type === "input" || event.type === "before_agent_start" || event.type === "agent_start") {
1813
1824
  status.semanticState = "working";
1814
1825
  status.processState = "alive";
@@ -2293,6 +2304,22 @@ export function createService(opts) {
2293
2304
  return { ok: true };
2294
2305
  },
2295
2306
 
2307
+ /**
2308
+ * Gives these rows this order, numbered from the lowest they had: rows created in the same
2309
+ * millisecond have no order between them to swap.
2310
+ * @param {string[]} viewIds
2311
+ */
2312
+ setRowOrder(viewIds) {
2313
+ const metas = viewIds.map((id) => loadRow(root, id)?.meta);
2314
+ if (metas.some((meta) => !meta)) return { ok: false, error: "Unknown session" };
2315
+ const base = Math.min(...metas.map((meta) => meta.order ?? meta.createdAt ?? 0));
2316
+ metas.forEach((meta, index) => {
2317
+ meta.order = base + index;
2318
+ writeMeta(root, meta);
2319
+ });
2320
+ return { ok: true };
2321
+ },
2322
+
2296
2323
  /** @param {string} viewId @param {string} name */
2297
2324
  rename(viewId, name) {
2298
2325
  const clean = String(name || "").trim();