@oneciel-ai/ciel-runtime 0.1.1 → 0.2.0

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 (287) hide show
  1. package/README.md +132 -0
  2. package/ciel-runtime-menu.py +56 -6
  3. package/ciel_runtime.py +9379 -35909
  4. package/ciel_runtime_support/advisor_client.py +193 -0
  5. package/ciel_runtime_support/advisor_policy.py +320 -0
  6. package/ciel_runtime_support/advisor_refinement.py +160 -0
  7. package/ciel_runtime_support/advisor_request_builder.py +261 -0
  8. package/ciel_runtime_support/agy_installer.py +169 -0
  9. package/ciel_runtime_support/agy_mcp_restore.py +182 -0
  10. package/ciel_runtime_support/anthropic_model_policy.py +186 -0
  11. package/ciel_runtime_support/anthropic_response_writer.py +255 -0
  12. package/ciel_runtime_support/anthropic_tool_turns.py +130 -0
  13. package/ciel_runtime_support/api_key_cooldown.py +159 -0
  14. package/ciel_runtime_support/architecture.py +488 -1
  15. package/ciel_runtime_support/architecture_budget.py +42 -0
  16. package/ciel_runtime_support/channel_backlog.py +90 -0
  17. package/ciel_runtime_support/channel_cli.py +119 -0
  18. package/ciel_runtime_support/channel_compact_injection.py +82 -0
  19. package/ciel_runtime_support/channel_compact_poll.py +67 -0
  20. package/ciel_runtime_support/channel_compact_request_repository.py +113 -0
  21. package/ciel_runtime_support/channel_config_service.py +281 -0
  22. package/ciel_runtime_support/channel_connection_lifecycle.py +180 -0
  23. package/ciel_runtime_support/channel_connection_registry.py +128 -0
  24. package/ciel_runtime_support/channel_connection_worker.py +284 -0
  25. package/ciel_runtime_support/channel_cursor_recovery.py +92 -0
  26. package/ciel_runtime_support/channel_cursor_repository.py +89 -0
  27. package/ciel_runtime_support/channel_cursor_service.py +178 -0
  28. package/ciel_runtime_support/channel_event_identity.py +212 -0
  29. package/ciel_runtime_support/channel_event_projection.py +315 -0
  30. package/ciel_runtime_support/channel_inflight.py +127 -0
  31. package/ciel_runtime_support/channel_injection.py +115 -0
  32. package/ciel_runtime_support/channel_launch_guard_repository.py +58 -0
  33. package/ciel_runtime_support/channel_launch_policy.py +180 -0
  34. package/ciel_runtime_support/channel_llm_context.py +156 -0
  35. package/ciel_runtime_support/channel_mcp_discovery.py +186 -0
  36. package/ciel_runtime_support/channel_mcp_http_controller.py +239 -0
  37. package/ciel_runtime_support/channel_mcp_ownership.py +148 -0
  38. package/ciel_runtime_support/channel_mcp_tools.py +240 -0
  39. package/ciel_runtime_support/channel_mcp_transport.py +394 -0
  40. package/ciel_runtime_support/channel_message_dedupe.py +65 -0
  41. package/ciel_runtime_support/channel_message_policy.py +256 -0
  42. package/ciel_runtime_support/channel_message_prompt.py +305 -0
  43. package/ciel_runtime_support/channel_message_repository.py +234 -0
  44. package/ciel_runtime_support/channel_notification_projection.py +217 -0
  45. package/ciel_runtime_support/channel_panel.py +162 -0
  46. package/ciel_runtime_support/channel_pending_injection.py +209 -0
  47. package/ciel_runtime_support/channel_pending_poll.py +109 -0
  48. package/ciel_runtime_support/channel_probe_cache.py +433 -0
  49. package/ciel_runtime_support/channel_probe_report.py +101 -0
  50. package/ciel_runtime_support/channel_runtime_environment.py +181 -0
  51. package/ciel_runtime_support/channel_session_lifecycle.py +114 -0
  52. package/ciel_runtime_support/channel_session_repository.py +90 -0
  53. package/ciel_runtime_support/channel_terminal_dispatch.py +115 -0
  54. package/ciel_runtime_support/channel_terminal_input.py +277 -0
  55. package/ciel_runtime_support/channel_terminal_proxy.py +447 -0
  56. package/ciel_runtime_support/channel_tool_context.py +166 -0
  57. package/ciel_runtime_support/channel_transcript.py +414 -0
  58. package/ciel_runtime_support/channel_transcript_repository.py +96 -0
  59. package/ciel_runtime_support/channel_wake_claim_repository.py +126 -0
  60. package/ciel_runtime_support/channel_wake_delivery_repository.py +88 -0
  61. package/ciel_runtime_support/chat_files.py +138 -0
  62. package/ciel_runtime_support/chat_http_controller.py +235 -0
  63. package/ciel_runtime_support/claude_environment.py +375 -0
  64. package/ciel_runtime_support/claude_router.py +247 -193
  65. package/ciel_runtime_support/cli_dispatch.py +792 -0
  66. package/ciel_runtime_support/cli_parser.py +165 -0
  67. package/ciel_runtime_support/cli_usage.py +100 -0
  68. package/ciel_runtime_support/codex_app_server.py +20 -5
  69. package/ciel_runtime_support/codex_channel_sse_launch.py +87 -0
  70. package/ciel_runtime_support/codex_cli.py +42 -6
  71. package/ciel_runtime_support/codex_config.py +323 -0
  72. package/ciel_runtime_support/codex_launch_configuration.py +240 -0
  73. package/ciel_runtime_support/codex_launch_policy.py +66 -0
  74. package/ciel_runtime_support/codex_mcp_integration.py +195 -0
  75. package/ciel_runtime_support/codex_mcp_restore.py +304 -0
  76. package/ciel_runtime_support/codex_model_catalog.py +133 -0
  77. package/ciel_runtime_support/codex_process_lifecycle.py +271 -0
  78. package/ciel_runtime_support/codex_router.py +147 -1
  79. package/ciel_runtime_support/codex_session_repository.py +115 -0
  80. package/ciel_runtime_support/codex_session_selection.py +114 -0
  81. package/ciel_runtime_support/command_asset_installer.py +103 -0
  82. package/ciel_runtime_support/compatibility_probe.py +295 -0
  83. package/ciel_runtime_support/compatibility_protocol.py +251 -0
  84. package/ciel_runtime_support/compatibility_runtime.py +166 -0
  85. package/ciel_runtime_support/compatibility_test.py +370 -0
  86. package/ciel_runtime_support/config_migrations.py +307 -0
  87. package/ciel_runtime_support/config_repository.py +175 -0
  88. package/ciel_runtime_support/config_value_codec.py +64 -0
  89. package/ciel_runtime_support/configuration_cli.py +374 -0
  90. package/ciel_runtime_support/context_compaction.py +280 -0
  91. package/ciel_runtime_support/context_setup.py +208 -0
  92. package/ciel_runtime_support/context_summary_policy.py +392 -0
  93. package/ciel_runtime_support/credential_cli.py +104 -0
  94. package/ciel_runtime_support/credential_management.py +261 -0
  95. package/ciel_runtime_support/credentials.py +269 -0
  96. package/ciel_runtime_support/executable_discovery.py +141 -0
  97. package/ciel_runtime_support/github_copilot_oauth.py +335 -0
  98. package/ciel_runtime_support/github_copilot_oauth_runtime.py +213 -0
  99. package/ciel_runtime_support/header_forwarding.py +73 -0
  100. package/ciel_runtime_support/headless_config.py +221 -0
  101. package/ciel_runtime_support/http_response.py +129 -0
  102. package/ciel_runtime_support/install_diagnostics.py +149 -0
  103. package/ciel_runtime_support/kimi_identity.py +123 -0
  104. package/ciel_runtime_support/launch_diagnostics.py +204 -0
  105. package/ciel_runtime_support/launch_state.py +127 -0
  106. package/ciel_runtime_support/live_api_key_controller.py +58 -0
  107. package/ciel_runtime_support/llm_config_http.py +148 -0
  108. package/ciel_runtime_support/llm_option_config.py +259 -0
  109. package/ciel_runtime_support/llm_presentation_data.py +447 -0
  110. package/ciel_runtime_support/llm_presets.py +773 -0
  111. package/ciel_runtime_support/lm_studio_runtime.py +401 -0
  112. package/ciel_runtime_support/managed_mcp_config.py +144 -0
  113. package/ciel_runtime_support/managed_mcp_discovery.py +151 -0
  114. package/ciel_runtime_support/managed_service_cleanup.py +89 -0
  115. package/ciel_runtime_support/mcp_config_reader.py +230 -0
  116. package/ciel_runtime_support/mcp_http_proxy.py +607 -0
  117. package/ciel_runtime_support/mcp_inventory.py +59 -0
  118. package/ciel_runtime_support/mcp_notification_wait_policy.py +159 -0
  119. package/ciel_runtime_support/mcp_probe_codec.py +136 -0
  120. package/ciel_runtime_support/mcp_probe_transport.py +328 -0
  121. package/ciel_runtime_support/mcp_proxy_codec.py +345 -0
  122. package/ciel_runtime_support/mcp_proxy_config.py +107 -0
  123. package/ciel_runtime_support/mcp_proxy_notifications.py +261 -0
  124. package/ciel_runtime_support/mcp_proxy_process.py +560 -0
  125. package/ciel_runtime_support/mcp_split_proxy_http.py +165 -0
  126. package/ciel_runtime_support/mcp_stdio_probe.py +240 -0
  127. package/ciel_runtime_support/mcp_transport.py +146 -0
  128. package/ciel_runtime_support/model_cache_lifecycle.py +78 -0
  129. package/ciel_runtime_support/model_catalog_projection.py +61 -0
  130. package/ciel_runtime_support/model_context_hints.py +109 -0
  131. package/ciel_runtime_support/model_panel.py +147 -0
  132. package/ciel_runtime_support/model_registry_repository.py +231 -0
  133. package/ciel_runtime_support/npm_runtime.py +191 -0
  134. package/ciel_runtime_support/ollama_catalog.py +462 -0
  135. package/ciel_runtime_support/ollama_catalog_cli.py +41 -0
  136. package/ciel_runtime_support/ollama_catalog_repository.py +75 -0
  137. package/ciel_runtime_support/ollama_context_sync.py +87 -0
  138. package/ciel_runtime_support/ollama_forwarding.py +449 -0
  139. package/ciel_runtime_support/openai_chat_passthrough.py +67 -0
  140. package/ciel_runtime_support/openai_chat_router.py +64 -0
  141. package/ciel_runtime_support/openai_forwarding.py +194 -0
  142. package/ciel_runtime_support/openai_responses_router.py +291 -0
  143. package/ciel_runtime_support/openai_responses_stream.py +135 -0
  144. package/ciel_runtime_support/output_budget.py +89 -0
  145. package/ciel_runtime_support/package_lifecycle.py +217 -0
  146. package/ciel_runtime_support/plan_artifact_controller.py +104 -0
  147. package/ciel_runtime_support/prelaunch.py +959 -0
  148. package/ciel_runtime_support/prelaunch_launch_preference.py +75 -0
  149. package/ciel_runtime_support/prelaunch_panel_projection.py +396 -0
  150. package/ciel_runtime_support/prelaunch_terminal.py +764 -0
  151. package/ciel_runtime_support/process_control.py +708 -0
  152. package/ciel_runtime_support/prompt_compaction.py +322 -0
  153. package/ciel_runtime_support/prompt_injection.py +176 -0
  154. package/ciel_runtime_support/protocols/__init__.py +24 -0
  155. package/ciel_runtime_support/protocols/anthropic_content.py +29 -0
  156. package/ciel_runtime_support/protocols/anthropic_thinking_policy.py +336 -0
  157. package/ciel_runtime_support/protocols/chat_projection.py +315 -0
  158. package/ciel_runtime_support/protocols/conversation_policy.py +217 -0
  159. package/ciel_runtime_support/protocols/conversation_turn_policy.py +972 -0
  160. package/ciel_runtime_support/protocols/ollama_chat.py +111 -0
  161. package/ciel_runtime_support/protocols/ollama_response.py +231 -0
  162. package/ciel_runtime_support/protocols/openai_reasoning.py +75 -0
  163. package/ciel_runtime_support/protocols/openai_responses.py +271 -0
  164. package/ciel_runtime_support/protocols/pseudo_tool_history.py +248 -0
  165. package/ciel_runtime_support/protocols/tool_result_projection.py +112 -0
  166. package/ciel_runtime_support/provider_adapters.py +160 -0
  167. package/ciel_runtime_support/provider_catalog_sources.py +316 -0
  168. package/ciel_runtime_support/provider_choice.py +201 -0
  169. package/ciel_runtime_support/provider_compatibility.py +165 -0
  170. package/ciel_runtime_support/provider_config_mutations.py +361 -0
  171. package/ciel_runtime_support/provider_configuration_service.py +162 -0
  172. package/ciel_runtime_support/provider_context.py +308 -0
  173. package/ciel_runtime_support/provider_contract_projection.py +73 -0
  174. package/ciel_runtime_support/provider_descriptor.py +82 -0
  175. package/ciel_runtime_support/provider_endpoint_policy.py +130 -0
  176. package/ciel_runtime_support/provider_endpoint_probe.py +165 -0
  177. package/ciel_runtime_support/provider_launch_endpoint.py +109 -0
  178. package/ciel_runtime_support/provider_limits.py +457 -0
  179. package/ciel_runtime_support/provider_model_identity.py +139 -0
  180. package/ciel_runtime_support/provider_model_selection.py +431 -0
  181. package/ciel_runtime_support/provider_model_specs.py +142 -0
  182. package/ciel_runtime_support/provider_models.py +263 -0
  183. package/ciel_runtime_support/provider_network.py +176 -0
  184. package/ciel_runtime_support/provider_option_cli.py +238 -0
  185. package/ciel_runtime_support/provider_option_panel.py +275 -0
  186. package/ciel_runtime_support/provider_option_status.py +192 -0
  187. package/ciel_runtime_support/provider_policy.py +101 -0
  188. package/ciel_runtime_support/provider_query_policy.py +67 -0
  189. package/ciel_runtime_support/provider_readiness.py +112 -0
  190. package/ciel_runtime_support/provider_request_access.py +131 -0
  191. package/ciel_runtime_support/provider_request_builder.py +250 -0
  192. package/ciel_runtime_support/provider_responses_passthrough.py +81 -0
  193. package/ciel_runtime_support/provider_runtime_info.py +113 -0
  194. package/ciel_runtime_support/provider_runtime_modes.py +150 -0
  195. package/ciel_runtime_support/provider_sampling_policy.py +46 -0
  196. package/ciel_runtime_support/provider_status.py +145 -0
  197. package/ciel_runtime_support/provider_timeout_policy.py +184 -0
  198. package/ciel_runtime_support/provider_tool_policy.py +145 -0
  199. package/ciel_runtime_support/providers/__init__.py +65 -0
  200. package/ciel_runtime_support/providers/anthropic.py +160 -0
  201. package/ciel_runtime_support/providers/anthropic_catalog.py +119 -0
  202. package/ciel_runtime_support/providers/base.py +232 -0
  203. package/ciel_runtime_support/providers/catalog.py +326 -0
  204. package/ciel_runtime_support/providers/cloud.py +194 -0
  205. package/ciel_runtime_support/providers/constants.py +54 -0
  206. package/ciel_runtime_support/providers/deepseek.py +115 -0
  207. package/ciel_runtime_support/providers/fireworks.py +158 -0
  208. package/ciel_runtime_support/providers/github_copilot_oauth.py +199 -0
  209. package/ciel_runtime_support/providers/kimi.py +297 -0
  210. package/ciel_runtime_support/providers/lm_studio.py +76 -0
  211. package/ciel_runtime_support/providers/meta.py +257 -0
  212. package/ciel_runtime_support/providers/native.py +194 -0
  213. package/ciel_runtime_support/providers/nim.py +69 -0
  214. package/ciel_runtime_support/providers/nvidia.py +158 -0
  215. package/ciel_runtime_support/providers/nvidia_runtime.py +285 -0
  216. package/ciel_runtime_support/providers/ollama.py +181 -0
  217. package/ciel_runtime_support/providers/ollama_context.py +195 -0
  218. package/ciel_runtime_support/providers/ollama_runtime.py +213 -0
  219. package/ciel_runtime_support/providers/opencode.py +220 -0
  220. package/ciel_runtime_support/providers/opencode_go.py +37 -0
  221. package/ciel_runtime_support/providers/openrouter.py +57 -0
  222. package/ciel_runtime_support/providers/vllm.py +65 -0
  223. package/ciel_runtime_support/providers/zai.py +119 -0
  224. package/ciel_runtime_support/pseudo_tool_parser.py +115 -0
  225. package/ciel_runtime_support/rate_limit_policy.py +117 -0
  226. package/ciel_runtime_support/rate_limit_repository.py +154 -0
  227. package/ciel_runtime_support/registry.py +46 -0
  228. package/ciel_runtime_support/request_shortcuts.py +253 -0
  229. package/ciel_runtime_support/request_trace.py +323 -0
  230. package/ciel_runtime_support/response_collection.py +209 -0
  231. package/ciel_runtime_support/router_access.py +238 -0
  232. package/ciel_runtime_support/router_client_lifecycle.py +366 -0
  233. package/ciel_runtime_support/router_health_policy.py +101 -0
  234. package/ciel_runtime_support/router_http.py +513 -0
  235. package/ciel_runtime_support/router_process_lifecycle.py +401 -0
  236. package/ciel_runtime_support/router_rate_limit_service.py +285 -0
  237. package/ciel_runtime_support/router_server_runtime.py +103 -0
  238. package/ciel_runtime_support/router_shortcuts.py +201 -0
  239. package/ciel_runtime_support/routing_fallback.py +73 -0
  240. package/ciel_runtime_support/runtime_activity_repository.py +143 -0
  241. package/ciel_runtime_support/runtime_adapters.py +104 -0
  242. package/ciel_runtime_support/runtime_command_factory.py +73 -0
  243. package/ciel_runtime_support/runtime_compatibility.py +50 -0
  244. package/ciel_runtime_support/runtime_constants.py +178 -0
  245. package/ciel_runtime_support/runtime_launch.py +1602 -0
  246. package/ciel_runtime_support/runtime_llm_options.py +312 -0
  247. package/ciel_runtime_support/runtime_logging.py +161 -0
  248. package/ciel_runtime_support/runtime_paths.py +157 -0
  249. package/ciel_runtime_support/runtime_restart.py +84 -0
  250. package/ciel_runtime_support/runtime_upgrade.py +149 -0
  251. package/ciel_runtime_support/secure_json_repository.py +55 -0
  252. package/ciel_runtime_support/session_import.py +356 -0
  253. package/ciel_runtime_support/settings_repository.py +8 -0
  254. package/ciel_runtime_support/slash_command_assets.py +211 -0
  255. package/ciel_runtime_support/sse_stream.py +57 -0
  256. package/ciel_runtime_support/sse_trace.py +225 -0
  257. package/ciel_runtime_support/statusline_script.py +593 -0
  258. package/ciel_runtime_support/statusline_settings.py +53 -0
  259. package/ciel_runtime_support/stream_chunk_policy.py +18 -0
  260. package/ciel_runtime_support/streaming_anthropic.py +1955 -0
  261. package/ciel_runtime_support/synthetic_tool_policy.py +105 -0
  262. package/ciel_runtime_support/terminal_platform_io.py +127 -0
  263. package/ciel_runtime_support/timeout_profile.py +196 -0
  264. package/ciel_runtime_support/tool_dialects.py +85 -0
  265. package/ciel_runtime_support/tool_exposure_policy.py +63 -0
  266. package/ciel_runtime_support/tool_guard_hooks.py +218 -0
  267. package/ciel_runtime_support/tool_request_projection.py +96 -0
  268. package/ciel_runtime_support/tool_schema.py +483 -0
  269. package/ciel_runtime_support/tool_side_effect_dedupe.py +95 -0
  270. package/ciel_runtime_support/ui_text.py +266 -0
  271. package/ciel_runtime_support/upstream_error_policy.py +104 -0
  272. package/ciel_runtime_support/upstream_retry.py +419 -0
  273. package/ciel_runtime_support/upstream_stream_io.py +106 -0
  274. package/ciel_runtime_support/usage_events.py +96 -0
  275. package/ciel_runtime_support/visible_stream_filters.py +130 -0
  276. package/ciel_runtime_support/web_endpoints.py +447 -0
  277. package/ciel_runtime_support/web_ui.py +915 -0
  278. package/ciel_runtime_support/web_ui_controller.py +189 -0
  279. package/ciel_runtime_support/windows_console_input.py +137 -0
  280. package/ciel_runtime_support/windows_console_mode.py +112 -0
  281. package/docs/Architecture.md +54 -0
  282. package/docs/Configuration.md +17 -0
  283. package/docs/Module-Map.md +1093 -26
  284. package/docs/Providers.md +46 -1
  285. package/docs/adr/0001-runtime-bounded-contexts.md +295 -0
  286. package/npm-bin/run-ciel-runtime.js +22 -2
  287. package/package.json +9 -2
