pi-squire 0.18.2 → 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 +26 -1
- package/README.md +24 -25
- package/package.json +1 -1
- package/src/index.ts +6 -6
- package/src/result-message.ts +5 -13
- 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.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
|
+
|
|
21
|
+
## [0.18.3] - 2026-11-01
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Model-facing delegate completion messages link the task copy instead of
|
|
26
|
+
quoting it, omit the workspace launch summary, and keep only the checkout
|
|
27
|
+
summary without path categories. Result bodies, errors, identity and file
|
|
28
|
+
pointers are unchanged. Task previews, workspace facts, and full snapshots
|
|
29
|
+
remain in saved message details and the expanded view. Tests that pinned the
|
|
30
|
+
previous model-facing wording now assert this shorter projection.
|
|
31
|
+
|
|
9
32
|
## [0.18.2] - 2026-11-01
|
|
10
33
|
|
|
11
34
|
### Fixed
|
|
@@ -1049,7 +1072,9 @@ author's dotfiles; the changes below are against that copy.
|
|
|
1049
1072
|
|
|
1050
1073
|
- The published file list includes `child.ts`, which `index.ts` imports.
|
|
1051
1074
|
|
|
1052
|
-
[Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.18.
|
|
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
|
|
1077
|
+
[0.18.3]: https://github.com/gvanderclay/pi-squire/compare/v0.18.2...v0.18.3
|
|
1053
1078
|
[0.18.2]: https://github.com/gvanderclay/pi-squire/compare/v0.18.1...v0.18.2
|
|
1054
1079
|
[0.18.1]: https://github.com/gvanderclay/pi-squire/compare/v0.18.0...v0.18.1
|
|
1055
1080
|
[0.18.0]: https://github.com/gvanderclay/pi-squire/compare/v0.17.0...v0.18.0
|
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ Then, from a Pi session running in tmux, hand it a task:
|
|
|
76
76
|
|
|
77
77
|
A detached tmux window named `scout-first` opens in your working directory.
|
|
78
78
|
When the delegate finishes, its answer reaches the parent as one `[delegate]`
|
|
79
|
-
message that
|
|
79
|
+
message that links the task copy (see [Results](#results)), and the delegate then
|
|
80
80
|
closes its own window ([auto-exit](#auto-exit)).
|
|
81
81
|
|
|
82
82
|
`/delegate` with no arguments opens a form instead: pick an agent, write the
|
|
@@ -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**,
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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.
|
|
@@ -1450,16 +1453,9 @@ the parent one message labelled delegate output:
|
|
|
1450
1453
|
```
|
|
1451
1454
|
[delegate] Result from scout (provider/model), delegation <id>, request <request id>.
|
|
1452
1455
|
Status: done
|
|
1453
|
-
Task
|
|
1454
|
-
> the task
|
|
1456
|
+
Task: <sent/ copy>
|
|
1455
1457
|
Working directory: <the directory its window opened in>
|
|
1456
|
-
Workspace: <how the directory was chosen and its Git checkout at launch>
|
|
1457
1458
|
Checkout when the result arrived: <branch, head, launch commit, commits since launch, clean or dirty>
|
|
1458
|
-
committed since launch: <paths>
|
|
1459
|
-
staged: <paths>
|
|
1460
|
-
unstaged: <paths>
|
|
1461
|
-
untracked: <paths>
|
|
1462
|
-
conflicted: <paths>
|
|
1463
1459
|
Envelope: <cur/ path>
|
|
1464
1460
|
Delegate session: <session file or id>
|
|
1465
1461
|
|
|
@@ -1501,19 +1497,22 @@ Delegate session: <session file or id>
|
|
|
1501
1497
|
delegations skip it.` (a model scope reads `on model provider/id`; a mark
|
|
1502
1498
|
that has since cleared reads `it was marked until …`). A failed reply with
|
|
1503
1499
|
no such mark is unchanged.
|
|
1504
|
-
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1500
|
+
- `Task:` points to the full request copy in `sent/`. The message details
|
|
1501
|
+
and expanded view retain a task preview capped at 2 KiB. A request with no
|
|
1502
|
+
copy gets a diagnostic with its request id.
|
|
1507
1503
|
- The working directory is the one recorded at launch, or
|
|
1508
|
-
`unknown (not recorded)` for a delegation recorded before it was kept.
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
- `Checkout when the result arrived:`
|
|
1513
|
-
taken as the reply was claimed
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1504
|
+
`unknown (not recorded)` for a delegation recorded before it was kept.
|
|
1505
|
+
The recorded [workspace](#working-directory) remains in message details
|
|
1506
|
+
and the expanded view, but the model-facing result omits its launch summary.
|
|
1507
|
+
Closed-without-a-result messages still include that summary when recorded.
|
|
1508
|
+
- `Checkout when the result arrived:` summarizes the
|
|
1509
|
+
[snapshot](#checkout-snapshots) taken as the reply was claimed. Git summaries
|
|
1510
|
+
keep the branch or detached HEAD, full head and launch commit, commits since
|
|
1511
|
+
launch, and working-tree state. Path categories remain in the saved snapshot,
|
|
1512
|
+
message details, and expanded view, not the model-facing result.
|
|
1513
|
+
The reply is claimed first, so a slow or failing Git cannot lose or duplicate
|
|
1514
|
+
it. An unknown snapshot includes the reason. A non-Git directory reads
|
|
1515
|
+
`not a Git checkout`.
|
|
1517
1516
|
- The envelope path is the claimed reply in the parent's `cur/`; the
|
|
1518
1517
|
delegate's session path is found by the delegation id in the session
|
|
1519
1518
|
directory recorded at launch (the parent's current one for older records),
|
package/package.json
CHANGED
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/result-message.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The text and details of the one message the parent receives for a settled delegation.
|
|
2
2
|
import { readMarks } from "./limits.ts";
|
|
3
|
-
import { cwdLine, modelLabel, snapshotLines
|
|
3
|
+
import { cwdLine, modelLabel, snapshotLines } from "./render.ts";
|
|
4
4
|
import { sessionIdOf } from "./result-sessions.ts";
|
|
5
5
|
import type { StartedRecorded } from "./result-types.ts";
|
|
6
6
|
import type { Inbound, MessageDetails, TaskQuote } from "./results.ts";
|
|
@@ -88,29 +88,21 @@ export function resultDetails(
|
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
/** The snapshot lines of the result text: a heading line, then its path lists indented. */
|
|
92
91
|
function checkoutText(snapshot: WorkspaceSnapshot): string[] {
|
|
93
|
-
const [first
|
|
94
|
-
return [`Checkout when the result arrived: ${first}
|
|
92
|
+
const [first] = snapshotLines(snapshot);
|
|
93
|
+
return [`Checkout when the result arrived: ${first}`];
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
/** The one message the parent model reads for a settled delegation. */
|
|
98
97
|
export function resultText(details: ResultDetails): string {
|
|
99
98
|
const { task } = details;
|
|
100
|
-
const
|
|
101
|
-
"note" in task
|
|
102
|
-
? task.note
|
|
103
|
-
: `Task, quoted from ${task.path}:\n${task.text
|
|
104
|
-
.split("\n")
|
|
105
|
-
.map((line) => `> ${line}`)
|
|
106
|
-
.join("\n")}`;
|
|
99
|
+
const taskLine = "note" in task ? task.note : `Task: ${task.path}`;
|
|
107
100
|
return [
|
|
108
101
|
`[delegate] Result from ${details.agent} (${modelLabel(details.model, details.finishedModel)}), delegation ${details.id}${details.resumedFrom === undefined ? "" : ` (resumed from ${details.resumedFrom})`}, request ${details.requestId}.`,
|
|
109
102
|
`Status: ${details.status}`,
|
|
110
103
|
...(details.limit === undefined ? [] : [details.limit]),
|
|
111
|
-
|
|
104
|
+
taskLine,
|
|
112
105
|
`Working directory: ${cwdLine(details.cwd)}`,
|
|
113
|
-
...(details.workspace === undefined ? [] : [`Workspace: ${workspaceLine(details.workspace)}`]),
|
|
114
106
|
...(details.workspaceSnapshot === undefined ? [] : checkoutText(details.workspaceSnapshot)),
|
|
115
107
|
`Envelope: ${details.envelopePath}`,
|
|
116
108
|
`Delegate session: ${details.sessionPath ?? details.sessionId ?? details.id}`,
|
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 {
|