@pi-unipi/unipi 3.0.0-alpha.18 → 3.0.0-alpha.19

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 CHANGED
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.0.0-alpha.19] — 2026-10-01
10
+
11
+ ### Added
12
+ - `subagents`: completed subagent steps stream into the lead transcript as delegated panels — a live `subagent-step` entry per step (foreground and background runs) renders in the shared UNI-2 rail style alongside the existing status card, grouped per subagent.
13
+
14
+ ### Changed
15
+ - render: the delegated-work panel (bright cyan `▏` rail on a single dark-cyan fill) moved into a shared `utility` helper used by both fusion sidekick steps and subagent steps; sidekick panels are grouped per handoff so a resumed handoff renders as its own panel instead of merging into the previous run.
16
+
17
+ ### Fixed
18
+ - render: panel continuity survives pi's `CustomEntryComponent` host wrapper — group metadata is resolved through the wrapper and re-resolved every render, and nested card backgrounds are stripped instead of stacking inside the cyan fill.
19
+ - render: fixed the alpha.18 OOM in the simple style — the final result baked the `· Ns` runtime suffix into the tool meta, re-arming the invalidate loop on every re-render (unbounded heap growth and eventual SIGABRT); the duration is pinned once and appended at paint.
20
+
9
21
  ## [3.0.0-alpha.18] — 2026-10-01
10
22
 
11
23
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/unipi",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "All-in-one extension suite for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -82,26 +82,26 @@
82
82
  "typebox": "^1.1.38"
83
83
  },
84
84
  "dependencies": {
85
- "@pi-unipi/ask-user": "3.0.0-alpha.18",
86
- "@pi-unipi/background-tasks": "3.0.0-alpha.18",
87
- "@pi-unipi/btw": "3.0.0-alpha.18",
88
- "@pi-unipi/command-enchantment": "3.0.0-alpha.18",
89
- "@pi-unipi/compactor": "3.0.0-alpha.18",
90
- "@pi-unipi/core": "3.0.0-alpha.18",
91
- "@pi-unipi/footer": "3.0.0-alpha.18",
92
- "@pi-unipi/fusion": "3.0.0-alpha.18",
93
- "@pi-unipi/info-screen": "3.0.0-alpha.18",
94
- "@pi-unipi/input-shortcuts": "3.0.0-alpha.18",
95
- "@pi-unipi/kanboard": "3.0.0-alpha.18",
96
- "@pi-unipi/mcp": "3.0.0-alpha.18",
97
- "@pi-unipi/memory": "3.0.0-alpha.18",
98
- "@pi-unipi/notify": "3.0.0-alpha.18",
99
- "@pi-unipi/skill-registry": "3.0.0-alpha.18",
100
- "@pi-unipi/subagents": "3.0.0-alpha.18",
101
- "@pi-unipi/updater": "3.0.0-alpha.18",
102
- "@pi-unipi/utility": "3.0.0-alpha.18",
103
- "@pi-unipi/web-api": "3.0.0-alpha.18",
104
- "@pi-unipi/workflow": "3.0.0-alpha.18"
85
+ "@pi-unipi/ask-user": "3.0.0-alpha.19",
86
+ "@pi-unipi/background-tasks": "3.0.0-alpha.19",
87
+ "@pi-unipi/btw": "3.0.0-alpha.19",
88
+ "@pi-unipi/command-enchantment": "3.0.0-alpha.19",
89
+ "@pi-unipi/compactor": "3.0.0-alpha.19",
90
+ "@pi-unipi/core": "3.0.0-alpha.19",
91
+ "@pi-unipi/footer": "3.0.0-alpha.19",
92
+ "@pi-unipi/fusion": "3.0.0-alpha.19",
93
+ "@pi-unipi/info-screen": "3.0.0-alpha.19",
94
+ "@pi-unipi/input-shortcuts": "3.0.0-alpha.19",
95
+ "@pi-unipi/kanboard": "3.0.0-alpha.19",
96
+ "@pi-unipi/mcp": "3.0.0-alpha.19",
97
+ "@pi-unipi/memory": "3.0.0-alpha.19",
98
+ "@pi-unipi/notify": "3.0.0-alpha.19",
99
+ "@pi-unipi/skill-registry": "3.0.0-alpha.19",
100
+ "@pi-unipi/subagents": "3.0.0-alpha.19",
101
+ "@pi-unipi/updater": "3.0.0-alpha.19",
102
+ "@pi-unipi/utility": "3.0.0-alpha.19",
103
+ "@pi-unipi/web-api": "3.0.0-alpha.19",
104
+ "@pi-unipi/workflow": "3.0.0-alpha.19"
105
105
  },
