pi-squire 0.9.3 → 0.9.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 +12 -1
- package/README.md +6 -1
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/render.ts +77 -7
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,16 @@ follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.4] - 2026-10-23
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- The `delegate` tool's row is drawn in the same style as the result message:
|
|
14
|
+
a call line naming the agent and label, then a ◐ header with the delegate's
|
|
15
|
+
name and model, a task preview and any skipped fallback models; expanded,
|
|
16
|
+
the whole task and the delegation id, window, thinking and auto-exit. The
|
|
17
|
+
text the model reads is unchanged.
|
|
18
|
+
|
|
9
19
|
## [0.9.3] - 2026-10-23
|
|
10
20
|
|
|
11
21
|
### Changed
|
|
@@ -354,7 +364,8 @@ author's dotfiles; the changes below are against that copy.
|
|
|
354
364
|
|
|
355
365
|
- The published file list includes `child.ts`, which `index.ts` imports.
|
|
356
366
|
|
|
357
|
-
[Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.9.
|
|
367
|
+
[Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.9.4...HEAD
|
|
368
|
+
[0.9.4]: https://github.com/gvanderclay/pi-squire/compare/v0.9.3...v0.9.4
|
|
358
369
|
[0.9.3]: https://github.com/gvanderclay/pi-squire/compare/v0.9.2...v0.9.3
|
|
359
370
|
[0.9.2]: https://github.com/gvanderclay/pi-squire/compare/v0.9.1...v0.9.2
|
|
360
371
|
[0.9.1]: https://github.com/gvanderclay/pi-squire/compare/v0.9.0...v0.9.1
|
package/README.md
CHANGED
|
@@ -453,7 +453,12 @@ Delegate session: <session file or id>
|
|
|
453
453
|
delegate's name and the model, then the first 3 non-blank lines of the
|
|
454
454
|
result with heading marks, backticks and bold stripped; expanded (Ctrl+O),
|
|
455
455
|
the quoted task, the result as Markdown, then the ids and files, with the
|
|
456
|
-
home directory shown as `~`.
|
|
456
|
+
home directory shown as `~`.
|
|
457
|
+
- The `delegate` tool's own row is drawn the same way, so a launch and its
|
|
458
|
+
result match: a call line naming the agent and label, then a header with
|
|
459
|
+
a ◐ started icon, the delegate's name and the model it started on, a preview of the task and any skipped fallback models;
|
|
460
|
+
expanded, the whole task, then the delegation id, window, thinking and
|
|
461
|
+
auto-exit. A refused call keeps its plain error text. The closed-without-a-result message gets the same header with ⊘.
|
|
457
462
|
- Stopping a delegate's run (Esc) sends no result: the provider holds the
|
|
458
463
|
task, so the delegation stays running while the user steers it. The next
|
|
459
464
|
run that completes sends the result, `done`, and its body opens with a note
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ import { Type } from "typebox";
|
|
|
49
49
|
import { type Agent, isThinking, type Roster, readRoster, THINKING_LEVELS } from "./agents.ts";
|
|
50
50
|
import { AGENT_ENV, AUTO_EXIT_ENV, FALLBACK_ENV, registerChild } from "./child.ts";
|
|
51
51
|
import { activeMark, clearMarks, isFree, readMarks } from "./limits.ts";
|
|
52
|
+
import { renderDelegateCall, renderDelegateResult } from "./render.ts";
|
|
52
53
|
import { createResults, delegateName } from "./results.ts";
|
|
53
54
|
import { createTmuxClient, type TmuxClient } from "./tmux.ts";
|
|
54
55
|
import { createTracking, toolResult } from "./tracking.ts";
|
|
@@ -596,6 +597,8 @@ export default function delegate(
|
|
|
596
597
|
),
|
|
597
598
|
}),
|
|
598
599
|
outputSchema: DelegateSchema,
|
|
600
|
+
renderCall: renderDelegateCall,
|
|
601
|
+
renderResult: renderDelegateResult,
|
|
599
602
|
// Opens a new window each call and removes nothing. Open world: the delegate calls a remote model and runs its agent's tools unattended.
|
|
600
603
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
601
604
|
async execute(
|
package/src/render.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// How
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// How delegations look in the parent: the `delegate` tool's row and the
|
|
2
|
+
// result or gone message that arrives later share one header and preview
|
|
3
|
+
// style. Both are drawn from structured details, while their text stays what
|
|
4
|
+
// the parent model reads.
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
6
|
|
|
6
7
|
import {
|
|
@@ -11,7 +12,9 @@ import {
|
|
|
11
12
|
type Theme,
|
|
12
13
|
} from "@earendil-works/pi-coding-agent";
|
|
13
14
|
import { Box, type Component, Markdown, Spacer, Text, TruncatedText } from "@earendil-works/pi-tui";
|
|
15
|
+
import type { Static } from "typebox";
|
|
14
16
|
|
|
17
|
+
import type { DelegateSchema } from "./index.ts";
|
|
15
18
|
import type { MessageDetails } from "./results.ts";
|
|
16
19
|
|
|
17
20
|
/** The launch model, or `launch → finished` when the delegate finished on another one. */
|
|
@@ -23,7 +26,8 @@ export function modelLabel(model: string, finished: string | undefined): string
|
|
|
23
26
|
const PREVIEW_LINES = 3;
|
|
24
27
|
|
|
25
28
|
/** The icon and theme color for each status; an unknown status gets a neutral icon. */
|
|
26
|
-
const STATUS_STYLE: Record<string, { icon: string; color: "success" | "error" | "warning" | "muted" }> = {
|
|
29
|
+
const STATUS_STYLE: Record<string, { icon: string; color: "accent" | "success" | "error" | "warning" | "muted" }> = {
|
|
30
|
+
started: { icon: "◐", color: "accent" },
|
|
27
31
|
done: { icon: "✓", color: "success" },
|
|
28
32
|
failed: { icon: "✗", color: "error" },
|
|
29
33
|
stopped: { icon: "■", color: "warning" },
|
|
@@ -38,12 +42,25 @@ function header(details: MessageDetails, theme: Theme): string {
|
|
|
38
42
|
let trailer = "";
|
|
39
43
|
if (details.kind === "gone") trailer = " closed without a result";
|
|
40
44
|
else if (!known) trailer = ` ${details.status}`;
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
return headerLine(theme, style, details.name, modelLabel(details.model, details.finishedModel), trailer);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function headerLine(
|
|
49
|
+
theme: Theme,
|
|
50
|
+
style: { icon: string; color: "accent" | "success" | "error" | "warning" | "muted" },
|
|
51
|
+
name: string,
|
|
52
|
+
model: string,
|
|
53
|
+
trailer: string,
|
|
54
|
+
): string {
|
|
55
|
+
return `${theme.fg(style.color, style.icon)} ${theme.fg("accent", name)} ${theme.fg("muted", model)}${
|
|
43
56
|
trailer === "" ? "" : theme.fg(style.color, trailer)
|
|
44
57
|
}`;
|
|
45
58
|
}
|
|
46
59
|
|
|
60
|
+
function expandHint(theme: Theme): Text {
|
|
61
|
+
return new Text(`${theme.fg("dim", keyText("app.tools.expand"))}${theme.fg("muted", " to expand")}`, 0, 0);
|
|
62
|
+
}
|
|
63
|
+
|
|
47
64
|
/** `path` with the home directory shown as `~`. */
|
|
48
65
|
function tilde(path: string): string {
|
|
49
66
|
const home = homedir();
|
|
@@ -97,7 +114,7 @@ export function renderMessage(
|
|
|
97
114
|
if (details.kind === "result") {
|
|
98
115
|
for (const line of preview(details.body)) box.addChild(new TruncatedText(theme.fg("muted", line)));
|
|
99
116
|
}
|
|
100
|
-
box.addChild(
|
|
117
|
+
box.addChild(expandHint(theme));
|
|
101
118
|
return box;
|
|
102
119
|
}
|
|
103
120
|
box.addChild(new Text(header(details, theme), 0, 0));
|
|
@@ -125,3 +142,56 @@ export function renderMessage(
|
|
|
125
142
|
box.addChild(metadataText);
|
|
126
143
|
return box;
|
|
127
144
|
}
|
|
145
|
+
|
|
146
|
+
/** The `delegate` tool's call line: the tool, the agent and any label. Arguments may still be streaming. */
|
|
147
|
+
export function renderDelegateCall(args: { agent?: unknown; label?: unknown }, theme: Theme): Component {
|
|
148
|
+
let line = theme.bold("delegate");
|
|
149
|
+
if (typeof args.agent === "string") line += ` ${theme.fg("accent", args.agent)}`;
|
|
150
|
+
if (typeof args.label === "string" && args.label !== "" && args.label !== "null")
|
|
151
|
+
line += ` ${theme.fg("muted", `· ${args.label}`)}`;
|
|
152
|
+
return new Text(line, 0, 0);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The `delegate` tool's result, in the result message's style: a started
|
|
157
|
+
* header, then a task preview and the hint, or, expanded, the whole task and
|
|
158
|
+
* the ids. A refused or blocked call has no details and keeps its plain text.
|
|
159
|
+
*/
|
|
160
|
+
export function renderDelegateResult(
|
|
161
|
+
result: { content: { type: string; text?: string }[]; details?: unknown },
|
|
162
|
+
{ expanded }: { expanded: boolean },
|
|
163
|
+
theme: Theme,
|
|
164
|
+
context: { args: { task?: unknown }; isError: boolean },
|
|
165
|
+
): Component {
|
|
166
|
+
const details = result.details as Partial<Static<typeof DelegateSchema>> | undefined;
|
|
167
|
+
if (context.isError || details?.outcome !== "started") {
|
|
168
|
+
const text = result.content.map((part) => part.text ?? "").join("\n");
|
|
169
|
+
return new Text(context.isError ? theme.fg("error", text) : text, 0, 0);
|
|
170
|
+
}
|
|
171
|
+
const started = details as Static<typeof DelegateSchema>;
|
|
172
|
+
const task = typeof context.args.task === "string" ? context.args.task : "";
|
|
173
|
+
const box = new Box(0, 0);
|
|
174
|
+
box.addChild(new TruncatedText(headerLine(theme, STATUS_STYLE.started, started.windowName, started.model, "")));
|
|
175
|
+
const skipped = started.skipped.map(({ model, reason }) => theme.fg("warning", `skipped ${model}: ${reason}`));
|
|
176
|
+
if (!expanded) {
|
|
177
|
+
for (const line of preview(task)) box.addChild(new TruncatedText(theme.fg("muted", line)));
|
|
178
|
+
for (const line of skipped) box.addChild(new TruncatedText(line));
|
|
179
|
+
box.addChild(expandHint(theme));
|
|
180
|
+
return box;
|
|
181
|
+
}
|
|
182
|
+
const quote = task
|
|
183
|
+
.split("\n")
|
|
184
|
+
.map((line) => theme.fg("muted", `> ${line}`))
|
|
185
|
+
.join("\n");
|
|
186
|
+
box.addChild(new Text(quote, 0, 0));
|
|
187
|
+
if (skipped.length + started.notices.length > 0)
|
|
188
|
+
box.addChild(new Text([...skipped, ...started.notices.map((line) => theme.fg("dim", line))].join("\n"), 0, 0));
|
|
189
|
+
box.addChild(new Spacer(1));
|
|
190
|
+
const ids = [
|
|
191
|
+
`delegation ${started.id}`,
|
|
192
|
+
`window ${started.windowName} (${started.windowId})`,
|
|
193
|
+
`thinking ${started.thinking}, auto-exit ${started.autoExit ? "on" : "off"}`,
|
|
194
|
+
];
|
|
195
|
+
box.addChild(new Text(ids.map((line) => theme.fg("dim", line)).join("\n"), 0, 0));
|
|
196
|
+
return box;
|
|
197
|
+
}
|