@oh-my-pi/pi-tui 18.9.1 → 18.9.2
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 +10 -0
- package/dist/types/chrome/transcript-container.d.ts +5 -0
- package/dist/types/tmux.d.ts +10 -0
- package/package.json +9 -9
- package/src/chrome/transcript-container.ts +22 -2
- package/src/terminal.ts +39 -0
- package/src/tmux.ts +43 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.9.2] - 2026-10-11
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added native TSP rendering support for Tern terminal sessions running inside tmux, with automatic session detection and safe title-setting behavior.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Improved responsiveness when inserting assistant messages after tool calls in long transcripts.
|
|
14
|
+
|
|
5
15
|
## [18.9.1] - 2026-10-11
|
|
6
16
|
|
|
7
17
|
### Fixed
|
|
@@ -63,6 +63,11 @@ export interface TranscriptViewportSpan {
|
|
|
63
63
|
export declare class TranscriptContainer extends Container {
|
|
64
64
|
#private;
|
|
65
65
|
addChild(component: Component): void;
|
|
66
|
+
/**
|
|
67
|
+
* `#entries` mirrors `children`; move both together or the next `#syncEntries`
|
|
68
|
+
* pays a full O(n) rebuild.
|
|
69
|
+
*/
|
|
70
|
+
insertChildAt(component: Component, index: number): void;
|
|
66
71
|
/** Removes `component` if {@link canRemoveBlock} allows it; otherwise does nothing. */
|
|
67
72
|
removeChild(component: Component): void;
|
|
68
73
|
clear(): void;
|
package/dist/types/tmux.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
export { wrapTmuxPassthrough } from "./multiplexers/tmux.js";
|
|
2
2
|
/** Pass a control sequence through tmux, leaving direct-terminal output unchanged. */
|
|
3
3
|
export declare function wrapTmuxPassthroughIfNeeded(payload: string, env?: NodeJS.ProcessEnv): string;
|
|
4
|
+
/**
|
|
5
|
+
* Whether the terminal hosting this tmux session draws TSP programs natively:
|
|
6
|
+
* it sets the session user option `@tern-tsp` while it watches the session.
|
|
7
|
+
*/
|
|
8
|
+
export declare function tmuxSessionSpeaksTsp(env?: NodeJS.ProcessEnv): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Stop (`on` false) or restore (`on` true) tmux turning this pane's APC
|
|
11
|
+
* strings into its title, which would otherwise swallow every TSP message.
|
|
12
|
+
*/
|
|
13
|
+
export declare function setTmuxPaneApcTitles(on: boolean, env?: NodeJS.ProcessEnv): void;
|
|
4
14
|
/**
|
|
5
15
|
* Resolve the terminal emulator name recorded for this tmux client.
|
|
6
16
|
*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "18.9.
|
|
4
|
+
"version": "18.9.2",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"gen:glyphs": "bun scripts/gen-glyphs.ts"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@oh-my-pi/omptype": "18.9.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "18.9.
|
|
46
|
-
"@oh-my-pi/pi-ai": "18.9.
|
|
47
|
-
"@oh-my-pi/pi-catalog": "18.9.
|
|
48
|
-
"@oh-my-pi/pi-natives": "18.9.
|
|
49
|
-
"@oh-my-pi/pi-utils": "18.9.
|
|
50
|
-
"@oh-my-pi/pi-wire": "18.9.
|
|
51
|
-
"@oh-my-pi/snapcompact": "18.9.
|
|
44
|
+
"@oh-my-pi/omptype": "18.9.2",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "18.9.2",
|
|
46
|
+
"@oh-my-pi/pi-ai": "18.9.2",
|
|
47
|
+
"@oh-my-pi/pi-catalog": "18.9.2",
|
|
48
|
+
"@oh-my-pi/pi-natives": "18.9.2",
|
|
49
|
+
"@oh-my-pi/pi-utils": "18.9.2",
|
|
50
|
+
"@oh-my-pi/pi-wire": "18.9.2",
|
|
51
|
+
"@oh-my-pi/snapcompact": "18.9.2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"kitty-vt-wasm": "^0.2.0"
|
|
@@ -227,11 +227,31 @@ export class TranscriptContainer extends Container {
|
|
|
227
227
|
emitted: 0,
|
|
228
228
|
stableFrozen: false,
|
|
229
229
|
});
|
|
230
|
-
//
|
|
231
|
-
//
|
|
230
|
+
// `children` is public and may be edited externally; re-check the whole
|
|
231
|
+
// list once instead of trusting positions. Insert-after-anchor callers
|
|
232
|
+
// use insertChildAt, which keeps both arrays in lockstep.
|
|
232
233
|
this.#entriesUnverified = true;
|
|
233
234
|
}
|
|
234
235
|
|
|
236
|
+
/**
|
|
237
|
+
* `#entries` mirrors `children`; move both together or the next `#syncEntries`
|
|
238
|
+
* pays a full O(n) rebuild.
|
|
239
|
+
*/
|
|
240
|
+
insertChildAt(component: Component, index: number): void {
|
|
241
|
+
this.addChild(component);
|
|
242
|
+
const from = this.children.length - 1;
|
|
243
|
+
if (index >= from) {
|
|
244
|
+
this.#entriesUnverified = false;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const [moved] = this.children.splice(from, 1);
|
|
248
|
+
if (moved) this.children.splice(index, 0, moved);
|
|
249
|
+
const [entry] = this.#entries.splice(this.#entries.length - 1, 1);
|
|
250
|
+
if (entry) this.#entries.splice(index, 0, entry);
|
|
251
|
+
// Both arrays moved in lockstep, so the live-tail check applies again.
|
|
252
|
+
this.#entriesUnverified = false;
|
|
253
|
+
}
|
|
254
|
+
|
|
235
255
|
/** Removes `component` if {@link canRemoveBlock} allows it; otherwise does nothing. */
|
|
236
256
|
override removeChild(component: Component): void {
|
|
237
257
|
const index = this.#removableIndex(component);
|
package/src/terminal.ts
CHANGED
|
@@ -28,8 +28,10 @@ import {
|
|
|
28
28
|
setTerminalGlyphProtocol,
|
|
29
29
|
TERMINAL,
|
|
30
30
|
} from "./terminal-capabilities";
|
|
31
|
+
import { tmuxMultiplexer } from "./multiplexers/tmux";
|
|
31
32
|
import type { TerminalMultiplexerBackgroundColorCache } from "./multiplexers/types";
|
|
32
33
|
import { terminalMultiplexerSessions } from "./terminal-multiplexer";
|
|
34
|
+
import { setTmuxPaneApcTitles, tmuxSessionSpeaksTsp } from "./tmux";
|
|
33
35
|
import { setHangulCompatibilityJamoWidth } from "./utils";
|
|
34
36
|
import { translateWindowsAltGrSequence } from "./windows-altgr";
|
|
35
37
|
import { Win32InputModeDecoder, Win32PasteMarkerNormalizer } from "./windows-input-mode";
|
|
@@ -50,6 +52,9 @@ const WIN32_RELAYED_ESCAPE_TIMEOUT_MS = 75;
|
|
|
50
52
|
*/
|
|
51
53
|
const TSP_OSC_PREFIX = `\x1b]877;${TSP_PREFIX.slice(2)}`;
|
|
52
54
|
|
|
55
|
+
/** How long a TSP hello waits for its reply inside tmux, which answers DA1 itself. */
|
|
56
|
+
const TSP_TMUX_HELLO_TIMEOUT_MS = 1000;
|
|
57
|
+
|
|
53
58
|
/**
|
|
54
59
|
* The TSP message a reassembled input string holds, in APC form, or
|
|
55
60
|
* `undefined` while its terminator has not arrived. An OSC 877 message may end
|
|
@@ -874,6 +879,10 @@ export class ProcessTerminal implements Terminal {
|
|
|
874
879
|
#tspPending = false;
|
|
875
880
|
#tspResult: TspHello | null | undefined;
|
|
876
881
|
#tspCallbacks: TspHelloHandler[] = [];
|
|
882
|
+
/** Hello deadline inside tmux, where no DA1 sentinel can end the probe. */
|
|
883
|
+
#tspTimeout: NodeJS.Timeout | undefined;
|
|
884
|
+
/** This tmux pane's APC titles are off for TSP and must be restored. */
|
|
885
|
+
#tspTmuxPaneGated = false;
|
|
877
886
|
#tspReplyBuffer = "";
|
|
878
887
|
#privateCsiResponseBuffer = "";
|
|
879
888
|
#da1SentinelOwners: Da1SentinelOwner[] = [];
|
|
@@ -1831,6 +1840,9 @@ export class ProcessTerminal implements Terminal {
|
|
|
1831
1840
|
#shouldQueryTspSupport(): boolean {
|
|
1832
1841
|
const override = $env.PI_TUI_NATIVE;
|
|
1833
1842
|
if (override === "0") return false;
|
|
1843
|
+
// tmux makes APC bodies the pane title unless the hosting terminal asked
|
|
1844
|
+
// for TSP through `@tern-tsp`: no override may force the probe there.
|
|
1845
|
+
if (tmuxMultiplexer.isInside($env)) return tmuxSessionSpeaksTsp($env);
|
|
1834
1846
|
if (override === "1") return true;
|
|
1835
1847
|
// Multiplexers swallow APC, so the reply could never arrive.
|
|
1836
1848
|
if (isInsideTerminalMultiplexer($env)) return false;
|
|
@@ -1848,10 +1860,30 @@ export class ProcessTerminal implements Terminal {
|
|
|
1848
1860
|
return;
|
|
1849
1861
|
}
|
|
1850
1862
|
this.#tspPending = true;
|
|
1863
|
+
if (tmuxMultiplexer.isInside($env)) {
|
|
1864
|
+
// tmux answers DA1 itself, ahead of any reply: wait for the reply instead.
|
|
1865
|
+
this.#tspTmuxPaneGated = true;
|
|
1866
|
+
setTmuxPaneApcTitles(false, $env);
|
|
1867
|
+
this.#tspTimeout = setTimeout(() => {
|
|
1868
|
+
this.#tspTimeout = undefined;
|
|
1869
|
+
this.#resolveTspSupport(null);
|
|
1870
|
+
}, TSP_TMUX_HELLO_TIMEOUT_MS);
|
|
1871
|
+
this.#safeWrite(encodeTspHelloQuery());
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1851
1874
|
this.#da1SentinelOwners.push({ kind: "tsp" });
|
|
1852
1875
|
this.#safeWrite(`${encodeTspHelloQuery()}\x1b[c`);
|
|
1853
1876
|
}
|
|
1854
1877
|
|
|
1878
|
+
/** Hand APC titles back to tmux once this pane stops speaking TSP. */
|
|
1879
|
+
#endTspTmuxPane(): void {
|
|
1880
|
+
clearTimeout(this.#tspTimeout);
|
|
1881
|
+
this.#tspTimeout = undefined;
|
|
1882
|
+
if (!this.#tspTmuxPaneGated) return;
|
|
1883
|
+
this.#tspTmuxPaneGated = false;
|
|
1884
|
+
setTmuxPaneApcTitles(true, $env);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1855
1887
|
#handleTspMessage(sequence: string): void {
|
|
1856
1888
|
const message = parseTspMessage(sequence);
|
|
1857
1889
|
if (message?.verb === "r" && message.reply.r === "hello") {
|
|
@@ -1867,6 +1899,12 @@ export class ProcessTerminal implements Terminal {
|
|
|
1867
1899
|
this.#tspPending = false;
|
|
1868
1900
|
const result = hello !== null && hello.v === TSP_VERSION ? hello : null;
|
|
1869
1901
|
if (hello !== null && result === null) logger.warn("TSP: unsupported protocol version", { v: hello.v });
|
|
1902
|
+
if (result === null) {
|
|
1903
|
+
this.#endTspTmuxPane();
|
|
1904
|
+
} else {
|
|
1905
|
+
clearTimeout(this.#tspTimeout);
|
|
1906
|
+
this.#tspTimeout = undefined;
|
|
1907
|
+
}
|
|
1870
1908
|
this.#tspResult = result;
|
|
1871
1909
|
for (const cb of this.#tspCallbacks) {
|
|
1872
1910
|
try {
|
|
@@ -2255,6 +2293,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
2255
2293
|
this.#sixelSupportCallbacks = [];
|
|
2256
2294
|
setTerminalGlyphProtocol(false);
|
|
2257
2295
|
this.#tspPending = false;
|
|
2296
|
+
this.#endTspTmuxPane();
|
|
2258
2297
|
this.#tspResult = undefined;
|
|
2259
2298
|
this.#tspReplyBuffer = "";
|
|
2260
2299
|
this.#tspCallbacks = [];
|
package/src/tmux.ts
CHANGED
|
@@ -31,6 +31,49 @@ function queryTmuxClientTerminalName(env: NodeJS.ProcessEnv): string | null {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
const TMUX_COMMAND_TIMEOUT_MS = 500;
|
|
35
|
+
|
|
36
|
+
/** Run a tmux command against the current server; its stdout, or null on failure. */
|
|
37
|
+
function runTmux(args: string[], env: NodeJS.ProcessEnv): string | null {
|
|
38
|
+
const tmux = $which("tmux", { PATH: env.PATH });
|
|
39
|
+
if (!tmux) return null;
|
|
40
|
+
try {
|
|
41
|
+
const result = Bun.spawnSync([tmux, ...args], {
|
|
42
|
+
env,
|
|
43
|
+
stdout: "pipe",
|
|
44
|
+
stderr: "ignore",
|
|
45
|
+
timeout: TMUX_COMMAND_TIMEOUT_MS,
|
|
46
|
+
killSignal: "SIGKILL",
|
|
47
|
+
});
|
|
48
|
+
return result.exitCode === 0 ? result.stdout.toString() : null;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Whether the terminal hosting this tmux session draws TSP programs natively:
|
|
56
|
+
* it sets the session user option `@tern-tsp` while it watches the session.
|
|
57
|
+
*/
|
|
58
|
+
export function tmuxSessionSpeaksTsp(env: NodeJS.ProcessEnv = Bun.env): boolean {
|
|
59
|
+
return (runTmux(["show-options", "-qv", "@tern-tsp"], env)?.trim() ?? "") !== "";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Stop (`on` false) or restore (`on` true) tmux turning this pane's APC
|
|
64
|
+
* strings into its title, which would otherwise swallow every TSP message.
|
|
65
|
+
*/
|
|
66
|
+
export function setTmuxPaneApcTitles(on: boolean, env: NodeJS.ProcessEnv = Bun.env): void {
|
|
67
|
+
const pane = env.TMUX_PANE;
|
|
68
|
+
if (!pane) return;
|
|
69
|
+
runTmux(
|
|
70
|
+
on
|
|
71
|
+
? ["set-option", "-pu", "-t", pane, "allow-set-title"]
|
|
72
|
+
: ["set-option", "-p", "-t", pane, "allow-set-title", "off"],
|
|
73
|
+
env,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
34
77
|
/**
|
|
35
78
|
* Resolve the terminal emulator name recorded for this tmux client.
|
|
36
79
|
*
|