@zhuxixi/pi-agent-board 0.8.0 → 0.9.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/CHANGELOG.md +20 -0
- package/README.md +12 -11
- package/docs/RELEASE.md +148 -0
- package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +25 -0
- package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +393 -0
- package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +51 -0
- package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +58 -0
- package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +698 -0
- package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +605 -0
- package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +43 -0
- package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +95 -0
- package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +63 -0
- package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +57 -0
- package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +98 -0
- package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +190 -0
- package/package.json +1 -1
- package/runner/pty-runner.mjs +34 -21
- package/scripts/release.mjs +446 -0
- package/src/core/host-protocol.mjs +43 -5
- package/src/core/launch-options.mjs +37 -0
- package/src/core/terminal-attach-client.mjs +11 -5
- package/src/core/terminal-query-sequences.mjs +132 -0
- package/src/ui/dashboard.ts +20 -8
- package/src/ui/pty-attach.ts +84 -2
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal query sequence extraction and color-scheme report mapping for the
|
|
3
|
+
* PTY attach client (issue #128).
|
|
4
|
+
*
|
|
5
|
+
* The attach client rebuilds the child screen in a headless terminal model, so
|
|
6
|
+
* the child's terminal capability queries (OSC 11 background color, color
|
|
7
|
+
* scheme change notifications) are consumed locally and never reach the real
|
|
8
|
+
* terminal. extractOscQuerySequences pulls exactly those query/notify forms
|
|
9
|
+
* out of the child output stream so the attach client can forward them to the
|
|
10
|
+
* real terminal; the replies travel back through the existing input
|
|
11
|
+
* passthrough (handleInput fallback). Setting forms (e.g. `\x1b]11;rgb:...`)
|
|
12
|
+
* are deliberately NOT extracted: forwarding those would repaint the local
|
|
13
|
+
* terminal's own colors.
|
|
14
|
+
*
|
|
15
|
+
* Kitty keyboard protocol / DA1 negotiation (`\x1b[>7u`, `\x1b[?u`, `\x1b[c`)
|
|
16
|
+
* is intentionally out of scope: the child negotiates once at spawn, before
|
|
17
|
+
* any attach client exists, and pushing kitty flags would retune the shared
|
|
18
|
+
* real-terminal keyboard stack under the local Pi's feet.
|
|
19
|
+
*
|
|
20
|
+
* Pure functions only — no I/O, no pi-tui import (the report format below is
|
|
21
|
+
* pinned by unit tests that round-trip it through pi-tui's parser).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// OSC 11 background-color QUERY. The `?` payload is what makes it a query;
|
|
25
|
+
// the SET form `\x1b]11;rgb:...` never matches this prefix (`;r` vs `;?`).
|
|
26
|
+
const OSC11_QUERY_PREFIX = "\x1b]11;?";
|
|
27
|
+
const OSC11_QUERY_BEL = OSC11_QUERY_PREFIX + "\x07";
|
|
28
|
+
const OSC11_QUERY_ST = OSC11_QUERY_PREFIX + "\x1b\\";
|
|
29
|
+
|
|
30
|
+
// Color-scheme change notification switch (host Pi toggles it at startup and
|
|
31
|
+
// when its own setting changes; the real terminal then reports scheme changes
|
|
32
|
+
// as `\x1b[?997;{1|2}n` reports). Fixed-form CSI, no variadic fields.
|
|
33
|
+
const COLOR_SCHEME_NOTIFY_ON = "\x1b[?2031h";
|
|
34
|
+
const COLOR_SCHEME_NOTIFY_OFF = "\x1b[?2031l";
|
|
35
|
+
|
|
36
|
+
// Carry slack aligned with the extractor family in pty-attach.ts (payload cap
|
|
37
|
+
// + 4096). Our longest target is 8 bytes, so 4096 is ample even for a
|
|
38
|
+
// malformed no-terminator tail, and slice(-N) keeps the carry bounded.
|
|
39
|
+
export const OSC_QUERY_CARRY_MAX_BYTES = 4096;
|
|
40
|
+
|
|
41
|
+
const TARGETS = [OSC11_QUERY_PREFIX, COLOR_SCHEME_NOTIFY_ON, COLOR_SCHEME_NOTIFY_OFF];
|
|
42
|
+
const MAX_TARGET_LENGTH = Math.max(...TARGETS.map((t) => t.length));
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Extract forwardable terminal query / notify sequences from a child output
|
|
46
|
+
* chunk. Returns the sequences (in order) and the carry: the unmatched tail
|
|
47
|
+
* to prepend to the next chunk. The carry is either a target prefix suffix
|
|
48
|
+
* (sequence split across chunks) or an unterminated OSC 11 query waiting for
|
|
49
|
+
* its BEL/ST terminator.
|
|
50
|
+
*/
|
|
51
|
+
export function extractOscQuerySequences(input) {
|
|
52
|
+
const sequences = [];
|
|
53
|
+
let scanFrom = 0;
|
|
54
|
+
let carryStart = -1;
|
|
55
|
+
while (scanFrom < input.length) {
|
|
56
|
+
const osc = input.indexOf(OSC11_QUERY_PREFIX, scanFrom);
|
|
57
|
+
const notifyOn = input.indexOf(COLOR_SCHEME_NOTIFY_ON, scanFrom);
|
|
58
|
+
const notifyOff = input.indexOf(COLOR_SCHEME_NOTIFY_OFF, scanFrom);
|
|
59
|
+
const start = firstIndex(firstIndex(osc, notifyOn), notifyOff);
|
|
60
|
+
if (start < 0) break;
|
|
61
|
+
if (start === osc) {
|
|
62
|
+
const after = start + OSC11_QUERY_PREFIX.length;
|
|
63
|
+
const bel = input.indexOf("\x07", after);
|
|
64
|
+
const st = input.indexOf("\x1b\\", after);
|
|
65
|
+
const end = firstTerminator(bel, st);
|
|
66
|
+
if (!end) {
|
|
67
|
+
carryStart = start;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
const [endIndex, terminatorLength] = end;
|
|
71
|
+
const seq = input.slice(start, endIndex + terminatorLength);
|
|
72
|
+
// Strict form: an OSC 11 query has no payload between `?` and the
|
|
73
|
+
// terminator. Anything else (e.g. `\x1b]11;?<junk>\x07`) is not a
|
|
74
|
+
// query we forward.
|
|
75
|
+
if (seq === OSC11_QUERY_BEL || seq === OSC11_QUERY_ST) sequences.push(seq);
|
|
76
|
+
scanFrom = endIndex + terminatorLength;
|
|
77
|
+
} else {
|
|
78
|
+
// Fixed-form CSI notify switch: an indexOf hit is always complete.
|
|
79
|
+
const seq = start === notifyOn ? COLOR_SCHEME_NOTIFY_ON : COLOR_SCHEME_NOTIFY_OFF;
|
|
80
|
+
sequences.push(seq);
|
|
81
|
+
scanFrom = start + seq.length;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const carry = carryStart >= 0
|
|
85
|
+
? input.slice(carryStart).slice(-OSC_QUERY_CARRY_MAX_BYTES)
|
|
86
|
+
: queryPrefixSuffix(input);
|
|
87
|
+
return { sequences, carry };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Longest suffix of `input` that is a strict prefix of one of our target
|
|
92
|
+
* sequences (empty when none matches). Used as the carry when every target
|
|
93
|
+
* occurrence in this chunk was complete.
|
|
94
|
+
*/
|
|
95
|
+
function queryPrefixSuffix(input) {
|
|
96
|
+
const max = Math.min(input.length, MAX_TARGET_LENGTH - 1);
|
|
97
|
+
for (let len = max; len > 0; len--) {
|
|
98
|
+
const suffix = input.slice(-len);
|
|
99
|
+
if (TARGETS.some((t) => t.startsWith(suffix))) return suffix;
|
|
100
|
+
}
|
|
101
|
+
return "";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function firstIndex(a, b) {
|
|
105
|
+
if (a < 0) return b;
|
|
106
|
+
if (b < 0) return a;
|
|
107
|
+
return Math.min(a, b);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function firstTerminator(bel, st) {
|
|
111
|
+
if (bel < 0 && st < 0) return null;
|
|
112
|
+
if (bel >= 0 && (st < 0 || bel < st)) return [bel, 1];
|
|
113
|
+
return [st, 2];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Color-scheme report expected by pi-tui's parseTerminalColorSchemeReport
|
|
117
|
+
// (node_modules/@earendil-works/pi-tui/dist/terminal-colors.js:19):
|
|
118
|
+
// /^\x1b\[\?997;(1|2)n$/ with "2" => light, "1" => dark (line 57). Note the
|
|
119
|
+
// report code point is 997 — the QUERY the host sends is `\x1b[?996n`.
|
|
120
|
+
const COLOR_SCHEME_REPORTS = {
|
|
121
|
+
light: "\x1b[?997;2n",
|
|
122
|
+
dark: "\x1b[?997;1n",
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Map a color scheme ("light" | "dark") to the report sequence the host Pi's
|
|
127
|
+
* pi-tui parser consumes. Returns "" for anything else; callers skip sending
|
|
128
|
+
* on empty.
|
|
129
|
+
*/
|
|
130
|
+
export function toColorSchemeReport(scheme) {
|
|
131
|
+
return Object.hasOwn(COLOR_SCHEME_REPORTS, scheme) ? COLOR_SCHEME_REPORTS[scheme] : "";
|
|
132
|
+
}
|
package/src/ui/dashboard.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { requestDashboardRender } from "../core/dashboard-render.mjs";
|
|
|
14
14
|
import { normalizeGenericStatusText } from "../core/derive.mjs";
|
|
15
15
|
import { firstSentence, truncate } from "../core/heuristics.mjs";
|
|
16
16
|
import {
|
|
17
|
+
browseTabCompletion,
|
|
17
18
|
canonicalModelRef,
|
|
18
19
|
clampThinkingLevel,
|
|
19
20
|
listDirectorySuggestions,
|
|
@@ -596,13 +597,24 @@ export class DashboardComponent implements Component {
|
|
|
596
597
|
return;
|
|
597
598
|
}
|
|
598
599
|
if (launch.picker === "cwd" && matchesKey(data, Key.tab)) {
|
|
599
|
-
if (launch.
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
600
|
+
if (launch.cwdSuggestions.length > 0) {
|
|
601
|
+
if (launch.cwdPickerMode === "browse") {
|
|
602
|
+
const completion = browseTabCompletion(launch.cwdQuery, launch.cwdSuggestions, launch.cwdSuggestionIndex);
|
|
603
|
+
if (completion) {
|
|
604
|
+
launch.cwdQuery = completion.query;
|
|
605
|
+
const state = nextCwdPickerState(launch.cwdQuery, launch.cwdRanked, launch.cwd);
|
|
606
|
+
launch.cwdPickerMode = state.mode;
|
|
607
|
+
launch.cwdSuggestions = state.suggestions;
|
|
608
|
+
launch.cwdSuggestionIndex = Math.max(0, state.suggestions.indexOf(completion.completed));
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
const completed = launch.cwdSuggestions[launch.cwdSuggestionIndex] ?? launch.cwdSuggestions[0];
|
|
612
|
+
launch.cwdQuery = completed;
|
|
613
|
+
const state = nextCwdPickerState(launch.cwdQuery, launch.cwdRanked, launch.cwd);
|
|
614
|
+
launch.cwdPickerMode = state.mode;
|
|
615
|
+
launch.cwdSuggestions = state.suggestions;
|
|
616
|
+
launch.cwdSuggestionIndex = Math.max(0, state.suggestions.indexOf(completed));
|
|
617
|
+
}
|
|
606
618
|
}
|
|
607
619
|
return;
|
|
608
620
|
}
|
|
@@ -1583,7 +1595,7 @@ export class DashboardComponent implements Component {
|
|
|
1583
1595
|
} else {
|
|
1584
1596
|
lines.push(...this.renderLaunchSuggestions(inner, launch.cwdSuggestions, launch.cwdSuggestionIndex, (value) => displayPath(value)));
|
|
1585
1597
|
lines.push("");
|
|
1586
|
-
lines.push(t.fg("dim", "type to filter folders · enter choose · esc back"));
|
|
1598
|
+
lines.push(t.fg("dim", "type to filter folders · tab complete · enter choose · esc back"));
|
|
1587
1599
|
}
|
|
1588
1600
|
} else if (launch.picker === "model") {
|
|
1589
1601
|
lines.push(t.fg("warning", `model› ${singleLineInput(launch.modelQuery)}${cursor()}`));
|
package/src/ui/pty-attach.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { evaluateAttachReconnect, shouldEscapeAttach } from "../core/pty-attach-
|
|
|
11
11
|
import { installImeCursorCoalesce } from "../core/ime-cursor-coalesce.mjs";
|
|
12
12
|
import { createJiggleRetryController } from "../core/pty-attach-jiggle-controller.mjs";
|
|
13
13
|
import { createTerminalAttachClient } from "../core/terminal-attach-client.mjs";
|
|
14
|
+
import { extractOscQuerySequences, toColorSchemeReport } from "../core/terminal-query-sequences.mjs";
|
|
14
15
|
import { clampInt, parseMouseInputChunk, resolveWheelLines, scrollViewportTop, selectionDragScrollLines } from "../core/pty-scroll.mjs";
|
|
15
16
|
|
|
16
17
|
export type PtyAttachResult = { action: "detached" } | { action: "closed"; exitCode?: number | null };
|
|
@@ -170,6 +171,7 @@ export class PtyAttachComponent implements Component {
|
|
|
170
171
|
private jiggleStartTimer: ReturnType<typeof setTimeout> | null = null;
|
|
171
172
|
private osc52Carry = "";
|
|
172
173
|
private passthroughCarry = "";
|
|
174
|
+
private oscQueryCarry = "";
|
|
173
175
|
private readonly connectStartedAt = Date.now();
|
|
174
176
|
// Set once the first socket connect succeeds; drives the reconnect give-up
|
|
175
177
|
// policy (short window after a live session dies, long window during cold
|
|
@@ -214,6 +216,10 @@ export class PtyAttachComponent implements Component {
|
|
|
214
216
|
private attaching = true;
|
|
215
217
|
private attachSettleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
216
218
|
private attachHardTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
219
|
+
// Issue #128 D2: unsubscriber for the color-scheme bridge registered at
|
|
220
|
+
// attach settle; nulled on every terminal path so the TUI never keeps a
|
|
221
|
+
// listener pointing at a dead attach surface.
|
|
222
|
+
private colorSchemeBridgeUnsubscribe: (() => void) | null = null;
|
|
217
223
|
private gracefulSocketCloseTimer: ReturnType<typeof setTimeout> | null = null;
|
|
218
224
|
// Force a single full-clear on the first paint so the prior session/dashboard can't
|
|
219
225
|
// ghost behind this overlay; every later paint uses the TUI's coalesced, throttled,
|
|
@@ -287,6 +293,18 @@ export class PtyAttachComponent implements Component {
|
|
|
287
293
|
this.send({ type: "input", data });
|
|
288
294
|
return;
|
|
289
295
|
}
|
|
296
|
+
if (matchesKey(data, "ctrl+\\")) {
|
|
297
|
+
// Unconditional, terminal-independent escape (issue #126): 0x1c is a
|
|
298
|
+
// single raw ASCII byte every terminal forwards unchanged in raw
|
|
299
|
+
// mode — no modifier-encoding dependency like Ctrl+Left (macOS
|
|
300
|
+
// Terminal.app never emits it, WezTerm consumes it for tab
|
|
301
|
+
// switching). Only caveat (documented in README): layouts where
|
|
302
|
+
// `\` sits on AltGr may never produce this byte; the escape ladder
|
|
303
|
+
// still covers those. matchesKey also covers the kitty CSI-u and
|
|
304
|
+
// modifyOtherKeys encodings of the same chord.
|
|
305
|
+
this.detach();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
290
308
|
if (matchesKey(data, Key.ctrl("left"))) {
|
|
291
309
|
// Explicit detach chord (issue #89): single ← is gated on editor state
|
|
292
310
|
// (it doubles as cursor-left inside a non-empty draft), so a user with
|
|
@@ -340,7 +358,7 @@ export class PtyAttachComponent implements Component {
|
|
|
340
358
|
}
|
|
341
359
|
const header =
|
|
342
360
|
this.theme.fg("accent", this.theme.bold(` ${this.opts.title} `)) +
|
|
343
|
-
this.theme.fg("muted", `${this.status} ·
|
|
361
|
+
this.theme.fg("muted", `${this.status} · opens links · drag selects+copies · Ctrl+←/Ctrl+\\ detach`);
|
|
344
362
|
return [clip(header, width), ...body.map((l) => clipTerminalLine(l, width)), this.theme.fg("dim", "─".repeat(width))];
|
|
345
363
|
}
|
|
346
364
|
|
|
@@ -356,7 +374,7 @@ export class PtyAttachComponent implements Component {
|
|
|
356
374
|
out.push(center(this.theme.fg("accent", this.theme.bold(title)), width));
|
|
357
375
|
out.push(center(this.theme.fg("muted", detail), width));
|
|
358
376
|
out.push("");
|
|
359
|
-
out.push(center(this.theme.fg("dim", "Ctrl
|
|
377
|
+
out.push(center(this.theme.fg("dim", "Ctrl+←/Ctrl+\\ to detach"), width));
|
|
360
378
|
while (out.length < height) out.push("");
|
|
361
379
|
return out.slice(0, height);
|
|
362
380
|
}
|
|
@@ -568,6 +586,56 @@ export class PtyAttachComponent implements Component {
|
|
|
568
586
|
// buffer, instead of diffing banner lines into buffer lines.
|
|
569
587
|
this.scheduleRender(true);
|
|
570
588
|
this.startDesyncProbe();
|
|
589
|
+
// Issue #128: the settle transition is the single point where `attaching`
|
|
590
|
+
// flips false (never re-armed), so both hooks run at most once per attach.
|
|
591
|
+
this.attachColorSchemeBridge();
|
|
592
|
+
this.replayBackgroundQuery();
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/** Issue #128 D2: the local pi-tui consumes the child's color-scheme
|
|
596
|
+
* traffic (996 queries / 2031 notifications forwarded by D1) — reports
|
|
597
|
+
* never reach the child. onTerminalColorSchemeChange fires from that same
|
|
598
|
+
* consumption point, so the bridge repackages each event as a 997 report
|
|
599
|
+
* on the child pty, where the child's pi-tui parser consumes it. Once per
|
|
600
|
+
* attach (settle is single-shot + explicit guard); kill switch skips it. */
|
|
601
|
+
private attachColorSchemeBridge(): void {
|
|
602
|
+
if (process.env.AGENT_BOARD_FORWARD_TERMINAL_QUERIES === "0") return;
|
|
603
|
+
if (this.colorSchemeBridgeUnsubscribe) return;
|
|
604
|
+
try {
|
|
605
|
+
this.colorSchemeBridgeUnsubscribe = this.tui.onTerminalColorSchemeChange((scheme) => {
|
|
606
|
+
const data = toColorSchemeReport(scheme);
|
|
607
|
+
if (data) this.send({ type: "input", data });
|
|
608
|
+
});
|
|
609
|
+
} catch {
|
|
610
|
+
/* best-effort: an unbridgeable TUI keeps pre-#128 behavior */
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/** Called on every terminal path (close() covers detach/dispose; the exit
|
|
615
|
+
* branch calls done() without close(), so it unsubscribes explicitly). */
|
|
616
|
+
private detachColorSchemeBridge(): void {
|
|
617
|
+
const unsubscribe = this.colorSchemeBridgeUnsubscribe;
|
|
618
|
+
this.colorSchemeBridgeUnsubscribe = null;
|
|
619
|
+
if (!unsubscribe) return;
|
|
620
|
+
try {
|
|
621
|
+
unsubscribe();
|
|
622
|
+
} catch {
|
|
623
|
+
/* best-effort: a stale listener must not block teardown */
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** Issue #128 D3: the child probed OSC 11 at spawn, before any attach
|
|
628
|
+
* client existed, so its background-color answer was lost. Re-ask the REAL
|
|
629
|
+
* terminal once now; the reply travels back through the existing input
|
|
630
|
+
* passthrough (handleInput fallback) to the child. Silent on failure — a
|
|
631
|
+
* missing answer keeps the pre-#128 fallback theme. */
|
|
632
|
+
private replayBackgroundQuery(): void {
|
|
633
|
+
if (process.env.AGENT_BOARD_FORWARD_TERMINAL_QUERIES === "0") return;
|
|
634
|
+
try {
|
|
635
|
+
this.tui.terminal.write("\x1b]11;?\x07");
|
|
636
|
+
} catch {
|
|
637
|
+
/* best-effort: enhancement, never critical */
|
|
638
|
+
}
|
|
571
639
|
}
|
|
572
640
|
|
|
573
641
|
private startDesyncProbe(): void {
|
|
@@ -1117,6 +1185,10 @@ export class PtyAttachComponent implements Component {
|
|
|
1117
1185
|
this.editorEmpty = typeof msg.empty === "boolean" ? msg.empty : null;
|
|
1118
1186
|
} else if (msg.type === "exit") {
|
|
1119
1187
|
this.status = "host exited";
|
|
1188
|
+
// The exit path calls done() directly (host disposes later);
|
|
1189
|
+
// drop the #128 bridge now so A7 symmetry doesn't depend on
|
|
1190
|
+
// the host honoring dispose().
|
|
1191
|
+
this.detachColorSchemeBridge();
|
|
1120
1192
|
this.done({ action: "closed", exitCode: msg.exitCode ?? null });
|
|
1121
1193
|
} else if (msg.type === "error") this.status = `error: ${msg.message ?? "host error"}`;
|
|
1122
1194
|
if (shouldScheduleAttachRenderForMessage(msg.type)) needsRender = true;
|
|
@@ -1228,6 +1300,15 @@ export class PtyAttachComponent implements Component {
|
|
|
1228
1300
|
this.passthroughCarry = carry;
|
|
1229
1301
|
toWrite.push(...sequences);
|
|
1230
1302
|
}
|
|
1303
|
+
if (process.env.AGENT_BOARD_FORWARD_TERMINAL_QUERIES !== "0") {
|
|
1304
|
+
// Issue #128 D1: forward the child's terminal capability queries
|
|
1305
|
+
// (OSC 11 background probe, 2031 scheme-notify switch) to the real
|
|
1306
|
+
// terminal; set-forms and kitty/DA negotiation stay excluded (see
|
|
1307
|
+
// extractOscQuerySequences).
|
|
1308
|
+
const { sequences, carry } = extractOscQuerySequences(this.oscQueryCarry + data);
|
|
1309
|
+
this.oscQueryCarry = carry;
|
|
1310
|
+
toWrite.push(...sequences);
|
|
1311
|
+
}
|
|
1231
1312
|
for (const seq of toWrite) {
|
|
1232
1313
|
try {
|
|
1233
1314
|
this.tui.terminal.write(seq);
|
|
@@ -1322,6 +1403,7 @@ export class PtyAttachComponent implements Component {
|
|
|
1322
1403
|
}
|
|
1323
1404
|
this.closed = true;
|
|
1324
1405
|
this.imeCoalesceUninstall?.();
|
|
1406
|
+
this.detachColorSchemeBridge();
|
|
1325
1407
|
this.attachClient.close();
|
|
1326
1408
|
this.jiggleRetry.restoreAndStop();
|
|
1327
1409
|
this.disableMouseScroll();
|