pi-stereo-view 0.1.3 → 0.1.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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4 - 2026-10-20
4
+
5
+ ### Fixed
6
+
7
+ - Keep the split layout active while Pi-Bolt streams thinking or text. Later replies no longer require a restart to restore the panels; saved panel sizes and layout settings stay intact.
8
+ - Hide empty self-rendered tool blocks without leaving blank spacing or timestamps, even when the tool returns images to the model. Normal tool output and visibly rendered images remain visible.
9
+
10
+ ### Changed
11
+
12
+ - Show late output linked to a tool call directly below that call in Activity, even when other work has happened since. Activity search follows the same grouping; combined history keeps its original order.
13
+
3
14
  ## 0.1.3 - 2026-10-19
4
15
 
5
16
  - Show the saved split layout from the first visible frame, without briefly displaying the normal layout first.
package/README.md CHANGED
@@ -25,6 +25,8 @@ The chat becomes two zones: your conversation with the agent, and the agent's wo
25
25
 
26
26
  Unrecognized content stays in Conversation unless its metadata links it to a known tool call.
27
27
 
28
+ Late output linked to a tool call appears directly below that call in Activity, even if other work has happened since. Combined history keeps its original order.
29
+
28
30
  Messages and tools keep their familiar appearance. The input stays full width below both panels.
29
31
 
30
32
  ## See it in action
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-stereo-view",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "Keep Pi conversation separate from tool output and agent activity.",
6
6
  "license": "MIT",
package/src/content.ts CHANGED
@@ -24,6 +24,8 @@ interface NativeAssistant extends Container {
24
24
  contentContainer: Container;
25
25
  hasToolCalls: boolean;
26
26
  lastMessage?: { content: { type: string; text?: string; thinking?: string }[] };
27
+ contentIsStale?: boolean;
28
+ buildContent?(message: NonNullable<NativeAssistant["lastMessage"]>): void;
27
29
  setHideThinkingBlock(hidden: boolean): void;
28
30
  }
29
31
 
@@ -50,6 +52,14 @@ function assistant(component: Component): NativeAssistant | undefined {
50
52
  (native.lastMessage !== undefined && !Array.isArray(native.lastMessage.content))) {
51
53
  throw new Error("Pi's assistant content metadata is not supported");
52
54
  }
55
+ // Pi-Bolt builds streamed children lazily. Synchronize the real owner before
56
+ // indexing or cloning it, so a pane cannot rebuild both content kinds itself.
57
+ if (native.contentIsStale === true) {
58
+ if (!native.lastMessage || typeof native.buildContent !== "function") {
59
+ throw new Error("Pi's deferred assistant content is not supported");
60
+ }
61
+ native.buildContent(native.lastMessage);
62
+ }
53
63
  return native;
54
64
  }
55
65
 
@@ -120,7 +130,16 @@ export class NativeContentIndex {
120
130
  }
121
131
  spacing = [];
122
132
  }
123
- this.content = entries;
133
+ // Project late execution output beside its owner, never reorder native history.
134
+ const linked = new Map<Component, ContentEntry[]>();
135
+ for (const entry of entries) {
136
+ if (entry.owner === entry.source) continue;
137
+ const group = linked.get(entry.owner) ?? [];
138
+ group.push(entry);
139
+ linked.set(entry.owner, group);
140
+ }
141
+ this.content = entries.flatMap(entry => entry.owner === entry.source
142
+ ? [entry, ...(linked.get(entry.source) ?? [])] : []);
124
143
  this.signature = signature;
125
144
  }
126
145
 
@@ -1,4 +1,23 @@
1
- import type { Component } from "@earendil-works/pi-tui";
1
+ import { stripTerminalSequences, type Component } from "@earendil-works/pi-tui";
2
+
3
+ const imagePlacement = (line: string): boolean => line.includes("\x1b_G") || line.includes("\x1b]1337;File=");
4
+
5
+ function nativeLines(component: Component, width: number): string[] {
6
+ const lines = component.render(width);
7
+ const tool = component as Component & {
8
+ toolDefinition?: { renderShell?: string; renderCall?: unknown; renderResult?: unknown };
9
+ selfRenderHeight?: number;
10
+ };
11
+ const definition = tool.toolDefinition;
12
+ const height = tool.selfRenderHeight;
13
+ if (component.constructor.name !== "ToolExecutionComponent" || definition?.renderShell !== "self" ||
14
+ typeof definition.renderCall !== "function" || typeof definition.renderResult !== "function" ||
15
+ typeof height !== "number" || !Number.isInteger(height) || height < 0) return lines;
16
+ // Native self shells prepend one row, then append result images outside the custom UI.
17
+ // An intentionally empty custom UI must not be made visible by those automatic images.
18
+ const content = height ? lines.slice(1, height + 1) : [];
19
+ return content.some(line => imagePlacement(line) || stripTerminalSequences(line).trim().length > 0) ? lines : [];
20
+ }
2
21
 
3
22
  interface Subscription {
4
23
  original: Component["invalidate"];
@@ -180,9 +199,9 @@ export class NativeRenderCache {
180
199
  const initial = components(this.component);
181
200
  const observed = this.watch(initial);
182
201
  const before = this.revision;
183
- const lines = this.component.render(width);
202
+ const lines = nativeLines(this.component, width);
184
203
  this.outputVersion++;
185
- this.imagePlacement = lines.some(line => line.includes("\x1b_G") || line.includes("\x1b]1337;File="));
204
+ this.imagePlacement = lines.some(imagePlacement);
186
205
  const links: NodeLinks[] = [];
187
206
  const after = components(this.component, links);
188
207
  const stableGraph = initial.length === after.length &&