@ferris1225/pi-subagents 4.1.23 → 4.1.24
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 -2
- package/package.json +55 -55
- package/src/config.ts +50 -2
- package/src/monitor.ts +11 -0
- package/src/recovery.ts +145 -145
- package/src/rpc-run.ts +993 -993
- package/src/setup.ts +12 -15
- package/src/widget.ts +57 -61
package/src/setup.ts
CHANGED
|
@@ -248,6 +248,9 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
248
248
|
|
|
249
249
|
const next: SubagentsConfig = {
|
|
250
250
|
enabledAgents: enabled,
|
|
251
|
+
// The wizard surfaces every built-in, so an untoggled one was seen and
|
|
252
|
+
// deliberately left off — record them all as known.
|
|
253
|
+
knownAgents: [...BUILTIN_AGENT_NAMES],
|
|
251
254
|
agentModels,
|
|
252
255
|
// Full setup returns every agent to capability-aware Auto thinking.
|
|
253
256
|
agentThinkingLevels: {},
|
|
@@ -283,23 +286,17 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
283
286
|
const enabled = await pickEnabledAgents(ctx, config.enabledAgents);
|
|
284
287
|
if (enabled === undefined) continue;
|
|
285
288
|
next.enabledAgents = enabled;
|
|
286
|
-
// A newly enabled role inherits
|
|
289
|
+
// A newly enabled role inherits explorer's configured model and
|
|
287
290
|
// thinking level, so the file reflects what it will actually run
|
|
288
|
-
// instead of silently falling back to the current main model:
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
[
|
|
293
|
-
|
|
294
|
-
["synthesizer", "explorer"],
|
|
295
|
-
];
|
|
296
|
-
for (const [agent, from] of modelInheritance) {
|
|
297
|
-
if (config.enabledAgents.includes(agent) || !enabled.includes(agent)) continue;
|
|
298
|
-
if (!next.agentModels[agent] && config.agentModels[from]) {
|
|
299
|
-
next.agentModels[agent] = config.agentModels[from];
|
|
291
|
+
// instead of silently falling back to the current main model: these
|
|
292
|
+
// roles do light migration-grade work on the fast explorer lane.
|
|
293
|
+
for (const agent of enabled) {
|
|
294
|
+
if (agent === "explorer" || config.enabledAgents.includes(agent)) continue;
|
|
295
|
+
if (!next.agentModels[agent] && config.agentModels.explorer) {
|
|
296
|
+
next.agentModels[agent] = config.agentModels.explorer;
|
|
300
297
|
}
|
|
301
|
-
if (!next.agentThinkingLevels[agent] && config.agentThinkingLevels
|
|
302
|
-
next.agentThinkingLevels[agent] = config.agentThinkingLevels
|
|
298
|
+
if (!next.agentThinkingLevels[agent] && config.agentThinkingLevels.explorer) {
|
|
299
|
+
next.agentThinkingLevels[agent] = config.agentThinkingLevels.explorer;
|
|
303
300
|
}
|
|
304
301
|
}
|
|
305
302
|
next.agentModels = keepAgentEntries(next.agentModels, enabled);
|
package/src/widget.ts
CHANGED
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
* - Aligned identity columns: `icon #id agent` pad to the widest displayed
|
|
6
6
|
* id and agent so every label starts at the same column; a resumed thread
|
|
7
7
|
* carries a dim `↻` inside the agent column.
|
|
8
|
-
* -
|
|
9
|
-
* token flow in the pi-footer
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* - A live run owns two lines. Line 1 is what it is: identity, task label,
|
|
9
|
+
* then the telemetry flow (`provider/model`, token flow in the pi-footer
|
|
10
|
+
* vocabulary `↑in ↓out R/W cache`, cost, wait state, seconds-precision
|
|
11
|
+
* elapsed). Line 2 is what it is doing right now: the live activity, dim,
|
|
12
|
+
* indented under the label column behind a `↳` marker.
|
|
12
13
|
* - Telemetry drops leftmost-first under width pressure (badge, wait, usage,
|
|
13
14
|
* model); the elapsed survives every width.
|
|
14
|
-
* - First line is the parent pi session itself: what the current
|
|
15
|
-
* doing right now while its agent loop runs (model/thinking, live
|
|
16
|
-
* loop elapsed), fed by the session's own extension events.
|
|
15
|
+
* - First line of the widget is the parent pi session itself: what the current
|
|
16
|
+
* model is doing right now while its agent loop runs (model/thinking, live
|
|
17
|
+
* activity, loop elapsed), fed by the session's own extension events.
|
|
17
18
|
* - A managed workflow renders as a tree chain under its parent line: one
|
|
18
19
|
* `├`/`└`-connected row per stage, each carrying its own model, token flow,
|
|
19
20
|
* and elapsed — settled stages from the snapshot frozen at settlement, the
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
formatUsageTokens,
|
|
34
35
|
isRunActiveStatus,
|
|
35
36
|
monitor,
|
|
37
|
+
shrinkRunLabel,
|
|
36
38
|
statusIcon,
|
|
37
39
|
sumUsage,
|
|
38
40
|
usageCostPart,
|
|
@@ -52,8 +54,10 @@ const MAX_WIDGET_LINES = 10;
|
|
|
52
54
|
const SEPARATOR = " · ";
|
|
53
55
|
/** Column gap between the identity block and the run's label. */
|
|
54
56
|
const IDENTITY_GAP = " ";
|
|
55
|
-
/** Splits "what this run is" from "what it is doing right now". */
|
|
57
|
+
/** Splits "what this run is" from "what it is doing right now" on a stage row. */
|
|
56
58
|
const ACTIVITY_SEPARATOR = " — ";
|
|
59
|
+
/** Marker introducing the live-activity second line of a running row. */
|
|
60
|
+
const ACTIVITY_MARKER = "↳ ";
|
|
57
61
|
/** Columns kept for left content before right-tail parts are dropped. */
|
|
58
62
|
const LEFT_MIN_CONTENT = 8;
|
|
59
63
|
/** Minimum useful width for a live-activity fragment. */
|
|
@@ -151,7 +155,7 @@ function telemetryTailParts(run: RunView, now: number, usage: UsageStats = run.u
|
|
|
151
155
|
// exactly what a multi-provider session needs to see.
|
|
152
156
|
const modelPart = run.status === "queued" || run.managedWorkflow || !run.model
|
|
153
157
|
? undefined
|
|
154
|
-
:
|
|
158
|
+
: run.model;
|
|
155
159
|
const badge = run.isolation === "worktree" ? worktreeBadge(run) : undefined;
|
|
156
160
|
const wait = run.status === "queued" ? waitWord(run) : undefined;
|
|
157
161
|
// Drop order under pressure: badge, wait word, usage, model; elapsed
|
|
@@ -159,11 +163,12 @@ function telemetryTailParts(run: RunView, now: number, usage: UsageStats = run.u
|
|
|
159
163
|
return [badge, wait, usagePart(usage), modelPart, formatElapsed(run, now) || undefined];
|
|
160
164
|
}
|
|
161
165
|
|
|
162
|
-
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
166
|
+
/** Two lines for a live run. Line 1 is what the run is: identity, task label,
|
|
167
|
+
* then the telemetry flow (worktree badge, token flow, cost, provider/model,
|
|
168
|
+
* wait state, elapsed). Line 2 is what it is doing right now: the live
|
|
169
|
+
* activity, dim, indented under the label column behind a `↳` marker. The
|
|
170
|
+
* label takes the full content budget on line 1; the identity and the elapsed
|
|
171
|
+
* survive every width. */
|
|
167
172
|
function primaryLine(
|
|
168
173
|
run: RunView,
|
|
169
174
|
theme: Theme,
|
|
@@ -171,8 +176,7 @@ function primaryLine(
|
|
|
171
176
|
now: number,
|
|
172
177
|
layout: ColumnLayout,
|
|
173
178
|
usage: UsageStats = run.usage,
|
|
174
|
-
): string {
|
|
175
|
-
const dim = (text: string): string => theme.fg("dim", text);
|
|
179
|
+
): string[] {
|
|
176
180
|
const identity = identitySegment(run, theme, layout);
|
|
177
181
|
const tailBudget = Math.max(0, width - visibleWidth(identity) - LEFT_MIN_CONTENT);
|
|
178
182
|
const tail = composeTail(telemetryTailParts(run, now, usage), tailBudget);
|
|
@@ -182,41 +186,32 @@ function primaryLine(
|
|
|
182
186
|
const label = run.parentRunId !== undefined
|
|
183
187
|
? [run.relationLabel, run.label].filter((part): part is string => Boolean(part)).join(SEPARATOR)
|
|
184
188
|
: run.label ?? formatTaskSummary(run.task, 48);
|
|
185
|
-
// The parent's own activity is a placeholder while a managed workflow runs;
|
|
186
|
-
// the timeline line below carries the live stage instead.
|
|
187
|
-
const activity = !run.managedWorkflow && (run.status === "running" || run.status === "interrupting")
|
|
188
|
-
? run.activity?.trim()
|
|
189
|
-
: undefined;
|
|
190
|
-
|
|
191
189
|
const contentBudget = width
|
|
192
190
|
- visibleWidth(identity)
|
|
193
191
|
- (tail ? visibleWidth(tail) + visibleWidth(SEPARATOR) : 0)
|
|
194
192
|
- visibleWidth(IDENTITY_GAP);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
content = labelText;
|
|
193
|
+
// The label is already fragment-extracted (runLabel); narrowing it keeps
|
|
194
|
+
// its tail so a second squeeze never trades away the recognisable
|
|
195
|
+
// filename, and no second head…tail ellipsis stacks on top of it.
|
|
196
|
+
const content = label && contentBudget > 0 ? shrinkRunLabel(label, contentBudget) : "";
|
|
197
|
+
const left = content ? `${identity}${IDENTITY_GAP}${content}` : identity;
|
|
198
|
+
const lines = [composeLine(left, tail, theme, width)];
|
|
199
|
+
|
|
200
|
+
// The parent's own activity is a placeholder while a managed workflow runs;
|
|
201
|
+
// the timeline chain below carries the live stage instead.
|
|
202
|
+
const activity = !run.managedWorkflow && (run.status === "running" || run.status === "interrupting")
|
|
203
|
+
? run.activity?.trim()
|
|
204
|
+
: undefined;
|
|
205
|
+
if (activity) {
|
|
206
|
+
const indent = visibleWidth(identity) + visibleWidth(IDENTITY_GAP);
|
|
207
|
+
const activityBudget = width - indent - visibleWidth(ACTIVITY_MARKER);
|
|
208
|
+
if (activityBudget >= ACTIVITY_MIN_WIDTH) {
|
|
209
|
+
lines.push(
|
|
210
|
+
`${" ".repeat(indent)}${theme.fg("dim", `${ACTIVITY_MARKER}${formatTaskSummary(activity, activityBudget)}`)}`,
|
|
211
|
+
);
|
|
215
212
|
}
|
|
216
213
|
}
|
|
217
|
-
|
|
218
|
-
const left = content ? `${identity}${IDENTITY_GAP}${content}` : identity;
|
|
219
|
-
return composeLine(left, tail, theme, width);
|
|
214
|
+
return lines;
|
|
220
215
|
}
|
|
221
216
|
|
|
222
217
|
function stageIcon(status: WorkflowStageStatus, theme: Theme): string {
|
|
@@ -329,20 +324,20 @@ function workflowStageLines(
|
|
|
329
324
|
return { lines, activeIndex };
|
|
330
325
|
}
|
|
331
326
|
|
|
332
|
-
/** Fit one workflow group into the remaining line budget: the primary
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
* stage itself. */
|
|
327
|
+
/** Fit one workflow group into the remaining line budget: the primary lines
|
|
328
|
+
* (identity line plus the live-activity line) always survive, and the stage
|
|
329
|
+
* window anchors on the live stage — settled stages above the window collapse
|
|
330
|
+
* into one `… +N` marker, never the live stage itself. */
|
|
336
331
|
function fitGroupLines(
|
|
337
|
-
primary: string,
|
|
332
|
+
primary: readonly string[],
|
|
338
333
|
stages: readonly string[],
|
|
339
334
|
activeIndex: number,
|
|
340
335
|
remaining: number,
|
|
341
336
|
theme: Theme,
|
|
342
337
|
): string[] {
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
-
|
|
338
|
+
if (primary.length > remaining) return primary.slice(0, Math.max(1, remaining));
|
|
339
|
+
if (stages.length === 0 || stages.length + primary.length <= remaining) return [...primary, ...stages];
|
|
340
|
+
const slots = remaining - primary.length;
|
|
346
341
|
// Reserve one line for an overflow marker so a cut is always announced.
|
|
347
342
|
const room = Math.max(1, slots - 1);
|
|
348
343
|
const anchor = activeIndex >= 0 ? activeIndex : 0;
|
|
@@ -354,12 +349,12 @@ function fitGroupLines(
|
|
|
354
349
|
...(start + room < stages.length ? [theme.fg("dim", `… +${stages.length - start - room}`)] : []),
|
|
355
350
|
];
|
|
356
351
|
while (parts.length > slots) parts.pop();
|
|
357
|
-
return [primary, ...parts];
|
|
352
|
+
return [...primary, ...parts];
|
|
358
353
|
}
|
|
359
354
|
|
|
360
355
|
/** Render active runs as compact per-run line groups: the parent model's own
|
|
361
|
-
* line first, then one line per simple run, a tree chain per managed
|
|
362
|
-
* (parent line + one row per stage). All rows share one column layout. */
|
|
356
|
+
* line first, then one two-line group per simple run, a tree chain per managed
|
|
357
|
+
* workflow (parent line + one row per stage). All rows share one column layout. */
|
|
363
358
|
export function formatActiveRunLines(
|
|
364
359
|
runs: readonly RunView[],
|
|
365
360
|
theme: Theme,
|
|
@@ -384,7 +379,7 @@ export function formatActiveRunLines(
|
|
|
384
379
|
idWidth: Math.max(...roots.map((root) => visibleWidth(`#${root.id}`)), 0),
|
|
385
380
|
agentWidth: Math.max(...roots.map((root) => visibleWidth(agentColumnText(root))), main ? visibleWidth("pi") : 0),
|
|
386
381
|
};
|
|
387
|
-
const groups: Array<{
|
|
382
|
+
const groups: Array<{ primary: readonly string[]; stages: readonly string[]; activeIndex: number }> = roots.map((root) => {
|
|
388
383
|
const children = childrenOf.get(root.id) ?? [];
|
|
389
384
|
// Workflow-wide tokens/cost on the parent line: every stage snapshot plus
|
|
390
385
|
// the live child (whose snapshot is frozen only at settlement). Without
|
|
@@ -395,14 +390,15 @@ export function formatActiveRunLines(
|
|
|
395
390
|
const live = children.find((candidate) => candidate.usage.input || candidate.usage.output || candidate.usage.cost);
|
|
396
391
|
usage = sumUsage([...(settled.length > 0 ? settled : [root.usage]), ...(live ? [live.usage] : [])]);
|
|
397
392
|
}
|
|
398
|
-
const
|
|
393
|
+
const primary = primaryLine(root, theme, width, now, layout, usage);
|
|
399
394
|
let activeIndex = -1;
|
|
395
|
+
let stages: readonly string[] = [];
|
|
400
396
|
if (root.managedWorkflow && root.workflowStages && root.workflowStages.length > 0) {
|
|
401
397
|
const rendered = workflowStageLines(root.workflowStages, children, theme, width, now);
|
|
402
|
-
|
|
398
|
+
stages = rendered.lines;
|
|
403
399
|
activeIndex = rendered.activeIndex;
|
|
404
400
|
}
|
|
405
|
-
return {
|
|
401
|
+
return { primary, stages, activeIndex };
|
|
406
402
|
});
|
|
407
403
|
|
|
408
404
|
const lines: string[] = [];
|
|
@@ -413,7 +409,7 @@ export function formatActiveRunLines(
|
|
|
413
409
|
for (const group of groups) {
|
|
414
410
|
const remaining = MAX_WIDGET_LINES - 1 - lines.length;
|
|
415
411
|
if (remaining <= 0) break;
|
|
416
|
-
lines.push(...fitGroupLines(group.
|
|
412
|
+
lines.push(...fitGroupLines(group.primary, group.stages, group.activeIndex, remaining, theme));
|
|
417
413
|
shownRoots++;
|
|
418
414
|
}
|
|
419
415
|
const hiddenRoots = roots.length - shownRoots;
|