pi-squire 0.18.3 → 0.19.0
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 +26 -1
- package/README.md +12 -10
- package/package.json +5 -5
- package/src/index.ts +6 -6
- package/src/navigator.ts +1 -0
- package/src/new-delegate.ts +1 -0
- package/src/render.ts +4 -4
- package/src/tree-notice.ts +30 -22
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,29 @@ follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.19.0] - 2026-11-02
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **Breaking:** Pi 1.5.0 or later is now required. Delegate result expand hints use Pi's
|
|
14
|
+
configured keybinding, and the inline delegate dashboard and new-delegate
|
|
15
|
+
settings form report that Pi is waiting for a choice. The render tests now
|
|
16
|
+
require the configured key in the hint instead of accepting a missing key;
|
|
17
|
+
dashboard and form tests pin their blocked-input status. The active-tools
|
|
18
|
+
test fake now preserves retained tools' order, matching Pi 1.5.
|
|
19
|
+
|
|
20
|
+
## [0.18.4] - 2026-11-02
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Fresh-session owner discovery no longer warns that tmux could not be asked
|
|
25
|
+
when tmux answered but a busy startup held its answer past the 200 ms notice
|
|
26
|
+
budget. Discovery now runs after `session_start` returns instead of holding
|
|
27
|
+
it, the tmux listing is bounded by the tmux client's own timeout, and the
|
|
28
|
+
200 ms budget covers only the registry scan after it. The warning now carries
|
|
29
|
+
tmux's error message, and the newest-tree sort reads each registry's
|
|
30
|
+
modification time once. Tests that read discovery notices now wait for them.
|
|
31
|
+
|
|
9
32
|
## [0.18.3] - 2026-11-01
|
|
10
33
|
|
|
11
34
|
### Changed
|
|
@@ -1060,7 +1083,9 @@ author's dotfiles; the changes below are against that copy.
|
|
|
1060
1083
|
|
|
1061
1084
|
- The published file list includes `child.ts`, which `index.ts` imports.
|
|
1062
1085
|
|
|
1063
|
-
[Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.
|
|
1086
|
+
[Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.19.0...HEAD
|
|
1087
|
+
[0.19.0]: https://github.com/gvanderclay/pi-squire/compare/v0.18.4...v0.19.0
|
|
1088
|
+
[0.18.4]: https://github.com/gvanderclay/pi-squire/compare/v0.18.3...v0.18.4
|
|
1064
1089
|
[0.18.3]: https://github.com/gvanderclay/pi-squire/compare/v0.18.2...v0.18.3
|
|
1065
1090
|
[0.18.2]: https://github.com/gvanderclay/pi-squire/compare/v0.18.1...v0.18.2
|
|
1066
1091
|
[0.18.1]: https://github.com/gvanderclay/pi-squire/compare/v0.18.0...v0.18.1
|
package/README.md
CHANGED
|
@@ -10,12 +10,11 @@ answers with one message, which the parent session shows as a result.
|
|
|
10
10
|
|
|
11
11
|
You need:
|
|
12
12
|
|
|
13
|
-
- Pi 1.
|
|
14
|
-
pi-squire is tested with. It provides
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
`typebox`.
|
|
13
|
+
- Pi 1.5.0 or later, on macOS or Linux (not Windows). Pi 1.5.0 is the version
|
|
14
|
+
pi-squire is tested with. It provides configured keybinding hints for delegate
|
|
15
|
+
results, blocked-input status for inline choice screens, and the extension
|
|
16
|
+
APIs the package relies on. Pi supplies the peer dependencies
|
|
17
|
+
`@earendil-works/pi-coding-agent` and `typebox`.
|
|
19
18
|
- tmux 3.2 or later, with the Pi session running in a tmux pane. That is the
|
|
20
19
|
first version with `new-session -e`. Windows are placed
|
|
21
20
|
relative to the pane's session (see [Where delegate windows
|
|
@@ -329,10 +328,13 @@ this agent directory's newest **64** registry files, and only entries using its
|
|
|
329
328
|
current session-storage directory. It reports at most **10** direct owners with
|
|
330
329
|
positively matched live windows and explains reopening the owning session (or
|
|
331
330
|
returning to its existing owner). Registry-content reads are limited to 1 MiB
|
|
332
|
-
per tree and one **200 ms total cooperative notice budget**,
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
331
|
+
per tree and one **200 ms total cooperative notice budget**, which starts once
|
|
332
|
+
tmux has listed its windows, with registry lock waits capped at the remaining
|
|
333
|
+
budget or 20 ms. Discovery runs after `session_start` returns, so it never
|
|
334
|
+
delays startup or a session switch; the tmux listing is bounded by the tmux
|
|
335
|
+
client's own timeout, and a session that ends first reports nothing. The scan
|
|
336
|
+
yields between trees and reports, once, a tmux failure with its message,
|
|
337
|
+
unreadable owner records or truncated scope. Individual filesystem operations cannot be preempted.
|
|
336
338
|
Directory names/mtimes are enumerated, but no recursive
|
|
337
339
|
session search, mailbox search or other agent-directory search occurs. Exact-id
|
|
338
340
|
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.
|
|
3
|
+
"version": "0.19.0",
|
|
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",
|
|
@@ -45,15 +45,15 @@
|
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@earendil-works/pi-ai": "*",
|
|
48
|
-
"@earendil-works/pi-coding-agent": ">=1.
|
|
48
|
+
"@earendil-works/pi-coding-agent": ">=1.5.0",
|
|
49
49
|
"@earendil-works/pi-tui": "*",
|
|
50
50
|
"typebox": "*"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@biomejs/biome": "2.5.14",
|
|
54
|
-
"@earendil-works/pi-ai": "1.
|
|
55
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
56
|
-
"@earendil-works/pi-tui": "1.
|
|
54
|
+
"@earendil-works/pi-ai": "1.5.0",
|
|
55
|
+
"@earendil-works/pi-coding-agent": "1.5.0",
|
|
56
|
+
"@earendil-works/pi-tui": "1.5.0",
|
|
57
57
|
"@types/node": "26.6.5",
|
|
58
58
|
"pi-session-mail": "0.6.0",
|
|
59
59
|
"typebox": "1.4.3",
|
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
|
-
|
|
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
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
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 = {
|
package/src/navigator.ts
CHANGED
package/src/new-delegate.ts
CHANGED
|
@@ -441,6 +441,7 @@ export async function startNewDelegate(ctx: ExtensionContext, deps: NewDelegateD
|
|
|
441
441
|
cwdProblem: (value: string): string | undefined => cwdProblem(value, ctx.cwd),
|
|
442
442
|
done,
|
|
443
443
|
}),
|
|
444
|
+
{ blocked: { kind: "question", message: "Choose delegate settings" } },
|
|
444
445
|
);
|
|
445
446
|
if (chosen === undefined) return;
|
|
446
447
|
await deps.start({ agent: agent.name, task, ...(label === undefined ? {} : { label }), ...chosen }, ctx);
|
package/src/render.ts
CHANGED
|
@@ -9,7 +9,6 @@ import { homedir } from "node:os";
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
getMarkdownTheme,
|
|
12
|
-
keyHint,
|
|
13
12
|
type MessageRenderer,
|
|
14
13
|
type MessageRenderOptions,
|
|
15
14
|
type Theme,
|
|
@@ -178,7 +177,7 @@ function idLines(details: MessageDetails): string[] {
|
|
|
178
177
|
*/
|
|
179
178
|
export function renderMessage(
|
|
180
179
|
message: Parameters<MessageRenderer>[0],
|
|
181
|
-
{ expanded, outputPad }: MessageRenderOptions,
|
|
180
|
+
{ expanded, outputPad, keyHint }: MessageRenderOptions,
|
|
182
181
|
theme: Theme,
|
|
183
182
|
): Component | undefined {
|
|
184
183
|
const details = message.details as MessageDetails | undefined;
|
|
@@ -192,7 +191,8 @@ export function renderMessage(
|
|
|
192
191
|
box.addChild(expanded ? new Text(header, 0, 0) : new TruncatedText(header));
|
|
193
192
|
if (details.kind === "result" && details.limit !== undefined)
|
|
194
193
|
box.addChild(new Text(theme.fg("warning", details.limit), 0, 0));
|
|
195
|
-
for (const child of expanded ? expandedLines(details, theme) : collapsedLines(details, theme))
|
|
194
|
+
for (const child of expanded ? expandedLines(details, theme) : collapsedLines(details, theme, keyHint))
|
|
195
|
+
box.addChild(child);
|
|
196
196
|
return box;
|
|
197
197
|
}
|
|
198
198
|
|
|
@@ -209,7 +209,7 @@ function headerLine(details: MessageDetails, status: string, theme: Theme): stri
|
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
/** A result's first lines, a count of the rest, and the expand hint. */
|
|
212
|
-
function collapsedLines(details: MessageDetails, theme: Theme): Component[] {
|
|
212
|
+
function collapsedLines(details: MessageDetails, theme: Theme, keyHint: MessageRenderOptions["keyHint"]): Component[] {
|
|
213
213
|
const children: Component[] = [];
|
|
214
214
|
if (details.kind === "result") {
|
|
215
215
|
const lines = plainLines(details.body);
|
package/src/tree-notice.ts
CHANGED
|
@@ -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
|
|
23
|
+
const newest: string[] = newestRoots(roots);
|
|
24
24
|
if (!newest.length) return;
|
|
25
|
-
const windows: TmuxWindow[] | undefined = await noticeWindows(tmux,
|
|
26
|
-
if (!active()) return;
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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 {
|