@oneciel-ai/ciel-runtime 0.2.38 → 0.2.40
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 +13 -0
- package/ciel_runtime.py +1 -0
- package/ciel_runtime_support/managed_tool_injection.py +53 -5
- package/ciel_runtime_support/router_access.py +13 -0
- package/ciel_runtime_support/runtime_constants.py +1 -1
- package/ciel_runtime_support/runtime_launch.py +10 -2
- package/docs/journal/2026/09/07/diagnostics/windows/clipboard-paste-markers.okf +18 -0
- package/docs/journal/2026/09/07/diagnostics/windows/web-backend-startup.okf +12 -0
- package/docs/journal/2026/09/07/fixes/codex/native-web-transport.okf +39 -0
- package/docs/journal/2026/09/07/release/stable/0.2.39.okf +10 -0
- package/docs/journal/2026/09/07/release/stable/0.2.40.okf +19 -0
- package/package.json +1 -1
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.40 — 2026-09-07
|
|
9
|
+
|
|
10
|
+
- Authenticate internal router health checks when the web backend uses a
|
|
11
|
+
specific LAN address. Preserve the external listener and authentication
|
|
12
|
+
requirements instead of reporting an authenticated running server as down.
|
|
13
|
+
|
|
14
|
+
## 0.2.39 — 2026-09-07
|
|
15
|
+
|
|
16
|
+
- Fix native Codex startup failing with `invalid transport` when DuckDuckGo or
|
|
17
|
+
web_fetch is not registered. Inspect the effective workspace configuration
|
|
18
|
+
before adding process-only disabling overrides; never modify global or
|
|
19
|
+
workspace TOML. Concurrent non-native sessions retain their MCP settings.
|
|
20
|
+
|
|
8
21
|
## 0.2.38 — 2026-09-07
|
|
9
22
|
|
|
10
23
|
- Disable inherited DuckDuckGo and web_fetch MCP servers with per-launch
|
package/ciel_runtime.py
CHANGED
|
@@ -3024,6 +3024,7 @@ def router_health() -> dict[str, Any] | None:
|
|
|
3024
3024
|
try:
|
|
3025
3025
|
data = http_json(
|
|
3026
3026
|
f"{ROUTER_BASE}/health",
|
|
3027
|
+
headers=_ROUTER_ACCESS_POLICY.health_headers(ROUTER_BASE, load_config(), router_external_access_token),
|
|
3027
3028
|
timeout=router_health_timeout_seconds(),
|
|
3028
3029
|
)
|
|
3029
3030
|
return data if isinstance(data, dict) else None
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
"""Launch-mode policy for Ciel-owned tools, never user MCP definitions."""
|
|
2
2
|
from collections.abc import Mapping
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import subprocess
|
|
7
|
+
from subprocess import run as run_codex_config_probe
|
|
3
8
|
from typing import Any
|
|
4
9
|
|
|
5
10
|
|
|
@@ -9,14 +14,57 @@ def should_inject_tool(*, native: bool, mode: str = "always") -> bool:
|
|
|
9
14
|
return mode == "always" or (mode == "native") == native
|
|
10
15
|
|
|
11
16
|
|
|
12
|
-
def codex_native_web_tool_overrides(
|
|
17
|
+
def codex_native_web_tool_overrides(
|
|
18
|
+
*, native: bool, passthrough: list[str] | None = None,
|
|
19
|
+
env: dict[str, str] | None = None, cwd: Path | None = None,
|
|
20
|
+
codex: str = "codex",
|
|
21
|
+
) -> list[str]:
|
|
13
22
|
"""Disable inherited replacement web MCPs for this launch, not on disk."""
|
|
14
23
|
if not native:
|
|
15
24
|
return []
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
# An enabled-only table is NOT a valid Codex MCP transport, even when
|
|
26
|
+
# disabled. Never manufacture such a table on a fresh installation.
|
|
27
|
+
# Ask the same executable to resolve its effective configuration. Merely
|
|
28
|
+
# scanning project TOML is unsafe: Codex can ignore untrusted projects,
|
|
29
|
+
# which would turn our enabled override into another transport-less table.
|
|
30
|
+
# `mcp list` reads configuration; it does not connect to MCP servers.
|
|
31
|
+
arguments = passthrough or []
|
|
32
|
+
configuration_args = []
|
|
33
|
+
index = 0
|
|
34
|
+
while index < len(arguments):
|
|
35
|
+
argument = arguments[index]
|
|
36
|
+
if argument == "--":
|
|
37
|
+
break
|
|
38
|
+
if argument in ("-c", "--config", "-p", "--profile", "-C", "--cd") and index + 1 < len(arguments):
|
|
39
|
+
configuration_args.extend([argument, arguments[index + 1]])
|
|
40
|
+
index += 1
|
|
41
|
+
elif argument.startswith(("--config=", "--profile=", "--cd=")):
|
|
42
|
+
configuration_args.append(argument)
|
|
43
|
+
index += 1
|
|
44
|
+
try:
|
|
45
|
+
completed = run_codex_config_probe(
|
|
46
|
+
[codex, *configuration_args, "mcp", "list", "--json"],
|
|
47
|
+
env=env, cwd=cwd, capture_output=True, text=True, encoding="utf-8",
|
|
48
|
+
errors="replace", timeout=5, check=False,
|
|
49
|
+
)
|
|
50
|
+
if completed.returncode != 0:
|
|
51
|
+
raise ValueError("configuration probe failed")
|
|
52
|
+
entries = json.loads(completed.stdout)
|
|
53
|
+
if not isinstance(entries, list):
|
|
54
|
+
raise ValueError("unexpected MCP list shape")
|
|
55
|
+
except (OSError, subprocess.TimeoutExpired, ValueError):
|
|
56
|
+
# Never print probe output: MCP configuration can contain credentials.
|
|
57
|
+
logging.getLogger(__name__).warning(
|
|
58
|
+
"Could not inspect Codex MCP configuration; skipping native web MCP overrides"
|
|
59
|
+
)
|
|
60
|
+
return []
|
|
61
|
+
names = {entry.get("name") for entry in entries if isinstance(entry, dict)
|
|
62
|
+
and isinstance(entry.get("name"), str)}
|
|
63
|
+
result = []
|
|
64
|
+
for name in ("duckduckgo", "web_fetch"):
|
|
65
|
+
if name in names:
|
|
66
|
+
result.extend(["-c", f"mcp_servers.{name}.enabled=false"])
|
|
67
|
+
return result
|
|
20
68
|
|
|
21
69
|
|
|
22
70
|
def select_managed_tools(
|
|
@@ -7,6 +7,7 @@ import json
|
|
|
7
7
|
import os
|
|
8
8
|
import secrets
|
|
9
9
|
import time
|
|
10
|
+
import urllib.parse
|
|
10
11
|
from collections.abc import Callable, Mapping, MutableMapping
|
|
11
12
|
from dataclasses import dataclass
|
|
12
13
|
from pathlib import Path
|
|
@@ -63,6 +64,18 @@ class RouterAccessPolicy:
|
|
|
63
64
|
parse_env_bool: Callable[[str | None, bool | None], bool | None]
|
|
64
65
|
load_config: Callable[[], dict[str, Any]]
|
|
65
66
|
|
|
67
|
+
def health_headers(
|
|
68
|
+
self, base: str, config: dict[str, Any], token_provider: Callable[[], str],
|
|
69
|
+
) -> dict[str, str]:
|
|
70
|
+
# Specific LAN binds are also used as the internal client address;
|
|
71
|
+
# those requests must satisfy external authentication just like peers.
|
|
72
|
+
if is_loopback_address(urllib.parse.urlparse(base).hostname):
|
|
73
|
+
return {}
|
|
74
|
+
if not self.administrative_external_access_enabled(config):
|
|
75
|
+
return {}
|
|
76
|
+
token = token_provider()
|
|
77
|
+
return {"Authorization": f"Bearer {token}"} if token else {}
|
|
78
|
+
|
|
66
79
|
def remote_bridge_enabled(self, config: Mapping[str, Any]) -> bool:
|
|
67
80
|
override = str(
|
|
68
81
|
self.environ.get("CIEL_RUNTIME_REMOTE_BRIDGE") or ""
|
|
@@ -927,7 +927,11 @@ def run_codex(
|
|
|
927
927
|
)
|
|
928
928
|
if workspace_mcp_launch is not None:
|
|
929
929
|
codex_mcp_compat_args.extend(workspace_mcp_launch.codex_args)
|
|
930
|
-
codex_mcp_compat_args.extend(codex_native_web_tool_overrides(
|
|
930
|
+
codex_mcp_compat_args.extend(codex_native_web_tool_overrides(
|
|
931
|
+
native=native_codex_enabled(provider),
|
|
932
|
+
codex=codex,
|
|
933
|
+
passthrough=[*codex_passthrough, *codex_mcp_compat_args], env=env, cwd=launch_cwd,
|
|
934
|
+
))
|
|
931
935
|
codex_yolo_args = codex_yolo_launch_args(codex_passthrough)
|
|
932
936
|
if not use_native_codex and not use_codex_routed:
|
|
933
937
|
env[CODEX_RUNTIME_API_KEY_ENV] = env.get(CODEX_RUNTIME_API_KEY_ENV) or "ciel-runtime-router-local-key"
|
|
@@ -1260,7 +1264,11 @@ def run_codex_app_server(
|
|
|
1260
1264
|
try:
|
|
1261
1265
|
if workspace_mcp_launch is not None:
|
|
1262
1266
|
codex_mcp_compat_args.extend(workspace_mcp_launch.codex_args)
|
|
1263
|
-
codex_mcp_compat_args.extend(codex_native_web_tool_overrides(
|
|
1267
|
+
codex_mcp_compat_args.extend(codex_native_web_tool_overrides(
|
|
1268
|
+
native=native_codex_enabled(provider),
|
|
1269
|
+
codex=codex,
|
|
1270
|
+
passthrough=[*passthrough, *config_args, *codex_mcp_compat_args], env=env, cwd=launch_cwd,
|
|
1271
|
+
))
|
|
1264
1272
|
config_args = [*config_args, *codex_mcp_compat_args]
|
|
1265
1273
|
listen_url = codex_app_server_default_listen_url()
|
|
1266
1274
|
app_server_args = codex_app_server_launch_args(
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
title: Remote Windows clipboard paste exposes delimiter-like text
|
|
2
|
+
date: 2026-09-07
|
|
3
|
+
status: investigated; remote cause not established
|
|
4
|
+
observation: User screenshot shows [20~ before a pasted URL and [201~ after it.
|
|
5
|
+
interpretation: Consistent with exposed bracketed-paste delimiters; not evidence of mouse reporting.
|
|
6
|
+
reference: https://github.com/microsoft/terminal/issues/12385
|
|
7
|
+
protocol: Bracketed paste wraps text in ESC[200~ and ESC[201~.
|
|
8
|
+
source_checks:
|
|
9
|
+
parent_input: windows_conpty._read_input_bytes uses ReadConsoleW then UTF-8 encoding.
|
|
10
|
+
pump: _pump_input observes draft state then writes input bytes without textual marker replacement.
|
|
11
|
+
transport: write loops until all bytes have been written to ConPTY.
|
|
12
|
+
verification:
|
|
13
|
+
command: python -m unittest discover -s tests -p test_windows_conpty.py -v
|
|
14
|
+
result: 33 tests passed in 0.361s on local Windows.
|
|
15
|
+
coverage: Includes real ConPTY bracketed-paste/large Korean payload byte integrity and submission tests.
|
|
16
|
+
limitation: Does not reproduce the remote user's clipboard, outer terminal, or Codex TUI input path.
|
|
17
|
+
next_evidence: Remote machine access or exact terminal/runtime versions and relevant transport startup logs.
|
|
18
|
+
changes: No runtime code changes or deployment; do not strip literal marker-like text from user messages.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
title: Router startup timeout after remote Windows web backend configuration
|
|
2
|
+
date: 2026-09-07
|
|
3
|
+
status: conditional configuration bug reproduced; remote root cause unconfirmed
|
|
4
|
+
evidence:
|
|
5
|
+
traceback: router_process_lifecycle.start_router_if_needed exhausted 30-second router_up polling.
|
|
6
|
+
remote_initial: http://10.0.0.238:9683/health returned 401 external authentication required.
|
|
7
|
+
remote_later: Connection timed out after 3015 milliseconds.
|
|
8
|
+
source: WebBackendSettings.client_host preserves specific LAN addresses; router_health sends no authentication; RouterAccessPolicy permits unauthenticated loopback only.
|
|
9
|
+
local_policy_reproduction: client_host=10.0.0.238; LAN health without token allowed=False; loopback health allowed=True.
|
|
10
|
+
limitations: No remote router.log or configured bind host available; startup process crash and bind errors remain possible.
|
|
11
|
+
changes: No runtime configuration or code modified.
|
|
12
|
+
required_evidence: Remote router.log or authenticated backend access after service recovery.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
title: Codex native web MCP overrides without cross-session configuration writes
|
|
2
|
+
date: 2026-09-07
|
|
3
|
+
status: source fixed and locally verified; not installed or published
|
|
4
|
+
request:
|
|
5
|
+
symptom: Codex 0.153.4 fails with invalid transport in mcp_servers.duckduckgo.
|
|
6
|
+
isolation: Do not change global TOML; isolate workspace/provider sessions.
|
|
7
|
+
evidence:
|
|
8
|
+
reproduction:
|
|
9
|
+
environment: Real Windows codex-cli 0.153.4 with temporary empty CODEX_HOME.
|
|
10
|
+
arguments: -c mcp_servers.duckduckgo.enabled=false mcp list --json
|
|
11
|
+
exit_code: 1
|
|
12
|
+
stderr: "Error: failed to load bootstrap configuration; invalid transport in mcp_servers.duckduckgo"
|
|
13
|
+
cause: The unconditional native override creates an enabled-only server table without command or URL when the server is absent.
|
|
14
|
+
official_documentation: https://learn.chatgpt.com/docs/extend/mcp?surface=cli
|
|
15
|
+
documented_constraints: STDIO requires command; HTTP requires url; project configuration applies only to trusted projects.
|
|
16
|
+
implementation:
|
|
17
|
+
discovery: Run the same Codex executable's mcp list --json with the launch environment, cwd, and configuration arguments.
|
|
18
|
+
isolation: Apply enabled=false only in this process's arguments, only for servers in the effective list; no TOML writes.
|
|
19
|
+
workspace: Codex resolves workspace trust/configuration itself; no independent TOML parser or global edits.
|
|
20
|
+
native: Disable existing duckduckgo and web_fetch entries for CLI and app-server launches.
|
|
21
|
+
nonnative: No discovery process and no disabling overrides.
|
|
22
|
+
failure_policy: Five-second bounded probe; if unavailable/invalid, log a credential-free warning and skip overrides.
|
|
23
|
+
limitation: If discovery fails, replacement MCPs may remain enabled; this must not be described as successful suppression.
|
|
24
|
+
verification:
|
|
25
|
+
real_cli_command: "$env:CIEL_TEST_CODEX_EXE='C:/Users/djlov/AppData/Local/Programs/OpenAI/Codex/bin/codex.exe'; python -m unittest discover -s tests -p test_codex_native_web_cli.py -v"
|
|
26
|
+
real_cli_output: "Ran 4 tests in 0.667s; OK"
|
|
27
|
+
real_cli_cases:
|
|
28
|
+
empty_home: No overrides; mcp list exits 0 instead of invalid transport.
|
|
29
|
+
concurrent_shared_home_different_workspaces: native false/false; nonnative true/true for duckduckgo/web_fetch.
|
|
30
|
+
concurrent_shared_home_same_workspace: native false/false; nonnative true/true for duckduckgo/web_fetch.
|
|
31
|
+
file_integrity: Shared test config SHA256 unchanged; no workspace config created by override logic.
|
|
32
|
+
project_configuration: Trusted and untrusted project cases match Codex's effective server list; project TOML bytes unchanged.
|
|
33
|
+
generated_transport: Native disabled; nonnative enabled; no MCP connection needed.
|
|
34
|
+
policy_tests: "python -m unittest discover -s tests -p test_managed_tool_injection.py -v: Ran 9 tests; OK"
|
|
35
|
+
runtime_group: "python scripts/run_test_group.py runtime: Ran 280 tests; OK (skipped=12), real CLI checks enabled"
|
|
36
|
+
static_checks: py_compile successful; ruff All checks passed; git diff --check clean.
|
|
37
|
+
boundaries:
|
|
38
|
+
unchanged: Real user global/project TOML, active sessions, installation, npm registry, git remotes.
|
|
39
|
+
tested: Configuration loading and launch argument policy, not a paid model inference or remote user's TUI.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
title: Publish native Codex transport regression fix as stable 0.2.39
|
|
2
|
+
date: 2026-09-07
|
|
3
|
+
authorization: User requested continuing through main branch deployment.
|
|
4
|
+
version: 0.2.39
|
|
5
|
+
scope: Commit verified fix and version increment, push main, verify CI and npm latest package.
|
|
6
|
+
verification_plan:
|
|
7
|
+
local: Lint, isolated runtime tests, real Codex config-loader regression checks.
|
|
8
|
+
remote: Publish workflow runs all test groups before publishing.
|
|
9
|
+
artifact: Download published package and verify runtime version and fixed helper behavior.
|
|
10
|
+
exclusions: No user configuration edits or active session restart.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
title: Authenticate LAN-bound managed router health checks
|
|
2
|
+
date: 2026-09-07
|
|
3
|
+
version: 0.2.40
|
|
4
|
+
authorization: User explicitly requested fix and new-version main deployment.
|
|
5
|
+
evidence:
|
|
6
|
+
remote_log: Listener and client base both use 10.0.0.238:9683.
|
|
7
|
+
observed_http: Earlier external health request returned 401.
|
|
8
|
+
source: router_health omitted authentication; non-loopback requests require authentication.
|
|
9
|
+
change:
|
|
10
|
+
health: Read existing administrative token for non-loopback health checks when administrative external access is enabled.
|
|
11
|
+
preserved: Bind address, port, global/workspace configuration, external authentication rules, active sessions.
|
|
12
|
+
secrets: No token generation or token logging in the health probe.
|
|
13
|
+
verification:
|
|
14
|
+
real_http: Isolated HTTP server with production access policy: missing token fails, correct token makes router_up true, wrong token fails.
|
|
15
|
+
limitations: Test socket is loopback with simulated LAN peer policy; remote user's final restart not yet verified.
|
|
16
|
+
targeted: 2 tests passed in 0.513s.
|
|
17
|
+
runtime: 280 tests passed with 16 skips.
|
|
18
|
+
static: ruff and py_compile passed.
|
|
19
|
+
deployment: main push triggers full-test npm latest publishing; registry and downloaded artifact checked after publishing.
|
package/package.json
CHANGED