@oh-my-pi/pi-tui 16.3.6 → 16.3.8
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 +7 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/components/box.d.ts +1 -1
- package/dist/types/components/cancellable-loader.d.ts +1 -1
- package/dist/types/components/editor.d.ts +4 -4
- package/dist/types/components/image.d.ts +2 -2
- package/dist/types/components/input.d.ts +1 -1
- package/dist/types/components/loader.d.ts +2 -2
- package/dist/types/components/markdown.d.ts +2 -2
- package/dist/types/components/scroll-view.d.ts +2 -2
- package/dist/types/components/select-list.d.ts +3 -3
- package/dist/types/components/settings-list.d.ts +2 -2
- package/dist/types/components/spacer.d.ts +1 -1
- package/dist/types/components/tab-bar.d.ts +1 -1
- package/dist/types/components/text.d.ts +1 -1
- package/dist/types/components/truncated-text.d.ts +1 -1
- package/dist/types/desktop-notify.d.ts +1 -1
- package/dist/types/editor-component.d.ts +2 -2
- package/dist/types/index.d.ts +32 -32
- package/dist/types/keybindings.d.ts +1 -1
- package/dist/types/terminal-capabilities.d.ts +6 -0
- package/dist/types/terminal.d.ts +1 -1
- package/dist/types/tui.d.ts +3 -3
- package/package.json +3 -3
- package/src/autocomplete.ts +57 -45
- package/src/components/image.ts +18 -2
- package/src/terminal-capabilities.ts +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.3.7] - 2026-07-05
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed an issue where `@` file-reference tokens in slash-command arguments incorrectly triggered prompt-composer autocompletion when the command did not define argument completions.
|
|
10
|
+
- Fixed a memory leak caused by unbounded map growth in the image budget cache.
|
|
11
|
+
|
|
5
12
|
## [16.3.6] - 2026-07-04
|
|
6
13
|
|
|
7
14
|
### Added
|
|
@@ -19,6 +19,8 @@ export interface SlashCommand {
|
|
|
19
19
|
aliases?: string[];
|
|
20
20
|
description?: string;
|
|
21
21
|
argumentHint?: string;
|
|
22
|
+
/** Whether the command consumes argument text after the command name. False means the full input stays normal prompt text once args are present. */
|
|
23
|
+
allowArgs?: boolean;
|
|
22
24
|
/** Dynamic display-only description for slash-command autocomplete. Must be synchronous and side-effect free. */
|
|
23
25
|
getAutocompleteDescription?: () => string | undefined;
|
|
24
26
|
getArgumentCompletions?(argumentPrefix: string): Awaitable<AutocompleteItem[] | null>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type AutocompleteProvider } from "../autocomplete";
|
|
2
|
-
import type { SymbolTheme } from "../symbols";
|
|
3
|
-
import { type Component, type Focusable } from "../tui";
|
|
4
|
-
import { type SelectListTheme } from "./select-list";
|
|
1
|
+
import { type AutocompleteProvider } from "../autocomplete.js";
|
|
2
|
+
import type { SymbolTheme } from "../symbols.js";
|
|
3
|
+
import { type Component, type Focusable } from "../tui.js";
|
|
4
|
+
import { type SelectListTheme } from "./select-list.js";
|
|
5
5
|
export interface EditorTheme {
|
|
6
6
|
borderColor: (str: string) => string;
|
|
7
7
|
selectList: SelectListTheme;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type ImageDimensions } from "../terminal-capabilities";
|
|
2
|
-
import type { Component } from "../tui";
|
|
1
|
+
import { type ImageDimensions } from "../terminal-capabilities.js";
|
|
2
|
+
import type { Component } from "../tui.js";
|
|
3
3
|
export interface ImageTheme {
|
|
4
4
|
fallbackColor: (str: string) => string;
|
|
5
5
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { SymbolTheme } from "../symbols";
|
|
2
|
-
import type { Component } from "../tui";
|
|
1
|
+
import type { SymbolTheme } from "../symbols.js";
|
|
2
|
+
import type { Component } from "../tui.js";
|
|
3
3
|
/** Drop all L2 cache entries. Call on theme change to prevent stale styled output. */
|
|
4
4
|
export declare function clearRenderCache(): void;
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Component } from "../tui";
|
|
2
|
-
import { Ellipsis } from "../utils";
|
|
1
|
+
import type { Component } from "../tui.js";
|
|
2
|
+
import { Ellipsis } from "../utils.js";
|
|
3
3
|
type ScrollbarMode = "auto" | "always" | "never";
|
|
4
4
|
export interface ScrollViewTheme {
|
|
5
5
|
track?: (text: string) => string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type MouseRoutable, type SgrMouseEvent } from "../mouse";
|
|
2
|
-
import type { SymbolTheme } from "../symbols";
|
|
3
|
-
import type { Component } from "../tui";
|
|
1
|
+
import { type MouseRoutable, type SgrMouseEvent } from "../mouse.js";
|
|
2
|
+
import type { SymbolTheme } from "../symbols.js";
|
|
3
|
+
import type { Component } from "../tui.js";
|
|
4
4
|
export interface SelectItem {
|
|
5
5
|
value: string;
|
|
6
6
|
label: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { SgrMouseEvent } from "../mouse";
|
|
2
|
-
import type { Component } from "../tui";
|
|
1
|
+
import type { SgrMouseEvent } from "../mouse.js";
|
|
2
|
+
import type { Component } from "../tui.js";
|
|
3
3
|
export interface SettingItem {
|
|
4
4
|
/** Unique identifier for this setting */
|
|
5
5
|
id: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TerminalId, TerminalNotification } from "./terminal-capabilities";
|
|
1
|
+
import type { TerminalId, TerminalNotification } from "./terminal-capabilities.js";
|
|
2
2
|
/** Resolved notifier binary used to fan a notification out to D-Bus. */
|
|
3
3
|
export type DesktopNotifierKind = "notify-send" | "gdbus";
|
|
4
4
|
export interface DesktopNotifier {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
export * from "./autocomplete";
|
|
2
|
-
export * from "./components/box";
|
|
3
|
-
export * from "./components/cancellable-loader";
|
|
4
|
-
export * from "./components/editor";
|
|
5
|
-
export * from "./components/image";
|
|
6
|
-
export * from "./components/input";
|
|
7
|
-
export * from "./components/loader";
|
|
8
|
-
export * from "./components/markdown";
|
|
9
|
-
export * from "./components/scroll-view";
|
|
10
|
-
export * from "./components/select-list";
|
|
11
|
-
export * from "./components/settings-list";
|
|
12
|
-
export * from "./components/spacer";
|
|
13
|
-
export * from "./components/tab-bar";
|
|
14
|
-
export * from "./components/text";
|
|
15
|
-
export * from "./components/truncated-text";
|
|
16
|
-
export * from "./deccara";
|
|
17
|
-
export * from "./desktop-notify";
|
|
18
|
-
export type * from "./editor-component";
|
|
19
|
-
export * from "./fuzzy";
|
|
20
|
-
export * from "./keybindings";
|
|
21
|
-
export * from "./keys";
|
|
22
|
-
export * from "./kitty-graphics";
|
|
23
|
-
export * from "./latex-block";
|
|
24
|
-
export * from "./latex-to-unicode";
|
|
25
|
-
export * from "./mouse";
|
|
26
|
-
export * from "./stdin-buffer";
|
|
27
|
-
export type * from "./symbols";
|
|
28
|
-
export * from "./terminal";
|
|
29
|
-
export * from "./terminal-capabilities";
|
|
30
|
-
export * from "./ttyid";
|
|
31
|
-
export * from "./tui";
|
|
32
|
-
export * from "./utils";
|
|
1
|
+
export * from "./autocomplete.js";
|
|
2
|
+
export * from "./components/box.js";
|
|
3
|
+
export * from "./components/cancellable-loader.js";
|
|
4
|
+
export * from "./components/editor.js";
|
|
5
|
+
export * from "./components/image.js";
|
|
6
|
+
export * from "./components/input.js";
|
|
7
|
+
export * from "./components/loader.js";
|
|
8
|
+
export * from "./components/markdown.js";
|
|
9
|
+
export * from "./components/scroll-view.js";
|
|
10
|
+
export * from "./components/select-list.js";
|
|
11
|
+
export * from "./components/settings-list.js";
|
|
12
|
+
export * from "./components/spacer.js";
|
|
13
|
+
export * from "./components/tab-bar.js";
|
|
14
|
+
export * from "./components/text.js";
|
|
15
|
+
export * from "./components/truncated-text.js";
|
|
16
|
+
export * from "./deccara.js";
|
|
17
|
+
export * from "./desktop-notify.js";
|
|
18
|
+
export type * from "./editor-component.js";
|
|
19
|
+
export * from "./fuzzy.js";
|
|
20
|
+
export * from "./keybindings.js";
|
|
21
|
+
export * from "./keys.js";
|
|
22
|
+
export * from "./kitty-graphics.js";
|
|
23
|
+
export * from "./latex-block.js";
|
|
24
|
+
export * from "./latex-to-unicode.js";
|
|
25
|
+
export * from "./mouse.js";
|
|
26
|
+
export * from "./stdin-buffer.js";
|
|
27
|
+
export type * from "./symbols.js";
|
|
28
|
+
export * from "./terminal.js";
|
|
29
|
+
export * from "./terminal-capabilities.js";
|
|
30
|
+
export * from "./ttyid.js";
|
|
31
|
+
export * from "./tui.js";
|
|
32
|
+
export * from "./utils.js";
|
|
@@ -42,6 +42,12 @@ export declare class TerminalInfo {
|
|
|
42
42
|
export declare function isInsideTmux(env?: NodeJS.ProcessEnv): boolean;
|
|
43
43
|
/** Detect terminal multiplexers where scrollback clearing and height-change redraws are hostile. */
|
|
44
44
|
export declare function isInsideTerminalMultiplexer(env?: NodeJS.ProcessEnv): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Whether the agent process is running inside a Zellij session. Read fresh on
|
|
47
|
+
* each call (like {@link isInsideTmux}) so a session attached/detached mid-run
|
|
48
|
+
* is observed and tests can toggle `Bun.env.ZELLIJ` per case.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isInsideZellij(env?: NodeJS.ProcessEnv): boolean;
|
|
45
51
|
/**
|
|
46
52
|
* Wrap a control-sequence payload in tmux's DCS passthrough envelope. Each
|
|
47
53
|
* ESC byte inside `payload` is doubled per tmux's escape rules. tmux strips
|
package/dist/types/terminal.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type HangulCompatibilityJamoWidth } from "./utils";
|
|
1
|
+
import { type HangulCompatibilityJamoWidth } from "./utils.js";
|
|
2
2
|
export declare function resolveHangulCompatibilityJamoWidthFromTerminalIdentity(env?: NodeJS.ProcessEnv): HangulCompatibilityJamoWidth;
|
|
3
3
|
/**
|
|
4
4
|
* Split `data` into chunks whose encoded UTF-8 byte length is no greater than
|
package/dist/types/tui.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ImageBudget } from "./components/image";
|
|
2
|
-
import { type Terminal } from "./terminal";
|
|
3
|
-
import { visibleWidth } from "./utils";
|
|
1
|
+
import { ImageBudget } from "./components/image.js";
|
|
2
|
+
import { type Terminal } from "./terminal.js";
|
|
3
|
+
import { visibleWidth } from "./utils.js";
|
|
4
4
|
type InputListenerResult = {
|
|
5
5
|
consume?: boolean;
|
|
6
6
|
data?: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "16.3.
|
|
4
|
+
"version": "16.3.8",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/pi-natives": "16.3.
|
|
41
|
-
"@oh-my-pi/pi-utils": "16.3.
|
|
40
|
+
"@oh-my-pi/pi-natives": "16.3.8",
|
|
41
|
+
"@oh-my-pi/pi-utils": "16.3.8",
|
|
42
42
|
"lru-cache": "11.5.1",
|
|
43
43
|
"marked": "^18.0.5"
|
|
44
44
|
},
|
package/src/autocomplete.ts
CHANGED
|
@@ -182,6 +182,8 @@ export interface SlashCommand {
|
|
|
182
182
|
aliases?: string[];
|
|
183
183
|
description?: string;
|
|
184
184
|
argumentHint?: string;
|
|
185
|
+
/** Whether the command consumes argument text after the command name. False means the full input stays normal prompt text once args are present. */
|
|
186
|
+
allowArgs?: boolean;
|
|
185
187
|
/** Dynamic display-only description for slash-command autocomplete. Must be synchronous and side-effect free. */
|
|
186
188
|
getAutocompleteDescription?: () => string | undefined;
|
|
187
189
|
// Function to get argument completions for this command
|
|
@@ -387,44 +389,18 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
387
389
|
const currentLine = lines[cursorLine] || "";
|
|
388
390
|
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
|
389
391
|
|
|
390
|
-
// Check for @ file reference (fuzzy search) - must be after a delimiter or at start
|
|
391
|
-
const atPrefix = this.#extractAtPrefix(textBeforeCursor);
|
|
392
|
-
if (atPrefix) {
|
|
393
|
-
const { rawPrefix, isQuotedPrefix } = parsePathPrefix(atPrefix);
|
|
394
|
-
// Recursive fuzzy walks rooted outside the project (e.g. `@../`,
|
|
395
|
-
// `@~/`, `@/abs`) can be huge — a parent dir full of sibling
|
|
396
|
-
// projects blows past several seconds of latency. Outside cwd,
|
|
397
|
-
// fall back to plain prefix listing of the immediate directory
|
|
398
|
-
// (matches Claude Code's behavior). Inside cwd we keep the
|
|
399
|
-
// fuzzy-then-prefix flow.
|
|
400
|
-
if (rawPrefix.length > 0 && this.#isOutsideCwd(rawPrefix)) {
|
|
401
|
-
const items = await this.#getFileSuggestions(atPrefix);
|
|
402
|
-
if (items.length === 0) return null;
|
|
403
|
-
return { items, prefix: atPrefix };
|
|
404
|
-
}
|
|
405
|
-
const suggestions =
|
|
406
|
-
rawPrefix.length > 0
|
|
407
|
-
? await this.#getFuzzyFileSuggestions(rawPrefix, { isQuotedPrefix })
|
|
408
|
-
: await this.#getFileSuggestions("@");
|
|
409
|
-
if (suggestions.length === 0 && rawPrefix.length > 0) {
|
|
410
|
-
const fallback = await this.#getFileSuggestions(atPrefix);
|
|
411
|
-
if (fallback.length === 0) return null;
|
|
412
|
-
return { items: fallback, prefix: atPrefix };
|
|
413
|
-
}
|
|
414
|
-
if (suggestions.length === 0) return null;
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
items: suggestions,
|
|
418
|
-
prefix: atPrefix,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
392
|
const leadingSlashStart = findLeadingSlashCommandStart(textBeforeCursor);
|
|
423
393
|
const trailingSlashStart = findTrailingSlashCommandStart(textBeforeCursor);
|
|
424
394
|
const hasPromptTextBeforeTrailingSlash =
|
|
425
395
|
trailingSlashStart !== null &&
|
|
426
396
|
hasPromptTextBeforeSlash(lines, cursorLine, textBeforeCursor, trailingSlashStart);
|
|
427
|
-
const
|
|
397
|
+
const hasPromptTextBeforeLeadingSlash =
|
|
398
|
+
leadingSlashStart !== null && hasPromptTextBeforeSlash(lines, cursorLine, textBeforeCursor, leadingSlashStart);
|
|
399
|
+
const slashStart = hasPromptTextBeforeTrailingSlash
|
|
400
|
+
? trailingSlashStart
|
|
401
|
+
: hasPromptTextBeforeLeadingSlash
|
|
402
|
+
? null
|
|
403
|
+
: leadingSlashStart;
|
|
428
404
|
if (slashStart !== null) {
|
|
429
405
|
const commandText = textBeforeCursor.slice(slashStart);
|
|
430
406
|
const spaceIndex = commandText.indexOf(" ");
|
|
@@ -453,26 +429,62 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|
|
453
429
|
// A mid-prompt slash token with no matching skill may still be an
|
|
454
430
|
// absolute path (`see /tmp`); fall through to file-path completion.
|
|
455
431
|
} else if (!isMidPromptSkillLookup) {
|
|
456
|
-
//
|
|
432
|
+
// Submitted slash commands own their argument text only when the
|
|
433
|
+
// matched command accepts args. No-arg slash-looking prompts such
|
|
434
|
+
// as `/settings @file` still fall through to prompt-composer
|
|
435
|
+
// completions because submit treats them as normal prompt text.
|
|
457
436
|
const commandName = commandText.slice(1, spaceIndex); // Command without "/"
|
|
458
437
|
const argumentText = commandText.slice(spaceIndex + 1); // Text after space
|
|
459
438
|
|
|
460
439
|
const command = this.#commands.find(cmd => commandMatchesNameOrAlias(cmd, commandName));
|
|
461
|
-
if (
|
|
462
|
-
|
|
463
|
-
|
|
440
|
+
if (command && (!("allowArgs" in command) || command.allowArgs !== false)) {
|
|
441
|
+
if (!("getArgumentCompletions" in command) || !command.getArgumentCompletions) {
|
|
442
|
+
return null; // No argument completion for this command
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const argumentSuggestions = await command.getArgumentCompletions(argumentText);
|
|
446
|
+
if (!Array.isArray(argumentSuggestions) || argumentSuggestions.length === 0) {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
464
449
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
450
|
+
return {
|
|
451
|
+
items: argumentSuggestions,
|
|
452
|
+
prefix: argumentText,
|
|
453
|
+
};
|
|
468
454
|
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
469
457
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
458
|
+
// Check for @ file reference (fuzzy search) - must be after a delimiter or at start
|
|
459
|
+
const atPrefix = this.#extractAtPrefix(textBeforeCursor);
|
|
460
|
+
if (atPrefix) {
|
|
461
|
+
const { rawPrefix, isQuotedPrefix } = parsePathPrefix(atPrefix);
|
|
462
|
+
// Recursive fuzzy walks rooted outside the project (e.g. `@../`,
|
|
463
|
+
// `@~/`, `@/abs`) can be huge — a parent dir full of sibling
|
|
464
|
+
// projects blows past several seconds of latency. Outside cwd,
|
|
465
|
+
// fall back to plain prefix listing of the immediate directory
|
|
466
|
+
// (matches Claude Code's behavior). Inside cwd we keep the
|
|
467
|
+
// fuzzy-then-prefix flow.
|
|
468
|
+
if (rawPrefix.length > 0 && this.#isOutsideCwd(rawPrefix)) {
|
|
469
|
+
const items = await this.#getFileSuggestions(atPrefix);
|
|
470
|
+
if (items.length === 0) return null;
|
|
471
|
+
return { items, prefix: atPrefix };
|
|
474
472
|
}
|
|
475
|
-
|
|
473
|
+
const suggestions =
|
|
474
|
+
rawPrefix.length > 0
|
|
475
|
+
? await this.#getFuzzyFileSuggestions(rawPrefix, { isQuotedPrefix })
|
|
476
|
+
: await this.#getFileSuggestions("@");
|
|
477
|
+
if (suggestions.length === 0 && rawPrefix.length > 0) {
|
|
478
|
+
const fallback = await this.#getFileSuggestions(atPrefix);
|
|
479
|
+
if (fallback.length === 0) return null;
|
|
480
|
+
return { items: fallback, prefix: atPrefix };
|
|
481
|
+
}
|
|
482
|
+
if (suggestions.length === 0) return null;
|
|
483
|
+
|
|
484
|
+
return {
|
|
485
|
+
items: suggestions,
|
|
486
|
+
prefix: atPrefix,
|
|
487
|
+
};
|
|
476
488
|
}
|
|
477
489
|
|
|
478
490
|
// Check for file paths - triggered by Tab or if we detect a path pattern
|
package/src/components/image.ts
CHANGED
|
@@ -65,6 +65,7 @@ export class ImageBudget {
|
|
|
65
65
|
#requestRender: () => void;
|
|
66
66
|
#nextId = nextImageIdSeed();
|
|
67
67
|
#keyToId = new Map<string, number>();
|
|
68
|
+
#idToKey = new Map<number, string>();
|
|
68
69
|
/** Display-order image ids observed during the in-flight pass. */
|
|
69
70
|
#passIds: number[] = [];
|
|
70
71
|
/**
|
|
@@ -132,6 +133,7 @@ export class ImageBudget {
|
|
|
132
133
|
const id = this.#nextId;
|
|
133
134
|
this.#nextId = (this.#nextId + 1) & 0xffffff || 1;
|
|
134
135
|
this.#keyToId.set(key, id);
|
|
136
|
+
this.#idToKey.set(id, key);
|
|
135
137
|
return id;
|
|
136
138
|
}
|
|
137
139
|
const id = this.#nextId;
|
|
@@ -163,11 +165,15 @@ export class ImageBudget {
|
|
|
163
165
|
*/
|
|
164
166
|
observe(imageId: number): boolean {
|
|
165
167
|
if (this.#stablePass) {
|
|
166
|
-
|
|
168
|
+
const suppressed = this.#cap > 0 && this.#suppressedIds.has(imageId);
|
|
169
|
+
if (suppressed) this.#forgetKeyForId(imageId);
|
|
170
|
+
return suppressed;
|
|
167
171
|
}
|
|
168
172
|
const index = this.#passIds.length;
|
|
169
173
|
this.#passIds.push(imageId);
|
|
170
|
-
|
|
174
|
+
const suppressed = this.#cap > 0 && index < this.#planned;
|
|
175
|
+
if (suppressed) this.#forgetKeyForId(imageId);
|
|
176
|
+
return suppressed;
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
/**
|
|
@@ -185,6 +191,7 @@ export class ImageBudget {
|
|
|
185
191
|
this.#purgeIds.push(id);
|
|
186
192
|
// d=I frees the data too, so the image must re-transmit if it returns.
|
|
187
193
|
this.#transmitted.delete(id);
|
|
194
|
+
this.#forgetKeyForId(id);
|
|
188
195
|
}
|
|
189
196
|
this.#onTerminal = this.#planned;
|
|
190
197
|
this.#applyingReset = false;
|
|
@@ -214,6 +221,8 @@ export class ImageBudget {
|
|
|
214
221
|
this.#transmitted.clear();
|
|
215
222
|
this.#purgeIds = [];
|
|
216
223
|
this.#pendingTransmits = [];
|
|
224
|
+
this.#keyToId.clear();
|
|
225
|
+
this.#idToKey.clear();
|
|
217
226
|
return ids;
|
|
218
227
|
}
|
|
219
228
|
|
|
@@ -274,6 +283,13 @@ export class ImageBudget {
|
|
|
274
283
|
this.#pendingTransmits = [];
|
|
275
284
|
}
|
|
276
285
|
|
|
286
|
+
#forgetKeyForId(id: number): void {
|
|
287
|
+
const key = this.#idToKey.get(id);
|
|
288
|
+
if (key === undefined) return;
|
|
289
|
+
this.#idToKey.delete(id);
|
|
290
|
+
if (this.#keyToId.get(key) === id) this.#keyToId.delete(key);
|
|
291
|
+
}
|
|
292
|
+
|
|
277
293
|
#reconcile(total: number): void {
|
|
278
294
|
const desired = this.#cap > 0 ? Math.max(0, total - this.#cap) : 0;
|
|
279
295
|
if (desired === this.#planned) {
|
|
@@ -122,6 +122,13 @@ export class TerminalInfo {
|
|
|
122
122
|
process.stdout.write(`${wrapTmuxPassthrough(formatted)}\x07`);
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
|
+
// Zellij drops OSC 9/99 and has no DCS passthrough envelope, but raises its
|
|
126
|
+
// `[!]` bell flag on a bare BEL — the same backgrounded-pane signal tmux
|
|
127
|
+
// users get. So follow the (Zellij-swallowed) OSC with a plain BEL.
|
|
128
|
+
if (this.notifyProtocol !== NotifyProtocol.Bell && isInsideZellij()) {
|
|
129
|
+
process.stdout.write(`${formatted}\x07`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
125
132
|
process.stdout.write(formatted);
|
|
126
133
|
// VTE-family terminals (Ptyxis, GNOME Terminal, Tilix, …) plus Alacritty
|
|
127
134
|
// and bare xterm-on-Wayland have no in-band escape that surfaces an
|
|
@@ -156,6 +163,15 @@ export function isInsideTerminalMultiplexer(env: NodeJS.ProcessEnv = Bun.env): b
|
|
|
156
163
|
return term.startsWith("tmux") || term.startsWith("screen");
|
|
157
164
|
}
|
|
158
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Whether the agent process is running inside a Zellij session. Read fresh on
|
|
168
|
+
* each call (like {@link isInsideTmux}) so a session attached/detached mid-run
|
|
169
|
+
* is observed and tests can toggle `Bun.env.ZELLIJ` per case.
|
|
170
|
+
*/
|
|
171
|
+
export function isInsideZellij(env: NodeJS.ProcessEnv = Bun.env): boolean {
|
|
172
|
+
return Boolean(env.ZELLIJ);
|
|
173
|
+
}
|
|
174
|
+
|
|
159
175
|
/**
|
|
160
176
|
* Wrap a control-sequence payload in tmux's DCS passthrough envelope. Each
|
|
161
177
|
* ESC byte inside `payload` is doubled per tmux's escape rules. tmux strips
|