@oneciel-ai/ciel-runtime 0.2.25 → 0.2.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 CHANGED
@@ -3,6 +3,19 @@
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.26 — 2026-08-24
7
+
8
+ - Added ZCode as a first-class launch client in the interactive menu,
9
+ `ciel-runtime zcode`, `ciel-runtimectl launch-zcode`, and `--ca-runtime zcode`.
10
+ - Routes ZCode through the selected Ciel provider with a workspace-isolated
11
+ ZCode home and custom Anthropic-provider configuration, without overwriting
12
+ the user's normal ZCode configuration.
13
+ - Shares Z.AI OAuth Coding Plan credentials across ZCode, Claude, Codex, AGY,
14
+ and other routed clients; ZCode TUI OAuth results are imported after exit.
15
+ - Added Z.AI OAuth login/status/logout actions to the provider API-key panel.
16
+ - Restores Windows bracketed-paste mode on runtime exit to prevent literal
17
+ `ESC[200~` / `ESC[201~` markers from leaking into the parent prompt.
18
+
6
19
  ## 0.2.25 — 2026-08-23
7
20
 
8
21
  - Added the ZCode wrapper's authorization-code contract as a cross-platform manual
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  **Run the coding agent you want on the model provider you choose.**
6
6
 
7
7
  Ciel Runtime is a cross-platform launcher, local protocol router, and workspace
8
- control plane for Claude Code, Codex, Codex App Server, AGY, Grok Build, and
8
+ control plane for Claude Code, Codex, Codex App Server, AGY, Grok Build, ZCode, and
9
9
  compatible AI coding-agent CLIs.
10
10
 
