@oneciel-ai/ciel-runtime 0.2.26 → 0.2.27
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 +8 -0
- package/ciel_runtime.py +2 -2
- package/ciel_runtime_support/codex_config.py +93 -0
- package/ciel_runtime_support/prelaunch.py +10 -2
- package/ciel_runtime_support/remote_instructions.py +3 -0
- package/ciel_runtime_support/runtime_constants.py +1 -1
- package/ciel_runtime_support/zai_oauth.py +9 -0
- package/docs/journal/2026/08/24/kevin-codex-config-mia-zai-oauth-recovery.okf +69 -0
- package/docs/journal/2026/08/24/zcode-runtime-oauth-windows-paste.okf +18 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
This file records stable Ciel Runtime releases. Changes are grouped by user-visible
|
|
4
4
|
capability, followed by the complete commit ledger merged into each release.
|
|
5
5
|
|
|
6
|
+
## 0.2.27 — 2026-08-24
|
|
7
|
+
|
|
8
|
+
- Repair Codex startup only when an invalid TOML file contains equivalent MCP
|
|
9
|
+
`http_headers` in both a legacy child table and a managed inline table;
|
|
10
|
+
preserve a timestamped backup before the atomic rewrite.
|
|
11
|
+
- Reject a pasted Z.AI authorization-page URL with explicit callback guidance
|
|
12
|
+
and keep the prelaunch API-key panel alive when OAuth validation fails.
|
|
13
|
+
|
|
6
14
|
## 0.2.26 — 2026-08-24
|
|
7
15
|
|
|
8
16
|
- Added ZCode as a first-class launch client in the interactive menu,
|
package/ciel_runtime.py
CHANGED
|
@@ -121,7 +121,7 @@ from ciel_runtime_support.codex_backend_context import CodexBackendChannelPorts,
|
|
|
121
121
|
from ciel_runtime_support.codex_reasoning_rejects import RejectedReasoningStore
|
|
122
122
|
from ciel_runtime_support.codex_cli import codex_passthrough_args_for_launch, codex_passthrough_has_command, codex_resume_picker_requested, codex_resume_with_session_id
|
|
123
123
|
from ciel_runtime_support.codex_config import codex_alternate_screen_value_from_config_text # noqa: F401
|
|
124
|
-
from ciel_runtime_support.codex_config import codex_config_paths_for_launch # noqa: F401 - compatibility export
|
|
124
|
+
from ciel_runtime_support.codex_config import codex_config_paths_for_launch, repair_codex_mcp_header_collisions # noqa: F401 - compatibility export
|
|
125
125
|
from ciel_runtime_support.codex_config import codex_config_override_keys as _codex_config_override_keys # noqa: F401
|
|
126
126
|
from ciel_runtime_support.codex_config import toml_scalar_without_comment as _toml_scalar_without_comment # noqa: F401
|
|
127
127
|
from ciel_runtime_support.codex_config import toml_string
|
|
@@ -4890,7 +4890,7 @@ def runtime_launch_context() -> RuntimeLaunchContext:
|
|
|
4890
4890
|
|
|
4891
4891
|
_RUNTIME_LAUNCH_API = RuntimeLaunchCompatibilityApi(runtime_launch_context)
|
|
4892
4892
|
launch_claude = SynchronizedLaunch(_RUNTIME_LAUNCH_API.launch_claude, sync_remote_launch_assets, "claude")
|
|
4893
|
-
launch_codex = SynchronizedLaunch(_RUNTIME_LAUNCH_API.launch_codex, sync_remote_launch_assets, "codex")
|
|
4893
|
+
launch_codex = SynchronizedLaunch(_RUNTIME_LAUNCH_API.launch_codex, sync_remote_launch_assets, "codex", lambda passthrough=None, **_kwargs: repair_codex_mcp_header_collisions(codex_config_paths_for_launch(list(passthrough or [])), report=lambda message: router_log("WARN", message)))
|
|
4894
4894
|
launch_codex_app_server = SynchronizedLaunch(_RUNTIME_LAUNCH_API.launch_codex_app_server, sync_remote_launch_assets, "codex-app-server")
|
|
4895
4895
|
launch_agy = SynchronizedLaunch(_RUNTIME_LAUNCH_API.launch_agy, sync_remote_launch_assets, "agy")
|
|
4896
4896
|
launch_grok = SynchronizedLaunch(launch_grok, sync_remote_launch_assets, "grok")
|
|
@@ -5,7 +5,16 @@ from __future__ import annotations
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
import re
|
|
8
|
+
import shutil
|
|
9
|
+
import stat
|
|
10
|
+
import time
|
|
8
11
|
from pathlib import Path
|
|
12
|
+
from typing import Any, Callable, Iterable
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import tomllib
|
|
16
|
+
except ModuleNotFoundError: # Python 3.10 remains a supported launcher runtime.
|
|
17
|
+
tomllib = None # type: ignore[assignment]
|
|
9
18
|
|
|
10
19
|
|
|
11
20
|
def toml_string(value: str) -> str:
|
|
@@ -124,3 +133,87 @@ def codex_config_paths_for_launch(
|
|
|
124
133
|
if path not in paths:
|
|
125
134
|
paths.append(path)
|
|
126
135
|
return paths
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
_MCP_PARENT_TABLE = re.compile(
|
|
139
|
+
r'^\s*\[mcp_servers\.(?P<name>"(?:[^"\\]|\\.)+"|[A-Za-z0-9_-]+)\]\s*(?:#.*)?$'
|
|
140
|
+
)
|
|
141
|
+
_MCP_HEADER_TABLE = re.compile(
|
|
142
|
+
r'^\s*\[mcp_servers\.(?P<name>"(?:[^"\\]|\\.)+"|[A-Za-z0-9_-]+)\.http_headers\]\s*(?:#.*)?$'
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _toml_table_blocks(lines: list[str], pattern: re.Pattern[str]) -> list[tuple[int, int, str, dict[str, Any]]]:
|
|
147
|
+
blocks: list[tuple[int, int, str, dict[str, Any]]] = []
|
|
148
|
+
if tomllib is None:
|
|
149
|
+
return blocks
|
|
150
|
+
for start, line in enumerate(lines):
|
|
151
|
+
if pattern.fullmatch(line.rstrip("\r\n")) is None:
|
|
152
|
+
continue
|
|
153
|
+
end = next((index for index in range(start + 1, len(lines)) if lines[index].lstrip().startswith("[")), len(lines))
|
|
154
|
+
try:
|
|
155
|
+
parsed = tomllib.loads("".join(lines[start:end]))
|
|
156
|
+
servers = parsed.get("mcp_servers", {})
|
|
157
|
+
name, server = next(iter(servers.items()))
|
|
158
|
+
except (ValueError, TypeError, AttributeError, StopIteration):
|
|
159
|
+
continue
|
|
160
|
+
if isinstance(server, dict):
|
|
161
|
+
blocks.append((start, end, str(name), server))
|
|
162
|
+
return blocks
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def repair_codex_mcp_header_collisions(
|
|
166
|
+
paths: Iterable[Path],
|
|
167
|
+
*,
|
|
168
|
+
report: Callable[[str], None] | None = None,
|
|
169
|
+
) -> list[Path]:
|
|
170
|
+
"""Repair only identical legacy-table/managed-inline MCP header collisions."""
|
|
171
|
+
|
|
172
|
+
repaired: list[Path] = []
|
|
173
|
+
if tomllib is None:
|
|
174
|
+
return repaired
|
|
175
|
+
for path in paths:
|
|
176
|
+
if not path.is_file() or path.is_symlink():
|
|
177
|
+
continue
|
|
178
|
+
try:
|
|
179
|
+
text = path.read_text(encoding="utf-8")
|
|
180
|
+
tomllib.loads(text)
|
|
181
|
+
continue
|
|
182
|
+
except (OSError, UnicodeError):
|
|
183
|
+
continue
|
|
184
|
+
except tomllib.TOMLDecodeError:
|
|
185
|
+
pass
|
|
186
|
+
lines = text.splitlines(keepends=True)
|
|
187
|
+
parents = {
|
|
188
|
+
name: server.get("http_headers")
|
|
189
|
+
for _start, _end, name, server in _toml_table_blocks(lines, _MCP_PARENT_TABLE)
|
|
190
|
+
if isinstance(server.get("http_headers"), dict)
|
|
191
|
+
}
|
|
192
|
+
removals: list[tuple[int, int, str]] = []
|
|
193
|
+
for start, end, name, server in _toml_table_blocks(lines, _MCP_HEADER_TABLE):
|
|
194
|
+
old_headers = server.get("http_headers")
|
|
195
|
+
if isinstance(old_headers, dict) and old_headers == parents.get(name):
|
|
196
|
+
removals.append((start, end, name))
|
|
197
|
+
if not removals:
|
|
198
|
+
continue
|
|
199
|
+
removed_lines = {index for start, end, _name in removals for index in range(start, end)}
|
|
200
|
+
candidate = "".join(line for index, line in enumerate(lines) if index not in removed_lines)
|
|
201
|
+
try:
|
|
202
|
+
tomllib.loads(candidate)
|
|
203
|
+
except tomllib.TOMLDecodeError:
|
|
204
|
+
continue
|
|
205
|
+
backup = path.with_name(f"{path.name}.ciel-mcp-repair-{time.time_ns()}.bak")
|
|
206
|
+
temporary = path.with_name(f".{path.name}.ciel-mcp-repair-{os.getpid()}.tmp")
|
|
207
|
+
try:
|
|
208
|
+
shutil.copy2(path, backup)
|
|
209
|
+
temporary.write_text(candidate, encoding="utf-8")
|
|
210
|
+
os.chmod(temporary, stat.S_IMODE(path.stat().st_mode))
|
|
211
|
+
os.replace(temporary, path)
|
|
212
|
+
except OSError:
|
|
213
|
+
temporary.unlink(missing_ok=True)
|
|
214
|
+
continue
|
|
215
|
+
repaired.append(path)
|
|
216
|
+
if report is not None:
|
|
217
|
+
names = ", ".join(sorted({name for _start, _end, name in removals}))
|
|
218
|
+
report(f"Repaired duplicate Codex MCP headers in {path} ({names}); backup: {backup}")
|
|
219
|
+
return repaired
|
|
@@ -180,6 +180,13 @@ class PrelaunchServices:
|
|
|
180
180
|
options: PrelaunchOptions
|
|
181
181
|
|
|
182
182
|
|
|
183
|
+
def guarded_zai_oauth_action(action: str, operation: Callable[[str], list[str]]) -> list[str]:
|
|
184
|
+
try:
|
|
185
|
+
return operation(action)
|
|
186
|
+
except RuntimeError as exc:
|
|
187
|
+
return [f"Z.AI OAuth failed: {exc}"]
|
|
188
|
+
|
|
189
|
+
|
|
183
190
|
def run_prelaunch_menu(passthrough: list[str] | None = None,
|
|
184
191
|
*,
|
|
185
192
|
services: PrelaunchServices,
|
|
@@ -570,8 +577,8 @@ def run_prelaunch_menu(passthrough: list[str] | None = None,
|
|
|
570
577
|
panel_rows, panel_values = api_key_panel_rows(provider, pcfg)
|
|
571
578
|
panel_idx = 0
|
|
572
579
|
elif value.startswith("zai-oauth-"):
|
|
573
|
-
messages =
|
|
574
|
-
value.removeprefix("zai-oauth-")
|
|
580
|
+
messages = guarded_zai_oauth_action(
|
|
581
|
+
value.removeprefix("zai-oauth-"), zai_oauth_action
|
|
575
582
|
)
|
|
576
583
|
refresh_checks()
|
|
577
584
|
cfg = load_config()
|
|
@@ -1082,5 +1089,6 @@ __all__ = [
|
|
|
1082
1089
|
"PrelaunchSecrets",
|
|
1083
1090
|
"PrelaunchServices",
|
|
1084
1091
|
"PrelaunchTerminal",
|
|
1092
|
+
"guarded_zai_oauth_action",
|
|
1085
1093
|
"run_prelaunch_menu",
|
|
1086
1094
|
]
|
|
@@ -206,8 +206,11 @@ class SynchronizedLaunch:
|
|
|
206
206
|
delegate: Callable[..., Any]
|
|
207
207
|
synchronize: Callable[..., Any]
|
|
208
208
|
runtime: str
|
|
209
|
+
prepare: Callable[..., Any] | None = None
|
|
209
210
|
|
|
210
211
|
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
212
|
+
if self.prepare is not None:
|
|
213
|
+
self.prepare(*args, **kwargs)
|
|
211
214
|
self.synchronize(self.runtime, reason="launch")
|
|
212
215
|
return self.delegate(*args, **kwargs)
|
|
213
216
|
|
|
@@ -147,6 +147,15 @@ class ZaiOAuthClient:
|
|
|
147
147
|
raise ZaiOAuthError("Z.AI returned an invalid OAuth callback URL.") from exc
|
|
148
148
|
path = f"/{parsed.path.strip('/')}"
|
|
149
149
|
if parsed.scheme != "zcode" or parsed.netloc != "zai-auth" or path != "/callback":
|
|
150
|
+
if (
|
|
151
|
+
parsed.scheme == "https"
|
|
152
|
+
and parsed.hostname == "chat.z.ai"
|
|
153
|
+
and path.endswith("/oauth/authorize")
|
|
154
|
+
):
|
|
155
|
+
raise ZaiOAuthError(
|
|
156
|
+
"The pasted URL is the authorization page, not the completed callback. "
|
|
157
|
+
"Finish authorization and paste the complete zcode://zai-auth/callback URL."
|
|
158
|
+
)
|
|
150
159
|
raise ZaiOAuthError("Z.AI returned an unexpected OAuth callback target.")
|
|
151
160
|
query = urllib.parse.parse_qs(parsed.query)
|
|
152
161
|
state = str((query.get("state") or [""])[0])
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
okf_version: "1.0"
|
|
2
|
+
knowledge:
|
|
3
|
+
task:
|
|
4
|
+
id: kevin-codex-config-mia-zai-oauth-recovery-20260824
|
|
5
|
+
date: "2026-08-24"
|
|
6
|
+
objective: >-
|
|
7
|
+
Restore Kevin's Codex startup and prevent Mia's Z.AI OAuth callback input
|
|
8
|
+
error from terminating the Ciel prelaunch interface.
|
|
9
|
+
status: validation-in-progress
|
|
10
|
+
|
|
11
|
+
evidence:
|
|
12
|
+
kevin:
|
|
13
|
+
observed_error: /home/kevin-codex/.codex/config.toml:63:1 duplicate key
|
|
14
|
+
conflicting_forms:
|
|
15
|
+
- table: mcp_servers.ai-net.http_headers
|
|
16
|
+
lines_before_repair: 36-38
|
|
17
|
+
- inline_key: mcp_servers.ai-net.http_headers
|
|
18
|
+
line_before_repair: 63
|
|
19
|
+
comparison:
|
|
20
|
+
header_names_equal: true
|
|
21
|
+
header_values_equal: true
|
|
22
|
+
repaired_config:
|
|
23
|
+
toml_valid: true
|
|
24
|
+
codex_mcp_list_exit: 0
|
|
25
|
+
ciel_runtime_codex_version_exit: 0
|
|
26
|
+
backup: /home/kevin-codex/.codex/config.toml.ciel-repair-20260824T0555Z.bak
|
|
27
|
+
attribution: >-
|
|
28
|
+
The config and AI-Net managed artifacts were written in the same second,
|
|
29
|
+
but the writer source was not retained; causal attribution is unconfirmed.
|
|
30
|
+
mia:
|
|
31
|
+
supplied_input_kind: https authorization page
|
|
32
|
+
required_input_kind: zcode callback URL
|
|
33
|
+
source_behavior: >-
|
|
34
|
+
ZaiOAuthClient rejected the target with ZaiOAuthError and the prelaunch
|
|
35
|
+
API-key panel invoked the action without a RuntimeError boundary.
|
|
36
|
+
|
|
37
|
+
implementation:
|
|
38
|
+
codex_preflight:
|
|
39
|
+
scope: all Codex launch paths
|
|
40
|
+
mutation_gate:
|
|
41
|
+
- original TOML must be invalid
|
|
42
|
+
- legacy child-table and managed inline header maps must be identical
|
|
43
|
+
- candidate TOML must parse successfully
|
|
44
|
+
recovery: timestamped backup followed by atomic replacement
|
|
45
|
+
non_matching_invalid_files: unchanged
|
|
46
|
+
zai_oauth:
|
|
47
|
+
authorize_page: rejected with completed-callback guidance
|
|
48
|
+
prelaunch_error: rendered in panel without unwinding the menu
|
|
49
|
+
|
|
50
|
+
verification:
|
|
51
|
+
completed:
|
|
52
|
+
- Kevin remote TOML candidate parse
|
|
53
|
+
- Kevin remote config backup and minimal repair
|
|
54
|
+
- Kevin codex mcp list exit 0
|
|
55
|
+
- Kevin Ciel-routed Codex version exit 0
|
|
56
|
+
- focused Codex repair tests
|
|
57
|
+
- focused Z.AI OAuth tests
|
|
58
|
+
- focused synchronized-launch tests
|
|
59
|
+
- Python compilation
|
|
60
|
+
- focused Ruff validation
|
|
61
|
+
- full regression suite: 2744 passed and 136 skipped
|
|
62
|
+
- documentation metadata: 23 Markdown files passed
|
|
63
|
+
- full Ruff validation
|
|
64
|
+
- npm package dry run for version 0.2.27
|
|
65
|
+
- local install reports ciel-runtime 0.2.27
|
|
66
|
+
- local Ciel-routed Codex version exit 0
|
|
67
|
+
pending:
|
|
68
|
+
- main and nightly publication
|
|
69
|
+
- Kevin and Mia nightly installation verification
|
|
@@ -6,7 +6,7 @@ knowledge:
|
|
|
6
6
|
objective: >-
|
|
7
7
|
Register ZCode as a supported Ciel runtime, share Z.AI OAuth credentials
|
|
8
8
|
across runtime clients, and repair leaked Windows bracketed-paste markers.
|
|
9
|
-
status:
|
|
9
|
+
status: complete
|
|
10
10
|
|
|
11
11
|
evidence:
|
|
12
12
|
source_code:
|
|
@@ -64,5 +64,20 @@ knowledge:
|
|
|
64
64
|
- local install to C:/Users/djlov/.local/share/ciel-runtime
|
|
65
65
|
- local launcher reports ciel-runtime 0.2.26
|
|
66
66
|
- local ZCode launch reports zcode-runtime 0.16.3
|
|
67
|
-
pending:
|
|
68
|
-
|
|
67
|
+
pending: []
|
|
68
|
+
|
|
69
|
+
deployment:
|
|
70
|
+
implementation_commit: 5e461f468ea78f82ac8b1ce531abec4401ee5a51
|
|
71
|
+
branches: [main, nightly]
|
|
72
|
+
local_version: 0.2.26
|
|
73
|
+
npm_latest: 0.2.26
|
|
74
|
+
npm_nightly: 0.2.26-nightly.20260824-051932.5e461f4
|
|
75
|
+
workflow_results:
|
|
76
|
+
main_ci: success
|
|
77
|
+
main_publish: success
|
|
78
|
+
nightly_ci: success
|
|
79
|
+
nightly_publish_and_tarball_verification: success
|
|
80
|
+
registry_execution:
|
|
81
|
+
ciel_runtime: 0.2.26
|
|
82
|
+
zcode_app_cli: 3.8.1-15
|
|
83
|
+
zcode_runtime: 0.16.3
|
package/package.json
CHANGED