@oneciel-ai/ciel-runtime 0.2.41 → 0.2.43

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 CHANGED
@@ -5,6 +5,19 @@ capability, followed by the complete commit ledger merged into each release.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.2.43 — 2026-09-08
9
+
10
+ - Isolate child ConPTY Win32-input and mouse-reporting modes from the parent
11
+ Windows terminal. Preserve display controls, bracketed paste and Unicode.
12
+ - Explicitly reset Win32-input mode on startup/shutdown and restore the parent
13
+ console even if closing the pseudo-console fails.
14
+
15
+ ## 0.2.42 — 2026-09-08
16
+
17
+ - Preserve fragmented OSC palette responses and other VT control strings as
18
+ complete frames in the Windows input bridge. Prevent RGB palette replies
19
+ from appearing in the Codex draft; retain literal text and bounded buffering.
20
+
8
21
  ## 0.2.41 — 2026-09-07
9
22
 
10
23
  - Automatically authenticate Codex model and Ciel MCP requests to LAN-bound
@@ -97,7 +97,7 @@ OPENCODE_ENDPOINT_ALIASES = {
97
97
  }
98
98
 
99
99
  APP_NAME = "Ciel Runtime"
100
- VERSION = "0.2.41"
100
+ VERSION = "0.2.43"
101
101
  CREDITS = "Credits: One Ciel LLC"
102
102
  PRELAUNCH_CANCEL = 10
103
103
  PRELAUNCH_LAUNCH_CODEX = 11
@@ -6,7 +6,7 @@ from collections.abc import Callable
6
6
 
7
7
 
8
8
  class TerminalInputFrames:
9
- """Do not expose a partial CSI to a child's keyboard-event parser.
9
+ """Do not expose a partial VT sequence to a child's keyboard parser.
10
10
 
11
11
  A bounded idle timer preserves a standalone Escape key. Nothing is
12
12
  stripped or rewritten, including literal text resembling paste markers.
@@ -32,13 +32,24 @@ class TerminalInputFrames:
32
32
  ready.append(byte)
33
33
  continue
34
34
  self.pending.append(byte)
35
- # CSI/SS3 sequences end with a final byte; other ESC keys
36
- # (including Alt+key) are complete after the next byte.
37
- complete = (
38
- len(self.pending) == 2 and byte not in (ord('['), ord('O'))
39
- ) or (
40
- len(self.pending) >= 3 and 0x40 <= byte <= 0x7e
41
- ) or len(self.pending) >= 64
35
+ introducer = self.pending[1]
36
+ # OSC (including palette replies) and other control strings
37
+ # end at ST, not at their first printable byte. OSC also
38
+ # accepts BEL. An ESC ending one read may start ST in the next.
39
+ if introducer in b']PX^_':
40
+ complete = (
41
+ self.pending.endswith(b'\x1b\\')
42
+ or (introducer == ord(']') and byte == 0x07)
43
+ or byte in (0x18, 0x1a) # CAN/SUB cancel the sequence.
44
+ or len(self.pending) >= 4096
45
+ )
46
+ else:
47
+ # CSI/SS3 final byte, or a two-byte ESC/Alt key.
48
+ complete = (
49
+ len(self.pending) == 2 and introducer not in b'[O'
50
+ ) or (
51
+ len(self.pending) >= 3 and 0x40 <= byte <= 0x7e
52
+ ) or len(self.pending) >= 64
42
53
  if complete:
43
54
  ready.extend(self.pending)
44
55
  self.pending.clear()
@@ -12,7 +12,10 @@ import time
12
12
  from collections.abc import Mapping
13
13
  from typing import Any, Callable
14
14
 
15
- from .terminal_platform_io import TERMINAL_INPUT_MODE_RESET
15
+ from .windows_terminal_modes import (
16
+ WINDOWS_TERMINAL_INPUT_MODE_RESET,
17
+ WindowsTerminalModeFilter,
18
+ )
16
19
  from .terminal_input_frames import TerminalInputFrames
17
20
  from .windows_command_line import command_line_for_create_process
18
21
 
@@ -435,19 +438,18 @@ class WindowsConPtySession:
435
438
  self._input_handle = None
436
439
  # Keep the output reader alive while ClosePseudoConsole asks its host
437
440
  # to flush and exit. Closing the output pipe first can deadlock conhost.