106
106
  "devDependencies": {
107
107
  "@earendil-works/pi-agent-core": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/ask-user",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Structured user input tool for Pi coding agent — single-select, multi-select, freeform",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -40,7 +40,7 @@
40
40
  "access": "public"
41
41
  },
42
42
  "dependencies": {
43
- "@pi-unipi/core": "3.0.0-alpha.18"
43
+ "@pi-unipi/core": "3.0.0-alpha.19"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/command-enchantment",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Enhanced TUI autocomplete for /unipi:* commands — colored, sorted, and grouped by package",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/background-tasks",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Background tasks for UniPi — durable shell jobs and delegated background agents with a footer dock",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "@earendil-works/pi-ai": "^0.87.1",
13
- "@pi-unipi/core": "3.0.0-alpha.18",
13
+ "@pi-unipi/core": "3.0.0-alpha.19",
14
14
  "turndown": "^7.2.4"
15
15
  },
16
16
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/btw",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "A pi extension for parallel side conversations with /unipi:btw — part of the Unipi suite",
5
5
  "type": "module",
6
6
  "main": "extensions/btw.ts",
@@ -37,7 +37,7 @@
37
37
  "@earendil-works/pi-tui": "^0.87.1"
38
38
  },
39
39
  "dependencies": {
40
- "@pi-unipi/core": "3.0.0-alpha.18"
40
+ "@pi-unipi/core": "3.0.0-alpha.19"
41
41
  },
