pi-minimalist 0.3.1 → 0.4.0
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/index.ts +18 -2
- package/package.json +13 -4
- package/src/bridge.ts +20 -2
- package/src/config-ui.ts +1 -1
- package/src/core-patch.ts +18 -2
package/index.ts
CHANGED
|
@@ -34,7 +34,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
34
34
|
// strand every row on stale state.
|
|
35
35
|
const { config, grouping } = sharedState(loadSettings());
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
// A timer (the post-stream thinking linger) must repaint outside any render.
|
|
38
|
+
// Extensions get no TUI handle except through a factory, so an invisible
|
|
39
|
+
// widget (zero lines, below the editor, no spacer) captures it.
|
|
40
|
+
let tui: { requestRender(): void } | undefined;
|
|
41
|
+
installBridges({ config, grouping, requestRender: () => tui?.requestRender() });
|
|
38
42
|
// Idempotent, so a /reload only refreshes the Bridge the wrappers read.
|
|
39
43
|
patchCore(core);
|
|
40
44
|
|
|
@@ -46,7 +50,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
46
50
|
// restart. Session overrides are re-applied by Config.replace().
|
|
47
51
|
config.replace(loadSettings());
|
|
48
52
|
};
|
|
49
|
-
pi.on("session_start",
|
|
53
|
+
pi.on("session_start", (_event, ctx) => {
|
|
54
|
+
refresh();
|
|
55
|
+
if (ctx.hasUI && ctx.mode === "tui") {
|
|
56
|
+
ctx.ui.setWidget(
|
|
57
|
+
"pi-minimalist-repaint",
|
|
58
|
+
(captured) => {
|
|
59
|
+
tui = captured;
|
|
60
|
+
return { render: () => [], invalidate() {} };
|
|
61
|
+
},
|
|
62
|
+
{ placement: "belowEditor" },
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
50
66
|
pi.on("turn_start", refresh);
|
|
51
67
|
pi.on("agent_start", () => grouping.agentStarted());
|
|
52
68
|
pi.on("agent_settled", () => grouping.agentSettled());
|
package/package.json
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-minimalist",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Compact one-line tool and thinking rows for Pi's interactive transcript",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package"
|
|
7
|
+
],
|
|
6
8
|
"license": "MIT",
|
|
7
9
|
"author": "Pinglei He",
|
|
8
10
|
"repository": {
|
|
@@ -10,13 +12,20 @@
|
|
|
10
12
|
"url": "git+https://github.com/EviHex/pi-minimalist.git"
|
|
11
13
|
},
|
|
12
14
|
"type": "module",
|
|
13
|
-
"files": [
|
|
15
|
+
"files": [
|
|
16
|
+
"index.ts",
|
|
17
|
+
"src/",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
14
21
|
"peerDependencies": {
|
|
15
22
|
"@earendil-works/pi-coding-agent": "*",
|
|
16
23
|
"@earendil-works/pi-tui": "*"
|
|
17
24
|
},
|
|
18
25
|
"pi": {
|
|
19
|
-
"extensions": [
|
|
26
|
+
"extensions": [
|
|
27
|
+
"./index.ts"
|
|
28
|
+
],
|
|
20
29
|
"image": "https://raw.githubusercontent.com/EviHex/pi-minimalist/main/docs/click.png"
|
|
21
30
|
}
|
|
22
31
|
}
|
package/src/bridge.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { RunGrouping } from "./run-grouping.ts";
|
|
|
16
16
|
import { Painter, type Row, type ThemeLike } from "./row.ts";
|
|
17
17
|
import { createToolRenderer, type ToolRenderer } from "./tool-renderer.ts";
|
|
18
18
|
import { createThinkingPreview, singleHueThinkingTheme, type ThinkingPreview } from "./thinking.ts";
|
|
19
|
-
import type
|
|
19
|
+
import { realTimers, type Timers } from "./components.ts";
|
|
20
20
|
|
|
21
21
|
export const BRIDGE_SYMBOLS = {
|
|
22
22
|
/** The `Bridge` object: everything the prototype wrappers call. */
|
|
@@ -42,6 +42,11 @@ export type Bridge = {
|
|
|
42
42
|
compactThinking: () => boolean;
|
|
43
43
|
/** Keep a STREAMING thinking block expanded. */
|
|
44
44
|
keepActiveThinkingExpanded: () => boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Run `callback` once, `THINKING_LINGER_MS` after a streamed message ends,
|
|
47
|
+
* then repaint. Keeps a short thought on screen long enough to be read.
|
|
48
|
+
*/
|
|
49
|
+
afterThinkingLinger: (callback: () => void) => void;
|
|
45
50
|
|
|
46
51
|
/** Chronology hooks that keep thinking and prose in transcript order. */
|
|
47
52
|
observeThinking: (owner: object, runIndex: number, streaming: boolean, hidden: boolean, timestamp?: number) => string;
|
|
@@ -103,8 +108,13 @@ export function sharedState(settings?: ConstructorParameters<typeof Config>[0]):
|
|
|
103
108
|
export type InstallOptions = SharedState & {
|
|
104
109
|
/** Injectable repaint scheduler; production uses Node's timers. */
|
|
105
110
|
timers?: Timers;
|
|
111
|
+
/** Repaint the TUI from outside a render (a timer). No-op without a TUI. */
|
|
112
|
+
requestRender?: () => void;
|
|
106
113
|
};
|
|
107
114
|
|
|
115
|
+
/** How long expanded thinking stays open after its message stops streaming. */
|
|
116
|
+
export const THINKING_LINGER_MS = 3000;
|
|
117
|
+
|
|
108
118
|
/**
|
|
109
119
|
* Install the bridge.
|
|
110
120
|
*
|
|
@@ -112,7 +122,7 @@ export type InstallOptions = SharedState & {
|
|
|
112
122
|
* from `session_shutdown`: reload ordering can let an old shutdown hook erase the
|
|
113
123
|
* newly installed bridge. Process exit clears globalThis naturally.
|
|
114
124
|
*/
|
|
115
|
-
export function installBridges({ config, grouping, timers }: InstallOptions): void {
|
|
125
|
+
export function installBridges({ config, grouping, timers = realTimers, requestRender }: InstallOptions): void {
|
|
116
126
|
const globals = globalThis as Globals;
|
|
117
127
|
const painter = (theme: ThemeLike) => new Painter(theme, config);
|
|
118
128
|
|
|
@@ -122,6 +132,14 @@ export function installBridges({ config, grouping, timers }: InstallOptions): vo
|
|
|
122
132
|
thinkingMarkdownTheme: (base, theme) => singleHueThinkingTheme(base, theme, config),
|
|
123
133
|
compactThinking: () => config.get("thinkingAsToolCall") === true,
|
|
124
134
|
keepActiveThinkingExpanded: () => config.get("keepActiveThinkingExpanded"),
|
|
135
|
+
afterThinkingLinger: (callback) => {
|
|
136
|
+
// Timers only has intervals; clearing on the first tick makes it one-shot.
|
|
137
|
+
const handle = timers.setInterval(() => {
|
|
138
|
+
timers.clearInterval(handle);
|
|
139
|
+
callback();
|
|
140
|
+
requestRender?.();
|
|
141
|
+
}, THINKING_LINGER_MS);
|
|
142
|
+
},
|
|
125
143
|
|
|
126
144
|
// Chronology: core walks message content in transcript order, and run
|
|
127
145
|
// folding depends on that order.
|
package/src/config-ui.ts
CHANGED
|
@@ -71,7 +71,7 @@ export const FIELDS: Field[] = [
|
|
|
71
71
|
{
|
|
72
72
|
key: "keepActiveThinkingExpanded",
|
|
73
73
|
label: "Show thinking while generating",
|
|
74
|
-
description: "Show the full thinking block as it arrives, instead of a one-line preview.",
|
|
74
|
+
description: "Show the full thinking block as it arrives (and for 3s after), instead of a one-line preview.",
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
key: "glyphStyle",
|
package/src/core-patch.ts
CHANGED
|
@@ -264,7 +264,10 @@ function isMouseRegion(value: any): boolean {
|
|
|
264
264
|
* `hideThinkingBlock` + the override map, then restoring both. Either way the
|
|
265
265
|
* click handler stores into the LIVE map, so an explicit Ctrl+T expansion
|
|
266
266
|
* always survives — the default only decides what happens when the user has
|
|
267
|
-
* not expressed a preference.
|
|
267
|
+
* not expressed a preference. When a streamed message ends, the forced
|
|
268
|
+
* expansion LINGERS for `THINKING_LINGER_MS`, then the message re-renders
|
|
269
|
+
* collapsed: a one-phrase thought would otherwise vanish before it is read.
|
|
270
|
+
* Replayed history never streamed, so it never lingers.
|
|
268
271
|
* - collapsed preview: swap the hidden run's `Text` for our compact line.
|
|
269
272
|
* - expanded theme: recolor the run's `Markdown` in place (its private `theme`
|
|
270
273
|
* field is read at render time, and this runs before the first render).
|
|
@@ -283,13 +286,26 @@ export function patchAssistantMessage(proto: AssistantProto): void {
|
|
|
283
286
|
// its mouse layout, so Pi's y (relative to the lines we returned) would
|
|
284
287
|
// miss the summary by that shift. Remember it to translate clicks back.
|
|
285
288
|
const mouseShift = new WeakMap<AssistantProto, { shift: number; height: number }>();
|
|
289
|
+
// Messages seen streaming, and those still inside their post-stream linger.
|
|
290
|
+
const streamed = new WeakSet<AssistantProto>();
|
|
291
|
+
const lingering = new WeakSet<AssistantProto>();
|
|
286
292
|
|
|
287
293
|
proto.updateContent = function (this: AssistantProto, message: any, isStreaming = this.isStreaming) {
|
|
288
294
|
const savedHide = this.hideThinkingBlock;
|
|
289
295
|
const savedOverrides = this.thinkingVisibilityOverrides;
|
|
296
|
+
const b = bridge();
|
|
297
|
+
const keepOpen = b?.keepActiveThinkingExpanded() === true;
|
|
298
|
+
if (isStreaming) streamed.add(this);
|
|
299
|
+
else if (streamed.delete(this) && keepOpen && b) {
|
|
300
|
+
lingering.add(this);
|
|
301
|
+
b.afterThinkingLinger(() => {
|
|
302
|
+
lingering.delete(this);
|
|
303
|
+
if (this.lastMessage) this.updateContent(this.lastMessage, false);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
290
306
|
// Default: a streaming block stays collapsed to its compact preview, which
|
|
291
307
|
// already shows the newest text. Opt in to force it open instead.
|
|
292
|
-
const forceOpen =
|
|
308
|
+
const forceOpen = keepOpen && (isStreaming || lingering.has(this));
|
|
293
309
|
if (forceOpen) {
|
|
294
310
|
this.hideThinkingBlock = false;
|
|
295
311
|
this.thinkingVisibilityOverrides = new Map();
|