@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,447 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import signal
5
+ import sys
6
+ import time
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Any, Callable
10
+
11
+ from ciel_runtime_support.channel_compact_poll import (
12
+ ChannelCompactInjectionOptions,
13
+ ChannelCompactPollServices,
14
+ ChannelCompactPollState,
15
+ poll_pending_compaction,
16
+ )
17
+ from ciel_runtime_support.channel_inflight import (
18
+ ChannelInflightPolicy,
19
+ ChannelInflightSnapshot,
20
+ advance_channel_inflight,
21
+ )
22
+ from ciel_runtime_support.channel_pending_poll import (
23
+ ChannelPendingInjectionOptions,
24
+ ChannelPendingPollPolicy,
25
+ ChannelPendingPollServices,
26
+ ChannelPendingPollState,
27
+ poll_pending_channel_messages,
28
+ )
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class ChannelTerminalProcess:
33
+ popen: Callable[..., Any]
34
+ write_child_record: Callable[[Path | None, int, list[str]], None]
35
+ terminate_child: Callable[[Any, str], None]
36
+ release_child_record: Callable[[Path | None, int], None]
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class ChannelTerminalIO:
41
+ terminal_size: Callable[[int], tuple[int, int]]
42
+ apply_terminal_size: Callable[[int, int, int], bool]
43
+ write_all: Callable[[Any, bytes], None]
44
+ mouse_filter: Callable[[], Any]
45
+ observed_enter: Callable[..., bytes | None]
46
+ reset_input_mode: Callable[[], None]
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class ChannelTerminalPolicy:
51
+ initial_cursor: Callable[[], int]
52
+ enter_bytes: Callable[[str | bytes | None], bytes]
53
+ enter_label: Callable[[bytes], str]
54
+ enter_is_fixed: Callable[[], bool]
55
+ unseen_retry_seconds: Callable[[], float]
56
+ inflight_is_stale: Callable[..., bool]
57
+ log: Callable[[str, str], None]
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class ChannelTerminalPolling:
62
+ inject_compact: Callable[..., Any]
63
+ file_marker: Callable[[], Any]
64
+ should_check: Callable[..., bool]
65
+ active_tool_call: Callable[..., bool]
66
+ inject_pending: Callable[..., Any]
67
+ wake_state: Callable[[int], Any]
68
+ inflight_effects: Callable[[], Any]
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class ChannelTerminalServices:
73
+ process: ChannelTerminalProcess
74
+ io: ChannelTerminalIO
75
+ policy: ChannelTerminalPolicy
76
+ polling: ChannelTerminalPolling
77
+
78
+
79
+ @dataclass(frozen=True)
80
+ class ChannelWindowsConsole:
81
+ reset_input_mode: Callable[[], None]
82
+ mouse_guard: Callable[[], Any]
83
+ input_writer: Callable[[], Any]
84
+ startup_grace_seconds: Callable[[], float]
85
+ reset_interval_seconds: Callable[[float], float]
86
+ active_turn: Callable[[], bool]
87
+ retry_submit: Callable[..., tuple[int, float]]
88
+ sleep: Callable[[float], None]
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class ChannelWindowsServices:
93
+ process: ChannelTerminalProcess
94
+ policy: ChannelTerminalPolicy
95
+ polling: ChannelTerminalPolling
96
+ console: ChannelWindowsConsole
97
+
98
+
99
+ def run_windows_channel_terminal_proxy(
100
+ cmd: list[str],
101
+ env: dict[str, str],
102
+ services: ChannelWindowsServices,
103
+ *,
104
+ inject_channel_messages: bool = True,
105
+ inject_web_chat_only: bool = False,
106
+ wake_for_llm_delivery: bool = False,
107
+ synthetic_enter_bytes: str | bytes | None = None,
108
+ channel_wake_submit_retries: int = 1,
109
+ channel_wake_confirm_submit: bool = False,
110
+ channel_wake_submit_delay_seconds: float | None = None,
111
+ tracked_child_pid_path: Path | None = None,
112
+ ) -> int:
113
+ process = services.process
114
+ policy = services.policy
115
+ polling = services.polling
116
+ console = services.console
117
+ console.reset_input_mode()
118
+ input_mode_guard = console.mouse_guard()
119
+ input_mode_guard.apply()
120
+ proc = process.popen(cmd, env=env)
121
+ process.write_child_record(tracked_child_pid_path, proc.pid, cmd)
122
+ writer = console.input_writer()
123
+ pending_poll_state = ChannelPendingPollState(last_id=policy.initial_cursor())
124
+ compact_poll_state = ChannelCompactPollState()
125
+ channel_submit_attempts = 0
126
+ channel_last_submit_at = 0.0
127
+ channel_enter_bytes = policy.enter_bytes(synthetic_enter_bytes)
128
+ channel_input_ready_at = time.time() + console.startup_grace_seconds()
129
+ # Windows Console consumes INPUT_RECORD events, so ANSI bracketed-paste
130
+ # delimiters would be interpreted as literal keys instead of a paste event.
131
+ windows_bracketed_paste = False
132
+ submit_retry_count = max(1, min(8, int(channel_wake_submit_retries or 1)))
133
+ compact_injection_options = ChannelCompactInjectionOptions(
134
+ submit_retry_count=submit_retry_count,
135
+ confirm_submit=channel_wake_confirm_submit,
136
+ bracketed_paste=windows_bracketed_paste,
137
+ submit_delay_seconds=channel_wake_submit_delay_seconds,
138
+ )
139
+ compact_poll_services = ChannelCompactPollServices(inject_pending=polling.inject_compact)
140
+ pending_injection_options = ChannelPendingInjectionOptions(
141
+ enabled=inject_channel_messages,
142
+ web_chat_only=inject_web_chat_only,
143
+ wake_for_llm_delivery=wake_for_llm_delivery,
144
+ submit_retry_count=submit_retry_count,
145
+ confirm_submit=channel_wake_confirm_submit,
146
+ bracketed_paste=windows_bracketed_paste,
147
+ submit_delay_seconds=channel_wake_submit_delay_seconds,
148
+ )
149
+ pending_poll_services = ChannelPendingPollServices(
150
+ file_marker=polling.file_marker,
151
+ should_check=polling.should_check,
152
+ active=lambda: polling.active_tool_call() or console.active_turn(),
153
+ ensure_cursor=policy.initial_cursor,
154
+ inject_pending=polling.inject_pending,
155
+ log=policy.log,
156
+ )
157
+ pending_poll_policy = ChannelPendingPollPolicy("channel_windows_console", "active_turn")
158
+ last_terminal_input_mode_reset = 0.0
159
+ terminal_input_mode_reset_interval = console.reset_interval_seconds(0.25)
160
+ policy.log(
161
+ "INFO",
162
+ "channel_windows_console_proxy_started "
163
+ f"pid={proc.pid} enter={policy.enter_label(channel_enter_bytes)} "
164
+ f"submit_retries={submit_retry_count} confirm_submit={bool(channel_wake_confirm_submit)} "
165
+ f"bracketed_paste={windows_bracketed_paste}",
166
+ )
167
+ try:
168
+ while proc.poll() is None:
169
+ now = time.time()
170
+ if now - last_terminal_input_mode_reset >= terminal_input_mode_reset_interval:
171
+ last_terminal_input_mode_reset = now
172
+ console.reset_input_mode()
173
+ input_mode_guard.apply()
174
+ if pending_poll_state.inflight_message_id is not None:
175
+ channel_inflight_state = polling.wake_state(pending_poll_state.inflight_message_id)
176
+ channel_submit_attempts, channel_last_submit_at = console.retry_submit(
177
+ writer,
178
+ channel_enter_bytes,
179
+ channel_inflight_state,
180
+ channel_submit_attempts,
181
+ submit_retry_count,
182
+ channel_last_submit_at,
183
+ now,
184
+ confirm_submit=channel_wake_confirm_submit,
185
+ turn_active=console.active_turn(),
186
+ )
187
+ inflight_update = advance_channel_inflight(
188
+ ChannelInflightSnapshot(
189
+ message_id=pending_poll_state.inflight_message_id,
190
+ cursor=pending_poll_state.inflight_cursor,
191
+ wake_state=channel_inflight_state,
192
+ started_at=pending_poll_state.inflight_started_at,
193
+ logged_at=pending_poll_state.inflight_logged_at,
194
+ now=now,
195
+ last_id=pending_poll_state.last_id,
196
+ ),
197
+ ChannelInflightPolicy(
198
+ unseen_retry_seconds=policy.unseen_retry_seconds(),
199
+ waiting_log_interval=30.0,
200
+ is_stale=policy.inflight_is_stale,
201
+ commit_cursor_on_stale=False,
202
+ log_namespace="channel_windows_console",
203
+ stale_event="stale_inflight_retry",
204
+ ),
205
+ polling.inflight_effects(),
206
+ )
207
+ pending_poll_state.inflight_message_id = inflight_update.message_id
208
+ pending_poll_state.inflight_cursor = inflight_update.cursor
209
+ pending_poll_state.inflight_started_at = inflight_update.started_at
210
+ pending_poll_state.inflight_logged_at = inflight_update.logged_at
211
+ pending_poll_state.pending_recheck = (
212
+ pending_poll_state.pending_recheck or inflight_update.pending_recheck
213
+ )
214
+ pending_poll_state.last_id = inflight_update.last_id
215
+ if inflight_update.action in {"completed", "unseen_retry", "stale"}:
216
+ channel_submit_attempts = 0
217
+ channel_last_submit_at = 0.0
218
+ compact_poll_state = poll_pending_compaction(
219
+ now,
220
+ writer,
221
+ channel_enter_bytes,
222
+ pending_poll_state.inflight_message_id,
223
+ compact_poll_state,
224
+ compact_injection_options,
225
+ compact_poll_services,
226
+ input_ready=now >= channel_input_ready_at,
227
+ )
228
+ previous_inflight_id = pending_poll_state.inflight_message_id
229
+ pending_poll_state = poll_pending_channel_messages(
230
+ now,
231
+ writer,
232
+ channel_enter_bytes,
233
+ pending_poll_state,
234
+ pending_injection_options,
235
+ pending_poll_policy,
236
+ pending_poll_services,
237
+ input_ready=now >= channel_input_ready_at,
238
+ )
239
+ if previous_inflight_id is None and pending_poll_state.inflight_message_id is not None:
240
+ channel_submit_attempts = 1
241
+ channel_last_submit_at = now
242
+ console.sleep(0.05)
243
+ return proc.wait()
244
+ finally:
245
+ console.reset_input_mode()
246
+ input_mode_guard.restore()
247
+ process.terminate_child(proc, "current Codex")
248
+ process.release_child_record(tracked_child_pid_path, proc.pid)
249
+
250
+
251
+ def run_posix_channel_terminal_proxy(
252
+ cmd: list[str],
253
+ env: dict[str, str],
254
+ services: ChannelTerminalServices,
255
+ *,
256
+ inject_channel_messages: bool = True,
257
+ inject_web_chat_only: bool = False,
258
+ wake_for_llm_delivery: bool = False,
259
+ synthetic_enter_bytes: str | bytes | None = None,
260
+ normalize_bare_cr_for_synthetic_enter: bool = True,
261
+ channel_wake_submit_retries: int = 1,
262
+ channel_wake_confirm_submit: bool = False,
263
+ channel_wake_bracketed_paste: bool = False,
264
+ channel_wake_submit_delay_seconds: float | None = None,
265
+ tracked_child_pid_path: Path | None = None,
266
+ ) -> int:
267
+ import pty
268
+ import select
269
+ import termios
270
+ import tty
271
+
272
+ process = services.process
273
+ terminal = services.io
274
+ policy = services.policy
275
+ polling = services.polling
276
+ pending_poll_state = ChannelPendingPollState(last_id=policy.initial_cursor())
277
+ master_fd, slave_fd = pty.openpty()
278
+ stdout_fd = sys.stdout.fileno()
279
+ rows, cols = terminal.terminal_size(stdout_fd)
280
+ if terminal.apply_terminal_size(slave_fd, rows, cols):
281
+ policy.log("INFO", f"channel_stdin_proxy_winsize_init rows={rows} cols={cols}")
282
+ proc = process.popen(cmd, stdin=slave_fd, stdout=slave_fd, stderr=slave_fd, env=env, close_fds=True)
283
+ process.write_child_record(tracked_child_pid_path, proc.pid, cmd)
284
+ os.close(slave_fd)
285
+ stdin_fd = sys.stdin.fileno()
286
+ old_attrs = termios.tcgetattr(stdin_fd)
287
+ old_sigwinch = None
288
+ sigwinch_installed = False
289
+ mouse_input_filter = terminal.mouse_filter()
290
+ channel_enter_bytes = policy.enter_bytes(synthetic_enter_bytes)
291
+ submit_retry_count = max(1, min(8, int(channel_wake_submit_retries or 1)))
292
+ compact_injection_options = ChannelCompactInjectionOptions(
293
+ submit_retry_count=submit_retry_count,
294
+ confirm_submit=channel_wake_confirm_submit,
295
+ bracketed_paste=channel_wake_bracketed_paste,
296
+ submit_delay_seconds=channel_wake_submit_delay_seconds,
297
+ )
298
+ compact_poll_services = ChannelCompactPollServices(inject_pending=polling.inject_compact)
299
+ compact_poll_state = ChannelCompactPollState()
300
+ pending_injection_options = ChannelPendingInjectionOptions(
301
+ enabled=inject_channel_messages,
302
+ web_chat_only=inject_web_chat_only,
303
+ wake_for_llm_delivery=wake_for_llm_delivery,
304
+ submit_retry_count=submit_retry_count,
305
+ confirm_submit=channel_wake_confirm_submit,
306
+ bracketed_paste=channel_wake_bracketed_paste,
307
+ submit_delay_seconds=channel_wake_submit_delay_seconds,
308
+ )
309
+ pending_poll_services = ChannelPendingPollServices(
310
+ file_marker=polling.file_marker,
311
+ should_check=polling.should_check,
312
+ active=polling.active_tool_call,
313
+ ensure_cursor=policy.initial_cursor,
314
+ inject_pending=polling.inject_pending,
315
+ log=policy.log,
316
+ )
317
+ pending_poll_policy = ChannelPendingPollPolicy("channel_stdin_proxy", "active_tool_call")
318
+ policy.log(
319
+ "INFO",
320
+ "channel_stdin_proxy_enter_default "
321
+ f"enter={policy.enter_label(channel_enter_bytes)} os={os.name} platform={sys.platform} "
322
+ f"submit_retries={submit_retry_count} confirm_submit={bool(channel_wake_confirm_submit)} "
323
+ f"bracketed_paste={bool(channel_wake_bracketed_paste)}",
324
+ )
325
+ terminal.reset_input_mode()
326
+
327
+ def handle_terminal_resize(signum: int, frame: Any) -> None:
328
+ new_rows, new_cols = terminal.terminal_size(stdout_fd)
329
+ if terminal.apply_terminal_size(master_fd, new_rows, new_cols):
330
+ policy.log("INFO", f"channel_stdin_proxy_winsize_resize rows={new_rows} cols={new_cols}")
331
+ if callable(old_sigwinch):
332
+ old_sigwinch(signum, frame)
333
+
334
+ try:
335
+ try:
336
+ old_sigwinch = signal.getsignal(signal.SIGWINCH)
337
+ signal.signal(signal.SIGWINCH, handle_terminal_resize)
338
+ sigwinch_installed = True
339
+ except Exception as exc:
340
+ policy.log("WARN", f"channel_sigwinch_install_failed error={type(exc).__name__}: {exc}")
341
+ tty.setraw(stdin_fd)
342
+ while proc.poll() is None:
343
+ try:
344
+ readable, _, _ = select.select([stdin_fd, master_fd], [], [], 0.2)
345
+ except OSError:
346
+ break
347
+ if stdin_fd in readable:
348
+ data = os.read(stdin_fd, 4096)
349
+ if data:
350
+ filtered_data = mouse_input_filter.feed(data)
351
+ if filtered_data:
352
+ observed_enter = terminal.observed_enter(
353
+ filtered_data,
354
+ normalize_bare_cr=normalize_bare_cr_for_synthetic_enter,
355
+ )
356
+ if observed_enter and not policy.enter_is_fixed():
357
+ if observed_enter != channel_enter_bytes:
358
+ policy.log(
359
+ "INFO",
360
+ f"channel_stdin_proxy_enter_observed enter={policy.enter_label(observed_enter)}",
361
+ )
362
+ channel_enter_bytes = observed_enter
363
+ terminal.write_all(master_fd, filtered_data)
364
+ if master_fd in readable:
365
+ try:
366
+ data = os.read(master_fd, 4096)
367
+ except OSError:
368
+ break
369
+ if data:
370
+ terminal.write_all(stdout_fd, data)
371
+ now = time.time()
372
+ if pending_poll_state.inflight_message_id is not None:
373
+ inflight_update = advance_channel_inflight(
374
+ ChannelInflightSnapshot(
375
+ message_id=pending_poll_state.inflight_message_id,
376
+ cursor=pending_poll_state.inflight_cursor,
377
+ wake_state=polling.wake_state(pending_poll_state.inflight_message_id),
378
+ started_at=pending_poll_state.inflight_started_at,
379
+ logged_at=pending_poll_state.inflight_logged_at,
380
+ now=now,
381
+ last_id=pending_poll_state.last_id,
382
+ ),
383
+ ChannelInflightPolicy(
384
+ unseen_retry_seconds=policy.unseen_retry_seconds(),
385
+ waiting_log_interval=30.0,
386
+ is_stale=policy.inflight_is_stale,
387
+ commit_cursor_on_stale=True,
388
+ log_namespace="channel_stdin_proxy",
389
+ stale_event="stale_inflight_skipped",
390
+ ),
391
+ polling.inflight_effects(),
392
+ )
393
+ pending_poll_state.inflight_message_id = inflight_update.message_id
394
+ pending_poll_state.inflight_cursor = inflight_update.cursor
395
+ pending_poll_state.inflight_started_at = inflight_update.started_at
396
+ pending_poll_state.inflight_logged_at = inflight_update.logged_at
397
+ pending_poll_state.pending_recheck = (
398
+ pending_poll_state.pending_recheck or inflight_update.pending_recheck
399
+ )
400
+ pending_poll_state.last_id = inflight_update.last_id
401
+ compact_poll_state = poll_pending_compaction(
402
+ now,
403
+ master_fd,
404
+ channel_enter_bytes,
405
+ pending_poll_state.inflight_message_id,
406
+ compact_poll_state,
407
+ compact_injection_options,
408
+ compact_poll_services,
409
+ )
410
+ pending_poll_state = poll_pending_channel_messages(
411
+ now,
412
+ master_fd,
413
+ channel_enter_bytes,
414
+ pending_poll_state,
415
+ pending_injection_options,
416
+ pending_poll_policy,
417
+ pending_poll_services,
418
+ )
419
+ while True:
420
+ try:
421
+ readable, _, _ = select.select([master_fd], [], [], 0)
422
+ if master_fd not in readable:
423
+ break
424
+ data = os.read(master_fd, 4096)
425
+ if not data:
426
+ break
427
+ terminal.write_all(stdout_fd, data)
428
+ except OSError:
429
+ break
430
+ return proc.returncode if proc.returncode is not None else 0
431
+ finally:
432
+ if sigwinch_installed:
433
+ try:
434
+ signal.signal(signal.SIGWINCH, old_sigwinch)
435
+ except Exception as exc:
436
+ policy.log("WARN", f"channel_sigwinch_restore_failed error={type(exc).__name__}: {exc}")
437
+ try:
438
+ termios.tcsetattr(stdin_fd, termios.TCSADRAIN, old_attrs)
439
+ except Exception as exc:
440
+ policy.log("WARN", f"channel_terminal_restore_failed error={type(exc).__name__}: {exc}")
441
+ terminal.reset_input_mode()
442
+ try:
443
+ os.close(master_fd)
444
+ except Exception as exc:
445
+ policy.log("WARN", f"channel_pty_close_failed error={type(exc).__name__}: {exc}")
446
+ process.terminate_child(proc, "current Codex")
447
+ process.release_child_record(tracked_child_pid_path, proc.pid)
@@ -0,0 +1,166 @@
1
+ """Channel-injected tool context storage and follow-up projection."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import threading
6
+ from dataclasses import dataclass
7
+ from typing import Any, Callable
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class ChannelToolContextPolicy:
12
+ max_inject: int = 8
13
+ prompt_limit: int = 4000
14
+
15
+
16
+ @dataclass(frozen=True, slots=True)
17
+ class ChannelToolContextPorts:
18
+ content_to_text: Callable[[Any], str]
19
+ truncate: Callable[[str, int], str]
20
+ now: Callable[[], float]
21
+ log: Callable[[str, str], None]
22
+
23
+
24
+ class ChannelToolContextRepository:
25
+ """Thread-safe bounded repository keyed by upstream tool-use id."""
26
+
27
+ def __init__(
28
+ self,
29
+ contexts: dict[str, dict[str, Any]] | None = None,
30
+ lock: threading.Lock | None = None,
31
+ limit: int = 200,
32
+ ) -> None:
33
+ self.contexts = contexts if contexts is not None else {}
34
+ self._lock = lock or threading.Lock()
35
+ self._limit = max(1, limit)
36
+
37
+ def store(self, tool_use_id: str, context: dict[str, Any]) -> None:
38
+ with self._lock:
39
+ self.contexts[tool_use_id] = context
40
+ overflow = len(self.contexts) - self._limit
41
+ if overflow <= 0:
42
+ return
43
+ oldest = sorted(
44
+ self.contexts.items(),
45
+ key=lambda item: item[1].get("created_at", 0),
46
+ )[:overflow]
47
+ for old_id, _context in oldest:
48
+ self.contexts.pop(old_id, None)
49
+
50
+ def take_for_body(self, body: dict[str, Any], limit: int) -> list[tuple[str, dict[str, Any]]]:
51
+ found: list[tuple[str, dict[str, Any]]] = []
52
+ with self._lock:
53
+ for message in body.get("messages") or []:
54
+ if not isinstance(message, dict) or message.get("role") != "user":
55
+ continue
56
+ for block in message.get("content") or []:
57
+ if not isinstance(block, dict) or block.get("type") != "tool_result":
58
+ continue
59
+ tool_use_id = str(block.get("tool_use_id") or "")
60
+ context = self.contexts.pop(tool_use_id, None)
61
+ if context:
62
+ found.append((tool_use_id, dict(context)))
63
+ if len(found) >= limit:
64
+ return found
65
+ return found
66
+
67
+
68
+ @dataclass(frozen=True, slots=True)
69
+ class ChannelToolContextService:
70
+ repository: ChannelToolContextRepository
71
+ policy: ChannelToolContextPolicy
72
+ ports: ChannelToolContextPorts
73
+
74
+ def prompt_text(self, body: dict[str, Any]) -> str:
75
+ metadata = body.get("metadata") if isinstance(body.get("metadata"), dict) else {}
76
+ for message in reversed(body.get("messages") or []):
77
+ if not isinstance(message, dict) or message.get("role") != "user":
78
+ continue
79
+ text = self.ports.content_to_text(message.get("content", ""))
80
+ is_channel_prompt = metadata.get("ciel_runtime_channel_injected") or (
81
+ "[external channel input]" in text or "[ciel-runtime channel inbox]" in text
82
+ )
83
+ if is_channel_prompt and text:
84
+ return self.ports.truncate(text, self.policy.prompt_limit)
85
+ return ""
86
+
87
+ def remember(self, source_body: dict[str, Any] | None, tool_use_id: str, tool_name: str, tool_input: Any) -> None:
88
+ if not isinstance(source_body, dict) or not tool_use_id:
89
+ return
90
+ metadata = source_body.get("metadata") if isinstance(source_body.get("metadata"), dict) else {}
91
+ if not metadata.get("ciel_runtime_channel_injected"):
92
+ return
93
+ context = {
94
+ "created_at": self.ports.now(),
95
+ "channel_message_ids": str(metadata.get("ciel_runtime_channel_message_ids") or ""),
96
+ "prompt": self.prompt_text(source_body),
97
+ "tool_name": tool_name,
98
+ "tool_input": self._json_value(tool_input),
99
+ }
100
+ self.repository.store(tool_use_id, context)
101
+ self.ports.log(
102
+ "INFO",
103
+ f"channel_llm_tool_context_stored tool_use_id={tool_use_id} tool={tool_name} "
104
+ f"message_ids={context['channel_message_ids']}",
105
+ )
106
+
107
+ def remember_message(self, source_body: dict[str, Any] | None, message: dict[str, Any]) -> None:
108
+ if not isinstance(message, dict):
109
+ return
110
+ for block in message.get("content") or []:
111
+ if isinstance(block, dict) and block.get("type") == "tool_use":
112
+ self.remember(
113
+ source_body,
114
+ str(block.get("id") or ""),
115
+ str(block.get("name") or "tool"),
116
+ block.get("input"),
117
+ )
118
+
119
+ def inject_followup(self, body: dict[str, Any]) -> dict[str, Any]:
120
+ metadata = body.get("metadata") if isinstance(body.get("metadata"), dict) else {}
121
+ if metadata.get("ciel_runtime_channel_tool_result_followup"):
122
+ return body
123
+ contexts = self.repository.take_for_body(body, self.policy.max_inject)
124
+ if not contexts:
125
+ return body
126
+ parts = [
127
+ "[ciel-runtime channel tool_result follow-up]",
128
+ "tool_result data from a previous channel-injected tool call.",
129
+ ]
130
+ for tool_use_id, context in contexts:
131
+ parts.append(self._context_text(tool_use_id, context))
132
+ projected = dict(body)
133
+ messages = [message for message in body.get("messages", []) if isinstance(message, dict)]
134
+ messages.append({"role": "user", "content": [{"type": "text", "text": "\n\n".join(parts)}]})
135
+ projected["messages"] = messages
136
+ projected["metadata"] = {**metadata, "ciel_runtime_channel_tool_result_followup": True}
137
+ self.ports.log(
138
+ "INFO",
139
+ "channel_llm_tool_result_context_injected tool_use_ids="
140
+ + ",".join(tool_use_id for tool_use_id, _context in contexts),
141
+ )
142
+ return projected
143
+
144
+ @staticmethod
145
+ def _json_value(value: Any) -> Any:
146
+ return value if isinstance(value, (dict, list, str, int, float, bool)) or value is None else str(value)
147
+
148
+ @staticmethod
149
+ def _context_text(tool_use_id: str, context: dict[str, Any]) -> str:
150
+ return "\n".join(
151
+ [
152
+ f"tool_use_id={tool_use_id}",
153
+ f"tool={context.get('tool_name') or 'tool'}",
154
+ f"channel_message_ids={context.get('channel_message_ids') or ''}",
155
+ f"tool_input={json.dumps(context.get('tool_input'), ensure_ascii=False)}",
156
+ f"original_channel_prompt:\n{context.get('prompt') or '(not captured)'}",
157
+ ]
158
+ )
159
+
160
+
161
+ __all__ = [
162
+ "ChannelToolContextPolicy",
163
+ "ChannelToolContextPorts",
164
+ "ChannelToolContextRepository",
165
+ "ChannelToolContextService",
166
+ ]