@oneciel-ai/ciel-runtime 0.2.2 → 0.2.3

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.
Files changed (126) hide show
  1. package/ciel_runtime.py +2553 -9635
  2. package/ciel_runtime_support/advisor_request_builder.py +8 -21
  3. package/ciel_runtime_support/anthropic_tool_turns.py +13 -8
  4. package/ciel_runtime_support/architecture.py +68 -0
  5. package/ciel_runtime_support/architecture_budget.py +1 -1
  6. package/ciel_runtime_support/channel_connection_context.py +233 -0
  7. package/ciel_runtime_support/channel_delivery_context.py +332 -0
  8. package/ciel_runtime_support/channel_mcp_context.py +313 -0
  9. package/ciel_runtime_support/channel_mcp_discovery.py +47 -0
  10. package/ciel_runtime_support/channel_mcp_transport.py +5 -1
  11. package/ciel_runtime_support/channel_message_context.py +212 -0
  12. package/ciel_runtime_support/channel_message_repository.py +14 -3
  13. package/ciel_runtime_support/channel_pending_injection.py +9 -0
  14. package/ciel_runtime_support/channel_probe_launch_context.py +213 -0
  15. package/ciel_runtime_support/channel_replay_policy.py +38 -0
  16. package/ciel_runtime_support/channel_runtime_environment.py +8 -0
  17. package/ciel_runtime_support/channel_session_context.py +236 -0
  18. package/ciel_runtime_support/channel_terminal_context.py +350 -0
  19. package/ciel_runtime_support/channel_wake_context.py +532 -0
  20. package/ciel_runtime_support/claude_environment.py +60 -0
  21. package/ciel_runtime_support/claude_launch_assembly.py +249 -0
  22. package/ciel_runtime_support/claude_router.py +62 -12
  23. package/ciel_runtime_support/cli_application_context.py +132 -0
  24. package/ciel_runtime_support/cli_assembly.py +50 -0
  25. package/ciel_runtime_support/codex_backend_context.py +363 -0
  26. package/ciel_runtime_support/codex_config.py +13 -1
  27. package/ciel_runtime_support/codex_launch_assembly.py +213 -0
  28. package/ciel_runtime_support/codex_launch_configuration.py +30 -1
  29. package/ciel_runtime_support/codex_mcp_integration.py +90 -8
  30. package/ciel_runtime_support/codex_model_catalog.py +4 -1
  31. package/ciel_runtime_support/codex_reasoning_rejects.py +225 -0
  32. package/ciel_runtime_support/codex_router.py +38 -8
  33. package/ciel_runtime_support/codex_turn_recovery.py +154 -0
  34. package/ciel_runtime_support/config_migrations.py +103 -0
  35. package/ciel_runtime_support/configuration_cli.py +38 -0
  36. package/ciel_runtime_support/context_compaction.py +9 -4
  37. package/ciel_runtime_support/credential_management.py +12 -0
  38. package/ciel_runtime_support/credentials.py +12 -0
  39. package/ciel_runtime_support/github_copilot_oauth.py +2 -2
  40. package/ciel_runtime_support/hosted_formula_tools.py +216 -0
  41. package/ciel_runtime_support/kimi_runtime_context.py +208 -0
  42. package/ciel_runtime_support/llm_preset_context.py +338 -0
  43. package/ciel_runtime_support/managed_mcp_config.py +8 -4
  44. package/ciel_runtime_support/mcp_configuration_context.py +291 -0
  45. package/ciel_runtime_support/mcp_http_proxy.py +14 -8
  46. package/ciel_runtime_support/mcp_probe_transport.py +47 -15
  47. package/ciel_runtime_support/mcp_transport.py +14 -1
  48. package/ciel_runtime_support/native_context_recovery.py +72 -0
  49. package/ciel_runtime_support/ollama_catalog_context.py +213 -0
  50. package/ciel_runtime_support/ollama_stream_collection.py +103 -0
  51. package/ciel_runtime_support/ollama_thinking.py +6 -1
  52. package/ciel_runtime_support/ollama_wire_projection.py +157 -0
  53. package/ciel_runtime_support/openai_forwarding.py +32 -10
  54. package/ciel_runtime_support/openai_responses_router.py +12 -0
  55. package/ciel_runtime_support/package_lifecycle.py +39 -0
  56. package/ciel_runtime_support/prelaunch_assembly.py +37 -0
  57. package/ciel_runtime_support/prelaunch_panel_context.py +418 -0
  58. package/ciel_runtime_support/prelaunch_shell_context.py +394 -0
  59. package/ciel_runtime_support/prompt_compaction.py +144 -0
  60. package/ciel_runtime_support/prompt_injection.py +45 -0
  61. package/ciel_runtime_support/protocols/anthropic_thinking_policy.py +1 -1
  62. package/ciel_runtime_support/protocols/chat_projection.py +85 -5
  63. package/ciel_runtime_support/protocols/conversation_turn_policy.py +43 -0
  64. package/ciel_runtime_support/protocols/ollama_chat.py +31 -0
  65. package/ciel_runtime_support/protocols/ollama_response.py +57 -5
  66. package/ciel_runtime_support/protocols/openai_reasoning.py +5 -2
  67. package/ciel_runtime_support/protocols/openai_responses.py +61 -15
  68. package/ciel_runtime_support/provider_adapters.py +26 -0
  69. package/ciel_runtime_support/provider_administration_context.py +207 -0
  70. package/ciel_runtime_support/provider_config_mutations.py +3 -0
  71. package/ciel_runtime_support/provider_model_catalog_context.py +137 -0
  72. package/ciel_runtime_support/provider_model_context.py +107 -0
  73. package/ciel_runtime_support/provider_model_metadata_context.py +197 -0
  74. package/ciel_runtime_support/provider_model_selection.py +10 -3
  75. package/ciel_runtime_support/provider_models.py +45 -2
  76. package/ciel_runtime_support/provider_option_cli.py +19 -0
  77. package/ciel_runtime_support/provider_policy.py +1 -1
  78. package/ciel_runtime_support/provider_readiness_context.py +189 -0
  79. package/ciel_runtime_support/provider_request_builder.py +64 -28
  80. package/ciel_runtime_support/provider_responses_passthrough.py +21 -2
  81. package/ciel_runtime_support/provider_timeout_policy.py +54 -0
  82. package/ciel_runtime_support/provider_tool_policy.py +9 -1
  83. package/ciel_runtime_support/providers/__init__.py +6 -0
  84. package/ciel_runtime_support/providers/alibaba.py +634 -0
  85. package/ciel_runtime_support/providers/catalog.py +24 -16
  86. package/ciel_runtime_support/providers/deepseek.py +73 -0
  87. package/ciel_runtime_support/providers/github_copilot_oauth.py +22 -1
  88. package/ciel_runtime_support/providers/kimi.py +69 -9
  89. package/ciel_runtime_support/providers/ollama.py +8 -0
  90. package/ciel_runtime_support/providers/ollama_context.py +21 -2
  91. package/ciel_runtime_support/providers/vllm.py +7 -1
  92. package/ciel_runtime_support/response_collection.py +68 -18
  93. package/ciel_runtime_support/response_collection_context.py +391 -0
  94. package/ciel_runtime_support/response_stream_context.py +555 -0
  95. package/ciel_runtime_support/responses_input_compatibility.py +121 -0
  96. package/ciel_runtime_support/responses_usage_observer.py +83 -0
  97. package/ciel_runtime_support/router_client_lifecycle.py +1 -0
  98. package/ciel_runtime_support/router_http.py +239 -3
  99. package/ciel_runtime_support/router_observability_context.py +251 -0
  100. package/ciel_runtime_support/router_process_context.py +200 -0
  101. package/ciel_runtime_support/router_process_lifecycle.py +2 -0
  102. package/ciel_runtime_support/router_request_assembly.py +399 -0
  103. package/ciel_runtime_support/router_request_context.py +215 -0
  104. package/ciel_runtime_support/router_server_context.py +82 -0
  105. package/ciel_runtime_support/runaway_output_guard.py +488 -0
  106. package/ciel_runtime_support/runtime_asset_assembly.py +147 -0
  107. package/ciel_runtime_support/runtime_asset_context.py +297 -0
  108. package/ciel_runtime_support/runtime_constants.py +16 -1
  109. package/ciel_runtime_support/runtime_launch.py +9 -5
  110. package/ciel_runtime_support/runtime_launch_context.py +130 -0
  111. package/ciel_runtime_support/runtime_maintenance_assembly.py +60 -0
  112. package/ciel_runtime_support/runtime_maintenance_context.py +309 -0
  113. package/ciel_runtime_support/runtime_maintenance_services.py +265 -0
  114. package/ciel_runtime_support/runtime_paths.py +60 -40
  115. package/ciel_runtime_support/runtime_primitives.py +78 -0
  116. package/ciel_runtime_support/sse_stream_collection.py +236 -0
  117. package/ciel_runtime_support/statusline_script.py +57 -8
  118. package/ciel_runtime_support/streaming_anthropic.py +361 -24
  119. package/ciel_runtime_support/tool_schema.py +40 -2
  120. package/ciel_runtime_support/tool_side_effect_dedupe.py +117 -12
  121. package/ciel_runtime_support/upstream_dump.py +68 -0
  122. package/ciel_runtime_support/upstream_retry_context.py +259 -0
  123. package/ciel_runtime_support/workspace_router_selection.py +86 -0
  124. package/docs/Configuration.md +50 -0
  125. package/docs/Test-Suite.md +1 -0
  126. package/package.json +1 -1
