@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,209 @@
1
+ """Pending channel-message injection application service."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, Callable
7
+
8
+
9
+ @dataclass(frozen=True, slots=True)
10
+ class ChannelInjectionState:
11
+ active_tool_call: Callable[[], bool]
12
+ active_turn: Callable[[], bool]
13
+ recover_cursor: Callable[[int], int]
14
+ pending_scan_limit: Callable[[], int]
15
+ superseded_ids: Callable[..., set[int]]
16
+ message_is_web_chat: Callable[..., bool]
17
+ message_skip_reason: Callable[..., str]
18
+ event_identity_key: Callable[..., tuple[str, ...]]
19
+ wake_state_for_message: Callable[..., str]
20
+ queued_wake_is_stale: Callable[..., bool]
21
+
22
+
23
+ @dataclass(frozen=True, slots=True)
24
+ class ChannelInjectionPrompts:
25
+ llm_delivery: Callable[..., str]
26
+ web_chat: Callable[..., str]
27
+ standard: Callable[..., str]
28
+ enter_bytes: Callable[..., bytes]
29
+ enter_label: Callable[..., str]
30
+
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class ChannelInjectionWakeStore:
34
+ claim_for_nonblocking_scan: Callable[[int], bool]
35
+ claim_prompt: Callable[[int, str], bool]
36
+ clear_claim: Callable[[int], Any]
37
+ release_stale: Callable[[int, bool], Any]
38
+ mark_delivered: Callable[[int], bool]
39
+ record_prompts: Callable[[list[dict[str, Any]], str], Any]
40
+ rollback: Callable[[list[dict[str, Any]], list[int]], Any]
41
+ commit_cursor: Callable[[int], Any]
42
+
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class ChannelInjectionIO:
46
+ inject_lock: Any
47
+ read_messages: Callable[..., list[dict[str, Any]]]
48
+ write_prompt: Callable[..., Any]
49
+ log: Callable[[str, str], Any]
50
+
51
+
52
+ @dataclass(frozen=True, slots=True)
53
+ class ChannelInjectionPolicy:
54
+ wake_batch_limit: Callable[[], int]
55
+
56
+
57
+ @dataclass(frozen=True, slots=True)
58
+ class ChannelInjectionServices:
59
+ state: ChannelInjectionState
60
+ prompts: ChannelInjectionPrompts
61
+ wake_store: ChannelInjectionWakeStore
62
+ io: ChannelInjectionIO
63
+ policy: ChannelInjectionPolicy
64
+
65
+
66
+ def _message_id(message: dict[str, Any]) -> int:
67
+ try:
68
+ return int(message.get("id") or 0)
69
+ except (TypeError, ValueError):
70
+ return 0
71
+
72
+
73
+ def inject_pending_channel_messages(
74
+ master_fd: int,
75
+ last_id: int,
76
+ enter_bytes: bytes | None = None,
77
+ *,
78
+ web_chat_only: bool = False,
79
+ wake_for_llm_delivery: bool = False,
80
+ commit_cursor: bool = True,
81
+ injected_message_ids: list[int] | None = None,
82
+ submit_retry_count: int = 1,
83
+ confirm_submit: bool = False,
84
+ bracketed_paste: bool = False,
85
+ submit_delay_seconds: float | None = None,
86
+ skip_blocking_wake_states: bool = False,
87
+ services: ChannelInjectionServices,
88
+ ) -> int:
89
+ state = services.state
90
+ prompts = services.prompts
91
+ wake_store = services.wake_store
92
+ io = services.io
93
+ with io.inject_lock:
94
+ if state.active_tool_call():
95
+ io.log("INFO", f"channel_stdin_proxy_deferred cursor={last_id} reason=active_tool_call")
96
+ return last_id
97
+ if state.active_turn():
98
+ io.log("INFO", f"channel_stdin_proxy_deferred cursor={last_id} reason=active_turn")
99
+ return last_id
100
+ if not web_chat_only:
101
+ last_id = state.recover_cursor(last_id)
102
+ pending: list[dict[str, Any]] = []
103
+ return_last_id = last_id
104
+ candidates = io.read_messages(last_id, None, None, state.pending_scan_limit())
105
+ superseded_ids = state.superseded_ids(candidates)
106
+ batch_limit = services.policy.wake_batch_limit() if wake_for_llm_delivery else 1
107
+ seen_event_keys: set[tuple[str, ...]] = set()
108
+ for message in candidates:
109
+ previous_last_id = last_id
110
+ message_id = _message_id(message)
111
+ if message_id <= 0:
112
+ continue
113
+ last_id = max(last_id, message_id)
114
+ channel = message.get("channel")
115
+ if web_chat_only and not state.message_is_web_chat(message):
116
+ io.log("INFO", f"channel_stdin_proxy_skipped_noise message_id={message_id} channel={channel} reason=not_web_chat")
117
+ continue
118
+ skip_reason = state.message_skip_reason(message)
119
+ if skip_reason:
120
+ io.log("INFO", f"channel_stdin_proxy_skipped_noise message_id={message_id} channel={channel} reason={skip_reason}")
121
+ continue
122
+ if message_id in superseded_ids:
123
+ io.log("INFO", f"channel_stdin_proxy_skipped_noise message_id={message_id} channel={channel} reason=superseded_channel_notice")
124
+ continue
125
+ event_key = state.event_identity_key(message)
126
+ if event_key and event_key in seen_event_keys:
127
+ io.log("INFO", f"channel_stdin_proxy_skipped_noise message_id={message_id} channel={channel} reason=duplicate_channel_event")
128
+ continue
129
+ if wake_for_llm_delivery:
130
+ message_prompt = prompts.llm_delivery([message])
131
+ elif web_chat_only and state.message_is_web_chat(message):
132
+ message_prompt = prompts.web_chat([message])
133
+ else:
134
+ message_prompt = prompts.standard([message])
135
+ wake_state = state.wake_state_for_message(message, message_prompt)
136
+ if wake_state == "completed":
137
+ io.log("INFO", f"channel_stdin_proxy_skipped_noise message_id={message_id} channel={channel} reason=stdin_wake_completed")
138
+ continue
139
+ if skip_blocking_wake_states and wake_state == "missing" and wake_store.claim_for_nonblocking_scan(message_id):
140
+ io.log("INFO", f"channel_stdin_proxy_skipped_noise message_id={message_id} channel={channel} reason=stdin_wake_claimed_continue")
141
+ continue
142
+ if wake_state in {"pending", "queued"}:
143
+ if wake_state == "queued" and state.queued_wake_is_stale(message, message_prompt):
144
+ wake_store.release_stale(message_id, not web_chat_only)
145
+ io.log("WARN", f"channel_stdin_proxy_skipped_noise message_id={message_id} channel={channel} reason=stale_queued_wake")
146
+ continue
147
+ if skip_blocking_wake_states:
148
+ io.log("INFO", f"channel_stdin_proxy_skipped_noise message_id={message_id} channel={channel} reason=stdin_wake_{wake_state}_continue")
149
+ continue
150
+ io.log("INFO", f"channel_stdin_proxy_waiting_for_turn_completion message_id={message_id} channel={channel} state={wake_state}")
151
+ if pending:
152
+ break
153
+ return previous_last_id
154
+ if not wake_for_llm_delivery and not wake_store.mark_delivered(message_id):
155
+ io.log("INFO", f"channel_stdin_proxy_skipped_noise message_id={message_id} channel={channel} reason=stdin_wake_delivered")
156
+ continue
157
+ pending.append(message)
158
+ if event_key:
159
+ seen_event_keys.add(event_key)
160
+ if wake_for_llm_delivery and len(pending) == 1:
161
+ return_last_id = previous_last_id
162
+ last_id = message_id
163
+ if len(pending) >= batch_limit:
164
+ break
165
+ if not pending:
166
+ return last_id
167
+ if wake_for_llm_delivery:
168
+ prompt = prompts.llm_delivery(pending)
169
+ elif web_chat_only and all(state.message_is_web_chat(message) for message in pending):
170
+ prompt = prompts.web_chat(pending)
171
+ else:
172
+ prompt = prompts.standard(pending)
173
+ claimed_ids: list[int] = []
174
+ if wake_for_llm_delivery:
175
+ for message in pending:
176
+ claim_id = _message_id(message)
177
+ if claim_id <= 0:
178
+ continue
179
+ if not wake_store.claim_prompt(claim_id, prompt):
180
+ io.log("INFO", f"channel_stdin_proxy_skipped_noise message_id={claim_id} channel={message.get('channel')} reason=stdin_wake_claimed")
181
+ return return_last_id
182
+ claimed_ids.append(claim_id)
183
+ submit_bytes = prompts.enter_bytes(enter_bytes)
184
+ try:
185
+ io.write_prompt(
186
+ master_fd,
187
+ prompt,
188
+ submit_bytes,
189
+ submit_retry_count=submit_retry_count,
190
+ confirm_submit=confirm_submit,
191
+ bracketed_paste=bracketed_paste,
192
+ submit_delay_seconds=submit_delay_seconds,
193
+ )
194
+ wake_store.record_prompts(pending, prompt)
195
+ except Exception:
196
+ wake_store.rollback(pending, claimed_ids)
197
+ raise
198
+ if not web_chat_only:
199
+ if commit_cursor:
200
+ wake_store.commit_cursor(last_id)
201
+ if injected_message_ids is not None:
202
+ injected_message_ids.extend(message_id for message in pending if (message_id := _message_id(message)) > 0)
203
+ ids = ",".join(str(message.get("id") or "") for message in pending)
204
+ channels = ",".join(sorted({str(message.get("channel") or "default") for message in pending}))
205
+ io.log(
206
+ "INFO",
207
+ f"channel_stdin_proxy_injected count={len(pending)} message_ids={ids} channels={channels} enter={prompts.enter_label(submit_bytes)} commit_cursor={commit_cursor}",
208
+ )
209
+ return return_last_id if wake_for_llm_delivery else last_id
@@ -0,0 +1,109 @@
1
+ """Shared polling state machine for pending channel message injection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
+
9
+
10
+ @dataclass(slots=True)
11
+ class ChannelPendingPollState:
12
+ last_id: int
13
+ last_marker: tuple[float, int] = (0.0, -1)
14
+ last_poll_at: float = 0.0
15
+ pending_recheck: bool = False
16
+ defer_logged_at: float = 0.0
17
+ inflight_message_id: int | None = None
18
+ inflight_cursor: int | None = None
19
+ inflight_logged_at: float = 0.0
20
+ inflight_started_at: float = 0.0
21
+
22
+
23
+ @dataclass(frozen=True, slots=True)
24
+ class ChannelPendingInjectionOptions:
25
+ enabled: bool
26
+ web_chat_only: bool
27
+ wake_for_llm_delivery: bool
28
+ submit_retry_count: int
29
+ confirm_submit: bool
30
+ bracketed_paste: bool
31
+ submit_delay_seconds: float | None
32
+
33
+
34
+ @dataclass(frozen=True, slots=True)
35
+ class ChannelPendingPollPolicy:
36
+ log_namespace: str
37
+ active_reason: str
38
+ poll_interval_seconds: float = 0.5
39
+ defer_log_interval_seconds: float = 30.0
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class ChannelPendingPollServices:
44
+ file_marker: Callable[[], tuple[float, int]]
45
+ should_check: Callable[[tuple[float, int], tuple[float, int], bool, int | None], bool]
46
+ active: Callable[[], bool]
47
+ ensure_cursor: Callable[[], int]
48
+ inject_pending: Callable[..., int]
49
+ log: Callable[[str, str], Any]
50
+
51
+
52
+ def poll_pending_channel_messages(
53
+ now: float,
54
+ writer: Any,
55
+ enter_bytes: bytes,
56
+ state: ChannelPendingPollState,
57
+ options: ChannelPendingInjectionOptions,
58
+ policy: ChannelPendingPollPolicy,
59
+ services: ChannelPendingPollServices,
60
+ *,
61
+ input_ready: bool = True,
62
+ ) -> ChannelPendingPollState:
63
+ if not input_ready or now - state.last_poll_at < policy.poll_interval_seconds:
64
+ return state
65
+ state.last_poll_at = now
66
+ marker = services.file_marker()
67
+ if not options.enabled or not services.should_check(
68
+ marker,
69
+ state.last_marker,
70
+ state.pending_recheck,
71
+ state.inflight_message_id,
72
+ ):
73
+ return state
74
+
75
+ if services.active():
76
+ state.pending_recheck = True
77
+ if now - state.defer_logged_at >= policy.defer_log_interval_seconds:
78
+ state.defer_logged_at = now
79
+ services.log(
80
+ "INFO",
81
+ f"{policy.log_namespace}_deferred cursor={state.last_id} reason={policy.active_reason}",
82
+ )
83
+ return state
84
+
85
+ if marker != state.last_marker:
86
+ state.last_marker = marker
87
+ state.pending_recheck = False
88
+ state.last_id = max(state.last_id, services.ensure_cursor())
89
+ injected_ids: list[int] = []
90
+ state.last_id = services.inject_pending(
91
+ writer,
92
+ state.last_id,
93
+ enter_bytes,
94
+ web_chat_only=options.web_chat_only,
95
+ wake_for_llm_delivery=options.wake_for_llm_delivery,
96
+ commit_cursor=False,
97
+ injected_message_ids=injected_ids,
98
+ submit_retry_count=options.submit_retry_count,
99
+ confirm_submit=options.confirm_submit,
100
+ bracketed_paste=options.bracketed_paste,
101
+ submit_delay_seconds=options.submit_delay_seconds,
102
+ skip_blocking_wake_states=state.inflight_message_id is not None,
103
+ )
104
+ if injected_ids:
105
+ state.inflight_message_id = injected_ids[-1]
106
+ state.inflight_cursor = state.last_id
107
+ state.inflight_logged_at = now
108
+ state.inflight_started_at = now
109
+ return state