@@ -0,0 +1,181 @@
1
+ """Bounded environment configuration policy for Channel runtime behavior."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Mapping
7
+
8
+
9
+ @dataclass(frozen=True, slots=True)
10
+ class ChannelRuntimeEnvironmentPolicy:
11
+ environment: Mapping[str, str]
12
+ launch_recent_default: float
13
+ probe_timeout_default: float
14
+
15
+ def launch_recent_seconds(self) -> float:
16
+ raw = str(
17
+ self.environment.get(
18
+ "CIEL_RUNTIME_CHANNEL_LAUNCH_RECENT_SECONDS",
19
+ "",
20
+ )
21
+ or ""
22
+ ).strip()
23
+ if not raw:
24
+ return self.launch_recent_default
25
+ return self._float(raw, self.launch_recent_default)
26
+
27
+ def probe_timeout_seconds(self) -> float:
28
+ raw = self.environment.get(
29
+ "CIEL_RUNTIME_CHANNEL_PROBE_TIMEOUT_SECONDS"
30
+ )
31
+ if raw is None:
32
+ return self.probe_timeout_default
33
+ value = self._float(str(raw).strip(), self.probe_timeout_default)
34
+ return value if value > 0 else self.probe_timeout_default
35
+
36
+ def pending_scan_limit(self) -> int:
37
+ return self._bounded_int(
38
+ self.environment.get(
39
+ "CIEL_RUNTIME_CHANNEL_PENDING_SCAN_LIMIT",
40
+ "500",
41
+ ),
42
+ default=500,
43
+ minimum=100,
44
+ maximum=5000,
45
+ )
46
+
47
+ def wake_batch_limit(self) -> int:
48
+ return self._bounded_int(
49
+ self.environment.get(
50
+ "CIEL_RUNTIME_CHANNEL_WAKE_BATCH_LIMIT",
51
+ "8",
52
+ ),
53
+ default=8,
54
+ minimum=1,
55
+ maximum=50,
56
+ )
57
+
58
+ def wake_claim_ttl_seconds(self) -> float:
59
+ return self._bounded_float(
60
+ self.environment.get(
61
+ "CIEL_RUNTIME_CHANNEL_WAKE_CLAIM_TTL_SECONDS"
62
+ ),
63
+ default=300.0,
64
+ minimum=5.0,
65
+ maximum=1800.0,
66
+ )
67
+
68
+ def unseen_retry_seconds(self) -> float:
69
+ return self._bounded_float(
70
+ self.environment.get(
71
+ "CIEL_RUNTIME_CHANNEL_WAKE_UNSEEN_RETRY_SECONDS"
72
+ ),
73
+ default=20.0,
74
+ minimum=2.0,
75
+ maximum=300.0,
76
+ )
77
+
78
+ def inflight_stale_seconds(self) -> float:
79
+ return self._bounded_float(
80
+ self.environment.get(
81
+ "CIEL_RUNTIME_CHANNEL_WAKE_INFLIGHT_STALE_SECONDS"
82
+ ),
83
+ default=180.0,
84
+ minimum=30.0,
85
+ maximum=1800.0,
86
+ )
87
+
88
+ def codex_submit_retries(self) -> int:
89
+ return self._bounded_int(
90
+ self.environment.get(
91
+ "CIEL_RUNTIME_CODEX_CHANNEL_WAKE_SUBMIT_RETRIES"
92
+ ),
93
+ default=4,
94
+ minimum=1,
95
+ maximum=8,
96
+ )
97
+
98
+ def codex_submit_delay_seconds(self) -> float:
99
+ return self._bounded_milliseconds(
100
+ self.environment.get(
101
+ "CIEL_RUNTIME_CODEX_CHANNEL_WAKE_SUBMIT_DELAY_MS"
102
+ ),
103
+ default=0.25,
104
+ minimum=0.13,
105
+ maximum=5.0,
106
+ )
107
+
108
+ def windows_startup_grace_seconds(self) -> float:
109
+ return self._bounded_milliseconds(
110
+ self.environment.get(
111
+ "CIEL_RUNTIME_WINDOWS_CHANNEL_STARTUP_GRACE_MS"
112
+ ),
113
+ default=8.0,
114
+ minimum=0.0,
115
+ maximum=60.0,
116
+ )
117
+
118
+ @staticmethod
119
+ def inflight_is_stale(
120
+ state: str,
121
+ started_at: float,
122
+ current_time: float,
123
+ stale_seconds: float,
124
+ ) -> bool:
125
+ return bool(
126
+ state in {"queued", "unknown"}
127
+ and started_at > 0
128
+ and current_time - started_at >= stale_seconds
129
+ )
130
+
131
+ @staticmethod
132
+ def _float(raw: str, default: float) -> float:
133
+ try:
134
+ return float(raw)
135
+ except (TypeError, ValueError):
136
+ return default
137
+
138
+ @classmethod
139
+ def _bounded_float(
140
+ cls,
141
+ raw: str | None,
142
+ *,
143
+ default: float,
144
+ minimum: float,
145
+ maximum: float,
146
+ ) -> float:
147
+ if raw is None:
148
+ return default
149
+ value = cls._float(raw, default)
150
+ return max(minimum, min(maximum, value))
151
+
152
+ @staticmethod
153
+ def _bounded_int(
154
+ raw: str | None,
155
+ *,
156
+ default: int,
157
+ minimum: int,
158
+ maximum: int,
159
+ ) -> int:
160
+ try:
161
+ value = int(str(raw).strip())
162
+ except (TypeError, ValueError):
163
+ return default
164
+ return max(minimum, min(maximum, value))
165
+
166
+ @classmethod
167
+ def _bounded_milliseconds(
168
+ cls,
169
+ raw: str | None,
170
+ *,
171
+ default: float,
172
+ minimum: float,
173
+ maximum: float,
174
+ ) -> float:
175
+ if raw is None:
176
+ return default
177
+ value = cls._float(raw, default * 1000.0) / 1000.0
178
+ return max(minimum, min(maximum, value))
179
+
180
+
181
+ __all__ = ["ChannelRuntimeEnvironmentPolicy"]
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+
3
+ import urllib.error
4
+ import urllib.request
5
+ from dataclasses import dataclass
6
+ from typing import Any, Callable
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class ChannelSessionLifecycleServices:
11
+ streamable_headers: Callable[..., dict[str, str]]
12
+ http_error_body: Callable[[urllib.error.HTTPError], str]
13
+ session_not_found: Callable[[urllib.error.HTTPError, str], bool]
14
+ records: Callable[[], list[dict[str, Any]]]
15
+ forget: Callable[[str, str, str | None], None]
16
+ log: Callable[[str, str], None]
17
+ urlopen: Callable[..., Any] = urllib.request.urlopen
18
+
19
+
20
+ def delete_channel_session(
21
+ name: str,
22
+ endpoint: str,
23
+ headers: dict[str, str],
24
+ protocol_version: str,
25
+ session_id: str | None,
26
+ reason: str,
27
+ services: ChannelSessionLifecycleServices,
28
+ *,
29
+ default_protocol_version: str,
30
+ timeout: float = 5.0,
31
+ ) -> bool:
32
+ session = str(session_id or "").strip()
33
+ if not session or not endpoint:
34
+ return True
35
+ request_headers = services.streamable_headers(
36
+ headers,
37
+ protocol_version or default_protocol_version,
38
+ session,
39
+ accept="application/json, text/event-stream",
40
+ )
41
+ try:
42
+ request = urllib.request.Request(endpoint, headers=request_headers, method="DELETE")
43
+ with services.urlopen(request, timeout=max(1.0, min(30.0, timeout))) as response:
44
+ try:
45
+ response.read()
46
+ except (OSError, ValueError) as exc:
47
+ services.log(
48
+ "WARN",
49
+ f"channel_http_mcp_session_delete_body_read_failed name={name} session={session} "
50
+ f"error={type(exc).__name__}: {exc}",
51
+ )
52
+ services.log(
53
+ "INFO",
54
+ f"channel_http_mcp_session_deleted name={name} session={session} reason={reason}",
55
+ )
56
+ services.forget(name, endpoint, session)
57
+ return True
58
+ except urllib.error.HTTPError as exc:
59
+ body_text = services.http_error_body(exc)
60
+ if exc.code in {404, 405} or services.session_not_found(exc, body_text):
61
+ services.log(
62
+ "INFO",
63
+ f"channel_http_mcp_session_delete_not_needed name={name} session={session} "
64
+ f"status={exc.code} reason={reason}",
65
+ )
66
+ services.forget(name, endpoint, session)
67
+ return True
68
+ services.log(
69
+ "WARN",
70
+ f"channel_http_mcp_session_delete_failed name={name} session={session} "
71
+ f"status={exc.code} reason={reason}",
72
+ )
73
+ return False
74
+ except Exception as exc:
75
+ services.log(
76
+ "WARN",
77
+ f"channel_http_mcp_session_delete_failed name={name} session={session} "
78
+ f"error={type(exc).__name__}: {exc} reason={reason}",
79
+ )
80
+ return False
81
+
82
+
83
+ def cleanup_stale_channel_sessions(
84
+ name: str,
85
+ url: str,
86
+ headers: dict[str, str],
87
+ protocol_version: str,
88
+ services: ChannelSessionLifecycleServices,
89
+ *,
90
+ default_protocol_version: str,
91
+ keep_session_id: str | None = None,
92
+ ) -> None:
93
+ keep = str(keep_session_id or "").strip()
94
+ for record in services.records():
95
+ if str(record.get("url") or "") != str(url):
96
+ continue
97
+ session = str(record.get("session_id") or "").strip()
98
+ if not session or session == keep:
99
+ continue
100
+ record_protocol = str(
101
+ record.get("protocol_version")
102
+ or protocol_version
103
+ or default_protocol_version
104
+ )
105
+ delete_channel_session(
106
+ name,
107
+ url,
108
+ headers,
109
+ record_protocol,
110
+ session,
111
+ "stale_session_cleanup",
112
+ services,
113
+ default_protocol_version=default_protocol_version,
114
+ )
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import time
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any, Callable
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class ChannelSessionRepository:
13
+ path: Path
14
+ default_protocol_version: str
15
+ log: Callable[[str, str], None]
16
+ process_id: Callable[[], int] = os.getpid
17
+ timestamp: Callable[[], str] = lambda: time.strftime("%Y-%m-%dT%H:%M:%S")
18
+ chmod: Callable[[Path, int], None] = os.chmod
19
+
20
+ def records(self) -> list[dict[str, Any]]:
21
+ try:
22
+ data = json.loads(self.path.read_text(encoding="utf-8"))
23
+ except FileNotFoundError:
24
+ return []
25
+ except Exception as exc:
26
+ self.log(
27
+ "WARN",
28
+ f"channel_http_session_record_read_failed error={type(exc).__name__}: {exc}",
29
+ )
30
+ return []
31
+ records = data.get("sessions") if isinstance(data, dict) else data
32
+ if not isinstance(records, list):
33
+ return []
34
+ return [item for item in records if isinstance(item, dict)]
35
+
36
+ def write(self, records: list[dict[str, Any]]) -> None:
37
+ try:
38
+ self.path.parent.mkdir(parents=True, exist_ok=True)
39
+ payload = {"sessions": records[-100:]}
40
+ self.path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
41
+ try:
42
+ self.chmod(self.path, 0o600)
43
+ except Exception as exc:
44
+ self.log(
45
+ "WARN",
46
+ f"channel_http_session_record_chmod_failed error={type(exc).__name__}: {exc}",
47
+ )
48
+ except Exception as exc:
49
+ self.log(
50
+ "WARN",
51
+ f"channel_http_session_record_write_failed error={type(exc).__name__}: {exc}",
52
+ )
53
+
54
+ def record(
55
+ self,
56
+ name: str,
57
+ url: str,
58
+ session_id: str | None,
59
+ protocol_version: str,
60
+ ) -> None:
61
+ session = str(session_id or "").strip()
62
+ if not session:
63
+ return
64
+ url_text = str(url)
65
+ records = [
66
+ item
67
+ for item in self.records()
68
+ if str(item.get("url") or "") != url_text
69
+ and str(item.get("session_id") or "") != session
70
+ ]
71
+ records.append(
72
+ {
73
+ "name": str(name),
74
+ "url": url_text,
75
+ "session_id": session,
76
+ "protocol_version": str(protocol_version or self.default_protocol_version),
77
+ "pid": self.process_id(),
78
+ "recorded_at": self.timestamp(),
79
+ }
80
+ )
81
+ self.write(records)
82
+
83
+ def forget(self, session_id: str | None) -> None:
84
+ session = str(session_id or "").strip()
85
+ if not session:
86
+ return
87
+ records = self.records()
88
+ kept = [item for item in records if str(item.get("session_id") or "") != session]
89
+ if len(kept) != len(records):
90
+ self.write(kept)
@@ -0,0 +1,115 @@
1
+ """Platform dispatch and direct-process lifecycle for channel terminals."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class ChannelTerminalDispatchSettings:
13
+ platform_name: str
14
+ stdin_isatty: Callable[[], bool]
15
+ stdout_isatty: Callable[[], bool]
16
+
17
+
18
+ @dataclass(frozen=True, slots=True)
19
+ class ChannelTerminalProxyPorts:
20
+ windows_supported: Callable[[], bool]
21
+ run_windows: Callable[..., int]
22
+ run_posix: Callable[..., int]
23
+ posix_services: Callable[[], Any]
24
+
25
+
26
+ @dataclass(frozen=True, slots=True)
27
+ class ChannelDirectProcessPorts:
28
+ call: Callable[..., int]
29
+ popen: Callable[..., Any]
30
+ write_record: Callable[[Path | None, int, list[str]], None]
31
+ terminate: Callable[[Any, str], None]
32
+ release_record: Callable[[Path | None, int], None]
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class ChannelTerminalDispatchService:
37
+ settings: ChannelTerminalDispatchSettings
38
+ proxy: ChannelTerminalProxyPorts
39
+ direct: ChannelDirectProcessPorts
40
+ log: Callable[[str, str], None]
41
+
42
+ def dispatch(
43
+ self,
44
+ cmd: list[str],
45
+ env: dict[str, str],
46
+ *,
47
+ inject_channel_messages: bool = True,
48
+ inject_web_chat_only: bool = False,
49
+ wake_for_llm_delivery: bool = False,
50
+ synthetic_enter_bytes: str | bytes | None = None,
51
+ normalize_bare_cr_for_synthetic_enter: bool = True,
52
+ channel_wake_submit_retries: int = 1,
53
+ channel_wake_confirm_submit: bool = False,
54
+ channel_wake_bracketed_paste: bool = False,
55
+ channel_wake_submit_delay_seconds: float | None = None,
56
+ tracked_child_pid_path: Path | None = None,
57
+ ) -> int:
58
+ options = {
59
+ "inject_channel_messages": inject_channel_messages,
60
+ "inject_web_chat_only": inject_web_chat_only,
61
+ "wake_for_llm_delivery": wake_for_llm_delivery,
62
+ "synthetic_enter_bytes": synthetic_enter_bytes,
63
+ "normalize_bare_cr_for_synthetic_enter": (
64
+ normalize_bare_cr_for_synthetic_enter
65
+ ),
66
+ "channel_wake_submit_retries": channel_wake_submit_retries,
67
+ "channel_wake_confirm_submit": channel_wake_confirm_submit,
68
+ "channel_wake_bracketed_paste": channel_wake_bracketed_paste,
69
+ "channel_wake_submit_delay_seconds": (
70
+ channel_wake_submit_delay_seconds
71
+ ),
72
+ "tracked_child_pid_path": tracked_child_pid_path,
73
+ }
74
+ if self.settings.platform_name == "nt" and self.proxy.windows_supported():
75
+ try:
76
+ return self.proxy.run_windows(cmd, env, **options)
77
+ except Exception as exc:
78
+ self.log(
79
+ "WARN",
80
+ "channel_windows_console_proxy_failed "
81
+ f"error={type(exc).__name__}: {exc}; "
82
+ "using direct subprocess call",
83
+ )
84
+ if (
85
+ self.settings.platform_name != "posix"
86
+ or not self.settings.stdin_isatty()
87
+ or not self.settings.stdout_isatty()
88
+ ):
89
+ self.log(
90
+ "INFO",
91
+ "channel_stdin_proxy_unavailable; using direct subprocess call",
92
+ )
93
+ return self.call_direct(cmd, env, tracked_child_pid_path)
94
+ return self.proxy.run_posix(
95
+ cmd,
96
+ env,
97
+ self.proxy.posix_services(),
98
+ **options,
99
+ )
100
+
101
+ def call_direct(
102
+ self,
103
+ cmd: list[str],
104
+ env: dict[str, str],
105
+ pid_path: Path | None = None,
106
+ ) -> int:
107
+ if pid_path is None:
108
+ return self.direct.call(cmd, env=env)
109
+ proc = self.direct.popen(cmd, env=env)
110
+ self.direct.write_record(pid_path, proc.pid, cmd)
111
+ try:
112
+ return proc.wait()
113
+ finally:
114
+ self.direct.terminate(proc, "current Codex")
115
+ self.direct.release_record(pid_path, proc.pid)