@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.
- package/README.md +132 -0
- package/ciel-runtime-menu.py +56 -6
- package/ciel_runtime.py +9379 -35909
- package/ciel_runtime_support/advisor_client.py +193 -0
- package/ciel_runtime_support/advisor_policy.py +320 -0
- package/ciel_runtime_support/advisor_refinement.py +160 -0
- package/ciel_runtime_support/advisor_request_builder.py +261 -0
- package/ciel_runtime_support/agy_installer.py +169 -0
- package/ciel_runtime_support/agy_mcp_restore.py +182 -0
- package/ciel_runtime_support/anthropic_model_policy.py +186 -0
- package/ciel_runtime_support/anthropic_response_writer.py +255 -0
- package/ciel_runtime_support/anthropic_tool_turns.py +130 -0
- package/ciel_runtime_support/api_key_cooldown.py +159 -0
- package/ciel_runtime_support/architecture.py +488 -1
- package/ciel_runtime_support/architecture_budget.py +42 -0
- package/ciel_runtime_support/channel_backlog.py +90 -0
- package/ciel_runtime_support/channel_cli.py +119 -0
- package/ciel_runtime_support/channel_compact_injection.py +82 -0
- package/ciel_runtime_support/channel_compact_poll.py +67 -0
- package/ciel_runtime_support/channel_compact_request_repository.py +113 -0
- package/ciel_runtime_support/channel_config_service.py +281 -0
- package/ciel_runtime_support/channel_connection_lifecycle.py +180 -0
- package/ciel_runtime_support/channel_connection_registry.py +128 -0
- package/ciel_runtime_support/channel_connection_worker.py +284 -0
- package/ciel_runtime_support/channel_cursor_recovery.py +92 -0
- package/ciel_runtime_support/channel_cursor_repository.py +89 -0
- package/ciel_runtime_support/channel_cursor_service.py +178 -0
- package/ciel_runtime_support/channel_event_identity.py +212 -0
- package/ciel_runtime_support/channel_event_projection.py +315 -0
- package/ciel_runtime_support/channel_inflight.py +127 -0
- package/ciel_runtime_support/channel_injection.py +115 -0
- package/ciel_runtime_support/channel_launch_guard_repository.py +58 -0
- package/ciel_runtime_support/channel_launch_policy.py +180 -0
- package/ciel_runtime_support/channel_llm_context.py +156 -0
- package/ciel_runtime_support/channel_mcp_discovery.py +186 -0
- package/ciel_runtime_support/channel_mcp_http_controller.py +239 -0
- package/ciel_runtime_support/channel_mcp_ownership.py +148 -0
- package/ciel_runtime_support/channel_mcp_tools.py +240 -0
- package/ciel_runtime_support/channel_mcp_transport.py +394 -0
- package/ciel_runtime_support/channel_message_dedupe.py +65 -0
- package/ciel_runtime_support/channel_message_policy.py +256 -0
- package/ciel_runtime_support/channel_message_prompt.py +305 -0
- package/ciel_runtime_support/channel_message_repository.py +234 -0
- package/ciel_runtime_support/channel_notification_projection.py +217 -0
- package/ciel_runtime_support/channel_panel.py +162 -0
- package/ciel_runtime_support/channel_pending_injection.py +209 -0
- package/ciel_runtime_support/channel_pending_poll.py +109 -0
- package/ciel_runtime_support/channel_probe_cache.py +433 -0
- package/ciel_runtime_support/channel_probe_report.py +101 -0
- package/ciel_runtime_support/channel_runtime_environment.py +181 -0
- package/ciel_runtime_support/channel_session_lifecycle.py +114 -0
- package/ciel_runtime_support/channel_session_repository.py +90 -0
- package/ciel_runtime_support/channel_terminal_dispatch.py +115 -0
- package/ciel_runtime_support/channel_terminal_input.py +277 -0
- package/ciel_runtime_support/channel_terminal_proxy.py +447 -0
- package/ciel_runtime_support/channel_tool_context.py +166 -0
- package/ciel_runtime_support/channel_transcript.py +414 -0
- package/ciel_runtime_support/channel_transcript_repository.py +96 -0
- package/ciel_runtime_support/channel_wake_claim_repository.py +126 -0
- package/ciel_runtime_support/channel_wake_delivery_repository.py +88 -0
- package/ciel_runtime_support/chat_files.py +138 -0
- package/ciel_runtime_support/chat_http_controller.py +235 -0
- package/ciel_runtime_support/claude_environment.py +375 -0
- package/ciel_runtime_support/claude_router.py +247 -193
- package/ciel_runtime_support/cli_dispatch.py +792 -0
- package/ciel_runtime_support/cli_parser.py +165 -0
- package/ciel_runtime_support/cli_usage.py +100 -0
- package/ciel_runtime_support/codex_app_server.py +20 -5
- package/ciel_runtime_support/codex_channel_sse_launch.py +87 -0
- package/ciel_runtime_support/codex_cli.py +42 -6
- package/ciel_runtime_support/codex_config.py +323 -0
- package/ciel_runtime_support/codex_launch_configuration.py +240 -0
- package/ciel_runtime_support/codex_launch_policy.py +66 -0
- package/ciel_runtime_support/codex_mcp_integration.py +195 -0
- package/ciel_runtime_support/codex_mcp_restore.py +304 -0
- package/ciel_runtime_support/codex_model_catalog.py +133 -0
- package/ciel_runtime_support/codex_process_lifecycle.py +271 -0
- package/ciel_runtime_support/codex_router.py +147 -1
- package/ciel_runtime_support/codex_session_repository.py +115 -0
- package/ciel_runtime_support/codex_session_selection.py +114 -0
- package/ciel_runtime_support/command_asset_installer.py +103 -0
- package/ciel_runtime_support/compatibility_probe.py +295 -0
- package/ciel_runtime_support/compatibility_protocol.py +251 -0
- package/ciel_runtime_support/compatibility_runtime.py +166 -0
- package/ciel_runtime_support/compatibility_test.py +370 -0
- package/ciel_runtime_support/config_migrations.py +307 -0
- package/ciel_runtime_support/config_repository.py +175 -0
- package/ciel_runtime_support/config_value_codec.py +64 -0
- package/ciel_runtime_support/configuration_cli.py +374 -0
- package/ciel_runtime_support/context_compaction.py +280 -0
- package/ciel_runtime_support/context_setup.py +208 -0
- package/ciel_runtime_support/context_summary_policy.py +392 -0
- package/ciel_runtime_support/credential_cli.py +104 -0
- package/ciel_runtime_support/credential_management.py +261 -0
- package/ciel_runtime_support/credentials.py +269 -0
- package/ciel_runtime_support/executable_discovery.py +141 -0
- package/ciel_runtime_support/github_copilot_oauth.py +335 -0
- package/ciel_runtime_support/github_copilot_oauth_runtime.py +213 -0
- package/ciel_runtime_support/header_forwarding.py +73 -0
- package/ciel_runtime_support/headless_config.py +221 -0
- package/ciel_runtime_support/http_response.py +129 -0
- package/ciel_runtime_support/install_diagnostics.py +149 -0
- package/ciel_runtime_support/kimi_identity.py +123 -0
- package/ciel_runtime_support/launch_diagnostics.py +204 -0
- package/ciel_runtime_support/launch_state.py +127 -0
- package/ciel_runtime_support/live_api_key_controller.py +58 -0
- package/ciel_runtime_support/llm_config_http.py +148 -0
- package/ciel_runtime_support/llm_option_config.py +259 -0
- package/ciel_runtime_support/llm_presentation_data.py +447 -0
- package/ciel_runtime_support/llm_presets.py +773 -0
- package/ciel_runtime_support/lm_studio_runtime.py +401 -0
- package/ciel_runtime_support/managed_mcp_config.py +144 -0
- package/ciel_runtime_support/managed_mcp_discovery.py +151 -0
- package/ciel_runtime_support/managed_service_cleanup.py +89 -0
- package/ciel_runtime_support/mcp_config_reader.py +230 -0
- package/ciel_runtime_support/mcp_http_proxy.py +607 -0
- package/ciel_runtime_support/mcp_inventory.py +59 -0
- package/ciel_runtime_support/mcp_notification_wait_policy.py +159 -0
- package/ciel_runtime_support/mcp_probe_codec.py +136 -0
- package/ciel_runtime_support/mcp_probe_transport.py +328 -0
- package/ciel_runtime_support/mcp_proxy_codec.py +345 -0
- package/ciel_runtime_support/mcp_proxy_config.py +107 -0
- package/ciel_runtime_support/mcp_proxy_notifications.py +261 -0
- package/ciel_runtime_support/mcp_proxy_process.py +560 -0
- package/ciel_runtime_support/mcp_split_proxy_http.py +165 -0
- package/ciel_runtime_support/mcp_stdio_probe.py +240 -0
- package/ciel_runtime_support/mcp_transport.py +146 -0
- package/ciel_runtime_support/model_cache_lifecycle.py +78 -0
- package/ciel_runtime_support/model_catalog_projection.py +61 -0
- package/ciel_runtime_support/model_context_hints.py +109 -0
- package/ciel_runtime_support/model_panel.py +147 -0
- package/ciel_runtime_support/model_registry_repository.py +231 -0
- package/ciel_runtime_support/npm_runtime.py +191 -0
- package/ciel_runtime_support/ollama_catalog.py +462 -0
- package/ciel_runtime_support/ollama_catalog_cli.py +41 -0
- package/ciel_runtime_support/ollama_catalog_repository.py +75 -0
- package/ciel_runtime_support/ollama_context_sync.py +87 -0
- package/ciel_runtime_support/ollama_forwarding.py +449 -0
- package/ciel_runtime_support/openai_chat_passthrough.py +67 -0
- package/ciel_runtime_support/openai_chat_router.py +64 -0
- package/ciel_runtime_support/openai_forwarding.py +194 -0
- package/ciel_runtime_support/openai_responses_router.py +291 -0
- package/ciel_runtime_support/openai_responses_stream.py +135 -0
- package/ciel_runtime_support/output_budget.py +89 -0
- package/ciel_runtime_support/package_lifecycle.py +217 -0
- package/ciel_runtime_support/plan_artifact_controller.py +104 -0
- package/ciel_runtime_support/prelaunch.py +959 -0
- package/ciel_runtime_support/prelaunch_launch_preference.py +75 -0
- package/ciel_runtime_support/prelaunch_panel_projection.py +396 -0
- package/ciel_runtime_support/prelaunch_terminal.py +764 -0
- package/ciel_runtime_support/process_control.py +708 -0
- package/ciel_runtime_support/prompt_compaction.py +322 -0
- package/ciel_runtime_support/prompt_injection.py +176 -0
- package/ciel_runtime_support/protocols/__init__.py +24 -0
- package/ciel_runtime_support/protocols/anthropic_content.py +29 -0
- package/ciel_runtime_support/protocols/anthropic_thinking_policy.py +336 -0
- package/ciel_runtime_support/protocols/chat_projection.py +315 -0
- package/ciel_runtime_support/protocols/conversation_policy.py +217 -0
- package/ciel_runtime_support/protocols/conversation_turn_policy.py +972 -0
- package/ciel_runtime_support/protocols/ollama_chat.py +111 -0
- package/ciel_runtime_support/protocols/ollama_response.py +231 -0
- package/ciel_runtime_support/protocols/openai_reasoning.py +75 -0
- package/ciel_runtime_support/protocols/openai_responses.py +271 -0
- package/ciel_runtime_support/protocols/pseudo_tool_history.py +248 -0
- package/ciel_runtime_support/protocols/tool_result_projection.py +112 -0
- package/ciel_runtime_support/provider_adapters.py +160 -0
- package/ciel_runtime_support/provider_catalog_sources.py +316 -0
- package/ciel_runtime_support/provider_choice.py +201 -0
- package/ciel_runtime_support/provider_compatibility.py +165 -0
- package/ciel_runtime_support/provider_config_mutations.py +361 -0
- package/ciel_runtime_support/provider_configuration_service.py +162 -0
- package/ciel_runtime_support/provider_context.py +308 -0
- package/ciel_runtime_support/provider_contract_projection.py +73 -0
- package/ciel_runtime_support/provider_descriptor.py +82 -0
- package/ciel_runtime_support/provider_endpoint_policy.py +130 -0
- package/ciel_runtime_support/provider_endpoint_probe.py +165 -0
- package/ciel_runtime_support/provider_launch_endpoint.py +109 -0
- package/ciel_runtime_support/provider_limits.py +457 -0
- package/ciel_runtime_support/provider_model_identity.py +139 -0
- package/ciel_runtime_support/provider_model_selection.py +431 -0
- package/ciel_runtime_support/provider_model_specs.py +142 -0
- package/ciel_runtime_support/provider_models.py +263 -0
- package/ciel_runtime_support/provider_network.py +176 -0
- package/ciel_runtime_support/provider_option_cli.py +238 -0
- package/ciel_runtime_support/provider_option_panel.py +275 -0
- package/ciel_runtime_support/provider_option_status.py +192 -0
- package/ciel_runtime_support/provider_policy.py +101 -0
- package/ciel_runtime_support/provider_query_policy.py +67 -0
- package/ciel_runtime_support/provider_readiness.py +112 -0
- package/ciel_runtime_support/provider_request_access.py +131 -0
- package/ciel_runtime_support/provider_request_builder.py +250 -0
- package/ciel_runtime_support/provider_responses_passthrough.py +81 -0
- package/ciel_runtime_support/provider_runtime_info.py +113 -0
- package/ciel_runtime_support/provider_runtime_modes.py +150 -0
- package/ciel_runtime_support/provider_sampling_policy.py +46 -0
- package/ciel_runtime_support/provider_status.py +145 -0
- package/ciel_runtime_support/provider_timeout_policy.py +184 -0
- package/ciel_runtime_support/provider_tool_policy.py +145 -0
- package/ciel_runtime_support/providers/__init__.py +65 -0
- package/ciel_runtime_support/providers/anthropic.py +160 -0
- package/ciel_runtime_support/providers/anthropic_catalog.py +119 -0
- package/ciel_runtime_support/providers/base.py +232 -0
- package/ciel_runtime_support/providers/catalog.py +326 -0
- package/ciel_runtime_support/providers/cloud.py +194 -0
- package/ciel_runtime_support/providers/constants.py +54 -0
- package/ciel_runtime_support/providers/deepseek.py +115 -0
- package/ciel_runtime_support/providers/fireworks.py +158 -0
- package/ciel_runtime_support/providers/github_copilot_oauth.py +199 -0
- package/ciel_runtime_support/providers/kimi.py +297 -0
- package/ciel_runtime_support/providers/lm_studio.py +76 -0
- package/ciel_runtime_support/providers/meta.py +257 -0
- package/ciel_runtime_support/providers/native.py +194 -0
- package/ciel_runtime_support/providers/nim.py +69 -0
- package/ciel_runtime_support/providers/nvidia.py +158 -0
- package/ciel_runtime_support/providers/nvidia_runtime.py +285 -0
- package/ciel_runtime_support/providers/ollama.py +181 -0
- package/ciel_runtime_support/providers/ollama_context.py +195 -0
- package/ciel_runtime_support/providers/ollama_runtime.py +213 -0
- package/ciel_runtime_support/providers/opencode.py +220 -0
- package/ciel_runtime_support/providers/opencode_go.py +37 -0
- package/ciel_runtime_support/providers/openrouter.py +57 -0
- package/ciel_runtime_support/providers/vllm.py +65 -0
- package/ciel_runtime_support/providers/zai.py +119 -0
- package/ciel_runtime_support/pseudo_tool_parser.py +115 -0
- package/ciel_runtime_support/rate_limit_policy.py +117 -0
- package/ciel_runtime_support/rate_limit_repository.py +154 -0
- package/ciel_runtime_support/registry.py +46 -0
- package/ciel_runtime_support/request_shortcuts.py +253 -0
- package/ciel_runtime_support/request_trace.py +323 -0
- package/ciel_runtime_support/response_collection.py +209 -0
- package/ciel_runtime_support/router_access.py +238 -0
- package/ciel_runtime_support/router_client_lifecycle.py +366 -0
- package/ciel_runtime_support/router_health_policy.py +101 -0
- package/ciel_runtime_support/router_http.py +513 -0
- package/ciel_runtime_support/router_process_lifecycle.py +401 -0
- package/ciel_runtime_support/router_rate_limit_service.py +285 -0
- package/ciel_runtime_support/router_server_runtime.py +103 -0
- package/ciel_runtime_support/router_shortcuts.py +201 -0
- package/ciel_runtime_support/routing_fallback.py +73 -0
- package/ciel_runtime_support/runtime_activity_repository.py +143 -0
- package/ciel_runtime_support/runtime_adapters.py +104 -0
- package/ciel_runtime_support/runtime_command_factory.py +73 -0
- package/ciel_runtime_support/runtime_compatibility.py +50 -0
- package/ciel_runtime_support/runtime_constants.py +178 -0
- package/ciel_runtime_support/runtime_launch.py +1602 -0
- package/ciel_runtime_support/runtime_llm_options.py +312 -0
- package/ciel_runtime_support/runtime_logging.py +161 -0
- package/ciel_runtime_support/runtime_paths.py +157 -0
- package/ciel_runtime_support/runtime_restart.py +84 -0
- package/ciel_runtime_support/runtime_upgrade.py +149 -0
- package/ciel_runtime_support/secure_json_repository.py +55 -0
- package/ciel_runtime_support/session_import.py +356 -0
- package/ciel_runtime_support/settings_repository.py +8 -0
- package/ciel_runtime_support/slash_command_assets.py +211 -0
- package/ciel_runtime_support/sse_stream.py +57 -0
- package/ciel_runtime_support/sse_trace.py +225 -0
- package/ciel_runtime_support/statusline_script.py +593 -0
- package/ciel_runtime_support/statusline_settings.py +53 -0
- package/ciel_runtime_support/stream_chunk_policy.py +18 -0
- package/ciel_runtime_support/streaming_anthropic.py +1955 -0
- package/ciel_runtime_support/synthetic_tool_policy.py +105 -0
- package/ciel_runtime_support/terminal_platform_io.py +127 -0
- package/ciel_runtime_support/timeout_profile.py +196 -0
- package/ciel_runtime_support/tool_dialects.py +85 -0
- package/ciel_runtime_support/tool_exposure_policy.py +63 -0
- package/ciel_runtime_support/tool_guard_hooks.py +218 -0
- package/ciel_runtime_support/tool_request_projection.py +96 -0
- package/ciel_runtime_support/tool_schema.py +483 -0
- package/ciel_runtime_support/tool_side_effect_dedupe.py +95 -0
- package/ciel_runtime_support/ui_text.py +266 -0
- package/ciel_runtime_support/upstream_error_policy.py +104 -0
- package/ciel_runtime_support/upstream_retry.py +419 -0
- package/ciel_runtime_support/upstream_stream_io.py +106 -0
- package/ciel_runtime_support/usage_events.py +96 -0
- package/ciel_runtime_support/visible_stream_filters.py +130 -0
- package/ciel_runtime_support/web_endpoints.py +447 -0
- package/ciel_runtime_support/web_ui.py +915 -0
- package/ciel_runtime_support/web_ui_controller.py +189 -0
- package/ciel_runtime_support/windows_console_input.py +137 -0
- package/ciel_runtime_support/windows_console_mode.py +112 -0
- package/docs/Architecture.md +54 -0
- package/docs/Configuration.md +17 -0
- package/docs/Module-Map.md +1093 -26
- package/docs/Providers.md +46 -1
- package/docs/adr/0001-runtime-bounded-contexts.md +295 -0
- package/npm-bin/run-ciel-runtime.js +22 -2
- package/package.json +9 -2
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Enforce the bounded-context migration's monotonically decreasing budget."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
FINAL_FILE_LINE_BUDGET = 4_999
|
|
9
|
+
MAIN_FILE_LINE_BUDGET = 12_989
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def production_python_files(root: Path) -> tuple[Path, ...]:
|
|
13
|
+
support = tuple(sorted((root / "ciel_runtime_support").rglob("*.py")))
|
|
14
|
+
entrypoints = (
|
|
15
|
+
root / "ciel_runtime.py",
|
|
16
|
+
root / "ciel-runtime-menu.py",
|
|
17
|
+
root / "ciel-runtime-tool-guard.py",
|
|
18
|
+
)
|
|
19
|
+
return tuple(path for path in (*entrypoints, *support) if path.is_file())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def physical_line_count(path: Path) -> int:
|
|
23
|
+
return len(path.read_text(encoding="utf-8").splitlines())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def architecture_budget_violations(root: Path) -> tuple[str, ...]:
|
|
27
|
+
violations: list[str] = []
|
|
28
|
+
main = root / "ciel_runtime.py"
|
|
29
|
+
main_lines = physical_line_count(main)
|
|
30
|
+
if main_lines > MAIN_FILE_LINE_BUDGET:
|
|
31
|
+
violations.append(
|
|
32
|
+
f"ciel_runtime.py has {main_lines} lines; ratchet budget is {MAIN_FILE_LINE_BUDGET}"
|
|
33
|
+
)
|
|
34
|
+
for path in production_python_files(root):
|
|
35
|
+
if path == main:
|
|
36
|
+
continue
|
|
37
|
+
lines = physical_line_count(path)
|
|
38
|
+
if lines > FINAL_FILE_LINE_BUDGET:
|
|
39
|
+
violations.append(
|
|
40
|
+
f"{path.relative_to(root)} has {lines} lines; file budget is {FINAL_FILE_LINE_BUDGET}"
|
|
41
|
+
)
|
|
42
|
+
return tuple(violations)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Application service for inspecting and discarding transient channel backlog."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class ChannelBacklogCursors:
|
|
10
|
+
scan_tail: Callable[[], int]
|
|
11
|
+
llm_lock: Any
|
|
12
|
+
read_llm: Callable[[], int]
|
|
13
|
+
write_llm: Callable[[int], None]
|
|
14
|
+
cache_llm: Callable[[int], None]
|
|
15
|
+
write_clear_floor: Callable[[int], None]
|
|
16
|
+
mcp_lock: Any
|
|
17
|
+
read_mcp: Callable[[], int]
|
|
18
|
+
write_mcp: Callable[[int], None]
|
|
19
|
+
cache_mcp: Callable[[int], None]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True, slots=True)
|
|
23
|
+
class ChannelBacklogRuntime:
|
|
24
|
+
recovery_cache: dict[Any, Any]
|
|
25
|
+
session_lock: Any
|
|
26
|
+
sessions: dict[Any, dict[str, Any]]
|
|
27
|
+
condition: Any
|
|
28
|
+
log: Callable[[str, str], None]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class ChannelBacklogService:
|
|
33
|
+
cursors: ChannelBacklogCursors
|
|
34
|
+
runtime: ChannelBacklogRuntime
|
|
35
|
+
|
|
36
|
+
def clear(self) -> dict[str, Any]:
|
|
37
|
+
tail = max(0, self.cursors.scan_tail())
|
|
38
|
+
with self.cursors.llm_lock:
|
|
39
|
+
old_llm = self.cursors.read_llm()
|
|
40
|
+
self.cursors.cache_llm(tail)
|
|
41
|
+
self._write_cursor("channel_llm_cursor", self.cursors.write_llm, tail)
|
|
42
|
+
self._write_cursor("channel_llm_clear_floor", self.cursors.write_clear_floor, tail)
|
|
43
|
+
self.runtime.recovery_cache.clear()
|
|
44
|
+
with self.cursors.mcp_lock:
|
|
45
|
+
old_mcp = self.cursors.read_mcp()
|
|
46
|
+
self.cursors.cache_mcp(tail)
|
|
47
|
+
self._write_cursor("channel_mcp_cursor", self.cursors.write_mcp, tail)
|
|
48
|
+
with self.runtime.session_lock:
|
|
49
|
+
for state in self.runtime.sessions.values():
|
|
50
|
+
try:
|
|
51
|
+
state["last_id"] = max(int(state.get("last_id") or 0), tail)
|
|
52
|
+
except (TypeError, ValueError, OverflowError):
|
|
53
|
+
state["last_id"] = tail
|
|
54
|
+
session_count = len(self.runtime.sessions)
|
|
55
|
+
with self.runtime.condition:
|
|
56
|
+
self.runtime.condition.notify_all()
|
|
57
|
+
stats = {
|
|
58
|
+
"chat_tail": tail,
|
|
59
|
+
"discarded_llm": max(0, tail - int(old_llm or 0)),
|
|
60
|
+
"discarded_mcp": max(0, tail - int(old_mcp or 0)),
|
|
61
|
+
"mcp_sessions_updated": session_count,
|
|
62
|
+
}
|
|
63
|
+
self.runtime.log(
|
|
64
|
+
"INFO",
|
|
65
|
+
f"channel_backlog_cleared chat_tail={tail} discarded_llm={stats['discarded_llm']} "
|
|
66
|
+
f"discarded_mcp={stats['discarded_mcp']} mcp_sessions_updated={session_count}",
|
|
67
|
+
)
|
|
68
|
+
return stats
|
|
69
|
+
|
|
70
|
+
def status(self) -> dict[str, Any]:
|
|
71
|
+
tail = max(0, self.cursors.scan_tail())
|
|
72
|
+
with self.cursors.llm_lock:
|
|
73
|
+
llm_cursor = self.cursors.read_llm()
|
|
74
|
+
with self.cursors.mcp_lock:
|
|
75
|
+
mcp_cursor = self.cursors.read_mcp()
|
|
76
|
+
return {
|
|
77
|
+
"chat_tail": tail,
|
|
78
|
+
"pending_llm": max(0, tail - int(llm_cursor or 0)),
|
|
79
|
+
"pending_mcp": max(0, tail - int(mcp_cursor or 0)),
|
|
80
|
+
"mcp_sessions": len(self.runtime.sessions),
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
def _write_cursor(self, name: str, writer: Callable[[int], None], tail: int) -> None:
|
|
84
|
+
try:
|
|
85
|
+
writer(tail)
|
|
86
|
+
except Exception as exc:
|
|
87
|
+
self.runtime.log("WARN", f"{name}_write_failed error={type(exc).__name__}: {exc}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
__all__ = ["ChannelBacklogCursors", "ChannelBacklogRuntime", "ChannelBacklogService"]
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""CLI controller for Channel configuration and capability probing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Callable, Mapping
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class ChannelCliView:
|
|
11
|
+
load_config: Callable[[], dict[str, Any]]
|
|
12
|
+
status_text: Callable[[dict[str, Any]], str]
|
|
13
|
+
delivery_mode: Callable[[dict[str, Any] | None], str]
|
|
14
|
+
configured_specs: Callable[[dict[str, Any]], list[str]]
|
|
15
|
+
official_plugins: Mapping[str, str]
|
|
16
|
+
output: Callable[[str], None]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class ChannelCliCommands:
|
|
21
|
+
add: Callable[[str], list[str]]
|
|
22
|
+
development: Callable[[bool], list[str]]
|
|
23
|
+
remove: Callable[[str], list[str]]
|
|
24
|
+
clear: Callable[[], list[str]]
|
|
25
|
+
refresh: Callable[[], dict[str, Any]]
|
|
26
|
+
report: Callable[[dict[str, Any]], list[str]]
|
|
27
|
+
set_delivery: Callable[[Any], list[str]]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ChannelCliController:
|
|
31
|
+
def __init__(self, view: ChannelCliView, commands: ChannelCliCommands) -> None:
|
|
32
|
+
self.view = view
|
|
33
|
+
self.commands = commands
|
|
34
|
+
|
|
35
|
+
def run(self, args: Any) -> None:
|
|
36
|
+
config = self.view.load_config()
|
|
37
|
+
values = list(getattr(args, "values", []) or [])
|
|
38
|
+
if not values:
|
|
39
|
+
self._status(config)
|
|
40
|
+
return
|
|
41
|
+
head = values[0].strip().lower()
|
|
42
|
+
if head in ("on", "enable", "add"):
|
|
43
|
+
self._require_value(values, "Usage: ciel-runtime channels add CHANNEL_SPEC")
|
|
44
|
+
self._lines(self.commands.add(values[1]))
|
|
45
|
+
return
|
|
46
|
+
if head in ("dev", "development"):
|
|
47
|
+
if len(values) >= 2 and values[1].lower() in (
|
|
48
|
+
"on",
|
|
49
|
+
"off",
|
|
50
|
+
"true",
|
|
51
|
+
"false",
|
|
52
|
+
"1",
|
|
53
|
+
"0",
|
|
54
|
+
):
|
|
55
|
+
self._lines(self.commands.development(True))
|
|
56
|
+
return
|
|
57
|
+
self._require_value(values, "Usage: ciel-runtime channels add CHANNEL_SPEC")
|
|
58
|
+
self._lines(self.commands.add(values[1]))
|
|
59
|
+
return
|
|
60
|
+
if head in ("off", "disable", "remove", "rm"):
|
|
61
|
+
self._require_value(values, "Usage: ciel-runtime channels remove CHANNEL_SPEC")
|
|
62
|
+
self._lines(self.commands.remove(values[1]))
|
|
63
|
+
return
|
|
64
|
+
if head in ("clear", "reset"):
|
|
65
|
+
self._lines(self.commands.clear())
|
|
66
|
+
return
|
|
67
|
+
if head in ("detect", "probe", "refresh"):
|
|
68
|
+
try:
|
|
69
|
+
result = self.commands.refresh()
|
|
70
|
+
except Exception as exc:
|
|
71
|
+
raise SystemExit(
|
|
72
|
+
f"Channel probe failed: {type(exc).__name__}: {exc}"
|
|
73
|
+
) from exc
|
|
74
|
+
self._lines(self.commands.report(result))
|
|
75
|
+
return
|
|
76
|
+
if head in ("delivery", "mode"):
|
|
77
|
+
if len(values) < 2:
|
|
78
|
+
self.view.output(f"channel_delivery: {self.view.delivery_mode(config)}")
|
|
79
|
+
else:
|
|
80
|
+
self._lines(self.commands.set_delivery(values[1]))
|
|
81
|
+
return
|
|
82
|
+
if head in self.view.official_plugins:
|
|
83
|
+
spec = self.view.official_plugins[head]
|
|
84
|
+
command = (
|
|
85
|
+
self.commands.remove
|
|
86
|
+
if spec in self.view.configured_specs(config)
|
|
87
|
+
else self.commands.add
|
|
88
|
+
)
|
|
89
|
+
self._lines(command(spec))
|
|
90
|
+
return
|
|
91
|
+
self._lines(self.commands.add(values[0]))
|
|
92
|
+
|
|
93
|
+
def delivery(self, args: Any) -> None:
|
|
94
|
+
value = getattr(args, "value", None)
|
|
95
|
+
if value:
|
|
96
|
+
self._lines(self.commands.set_delivery(value))
|
|
97
|
+
else:
|
|
98
|
+
self.view.output(f"channel_delivery: {self.view.delivery_mode(None)}")
|
|
99
|
+
|
|
100
|
+
def _status(self, config: dict[str, Any]) -> None:
|
|
101
|
+
self.view.output(f"channels: {self.view.status_text(config)}")
|
|
102
|
+
self.view.output(f"delivery: {self.view.delivery_mode(config)}")
|
|
103
|
+
specs = self.view.configured_specs(config)
|
|
104
|
+
for name, spec in self.view.official_plugins.items():
|
|
105
|
+
mark = "*" if spec in specs else " "
|
|
106
|
+
self.view.output(f" {mark} {name:<10} {spec}")
|
|
107
|
+
official = set(self.view.official_plugins.values())
|
|
108
|
+
for spec in specs:
|
|
109
|
+
if spec not in official:
|
|
110
|
+
self.view.output(f" * custom {spec}")
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def _require_value(values: list[str], message: str) -> None:
|
|
114
|
+
if len(values) < 2:
|
|
115
|
+
raise SystemExit(message)
|
|
116
|
+
|
|
117
|
+
def _lines(self, lines: list[str]) -> None:
|
|
118
|
+
for line in lines:
|
|
119
|
+
self.view.output(line)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Application service for injecting queued channel compaction commands."""
|
|
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(frozen=True, slots=True)
|
|
11
|
+
class ChannelCompactRequestPorts:
|
|
12
|
+
read: Callable[[], dict[str, Any] | None]
|
|
13
|
+
clear: Callable[[str | None], None]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True)
|
|
17
|
+
class ChannelCompactRuntimePorts:
|
|
18
|
+
active_tool_call: Callable[[], bool]
|
|
19
|
+
active_turn: Callable[[], bool]
|
|
20
|
+
enter_bytes: Callable[[bytes | None], bytes]
|
|
21
|
+
write_prompt: Callable[..., None]
|
|
22
|
+
enter_label: Callable[[bytes], str]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class ChannelCompactInjectionService:
|
|
27
|
+
request: ChannelCompactRequestPorts
|
|
28
|
+
runtime: ChannelCompactRuntimePorts
|
|
29
|
+
log: Callable[[str, str], None]
|
|
30
|
+
|
|
31
|
+
def inject(
|
|
32
|
+
self,
|
|
33
|
+
writer: Any,
|
|
34
|
+
enter_bytes: bytes | None = None,
|
|
35
|
+
*,
|
|
36
|
+
log_defer: bool = True,
|
|
37
|
+
submit_retry_count: int = 1,
|
|
38
|
+
confirm_submit: bool = False,
|
|
39
|
+
bracketed_paste: bool = False,
|
|
40
|
+
submit_delay_seconds: float | None = None,
|
|
41
|
+
) -> str:
|
|
42
|
+
request = self.request.read()
|
|
43
|
+
if not request:
|
|
44
|
+
return "none"
|
|
45
|
+
request_id = str(request.get("id") or "")
|
|
46
|
+
if self.runtime.active_tool_call():
|
|
47
|
+
self._log_deferred(request_id, "active_tool_call", log_defer)
|
|
48
|
+
return "deferred"
|
|
49
|
+
if self.runtime.active_turn():
|
|
50
|
+
self._log_deferred(request_id, "active_turn", log_defer)
|
|
51
|
+
return "deferred"
|
|
52
|
+
|
|
53
|
+
command = str(request.get("command") or "/compact").strip() or "/compact"
|
|
54
|
+
if command != "/compact":
|
|
55
|
+
command = "/compact"
|
|
56
|
+
submit_bytes = self.runtime.enter_bytes(enter_bytes)
|
|
57
|
+
self.runtime.write_prompt(
|
|
58
|
+
writer,
|
|
59
|
+
command,
|
|
60
|
+
submit_bytes,
|
|
61
|
+
submit_retry_count=submit_retry_count,
|
|
62
|
+
confirm_submit=confirm_submit,
|
|
63
|
+
bracketed_paste=bracketed_paste,
|
|
64
|
+
submit_delay_seconds=submit_delay_seconds,
|
|
65
|
+
)
|
|
66
|
+
self.request.clear(request_id or None)
|
|
67
|
+
self.log(
|
|
68
|
+
"INFO",
|
|
69
|
+
f"channel_compact_request_injected id={request_id or '-'} "
|
|
70
|
+
f"enter={self.runtime.enter_label(submit_bytes)}",
|
|
71
|
+
)
|
|
72
|
+
return "injected"
|
|
73
|
+
|
|
74
|
+
def _log_deferred(
|
|
75
|
+
self, request_id: str, reason: str, enabled: bool
|
|
76
|
+
) -> None:
|
|
77
|
+
if enabled:
|
|
78
|
+
self.log(
|
|
79
|
+
"INFO",
|
|
80
|
+
f"channel_compact_request_deferred id={request_id or '-'} "
|
|
81
|
+
f"reason={reason}",
|
|
82
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Polling policy for injecting queued session compaction requests."""
|
|
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(frozen=True, slots=True)
|
|
11
|
+
class ChannelCompactPollState:
|
|
12
|
+
last_poll_at: float = 0.0
|
|
13
|
+
defer_logged_at: float = 0.0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True)
|
|
17
|
+
class ChannelCompactInjectionOptions:
|
|
18
|
+
submit_retry_count: int
|
|
19
|
+
confirm_submit: bool
|
|
20
|
+
bracketed_paste: bool
|
|
21
|
+
submit_delay_seconds: float | None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, slots=True)
|
|
25
|
+
class ChannelCompactPollPolicy:
|
|
26
|
+
poll_interval_seconds: float = 0.5
|
|
27
|
+
defer_log_interval_seconds: float = 30.0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, slots=True)
|
|
31
|
+
class ChannelCompactPollServices:
|
|
32
|
+
inject_pending: Callable[..., str]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def poll_pending_compaction(
|
|
36
|
+
now: float,
|
|
37
|
+
writer: Any,
|
|
38
|
+
enter_bytes: bytes,
|
|
39
|
+
inflight_message_id: int | None,
|
|
40
|
+
state: ChannelCompactPollState,
|
|
41
|
+
options: ChannelCompactInjectionOptions,
|
|
42
|
+
services: ChannelCompactPollServices,
|
|
43
|
+
policy: ChannelCompactPollPolicy = ChannelCompactPollPolicy(),
|
|
44
|
+
*,
|
|
45
|
+
input_ready: bool = True,
|
|
46
|
+
) -> ChannelCompactPollState:
|
|
47
|
+
if not input_ready or now - state.last_poll_at < policy.poll_interval_seconds:
|
|
48
|
+
return state
|
|
49
|
+
if inflight_message_id is not None:
|
|
50
|
+
return ChannelCompactPollState(now, state.defer_logged_at)
|
|
51
|
+
|
|
52
|
+
log_defer = now - state.defer_logged_at >= policy.defer_log_interval_seconds
|
|
53
|
+
status = services.inject_pending(
|
|
54
|
+
writer,
|
|
55
|
+
enter_bytes,
|
|
56
|
+
log_defer=log_defer,
|
|
57
|
+
submit_retry_count=options.submit_retry_count,
|
|
58
|
+
confirm_submit=options.confirm_submit,
|
|
59
|
+
bracketed_paste=options.bracketed_paste,
|
|
60
|
+
submit_delay_seconds=options.submit_delay_seconds,
|
|
61
|
+
)
|
|
62
|
+
defer_logged_at = state.defer_logged_at
|
|
63
|
+
if status == "deferred" and log_defer:
|
|
64
|
+
defer_logged_at = now
|
|
65
|
+
elif status == "injected":
|
|
66
|
+
defer_logged_at = 0.0
|
|
67
|
+
return ChannelCompactPollState(now, defer_logged_at)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Persistent single-slot repository for channel-triggered compact requests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import time
|
|
8
|
+
import uuid
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ChannelCompactRequestRepository:
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
path: Path,
|
|
18
|
+
lock: Any,
|
|
19
|
+
save: Callable[[dict[str, Any], str], None],
|
|
20
|
+
truncate: Callable[[str, int], str],
|
|
21
|
+
log: Callable[[str, str], None],
|
|
22
|
+
ttl: Callable[[], float],
|
|
23
|
+
) -> None:
|
|
24
|
+
self.path = path
|
|
25
|
+
self.lock = lock
|
|
26
|
+
self.save = save
|
|
27
|
+
self.truncate = truncate
|
|
28
|
+
self.log = log
|
|
29
|
+
self.ttl = ttl
|
|
30
|
+
|
|
31
|
+
def payload(self, source: str, reason: str) -> dict[str, Any]:
|
|
32
|
+
now = time.time()
|
|
33
|
+
return {
|
|
34
|
+
"id": uuid.uuid4().hex,
|
|
35
|
+
"command": "/compact",
|
|
36
|
+
"source": str(source or "mcp"),
|
|
37
|
+
"reason": self.truncate(str(reason or ""), 1000),
|
|
38
|
+
"requested_at": now,
|
|
39
|
+
"expires_at": now + self.ttl(),
|
|
40
|
+
"pid": os.getpid(),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def queue(self, source: str = "mcp", reason: str = "") -> dict[str, Any]:
|
|
44
|
+
request = self.payload(source, reason)
|
|
45
|
+
with self.lock:
|
|
46
|
+
self.save(request, "channel_compact_request")
|
|
47
|
+
self.log(
|
|
48
|
+
"INFO",
|
|
49
|
+
f"channel_compact_request_queued id={request['id']} source={request['source']}",
|
|
50
|
+
)
|
|
51
|
+
return request
|
|
52
|
+
|
|
53
|
+
def read(self) -> dict[str, Any] | None:
|
|
54
|
+
with self.lock:
|
|
55
|
+
try:
|
|
56
|
+
data = json.loads(self.path.read_text(encoding="utf-8"))
|
|
57
|
+
except FileNotFoundError:
|
|
58
|
+
return None
|
|
59
|
+
except Exception as exc:
|
|
60
|
+
self.log(
|
|
61
|
+
"WARN",
|
|
62
|
+
f"channel_compact_request_read_failed error={type(exc).__name__}: {exc}",
|
|
63
|
+
)
|
|
64
|
+
return None
|
|
65
|
+
if not isinstance(data, dict):
|
|
66
|
+
return None
|
|
67
|
+
try:
|
|
68
|
+
expires_at = float(data.get("expires_at") or 0)
|
|
69
|
+
except Exception:
|
|
70
|
+
expires_at = 0.0
|
|
71
|
+
if expires_at and time.time() > expires_at:
|
|
72
|
+
self._unlink(str(data.get("id") or "-"))
|
|
73
|
+
self.log(
|
|
74
|
+
"INFO", f"channel_compact_request_expired id={data.get('id') or '-'}"
|
|
75
|
+
)
|
|
76
|
+
return None
|
|
77
|
+
return data
|
|
78
|
+
|
|
79
|
+
def clear(self, request_id: str | None = None) -> bool:
|
|
80
|
+
with self.lock:
|
|
81
|
+
if request_id:
|
|
82
|
+
try:
|
|
83
|
+
data = json.loads(self.path.read_text(encoding="utf-8"))
|
|
84
|
+
except FileNotFoundError:
|
|
85
|
+
return False
|
|
86
|
+
except Exception:
|
|
87
|
+
data = {}
|
|
88
|
+
if isinstance(data, dict) and str(data.get("id") or "") != str(request_id):
|
|
89
|
+
return False
|
|
90
|
+
return self._unlink(request_id or "-", missing=False)
|
|
91
|
+
|
|
92
|
+
def _unlink(self, request_id: str, *, missing: bool = True) -> bool:
|
|
93
|
+
try:
|
|
94
|
+
self.path.unlink()
|
|
95
|
+
return True
|
|
96
|
+
except FileNotFoundError:
|
|
97
|
+
return missing
|
|
98
|
+
except Exception as exc:
|
|
99
|
+
self.log(
|
|
100
|
+
"WARN",
|
|
101
|
+
f"channel_compact_request_clear_failed id={request_id} "
|
|
102
|
+
f"error={type(exc).__name__}: {exc}",
|
|
103
|
+
)
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def compact_request_ttl(value: str | None) -> float:
|
|
108
|
+
if value is None:
|
|
109
|
+
return 600.0
|
|
110
|
+
try:
|
|
111
|
+
return max(5.0, min(3600.0, float(str(value).strip())))
|
|
112
|
+
except (TypeError, ValueError):
|
|
113
|
+
return 600.0
|