pi-jarvis 1.4.0 → 1.5.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/AGENTS.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Project Scope
4
4
  - `pi-jarvis` is a Pi extension that opens a `/jarvis` side-conversation overlay.
5
- - Core runtime files: `index.ts`, `side-session.ts`, `overlay.ts`, `model-picker.ts`, `jarvis-config.ts`, `session-ref.ts`.
5
+ - Core runtime files: `index.ts`, `side-session.ts`, `overlay.ts`, `overlay-layout.ts`, `draft-editor.ts`, `transcript-viewport.ts`, `model-picker.ts`, `jarvis-config.ts`, `session-ref.ts`.
6
6
  - Current baseline: Pi 1.0.0 (`@earendil-works` packages), Node.js >=22.19.0. Older Pi hosts are not supported.
7
7
 
8
8
  ## Current `/jarvis-model` and `/jarvis-thinking` Behavior
@@ -26,6 +26,18 @@
26
26
  - Use `agent_settled`, not `agent_end`, for final idle state. Main context must honor Pi context-edit/compaction projection.
27
27
  - Runtime disposal must not detach the overlay's bridge subscription. Queue ownership must survive side `/new` but not main-session replacement; failed/uncertain sends are not auto-retried.
28
28
 
29
+ ## Overlay UX (1.5.0)
30
+ - Compact header by default; Ctrl+O expands diagnostics, PageUp/PageDown scroll transcript, Ctrl+End follows live, Ctrl+L dismisses notices.
31
+ - Use Pi's public multiline Editor. Enter sends; Shift+Enter/Ctrl+J inserts newline. Preserve indentation and reject unsafe/oversized drafts (64 KiB) explicitly.
32
+ - Bridge-owned in-memory drafts survive close/reopen. Bridge reset clears draft/recall on side `/new` or main/side-reference replacement; side-tree navigation only resets scroll.
33
+ - Consume bracketed paste before global shortcuts and confirmation keys; pasted content must not toggle permissions or approve a redirect.
34
+ - Queue count excludes active work; processing includes boot and complete queue settlement. Keep activity separate from scrollback and never replay uncertain sends.
35
+ - Bound overlay history (500 entries, 512K UTF-16 units total, 64K per entry) with explicit omission notices; never truncate persisted history.
36
+
37
+ ## Git and Release Policy
38
+ - Every commit must have an annotated git tag; this is mandatory. Release commits use `v<version>`; other commits use `commit-<short-sha>`.
39
+ - Push each commit with its tag. Keep release versions synchronized across package metadata, lockfile, README, and changelog.
40
+
29
41
  ## Validation
30
42
  - Run `npm test` for full validation.
31
43
  - Run `npm run build` before release packaging.
