@oneciel-ai/ciel-runtime 0.2.40 → 0.2.42

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,21 @@ capability, followed by the complete commit ledger merged into each release.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.2.42 — 2026-09-08
9
+
10
+ - Preserve fragmented OSC palette responses and other VT control strings as
11
+ complete frames in the Windows input bridge. Prevent RGB palette replies
12
+ from appearing in the Codex draft; retain literal text and bounded buffering.
13
+
14
+ ## 0.2.41 — 2026-09-07
15
+
16
+ - Automatically authenticate Codex model and Ciel MCP requests to LAN-bound
17
+ routers using a separate process-local header. Preserve native OpenAI
18
+ authorization and strip Ciel credentials before forwarding upstream.
19
+ - Reassemble fragmented Windows console VT input sequences before forwarding
20
+ them to ConPTY, preventing split paste boundaries from entering the Codex
21
+ draft as text. Preserve literal text and standalone Escape (100ms idle bound).
22
+
8
23
  ## 0.2.40 — 2026-09-07
9
24
 
10
25
  - Authenticate internal router health checks when the web backend uses a
@@ -0,0 +1,88 @@
1
+ """Attach router authentication without replacing native OpenAI credentials."""
2
+ import json
3
+ from pathlib import Path
4
+ import re
5
+ from urllib.parse import urlparse
6
+
7
+ from .router_access import is_loopback_address
8
+
9
+ CLIENT_TOKEN_ENV = "CIEL_RUNTIME_ROUTER_CLIENT_TOKEN"
10
+
11
+
12
+ def authenticated_codex_command(cmd: list[str], env: dict[str, str], router_base: str) -> list[str]:
13
+ router = urlparse(router_base)
14
+ targets = []
15
+ builtin_base = None
16
+ overrides = {}
17
+ index = 1
18
+ while index < len(cmd):
19
+ argument = cmd[index]
20
+ index += 1
21
+ if argument == '--':
22
+ break
23
+ if argument in ('-c', '--config') and index < len(cmd):
24
+ setting = cmd[index]
25
+ index += 1
26
+ elif argument.startswith('--config='):
27
+ setting = argument.split('=', 1)[1]
28
+ else:
29
+ continue
30
+ key, separator, raw = setting.partition('=')
31
+ if separator:
32
+ key = key.strip()
33
+ # Do not attach credentials based on an overridden earlier URL.
34
+ overrides = {old: value for old, value in overrides.items()
35
+ if not old.startswith(key + '.')}
36
+ overrides[key] = raw
37
+ for key, raw in overrides.items():
38
+ if not raw:
39
+ continue
40
+ match = re.fullmatch(r"model_providers\.([\w-]+)\.base_url", key)
41
+ if match:
42
+ prefix = f"model_providers.{match.group(1)}"
43
+ elif key == "openai_base_url":
44
+ prefix = "model_providers.ciel-runtime-native-auth"
45
+ elif key == "mcp_servers.ciel-runtime-router.url":
46
+ prefix = "mcp_servers.ciel-runtime-router"
47
+ else:
48
+ continue
49
+ try:
50
+ url = urlparse(json.loads(raw))
51
+ except (ValueError, TypeError):
52
+ continue
53
+ # These are Ciel's generated local-router routes, not arbitrary
54
+ # upstream provider URLs. Only authorize this launch's exact origin.
55
+ if (not is_loopback_address(url.hostname) and url.scheme == router.scheme
56
+ and url.netloc == router.netloc
57
+ and url.path.rstrip('/') in ('/v1', '/backend-api/codex', '/ca/mcp')):
58
+ targets.append(prefix)
59
+ if key == 'openai_base_url':
60
+ builtin_base = raw
61
+ if not targets:
62
+ return cmd
63
+ token = str(env.get("CIEL_RUNTIME_ROUTER_EXTERNAL_TOKEN") or "").strip()
64
+ if not token:
65
+ state = env.get("CIEL_RUNTIME_STATE_DIR")
66
+ if state:
67
+ try:
68
+ token = (Path(state) / "router-external-token").read_text(encoding="utf-8").strip()
69
+ except OSError:
70
+ pass
71
+ if not token:
72
+ return cmd
73
+ env[CLIENT_TOKEN_ENV] = token
74
+ options = []
75
+ if builtin_base is not None:
76
+ # Modern Codex forbids overriding the built-in `openai` table.
77
+ # Use a per-launch provider retaining native OpenAI authentication.
78
+ provider = 'ciel-runtime-native-auth'
79
+ cmd = [f'model_provider="{provider}"' if item == 'model_provider="openai"' else item for item in cmd]
80
+ for setting in (f'model_provider="{provider}"',
81
+ f'model_providers.{provider}.name="Ciel Runtime Codex"',
82
+ f'model_providers.{provider}.base_url={builtin_base}',
83
+ f'model_providers.{provider}.wire_api="responses"',
84
+ f'model_providers.{provider}.requires_openai_auth=true'):
85
+ options.extend(['-c', setting])
86
+ for prefix in dict.fromkeys(targets):
87
+ options.extend(['-c', f'{prefix}.env_http_headers.x-ciel-runtime-token="{CLIENT_TOKEN_ENV}"'])
88
+ return [cmd[0], *options, *cmd[1:]]
@@ -201,9 +201,11 @@ class RouterAccessPolicy:
201
201
  and hmac.compare_digest(expected, bridge_expected)