438
- if self._hpc and self._kernel32:
439
- self._kernel32.ClosePseudoConsole(self._hpc)
440
- self._hpc = None
441
- if self._output_thread is not None:
442
- self._output_thread.join(timeout=1.0)
443
- self._stop.set()
444
- # Interactive TUIs enable DEC mouse/focus reporting in the terminal
445
- # emulator. A normal CLI shutdown disables those modes itself, but a
446
- # crash leaves Windows Terminal sending sequences such as
447
- # ``ESC[<35;29;23M`` into the parent shell on every mouse move. The
448
- # Win32 console-mode restore below cannot clear emulator-owned DEC
449
- # state, so reset it explicitly while VT output is still enabled.
450
- self._reset_and_restore_parent_console()
441
+ try:
442
+ if self._hpc and self._kernel32:
443
+ self._kernel32.ClosePseudoConsole(self._hpc)
444
+ self._hpc = None
445
+ if self._output_thread is not None:
446
+ self._output_thread.join(timeout=1.0)
447
+ finally:
448
+ self._stop.set()
449
+ # Win32 SetConsoleMode cannot clear emulator-owned DEC state.
450
+ # Reset even when closing conhost fails, then prevent late output
451
+ # from re-enabling modes after the parent console is restored.
452
+ self._reset_and_restore_parent_console()
451
453
  if self._output_handle and self._kernel32:
452
454
  try:
453
455
  self._kernel32.CloseHandle(self._output_handle)
@@ -459,12 +461,12 @@ class WindowsConPtySession:
459
461
  self._process_handle = None
460
462
 
461
463
  def _reset_parent_terminal_modes(self) -> None:
462
- self._write_parent_terminal_modes(TERMINAL_INPUT_MODE_RESET)
464
+ self._write_parent_terminal_modes(WINDOWS_TERMINAL_INPUT_MODE_RESET)
463
465
 
464
466
  def _reset_and_restore_parent_console(self) -> None:
465
467
  with self._parent_output_lock():
466
468
  self._write_parent_terminal_modes_unlocked(
467
- TERMINAL_INPUT_MODE_RESET
469
+ WINDOWS_TERMINAL_INPUT_MODE_RESET
468
470
  )
469
471
  self._mirror_output = False
470
472
  self._restore_parent_console()
@@ -848,6 +850,10 @@ class WindowsConPtySession:
848
850
  with self._parent_output_lock():
849
851
  if not self._mirror_output:
850
852
  return
853
+ mode_filter = getattr(self, "_parent_mode_filter", None)
854
+ if mode_filter is None:
855
+ mode_filter = self._parent_mode_filter = WindowsTerminalModeFilter()
856
+ data = mode_filter.feed(data, final=final)
851
857
  if self._stdout_console_handle is not None and self._output_decoder is not None:
852
858
  text = self._output_decoder.decode(data, final=final)
853
859
  if text:
@@ -0,0 +1,67 @@
1
+ """Keep child ConPTY input protocols from taking over the parent terminal."""
2
+
3
+ from .terminal_platform_io import TERMINAL_INPUT_MODE_RESET
4
+
5
+
6
+ WINDOWS_TERMINAL_INPUT_MODE_RESET = TERMINAL_INPUT_MODE_RESET + "\x1b[?9001l"
7
+
8
+
9
+ class WindowsTerminalModeFilter:
10
+ """Filter DECSET at the output boundary, not user text at the input boundary.
11
+
12
+ Our parent reader consumes ordinary VT input, not Win32-input-mode records.
13
+ Mouse reporting is also intentionally disabled on Windows. Display modes,
14
+ focus events and bracketed paste are preserved. State spans pipe reads;
15
+ control strings are streamed without buffering their potentially large bodies.
16
+ """
17
+
18
+ _blocked = {9, 1000, 1001, 1002, 1003, 1005, 1006, 1015, 1016, 9001}
19
+
20
+ def __init__(self) -> None:
21
+ self._pending = bytearray()
22
+ self._string = 0
23
+ self._string_escape = False
24
+
25
+ def feed(self, data: bytes, *, final: bool = False) -> bytes:
26
+ output = bytearray()
27
+ for value in data:
28
+ if self._string:
29
+ output.append(value)
30
+ if (self._string_escape and value == 92) or value in (24, 26) or (
31
+ self._string == 93 and value == 7
32
+ ):
33
+ self._string = 0
34
+ self._string_escape = value == 27
35
+ continue
36
+ if value == 27:
37
+ output.extend(self._pending)
38
+ self._pending[:] = b"\x1b"
39
+ continue
40
+ if not self._pending:
41
+ output.append(value)
42
+ continue
43
+ self._pending.append(value)
44
+ if len(self._pending) == 2:
45
+ if value in b"]PX^_":
46
+ self._string = value
47
+ self._string_escape = False
48
+ if value == 91:
49
+ continue
50
+ elif not (64 <= value <= 126) and value not in (24, 26):
51
+ if len(self._pending) < 256:
52
+ continue
53
+ output.extend(self._filter_csi(bytes(self._pending)))
54
+ self._pending.clear()
55
+ if final:
56
+ output.extend(self._pending)
57
+ self._pending.clear()
58
+ return bytes(output)
59
+
60
+ def _filter_csi(self, sequence: bytes) -> bytes:
61
+ if not sequence.startswith(b"\x1b[?") or not sequence.endswith(b"h"):
62
+ return sequence
63
+ parameters = sequence[3:-1].split(b";")
64
+ if not all(part.isdigit() for part in parameters):
65
+ return sequence
66
+ kept = [part for part in parameters if int(part) not in self._blocked]
67
+ return b"\x1b[?" + b";".join(kept) + b"h" if kept else b""
@@ -0,0 +1,18 @@
1
+ title: Windows OSC palette response framing regression
2
+ date: 2026-09-08
3
+ version: 0.2.42
4
+ request: Continue handling user-reported OSC 4 palette strings exposed in console input.
5
+ cause:
6
+ source: TerminalInputFrames treated ESC ] as a complete two-byte key, forwarding the subsequent OSC body separately.
7
+ actual_cli_before: Codex rendered 4;0;rgb:0c0c/0c0c/0c0c followed by the test paste.
8
+ change:
9
+ protocol: Keep OSC, DCS, SOS, PM and APC strings until ST; OSC also accepts BEL; CAN/SUB cancel.
10
+ safety: Never remove literal text; 4096-byte bound and existing 100ms idle flush retained.
11
+ verification:
12
+ actual_cli_after: Both palette and paste tests passed; palette text absent and 1660377 still visible. No model prompt submitted.
13
+ frames: Six tests passed including every control-string split, BEL/ST, plain literal RGB text, bounded malformed strings, standalone Esc and paste.
14
+ static: Ruff passed.
15
+ reference: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
16
+ limitations: Remote user's terminal not directly manipulated; local real Codex reproduction uses simulated split parent-console reads.
17
+ deployment: Continue main stable release workflow; validate npm artifact after publishing.
18
+ excluded: No Kevin AI Net authentication changes or session restart.
@@ -0,0 +1,33 @@
1
+ # Windows parent-terminal input protocol isolation
2
+
3
+ ## Evidence
4
+ - User screenshot: after Codex exits, cmd receives ESC[9;15;9;0;32;1_ and similar records.
5
+ - Microsoft protocol/issues: https://github.com/microsoft/terminal/issues/19576
6
+ - Double encoded mouse reproduction: https://github.com/microsoft/terminal/issues/17851
7
+ - Prior windows_conpty._mirror_bytes forwarded child DECSET without filtering.
8
+ - Prior TERMINAL_INPUT_MODE_RESET omitted DEC private mode 9001.
9
+ - These establish an output-boundary/cleanup defect, not the remote process crash cause.
10
+
11
+ ## Changes
12
+ - New streaming WindowsTerminalModeFilter blocks child enabling Win32 input mode9001
13
+ and mouse reporting on the parent terminal; keeps display, paste, focus and Unicode.
14
+ - Parses across read boundaries; streams control strings without accumulating bodies.
15
+ - Parent startup/shutdown reset now includes 9001l; close failure still restores console.
16
+ - User input is not pattern stripped, rewritten, or redirected to router transport.
17
+
18
+ ## Executed verification
19
+ - test_windows_terminal_modes.py: 5/5 passed on native Windows.
20
+ - Real ConPTY child exited7, raw output contained9001h; parent mirror did not;
21
+ final parent output ended with9001l. Parent output captured, not a real clicked UI.
22
+ - test_windows_conpty.py: 33/33 passed including native transport and resize tests.
23
+ - test_codex_paste_frames.py: 2/2 passed using locally installed actual Codex executable.
24
+ Split paste and OSC palette replies did not enter the Codex prompt as marker text.
25
+ - test_terminal_input_frames.py: 6/6 passed. Architecture budget: 2/2 passed.
26
+ - Replacing the filter with prior passthrough behavior: same native child test
27
+ failed exactly once, confirming the regression test detects the original leak.
28
+ - Runtime group: 287 tests, OK (19 skipped); ruff repository check passed.
29
+
30
+ ## Limits
31
+ - Remote Windows Terminal mouse clicks and the reported process crash have not been
32
+ reproduced directly. Do not report the remote crash as resolved by these tests.
33
+ - This journal contains no authentication tokens or unrelated session content.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneciel-ai/ciel-runtime",
3
- "version": "0.2.41",
3
+ "version": "0.2.43",
4
4
  "description": "Universal AI coding-agent runtime and model-routing layer for Claude, Codex, AGY, ZCode, and compatible runtimes.",
5
5
  "license": "MIT",
6
6
  "author": "One Ciel LLC",