@@ -0,0 +1,297 @@
1
+ """Executable discovery and runtime-owned command asset installation context."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any, Callable, Mapping
8
+
9
+ from .command_asset_installer import CommandAsset, CommandAssetInstaller
10
+ from .executable_discovery import ExecutableDiscovery
11
+ from .statusline_settings import StatusLineServices, install_statusline_settings
12
+ from .tool_guard_hooks import (
13
+ LegacyToolGuardShimInstaller,
14
+ LegacyToolGuardShimServices,
15
+ ToolGuardHookServices,
16
+ install_tool_guard_hook_settings,
17
+ )
18
+
19
+
20
+ @dataclass(frozen=True, slots=True)
21
+ class RuntimeExecutablePaths:
22
+ home: Path
23
+ source_file: Path
24
+ platform_path: Callable[[], str]
25
+ runtime_bin_dir: Callable[[], Path]
26
+ agy_bin_dir: Callable[[], Path]
27
+ python_executable: str
28
+
29
+
30
+ @dataclass(frozen=True, slots=True)
31
+ class RuntimeAssetPaths:
32
+ package_root: Path
33
+ statusline_path: Path
34
+ statusline_script: Path
35
+ claude_commands_dir: Path
36
+ codex_prompts_dir_name: str
37
+
38
+
39
+ @dataclass(frozen=True, slots=True)
40
+ class RuntimeAssetEffects:
41
+ settings_repository: Callable[[], Any]
42
+ chmod: Callable[[Path, int], None]
43
+ log: Callable[[str, str], None]
44
+ warn: Callable[[str], None]
45
+ environ: Mapping[str, str]
46
+
47
+
48
+ @dataclass(frozen=True, slots=True)
49
+ class RuntimeCommandAssetCatalog:
50
+ standard: dict[str, CommandAsset]
51
+ advisor: CommandAsset
52
+ advisor_markers: tuple[str, ...]
53
+ import_session: CommandAsset
54
+ import_markers: tuple[str, ...]
55
+ stale_markers: tuple[str, ...]
56
+
57
+
58
+ @dataclass(frozen=True, slots=True)
59
+ class RuntimeToolGuardPolicy:
60
+ default_hook_policy: Any
61
+
62
+
63
+ @dataclass(frozen=True, slots=True)
64
+ class RuntimeAssetCompatibilityPorts:
65
+ find_executable: Callable[[str], str | None]
66
+ find_tool_guard: Callable[[], Path | None]
67
+ tool_guard_command: Callable[[], str | None]
68
+ install_legacy_shim: Callable[[], None]
69
+ command_asset_installer: Callable[[Path], CommandAssetInstaller]
70
+ remove_advisor_command: Callable[[], None]
71
+ codex_prompts_dir: Callable[[Mapping[str, str] | None], Path]
72
+ command_assets: Callable[[bool], dict[str, CommandAsset]]
73
+
74
+
75
+ @dataclass(frozen=True, slots=True)
76
+ class RuntimeAssetContext:
77
+ executable: RuntimeExecutablePaths
78
+ paths: RuntimeAssetPaths
79
+ effects: RuntimeAssetEffects
80
+ catalog: RuntimeCommandAssetCatalog
81
+ tool_guard: RuntimeToolGuardPolicy
82
+ compatibility: RuntimeAssetCompatibilityPorts
83
+
84
+ def executable_discovery(self) -> ExecutableDiscovery:
85
+ return ExecutableDiscovery(
86
+ self.executable.home,
87
+ self.executable.source_file,
88
+ self.executable.platform_path,
89
+ self.executable.runtime_bin_dir,
90
+ self.executable.agy_bin_dir,
91
+ )
92
+
93
+ def executable_extra_dirs(self) -> list[Path]:
94
+ return self.executable_discovery().extra_dirs()
95
+
96
+ def find_executable(self, name: str) -> str | None:
97
+ return self.executable_discovery().find(name)
98
+
99
+ def resolve_executable(self, command: str) -> str:
100
+ return self.executable_discovery().resolve(command)
101
+
102
+ def resolve_mcp_process(
103
+ self, command: str, args: list[str]
104
+ ) -> tuple[str, list[str]]:
105
+ return self.executable_discovery().resolve_mcp_process(
106
+ command, args, self.compatibility.find_executable
107
+ )
108
+
109
+ @staticmethod
110
+ def shell_command(args: list[str]) -> str:
111
+ return ExecutableDiscovery.shell_command(args)
112
+
113
+ def find_tool_guard(self) -> Path | None:
114
+ return self.executable_discovery().find_tool_guard(self.find_executable)
115
+
116
+ def tool_guard_command(self) -> str | None:
117
+ script = self.compatibility.find_tool_guard()
118
+ if script is None:
119
+ return None
120
+ command = (
121
+ [self.executable.python_executable, str(script)]
122
+ if script.suffix == ".py"
123
+ else [str(script)]
124
+ )
125
+ return self.shell_command(command)
126
+
127
+ def install_legacy_tool_guard_shim(self) -> None:
128
+ LegacyToolGuardShimInstaller(
129
+ LegacyToolGuardShimServices(
130
+ package_root=self.paths.package_root,
131
+ find_target=self.compatibility.find_tool_guard,
132
+ chmod=self.effects.chmod,
133
+ log=self.effects.log,
134
+ )
135
+ ).install()
136
+
137
+ def install_tool_guard_hooks(self) -> None:
138
+ install_tool_guard_hook_settings(
139
+ self.compatibility.tool_guard_command(),
140
+ self.tool_guard.default_hook_policy,
141
+ ToolGuardHookServices(
142
+ repository=self.effects.settings_repository(),
143
+ install_legacy_shim=self.compatibility.install_legacy_shim,
144
+ warn=self.effects.warn,
145
+ ),
146
+ )
147
+
148
+ def install_statusline(self) -> None:
149
+ install_statusline_settings(
150
+ self.paths.statusline_path,
151
+ self.paths.statusline_script,
152
+ self.executable.python_executable,
153
+ StatusLineServices(
154
+ repository=self.effects.settings_repository(),
155
+ warn=self.effects.warn,
156
+ ),
157
+ )
158
+
159
+ def command_asset_installer(self, directory: Path) -> CommandAssetInstaller:
160
+ return CommandAssetInstaller(directory, self.effects.warn)
161
+
162
+ def remove_advisor_command(self) -> None:
163
+ self.compatibility.command_asset_installer(
164
+ self.paths.claude_commands_dir
165
+ ).remove_one(
166
+ "advisor.md", self.catalog.advisor_markers
167
+ )
168
+
169
+ def codex_prompts_dir(
170
+ self, env: Mapping[str, str] | None = None
171
+ ) -> Path:
172
+ environment = env or self.effects.environ
173
+ raw_home = environment.get("CODEX_HOME")
174
+ home = (
175
+ Path(raw_home).expanduser()
176
+ if raw_home
177
+ else self.executable.home / ".codex"
178
+ )
179
+ return home / self.paths.codex_prompts_dir_name
180
+
181
+ def install_codex_prompts(
182
+ self, env: Mapping[str, str] | None = None
183
+ ) -> None:
184
+ self.compatibility.command_asset_installer(
185
+ self.compatibility.codex_prompts_dir(env)
186
+ ).install_one(
187
+ "ImportSession.md", self.catalog.import_session
188
+ )
189
+
190
+ def disable_codex_prompts(
191
+ self, env: Mapping[str, str] | None = None
192
+ ) -> None:
193
+ self.compatibility.command_asset_installer(
194
+ self.compatibility.codex_prompts_dir(env)
195
+ ).remove_one(
196
+ "ImportSession.md", self.catalog.import_markers
197
+ )
198
+
199
+ def command_assets(self, include_advisor: bool = True) -> dict[str, CommandAsset]:
200
+ assets = dict(self.catalog.standard)
201
+ if include_advisor:
202
+ assets["advisor.md"] = self.catalog.advisor
203
+ return assets
204
+
205
+ def install_slash_commands(self, include_advisor: bool = True) -> None:
206
+ if not include_advisor:
207
+ self.compatibility.remove_advisor_command()
208
+ self.compatibility.command_asset_installer(
209
+ self.paths.claude_commands_dir
210
+ ).install_all(
211
+ self.compatibility.command_assets(include_advisor),
212
+ stale_glob="llm-*.md",
213
+ stale_markers=self.catalog.stale_markers,
214
+ )
215
+
216
+ def disable_slash_commands(self) -> None:
217
+ self.compatibility.command_asset_installer(
218
+ self.paths.claude_commands_dir
219
+ ).remove_all(
220
+ self.compatibility.command_assets(True),
221
+ stale_glob="llm-*.md",
222
+ stale_markers=self.catalog.stale_markers,
223
+ )
224
+
225
+
226
+ @dataclass(frozen=True, slots=True)
227
+ class RuntimeAssetCompatibilityApi:
228
+ context: Callable[[], RuntimeAssetContext]
229
+
230
+ def executable_discovery(self) -> ExecutableDiscovery:
231
+ return self.context().executable_discovery()
232
+
233
+ def executable_extra_dirs(self) -> list[Path]:
234
+ return self.context().executable_extra_dirs()
235
+
236
+ def find_executable(self, name: str) -> str | None:
237
+ return self.context().find_executable(name)
238
+
239
+ def resolve_executable(self, command: str) -> str:
240
+ return self.context().resolve_executable(command)
241
+
242
+ def resolve_mcp_process(self, *args: Any, **kwargs: Any) -> tuple[str, list[str]]:
243
+ return self.context().resolve_mcp_process(*args, **kwargs)
244
+
245
+ def shell_command(self, args: list[str]) -> str:
246
+ return self.context().shell_command(args)
247
+
248
+ def find_tool_guard(self) -> Path | None:
249
+ return self.context().find_tool_guard()
250
+
251
+ def tool_guard_command(self) -> str | None:
252
+ return self.context().tool_guard_command()
253
+
254
+ def install_legacy_tool_guard_shim(self) -> None:
255
+ self.context().install_legacy_tool_guard_shim()
256
+
257
+ def install_tool_guard_hooks(self) -> None:
258
+ self.context().install_tool_guard_hooks()
259
+
260
+ def install_statusline(self) -> None:
261
+ self.context().install_statusline()
262
+
263
+ def command_asset_installer(self, directory: Path) -> CommandAssetInstaller:
264
+ return self.context().command_asset_installer(directory)
265
+
266
+ def remove_advisor_command(self) -> None:
267
+ self.context().remove_advisor_command()
268
+
269
+ def codex_prompts_dir(self, *args: Any, **kwargs: Any) -> Path:
270
+ return self.context().codex_prompts_dir(*args, **kwargs)
271
+
272
+ def install_codex_prompts(self, *args: Any, **kwargs: Any) -> None:
273
+ self.context().install_codex_prompts(*args, **kwargs)
274
+
275
+ def disable_codex_prompts(self, *args: Any, **kwargs: Any) -> None:
276
+ self.context().disable_codex_prompts(*args, **kwargs)
277
+
278
+ def command_assets(self, include_advisor: bool = True) -> dict[str, CommandAsset]:
279
+ return self.context().command_assets(include_advisor)
280
+
281
+ def install_slash_commands(self, include_advisor: bool = True) -> None:
282
+ self.context().install_slash_commands(include_advisor)
283
+
284
+ def disable_slash_commands(self) -> None:
285
+ self.context().disable_slash_commands()
286
+
287
+
288
+ __all__ = [
289
+ "RuntimeAssetCompatibilityApi",
290
+ "RuntimeAssetCompatibilityPorts",
291
+ "RuntimeAssetContext",
292
+ "RuntimeAssetEffects",
293
+ "RuntimeAssetPaths",
294
+ "RuntimeCommandAssetCatalog",
295
+ "RuntimeExecutablePaths",
296
+ "RuntimeToolGuardPolicy",
297
+ ]
@@ -93,7 +93,7 @@ OFFICIAL_CHANNEL_PLUGINS = {
93
93
  }
94
94
 
95
95
  APP_NAME = "Ciel Runtime"
96
- VERSION = "0.2.2"
96
+ VERSION = "0.2.3"
97
97
  CREDITS = "Credits: One Ciel LLC"
98
98
  PRELAUNCH_CANCEL = 10
99
99
  PRELAUNCH_LAUNCH_CODEX = 11
@@ -159,6 +159,21 @@ ROUTED_COMPAT_PROMPT = (
159
159
  "or report the concrete connection state. Never write pseudo tool calls, partial JSON, or markdown code fences when a real Claude Code tool call is required."
160
160
  )
161
161
  NON_ANTHROPIC_COMPAT_PROMPT = ROUTED_COMPAT_PROMPT
162
+ # ROUTED_COMPAT_PROMPT is Claude Code specific -- it names Claude Code tools and
163
+ # is delivered with --append-system-prompt, a flag Codex classifies as Claude-only.
164
+ # Routed Codex sessions therefore had no equivalent instruction and would end a
165
+ # turn right after announcing what they were about to do. This is the Codex
166
+ # wording, appended to the Responses `instructions` field instead of a CLI flag.
167
+ # Keep it constant: it sits at the head of the prompt, so any variation would
168
+ # invalidate the upstream prefix cache on every turn.
169
+ ROUTED_CODEX_COMPAT_PROMPT = (
170
+ "You are running inside Codex CLI through the ciel-runtime router. Do not stop after announcing what you plan to do. "
171
+ "When the user asks you to inspect, create, edit, or run code, call the tools available in this turn and report the concrete result. "
172
+ "Never end a turn with only a statement of intent such as saying you will now start; perform the work in the same turn. "
173
+ "If the task has several reasonable in-scope parts, do all of them; do not ask which part to start unless the user explicitly asked for a choice. "
174
+ "If you decide not to use tools, give the complete answer in the same turn. "
175
+ "Use exactly the tool schema provided and do not invent extra fields, and never write pseudo tool calls or partial JSON where a real tool call is required."
176
+ )
162
177
  LANGUAGES = {"en": "English", "ko": "한국어", "ja": "日本語", "zh": "中文"}
163
178
  MODEL_PRESETS: dict[str, dict[str, Any]] = {
164
179
  "deepseek-v4-flash:0731": {"compat_max_tokens": 64, "thinking": True, "num_ctx_min": 32768, "num_ctx_max": 1000000},
@@ -18,7 +18,7 @@ from ciel_runtime_support.runtime_constants import (
18
18
  PRELAUNCH_LAUNCH_CODEX_APP_SERVER,
19
19
  ROUTED_COMPAT_PROMPT,
20
20
  )
21
- from ciel_runtime_support.runtime_paths import CONFIG_DIR, LOG_PATH
21
+ from ciel_runtime_support.runtime_paths import CONFIG_DIR, LOG_PATH, ROUTER_INSTANCE_DIR
22
22
  from ciel_runtime_support.web_endpoints import web_backend_settings
23
23
 
24
24
 
@@ -160,7 +160,6 @@ class ClaudeLaunchChannelDelivery:
160
160
  claude_channel_args: Callable[..., Any]
161
161
  native_channel_passthrough_requested: Callable[..., Any]
162
162
  normalize_channel_passthrough: Callable[..., Any]
163
- prepare_channel_llm_delivery_for_launch: Callable[..., Any]
164
163
  should_launch_process_start_channel_sse: Callable[..., Any]
165
164
  should_use_channel_llm_delivery: Callable[..., Any]
166
165
  should_use_channel_stdin_proxy: Callable[..., Any]
@@ -255,7 +254,6 @@ def run_claude(
255
254
  native_channel_passthrough_requested = services.channel_delivery.native_channel_passthrough_requested
256
255
  normalize_channel_passthrough = services.channel_delivery.normalize_channel_passthrough
257
256
  path_with_ciel_runtime_user_dirs = services.process.path_with_ciel_runtime_user_dirs
258
- prepare_channel_llm_delivery_for_launch = services.channel_delivery.prepare_channel_llm_delivery_for_launch
259
257
  print_routed_claude_exit_diagnostics = services.process.print_routed_claude_exit_diagnostics
260
258
  provider_menu_label = services.config.provider_menu_label
261
259
  read_channel_probe_cache = services.channel_discovery.read_channel_probe_cache
@@ -347,6 +345,9 @@ def run_claude(
347
345
  )
348
346
  cleanup_managed_services_for_provider(provider, pcfg, cfg, quiet=True)
349
347
  env = os.environ.copy()
348
+ env["CIEL_RUNTIME_CONFIG_DIR"] = str(CONFIG_DIR)
349
+ env["CIEL_RUNTIME_STATE_DIR"] = str(ROUTER_INSTANCE_DIR)
350
+ env["CIEL_RUNTIME_LAUNCH_CWD"] = str(Path.cwd())
350
351
  env["PATH"] = path_with_ciel_runtime_user_dirs(env)
351
352
  launch_passthrough = normalize_channel_passthrough(passthrough)
352
353
  native_channel_bridge = should_use_native_channel_bridge(use_router_mode, cfg, launch_passthrough)
@@ -513,8 +514,6 @@ def run_claude(
513
514
  mcp_config_paths.extend(native_direct_mcp_config_paths)
514
515
  claude_passthrough = strip_mcp_config_passthrough(launch_passthrough)
515
516
  elif stdin_channel_proxy or llm_channel_delivery or native_auto_channel_specs:
516
- if llm_channel_delivery:
517
- prepare_channel_llm_delivery_for_launch()
518
517
  if should_launch_process_start_channel_sse(stdin_channel_proxy, native_channel_bridge, llm_channel_delivery):
519
518
  auto_start_sse_channels_from_mcp_configs(
520
519
  launch_passthrough,
@@ -829,6 +828,7 @@ def run_codex(
829
828
  warn_if_multiple_ciel_runtime_installs()
830
829
  run_ciel_runtime_update_check(enabled=self_update_check)
831
830
  env = os.environ.copy()
831
+ env["CIEL_RUNTIME_STATE_DIR"] = str(ROUTER_INSTANCE_DIR)
832
832
  env["PATH"] = path_with_ciel_runtime_user_dirs(env)
833
833
  codex = install_codex_if_missing()
834
834
  if not codex:
@@ -1172,6 +1172,7 @@ def run_codex_app_server(
1172
1172
  warn_if_multiple_ciel_runtime_installs()
1173
1173
  run_ciel_runtime_update_check(enabled=self_update_check)
1174
1174
  env = os.environ.copy()
1175
+ env["CIEL_RUNTIME_STATE_DIR"] = str(ROUTER_INSTANCE_DIR)
1175
1176
  env["PATH"] = path_with_ciel_runtime_user_dirs(env)
1176
1177
  codex = install_codex_if_missing()
1177
1178
  if not codex:
@@ -1453,6 +1454,9 @@ def run_agy(
1453
1454
  warn_if_multiple_ciel_runtime_installs()
1454
1455
  run_ciel_runtime_update_check(enabled=self_update_check)
1455
1456
  env = os.environ.copy()
1457
+ env["CIEL_RUNTIME_CONFIG_DIR"] = str(CONFIG_DIR)
1458
+ env["CIEL_RUNTIME_STATE_DIR"] = str(ROUTER_INSTANCE_DIR)
1459
+ env["CIEL_RUNTIME_LAUNCH_CWD"] = str(Path.cwd())
1456
1460
  env["PATH"] = path_with_ciel_runtime_user_dirs(env)
1457
1461
  agy = install_agy_if_missing()
1458
1462
  if not agy:
@@ -0,0 +1,130 @@
1
+ """Runtime launch dispatch context with explicitly supplied service factories."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, Callable
7
+
8
+ from .runtime_launch import (
9
+ AgyLaunchServices,
10
+ ClaudeLaunchServices,
11
+ CodexAppServerLaunchServices,
12
+ CodexLaunchServices,
13
+ )
14
+
15
+
16
+ @dataclass(frozen=True, slots=True)
17
+ class RuntimeLaunchRunners:
18
+ claude: Callable[..., int]
19
+ codex: Callable[..., int]
20
+ codex_app_server: Callable[..., int]
21
+ agy: Callable[..., int]
22
+
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class RuntimeLaunchServiceFactories:
26
+ claude: Callable[[], ClaudeLaunchServices]
27
+ codex: Callable[[], CodexLaunchServices]
28
+ codex_app_server: Callable[[], CodexAppServerLaunchServices]
29
+ agy: Callable[[], AgyLaunchServices]
30
+
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class RuntimeLaunchContext:
34
+ runners: RuntimeLaunchRunners
35
+ services: RuntimeLaunchServiceFactories
36
+
37
+ def launch_claude(
38
+ self,
39
+ passthrough: list[str],
40
+ skip_menu: bool = False,
41
+ force_menu: bool = False,
42
+ web_search_override: bool | None = None,
43
+ update_check: bool = True,
44
+ self_update_check: bool = True,
45
+ ) -> int:
46
+ return self.runners.claude(
47
+ passthrough,
48
+ skip_menu=skip_menu,
49
+ force_menu=force_menu,
50
+ web_search_override=web_search_override,
51
+ update_check=update_check,
52
+ self_update_check=self_update_check,
53
+ services=self.services.claude(),
54
+ )
55
+
56
+ def launch_codex(
57
+ self,
58
+ passthrough: list[str],
59
+ skip_menu: bool = False,
60
+ force_menu: bool = False,
61
+ update_check: bool = True,
62
+ self_update_check: bool = True,
63
+ ) -> int:
64
+ return self.runners.codex(
65
+ passthrough,
66
+ skip_menu=skip_menu,
67
+ force_menu=force_menu,
68
+ update_check=update_check,
69
+ self_update_check=self_update_check,
70
+ services=self.services.codex(),
71
+ )
72
+
73
+ def launch_codex_app_server(
74
+ self,
75
+ passthrough: list[str],
76
+ skip_menu: bool = True,
77
+ force_menu: bool = False,
78
+ update_check: bool = True,
79
+ self_update_check: bool = True,
80
+ ) -> int:
81
+ return self.runners.codex_app_server(
82
+ passthrough,
83
+ skip_menu=skip_menu,
84
+ force_menu=force_menu,
85
+ update_check=update_check,
86
+ self_update_check=self_update_check,
87
+ services=self.services.codex_app_server(),
88
+ )
89
+
90
+ def launch_agy(
91
+ self,
92
+ passthrough: list[str],
93
+ skip_menu: bool = False,
94
+ force_menu: bool = False,
95
+ update_check: bool = True,
96
+ self_update_check: bool = True,
97
+ ) -> int:
98
+ return self.runners.agy(
99
+ passthrough,
100
+ skip_menu=skip_menu,
101
+ force_menu=force_menu,
102
+ update_check=update_check,
103
+ self_update_check=self_update_check,
104
+ services=self.services.agy(),
105
+ )
106
+
107
+
108
+ @dataclass(frozen=True, slots=True)
109
+ class RuntimeLaunchCompatibilityApi:
110
+ context: Callable[[], RuntimeLaunchContext]
111
+
112
+ def launch_claude(self, *args: Any, **kwargs: Any) -> int:
113
+ return self.context().launch_claude(*args, **kwargs)
114
+
115
+ def launch_codex(self, *args: Any, **kwargs: Any) -> int:
116
+ return self.context().launch_codex(*args, **kwargs)
117
+
118
+ def launch_codex_app_server(self, *args: Any, **kwargs: Any) -> int:
119
+ return self.context().launch_codex_app_server(*args, **kwargs)
120
+
121
+ def launch_agy(self, *args: Any, **kwargs: Any) -> int:
122
+ return self.context().launch_agy(*args, **kwargs)
123
+
124
+
125
+ __all__ = [
126
+ "RuntimeLaunchCompatibilityApi",
127
+ "RuntimeLaunchContext",
128
+ "RuntimeLaunchRunners",
129
+ "RuntimeLaunchServiceFactories",
130
+ ]
@@ -0,0 +1,60 @@
1
+ """Composition boundary for runtime installation and maintenance services."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Callable, Mapping
7
+
8
+ from .runtime_maintenance_context import (
9
+ RuntimeAgyPorts,
10
+ RuntimeLifecyclePorts,
11
+ RuntimeMaintenanceContext,
12
+ RuntimePackagePorts,
13
+ RuntimeUpgradeCommandPorts,
14
+ )
15
+ from .runtime_maintenance_services import RuntimeMaintenanceServices
16
+
17
+
18
+ @dataclass(frozen=True, slots=True)
19
+ class RuntimeMaintenanceCommandPorts:
20
+ environment: Mapping[str, str]
21
+ claude_version: Callable[[str], str]
22
+ codex_version: Callable[[str], str]
23
+ upgrade_runtime: Callable[[], int]
24
+ upgrade_claude: Callable[[], int]
25
+ upgrade_codex: Callable[[], int]
26
+ upgrade_agy: Callable[[], int]
27
+
28
+
29
+ @dataclass(frozen=True, slots=True)
30
+ class RuntimeMaintenanceAssembly:
31
+ service_graph: RuntimeMaintenanceServices
32
+ commands: RuntimeMaintenanceCommandPorts
33
+
34
+ def context(self) -> RuntimeMaintenanceContext:
35
+ services = self.service_graph
36
+ commands = self.commands
37
+ return RuntimeMaintenanceContext(
38
+ packages=RuntimePackagePorts(
39
+ services.npm_lifecycle,
40
+ commands.environment,
41
+ commands.claude_version,
42
+ commands.codex_version,
43
+ ),
44
+ lifecycle=RuntimeLifecyclePorts(
45
+ services.install_diagnostics,
46
+ services.restart_service,
47
+ services.self_update,
48
+ services.upgrade,
49
+ ),
50
+ agy=RuntimeAgyPorts(services.agy_installer),
51
+ upgrade_commands=RuntimeUpgradeCommandPorts(
52
+ commands.upgrade_runtime,
53
+ commands.upgrade_claude,
54
+ commands.upgrade_codex,
55
+ commands.upgrade_agy,
56
+ ),
57
+ )
58
+
59
+
60
+ __all__ = ["RuntimeMaintenanceAssembly", "RuntimeMaintenanceCommandPorts"]