11
11
  [![npm](https://img.shields.io/npm/v/@oneciel-ai/ciel-runtime?label=npm)](https://www.npmjs.com/package/@oneciel-ai/ciel-runtime)
@@ -76,6 +76,7 @@ Launch another runtime while preserving its remaining CLI arguments:
76
76
  ciel-runtime codex
77
77
  ciel-runtime agy
78
78
  ciel-runtime grok
79
+ ciel-runtime zcode
79
80
  ciel-runtime --ca-runtime codex-app-server
80
81
  ```
81
82
 
@@ -91,7 +92,7 @@ ciel-runtimectl test
91
92
 
92
93
  | Capability | What it provides |
93
94
  |---|---|
94
- | Runtime launch | Claude Code, Codex, Codex App Server, AGY, and Grok Build from one entrypoint |
95
+ | Runtime launch | Claude Code, Codex, Codex App Server, AGY, Grok Build, and ZCode from one entrypoint |
95
96
  | Provider routing | Native connections where supported; otherwise a loopback HTTP router with provider-owned endpoint and authentication rules |
96
97
  | Protocol adaptation | Anthropic Messages, OpenAI Chat, OpenAI Responses, Ollama Chat, tool calls, thinking blocks, and SSE streams |
97
98
  | Model control | Provider catalogs, context/output limits, reasoning effort, sampling options, API-key rotation, and rate-limit handling |
package/ciel_runtime.py CHANGED
@@ -23,6 +23,7 @@ from ciel_runtime_support import channel_cursor_repository as channel_cursor_sto
23
23
  from ciel_runtime_support import channel_llm_context, claude_launch_assembly, cli_assembly, cli_dispatch, cli_parser, codex_launch_configuration, codex_turn_recovery, kimi_identity, llm_option_config, llm_presets, native_context_recovery
24
24
  from ciel_runtime_support import ollama_catalog as ollama_catalog_policy
25
25
  from ciel_runtime_support import prelaunch, prelaunch_assembly, provider_catalog_sources, provider_models, provider_network, rate_limit_policy, router_request_assembly, router_server_runtime, runtime_asset_assembly, runtime_launch, runtime_primitives, terminal_platform_io, windows_console_mode
26
+ from ciel_runtime_support.prelaunch_launch_panel import launch_panel_rows as project_launch_panel_rows
26
27
  from ciel_runtime_support.advisor_client import AdvisorClient, AdvisorClientIO, AdvisorClientPolicy, ProviderChatExecutor, ProviderChatIO, ProviderChatPolicy
27
28
  from ciel_runtime_support.advisor_policy import AdvisorDecisionServices, AdvisorServices, AdvisorShortcutController, AdvisorShortcutPorts, AdvisorTextServices
28
29
  from ciel_runtime_support.advisor_policy import advisor_focus_for_message as project_advisor_focus
@@ -172,6 +173,7 @@ from ciel_runtime_support.zai_oauth import ZaiOAuthClient, ZaiOAuthHttp, ZaiOAut
172
173
  from ciel_runtime_support.headless_config import HeadlessConfigCommands, HeadlessConfigServices, HeadlessEnvFileLoader, apply_headless_config
173
174
  from ciel_runtime_support.http_response import ChannelDeliveryGuard, HttpResponseAdapter
174
175
  from ciel_runtime_support.kimi_runtime_context import KimiConfigurationPorts, KimiIdentityPorts, KimiLifecyclePorts, KimiProcessPorts, KimiRuntimeCompatibilityApi, KimiRuntimeContext
176
+ from ciel_runtime_support.zcode_runtime_context import ZcodeConfigurationPorts, ZcodeLifecyclePorts, ZcodeProcessPorts, ZcodeRuntimeCompatibilityApi, ZcodeRuntimeContext
175
177
  from ciel_runtime_support.launch_diagnostics import LaunchCommandDiagnostics, StderrCaptureAdapter
176
178
  from ciel_runtime_support.launch_state import LaunchStateRepository
177
179
  from ciel_runtime_support.launch_state import current_launch_cwd_key as project_current_launch_cwd_key
@@ -4006,6 +4008,21 @@ run_kimi_oauth_login = _KIMI_RUNTIME_API.oauth_login
4006
4008
  run_kimi_oauth_action = _KIMI_RUNTIME_API.oauth_action
4007
4009
  launch_kimi = _KIMI_RUNTIME_API.launch
4008
4010
  launch_kimi = SynchronizedLaunch(launch_kimi, sync_remote_launch_assets, "kimi")
4011
+
4012
+ def zcode_runtime_context() -> ZcodeRuntimeContext:
4013
+ return ZcodeRuntimeContext(
4014
+ process=ZcodeProcessPorts(find_executable, subprocess.run, subprocess.call, print, os.environ, path_with_ciel_runtime_user_dirs),
4015
+ config=ZcodeConfigurationPorts(load_config, get_current_provider, current_alias, claude_code_router_auth_token, ROUTER_BASE, ZAI_ANTHROPIC_BASE_URL,
4016
+ WORKSPACE_STATE_DIR / "zcode-home" / ".zcode" / "cli" / "config.json",
4017
+ lambda path, data, purpose: json_artifact_repository(path).save(data, purpose), lambda key: zai_oauth_runtime().import_api_key(key, source="zcode")),
4018
+ lifecycle=ZcodeLifecyclePorts(run_zai_oauth_action, start_router_if_needed, run_with_router_lifetime, materialize_runtime_command,
4019
+ lambda provider, model: record_launch_state_for_cwd(current_launch_cwd_key(), provider, "zcode-routed", model)),
4020
+ )
4021
+
4022
+ _ZCODE_RUNTIME_API = ZcodeRuntimeCompatibilityApi(zcode_runtime_context)
4023
+ def install_zcode_if_missing() -> str: return _ZCODE_RUNTIME_API.install_if_missing()
4024
+ def launch_zcode(passthrough: list[str] | None = None, **_kwargs: Any) -> int: return _ZCODE_RUNTIME_API.launch(list(passthrough or []))
4025
+ launch_zcode = SynchronizedLaunch(launch_zcode, sync_remote_launch_assets, "zcode")
4009
4026
  enable_ansi = enable_terminal_ansi
4010
4027
  ansi = render_ansi
4011
4028
  animated_ansi_text = render_animated_ansi_text
@@ -4064,25 +4081,7 @@ prompt_menu_value = _PRELAUNCH_SHELL_API.prompt_menu_value
4064
4081
  _prompt_menu_multiline_value_raw = _PRELAUNCH_SHELL_API.prompt_menu_multiline_value_raw
4065
4082
  prompt_menu_multiline_value = _PRELAUNCH_SHELL_API.prompt_menu_multiline_value
4066
4083
 
4067
- def launch_panel_rows(cfg: dict[str, Any]) -> tuple[list[str], list[str]]:
4068
- provider, pcfg = get_current_provider(cfg)
4069
- family = provider_menu_label(provider, pcfg)
4070
- claude_suffix = "" if claude_launch_enabled_for_provider(provider) else f" [disabled: {family} provider selected]"
4071
- codex_suffix = "" if codex_launch_enabled_for_provider(provider) else f" [disabled: {family} provider selected]"
4072
- agy_suffix = "" if agy_launch_enabled_for_provider(provider) else " [disabled: select AGY provider]"
4073
- kimi_suffix = "" if provider == "kimi" else " [disabled: select Kimi provider]"
4074
- return (
4075
- [
4076
- f"Claude{claude_suffix}",
4077
- f"Codex{codex_suffix}",
4078
- f"AGY{agy_suffix}",
4079
- f"Kimi{kimi_suffix}",
4080
- "Grok Build",
4081
- f"Codex app server{codex_suffix}",
4082
- "Back",
4083
- ],
4084
- ["launch", "launch-codex", "launch-agy", "launch-kimi", "launch-grok", "launch-codex-app-server", "back"],
4085
- )
4084
+ def launch_panel_rows(cfg: dict[str, Any]) -> tuple[list[str], list[str]]: return project_launch_panel_rows(cfg, current_provider=get_current_provider, provider_label=provider_menu_label, claude_enabled=claude_launch_enabled_for_provider, codex_enabled=codex_launch_enabled_for_provider, agy_enabled=agy_launch_enabled_for_provider)
4086
4085
 
4087
4086
  def request_limits_menu_service() -> RequestLimitsMenuService:
4088
4087
  return RequestLimitsMenuService(load_config, save_config, ROUTER_WORKSPACE, os.environ)
@@ -4136,14 +4135,14 @@ def portable_prelaunch_menu(passthrough: list[str] | None = None) -> int:
4136
4135
  config=prelaunch.PrelaunchConfig(clear_model_cache, current_provider_panel_choice, default_base_url, get_current_provider, load_config, preflight_lines,
4137
4136
  provider_menu_label, save_config, settings_ready_except_api_key, read_model_list_cache),
4138
4137
  launch_policy=prelaunch.PrelaunchLaunchPolicy(agy_launch_enabled_for_provider, claude_launch_enabled_for_provider, codex_launch_enabled_for_provider,
4139
- launch_blockers_require_api_key, launch_readiness_errors, launch_kimi, launch_grok),
4138
+ launch_blockers_require_api_key, launch_readiness_errors, launch_kimi, launch_grok, launch_zcode),
4140
4139
  panel_rows=prelaunch.PrelaunchPanelRows(advisor_model_panel_rows, api_key_panel_rows, base_url_panel_rows, context_setup_panel_rows, language_panel_rows,
4141
4140
  llm_option_panel_rows, llm_preset_panel_rows, log_level_panel_rows, model_panel_rows, provider_panel_rows),
4142
4141
  mutations=prelaunch.PrelaunchMutations(apply_context_setup_config, apply_llm_preset_config, apply_timeout_profile_to_provider,
4143
4142
  set_advisor_model_config, set_base_url_config, set_llm_option_config, set_log_level_config,
4144
4143
  set_model_config, set_provider_choice_config, request_limits_menu_service()),
4145
4144
  secrets=prelaunch.PrelaunchSecrets(clear_api_key_config, mask_secret, parse_api_key_list, secret_fingerprint, store_api_key_input_config,
4146
- store_api_keys_config, run_copilot_oauth_action, run_kimi_oauth_action),
4145
+ store_api_keys_config, run_copilot_oauth_action, run_kimi_oauth_action, run_zai_oauth_action),
4147
4146
  options=prelaunch.PrelaunchOptions(llm_option_current_bool, llm_option_prompt_default, timeout_profile_panel_rows, web_backend_panel_rows,
4148
4147
  set_web_backend_config, external_event_panel_rows, set_external_event_config,
4149
4148
  launch_panel_rows, remote_instruction_panel_rows, set_remote_instruction_config,
@@ -4939,7 +4938,7 @@ def cli_services() -> cli_dispatch.CliServices:
4939
4938
  core=cli_dispatch.CliCore(VERSION, cli_usage, find_executable, get_current_provider, load_config, pop_headless_env_file_args,
4940
4939
  portable_provider_menu, run_external_menu, run_quiet_upgrade_and_exit),
4941
4940
  runtime=cli_dispatch.CliRuntime(agy_passthrough_has_command, codex_passthrough_has_command, last_launch_runtime, launch_agy, launch_claude,
4942
- launch_codex, launch_codex_app_server, native_agy_enabled, native_codex_enabled, launch_grok),
4941
+ launch_codex, launch_codex_app_server, native_agy_enabled, native_codex_enabled, launch_grok, launch_zcode),
4943
4942
  provider_commands=cli_dispatch.CliProviderCommands(cmd_advisor_model, cmd_api_key, cmd_base_url, cmd_language, cmd_log_level, cmd_model,
4944
4943
  cmd_models, cmd_provider, cmd_provider_options, cmd_set_api_key),
4945
4944
  special_commands=cli_dispatch.CliSpecialCommands(cmd_ollama_catalog, cmd_ollama_native, cmd_ollama_options, cmd_web_fetch, cmd_web_search),
@@ -4949,7 +4948,7 @@ def cli_services() -> cli_dispatch.CliServices:
4949
4948
  ).services()
4950
4949
  def cli_parser_services() -> cli_parser.CliParserServices:
4951
4950
  return cli_assembly.CliParserAssembly(
4952
- launch=cli_parser.CliParserLaunch(cmd_cli, cmd_launch, cmd_launch_codex, cmd_launch_codex_app_server, cmd_launch_agy, serve, cmd_launch_grok),
4951
+ launch=cli_parser.CliParserLaunch(cmd_cli, cmd_launch, cmd_launch_codex, cmd_launch_codex_app_server, cmd_launch_agy, serve, cmd_launch_grok, cmd_launch_zcode),
4953
4952
  runtime=cli_parser.CliParserRuntime(cmd_version, cmd_status, cmd_env, cmd_stop, cmd_test),
4954
4953
  settings=cli_parser.CliParserSettings(cmd_language, cmd_web_search, cmd_web_fetch, cmd_log_level, *event_settings_cli.handlers(event_settings_cli.EventSettingsCliPorts(load_config, save_config, external_event_receiver_service, lambda: set_remote_instruction_config('sync', ''), sync_all_remote_memories, print, lambda: USAGE_API_KEYS))),
4955
4954
  provider=cli_parser.CliParserProvider(cmd_ollama_native, cmd_ollama_options, cmd_provider_options, cmd_ollama_catalog, cmd_provider,
@@ -4960,7 +4959,7 @@ def cli_parser_services() -> cli_parser.CliParserServices:
4960
4959
  def cli_application_context() -> CliApplicationContext:
4961
4960
  return CliApplicationContext(
4962
4961
  dispatch=CliApplicationDispatchPorts(dispatch_cli, cli_services, launch_claude, launch_codex, launch_codex_app_server,
4963
- launch_agy, launch_kimi, run_kimi_oauth_login, launch_grok),
4962
+ launch_agy, launch_kimi, run_kimi_oauth_login, launch_grok, launch_zcode),
4964
4963
  presentation=CliApplicationPresentationPorts(build_cli_parser, cli_parser_services, VERSION, print, lambda: sys.argv),
4965
4964
  )
4966
4965
 
@@ -4973,6 +4972,7 @@ cmd_launch_codex = _CLI_APPLICATION_API.cmd_launch_codex
4973
4972
  cmd_launch_codex_app_server = _CLI_APPLICATION_API.cmd_launch_codex_app_server
4974
4973
  cmd_launch_agy = _CLI_APPLICATION_API.cmd_launch_agy
4975
4974
  cmd_launch_grok = _CLI_APPLICATION_API.cmd_launch_grok
4975
+ cmd_launch_zcode = _CLI_APPLICATION_API.cmd_launch_zcode
4976
4976
  cmd_version = _CLI_APPLICATION_API.cmd_version
4977
4977
  main = _CLI_APPLICATION_API.main
4978
4978
 
@@ -18,6 +18,7 @@ class CliApplicationDispatchPorts:
18
18
  launch_kimi: Callable[[list[str]], int]
19
19
  kimi_login: Callable[[], int]
20
20
  launch_grok: Callable[[list[str]], int] = lambda _argv: 127
21
+ launch_zcode: Callable[[list[str]], int] = lambda _argv: 127
21
22
 
22
23
 
23
24
  @dataclass(frozen=True, slots=True)
@@ -60,6 +61,9 @@ class CliApplicationContext:
60
61
  def cmd_launch_grok(self, args: argparse.Namespace) -> None:
61
62
  raise SystemExit(self.dispatch.launch_grok(args.argv))
62
63
 
64
+ def cmd_launch_zcode(self, args: argparse.Namespace) -> None:
65
+ raise SystemExit(self.dispatch.launch_zcode(args.argv))
66
+
63
67
  def cmd_version(self, _: argparse.Namespace) -> None:
64
68
  self.presentation.output(f"ciel-runtime {self.presentation.version}")
65
69
 
@@ -84,6 +88,8 @@ class CliApplicationContext:
84
88
  "grok": self.dispatch.launch_grok,
85
89
  "grok-build": self.dispatch.launch_grok,
86
90
  "launch-grok": self.dispatch.launch_grok,
91
+ "zcode": self.dispatch.launch_zcode,
92
+ "launch-zcode": self.dispatch.launch_zcode,
87
93
  }
88
94
  if len(arguments) >= 2 and arguments[1] in routes:
89
95
  raise SystemExit(routes[arguments[1]](arguments[2:]))
@@ -124,6 +130,9 @@ class CliApplicationCompatibilityApi:
124
130
  def cmd_launch_grok(self, args: argparse.Namespace) -> None:
125
131
  self.context().cmd_launch_grok(args)
126
132
 
133
+ def cmd_launch_zcode(self, args: argparse.Namespace) -> None:
134
+ self.context().cmd_launch_zcode(args)
135
+
127
136
  def cmd_version(self, args: argparse.Namespace) -> None:
128
137
  self.context().cmd_version(args)
129
138
 
@@ -34,6 +34,7 @@ class CliRuntime:
34
34
  native_agy_enabled: Any
35
35
  native_codex_enabled: Any
36
36
  launch_grok: Any = lambda *_args, **_kwargs: 127
37
+ launch_zcode: Any = lambda *_args, **_kwargs: 127
37
38
 
38
39
 
39
40
  @dataclass(frozen=True)
@@ -119,6 +120,7 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
119
120
  launch_codex = services.runtime.launch_codex
120
121
  launch_codex_app_server = services.runtime.launch_codex_app_server
121
122
  launch_grok = services.runtime.launch_grok
123
+ launch_zcode = services.runtime.launch_zcode
122
124
  load_config = services.core.load_config
123
125
  native_agy_enabled = services.runtime.native_agy_enabled
124
126
  native_codex_enabled = services.runtime.native_codex_enabled
@@ -142,6 +144,8 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
142
144
  return launch_codex(rest)
143
145
  if head in ("grok", "grok-build", "launch-grok"):
144
146
  return launch_grok(rest)
147
+ if head in ("zcode", "launch-zcode"):
148
+ return launch_zcode(rest)
145
149
  if head in ("version", "--version", "-v"):
146
150
  print(f"ciel-runtime {VERSION}")
147
151
  return 0
@@ -295,8 +299,8 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
295
299
  runtime = str(value or "").strip().lower()
296
300
  if runtime in ("codex-app", "codex-appserver"):
297
301
  runtime = "codex-app-server"
298
- if runtime not in ("claude", "codex", "codex-app-server", "agy", "grok", "last"):
299
- raise SystemExit("--ca-runtime must be claude, codex, codex-app-server, agy, grok, or last")
302
+ if runtime not in ("claude", "codex", "codex-app-server", "agy", "grok", "zcode", "last"):
303
+ raise SystemExit("--ca-runtime must be claude, codex, codex-app-server, agy, grok, zcode, or last")
300
304
  elif arg in ("--ca-no-launch", "--ca-configure-only", "--ca-setup-only"):
301
305
  configure_only = True
302
306
  skip_menu = True
@@ -667,7 +671,7 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
667
671
  return 0
668
672
  if runtime == "last":
669
673
  remembered_runtime = str(last_launch_runtime() or "").strip().lower()
670
- runtime = remembered_runtime if remembered_runtime in {"claude", "codex", "agy", "grok"} else "claude"
674
+ runtime = remembered_runtime if remembered_runtime in {"claude", "codex", "agy", "grok", "zcode"} else "claude"
671
675
  if runtime == "agy":
672
676
  return launch_agy(
673
677
  passthrough,
@@ -700,6 +704,14 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
700
704
  update_check=update_check,
701
705
  self_update_check=self_update_check,
702
706
  )
707
+ if runtime == "zcode":
708
+ return launch_zcode(
709
+ passthrough,
710
+ skip_menu=skip_menu,
711
+ force_menu=force_menu,
712
+ update_check=update_check,
713
+ self_update_check=self_update_check,
714
+ )
703
715
  return launch_claude(
704
716
  passthrough,
705
717
  skip_menu=skip_menu,
@@ -16,6 +16,7 @@ class CliParserLaunch:
16
16
  launch_agy: CliHandler
17
17
  serve: CliHandler
18
18
  launch_grok: CliHandler = lambda _args: None
19
+ launch_zcode: CliHandler = lambda _args: None
19
20
 
20
21
 
21
22
  @dataclass(frozen=True)
@@ -84,6 +85,7 @@ def build_cli_parser(services: CliParserServices) -> argparse.ArgumentParser:
84
85
  )
85
86
  _add_remainder_command(commands, "launch-agy", services.launch.launch_agy)
86
87
  _add_remainder_command(commands, "launch-grok", services.launch.launch_grok)
88
+ _add_remainder_command(commands, "launch-zcode", services.launch.launch_zcode)
87
89
  commands.add_parser("serve").set_defaults(func=services.launch.serve)
88
90
  commands.add_parser("version").set_defaults(func=services.runtime.version)
89
91
  commands.add_parser("status").set_defaults(func=services.runtime.status)
@@ -7,6 +7,7 @@ def cli_usage_text() -> str:
7
7
  ciel-runtime codex-app-server [args...] Launch Codex app-server through ciel-runtime
8
8
  ciel-runtime agy [args...] Launch Google Antigravity CLI through ciel-runtime
9
9
  ciel-runtime grok [args...] Launch the official Grok Build CLI
10
+ ciel-runtime zcode [args...] Launch ZCode through ciel-runtime router
10
11
  ciel-runtime resume Resume Codex/AGY when that runtime provider is selected
11
12
 
12
13
  Control plane, runs before Claude Code and does not require LLM connectivity:
@@ -42,7 +43,7 @@ Headless setup flags, namespaced to avoid Claude CLI collisions:
42
43
  Publish localhost through Tailscale Serve HTTPS
43
44
  ciel-runtime --ca-provider PROVIDER Set provider, then launch
44
45
  ciel-runtime --ca-env-file PATH Load CIEL_RUNTIME_* values from a .env file
45
- ciel-runtime --ca-runtime claude|codex|codex-app-server|agy|grok
46
+ ciel-runtime --ca-runtime claude|codex|codex-app-server|agy|grok|zcode
46
47
  Select Claude Code, Codex, Codex app-server, AGY, or Grok Build
47
48
  ciel-runtime --ca-menu Apply setup values, then open the menu
48
49
  ciel-runtime --ca-language en|ko|ja|zh
@@ -81,6 +81,7 @@ class PrelaunchLaunchPolicy:
81
81
  launch_readiness_errors: Callable[..., Any]
82
82
  launch_kimi: Callable[..., Any]
83
83
  launch_grok: Callable[..., Any] = lambda *_args, **_kwargs: 0
84
+ launch_zcode: Callable[..., Any] = lambda *_args, **_kwargs: 0
84
85
 
85
86
 
86
87
  @dataclass(frozen=True, slots=True)
@@ -135,6 +136,7 @@ class PrelaunchSecrets:
135
136
  store_api_keys_config: Callable[..., Any]
136
137
  copilot_oauth_action: Callable[..., Any]
137
138
  kimi_oauth_action: Callable[..., Any]
139
+ zai_oauth_action: Callable[..., Any] = lambda _action: []
138
140
 
139
141
 
140
142
  @dataclass(frozen=True, slots=True)
@@ -213,6 +215,7 @@ def run_prelaunch_menu(passthrough: list[str] | None = None,
213
215
  launch_readiness_errors = services.launch_policy.launch_readiness_errors
214
216
  launch_kimi = services.launch_policy.launch_kimi
215
217
  launch_grok = services.launch_policy.launch_grok
218
+ launch_zcode = services.launch_policy.launch_zcode
216
219
  llm_option_current_bool = services.options.llm_option_current_bool
217
220
  llm_option_panel_rows = services.panel_rows.llm_option_panel_rows
218
221
  llm_option_prompt_default = services.options.llm_option_prompt_default
@@ -247,6 +250,7 @@ def run_prelaunch_menu(passthrough: list[str] | None = None,
247
250
  store_api_keys_config = services.secrets.store_api_keys_config
248
251
  copilot_oauth_action = services.secrets.copilot_oauth_action
249
252
  kimi_oauth_action = services.secrets.kimi_oauth_action
253
+ zai_oauth_action = services.secrets.zai_oauth_action
250
254
  timeout_profile_panel_rows = services.options.timeout_profile_panel_rows
251
255
  web_backend_panel_rows = services.options.web_backend_panel_rows
252
256
  set_web_backend_config = services.options.set_web_backend_config
@@ -485,6 +489,10 @@ def run_prelaunch_menu(passthrough: list[str] | None = None,
485
489
  persist_launch_action(action)
486
490
  launch_grok([])
487
491
  return PRELAUNCH_CANCEL
492
+ if action == "launch-zcode":
493
+ persist_launch_action(action)
494
+ launch_zcode([])
495
+ return PRELAUNCH_CANCEL
488
496
  continue
489
497
  if panel == "language" and value:
490
498
  cfg["language"] = value
@@ -561,6 +569,15 @@ def run_prelaunch_menu(passthrough: list[str] | None = None,
561
569
  provider, pcfg = get_current_provider(cfg)
562
570
  panel_rows, panel_values = api_key_panel_rows(provider, pcfg)
563
571
  panel_idx = 0
572
+ elif value.startswith("zai-oauth-"):
573
+ messages = zai_oauth_action(
574
+ value.removeprefix("zai-oauth-")
575
+ )
576
+ refresh_checks()
577
+ cfg = load_config()
578
+ provider, pcfg = get_current_provider(cfg)
579
+ panel_rows, panel_values = api_key_panel_rows(provider, pcfg)
580
+ panel_idx = 0
564
581
  elif value.startswith("oauth-"):
565
582
  messages = copilot_oauth_action(
566
583
  value.removeprefix("oauth-")
@@ -0,0 +1,38 @@
1
+ """Runtime launch chooser projection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Callable
6
+
7
+
8
+ def launch_panel_rows(
9
+ config: dict[str, Any],
10
+ *,
11
+ current_provider: Callable[..., tuple[str, dict[str, Any]]],
12
+ provider_label: Callable[..., str],
13
+ claude_enabled: Callable[[str], bool],
14
+ codex_enabled: Callable[[str], bool],
15
+ agy_enabled: Callable[[str], bool],
16
+ ) -> tuple[list[str], list[str]]:
17
+ provider, provider_config = current_provider(config)
18
+ family = provider_label(provider, provider_config)
19
+ claude_suffix = "" if claude_enabled(provider) else f" [disabled: {family} provider selected]"
20
+ codex_suffix = "" if codex_enabled(provider) else f" [disabled: {family} provider selected]"
21
+ agy_suffix = "" if agy_enabled(provider) else " [disabled: select AGY provider]"
22
+ kimi_suffix = "" if provider == "kimi" else " [disabled: select Kimi provider]"
23
+ return (
24
+ [
25
+ f"Claude{claude_suffix}",
26
+ f"Codex{codex_suffix}",
27
+ f"AGY{agy_suffix}",
28
+ f"Kimi{kimi_suffix}",
29
+ "Grok Build",
30
+ "ZCode",
31
+ f"Codex app server{codex_suffix}",
32
+ "Back",
33
+ ],
34
+ ["launch", "launch-codex", "launch-agy", "launch-kimi", "launch-grok", "launch-zcode", "launch-codex-app-server", "back"],
35
+ )
36
+
37
+
38
+ __all__ = ["launch_panel_rows"]
@@ -15,6 +15,7 @@ REMEMBERED_LAUNCH_ACTIONS = frozenset(
15
15
  "launch-codex-app-server",
16
16
  "launch-kimi",
17
17
  "launch-grok",
18
+ "launch-zcode",
18
19
  }
19
20
  )
20
21
 
@@ -33,7 +34,7 @@ def preferred_launch_action(
33
34
  return remembered
34
35
  if remembered in {"launch-codex", "launch-codex-app-server"} and supports_codex(provider):
35
36
  return remembered
36
- if remembered == "launch-grok":
37
+ if remembered in {"launch-grok", "launch-zcode"}:
37
38
  return remembered
38
39
  return fallback(provider)
39
40
 
@@ -247,6 +247,32 @@ class PrelaunchPanelContext:
247
247
  def api_key_panel_rows(
248
248
  self, provider: str, provider_config: dict[str, Any] | None = None
249
249
  ) -> PanelRows:
250
+ if provider == "zai":
251
+ config = provider_config or {}
252
+ source = str(config.get("credential_source") or "").strip()
253
+ configured = bool(self.configuration.api_key_count(provider, config))
254
+ status = "connected" if configured and source == "zai-oauth" else (
255
+ "API key configured" if configured else "login required"
256
+ )
257
+ rows, values = self.configuration_panel_projection().api_key_rows(
258
+ provider, provider_config
259
+ )
260
+ return (
261
+ [
262
+ f"Z.AI OAuth: {status}",
263
+ "Login with Z.AI OAuth (shared by all runtimes)",
264
+ "Refresh Z.AI OAuth status",
265
+ "Logout shared Z.AI OAuth credential",
266
+ *rows,
267
+ ],
268
+ [
269
+ "__info__",
270
+ "zai-oauth-login",
271
+ "zai-oauth-status",
272
+ "zai-oauth-logout",
273
+ *values,
274
+ ],
275
+ )
250
276
  if provider == "kimi":
251
277
  status = (
252
278
  "managed profile detected"
@@ -151,8 +151,8 @@ class PrelaunchShellContext:
151
151
 
152
152
  def default_prelaunch_action(self, provider: str) -> str:
153
153
  config = self.provider.load_config()
154
- if str(config.get("last_launch_action") or "").strip() == "launch-grok":
155
- return "launch-grok"
154
+ if str(config.get("last_launch_action") or "").strip() in {"launch-grok", "launch-zcode"}:
155
+ return str(config.get("last_launch_action")).strip()
156
156
  if provider == "kimi":
157
157
  remembered = str(config.get("last_launch_action") or "").strip()
158
158
  if remembered in {"launch", "launch-kimi"}:
@@ -169,7 +169,7 @@ class PrelaunchShellContext:
169
169
  )
170
170
 
171
171
  def prelaunch_action_index(self, action: str) -> int:
172
- if action in {"launch", "launch-codex", "launch-codex-app-server", "launch-agy", "launch-kimi", "launch-grok"}:
172
+ if action in {"launch", "launch-codex", "launch-codex-app-server", "launch-agy", "launch-kimi", "launch-grok", "launch-zcode"}:
173
173
  action = "launch-menu"
174
174
  try:
175
175
  return self.main_menu_actions.index(action)
@@ -23,6 +23,8 @@ RUNTIME_FILES = {
23
23
  "kimi": "AGENTS.md",
24
24
  # Grok Build reads the AGENTS.md family, like Codex and Kimi.
25
25
  "grok": "AGENTS.md",
26
+ # ZCode discovers workspace instructions from AGENTS.md.
27
+ "zcode": "AGENTS.md",
26
28
  }
27
29
  URL_KEYS = {
28
30
  "claude": "claude_url",
@@ -107,11 +107,21 @@ class GrokRuntimeAdapter(CliRuntimeAdapter):
107
107
  return RuntimeCommand(argv=tuple(argv), env=dict(self.environment), cwd=spec.cwd)
108
108
 
109
109
 
110
+ class ZcodeRuntimeAdapter(CliRuntimeAdapter):
111
+ """ZCode command materialization for a launch-scoped storage root."""
112
+
113
+ def build_command(self, spec: LaunchSpec) -> RuntimeCommand:
114
+ argv = [str(spec.runtime.executable or self.executable)]
115
+ argv.extend(spec.passthrough)
116
+ return RuntimeCommand(argv=tuple(argv), env=dict(self.environment), cwd=spec.cwd)
117
+
118
+
110
119
  RUNTIME_ADAPTERS: AdapterRegistry[RuntimeAdapter] = AdapterRegistry()
111
120
  RUNTIME_ADAPTERS.register("claude", lambda **kwargs: ClaudeRuntimeAdapter(name="claude", **kwargs))
112
121
  RUNTIME_ADAPTERS.register("codex", lambda **kwargs: CodexRuntimeAdapter(name="codex", **kwargs))
113
122
  RUNTIME_ADAPTERS.register("agy", lambda **kwargs: AgyRuntimeAdapter(name="agy", **kwargs))
114
123
  RUNTIME_ADAPTERS.register("grok", lambda **kwargs: GrokRuntimeAdapter(name="grok", **kwargs))
124
+ RUNTIME_ADAPTERS.register("zcode", lambda **kwargs: ZcodeRuntimeAdapter(name="zcode", **kwargs))
115
125
 
116
126
 
117
127
  __all__ = [
@@ -121,4 +131,5 @@ __all__ = [
121
131
  "CliRuntimeAdapter",
122
132
  "CodexRuntimeAdapter",
123
133
  "GrokRuntimeAdapter",
134
+ "ZcodeRuntimeAdapter",
124
135
  ]
@@ -87,7 +87,7 @@ OPENCODE_ENDPOINT_ALIASES = {
87
87
  }
88
88
 
89
89
  APP_NAME = "Ciel Runtime"
90
- VERSION = "0.2.25"
90
+ VERSION = "0.2.26"
91
91
  CREDITS = "Credits: One Ciel LLC"
92
92
  PRELAUNCH_CANCEL = 10
93
93
  PRELAUNCH_LAUNCH_CODEX = 11
@@ -13,7 +13,7 @@ from typing import Any
13
13
 
14
14
  TERMINAL_INPUT_MODE_RESET = (
15
15
  "\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1004l"
16
- "\x1b[?1005l\x1b[?1006l\x1b[?1015l"
16
+ "\x1b[?1005l\x1b[?1006l\x1b[?1015l\x1b[?2004l"
17
17
  )
18
18
 
19
19
 
@@ -399,6 +399,30 @@ class ZaiOAuthRuntime:
399
399
  return ""
400
400
  return str(provider.get("api_key") or "").strip()
401
401
 
402
+ def import_api_key(self, api_key: str, *, source: str = "zcode") -> list[str]:
403
+ key = str(api_key or "").strip()
404
+ if not key:
405
+ return ["Z.AI OAuth import skipped: no Coding Plan API key was found."]
406
+ config = self.ports.load_config()
407
+ provider = config.setdefault("providers", {}).setdefault("zai", {})
408
+ if (
409
+ str(provider.get("api_key") or "").strip() == key
410
+ and provider.get("credential_source") == "zai-oauth"
411
+ ):
412
+ return ["Z.AI OAuth credential is already shared by all runtimes."]
413
+ provider["api_key"] = key
414
+ provider.pop("api_keys", None)
415
+ provider["credential_source"] = "zai-oauth"
416
+ provider["oauth_authenticated_at"] = datetime.now(timezone.utc).isoformat()
417
+ provider["oauth_import_source"] = str(source or "zcode")
418
+ config["current_provider"] = "zai"
419
+ self.ports.save_config(config)
420
+ self.ports.clear_model_cache()
421
+ return [
422
+ "Z.AI OAuth Coding Plan credential imported into Ciel Runtime for all clients.",
423
+ f"Credential: {self.ports.mask(key)}; fp {self.ports.fingerprint(key)}",
424
+ ]
425
+
402
426
  def action(self, action: str, *, no_browser: bool = False) -> list[str]:
403
427
  if action == "status":
404
428
  token = self.token()
@@ -0,0 +1,204 @@
1
+ """ZCode installation, shared Z.AI OAuth, and routed launch context."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Any, Callable
9
+
10
+
11
+ def zcode_settings(*, model: str, base_url: str, api_key: str) -> dict[str, Any]:
12
+ """Build the documented ZCode custom Anthropic-provider configuration."""
13
+
14
+ model_id = str(model or "model").strip() or "model"
15
+ return {
16
+ "provider": {
17
+ "zai": {
18
+ "kind": "anthropic",
19
+ "name": "Ciel Runtime",
20
+ "options": {
21
+ "apiKey": str(api_key or "not-used"),
22
+ "apiKeyRequired": True,
23
+ "baseURL": str(base_url).rstrip("/"),
24
+ },
25
+ "headers": {},
26
+ "models": {model_id: {"name": model_id}},
27
+ }
28
+ },
29
+ "model": {"main": f"zai/{model_id}", "lite": f"zai/{model_id}"},
30
+ }
31
+
32
+
33
+ @dataclass(frozen=True, slots=True)
34
+ class ZcodeProcessPorts:
35
+ find_executable: Callable[[str], str | None]
36
+ run: Callable[..., Any]
37
+ call: Callable[..., int]
38
+ print_line: Callable[..., None]
39
+ environment: dict[str, str]
40
+ augment_path: Callable[[dict[str, str]], str]
41
+
42
+
43
+ @dataclass(frozen=True, slots=True)
44
+ class ZcodeConfigurationPorts:
45
+ load: Callable[[], dict[str, Any]]
46
+ current_provider: Callable[[dict[str, Any]], tuple[str, dict[str, Any]]]
47
+ current_alias: Callable[[dict[str, Any]], str]
48
+ router_auth_token: Callable[[str, dict[str, Any]], str]
49
+ router_base: str
50
+ zai_anthropic_base_url: str
51
+ settings_path: Path
52
+ save_json: Callable[[Path, dict[str, Any], str], None]
53
+ import_oauth_api_key: Callable[[str], list[str]]
54
+
55
+
56
+ @dataclass(frozen=True, slots=True)
57
+ class ZcodeLifecyclePorts:
58
+ oauth_action: Callable[..., list[str]]
59
+ start_router: Callable[[], Any]
60
+ run_with_router: Callable[[Callable[[], int], bool], int]
61
+ materialize_command: Callable[..., tuple[list[str], dict[str, str]]]
62
+ record_launch: Callable[[str, str], None]
63
+
64
+
65
+ @dataclass(frozen=True, slots=True)
66
+ class ZcodeRuntimeContext:
67
+ process: ZcodeProcessPorts
68
+ config: ZcodeConfigurationPorts
69
+ lifecycle: ZcodeLifecyclePorts
70
+
71
+ def install_if_missing(self) -> str:
72
+ executable = self.process.find_executable("zcode")
73
+ if executable:
74
+ return executable
75
+ npm = self.process.find_executable("npm")
76
+ if not npm:
77
+ raise RuntimeError(
78
+ "ZCode CLI is missing; install zcode-app-cli (Node.js 22.19+)."
79
+ )
80
+ self.process.print_line(
81
+ "Installing ZCode CLI (zcode-app-cli)...", flush=True
82
+ )
83
+ result = self.process.run(
84
+ [npm, "install", "-g", "zcode-app-cli"], check=False
85
+ )
86
+ if result.returncode:
87
+ raise RuntimeError(
88
+ f"ZCode CLI installation failed (exit {result.returncode})."
89
+ )
90
+ executable = self.process.find_executable("zcode")
91
+ if not executable:
92
+ raise RuntimeError(
93
+ "ZCode CLI installed but 'zcode' is not available on PATH."
94
+ )
95
+ return executable
96
+
97
+ @staticmethod
98
+ def _shared_oauth_action(argv: list[str]) -> str | None:
99
+ if not argv:
100
+ return None
101
+ if argv[0] == "login" and (len(argv) == 1 or "--oauth" in argv):
102
+ return "login"
103
+ if argv[0] == "logout":
104
+ return "logout"
105
+ return None
106
+
107
+ def _run_shared_oauth(self, action: str, argv: list[str]) -> int:
108
+ try:
109
+ lines = self.lifecycle.oauth_action(
110
+ action, no_browser="--no-browser" in argv
111
+ )
112
+ except RuntimeError as exc:
113
+ self.process.print_line(f"Z.AI OAuth failed: {exc}", flush=True)
114
+ return 1
115
+ for line in lines:
116
+ self.process.print_line(line, flush=True)
117
+ return 0
118
+
119
+ def write_settings(
120
+ self, config: dict[str, Any], provider: str, provider_config: dict[str, Any]
121
+ ) -> Path:
122
+ payload = zcode_settings(
123
+ model=self.config.current_alias(config),
124
+ base_url=self.config.router_base,
125
+ api_key=self.config.router_auth_token(provider, provider_config),
126
+ )
127
+ self.config.save_json(
128
+ self.config.settings_path, payload, "zcode routed settings"
129
+ )
130
+ return self.config.settings_path
131
+
132
+ def import_zcode_oauth_credential(self, settings_path: Path) -> list[str]:
133
+ try:
134
+ payload = json.loads(settings_path.read_text(encoding="utf-8"))
135
+ provider = payload.get("provider", {}).get("zai", {})
136
+ options = provider.get("options", {})
137
+ base_url = str(options.get("baseURL") or "").rstrip("/")
138
+ key = str(options.get("apiKey") or "").strip()
139
+ except (OSError, UnicodeError, json.JSONDecodeError, AttributeError):
140
+ return []
141
+ if base_url != self.config.zai_anthropic_base_url.rstrip("/") or not key:
142
+ return []
143
+ return self.config.import_oauth_api_key(key)
144
+
145
+ def launch(self, passthrough: list[str]) -> int:
146
+ argv = list(passthrough)
147
+ oauth_action = self._shared_oauth_action(argv)
148
+ if oauth_action:
149
+ return self._run_shared_oauth(oauth_action, argv)
150
+ executable = self.install_if_missing()
151
+ config = self.config.load()
152
+ provider, provider_config = self.config.current_provider(config)
153
+ settings_path = self.write_settings(config, provider, provider_config)
154
+ env = self.process.environment.copy()
155
+ env["PATH"] = self.process.augment_path(env)
156
+ launch_home = settings_path.parents[2]
157
+ env["USERPROFILE"] = str(launch_home)
158
+ env["HOME"] = str(launch_home)
159
+ env["ZCODE_HOME"] = str(launch_home / ".zcode")
160
+ env["ZCODE_STORAGE_DIR"] = str(launch_home / ".zcode")
161
+ command, child_env = self.lifecycle.materialize_command(
162
+ "zcode",
163
+ executable,
164
+ env,
165
+ provider,
166
+ provider_config,
167
+ mode="routed",
168
+ protocol="anthropic_messages",
169
+ cwd=Path.cwd(),
170
+ enable_channels=False,
171
+ passthrough=argv,
172
+ options={},
173
+ )
174
+ manage_router = bool(self.lifecycle.start_router())
175
+ if not argv or argv[0] not in {"--help", "-h", "--version", "-v", "version"}:
176
+ self.lifecycle.record_launch(provider, self.config.current_alias(config))
177
+ result = self.lifecycle.run_with_router(
178
+ lambda: self.process.call(command, env=child_env), manage_router
179
+ )
180
+ if result == 0:
181
+ for line in self.import_zcode_oauth_credential(settings_path):
182
+ self.process.print_line(line, flush=True)
183
+ return result
184
+
185
+
186
+ @dataclass(frozen=True, slots=True)
187
+ class ZcodeRuntimeCompatibilityApi:
188
+ context: Callable[[], ZcodeRuntimeContext]
189
+
190
+ def install_if_missing(self) -> str:
191
+ return self.context().install_if_missing()
192
+
193
+ def launch(self, passthrough: list[str]) -> int:
194
+ return self.context().launch(passthrough)
195
+
196
+
197
+ __all__ = [
198
+ "ZcodeConfigurationPorts",
199
+ "ZcodeLifecyclePorts",
200
+ "ZcodeProcessPorts",
201
+ "ZcodeRuntimeCompatibilityApi",
202
+ "ZcodeRuntimeContext",
203
+ "zcode_settings",
204
+ ]
@@ -7,11 +7,12 @@
7
7
 
8
8
  ## ciel-runtime
9
9
 
10
- 기본값은 Claude Code를 Router를 통해 실행한다. Codex 런타임은 명시적으로 선택한다.
10
+ 기본값은 Claude Code를 Router를 통해 실행한다. 다른 런타임은 명시적으로 선택한다.
11
11
 
12
12
  ```bash
13
13
  ciel-runtime [OPTIONS] [-- RUNTIME_ARGS...]
14
14
  ciel-runtime codex [CODEX_ARGS...]
15
+ ciel-runtime zcode [ZCODE_ARGS...]
15
16
  ciel-runtime --ca-runtime codex -- exec "hello"
16
17
  ```
17
18
 
@@ -23,7 +24,7 @@ ciel-runtime --ca-runtime codex -- exec "hello"
23
24
  | `--ca-model <id>` | 현재 제공자 모델 설정 후 실행 |
24
25
  | `--ca-base-url <url>` | 현재 제공자 기본 URL 설정 후 실행 |
25
26
  | `--ca-api-key <key>` | 현재 제공자 API 키 설정 후 실행 |
26
- | `--ca-runtime claude\|codex` | 실행 런타임 선택 |
27
+ | `--ca-runtime claude\|codex\|codex-app-server\|agy\|grok\|zcode` | 실행 런타임 선택 |
27
28
  | `--ca-no-launch` | 설정만 적용하고 런타임 실행 생략 |
28
29
  | `--` | 이후 인자를 선택된 런타임에 그대로 전달 |
29
30
 
@@ -37,6 +38,20 @@ ciel-runtime --ca-provider ollama --ca-runtime codex -- exec "작업 내용"
37
38
 
38
39
  Codex 런처는 `~/.codex/config.toml`을 수정하지 않는다. 실행 시점에 `-c model_providers.ciel-runtime...` 오버라이드를 붙여 로컬 Router를 OpenAI Responses provider로 등록한다.
39
40
 
41
+ ### ZCode 실행
42
+
43
+ ```bash
44
+ ciel-runtime zcode
45
+ ciel-runtime --ca-runtime zcode -- --continue
46
+ ciel-runtimectl launch-zcode --prompt "작업 내용"
47
+ ```
48
+
49
+ ZCode는 워크스페이스별 Ciel 관리 홈과 `ZCODE_STORAGE_DIR`을 사용한다. 선택된
50
+ provider/model은 로컬 Anthropic Router 설정으로 투영되며 사용자의 일반
51
+ `%USERPROFILE%\.zcode` 또는 `~/.zcode` 설정은 덮어쓰지 않는다. `ciel-runtime
52
+ zcode login --oauth`와 API-key 패널의 Z.AI OAuth는 Ciel 공용 credential store에
53
+ Coding Plan 키를 저장하므로 다른 런타임도 같은 키를 사용한다.
54
+
40
55
  ---
41
56
 
42
57
  ## ciel-runtimectl
@@ -118,6 +118,19 @@ Windows 설치 명령은 `irm https://x.ai/cli/install.ps1 | iex`이고 Linux/ma
118
118
  xAI API key, 선택 모델, reasoning effort를 launch process에만 투영한다. Grok의
119
119
  `~/.grok` 인증·세션·사용자 설정은 Ciel이 덮어쓰지 않는다.
120
120
 
121
+ ### ZCode 실행
122
+
123
+ ```bash
124
+ ciel-runtime zcode
125
+ ciel-runtime zcode --continue
126
+ ```
127
+
128
+ ZCode가 없으면 `zcode-app-cli`를 npm으로 설치한다. 실행 시 워크스페이스별
129
+ Ciel 관리 홈을 사용하고 선택된 provider/model을 로컬 Router로 연결한다.
130
+ Z.AI OAuth는 `ciel-runtimectl zai-oauth login` 또는 `ciel-runtime zcode login
131
+ --oauth`로 등록하며, 최종 Coding Plan API key만 Ciel의 공용 provider 설정에
132
+ 저장되어 Claude, Codex, AGY, ZCode 등 routed client가 함께 사용한다.
133
+
121
134
  ---
122
135
 
123
136
  ## Router 시작/중지
@@ -0,0 +1,68 @@
1
+ okf_version: "1.0"
2
+ knowledge:
3
+ task:
4
+ id: zcode-runtime-oauth-windows-paste-20260824
5
+ date: "2026-08-24"
6
+ objective: >-
7
+ Register ZCode as a supported Ciel runtime, share Z.AI OAuth credentials
8
+ across runtime clients, and repair leaked Windows bracketed-paste markers.
9
+ status: deployment-in-progress
10
+
11
+ evidence:
12
+ source_code:
13
+ terminal_reset:
14
+ file: ciel_runtime_support/terminal_platform_io.py
15
+ observation: >-
16
+ The reset sequence disabled mouse and focus modes but omitted DECSET
17
+ bracketed-paste disable ESC[?2004l.
18
+ runtime_registry:
19
+ file: ciel_runtime_support/runtime_adapters.py
20
+ observation: ZCode had no registered runtime adapter.
21
+ oauth_storage:
22
+ file: ciel_runtime_support/zai_oauth.py
23
+ observation: >-
24
+ The resolved Coding Plan API key is stored under providers.zai.api_key,
25
+ which is the shared provider credential consumed by routed clients.
26
+ upstream_runtime:
27
+ package: zcode-app-cli@3.8.1-15
28
+ runtime: zcode-runtime@0.16.3
29
+ observations:
30
+ - The help advertises --settings, but the bundled runtime rejected it as Unknown option.
31
+ - The bundled code resolves ZCODE_STORAGE_DIR and workspace AGENTS.md.
32
+ - A workspace-isolated USERPROFILE/HOME loaded the generated Ciel provider config.
33
+
34
+ implementation:
35
+ runtime:
36
+ registration:
37
+ commands: [ciel-runtime-zcode, launch-zcode, ca-runtime-zcode]
38
+ storage:
39
+ scope: workspace
40
+ config: zcode-home/.zcode/cli/config.json
41
+ routing:
42
+ protocol: anthropic_messages
43
+ provider_id: zai
44
+ endpoint: ciel-runtime-loopback-router
45
+ authentication:
46
+ shared_store: providers.zai.api_key
47
+ entrypoints: [provider-api-key-panel, zai-oauth-cli, zcode-login-oauth]
48
+ reverse_import: official-zcode-oauth-config-after-exit
49
+ terminal:
50
+ cleanup_sequence_added: ESC[?2004l
51
+
52
+ verification:
53
+ completed:
54
+ - Python compilation
55
+ - focused ZCode runtime tests
56
+ - focused Z.AI OAuth tests
57
+ - focused remote-memory tests
58
+ - actual zcode doctor execution
59
+ - actual ZCode TUI model-status rendering through the Ciel router
60
+ - full regression suite: 2739 passed and 136 skipped
61
+ - documentation validation: 23 Markdown files passed
62
+ - lint and architecture checks
63
+ - npm package dry run for version 0.2.26
64
+ - local install to C:/Users/djlov/.local/share/ciel-runtime
65
+ - local launcher reports ciel-runtime 0.2.26
66
+ - local ZCode launch reports zcode-runtime 0.16.3
67
+ pending:
68
+ - nightly publication
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oneciel-ai/ciel-runtime",
3
- "version": "0.2.25",
4
- "description": "Universal AI coding-agent runtime and model-routing layer for Claude, Codex, AGY, and compatible runtimes.",
3
+ "version": "0.2.26",
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",
7
7
  "homepage": "https://github.com/OneCielAI/ciel-runtime#readme",