42
42
  "pi": {
43
43
  "extensions": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/compactor",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Compaction for Pi — lossless zero-LLM or model summaries, rebuilt from full history, with session recall",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -33,8 +33,8 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@pi-unipi/core": "3.0.0-alpha.18",
37
- "@pi-unipi/info-screen": "3.0.0-alpha.18",
36
+ "@pi-unipi/core": "3.0.0-alpha.19",
37
+ "@pi-unipi/info-screen": "3.0.0-alpha.19",
38
38
  "@earendil-works/pi-agent-core": "^0.87.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/core",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Shared utilities, event types, and constants for Unipi extension suite",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/footer",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Persistent status bar for Unipi — subscribes to UNIPI_EVENTS and renders key stats from all unipi packages",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -32,8 +32,8 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "3.0.0-alpha.18",
36
- "@pi-unipi/background-tasks": "3.0.0-alpha.18"
35
+ "@pi-unipi/core": "3.0.0-alpha.19",
36
+ "@pi-unipi/background-tasks": "3.0.0-alpha.19"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/fusion",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Devin-style model picker, fusion presets (lead + sidekick), and Local Fusion runtime for UniPi",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -32,9 +32,9 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "3.0.0-alpha.18",
36
- "@pi-unipi/subagents": "3.0.0-alpha.18",
37
- "@pi-unipi/utility": "3.0.0-alpha.18"
35
+ "@pi-unipi/core": "3.0.0-alpha.19",
36
+ "@pi-unipi/subagents": "3.0.0-alpha.19",
37
+ "@pi-unipi/utility": "3.0.0-alpha.19"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@earendil-works/pi-ai": "^0.87.1",
@@ -320,7 +320,11 @@ export default function fusionExtension(pi: ExtensionAPI): void {
320
320
  onProgress: () => publishStatusLater(),
321
321
  onStep: (step) => {
322
322
  try {
323
- pi.appendEntry("sidekick-step", step as unknown as Record<string, unknown>);
323
+ // Group per handoff so resumed handoffs render as separate panels
324
+ // even with no lead turn between them; old entries without a
325
+ // group fall back to the shared "sidekick" run.
326
+ const handoffId = runtime?.latest()?.id;
327
+ pi.appendEntry("sidekick-step", { ...step, group: `sidekick:${handoffId ?? "default"}`, label: "Sidekick" });
324
328
  } catch {
325
329
  /* entry rendering never affects the handoff */
326
330
  }
@@ -350,8 +354,13 @@ export default function fusionExtension(pi: ExtensionAPI): void {
350
354
  return `Sidekick tokens: in ${String(runtime.usage.input)} · out ${String(runtime.usage.output)} · cached ${String(runtime.usage.cacheRead)} · cache write ${String(runtime.usage.cacheWrite)}\nSidekick cost: $${savings.sidekickUsd.toFixed(2)} · at lead prices: $${savings.atLeadUsd.toFixed(2)} · saved: $${savings.savedUsd.toFixed(2)}\nHandoffs: ${String(runtime.reports.size)} · runtime alive: ${String(runtime.isAlive())} · busy: ${String(runtime.isBusy())}${pricing}`;
351
355
  }
352
356
 
353
- pi.registerEntryRenderer("sidekick-step", (entry: { data?: unknown }, options: { expanded?: boolean }, theme) =>
354
- renderSidekickStep(entry.data as never, options.expanded === true, theme as never));
357
+ pi.registerEntryRenderer("sidekick-step", (entry: { data?: { group?: string; label?: string } }, options: { expanded?: boolean }, theme) =>
358
+ renderSidekickStep(
359
+ entry.data as never,
360
+ options.expanded === true,
361
+ theme as never,
362
+ { group: entry.data?.group ?? "sidekick", label: entry.data?.label },
363
+ ));
355
364
 
356
365
  registerFusionTools(pi, {
357
366
  getRuntime,
@@ -1,20 +1,15 @@
1
1
  import { Markdown, type Component } from "@earendil-works/pi-tui";
2
2
  import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
3
- import {
4
- nativeToolComponent,
5
- renderStyle,
6
- styledComponent,
7
- styledTextLines,
8
- styledToolCallLines,
9
- } from "@pi-unipi/utility/src/render/styled.js";
10
- import { paintLine, replyBg, trimEdgeBlankLines } from "@pi-unipi/utility/src/render/reply-bg.js";
11
- import type { SpacingGroupPosition } from "@pi-unipi/utility/src/render/spacing.js";
3
+ import { renderDelegatedStep, type DelegatedStep } from "@pi-unipi/utility/src/render/delegated.js";
12
4
  import type { SidekickStep } from "./sidekick-runtime.js";
13
5
 
14
6
  export interface ThemeLike {
15
7
  fg: (color: string, text: string) => string;
16
8
  bold: (text: string) => string;
17
9
  getBgAnsi?: (key: string) => string;
10
+ /** Colour mode when the caller knows it — the delegated panel uses it for
11
+ * its truecolor/256 rail+fill+fg fallbacks. */
12
+ getColorMode?: () => "truecolor" | "256color";
18
13
  }
19
14
 
20
15
  export function duration(ms: number): string {
@@ -47,85 +42,24 @@ export function primaryArg(name: string, args: Record<string, unknown> | undefin
47
42
 
48
43
  // ─── sidekick-step entry renderer (Devin-style merged steps) ────────────────
49
44
 
50
- /** The sidekick rail: muted left border on the custom-message background —
51
- * the tinted panel Devin draws around delegated work. */
52
- const RAIL = "▏ ";
53
-
54
- function railBg(theme: ThemeLike): string {
55
- try {
56
- return theme.getBgAnsi?.("customMessageBg") || replyBg();
57
- } catch {
58
- return replyBg();
59
- }
45
+ export interface StepIdentity {
46
+ /** spacingGroup run — unique per handoff so resumed handoffs don't merge. */
47
+ group?: string;
48
+ /** Panel header drawn once on the group's first position (e.g. "Sidekick"). */
49
+ label?: string;
60
50
  }
61
51
 
62
52
  /**
63
- * One sidekick step in the lead chat, drawn in the active render style
64
- * (simple mcode row / advanced header+gutter / regular pi card) behind a
65
- * `▏` border on the custom-message background — every line of the step sits
66
- * on the rail so a sidekick block reads as one tinted unit.
67
- *
68
- * Steps carry `spacingGroup: "sidekick"`: adjacent steps in the transcript
69
- * join into one rail block with no blank separator, and the spacing patch
70
- * reports each member's position so a consecutive run of tool steps draws
71
- * the same `├…├…└` tree the lead's tools get (last member or a step
72
- * followed by prose gets `└`).
53
+ * One sidekick step in the lead chat, drawn by the shared delegated-panel
54
+ * helper: the active render style behind the approved cyan `▏` rail on a
55
+ * single dark-cyan fill. Steps carry `spacingGroup` so adjacent steps of the
56
+ * same handoff join into one continuous panel (`├…└` tree, no blank
57
+ * separators); the optional identity comes from the persisted entry (older
58
+ * entries without one fall back to the shared "sidekick" group).
73
59
  */
74
- export function renderSidekickStep(step: SidekickStep, expanded: boolean, theme: ThemeLike): Component {
75
- const t = theme;
76
- let pos: SpacingGroupPosition | undefined;
77
- let native: (Component & { setExpanded?: (b: boolean) => void }) | null | undefined;
78
- const comp = styledComponent((width: number) => {
79
- const inner = Math.max(8, width - 2);
80
- const style = renderStyle();
81
- let lines: string[];
82
- if (step.kind === "text") {
83
- lines = styledTextLines(style, step.text, { thinking: expanded ? step.thinking : undefined }, t, inner);
84
- } else if (style === "regular") {
85
- if (native === undefined) {
86
- native = nativeToolComponent({
87
- name: step.name,
88
- args: step.args,
89
- output: step.output,
90
- isError: step.isError,
91
- expanded,
92
- }) ?? null;
93
- }
94
- if (native !== null) {
95
- native.setExpanded?.(expanded);
96
- lines = trimEdgeBlankLines(native.render(inner));
97
- } else {
98
- lines = styledToolCallLines("regular", {
99
- name: step.name,
100
- arg: step.arg,
101
- output: step.output,
102
- isError: step.isError,
103
- expanded,
104
- durationMs: step.durationMs,
105
- }, t, inner);
106
- }
107
- } else {
108
- lines = styledToolCallLines(style, {
109
- name: step.name,
110
- arg: step.arg,
111
- output: step.output,
112
- isError: step.isError,
113
- expanded,
114
- durationMs: step.durationMs,
115
- connector: pos !== undefined && pos.nextKind === "tool" ? "├" : "└",
116
- }, t, inner);
117
- }
118
- const bg = railBg(t);
119
- return lines.map((l) => paintLine(`${t.fg("borderMuted", RAIL)}${l}`, width, bg));
120
- }) as Component & {
121
- spacingGroup: string;
122
- spacingKind: string;
123
- setGroupPosition: (p: SpacingGroupPosition) => void;
124
- };
125
- comp.spacingGroup = "sidekick";
126
- comp.spacingKind = step.kind === "tool" ? "tool" : "text";
127
- comp.setGroupPosition = (p) => {
128
- pos = p;
129
- };
130
- return comp;
60
+ export function renderSidekickStep(step: SidekickStep, expanded: boolean, theme: ThemeLike, identity?: StepIdentity): Component {
61
+ return renderDelegatedStep(step as DelegatedStep, expanded, theme as never, {
62
+ group: identity?.group ?? "sidekick",
63
+ label: identity?.label,
64
+ });
131
65
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/info-screen",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Dashboard and module registry for Unipi — configurable info overlay with tabbed groups",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -32,7 +32,7 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "3.0.0-alpha.18"
35
+ "@pi-unipi/core": "3.0.0-alpha.19"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/input-shortcuts",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Keyboard shortcuts for stash/restore, undo/redo, clipboard, and thinking toggle — chord-based overlay system",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@pi-unipi/core": "3.0.0-alpha.18"
36
+ "@pi-unipi/core": "3.0.0-alpha.19"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/kanboard",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Kanboard v3 — deferred-work board: CLI/daemon bridge, task runner and skill",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -31,7 +31,7 @@
31
31
  "access": "public"
32
32
  },
33
33
  "dependencies": {
34
- "@pi-unipi/core": "3.0.0-alpha.18"
34
+ "@pi-unipi/core": "3.0.0-alpha.19"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -48,10 +48,10 @@
48
48
  "themes": []
49
49
  },
50
50
  "optionalDependencies": {
51
- "@pi-unipi/kanboard-linux-x64": "3.0.0-alpha.18",
52
- "@pi-unipi/kanboard-linux-arm64": "3.0.0-alpha.18",
53
- "@pi-unipi/kanboard-darwin-x64": "3.0.0-alpha.18",
54
- "@pi-unipi/kanboard-darwin-arm64": "3.0.0-alpha.18",
55
- "@pi-unipi/kanboard-win32-x64": "3.0.0-alpha.18"
51
+ "@pi-unipi/kanboard-linux-x64": "3.0.0-alpha.19",
52
+ "@pi-unipi/kanboard-linux-arm64": "3.0.0-alpha.19",
53
+ "@pi-unipi/kanboard-darwin-x64": "3.0.0-alpha.19",
54
+ "@pi-unipi/kanboard-darwin-arm64": "3.0.0-alpha.19",
55
+ "@pi-unipi/kanboard-win32-x64": "3.0.0-alpha.19"
56
56
  }
57
57
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/long-horizon",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Long-horizon execution for Pi — mode-gated /goal /ralph /swarm /graph orchestration with a TypeSafe prompt judge",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -37,7 +37,7 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@pi-unipi/core": "3.0.0-alpha.18"
40
+ "@pi-unipi/core": "3.0.0-alpha.19"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/mcp",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "MCP server management extension for Pi coding agent — browse, add, configure, and use MCP servers",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -30,7 +30,7 @@
30
30
  "test": "npx tsx --test tests/**/*.test.ts"
31
31
  },
32
32
  "dependencies": {
33
- "@pi-unipi/core": "3.0.0-alpha.18"
33
+ "@pi-unipi/core": "3.0.0-alpha.19"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/memory",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Persistent cross-session memory on MemPalace (auto-installed): daemon-routed writes, shared palace with other agents, for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -42,8 +42,8 @@
42
42
  "README.md"
43
43
  ],
44
44
  "dependencies": {
45
- "@pi-unipi/core": "3.0.0-alpha.18",
46
- "@pi-unipi/info-screen": "3.0.0-alpha.18",
45
+ "@pi-unipi/core": "3.0.0-alpha.19",
46
+ "@pi-unipi/info-screen": "3.0.0-alpha.19",
47
47
  "js-yaml": "^4.1.0"
48
48
  },
49
49
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/notify",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Cross-platform notification extension for Pi — native OS, Gotify, and Telegram notifications for agent lifecycle events",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "3.0.0-alpha.18",
37
+ "@pi-unipi/core": "3.0.0-alpha.19",
38
38
  "node-notifier": "^10.0.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/skill-registry",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Skill registry for pi — per-project skill on/off, a skill vault, and jev-judged skill exposure",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "main": "index.ts",
35
35
  "dependencies": {
36
- "@pi-unipi/core": "3.0.0-alpha.18"
36
+ "@pi-unipi/core": "3.0.0-alpha.19"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/subagents",
3
- "version": "3.0.0-alpha.18",
3
+ "version": "3.0.0-alpha.19",
4
4
  "description": "Subagents for UniPi — Devin-model independent child agents",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -9,7 +9,8 @@
9
9
  "test": "npx tsx --test src/__tests__/*.test.ts"
10
10
  },
11
11
  "dependencies": {
12
- "@pi-unipi/core": "3.0.0-alpha.18",
12
+ "@pi-unipi/core": "3.0.0-alpha.19",
13
+ "@pi-unipi/utility": "3.0.0-alpha.19",
13
14
  "@earendil-works/pi-agent-core": "^0.87.1",
14
15
  "acorn": "8.18.0"
15
16
  },
@@ -16,12 +16,13 @@
16
16
  import { type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
17
17
  import { Type } from "typebox";
18
18
  import { Key, matchesKey, truncateToWidth, type TUI } from "@earendil-works/pi-tui";
19
+ import { renderDelegatedStep, type DelegatedStep } from "@pi-unipi/utility/src/render/delegated.js";
19
20
  import {
20
21
  UNIPI_EVENTS, emitEvent, getSettings, isChildProcess, registerSettings, registerWaitSource, getSharedFusionStatus, SPINNER_MS,
21
22
  } from "@pi-unipi/core";
22
23
  import {
23
24
  ensureReadSubagentTool, registerSubagentReader, setReadSubagentDemand,
24
- createCompletionDelivery, type HandoffReport,
25
+ createCompletionDelivery, type HandoffReport, type SidekickStep,
25
26
  } from "@pi-unipi/core/child-agent.js";
26
27
  import {
27
28
  SubagentManager, backgroundRunningReason, canSpawn, getSharedSubagents, subscribeSubagents, recordStatusFor,
@@ -146,8 +147,33 @@ export function resolveSubagentThinking(
146
147
 
147
148
  export { cardOutcome };
148
149
 
149
- export default function subagents(pi: ExtensionAPI): void {
150
- const manager = new SubagentManager();
150
+ /**
151
+ * UI-only completed-step entry: payload + guarded append, identical for
152
+ * foreground and background runs. The final report is intentionally never
153
+ * streamed here — the lead reports it itself. Exported for tests.
154
+ */
155
+ export function appendSubagentStep(
156
+ appendEntry: (type: string, data: Record<string, unknown>) => void,
157
+ record: Pick<SubagentRecord, "id" | "title" | "profile" | "startedAt">,
158
+ step: SidekickStep,
159
+ ): void {
160
+ try {
161
+ appendEntry("subagent-step", {
162
+ ...step,
163
+ agentId: record.id,
164
+ id: record.id,
165
+ title: record.title,
166
+ profile: record.profile,
167
+ group: `subagent:${record.id}:${record.startedAt}`,
168
+ label: `${profileLabel(record.profile)} subagent · ${record.title}`,
169
+ });
170
+ } catch {
171
+ /* entry rendering never affects the run */
172
+ }
173
+ }
174
+
175
+ export default function subagents(pi: ExtensionAPI, deps?: { manager?: SubagentManager }): void {
176
+ const manager = deps?.manager ?? new SubagentManager();
151
177
  const delivery = createCompletionDelivery<HandoffReport>((report) => deliverCompletion(report));
152
178
 
153
179
  // Turn-arbiter wait source (lead only): a background subagent is still
@@ -462,7 +488,6 @@ export default function subagents(pi: ExtensionAPI): void {
462
488
  syncUi();
463
489
  }
464
490
  }
465
-
466
491
  async function runSubagent(
467
492
  _toolCallId: string,
468
493
  params: { title: string; task: string; profile: string; is_background?: boolean; resume?: string },
@@ -489,6 +514,7 @@ export default function subagents(pi: ExtensionAPI): void {
489
514
  background: resumeId === undefined && params.is_background === true,
490
515
  resume: resumeId,
491
516
  maxConcurrent: config.maxConcurrent,
517
+ onStep: (record, step) => appendSubagentStep((type, data) => pi.appendEntry(type, data), record, step),
492
518
  });
493
519
  if ("error" in start) return textResult(start.error, { owner: "subagents", status: "failed", title: params.title, error: start.error }, true);
494
520
  const { run } = start;
@@ -578,6 +604,16 @@ export default function subagents(pi: ExtensionAPI): void {
578
604
  pi.registerMessageRenderer("subagent-completion", (message: { details?: CardDetails & { report?: string } }, opts: { expanded?: boolean }, theme) =>
579
605
  renderCompletion(message.details, opts.expanded === true, theme));
580
606
 
607
+ // Completed subagent steps as a delegated panel (shared UNI-2 renderer;
608
+ // historical entries keep their cards, new ones stream below them).
609
+ pi.registerEntryRenderer("subagent-step", (entry: { data?: DelegatedStep & { group?: string; label?: string } }, opts: { expanded?: boolean }, theme) =>
610
+ renderDelegatedStep(
611
+ entry.data as DelegatedStep,
612
+ opts.expanded === true,
613
+ theme as never,
614
+ { group: entry.data?.group ?? "subagent", label: entry.data?.label },
615
+ ));
616
+
581
617
  // ── Prompt section (only while enabled) ───────────────────────────────────
582
618
  pi.on("before_agent_start", (event, ctx) => {
583
619
  refresh(ctx.cwd ?? process.cwd(), ctx);