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 +13 -1
- package/README.md +26 -2
- package/dist/draft-editor.d.ts +53 -0
- package/dist/draft-editor.d.ts.map +1 -0
- package/dist/draft-editor.js +303 -0
- package/dist/draft-editor.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -9
- package/dist/index.js.map +1 -1
- package/dist/overlay-layout.d.ts +21 -0
- package/dist/overlay-layout.d.ts.map +1 -0
- package/dist/overlay-layout.js +132 -0
- package/dist/overlay-layout.js.map +1 -0
- package/dist/overlay.d.ts +28 -12
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +234 -214
- package/dist/overlay.js.map +1 -1
- package/dist/transcript-viewport.d.ts +51 -0
- package/dist/transcript-viewport.d.ts.map +1 -0
- package/dist/transcript-viewport.js +146 -0
- package/dist/transcript-viewport.js.map +1 -0
- package/package.json +2 -2
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
|
[](https://github.com/crustyhacker/pi-jarvis)
|
|
14
14
|
[](./package.json)
|
|
15
15
|
|
|
16
|
-
<p><strong>Current version:</strong> 1.
|
|
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** |
|
|
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":"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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
})
|
|
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) {
|