@bermudi/pi-delegate 0.1.13 → 0.1.15
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/README.md +14 -17
- package/delegate.ts +0 -8
- package/dispatch.ts +25 -2
- package/extension.ts +14 -6
- package/file-tracking.ts +286 -15
- package/format.ts +107 -21
- package/isolated-workspace.ts +550 -62
- package/key-hints.ts +38 -0
- package/lifecycle.ts +559 -80
- package/manual.ts +1 -1
- package/model.ts +6 -1
- package/package.json +13 -9
- package/pool.ts +37 -6
- package/quiescence.ts +19 -6
- package/render-branches.ts +243 -85
- package/render-result.ts +116 -16
- package/runner.ts +361 -82
- package/session-quarantine.ts +266 -0
- package/spill.ts +93 -22
- package/task-resolution.ts +63 -50
- package/telemetry.ts +1 -1
- package/ticket-format.ts +15 -9
- package/tickets.ts +36 -14
- package/tools.ts +5 -4
- package/types.ts +92 -8
- package/utils.ts +123 -2
- package/workspace.ts +133 -0
- package/settings.ts +0 -418
package/render-result.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
1
|
+
import { Box, Text } from "@earendil-works/pi-tui";
|
|
2
2
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import {
|
|
4
4
|
fmtDuration,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type RenderHelpers,
|
|
16
16
|
} from "./render-branches.ts";
|
|
17
17
|
import type { DelegateDetails, TaskProgress } from "./types.ts";
|
|
18
|
+
import { sanitizeTerminalLine, sanitizeTerminalText } from "./utils.ts";
|
|
18
19
|
|
|
19
20
|
/** Build the shared helpers bound to a theme, width, and the lines sink.
|
|
20
21
|
* Both render branches consume one bound set so warning/activity formatting
|
|
@@ -25,9 +26,11 @@ function makeRenderHelpers(
|
|
|
25
26
|
lines: string[],
|
|
26
27
|
): RenderHelpers {
|
|
27
28
|
const statJoin = (parts: string[]) =>
|
|
28
|
-
parts.length ? theme.fg("
|
|
29
|
-
const modelLabel = (p: TaskProgress) =>
|
|
30
|
-
p.model ?
|
|
29
|
+
parts.length ? theme.fg("dim", ` · ${parts.join(" · ")}`) : "";
|
|
30
|
+
const modelLabel = (p: TaskProgress) => {
|
|
31
|
+
const model = p.model ? sanitizeTerminalLine(p.model) : "";
|
|
32
|
+
return model ? theme.fg("dim", ` · ${model}`) : "";
|
|
33
|
+
};
|
|
31
34
|
|
|
32
35
|
// Push muted warning lines for a task under its status row. Rendered in
|
|
33
36
|
// both partial and final views so a human watching the TUI sees that tools
|
|
@@ -35,11 +38,21 @@ function makeRenderHelpers(
|
|
|
35
38
|
const pushWarnings = (p: TaskProgress, ind: string) => {
|
|
36
39
|
if (!p.warnings?.length) return;
|
|
37
40
|
for (const wn of p.warnings) {
|
|
38
|
-
|
|
41
|
+
const warning = sanitizeTerminalLine(wn);
|
|
42
|
+
if (warning) {
|
|
43
|
+
lines.push(
|
|
44
|
+
truncLine(`${ind}${theme.fg("warning", `⚠ ${warning}`)}`, w),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
39
47
|
}
|
|
40
48
|
};
|
|
41
49
|
|
|
42
|
-
return {
|
|
50
|
+
return {
|
|
51
|
+
statJoin,
|
|
52
|
+
modelLabel,
|
|
53
|
+
compactActivity: (p) => sanitizeTerminalLine(compactActivity(p)),
|
|
54
|
+
pushWarnings,
|
|
55
|
+
};
|
|
43
56
|
}
|
|
44
57
|
|
|
45
58
|
/** Minimal structural view of Pi's `ToolRenderContext` used by the delegate
|
|
@@ -50,6 +63,12 @@ interface RenderCtx {
|
|
|
50
63
|
invalidate: () => void;
|
|
51
64
|
executionStarted: boolean;
|
|
52
65
|
isPartial: boolean;
|
|
66
|
+
/** Message renderers own their horizontal padding; tool renderers do not. */
|
|
67
|
+
outputPad?: number;
|
|
68
|
+
/** Width already consumed by an outer shell such as the async result box. */
|
|
69
|
+
widthInset?: number;
|
|
70
|
+
/** Standalone custom messages do not need the tool-result leading spacer. */
|
|
71
|
+
standalone?: boolean;
|
|
53
72
|
}
|
|
54
73
|
|
|
55
74
|
interface RenderResultOptions {
|
|
@@ -65,6 +84,15 @@ interface RenderResult {
|
|
|
65
84
|
details?: DelegateDetails;
|
|
66
85
|
}
|
|
67
86
|
|
|
87
|
+
interface AsyncDelegateMessage {
|
|
88
|
+
content: string | Array<{ type: string; text?: string }>;
|
|
89
|
+
details?: DelegateDetails;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface AsyncMessageRenderOptions {
|
|
93
|
+
expanded: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
68
96
|
/** Custom rendering for the tool *call* display — minimal by design; the result
|
|
69
97
|
* renderer shows all detail. Only animates a spinner while still running. */
|
|
70
98
|
export function renderDelegateCall(
|
|
@@ -75,7 +103,9 @@ export function renderDelegateCall(
|
|
|
75
103
|
const state = ctx.state;
|
|
76
104
|
const rawTasks = args.tasks;
|
|
77
105
|
const tasks = Array.isArray(rawTasks) ? rawTasks : [];
|
|
78
|
-
const text =
|
|
106
|
+
const text =
|
|
107
|
+
(ctx.lastComponent as Text | undefined) ??
|
|
108
|
+
new Text("", ctx.outputPad ?? 0, 0);
|
|
79
109
|
if (typeof rawTasks === "string") {
|
|
80
110
|
text.setText(theme.fg("toolTitle", theme.bold("delegate invalid tasks")));
|
|
81
111
|
return text;
|
|
@@ -129,7 +159,9 @@ export function renderDelegateResult(
|
|
|
129
159
|
clearInterval(state.interval);
|
|
130
160
|
state.interval = undefined;
|
|
131
161
|
}
|
|
132
|
-
const text =
|
|
162
|
+
const text =
|
|
163
|
+
(ctx.lastComponent as Text | undefined) ??
|
|
164
|
+
new Text("", ctx.outputPad ?? 0, 0);
|
|
133
165
|
|
|
134
166
|
const details = result.details;
|
|
135
167
|
if (!details?.progress?.length) {
|
|
@@ -138,7 +170,10 @@ export function renderDelegateResult(
|
|
|
138
170
|
?.filter((c) => c.type === "text")
|
|
139
171
|
.map((c) => c.text)
|
|
140
172
|
.join("\n") ?? "";
|
|
141
|
-
|
|
173
|
+
const safeContent = sanitizeTerminalText(content);
|
|
174
|
+
text.setText(
|
|
175
|
+
safeContent ? `${ctx.standalone ? "" : "\n"}${safeContent}` : "",
|
|
176
|
+
);
|
|
142
177
|
return text;
|
|
143
178
|
}
|
|
144
179
|
|
|
@@ -149,8 +184,11 @@ export function renderDelegateResult(
|
|
|
149
184
|
status: ticketStatus,
|
|
150
185
|
} = details;
|
|
151
186
|
const total = progress.length;
|
|
152
|
-
const
|
|
153
|
-
|
|
187
|
+
const widthInset =
|
|
188
|
+
ctx.widthInset ??
|
|
189
|
+
(ctx.outputPad === undefined ? 4 : Math.max(0, ctx.outputPad * 2));
|
|
190
|
+
const w = getTermWidth() - widthInset;
|
|
191
|
+
const lines: string[] = ctx.standalone ? [] : [""];
|
|
154
192
|
const helpers = makeRenderHelpers(theme, w, lines);
|
|
155
193
|
|
|
156
194
|
const branchCtx = {
|
|
@@ -164,19 +202,32 @@ export function renderDelegateResult(
|
|
|
164
202
|
lines,
|
|
165
203
|
ticketId,
|
|
166
204
|
ticketStatus,
|
|
205
|
+
elapsedMs: details.elapsedMs,
|
|
167
206
|
};
|
|
168
207
|
|
|
169
208
|
// Surface batch-level warnings at the top of the TUI. The same text already
|
|
170
209
|
// lives in textual content, but the progress renderer ignores content.
|
|
171
210
|
if (details?.dispatchWarning) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
"",
|
|
175
|
-
|
|
211
|
+
const warning = sanitizeTerminalLine(details.dispatchWarning);
|
|
212
|
+
if (warning) {
|
|
213
|
+
lines.push(truncLine(theme.fg("warning", `⚠ ${warning}`), w), "");
|
|
214
|
+
}
|
|
176
215
|
}
|
|
177
216
|
if (details?.overlapWarning) {
|
|
217
|
+
const warning = sanitizeTerminalLine(details.overlapWarning);
|
|
218
|
+
if (warning) {
|
|
219
|
+
lines.push(truncLine(theme.fg("warning", `⚠ ${warning}`), w), "");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (details?.crossLeafDelivery) {
|
|
178
223
|
lines.push(
|
|
179
|
-
truncLine(
|
|
224
|
+
truncLine(
|
|
225
|
+
theme.fg(
|
|
226
|
+
"warning",
|
|
227
|
+
"↳ Result delivered from another session-tree branch",
|
|
228
|
+
),
|
|
229
|
+
w,
|
|
230
|
+
),
|
|
180
231
|
"",
|
|
181
232
|
);
|
|
182
233
|
}
|
|
@@ -195,3 +246,52 @@ export function renderDelegateResult(
|
|
|
195
246
|
text.setText(budgeted.join("\n"));
|
|
196
247
|
return text;
|
|
197
248
|
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Render an automatically delivered async result with the same compact /
|
|
252
|
+
* expanded presentation as a synchronous tool result. The full message
|
|
253
|
+
* content remains untouched in model context; only its TUI presentation is
|
|
254
|
+
* replaced.
|
|
255
|
+
*/
|
|
256
|
+
export function renderAsyncDelegateMessage(
|
|
257
|
+
message: AsyncDelegateMessage,
|
|
258
|
+
options: AsyncMessageRenderOptions,
|
|
259
|
+
theme: Theme,
|
|
260
|
+
): Box {
|
|
261
|
+
const content =
|
|
262
|
+
typeof message.content === "string"
|
|
263
|
+
? [{ type: "text", text: message.content }]
|
|
264
|
+
: message.content;
|
|
265
|
+
const result = renderDelegateResult(
|
|
266
|
+
{ content, details: message.details },
|
|
267
|
+
{ isPartial: false, expanded: options.expanded },
|
|
268
|
+
theme,
|
|
269
|
+
{
|
|
270
|
+
state: {},
|
|
271
|
+
lastComponent: undefined,
|
|
272
|
+
invalidate: () => undefined,
|
|
273
|
+
executionStarted: false,
|
|
274
|
+
isPartial: false,
|
|
275
|
+
// The Box below owns the same one-column padding as Pi's tool shell.
|
|
276
|
+
outputPad: 0,
|
|
277
|
+
widthInset: 2,
|
|
278
|
+
standalone: true,
|
|
279
|
+
},
|
|
280
|
+
);
|
|
281
|
+
const status = message.details?.status;
|
|
282
|
+
const failed =
|
|
283
|
+
status === "failed" ||
|
|
284
|
+
status === "cancelled" ||
|
|
285
|
+
message.details?.progress?.some((task) => task.status === "failed");
|
|
286
|
+
const pending = status === "running" || status === "cancelling";
|
|
287
|
+
let background: "toolPendingBg" | "toolErrorBg" | "toolSuccessBg" =
|
|
288
|
+
"toolSuccessBg";
|
|
289
|
+
if (pending) {
|
|
290
|
+
background = "toolPendingBg";
|
|
291
|
+
} else if (failed) {
|
|
292
|
+
background = "toolErrorBg";
|
|
293
|
+
}
|
|
294
|
+
const box = new Box(1, 1, (text) => theme.bg(background, text));
|
|
295
|
+
box.addChild(result);
|
|
296
|
+
return box;
|
|
297
|
+
}
|