jeo-code 0.7.24 → 0.7.26
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 +21 -0
- package/README.ja.md +2 -2
- package/README.ko.md +2 -2
- package/README.md +2 -2
- package/README.zh.md +2 -2
- package/package.json +2 -1
- package/src/commands/launch/input.ts +55 -1
- package/src/commands/launch.ts +37 -9
- package/src/tui/components/input-box.ts +27 -0
- package/src/tui/terminal.ts +35 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
The README mirrors the latest 5 entries — regenerate with `bun run changelog:sync`.
|
|
8
8
|
|
|
9
|
+
## [0.7.26] - 2026-07-02
|
|
10
|
+
_Esc/Ctrl+C reliably clears the input box (fixes a gate the earlier reorder missed), and un-bracketed multi-line pastes no longer shred into premature per-line submits._
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Esc (and meta-mapped Cmd+C) at the prompt intermittently did nothing.** A prior fix reordered the Esc check ahead of the `previewPending` reentrancy gate, but missed an earlier, more totalizing `!previewArmed || pickerActive` gate that Ctrl+C already bypassed (checked first) while Esc did not — so a fresh session's very first Esc after typing could be silently swallowed. The Esc/meta+C check now runs immediately after the Ctrl+C check, before that gate, mirroring Ctrl+C's existing precedent. Verified live in a real `jeo` TUI (tmux-driven keystrokes): 10/10 fresh-session repro attempts now clear the box; previously flaky.
|
|
14
|
+
- **Un-bracketed multi-line paste shredded into separate premature submits.** Terminals/paths that deliver a multi-line paste as ONE stdin chunk WITHOUT bracketed-paste markers (DECSET 2004) — e.g. a raw X11 primary-selection middle-click paste, some SSH clients, or a tmux `paste-buffer` binding without `-p` — hit `filterPromptInputChunk`'s bare `\r`/`\n` pass-through and had each embedded line submitted as its own message mid-composition instead of landing as one multi-line draft. Reproduced live: pasting `"first line\nsecond line\nthird line"` via an un-bracketed tmux paste immediately submitted line 1 to the model, leaving the rest to leak in as orphaned lines. Fixed with a guard in `src/commands/launch/input.ts`: outside an active bracketed paste, a linebreak with MORE data after it in the SAME synchronous chunk (impossible for a genuine standalone Enter keypress, which always arrives as the last byte of its read) now folds to the multi-line sentinel instead of submitting — matching the bracketed-paste contract (review the whole block, then press Enter once). A trailing linebreak with nothing after it is unaffected and still submits normally.
|
|
15
|
+
|
|
16
|
+
### Verified
|
|
17
|
+
- `bun run typecheck` clean; full suite green — 2077 pass / 0 fail across 239 files (4 new cases in `test/prompt-key-filter.test.ts` covering the un-bracketed paste guard, including a no-false-positive check for ordinary single-line typing).
|
|
18
|
+
- Both fixes reproduced and confirmed live in a real running `jeo --no-session` TUI inside tmux (not just unit tests): the exact 3-line un-bracketed paste that previously triggered a premature submit now stays as one multi-line draft, and Esc immediately clears it.
|
|
19
|
+
|
|
20
|
+
## [0.7.25] - 2026-07-02
|
|
21
|
+
_Shift+Enter now reliably distinguishable from Enter on kitty/WezTerm/iTerm2/xterm via modifyOtherKeys + kitty keyboard protocol; row-aware Home/End and Cmd+Left/Right in multi-row drafts._
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- **modifyOtherKeys + kitty keyboard protocol on launch.** `enableModifyOtherKeys()` (`CSI >4;2m`) and `enableKittyKeyboard()` (`CSI >1u`) are sent to the terminal at REPL startup so Shift+Enter is distinguishable from plain Enter on every modern terminal that supports either protocol. Terminals that support neither silently ignore both sequences. Both modes are restored to defaults (`CSI >4;0m` / `CSI >0u`) on process exit via `process.once("exit", …)`. Four new exports in `src/tui/terminal.ts`: `enableModifyOtherKeys`, `disableModifyOtherKeys`, `enableKittyKeyboard`, `disableKittyKeyboard`.
|
|
25
|
+
- **Row-aware Home/End (and macOS Cmd+Left/Right) in multi-row input.** `ROW_HOME_SEQS` / `ROW_END_SEQS` sequences are now caught by `filterPromptInputChunk` _before_ the old whole-buffer `CURSOR_COMBO_REWRITES` path. `rowBoundaryOffset()` (new export in `input-box.ts`) walks the same `caretCells` grid as vertical navigation and jumps the caret to the start/end of the current **visual row**, degenerating to `0`/`length` on a single-row draft — matching macOS/editor convention of "start/end of THIS line".
|
|
26
|
+
|
|
27
|
+
### Verified
|
|
28
|
+
- `bun run typecheck` clean; full suite green — 2073 pass / 0 fail across 239 files — including two new test files: `test/row-boundary.test.ts` (9 cases for `rowBoundaryOffset`) and `test/row-home-end-filter.test.ts` (5 cases for filter-level row-aware Home/End).
|
|
29
|
+
|
|
9
30
|
## [0.7.24] - 2026-07-02
|
|
10
31
|
_OAuth callback page redesign with embedded jeo wordmark + Close button; memory budget O(n²)→O(n) incremental tracking fix._
|
|
11
32
|
|
package/README.ja.md
CHANGED
|
@@ -205,11 +205,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
|
|
|
205
205
|
## 変更履歴 (Changelog)
|
|
206
206
|
|
|
207
207
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
208
|
+
- **[0.7.26]** (2026-07-02) — Esc/Ctrl+C reliably clears the input box (fixes a gate the earlier reorder missed), and un-bracketed multi-line pastes no longer shred into premature per-line submits.
|
|
209
|
+
- **[0.7.25]** (2026-07-02) — Shift+Enter now reliably distinguishable from Enter on kitty/WezTerm/iTerm2/xterm via modifyOtherKeys + kitty keyboard protocol; row-aware Home/End and Cmd+Left/Right in multi-row drafts.
|
|
208
210
|
- **[0.7.24]** (2026-07-02) — OAuth callback page redesign with embedded jeo wordmark + Close button; memory budget O(n²)→O(n) incremental tracking fix.
|
|
209
211
|
- **[0.7.23]** (2026-07-01) — Anthropic 5th-generation model catalog: adds Sonnet 5, Fable 5, and Mythos 5, promotes the 4.6 generation to 1M context / 128k output, and retires the superseded 4.5/3.5/4.1 ids. Bumps the default model to Sonnet 4.6, prices the new tiers, and fixes the thinking transport for Haiku 4.5 and every 5th-gen model.
|
|
210
212
|
- **[0.7.22]** (2026-06-30) — gajae-code 0.7.3–0.7.8 parity sweep: terminal-bell notifications, a gruvbox-dark TUI theme, a resilient `jeo update` runtime check, and an ultragoal artifact gate. Ports the user-facing improvements from upstream gajae-code that jeo did not yet have, re-implemented to fit jeo's config, command, theming, and skill surfaces.
|
|
211
|
-
- **[0.7.21]** (2026-06-26) — Global llm-wiki vault integration, Gemini/Antigravity thinking indicators, generous file-reading windows, and autopilot flag validation. Adds a shared global wiki root configuration with a /wiki slash command, fires reasoning start signals up front for Gemini/Antigravity models, adjusts the large-file reading discipline to use generous windows, and validates autopilot goal and integer flags.
|
|
212
|
-
- **[0.7.20]** (2026-06-26) — OKF concept-memory retrieval gains a hybrid reranker ported from memsearch. Injection priority no longer rides one raw keyword score — it fuses two complementary ranked channels by Reciprocal Rank Fusion (RRF): IDF-weighted lexical relevance (the sparse/BM25 channel, so rare discriminating terms steer recall) and concept-graph proximity (the local dense/semantic-neighbour channel, so a hub linked from multiple query hits surfaces even with no keyword of its own). All embedding-free and deterministic, layered atop the existing failure-first tier and pinned-invariant reserved budget.
|
|
213
213
|
|
|
214
214
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
215
215
|
<!-- CHANGELOG:END -->
|
package/README.ko.md
CHANGED
|
@@ -205,11 +205,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
|
|
|
205
205
|
## 변경 이력 (Changelog)
|
|
206
206
|
|
|
207
207
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
208
|
+
- **[0.7.26]** (2026-07-02) — Esc/Ctrl+C reliably clears the input box (fixes a gate the earlier reorder missed), and un-bracketed multi-line pastes no longer shred into premature per-line submits.
|
|
209
|
+
- **[0.7.25]** (2026-07-02) — Shift+Enter now reliably distinguishable from Enter on kitty/WezTerm/iTerm2/xterm via modifyOtherKeys + kitty keyboard protocol; row-aware Home/End and Cmd+Left/Right in multi-row drafts.
|
|
208
210
|
- **[0.7.24]** (2026-07-02) — OAuth callback page redesign with embedded jeo wordmark + Close button; memory budget O(n²)→O(n) incremental tracking fix.
|
|
209
211
|
- **[0.7.23]** (2026-07-01) — Anthropic 5th-generation model catalog: adds Sonnet 5, Fable 5, and Mythos 5, promotes the 4.6 generation to 1M context / 128k output, and retires the superseded 4.5/3.5/4.1 ids. Bumps the default model to Sonnet 4.6, prices the new tiers, and fixes the thinking transport for Haiku 4.5 and every 5th-gen model.
|
|
210
212
|
- **[0.7.22]** (2026-06-30) — gajae-code 0.7.3–0.7.8 parity sweep: terminal-bell notifications, a gruvbox-dark TUI theme, a resilient `jeo update` runtime check, and an ultragoal artifact gate. Ports the user-facing improvements from upstream gajae-code that jeo did not yet have, re-implemented to fit jeo's config, command, theming, and skill surfaces.
|
|
211
|
-
- **[0.7.21]** (2026-06-26) — Global llm-wiki vault integration, Gemini/Antigravity thinking indicators, generous file-reading windows, and autopilot flag validation. Adds a shared global wiki root configuration with a /wiki slash command, fires reasoning start signals up front for Gemini/Antigravity models, adjusts the large-file reading discipline to use generous windows, and validates autopilot goal and integer flags.
|
|
212
|
-
- **[0.7.20]** (2026-06-26) — OKF concept-memory retrieval gains a hybrid reranker ported from memsearch. Injection priority no longer rides one raw keyword score — it fuses two complementary ranked channels by Reciprocal Rank Fusion (RRF): IDF-weighted lexical relevance (the sparse/BM25 channel, so rare discriminating terms steer recall) and concept-graph proximity (the local dense/semantic-neighbour channel, so a hub linked from multiple query hits surfaces even with no keyword of its own). All embedding-free and deterministic, layered atop the existing failure-first tier and pinned-invariant reserved budget.
|
|
213
213
|
|
|
214
214
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
215
215
|
<!-- CHANGELOG:END -->
|
package/README.md
CHANGED
|
@@ -232,11 +232,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
|
|
|
232
232
|
## Changelog
|
|
233
233
|
|
|
234
234
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
235
|
+
- **[0.7.26]** (2026-07-02) — Esc/Ctrl+C reliably clears the input box (fixes a gate the earlier reorder missed), and un-bracketed multi-line pastes no longer shred into premature per-line submits.
|
|
236
|
+
- **[0.7.25]** (2026-07-02) — Shift+Enter now reliably distinguishable from Enter on kitty/WezTerm/iTerm2/xterm via modifyOtherKeys + kitty keyboard protocol; row-aware Home/End and Cmd+Left/Right in multi-row drafts.
|
|
235
237
|
- **[0.7.24]** (2026-07-02) — OAuth callback page redesign with embedded jeo wordmark + Close button; memory budget O(n²)→O(n) incremental tracking fix.
|
|
236
238
|
- **[0.7.23]** (2026-07-01) — Anthropic 5th-generation model catalog: adds Sonnet 5, Fable 5, and Mythos 5, promotes the 4.6 generation to 1M context / 128k output, and retires the superseded 4.5/3.5/4.1 ids. Bumps the default model to Sonnet 4.6, prices the new tiers, and fixes the thinking transport for Haiku 4.5 and every 5th-gen model.
|
|
237
239
|
- **[0.7.22]** (2026-06-30) — gajae-code 0.7.3–0.7.8 parity sweep: terminal-bell notifications, a gruvbox-dark TUI theme, a resilient `jeo update` runtime check, and an ultragoal artifact gate. Ports the user-facing improvements from upstream gajae-code that jeo did not yet have, re-implemented to fit jeo's config, command, theming, and skill surfaces.
|
|
238
|
-
- **[0.7.21]** (2026-06-26) — Global llm-wiki vault integration, Gemini/Antigravity thinking indicators, generous file-reading windows, and autopilot flag validation. Adds a shared global wiki root configuration with a /wiki slash command, fires reasoning start signals up front for Gemini/Antigravity models, adjusts the large-file reading discipline to use generous windows, and validates autopilot goal and integer flags.
|
|
239
|
-
- **[0.7.20]** (2026-06-26) — OKF concept-memory retrieval gains a hybrid reranker ported from memsearch. Injection priority no longer rides one raw keyword score — it fuses two complementary ranked channels by Reciprocal Rank Fusion (RRF): IDF-weighted lexical relevance (the sparse/BM25 channel, so rare discriminating terms steer recall) and concept-graph proximity (the local dense/semantic-neighbour channel, so a hub linked from multiple query hits surfaces even with no keyword of its own). All embedding-free and deterministic, layered atop the existing failure-first tier and pinned-invariant reserved budget.
|
|
240
240
|
|
|
241
241
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
242
242
|
<!-- CHANGELOG:END -->
|
package/README.zh.md
CHANGED
|
@@ -205,11 +205,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
|
|
|
205
205
|
## 更新日志 (Changelog)
|
|
206
206
|
|
|
207
207
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
208
|
+
- **[0.7.26]** (2026-07-02) — Esc/Ctrl+C reliably clears the input box (fixes a gate the earlier reorder missed), and un-bracketed multi-line pastes no longer shred into premature per-line submits.
|
|
209
|
+
- **[0.7.25]** (2026-07-02) — Shift+Enter now reliably distinguishable from Enter on kitty/WezTerm/iTerm2/xterm via modifyOtherKeys + kitty keyboard protocol; row-aware Home/End and Cmd+Left/Right in multi-row drafts.
|
|
208
210
|
- **[0.7.24]** (2026-07-02) — OAuth callback page redesign with embedded jeo wordmark + Close button; memory budget O(n²)→O(n) incremental tracking fix.
|
|
209
211
|
- **[0.7.23]** (2026-07-01) — Anthropic 5th-generation model catalog: adds Sonnet 5, Fable 5, and Mythos 5, promotes the 4.6 generation to 1M context / 128k output, and retires the superseded 4.5/3.5/4.1 ids. Bumps the default model to Sonnet 4.6, prices the new tiers, and fixes the thinking transport for Haiku 4.5 and every 5th-gen model.
|
|
210
212
|
- **[0.7.22]** (2026-06-30) — gajae-code 0.7.3–0.7.8 parity sweep: terminal-bell notifications, a gruvbox-dark TUI theme, a resilient `jeo update` runtime check, and an ultragoal artifact gate. Ports the user-facing improvements from upstream gajae-code that jeo did not yet have, re-implemented to fit jeo's config, command, theming, and skill surfaces.
|
|
211
|
-
- **[0.7.21]** (2026-06-26) — Global llm-wiki vault integration, Gemini/Antigravity thinking indicators, generous file-reading windows, and autopilot flag validation. Adds a shared global wiki root configuration with a /wiki slash command, fires reasoning start signals up front for Gemini/Antigravity models, adjusts the large-file reading discipline to use generous windows, and validates autopilot goal and integer flags.
|
|
212
|
-
- **[0.7.20]** (2026-06-26) — OKF concept-memory retrieval gains a hybrid reranker ported from memsearch. Injection priority no longer rides one raw keyword score — it fuses two complementary ranked channels by Reciprocal Rank Fusion (RRF): IDF-weighted lexical relevance (the sparse/BM25 channel, so rare discriminating terms steer recall) and concept-graph proximity (the local dense/semantic-neighbour channel, so a hub linked from multiple query hits surfaces even with no keyword of its own). All embedding-free and deterministic, layered atop the existing failure-first tier and pinned-invariant reserved budget.
|
|
213
213
|
|
|
214
214
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
215
215
|
<!-- CHANGELOG:END -->
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { verticalCursorOffset } from "../../tui/components/input-box";
|
|
1
|
+
import { verticalCursorOffset, rowBoundaryOffset } from "../../tui/components/input-box";
|
|
2
|
+
|
|
2
3
|
|
|
3
4
|
export interface InFlightAbortHarness {
|
|
4
5
|
controller: AbortController;
|
|
@@ -556,6 +557,17 @@ export function decideCtrlC(
|
|
|
556
557
|
* `modifyOtherKeys` form (`CSI 27;2;13~`) and the kitty keyboard-protocol form
|
|
557
558
|
* (`CSI 13;2u`). Shared by the live input filter and its tests so they can't drift. */
|
|
558
559
|
export const SHIFT_ENTER_SEQS: readonly string[] = ["\u001b[27;2;13~", "\u001b[13;2u"];
|
|
560
|
+
/** Sequences that should jump the caret to the START of the current VISUAL ROW: macOS
|
|
561
|
+
* Cmd+Left (`CSI 1;9D`) and the platform-neutral Home key in both its xterm (`CSI H`)
|
|
562
|
+
* and vt220 (`CSI 1~`) forms, plus the SS3 variant (`ESC O H`) some terminals send in
|
|
563
|
+
* application-cursor-keys mode. Windows terminals have no Cmd-combo equivalent — bare
|
|
564
|
+
* Home is the "적절한 윈도우 단축키" for this action, so it must get the SAME row-aware
|
|
565
|
+
* treatment as Cmd+Left, not just readline's native (whole-buffer) binding. */
|
|
566
|
+
export const ROW_HOME_SEQS: readonly string[] = ["\u001b[1;9D", "\u001b[H", "\u001b[1~", "\u001bOH"];
|
|
567
|
+
/** Mirror of {@link ROW_HOME_SEQS} for the END of the current visual row: macOS
|
|
568
|
+
* Cmd+Right (`CSI 1;9C`) and the xterm/vt220/SS3 End key forms. */
|
|
569
|
+
export const ROW_END_SEQS: readonly string[] = ["\u001b[1;9C", "\u001b[F", "\u001b[4~", "\u001bOF"];
|
|
570
|
+
|
|
559
571
|
|
|
560
572
|
/** Minimal readline view the prompt key-filter reads (and mutates `cursor` on a
|
|
561
573
|
* vertical-nav "move"). The live filter passes the real readline interface; tests
|
|
@@ -697,6 +709,30 @@ export function filterPromptInputChunk(
|
|
|
697
709
|
if (data.startsWith(seq, i)) { out += MULTILINE_SENTINEL; i += seq.length; matched = true; break; }
|
|
698
710
|
}
|
|
699
711
|
if (matched) continue;
|
|
712
|
+
// Row-aware Home/End: macOS Cmd+Left/Right and the bare Home/End keys (the Windows/
|
|
713
|
+
// Linux equivalent — there is no Cmd-combo there) jump to the START/END of the
|
|
714
|
+
// CURRENT VISUAL ROW, not the whole buffer. Native readline binds these to
|
|
715
|
+
// beginning-of-line/end-of-line, which is only correct on a single-row draft; on a
|
|
716
|
+
// multi-row draft (Shift+Enter breaks or box soft-wrap) it overshoots past the
|
|
717
|
+
// current row. Checked BEFORE `matchCursorCombo` (which would otherwise rewrite
|
|
718
|
+
// Cmd+Left/Right to the whole-buffer Ctrl+A/E) so this always wins when `rl` can
|
|
719
|
+
// supply a cursor to compute from; without `rl` it falls through to that rewrite
|
|
720
|
+
// (or, for bare Home/End, straight to readline's native binding) as a safe degrade.
|
|
721
|
+
const rowEdge = ROW_HOME_SEQS.find(seq => data.startsWith(seq, i)) !== undefined ? "start"
|
|
722
|
+
: ROW_END_SEQS.find(seq => data.startsWith(seq, i)) !== undefined ? "end"
|
|
723
|
+
: undefined;
|
|
724
|
+
if (rowEdge && rl) {
|
|
725
|
+
const seq = (rowEdge === "start" ? ROW_HOME_SEQS : ROW_END_SEQS).find(s => data.startsWith(s, i))!;
|
|
726
|
+
const line = rl.line ?? "";
|
|
727
|
+
const winCols = Math.max(24, env.columns - 1);
|
|
728
|
+
const textWidth = Math.max(1, Math.max(24, winCols) - 6);
|
|
729
|
+
const cur = typeof rl.cursor === "number" ? rl.cursor : line.length;
|
|
730
|
+
const expanded = line.split(MULTILINE_SENTINEL).join("\n");
|
|
731
|
+
rl.cursor = rowBoundaryOffset(expanded, cur, textWidth, rowEdge);
|
|
732
|
+
i += seq.length;
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
|
|
700
736
|
const combo = matchCursorCombo(data, i);
|
|
701
737
|
if (combo) { out += combo[1]; i += combo[0].length; continue; }
|
|
702
738
|
// Up/Down between the box's visual rows (textarea feel) for any MULTI-ROW draft;
|
|
@@ -715,7 +751,25 @@ export function filterPromptInputChunk(
|
|
|
715
751
|
}
|
|
716
752
|
out += data.slice(i, i + 3); i += 3; continue;
|
|
717
753
|
}
|
|
754
|
+
// Un-bracketed multi-line paste guard: some terminals/multiplexers (a raw X11
|
|
755
|
+
// primary-selection middle-click paste, certain SSH clients, or a tmux binding
|
|
756
|
+
// that omits `-p`) deliver a multi-line paste as ONE stdin chunk WITHOUT the
|
|
757
|
+
// bracketed-paste markers this filter otherwise relies on (PASTE_START/PASTE_END
|
|
758
|
+
// above). Outside an active bracketed paste, readline treats every bare `\r`/`\n`
|
|
759
|
+
// as Enter — so an un-bracketed 3-line paste submitted line 1 immediately, mid-
|
|
760
|
+
// composition, and left the rest to leak in as separate prompts (the "붙여넣기가
|
|
761
|
+
// 잘 안됨" bug: works for single-line/bracketed paste, corrupts multi-line paste
|
|
762
|
+
// when the terminal doesn't negotiate DECSET 2004). A genuine Enter keypress is
|
|
763
|
+
// always the LAST byte of its chunk — raw mode delivers one keystroke per read —
|
|
764
|
+
// so a linebreak with MORE data after it in the SAME synchronous chunk can only be
|
|
765
|
+
// a paste (or a scripted multi-line feed); fold it to the sentinel instead of
|
|
766
|
+
// submitting, matching the bracketed-paste contract (review the whole block, then
|
|
767
|
+
// press Enter once to submit). A trailing linebreak (nothing after it) keeps the
|
|
768
|
+
// existing behavior below — including the opt-in lone-LF Shift+Enter rule.
|
|
769
|
+
const bareBreakLen = data.startsWith("\r\n", i) ? 2 : (data[i] === "\r" || data[i] === "\n") ? 1 : 0;
|
|
770
|
+
if (bareBreakLen > 0 && i + bareBreakLen < data.length) { out += MULTILINE_SENTINEL; i += bareBreakLen; continue; }
|
|
718
771
|
if (env.loneLfShiftEnter && data[i] === "\n") { out += MULTILINE_SENTINEL; i += 1; continue; } // lone LF = Shift+Enter (opt-in)
|
|
772
|
+
|
|
719
773
|
// Ctrl+L (form feed): the prompt redraw hotkey, handled on the process.stdin keypress
|
|
720
774
|
// listener — never forward it to readline as a literal char.
|
|
721
775
|
if (data[i] === "\u000c") { i += 1; continue; }
|
package/src/commands/launch.ts
CHANGED
|
@@ -101,7 +101,8 @@ import {
|
|
|
101
101
|
sessionPath,
|
|
102
102
|
appendCompaction,
|
|
103
103
|
} from "../agent/session";
|
|
104
|
-
import { clearLine, cursorUp, toColumn, truncate as truncateAnsi, size as terminalSize, resetMouseTracking, clearScreen, clearVisible, clearToEnd } from "../tui/terminal";
|
|
104
|
+
import { clearLine, cursorUp, toColumn, truncate as truncateAnsi, size as terminalSize, resetMouseTracking, clearScreen, clearVisible, clearToEnd, enableModifyOtherKeys, disableModifyOtherKeys, enableKittyKeyboard, disableKittyKeyboard } from "../tui/terminal";
|
|
105
|
+
|
|
105
106
|
|
|
106
107
|
import {
|
|
107
108
|
type LaunchFlags,
|
|
@@ -1563,7 +1564,19 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1563
1564
|
// 'keypress' too so the footer-redraw / paste-marker / picker listeners (registered
|
|
1564
1565
|
// on process.stdin below) still fire.
|
|
1565
1566
|
emitKeypressEvents(process.stdin);
|
|
1567
|
+
// Ask the terminal to make Shift+Enter DISTINGUISHABLE from plain Enter — without
|
|
1568
|
+
// this, `SHIFT_ENTER_SEQS`/the lone-LF opt-in never see anything to match, because
|
|
1569
|
+
// most terminals send NOTHING (or a byte identical to Enter) for Shift+Enter unless
|
|
1570
|
+
// one of these protocols is explicitly requested. Both are safe to send unconditionally:
|
|
1571
|
+
// a terminal that doesn't implement either silently ignores the unrecognized private-mode
|
|
1572
|
+
// sequence. Restored on exit so a shell/editor started afterward isn't left with an
|
|
1573
|
+
// unexpected keyboard-encoding mode.
|
|
1574
|
+
process.stdout.write(enableModifyOtherKeys() + enableKittyKeyboard());
|
|
1575
|
+
process.once("exit", () => {
|
|
1576
|
+
try { process.stdout.write(disableKittyKeyboard() + disableModifyOtherKeys()); } catch { /* terminal gone */ }
|
|
1577
|
+
});
|
|
1566
1578
|
}
|
|
1579
|
+
|
|
1567
1580
|
const rl = createInterface({
|
|
1568
1581
|
input: keyFilter ?? process.stdin,
|
|
1569
1582
|
// Single-box input: gate readline's output while the boxed footer is armed so its own
|
|
@@ -1889,12 +1902,16 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1889
1902
|
// the prompt never carries a trailing/floating blank block.
|
|
1890
1903
|
const setFooterRows = (n: number) => {
|
|
1891
1904
|
n = Math.max(1, Math.min(n, footerRows));
|
|
1905
|
+
|
|
1892
1906
|
if (!previewArmed || n === footerRendered) return;
|
|
1907
|
+
|
|
1893
1908
|
let s = footerParkedRow > 0 ? cursorUp(footerParkedRow) : "";
|
|
1894
1909
|
s += toColumn(1) + clearToEnd(); // wipe old region; cursor now at its top (after output)
|
|
1895
1910
|
if (n > 1) s += "\n".repeat(n - 1) + cursorUp(n - 1);
|
|
1896
1911
|
s += toColumn(1);
|
|
1912
|
+
|
|
1897
1913
|
out.write(s);
|
|
1914
|
+
|
|
1898
1915
|
footerRendered = n;
|
|
1899
1916
|
footerParkedRow = 0;
|
|
1900
1917
|
lastFooterKey = ""; // force a full repaint into the resized region
|
|
@@ -2099,6 +2116,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
2099
2116
|
// Re-pin the reservation to the height the latest preview/panel wants (compact at
|
|
2100
2117
|
// idle, grown for a dropdown) BEFORE painting, so no reserved blank trails the prompt.
|
|
2101
2118
|
setFooterRows(footerWantRows);
|
|
2119
|
+
|
|
2102
2120
|
// ALWAYS paint exactly footerRendered rows so the reservation is fully covered
|
|
2103
2121
|
// and no row can spill past it — the bug fix that kept `@folder<more text>`
|
|
2104
2122
|
// typing from scrolling the input box (and prior output) off the top.
|
|
@@ -2173,7 +2191,9 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
2173
2191
|
const clearTypedInput = (): boolean => {
|
|
2174
2192
|
const rli = rl as unknown as { line: string; cursor: number; _refreshLine?: () => void };
|
|
2175
2193
|
const hadPastedQueue = queuedPromptInput.pastedLines.length > 0;
|
|
2194
|
+
|
|
2176
2195
|
if (!promptHasContent()) return false;
|
|
2196
|
+
|
|
2177
2197
|
// ESC is the escape hatch for an accidental giant paste: drop the queued batch.
|
|
2178
2198
|
if (hadPastedQueue) {
|
|
2179
2199
|
const dropped = queuedPromptInput.pastedLines.splice(0).length;
|
|
@@ -2719,6 +2739,20 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
2719
2739
|
handleCtrlC();
|
|
2720
2740
|
return;
|
|
2721
2741
|
}
|
|
2742
|
+
// ESC (or a meta-mapped Cmd+C) at the prompt: wipe the typed text. Checked HERE,
|
|
2743
|
+
// before the `!previewArmed || pickerActive` gate below — mirroring Ctrl+C right
|
|
2744
|
+
// above, which already bypasses that gate. Without this, a transient window where
|
|
2745
|
+
// `previewArmed` is momentarily false (the same class of race Ctrl+C was already
|
|
2746
|
+
// immune to) silently swallowed the ESC keypress: the box stayed unaffected and
|
|
2747
|
+
// `clearTypedInput()` never ran (the "esc 입력 후 안 먹힘" bug — Esc after typing
|
|
2748
|
+
// intermittently did nothing). `clearTypedInput()` itself already no-ops safely
|
|
2749
|
+
// when there is nothing to clear or no armed box to redraw into, so running it
|
|
2750
|
+
// unconditionally here is safe even mid-picker or pre-arm.
|
|
2751
|
+
if (key && ((key.name === "escape" && !key.ctrl) || (key.meta && key.name === "c"))) {
|
|
2752
|
+
clearTypedInput();
|
|
2753
|
+
return;
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2722
2756
|
if (!previewArmed || pickerActive) return;
|
|
2723
2757
|
// Drag-and-drop file attach: a terminal delivers a dropped file as its PATH typed
|
|
2724
2758
|
// into the box, wrapped in a bracketed paste. On paste-end, swap any readable image
|
|
@@ -2826,16 +2860,10 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
2826
2860
|
return;
|
|
2827
2861
|
}
|
|
2828
2862
|
if (previewPending) return;
|
|
2829
|
-
|
|
2830
|
-
// ESC decodes as `escape` (meta is set for a lone ESC byte — accept both)
|
|
2831
|
-
// only after readline's escape-sequence timeout, so arrow/wheel sequences
|
|
2832
|
-
// never trigger this.
|
|
2833
|
-
if (key && ((key.name === "escape" && !key.ctrl) || (key.meta && key.name === "c"))) {
|
|
2834
|
-
clearTypedInput();
|
|
2835
|
-
return;
|
|
2836
|
-
}
|
|
2863
|
+
|
|
2837
2864
|
// Ctrl+C is handled above (clear-or-exit); keep this guard for defensive ordering only.
|
|
2838
2865
|
if (key?.ctrl && key.name === "c") return;
|
|
2866
|
+
|
|
2839
2867
|
previewPending = true;
|
|
2840
2868
|
setImmediate(() => {
|
|
2841
2869
|
previewPending = false;
|
|
@@ -242,3 +242,30 @@ export function verticalCursorOffset(
|
|
|
242
242
|
}
|
|
243
243
|
return best !== -1 ? best : firstOnRow;
|
|
244
244
|
}
|
|
245
|
+
/** Caret offset at the START or END of the VISUAL ROW containing `cursor`, using the
|
|
246
|
+
* SAME wrapping rule as the input box (`caretCells`/`wrapWithCursor`). This is the
|
|
247
|
+
* row-aware counterpart to Home/End (and macOS Cmd+Left/Right): on a single-row draft
|
|
248
|
+
* it degenerates to the whole buffer's start/end (0 / length), but on a multi-row draft
|
|
249
|
+
* (Shift+Enter hard breaks OR a long line the box soft-wraps) it stops at the CURRENT
|
|
250
|
+
* row's boundary instead of jumping past other rows — matching the platform convention
|
|
251
|
+
* (macOS Cmd+←/→, editors' Home/End) of "start/end of THIS line", not "start/end of the
|
|
252
|
+
* whole document". */
|
|
253
|
+
export function rowBoundaryOffset(
|
|
254
|
+
text: string,
|
|
255
|
+
cursor: number,
|
|
256
|
+
width: number,
|
|
257
|
+
edge: "start" | "end",
|
|
258
|
+
): number {
|
|
259
|
+
const cells = caretCells(text, Math.max(1, width));
|
|
260
|
+
if (cells.length === 0) return 0;
|
|
261
|
+
const pos = Math.max(0, Math.min(cursor, cells.length - 1));
|
|
262
|
+
const row = cells[pos]!.row;
|
|
263
|
+
let start = pos;
|
|
264
|
+
let end = pos;
|
|
265
|
+
for (let p = 0; p < cells.length; p++) {
|
|
266
|
+
if (cells[p]!.row !== row) continue;
|
|
267
|
+
if (p < start) start = p;
|
|
268
|
+
if (p > end) end = p;
|
|
269
|
+
}
|
|
270
|
+
return edge === "start" ? start : end;
|
|
271
|
+
}
|
package/src/tui/terminal.ts
CHANGED
|
@@ -100,3 +100,38 @@ export function isTTY(): boolean {
|
|
|
100
100
|
export function truncate(line: string, cols: number): string {
|
|
101
101
|
return truncateToWidth(line, cols);
|
|
102
102
|
}
|
|
103
|
+
/** Request xterm's `modifyOtherKeys` mode 2 (`CSI > 4 ; 2 m`): the terminal starts
|
|
104
|
+
* encoding "inert" combos — Shift+Enter among them — as a full `CSI 27;<mod>;<code>~`
|
|
105
|
+
* sequence instead of sending a byte indistinguishable from plain Enter (or nothing at
|
|
106
|
+
* all). Without this, jeo's Shift+Enter → newline handling (`SHIFT_ENTER_SEQS` in
|
|
107
|
+
* `commands/launch/input.ts`) never actually fires on most terminals — the terminal
|
|
108
|
+
* never emits a sequence to match because nothing asked it to. Terminals that don't
|
|
109
|
+
* implement the mode (Windows Terminal's legacy conpty path, some minimal emulators)
|
|
110
|
+
* silently ignore the unrecognized private-mode sequence — safe to send unconditionally. */
|
|
111
|
+
export function enableModifyOtherKeys(): string {
|
|
112
|
+
return `${ESC}>4;2m`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Restore `modifyOtherKeys` to its default (mode 0 — "no special encoding") on exit,
|
|
116
|
+
* the counterpart to {@link enableModifyOtherKeys}. */
|
|
117
|
+
export function disableModifyOtherKeys(): string {
|
|
118
|
+
return `${ESC}>4;0m`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Enable the kitty keyboard protocol's "disambiguate escape codes" flag (bit 1 of
|
|
122
|
+
* `CSI > flags u`): the modern, more widely supported (kitty, wezterm, foot, contour,
|
|
123
|
+
* ghostty, iTerm2, and increasingly Windows Terminal) alternative delivery mechanism
|
|
124
|
+
* for the same Shift+Enter sequence jeo already recognizes (`CSI 13;2u`,
|
|
125
|
+
* `SHIFT_ENTER_SEQS`). Terminals without kitty-protocol support ignore the private-mode
|
|
126
|
+
* `u`-suffixed CSI sequence — safe to send unconditionally alongside
|
|
127
|
+
* {@link enableModifyOtherKeys} so whichever protocol the terminal supports (if either)
|
|
128
|
+
* makes Shift+Enter distinguishable from plain Enter. */
|
|
129
|
+
export function enableKittyKeyboard(): string {
|
|
130
|
+
return `${ESC}>1u`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Reset the kitty keyboard protocol flags to 0 (disabled) on exit, the counterpart to
|
|
134
|
+
* {@link enableKittyKeyboard}. */
|
|
135
|
+
export function disableKittyKeyboard(): string {
|
|
136
|
+
return `${ESC}>0u`;
|
|
137
|
+
}
|