pi-squire 0.18.3 → 0.18.4

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/CHANGELOG.md CHANGED
@@ -6,6 +6,18 @@ follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.18.4] - 2026-11-02
10
+
11
+ ### Fixed
12
+
13
+ - Fresh-session owner discovery no longer warns that tmux could not be asked
14
+ when tmux answered but a busy startup held its answer past the 200 ms notice
15
+ budget. Discovery now runs after `session_start` returns instead of holding
16
+ it, the tmux listing is bounded by the tmux client's own timeout, and the
17
+ 200 ms budget covers only the registry scan after it. The warning now carries
18
+ tmux's error message, and the newest-tree sort reads each registry's
19
+ modification time once. Tests that read discovery notices now wait for them.
20
+
9
21
  ## [0.18.3] - 2026-11-01
10
22
 
11
23
  ### Changed
@@ -1060,7 +1072,8 @@ author's dotfiles; the changes below are against that copy.
1060
1072
 
1061
1073
  - The published file list includes `child.ts`, which `index.ts` imports.
1062
1074
 
1063
- [Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.18.3...HEAD
1075
+ [Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.18.4...HEAD
1076
+ [0.18.4]: https://github.com/gvanderclay/pi-squire/compare/v0.18.3...v0.18.4
1064
1077
  [0.18.3]: https://github.com/gvanderclay/pi-squire/compare/v0.18.2...v0.18.3
1065
1078
  [0.18.2]: https://github.com/gvanderclay/pi-squire/compare/v0.18.1...v0.18.2
1066
1079
  [0.18.1]: https://github.com/gvanderclay/pi-squire/compare/v0.18.0...v0.18.1
package/README.md CHANGED
@@ -329,10 +329,13 @@ this agent directory's newest **64** registry files, and only entries using its
329
329
  current session-storage directory. It reports at most **10** direct owners with
330
330
  positively matched live windows and explains reopening the owning session (or
331
331
  returning to its existing owner). Registry-content reads are limited to 1 MiB
332
- per tree and one **200 ms total cooperative notice budget**, including the tmux
333
- wait, with registry lock waits capped at the remaining budget or 20 ms. The scan
334
- yields between trees and reports unavailable tmux, unreadable owner records or
335
- truncated scope once. Individual filesystem operations cannot be preempted.
332
+ per tree and one **200 ms total cooperative notice budget**, which starts once
333
+ tmux has listed its windows, with registry lock waits capped at the remaining
334
+ budget or 20 ms. Discovery runs after `session_start` returns, so it never
335
+ delays startup or a session switch; the tmux listing is bounded by the tmux
336
+ client's own timeout, and a session that ends first reports nothing. The scan
337
+ yields between trees and reports, once, a tmux failure with its message,
338
+ unreadable owner records or truncated scope. Individual filesystem operations cannot be preempted.
336
339
  Directory names/mtimes are enumerated, but no recursive
337
340
  session search, mailbox search or other agent-directory search occurs. Exact-id
338
341
  restoration is not restricted by this notice budget.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-squire",
3
- "version": "0.18.3",
3
+ "version": "0.18.4",
4
4
  "description": "Starts a delegate Pi session in a background tmux window, sends it the task over message:*, and shows the parent its result",
5
5
  "keywords": [
6
6
  "pi-package",
package/src/index.ts CHANGED
@@ -741,13 +741,13 @@ export default function delegate(
741
741
  pi.on("session_start", (_event, ctx) => {
742
742
  viewContext = ctx;
743
743
  });
744
- pi.on("session_start", async (_event, ctx) => {
744
+ // Discovery is advisory: it runs after session_start returns, so tmux never delays startup or a session switch.
745
+ pi.on("session_start", (_event, ctx) => {
745
746
  if (!identity.current() && results.list().length === 0) {
746
- try {
747
- await treeNotice(ctx, tmux, () => owner.active());
748
- } catch (error) {
749
- ctx.ui.notify(`delegate: owner discovery unavailable: ${String(error)}`, "warning");
750
- }
747
+ const active = () => owner.active();
748
+ treeNotice(ctx, tmux, active).catch((error: unknown) => {
749
+ if (active()) ctx.ui.notify(`delegate: owner discovery unavailable: ${String(error)}`, "warning");
750
+ });
751
751
  }
752
752
  });
753
753
  const viewResults = {
@@ -1,4 +1,6 @@
1
1
  // Bounded discovery is a notice only. Exact-session restore never uses this budget.
2
+ // Callers start it without awaiting, so a slow tmux never holds session_start; the
3
+ // tmux client's own timeout bounds the listing, and the budget bounds only the scan.
2
4
  import { statSync } from "node:fs";
3
5
  import { performance } from "node:perf_hooks";
4
6
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
@@ -6,7 +8,6 @@ import { findSession } from "./result-sessions.ts";
6
8
  import { ownerDetails, sessionIsOwned } from "./session-owner.ts";
7
9
  import type { TmuxClient, TmuxWindow } from "./tmux.ts";
8
10
  import { listTreeRoots, readTree, type TreeEntry, treePath } from "./tree-registry.ts";
9
- import { listWindows } from "./windows.ts";
10
11
 
11
12
  export const NOTICE_TREES = 64;
12
13
  export const NOTICE_OWNERS = 10;
@@ -16,21 +17,15 @@ export async function treeNotice(
16
17
  tmux: TmuxClient,
17
18
  active: () => boolean = () => true,
18
19
  ): Promise<void> {
19
- const deadline: number = performance.now() + NOTICE_MS;
20
20
  const session: string = ctx.sessionManager.getSessionId();
21
21
  const dir: string = ctx.sessionManager.getSessionDir();
22
22
  const roots: string[] = listTreeRoots().filter((root) => root !== session);
23
- const newest: string[] = roots.sort((a, b) => modified(b) - modified(a)).slice(0, NOTICE_TREES);
23
+ const newest: string[] = newestRoots(roots);
24
24
  if (!newest.length) return;
25
- const windows: TmuxWindow[] | undefined = await noticeWindows(tmux, deadline);
26
- if (!active()) return;
27
- if (!windows) {
28
- ctx.ui.notify(
29
- "delegate: owner discovery unavailable: tmux could not be asked within the total notice budget; no absence or ownership was inferred.",
30
- "warning",
31
- );
32
- return;
33
- }
25
+ const windows: TmuxWindow[] | undefined = await noticeWindows(ctx, tmux, active);
26
+ if (!windows || !active()) return;
27
+ // The scan budget starts once tmux has answered.
28
+ const deadline: number = performance.now() + NOTICE_MS;
34
29
  const owners: Map<string, TreeEntry> = new Map();
35
30
  let unavailable = 0,
36
31
  scanned = 0,
@@ -73,17 +68,30 @@ function reportOwners(ctx: ExtensionContext, dir: string, owners: Map<string, Tr
73
68
  }
74
69
  return ownerErrors;
75
70
  }
76
- async function noticeWindows(tmux: TmuxClient, deadline: number): Promise<TmuxWindow[] | undefined> {
77
- let timer: ReturnType<typeof setTimeout> | undefined;
71
+ /** The newest roots first; each tree is stat'ed once, not on every sort comparison. */
72
+ function newestRoots(roots: string[]): string[] {
73
+ return roots
74
+ .map((root) => ({ root, mtime: modified(root) }))
75
+ .sort((a, b) => b.mtime - a.mtime)
76
+ .slice(0, NOTICE_TREES)
77
+ .map(({ root }) => root);
78
+ }
79
+ /** tmux's windows, or undefined after warning (while still active) that tmux failed. */
80
+ async function noticeWindows(
81
+ ctx: ExtensionContext,
82
+ tmux: TmuxClient,
83
+ active: () => boolean,
84
+ ): Promise<TmuxWindow[] | undefined> {
78
85
  try {
79
- return await Promise.race([
80
- listWindows(tmux),
81
- new Promise<undefined>((resolve) => {
82
- timer = setTimeout(() => resolve(undefined), remaining(deadline));
83
- }),
84
- ]);
85
- } finally {
86
- if (timer) clearTimeout(timer);
86
+ return await tmux.listWindows();
87
+ } catch (error) {
88
+ if (!active()) return undefined;
89
+ const reason: string = error instanceof Error ? error.message : String(error);
90
+ ctx.ui.notify(
91
+ `delegate: owner discovery unavailable: tmux could not be asked: ${reason}; no absence or ownership was inferred.`,
92
+ "warning",
93
+ );
94
+ return undefined;
87
95
  }
88
96
  }
89
97
  function remaining(deadline: number): number {