202
202
  ):
203
203
  return False
204
- return bool(
205
- expected and supplied and hmac.compare_digest(expected, supplied)
206
- )
204
+ local_token = str(handler.headers.get("x-ciel-runtime-token") or "").strip()
205
+ return bool(expected and (
206
+ (supplied and hmac.compare_digest(expected, supplied))
207
+ or (local_token and hmac.compare_digest(expected, local_token))
208
+ ))
207
209
  return False
208
210
 
209
211
 
@@ -97,7 +97,7 @@ OPENCODE_ENDPOINT_ALIASES = {
97
97
  }
98
98
 
99
99
  APP_NAME = "Ciel Runtime"
100
- VERSION = "0.2.40"
100
+ VERSION = "0.2.42"
101
101
  CREDITS = "Credits: One Ciel LLC"
102
102
  PRELAUNCH_CANCEL = 10
103
103
  PRELAUNCH_LAUNCH_CODEX = 11
@@ -9,6 +9,7 @@ from dataclasses import dataclass
9
9
 
10
10
  from ciel_runtime_support.claude_environment import CLAUDE_PROJECTED_ENV_KEYS
11
11
  from ciel_runtime_support.managed_tool_injection import should_inject_tool, codex_native_web_tool_overrides
12
+ from ciel_runtime_support.codex_router_auth import authenticated_codex_command
12
13
  from ciel_runtime_support.runtime_constants import (
13
14
  CLAUDE_SERVER_SIDE_WEB_TOOLS,
14
15
  CODEX_RUNTIME_API_KEY_ENV,
@@ -973,6 +974,7 @@ def run_codex(
973
974
  if workspace_mcp is not None and workspace_mcp_launch is not None:
974
975
  workspace_mcp.finish(workspace_mcp_launch)
975
976
  raise
977
+ cmd = authenticated_codex_command(cmd, env, ROUTER_BASE)
976
978
  _log_codex_command_for_diagnostics(cmd, env)
977
979
  record_launch_state_for_cwd(
978
980
  current_launch_cwd_key(),
@@ -1287,6 +1289,7 @@ def run_codex_app_server(
1287
1289
  print(f"Codex App Server listen: {cmd[cmd.index('--listen') + 1]}", flush=True)
1288
1290
  except Exception:
1289
1291
  pass
1292
+ cmd = authenticated_codex_command(cmd, env, ROUTER_BASE)
1290
1293
  _log_codex_app_server_command_for_diagnostics(cmd, env)
1291
1294
  record_launch_state_for_cwd(
1292
1295
  current_launch_cwd_key(),
@@ -0,0 +1,89 @@
1
+ """Reassemble short VT input sequences across parent-console reads."""
2
+ from __future__ import annotations
3
+
4
+ import threading
5
+ from collections.abc import Callable
6
+
7
+
8
+ class TerminalInputFrames:
9
+ """Do not expose a partial VT sequence to a child's keyboard parser.
10
+
11
+ A bounded idle timer preserves a standalone Escape key. Nothing is
12
+ stripped or rewritten, including literal text resembling paste markers.
13
+ """
14
+
15
+ def __init__(self, write: Callable[[bytes], None], *, idle_seconds: float = 0.1):
16
+ self.write = write
17
+ self.idle_seconds = idle_seconds
18
+ self.pending = bytearray()
19
+ self.lock = threading.Lock()
20
+ self.timer: threading.Timer | None = None
21
+ self.generation = 0
22
+
23
+ def feed(self, data: bytes) -> None:
24
+ with self.lock:
25
+ self._cancel()
26
+ ready = bytearray()
27
+ for byte in data:
28
+ if not self.pending:
29
+ if byte == 0x1b:
30
+ self.pending.append(byte)
31
+ else:
32
+ ready.append(byte)
33
+ continue
34
+ self.pending.append(byte)
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
53
+ if complete:
54
+ ready.extend(self.pending)
55
+ self.pending.clear()
56
+ if ready:
57
+ self.write(bytes(ready))
58
+ if self.pending:
59
+ generation = self.generation
60
+ self.timer = threading.Timer(self.idle_seconds, self._expire, args=(generation,))
61
+ self.timer.daemon = True
62
+ self.timer.start()
63
+
64
+ def _cancel(self) -> None:
65
+ self.generation += 1
66
+ if self.timer is not None:
67
+ self.timer.cancel()
68
+ self.timer = None
69
+
70
+ def _expire(self, generation: int) -> None:
71
+ with self.lock:
72
+ if generation != self.generation:
73
+ return
74
+ self.timer = None
75
+ self._flush()
76
+
77
+ def _flush(self) -> None:
78
+ if self.pending:
79
+ data = bytes(self.pending)
80
+ self.pending.clear()
81
+ try:
82
+ self.write(data)
83
+ except OSError:
84
+ pass # Child may have exited while Escape was pending.
85
+
86
+ def close(self) -> None:
87
+ with self.lock:
88
+ self._cancel()
89
+ self._flush()
@@ -13,6 +13,7 @@ from collections.abc import Mapping
13
13
  from typing import Any, Callable
14
14
 
15
15
  from .terminal_platform_io import TERMINAL_INPUT_MODE_RESET
16
+ from .terminal_input_frames import TerminalInputFrames
16
17
  from .windows_command_line import command_line_for_create_process
17
18
 
18
19
 
@@ -980,15 +981,19 @@ class WindowsConPtySession:
980
981
  self._parent_input_draft = draft
981
982
 
982
983
  def _pump_input(self) -> None:
983
- while not self._stop.is_set():
984
- try:
985
- data = self._read_input_bytes()
986
- if not data:
984
+ frames = TerminalInputFrames(self.write)
985
+ try:
986
+ while not self._stop.is_set():
987
+ try:
988
+ data = self._read_input_bytes()
989
+ if not data:
990
+ return
991
+ self._observe_parent_input(data)
992
+ frames.feed(data)
993
+ except OSError:
987
994
  return
988
- self._observe_parent_input(data)
989
- self.write(data)
990
- except OSError:
991
- return
995
+ finally:
996
+ frames.close()
992
997
 
993
998
 
994
999
  __all__ = ["WindowsConPtySession", "conpty_enabled"]
@@ -0,0 +1,14 @@
1
+ title: Attempt remote web backend log capture for Windows paste-marker issue
2
+ date: 2026-09-07
3
+ target: http://10.0.0.238:9683
4
+ authorization: User requested backend log capture and console/mouse diagnostics.
5
+ request: GET /health without credentials
6
+ response:
7
+ timestamp: Mon, 07 Sep 2026 18:02:22 GMT
8
+ status: HTTP/1.0 401 Unauthorized
9
+ challenge: Bearer realm="ciel-runtime"
10
+ message: ciel-runtime router external authentication is required.
11
+ conclusion: Backend reachable; administrative authentication required before logs or TUI diagnostics can be retrieved.
12
+ limitation: Remote runtime version, logs, and console state not retrieved; no assertion that paste issue is diagnosed.
13
+ next_requirement: User-provided administrative external access token for this remote backend.
14
+ changes: No remote writes, input injection, configuration changes, or authentication bypass attempted.
@@ -0,0 +1,36 @@
1
+ title: Authenticated remote backend console investigation
2
+ date: 2026-09-07
3
+ target: http://10.0.0.238:9683
4
+ credentials: Omitted; user-provided token used only for authenticated requests.
5
+ captures:
6
+ health:
7
+ ok: true
8
+ version: 0.2.40
9
+ source_fingerprint: 3bc950932f5d1691
10
+ pid: 21824
11
+ workspace: c:\users\1347817
12
+ router_port: 9683
13
+ instance_id: 9683-a2f58d1342e0
14
+ active_client_count: 1
15
+ active_client_pids: [29116]
16
+ provider: codex
17
+ chat_health:
18
+ ok: true
19
+ base: http://10.0.0.238:9683
20
+ tui_status:
21
+ ok: true
22
+ enabled: true
23
+ active_count: 0
24
+ latest_event_id: 0
25
+ capture_scope: routed runtime traffic
26
+ excluded: hidden thinking, tool arguments, native traffic that bypasses this router, terminal pixels
27
+ tui_recent: '{"ok":true,"events":[],"last_id":0,"active":[]}'
28
+ events_recent: '{"ok":true,"events":[]}'
29
+ source_audit:
30
+ router_http: Authenticated GET dispatches TUI/events/config/web/chat/runtime APIs; no raw router.log download handler found.
31
+ tui: Observation API explicitly excludes terminal pixels and native bypass traffic.
32
+ conclusion:
33
+ confirmed: Remote 0.2.40 is running and reachable on LAN with authentication; one active client recorded.
34
+ not_confirmed: Clipboard delimiter corruption location, actual ConPTY/fallback selection, mouse state, live terminal input bytes.
35
+ blocker: Existing backend does not expose the raw log and console input/output evidence required for this native-terminal issue.
36
+ remote_changes: None; no input injection, model request, log-level change, or session restart.
@@ -0,0 +1,26 @@
1
+ title: Codex LAN client authentication and Windows paste framing
2
+ date: 2026-09-07
3
+ version: 0.2.41
4
+ authorization: User requested handling paste corruption and internal Codex router authentication failure; continuing requested main release workflow.
5
+ causes:
6
+ auth: Previous patch authenticated health only; native model Authorization cannot double as Ciel administrative token.
7
+ paste_reproduction: Real Windows Codex 0.153.4 rendered [200~1660377[201~ when ESC and remaining delimiter bytes were written 20ms apart.
8
+ changes:
9
+ auth: Model and builtin Ciel MCP receive x-ciel-runtime-token from process environment, loaded from this instance token repository; no user TOML edits.
10
+ scope: Exact router origin only; later URL overrides excluded; native OpenAI Authorization retained.
11
+ upstream: Existing x-ciel-runtime header exclusion verified; router credential not forwarded to OpenAI.
12
+ builtin: Reserved openai provider uses a per-launch custom provider retaining requires_openai_auth, verified with actual Codex config loader.
13
+ paste: Frame CSI/SS3 boundaries across parent reads without stripping or rewriting payload; single Escape flushed after 100ms idle.
14
+ verification:
15
+ actual_codex_paste: Replayed split input through corrected pump; 1660377 visible, neither delimiter visible; no prompt submitted.
16
+ actual_codex_http: Local test server received Ciel token on MCP and model requests; native Authorization also present on model requests. Server intentionally returns 400 without inference or forwarding.
17
+ auth_tests: 5 passed, including real CLI and overridden destination protection.
18
+ conpty: 33 passed.
19
+ framing: 3 passed, covering every split, Korean, literal marker text, standalone Escape, arrows and F9.
20
+ runtime_group: 285 passed with 12 skips before additional URL-override test.
21
+ router_debug: 25 passed.
22
+ static: Ruff and architecture budget passed.
23
+ limitations:
24
+ remote: Original mouse gesture on remote machine not directly replayed; reproduced matching corruption with fragmented native Codex input locally.
25
+ idle: Incomplete sequences exceeding 100ms idle are flushed unchanged to preserve standalone Escape.
26
+ deployment: Main workflow and published package verification follow commit.
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneciel-ai/ciel-runtime",
3
- "version": "0.2.40",
3
+ "version": "0.2.42",
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",