package/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  [![Pi extension](https://img.shields.io/badge/Pi-extension-06b6d4?style=for-the-badge)](https://github.com/crustyhacker/pi-jarvis)
14
14
  [![TypeScript](https://img.shields.io/badge/TypeScript-powered-2563eb?style=for-the-badge)](./package.json)
15
15
 
16
- <p><strong>Current version:</strong> 1.4.0</p>
16
+ <p><strong>Current version:</strong> 1.5.0</p>
17
17
 
18
18
  <p>
19
19
  <strong>Persistent side session</strong> ·
@@ -60,7 +60,7 @@ The main Pi session should stay on the critical path.
60
60
  | **Permission-gated tools** | Local `read`, `bash`, `edit`, and `write` stay off until you enable them |
61
61
  | **Safe main-session handoff** | `Note main` is quiet; `Redirect` is confirmation-gated |
62
62
  | **Independent model control** | Follow the main model or pin `/jarvis` to a separate model |
63
- | **Cleaner UX** | Thinking-step streaming is collapsed into a cleaner animated fallback |
63
+ | **Cleaner UX** | Compact header, scrollable history, multiline drafts, and independent activity/queue feedback |
64
64
 
65
65
  ---
66
66
 
@@ -226,6 +226,30 @@ The overlay header exposes three controls, all **off by default**:
226
226
 
227
227
  Long redirects are paged: review every page with Up/Down or PageUp/PageDown before pressing Y. Resize if the terminal is too small to review safely. Configured Pi selection keybindings are respected.
228
228
 
229
+ ### Keyboard and drafts
230
+
231
+ Version **1.5.0** adds compact diagnostics, scrollback, and a multiline draft editor.
232
+
233
+ | Key | Action |
234
+ |---|---|
235
+ | Enter | Send the draft; toggle a focused permission control |
236
+ | Shift+Enter / Ctrl+J | Insert a newline |
237
+ | Tab / Shift+Tab | Cycle between the editor and permission controls |
238
+ | Space | Toggle a focused permission control |
239
+ | PageUp / PageDown | Scroll conversation history; reaching the bottom resumes live following |
240
+ | Ctrl+End | Jump back to live output |
241
+ | Ctrl+O | Expand/collapse model, main-context delta, and access details |
242
+ | Ctrl+L | Dismiss notices |
243
+ | Escape | Close; during redirect review, cancel the confirmation instead |
244
+
245
+ The compact header keeps main status, the side model, main focus, and permissions visible. Activity and waiting-message counts remain visible while reading older output; counts exclude the active request. The latest notice appears separately; expand details for more of a long notice.
246
+
247
+ The multiline editor uses Pi's public editor and configured editing keybindings. Up/Down move within a draft; Up at the beginning of the first line (or in an empty editor) recalls prompts. Down past recalled prompts restores the draft. Pasted indentation and newlines are preserved, with Pi's normal tab-to-spaces normalization. Oversized drafts (over 64 KiB) and terminal-control payloads are rejected explicitly, never silently truncated or sent.
248
+
249
+ Unsent drafts survive closing and reopening `/jarvis` in the same running Pi session. They are not saved to disk. Side `/new`, main-session replacement, and switching to an unrelated side-session reference clear them; navigating the side tree only resets the transcript view. Closing still revokes every permission.
250
+
251
+ Scrollback follows new output until you scroll up. To bound rendering work, the overlay retains up to 500 recent entries and 512K UTF-16 code units of source text, with a 64K-unit per-entry limit. Omitted content is marked explicitly; these display limits do not modify persisted conversation history. Resizing preserves the reading position on a best-effort basis.
252
+
229
253
  ### Permission flow
230
254
 
231
255
  ```mermaid
@@ -0,0 +1,53 @@
1
+ import type { Theme } from "@earendil-works/pi-coding-agent";
2
+ import { type Focusable, type TUI } from "@earendil-works/pi-tui";
3
+ export declare const MAX_DRAFT_BYTES: number;
4
+ export interface JarvisDraftEditorCallbacks {
5
+ onSubmit: (text: string) => void;
6
+ onChange: (text: string) => void;
7
+ /** Without a handler, rejected text raises an explicit error instead. */
8
+ onError?: (message: string) => void;
9
+ }
10
+ /** A content-only, bounded viewport around Pi's public multiline Editor. */
11
+ export declare class JarvisDraftEditor implements Focusable {
12
+ private readonly tui;
13
+ private readonly callbacks;
14
+ private readonly editor;
15
+ private disposed;
16
+ private submitted;
17
+ private inPaste;
18
+ private pasteBuffer;
19
+ private pasteTail;
20
+ private pasteRejected;
21
+ private pasteDiscard;
22
+ private startTail;
23
+ constructor(tui: TUI, theme: Theme, callbacks: JarvisDraftEditorCallbacks, initialText?: string);
24
+ get focused(): boolean;
25
+ set focused(value: boolean);
26
+ /** Includes all pasted text, never a display-only paste placeholder. */
27
+ getText(): string;
28
+ /** Silent programmatic replacement/restore; does not publish onChange. */
29
+ setText(text: string): void;
30
+ addToHistory(text: string): void;
31
+ /**
32
+ * Route this BEFORE confirmation/global shortcuts. Returns true when the
33
+ * entire packet was consumed as paste framing/content, never as keys.
34
+ * Discard is sticky through the paste, even if confirmation later closes.
35
+ */
36
+ handlePasteInput(data: string, discard?: boolean): boolean;
37
+ /** Carry only framing across a thread reset, discarding all old paste text. */
38
+ inheritPasteDrain(previous: JarvisDraftEditor): void;
39
+ handleInput(data: string): void;
40
+ /** Crops around the public cursor marker, including one-cell viewports. */
41
+ render(width: number, maxLines: number): string[];
42
+ invalidate(): void;
43
+ dispose(): void;
44
+ private error;
45
+ private acceptText;
46
+ private textError;
47
+ private appendPaste;
48
+ private resetPaste;
49
+ private insertText;
50
+ private dispatch;
51
+ private publishChange;
52
+ }
53
+ //# sourceMappingURL=draft-editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft-editor.d.ts","sourceRoot":"","sources":["../draft-editor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAEwC,KAAK,SAAS,EAAmB,KAAK,GAAG,EACvF,MAAM,wBAAwB,CAAC;AAEhC,eAAO,MAAM,eAAe,QAAY,CAAC;AAEzC,MAAM,WAAW,0BAA0B;IAC1C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,yEAAyE;IACzE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AA2BD,4EAA4E;AAC5E,qBAAa,iBAAkB,YAAW,SAAS;IAYjD,OAAO,CAAC,QAAQ,CAAC,GAAG;IAEpB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAb3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAM;gBAGL,GAAG,EAAE,GAAG,EACzB,KAAK,EAAE,KAAK,EACK,SAAS,EAAE,0BAA0B,EACtD,WAAW,SAAK;IAkBjB,IAAI,OAAO,IAAI,OAAO,CAAgC;IACtD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAAoD;IAE9E,wEAAwE;IACxE,OAAO,IAAI,MAAM;IAEjB,0EAA0E;IAC1E,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAO3B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAahC;;;;OAIG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,UAAQ,GAAG,OAAO;IA2CxD,+EAA+E;IAC/E,iBAAiB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAQpD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK/B,2EAA2E;IAC3E,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE;IA+BjD,UAAU,IAAI,IAAI;IAMlB,OAAO,IAAI,IAAI;IAQf,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,QAAQ;IA2ChB,OAAO,CAAC,aAAa;CAKrB"}
@@ -0,0 +1,303 @@
1
+ import { CURSOR_MARKER, Editor, decodeKittyPrintable, getKeybindings, isKeyRelease, matchesKey, parseKey, sliceByColumn, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
2
+ export const MAX_DRAFT_BYTES = 64 * 1024;
3
+ const PASTE_START = "\x1b[200~";
4
+ const PASTE_END = "\x1b[201~";
5
+ const EDITOR_ACTIONS = [
6
+ "tui.editor.cursorUp", "tui.editor.cursorDown", "tui.editor.historyPrevious", "tui.editor.historyNext",
7
+ "tui.editor.cursorLeft", "tui.editor.cursorRight", "tui.editor.cursorWordLeft", "tui.editor.cursorWordRight",
8
+ "tui.editor.cursorLineStart", "tui.editor.cursorLineEnd", "tui.editor.jumpForward", "tui.editor.jumpBackward",
9
+ "tui.editor.pageUp", "tui.editor.pageDown", "tui.editor.deleteCharBackward", "tui.editor.deleteCharForward",
10
+ "tui.editor.deleteWordBackward", "tui.editor.deleteWordForward", "tui.editor.deleteToLineStart",
11
+ "tui.editor.deleteToLineEnd", "tui.editor.yank", "tui.editor.yankPop", "tui.editor.undo",
12
+ "tui.input.newLine", "tui.input.submit", "tui.input.tab", "tui.input.copy",
13
+ ];
14
+ // Only used for the size preflight. Actual normalization belongs to Pi's public
15
+ // setText/insertTextAtCursor APIs (CRLF/CR -> LF, tabs -> four spaces).
16
+ function normalizedBytes(text) {
17
+ return Buffer.byteLength(text.replace(/\r\n?/g, "\n").replace(/\t/g, " "), "utf8");
18
+ }
19
+ function delimiterTail(text, delimiter) {
20
+ for (let size = Math.min(text.length, delimiter.length - 1); size > 0; size--) {
21
+ if (text.endsWith(delimiter.slice(0, size)))
22
+ return size;
23
+ }
24
+ return 0;
25
+ }
26
+ /** A content-only, bounded viewport around Pi's public multiline Editor. */
27
+ export class JarvisDraftEditor {
28
+ tui;
29
+ callbacks;
30
+ editor;
31
+ disposed = false;
32
+ submitted = false;
33
+ inPaste = false;
34
+ pasteBuffer = "";
35
+ pasteTail = "";
36
+ pasteRejected = false;
37
+ pasteDiscard = false;
38
+ startTail = "";
39
+ constructor(tui, theme, callbacks, initialText = "") {
40
+ this.tui = tui;
41
+ this.callbacks = callbacks;
42
+ this.editor = new Editor(tui, {
43
+ borderColor: (text) => theme.fg("borderMuted", text),
44
+ selectList: {
45
+ selectedPrefix: (text) => theme.fg("accent", text),
46
+ selectedText: (text) => theme.fg("accent", text),
47
+ description: (text) => theme.fg("muted", text),
48
+ scrollInfo: (text) => theme.fg("dim", text),
49
+ noMatch: (text) => theme.fg("muted", text),
50
+ },
51
+ }, { paddingX: 0 });
52
+ // Pi submits a trimmed value. Capture the full pre-submit draft in
53
+ // dispatch instead; let Pi still own clearing and its undo lifecycle.
54
+ this.editor.onSubmit = () => { this.submitted = true; };
55
+ this.setText(initialText);
56
+ }
57
+ get focused() { return this.editor.focused; }
58
+ set focused(value) { this.editor.focused = !this.disposed && value; }
59
+ /** Includes all pasted text, never a display-only paste placeholder. */
60
+ getText() { return this.editor.getExpandedText(); }
61
+ /** Silent programmatic replacement/restore; does not publish onChange. */
62
+ setText(text) {
63
+ if (this.disposed || !this.acceptText(text))
64
+ return;
65
+ this.resetPaste();
66
+ this.editor.setText(text);
67
+ this.invalidate();
68
+ }
69
+ addToHistory(text) {
70
+ if (this.disposed || !this.acceptText(text))
71
+ return;
72
+ // addToHistory itself does not normalize line endings/tabs. Normalize
73
+ // through a temporary public Editor without changing this draft/cursor.
74
+ const normalizer = new Editor(this.tui, {
75
+ borderColor: (value) => value,
76
+ selectList: { selectedPrefix: (v) => v, selectedText: (v) => v, description: (v) => v,
77
+ scrollInfo: (v) => v, noMatch: (v) => v },
78
+ });
79
+ normalizer.setText(text);
80
+ this.editor.addToHistory(normalizer.getExpandedText());
81
+ }
82
+ /**
83
+ * Route this BEFORE confirmation/global shortcuts. Returns true when the
84
+ * entire packet was consumed as paste framing/content, never as keys.
85
+ * Discard is sticky through the paste, even if confirmation later closes.
86
+ */
87
+ handlePasteInput(data, discard = false) {
88
+ if (this.disposed || !data)
89
+ return false;
90
+ if (!this.inPaste) {
91
+ const pendingStart = Boolean(this.startTail);
92
+ const packet = this.startTail + data;
93
+ const start = packet.indexOf(PASTE_START);
94
+ const tail = delimiterTail(packet, PASTE_START);
95
+ // A lone Escape remains an ordinary close/cancel key outside paste.
96
+ if (start < 0 && !pendingStart && tail <= 1)
97
+ return false;
98
+ this.pasteDiscard ||= discard;
99
+ this.startTail = "";
100
+ if (start < 0) {
101
+ if (tail > 1)
102
+ this.startTail = packet.slice(packet.length - tail);
103
+ else
104
+ this.resetPaste(); // Consume, don't replay a malformed opener.
105
+ return true;
106
+ }
107
+ this.inPaste = true;
108
+ // No ambiguous keyboard prefix in a framing packet gets replayed.
109
+ data = packet.slice(start + PASTE_START.length);
110
+ }
111
+ else {
112
+ this.pasteDiscard ||= discard;
113
+ }
114
+ if (this.pasteDiscard)
115
+ this.pasteBuffer = "";
116
+ const packet = this.pasteTail + data;
117
+ this.pasteTail = "";
118
+ const end = packet.indexOf(PASTE_END);
119
+ if (end < 0) {
120
+ const tail = delimiterTail(packet, PASTE_END);
121
+ this.pasteTail = packet.slice(packet.length - tail);
122
+ if (!this.pasteDiscard)
123
+ this.appendPaste(packet.slice(0, packet.length - tail));
124
+ return true;
125
+ }
126
+ if (!this.pasteDiscard)
127
+ this.appendPaste(packet.slice(0, end));
128
+ const paste = this.pasteBuffer;
129
+ const rejected = this.pasteRejected || this.pasteDiscard;
130
+ this.resetPaste();
131
+ // Public insertion avoids Pi marker-ID collisions with literal markers,
132
+ // stale history, and recursively expanded IDs. Anything attached AFTER
133
+ // the terminator is dropped, including Enter or confirmation shortcuts.
134
+ if (!rejected)
135
+ this.insertText(paste);
136
+ return true;
137
+ }
138
+ /** Carry only framing across a thread reset, discarding all old paste text. */
139
+ inheritPasteDrain(previous) {
140
+ this.resetPaste();
141
+ this.inPaste = previous.inPaste;
142
+ this.startTail = previous.startTail;
143
+ this.pasteTail = previous.pasteTail;
144
+ this.pasteDiscard = this.inPaste || Boolean(this.startTail);
145
+ }
146
+ handleInput(data) {
147
+ if (this.disposed || !data || this.handlePasteInput(data))
148
+ return;
149
+ this.dispatch(data);
150
+ }
151
+ /** Crops around the public cursor marker, including one-cell viewports. */
152
+ render(width, maxLines) {
153
+ width = Number.isFinite(width) ? Math.max(0, Math.floor(width)) : 0;
154
+ maxLines = Number.isFinite(maxLines) ? Math.max(0, Math.floor(maxLines)) : 0;
155
+ if (this.disposed || !width || !maxLines)
156
+ return [];
157
+ const focused = this.focused;
158
+ let lines;
159
+ // Obtain cursor geometry even while unfocused, then remove the marker.
160
+ // Width >=3 avoids Pi's one-column wide-grapheme wrapping edge case.
161
+ this.editor.focused = true;
162
+ try {
163
+ lines = this.editor.render(Math.max(3, width)).slice(1, -1);
164
+ }
165
+ finally {
166
+ this.editor.focused = focused;
167
+ }
168
+ const cursor = Math.max(0, lines.findIndex((line) => line.includes(CURSOR_MARKER)));
169
+ const start = Math.max(0, Math.min(cursor - Math.floor(maxLines / 2), lines.length - maxLines));
170
+ return lines.slice(start, start + maxLines).map((line) => {
171
+ const marker = line.indexOf(CURSOR_MARKER);
172
+ if (visibleWidth(line) > width && marker >= 0) {
173
+ const before = line.slice(0, marker);
174
+ const beforeWidth = visibleWidth(before);
175
+ const prefix = sliceByColumn(before, Math.max(0, beforeWidth - width + 1), width - 1, true);
176
+ let suffix = sliceByColumn(line.slice(marker + CURSOR_MARKER.length), 0, width - visibleWidth(prefix), true);
177
+ // A wide cursor grapheme cannot fit in one cell; retain a visible
178
+ // cursor cell rather than dropping either the cursor or its marker.
179
+ if (!visibleWidth(suffix))
180
+ suffix = "\x1b[7m \x1b[0m";
181
+ line = prefix + CURSOR_MARKER + suffix;
182
+ }
183
+ else {
184
+ line = truncateToWidth(line, width, "");
185
+ }
186
+ return focused ? line : line.replaceAll(CURSOR_MARKER, "");
187
+ });
188
+ }
189
+ invalidate() {
190
+ if (this.disposed)
191
+ return;
192
+ this.editor.invalidate();
193
+ this.tui.requestRender();
194
+ }
195
+ dispose() {
196
+ if (this.disposed)
197
+ return;
198
+ this.focused = false;
199
+ this.disposed = true;
200
+ this.resetPaste();
201
+ this.editor.onSubmit = undefined;
202
+ }
203
+ error(message) {
204
+ if (this.callbacks.onError)
205
+ this.callbacks.onError(message);
206
+ else
207
+ throw new Error(message);
208
+ return false;
209
+ }
210
+ acceptText(text, existingBytes = 0) {
211
+ const rejection = this.textError(text, existingBytes);
212
+ return rejection ? this.error(rejection) : true;
213
+ }
214
+ textError(text, existingBytes = 0) {
215
+ // Reject complete text operations instead of leaking printable tails of
216
+ // ANSI/OSC/APC sequences. LF/CR/tabs are data and Pi normalizes them.
217
+ if (/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/.test(text)) {
218
+ return "Draft rejected: terminal control characters are not allowed.";
219
+ }
220
+ if (existingBytes + normalizedBytes(text) > MAX_DRAFT_BYTES) {
221
+ return "Draft rejected: maximum size is 64 KiB. Nothing was truncated or sent.";
222
+ }
223
+ return undefined;
224
+ }
225
+ appendPaste(text) {
226
+ if (this.pasteRejected)
227
+ return;
228
+ this.pasteBuffer += text;
229
+ if (normalizedBytes(this.pasteBuffer) + Buffer.byteLength(this.getText(), "utf8") > MAX_DRAFT_BYTES) {
230
+ this.pasteRejected = true;
231
+ this.pasteBuffer = "";
232
+ this.error("Paste rejected: draft maximum size is 64 KiB. Nothing was truncated or sent.");
233
+ }
234
+ }
235
+ resetPaste() {
236
+ this.inPaste = false;
237
+ this.pasteBuffer = this.pasteTail = this.startTail = "";
238
+ this.pasteRejected = this.pasteDiscard = false;
239
+ }
240
+ insertText(text) {
241
+ const before = this.getText();
242
+ if (!this.acceptText(text, Buffer.byteLength(before, "utf8")))
243
+ return false;
244
+ this.editor.insertTextAtCursor(text);
245
+ this.publishChange(before);
246
+ return true;
247
+ }
248
+ dispatch(data) {
249
+ if (isKeyRelease(data))
250
+ return;
251
+ const kb = getKeybindings();
252
+ const action = EDITOR_ACTIONS.some((id) => kb.matches(data, id));
253
+ const legacyNewline = data === "\x1b\r" || data === "\x1b[13;2~";
254
+ const extraEditingKey = matchesKey(data, "shift+backspace") || matchesKey(data, "shift+delete");
255
+ const key = parseKey(data);
256
+ // Pi exposes a Kitty printable decoder and a protocol-independent key
257
+ // parser. The latter also recognizes xterm modifyOtherKeys characters;
258
+ // use its identity only for preflight, leaving actual decoding to Pi.
259
+ const printable = decodeKittyPrintable(data) ?? (key === "space" || key === "shift+space" ? " " :
260
+ key?.length === 1 ? key : key?.startsWith("shift+") && key.length === 7 ? key.slice(-1) : undefined);
261
+ if (!action && !legacyNewline && !extraEditingKey && printable === undefined) {
262
+ // Unknown escape/control packets are not text. Bulk printable input,
263
+ // including unframed multiline text, uses Pi's normalized insertion.
264
+ if (data.startsWith("\x1b") || (data.length === 1 && data.charCodeAt(0) < 32))
265
+ return;
266
+ if (/[\r\n\t]/.test(data)) {
267
+ this.insertText(data);
268
+ return;
269
+ }
270
+ }
271
+ const before = this.getText();
272
+ if (printable !== undefined || (!action && !legacyNewline && !extraEditingKey)) {
273
+ if (!this.acceptText(printable ?? data, Buffer.byteLength(before, "utf8")))
274
+ return;
275
+ }
276
+ if ((kb.matches(data, "tui.input.newLine") || legacyNewline || data === "\n") &&
277
+ !this.acceptText("\n", Buffer.byteLength(before, "utf8")))
278
+ return;
279
+ this.submitted = false;
280
+ this.editor.handleInput(data);
281
+ const after = this.getText();
282
+ // Defensive guard for Pi actions such as yank/undo/history. All external
283
+ // text is already checked; reject any unexpected expanded oversize edit.
284
+ const rejection = this.textError(after);
285
+ if (rejection) {
286
+ this.editor.setText(before);
287
+ this.invalidate();
288
+ this.error(rejection);
289
+ return;
290
+ }
291
+ const submitted = this.submitted;
292
+ this.publishChange(before);
293
+ if (submitted && before.trim())
294
+ this.callbacks.onSubmit(before);
295
+ }
296
+ publishChange(before) {
297
+ const after = this.getText();
298
+ if (after !== before)
299
+ this.callbacks.onChange(after);
300
+ this.invalidate();
301
+ }
302
+ }
303
+ //# sourceMappingURL=draft-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft-editor.js","sourceRoot":"","sources":["../draft-editor.ts"],"names":[],"mappings":"AACA,OAAO,EACN,aAAa,EAAE,MAAM,EAAE,oBAAoB,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAC/F,aAAa,EAAE,eAAe,EAAE,YAAY,GAC5C,MAAM,wBAAwB,CAAC;AAEhC,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,GAAG,IAAI,CAAC;AASzC,MAAM,WAAW,GAAG,WAAW,CAAC;AAChC,MAAM,SAAS,GAAG,WAAW,CAAC;AAC9B,MAAM,cAAc,GAA0B;IAC7C,qBAAqB,EAAE,uBAAuB,EAAE,4BAA4B,EAAE,wBAAwB;IACtG,uBAAuB,EAAE,wBAAwB,EAAE,2BAA2B,EAAE,4BAA4B;IAC5G,4BAA4B,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,yBAAyB;IAC7G,mBAAmB,EAAE,qBAAqB,EAAE,+BAA+B,EAAE,8BAA8B;IAC3G,+BAA+B,EAAE,8BAA8B,EAAE,8BAA8B;IAC/F,4BAA4B,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,iBAAiB;IACxF,mBAAmB,EAAE,kBAAkB,EAAE,eAAe,EAAE,gBAAgB;CAC1E,CAAC;AAEF,gFAAgF;AAChF,wEAAwE;AACxE,SAAS,eAAe,CAAC,IAAY;IACpC,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;AACvF,CAAC;AAED,SAAS,aAAa,CAAC,IAAY,EAAE,SAAiB;IACrD,KAAK,IAAI,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC;QAC/E,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;IAC1D,CAAC;IACD,OAAO,CAAC,CAAC;AACV,CAAC;AAED,4EAA4E;AAC5E,MAAM,OAAO,iBAAiB;IAYX;IAEA;IAbD,MAAM,CAAS;IACxB,QAAQ,GAAG,KAAK,CAAC;IACjB,SAAS,GAAG,KAAK,CAAC;IAClB,OAAO,GAAG,KAAK,CAAC;IAChB,WAAW,GAAG,EAAE,CAAC;IACjB,SAAS,GAAG,EAAE,CAAC;IACf,aAAa,GAAG,KAAK,CAAC;IACtB,YAAY,GAAG,KAAK,CAAC;IACrB,SAAS,GAAG,EAAE,CAAC;IAEvB,YACkB,GAAQ,EACzB,KAAY,EACK,SAAqC,EACtD,WAAW,GAAG,EAAE;QAHC,QAAG,GAAH,GAAG,CAAK;QAER,cAAS,GAAT,SAAS,CAA4B;QAGtD,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC,GAAG,EAAE;YAC7B,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,aAAa,EAAE,IAAI,CAAC;YACpD,UAAU,EAAE;gBACX,cAAc,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC;gBAClD,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC;gBAChD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC;gBAC9C,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC;gBAC3C,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC;aAC1C;SACD,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;QACpB,mEAAmE;QACnE,sEAAsE;QACtE,IAAI,CAAC,MAAM,CAAC,QAAQ,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3B,CAAC;IAED,IAAI,OAAO,KAAc,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACtD,IAAI,OAAO,CAAC,KAAc,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC;IAE9E,wEAAwE;IACxE,OAAO,KAAa,OAAO,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;IAE3D,0EAA0E;IAC1E,OAAO,CAAC,IAAY;QACnB,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO;QACpD,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,UAAU,EAAE,CAAC;IACnB,CAAC;IAED,YAAY,CAAC,IAAY;QACxB,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO;QACpD,sEAAsE;QACtE,wEAAwE;QACxE,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YACvC,WAAW,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK;YAC7B,UAAU,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACpF,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE;SAC1C,CAAC,CAAC;QACH,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,eAAe,EAAE,CAAC,CAAC;IACxD,CAAC;IAED;;;;OAIG;IACH,gBAAgB,CAAC,IAAY,EAAE,OAAO,GAAG,KAAK;QAC7C,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACzC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACnB,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACrC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YAC1C,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;YAChD,oEAAoE;YACpE,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,IAAI,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC1D,IAAI,CAAC,YAAY,KAAK,OAAO,CAAC;YAC9B,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;YACpB,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBACf,IAAI,IAAI,GAAG,CAAC;oBAAE,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;;oBAC7D,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,4CAA4C;gBACpE,OAAO,IAAI,CAAC;YACb,CAAC;YACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,kEAAkE;YAClE,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QACjD,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,YAAY,KAAK,OAAO,CAAC;QAC/B,CAAC;QACD,IAAI,IAAI,CAAC,YAAY;YAAE,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACrC,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;QACpB,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;YAC9C,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;YACpD,IAAI,CAAC,IAAI,CAAC,YAAY;gBAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;YAChF,OAAO,IAAI,CAAC;QACb,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,YAAY,CAAC;QACzD,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,wEAAwE;QACxE,uEAAuE;QACvE,wEAAwE;QACxE,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,+EAA+E;IAC/E,iBAAiB,CAAC,QAA2B;QAC5C,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;QACpC,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;QACpC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7D,CAAC;IAED,WAAW,CAAC,IAAY;QACvB,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;YAAE,OAAO;QAClE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC;IAED,2EAA2E;IAC3E,MAAM,CAAC,KAAa,EAAE,QAAgB;QACrC,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7E,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,CAAC;QACpD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,KAAe,CAAC;QACpB,uEAAuE;QACvE,qEAAqE;QACrE,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC;YAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAAC,CAAC;gBAC5D,CAAC;YAAC,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC;QAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;QACpF,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC;QAChG,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACxD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;YAC3C,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;gBAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;gBACrC,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;gBACzC,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,KAAK,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;gBAC5F,IAAI,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;gBAC7G,kEAAkE;gBAClE,oEAAoE;gBACpE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;oBAAE,MAAM,GAAG,iBAAiB,CAAC;gBACtD,IAAI,GAAG,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;YACxC,CAAC;iBAAM,CAAC;gBACP,IAAI,GAAG,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YACzC,CAAC;YACD,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,UAAU;QACT,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACzB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;IAC1B,CAAC;IAED,OAAO;QACN,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;IAClC,CAAC;IAEO,KAAK,CAAC,OAAe;QAC5B,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO;YAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;;YACvD,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,OAAO,KAAK,CAAC;IACd,CAAC;IAEO,UAAU,CAAC,IAAY,EAAE,aAAa,GAAG,CAAC;QACjD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QACtD,OAAO,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjD,CAAC;IAEO,SAAS,CAAC,IAAY,EAAE,aAAa,GAAG,CAAC;QAChD,wEAAwE;QACxE,sEAAsE;QACtE,IAAI,uCAAuC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACxD,OAAO,8DAA8D,CAAC;QACvE,CAAC;QACD,IAAI,aAAa,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,eAAe,EAAE,CAAC;YAC7D,OAAO,wEAAwE,CAAC;QACjF,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;IAEO,WAAW,CAAC,IAAY;QAC/B,IAAI,IAAI,CAAC,aAAa;YAAE,OAAO;QAC/B,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC;QACzB,IAAI,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,GAAG,eAAe,EAAE,CAAC;YACrG,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;YAC1B,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,KAAK,CAAC,8EAA8E,CAAC,CAAC;QAC5F,CAAC;IACF,CAAC;IAEO,UAAU;QACjB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;QACxD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;IAChD,CAAC;IAEO,UAAU,CAAC,IAAY;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QAC5E,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC;IACb,CAAC;IAEO,QAAQ,CAAC,IAAY;QAC5B,IAAI,YAAY,CAAC,IAAI,CAAC;YAAE,OAAO;QAC/B,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QACjE,MAAM,aAAa,GAAG,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,YAAY,CAAC;QACjE,MAAM,eAAe,GAAG,UAAU,CAAC,IAAI,EAAE,iBAAiB,CAAC,IAAI,UAAU,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;QAChG,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC3B,sEAAsE;QACtE,uEAAuE;QACvE,sEAAsE;QACtE,MAAM,SAAS,GAAG,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAC/C,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAChD,GAAG,EAAE,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,UAAU,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CACnG,CAAC;QACF,IAAI,CAAC,MAAM,IAAI,CAAC,aAAa,IAAI,CAAC,eAAe,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC9E,qEAAqE;YACrE,qEAAqE;YACrE,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;gBAAE,OAAO;YACtF,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;QAC9D,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC9B,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,aAAa,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;YAChF,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,IAAI,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAAE,OAAO;QACpF,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,mBAAmB,CAAC,IAAI,aAAa,IAAI,IAAI,KAAK,IAAI,CAAC;YAC5E,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAAE,OAAO;QACnE,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC7B,yEAAyE;QACzE,yEAAyE;QACzE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,SAAS,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC5B,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YACtB,OAAO;QACR,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QACjC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC3B,IAAI,SAAS,IAAI,MAAM,CAAC,IAAI,EAAE;YAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjE,CAAC;IAEO,aAAa,CAAC,MAAc;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,KAAK,MAAM;YAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACrD,IAAI,CAAC,UAAU,EAAE,CAAC;IACnB,CAAC;CACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,KAAK,YAAY,EAGjB,MAAM,iCAAiC,CAAC;AAkGzC,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAqhB9D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,KAAK,YAAY,EAGjB,MAAM,iCAAiC,CAAC;AAkGzC,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAwhB9D"}
package/dist/index.js CHANGED
@@ -322,6 +322,7 @@ export default function jarvisExtension(pi) {
322
322
  state.bootPromise = undefined;
323
323
  state.flushPromise = undefined;
324
324
  state.queuedMessages = [];
325
+ state.bridge.refresh();
325
326
  state.lastJarvisSeenMainContext = undefined;
326
327
  state.allowSideTools = false;
327
328
  state.allowFollowUpToMain = false;
@@ -379,6 +380,7 @@ export default function jarvisExtension(pi) {
379
380
  state.bootPromise = undefined;
380
381
  state.flushPromise = undefined;
381
382
  state.queuedMessages = [];
383
+ state.bridge.refresh();
382
384
  state.lastJarvisSeenMainContext = undefined;
383
385
  state.sessionRef = sessionRef;
384
386
  resetTransientAccessControls(state);
@@ -446,6 +448,7 @@ export default function jarvisExtension(pi) {
446
448
  state.bootPromise = undefined;
447
449
  state.flushPromise = undefined;
448
450
  state.queuedMessages = [];
451
+ state.bridge.refresh();
449
452
  state.lastJarvisSeenMainContext = undefined;
450
453
  state.jarvisModelSelection = { mode: "follow-main" };
451
454
  state.jarvisModelSelectionSource = "default";
@@ -791,10 +794,14 @@ async function executeJarvisSideCommand(pi, state, ctx, runtime, command) {
791
794
  runtime.addSystemMessage(runtime.describeSessionTree());
792
795
  return runtime;
793
796
  }
797
+ const generation = state.bootGeneration;
794
798
  await runtime.navigateSessionTree(command.targetId, {
795
799
  summarize: command.summarize,
796
800
  customInstructions: command.customInstructions,
797
801
  });
802
+ if (generation === state.bootGeneration && state.runtime === runtime) {
803
+ state.bridge.resetTranscript();
804
+ }
798
805
  return runtime;
799
806
  }
800
807
  const generation = ++state.bootGeneration;
@@ -820,6 +827,7 @@ function normalizeInitialMessage(args) {
820
827
  }
821
828
  function queueMessage(state, message) {
822
829
  state.queuedMessages.push(message);
830
+ state.bridge.refresh();
823
831
  }
824
832
  function parseJarvisSideCommand(message) {
825
833
  const text = message.trim();
@@ -863,6 +871,8 @@ function createOverlayView(pi, state, ctx) {
863
871
  return {
864
872
  isReady: () => state.runtime?.isReady() ?? false,
865
873
  isStreaming: () => state.runtime?.isStreaming() ?? false,
874
+ getQueuedMessageCount: () => state.queuedMessages === queue ? queue.length : 0,
875
+ getIsProcessing: () => state.queuedMessages === queue && Boolean(state.bootPromise || state.flushPromise),
866
876
  getModelLabel: () => state.runtime?.getModelLabel() ?? formatModelLabel(getDesiredJarvisModel(state)),
867
877
  getModelModeLabel: () => getJarvisModelModeLabel(state.jarvisModelSelection),
868
878
  getMainStatusLabel: () => state.mainContext.summary.mainStatus,
@@ -898,6 +908,8 @@ function createOverlayView(pi, state, ctx) {
898
908
  if (state.queuedMessages !== queue)
899
909
  return;
900
910
  queueMessage(state, text);
911
+ if (state.queuedMessages !== queue)
912
+ return;
901
913
  await flushQueuedMessages(pi, state, ctx);
902
914
  },
903
915
  };
@@ -905,7 +917,8 @@ function createOverlayView(pi, state, ctx) {
905
917
  function getOverlayEntries(state) {
906
918
  let entries;
907
919
  if (state.runtime) {
908
- entries = state.runtime.getDisplayEntries();
920
+ // Do not append per-render UI status to runtime-owned transcript arrays.
921
+ entries = [...state.runtime.getDisplayEntries()];
909
922
  }
910
923
  else {
911
924
  const hasRestorableSessionRef = Boolean(state.sessionRef?.file && existsSync(state.sessionRef.file));
@@ -917,12 +930,7 @@ function getOverlayEntries(state) {
917
930
  : "Starting /jarvis side conversation…",
918
931
  },
919
932
  ];
920
- if (state.queuedMessages.length > 0) {
921
- entries.push({ kind: "status", text: `Queued ${state.queuedMessages.length} message${state.queuedMessages.length === 1 ? "" : "s"}…` });
922
- }
923
933
  }
924
- entries.push({ kind: "status", text: `Since last /jarvis turn: ${formatMainContextDeltaLabel(state.lastJarvisSeenMainContext, state.mainContext)}` });
925
- entries.push({ kind: "status", text: `Repo tools: ${state.runtime?.getRepoToolsDetailLabel() ?? (state.allowSideTools ? "local tools only" : "repo tools off")}` });
926
934
  if (!isModelBridgeCompatible(getDesiredJarvisModel(state))) {
927
935
  entries.push({ kind: "status", text: "Relay disabled: current /jarvis model is incompatible with bridge tools" });
928
936
  }
@@ -965,7 +973,10 @@ async function flushQueuedMessages(pi, state, ctx) {
965
973
  const queue = state.queuedMessages;
966
974
  const isCurrent = () => state.queuedMessages === queue;
967
975
  let flushPromise;
968
- flushPromise = (async () => {
976
+ // Publish ownership before any refresh or asynchronous work can reenter.
977
+ flushPromise = Promise.resolve().then(async () => {
978
+ if (!isCurrent())
979
+ return;
969
980
  try {
970
981
  updateContextState(pi, state, ctx);
971
982
  let runtime = await ensureRuntime(pi, state, ctx);
@@ -975,6 +986,9 @@ async function flushQueuedMessages(pi, state, ctx) {
975
986
  // Consume once: a failed command must not poison the queue, and an
976
987
  // uncertain provider/tool send must not be retried automatically.
977
988
  const message = queue.shift();
989
+ state.bridge.refresh();
990
+ if (!isCurrent())
991
+ return;
978
992
  try {
979
993
  const command = parseJarvisSideCommand(message);
980
994
  if (command) {
@@ -1003,13 +1017,19 @@ async function flushQueuedMessages(pi, state, ctx) {
1003
1017
  if (!isCurrent() || isStaleJarvisBootError(error))
1004
1018
  return;
1005
1019
  queue.length = 0;
1020
+ state.bridge.refresh();
1021
+ if (!isCurrent())
1022
+ return;
1006
1023
  state.bridge.notify(`/jarvis startup failed: ${error instanceof Error ? error.message : String(error)}. Please submit again.`, "error");
1007
1024
  }
1008
- })().finally(() => {
1009
- if (state.flushPromise === flushPromise)
1025
+ }).finally(() => {
1026
+ if (state.flushPromise === flushPromise) {
1010
1027
  state.flushPromise = undefined;
1028
+ state.bridge.refresh();
1029
+ }
1011
1030
  });
1012
1031
  state.flushPromise = flushPromise;
1032
+ state.bridge.refresh();
1013
1033
  return flushPromise;
1014
1034
  }
1015
1035
  async function ensureRuntime(pi, state, ctx) {
@@ -1083,9 +1103,11 @@ async function ensureRuntime(pi, state, ctx) {
1083
1103
  }).finally(() => {
1084
1104
  if (state.bootPromise === bootPromise) {
1085
1105
  state.bootPromise = undefined;
1106
+ state.bridge.refresh();
1086
1107
  }
1087
1108
  });
1088
1109
  state.bootPromise = bootPromise;
1110
+ state.bridge.refresh();
1089
1111
  return bootPromise;
1090
1112
  }
1091
1113
  function formatModelLabel(model) {