@oneciel-ai/ciel-runtime 0.2.37 → 0.2.39
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/CHANGELOG.md +85 -0
- package/README.md +5 -2
- package/ciel_runtime.py +94 -96
- package/ciel_runtime_support/anthropic_model_policy.py +20 -3
- package/ciel_runtime_support/architecture.py +5 -0
- package/ciel_runtime_support/channel_inflight.py +4 -4
- package/ciel_runtime_support/channel_injection.py +56 -1
- package/ciel_runtime_support/channel_mcp_tools.py +34 -0
- package/ciel_runtime_support/channel_message_policy.py +9 -1
- package/ciel_runtime_support/channel_message_prompt.py +23 -0
- package/ciel_runtime_support/channel_pending_injection.py +86 -32
- package/ciel_runtime_support/channel_pending_poll.py +6 -2
- package/ciel_runtime_support/channel_runtime_environment.py +0 -11
- package/ciel_runtime_support/channel_terminal_context.py +0 -6
- package/ciel_runtime_support/channel_terminal_proxy.py +3 -42
- package/ciel_runtime_support/channel_transcript_repository.py +16 -1
- package/ciel_runtime_support/channel_wake_claim_repository.py +0 -19
- package/ciel_runtime_support/channel_wake_context.py +6 -13
- package/ciel_runtime_support/channel_wake_delivery_repository.py +50 -0
- package/ciel_runtime_support/chat_http_controller.py +88 -5
- package/ciel_runtime_support/claude_environment.py +100 -3
- package/ciel_runtime_support/claude_launch_assembly.py +4 -0
- package/ciel_runtime_support/claude_session_socket.py +181 -0
- package/ciel_runtime_support/cli_application_context.py +18 -10
- package/ciel_runtime_support/cli_dispatch.py +19 -3
- package/ciel_runtime_support/cli_parser.py +2 -0
- package/ciel_runtime_support/cli_usage.py +3 -2
- package/ciel_runtime_support/codex_backend_context.py +8 -3
- package/ciel_runtime_support/codex_completion_gate.py +198 -0
- package/ciel_runtime_support/codex_turn_recovery.py +240 -83
- package/ciel_runtime_support/compatibility_protocol.py +5 -2
- package/ciel_runtime_support/config_migrations.py +79 -0
- package/ciel_runtime_support/config_repository.py +7 -0
- package/ciel_runtime_support/context_summary_policy.py +24 -4
- package/ciel_runtime_support/external_event_menu.py +107 -0
- package/ciel_runtime_support/external_event_receiver.py +19 -1
- package/ciel_runtime_support/launch_state.py +2 -0
- package/ciel_runtime_support/managed_tool_injection.py +81 -0
- package/ciel_runtime_support/muse_runtime_context.py +291 -0
- package/ciel_runtime_support/ollama_thinking.py +12 -26
- package/ciel_runtime_support/prelaunch.py +13 -0
- package/ciel_runtime_support/prelaunch_launch_panel.py +2 -1
- package/ciel_runtime_support/prompt_compaction.py +39 -0
- package/ciel_runtime_support/provider_files_proxy.py +248 -0
- package/ciel_runtime_support/provider_model_identity.py +10 -2
- package/ciel_runtime_support/provider_option_cli.py +1 -1
- package/ciel_runtime_support/provider_request_access.py +54 -1
- package/ciel_runtime_support/provider_responses_passthrough.py +152 -15
- package/ciel_runtime_support/providers/alibaba.py +34 -1
- package/ciel_runtime_support/providers/anthropic.py +18 -1
- package/ciel_runtime_support/providers/meta.py +252 -21
- package/ciel_runtime_support/pseudo_tool_parser.py +17 -1
- package/ciel_runtime_support/remote_bridge.py +4 -0
- package/ciel_runtime_support/remote_instructions.py +5 -0
- package/ciel_runtime_support/remote_memory.py +1 -1
- package/ciel_runtime_support/responses_cache_diagnostics.py +82 -0
- package/ciel_runtime_support/responses_custom_tool_bridge.py +282 -0
- package/ciel_runtime_support/responses_input_compatibility.py +19 -3
- package/ciel_runtime_support/router_http.py +301 -4
- package/ciel_runtime_support/router_observability_context.py +26 -0
- package/ciel_runtime_support/router_server_context.py +1 -0
- package/ciel_runtime_support/runtime_adapters.py +31 -0
- package/ciel_runtime_support/runtime_constants.py +23 -1
- package/ciel_runtime_support/runtime_input_gateway.py +85 -5
- package/ciel_runtime_support/runtime_input_status.py +150 -0
- package/ciel_runtime_support/runtime_launch.py +35 -4
- package/ciel_runtime_support/runtime_paths.py +1 -0
- package/ciel_runtime_support/speech_http_controller.py +3 -2
- package/ciel_runtime_support/streaming_anthropic.py +3 -0
- package/ciel_runtime_support/tool_call_events.py +98 -0
- package/ciel_runtime_support/transcript_delta_delivery.py +140 -1
- package/ciel_runtime_support/ui_text.py +1 -0
- package/ciel_runtime_support/web_search_result_events.py +130 -0
- package/ciel_runtime_support/web_ui.py +1 -0
- package/ciel_runtime_support/windows_conpty.py +92 -8
- package/ciel_runtime_support/workspace_mcp.py +4 -1
- package/ciel_runtime_support/workspace_state.py +1 -0
- package/docs/Configuration.md +20 -4
- package/docs/MCP-Channels.md +79 -6
- package/docs/Managed-Tool-Injection.md +25 -0
- package/docs/Module-Map.md +4 -0
- package/docs/Muse-Code.md +73 -0
- package/docs/Observability.md +39 -0
- package/docs/Providers.md +29 -2
- package/docs/Remote-Bridge.md +12 -0
- package/docs/Router.md +1 -0
- package/docs/journal/2026/09/01/diagnostics/anthropic/router/fable51-usage-credits.okf +70 -0
- package/docs/journal/2026/09/01/implementation/claude/session-socket/all-input-paths/default-delivery.okf +133 -0
- package/docs/journal/2026/09/01/implementation/claude/session-socket/windows-direct-input.okf +112 -0
- package/docs/journal/2026/09/02/diagnostics/cache/alibaba/qwen38-post-restart-hit-rate.okf +107 -0
- package/docs/journal/2026/09/02/diagnostics/cache/ollama-cloud/kimi-k3-codex-hit-rate.okf +91 -0
- package/docs/journal/2026/09/02/diagnostics/codex/tui/statusline-cache-metrics.okf +44 -0
- package/docs/journal/2026/09/02/diagnostics/input/transport/fallback-behavior.okf +31 -0
- package/docs/journal/2026/09/02/diagnostics/providers/ollama-cloud/kimi-k3-agent-turn-recovery.okf +125 -0
- package/docs/journal/2026/09/02/diagnostics/providers/ollama-cloud/kimi-k3-parameters.okf +128 -0
- package/docs/journal/2026/09/02/diagnostics/remote/mia/socket-tui-visibility.okf +47 -0
- package/docs/journal/2026/09/02/diagnostics/runtime/alibaba/token-plan/qwen38-latency.okf +108 -0
- package/docs/journal/2026/09/02/diagnostics/runtime/alibaba/token-plan/qwen38-vs-gpt56-latency.okf +92 -0
- package/docs/journal/2026/09/02/implementation/anthropic/context/one-million-defaults.okf +85 -0
- package/docs/journal/2026/09/02/implementation/cache/alibaba/qwen38-cache-hit-improvement.okf +104 -0
- package/docs/journal/2026/09/02/implementation/cache/alibaba/qwen38-cache-hit-improvement.png +0 -0
- package/docs/journal/2026/09/02/implementation/cache/alibaba/responses-session-cache.okf +97 -0
- package/docs/journal/2026/09/02/implementation/providers/alibaba/qwen38/0902-parameter-alignment.okf +97 -0
- package/docs/journal/2026/09/02/implementation/providers/meta/multimodal-tools/contributor-protocol-adoption.okf +140 -0
- package/docs/journal/2026/09/02/implementation/providers/meta/muse-spark-1.3-support.okf +53 -0
- package/docs/journal/2026/09/02/monitoring/cache/alibaba/cielarvis-qwen38-live-hit-rate.okf +858 -0
- package/docs/journal/2026/09/02/operations/release/main-merge-local-deploy.okf +36 -0
- package/docs/journal/2026/09/02/operations/release/nightly/local-cache-deployment.okf +54 -0
- package/docs/journal/2026/09/02/operations/release/nightly/ollama-kimi-recovery-model-audit-deployment.okf +58 -0
- package/docs/journal/2026/09/02/operations/release/nightly/qwen38-cache-improvement-deployment.okf +72 -0
- package/docs/journal/2026/09/02/research/cache/alibaba/qwen38-hit-rate-improvement.okf +233 -0
- package/docs/journal/2026/09/02/research/cache/codex/provider-scope.okf +77 -0
- package/docs/journal/2026/09/02/research/providers/ollama-cloud/desktop-model-exhaustive-audit.okf +159 -0
- package/docs/journal/2026/09/03/diagnostics/providers/meta/muse-spark-contributor-cache-hit.okf +148 -0
- package/docs/journal/2026/09/03/diagnostics/providers/meta/muse-spark-contributor-required-schema.okf +127 -0
- package/docs/journal/2026/09/03/diagnostics/providers/ollama-cloud/kimi-k3-resumed-session-stall.okf +160 -0
- package/docs/journal/2026/09/03/diagnostics/providers/ollama-cloud/kimi-k3-substantive-dangling-action.okf +94 -0
- package/docs/journal/2026/09/03/diagnostics/remote/kevin/codex-gpt-early-turn-completion.okf +162 -0
- package/docs/journal/2026/09/03/implementation/runtimes/meta/muse-code/default-yolo.okf +40 -0
- package/docs/journal/2026/09/03/implementation/runtimes/meta/muse-code/native-subscription-router.okf +86 -0
- package/docs/journal/2026/09/03/operations/deployment/local/session-socket-default-fallback.okf +41 -0
- package/docs/journal/2026/09/03/research/deployment/colab/tailscale/credential-storage.okf +41 -0
- package/docs/journal/2026/09/03/research/providers/meta/muse-code/subscription-billing-boundary.okf +64 -0
- package/docs/journal/2026/09/03/research/web-chat/voice/instruction-format.okf +135 -0
- package/docs/journal/2026/09/04/diagnostics/claude/context/early-auto-compaction.okf +75 -0
- package/docs/journal/2026/09/04/diagnostics/claude/context/fable-51-status-200k.okf +54 -0
- package/docs/journal/2026/09/04/diagnostics/runtime/codex/responses/replayed-item-id-validation.okf +74 -0
- package/docs/journal/2026/09/04/diagnostics/workspaces/onecieldmsui/codex/replay-stall.okf +100 -0
- package/docs/journal/2026/09/04/implementation/observability/tool-calls/websocket-stream.okf +49 -0
- package/docs/journal/2026/09/04/implementation/web-chat/input/raw-injection.okf +42 -0
- package/docs/journal/2026/09/05/diagnostics/tty/claude-raw-idle-render.okf +25 -0
- package/docs/journal/2026/09/05/implementation/events/search-response-urls.okf +23 -0
- package/docs/journal/2026/09/05/implementation/tools/native-injection.okf +19 -0
- package/docs/journal/2026/09/05/implementation/windows/conpty/prompt-delivery-lifecycle.okf +43 -0
- package/docs/journal/2026/09/05/release/nightly/conpty-native-tools.okf +13 -0
- package/docs/journal/2026/09/05/release/nightly/search-result-urls.okf +14 -0
- package/docs/journal/2026/09/06/implementation/codex/inherited-web-mcp.okf +22 -0
- package/docs/journal/2026/09/07/fixes/codex/native-web-transport.okf +39 -0
- package/docs/journal/2026/09/07/integration/main/nightly-merge.okf +13 -0
- package/docs/journal/2026/09/07/release/stable/0.2.38.okf +10 -0
- package/docs/journal/2026/09/07/release/stable/0.2.39.okf +10 -0
- package/package.json +1 -1
|
@@ -28,6 +28,7 @@ class ChannelTranscriptRepository:
|
|
|
28
28
|
*,
|
|
29
29
|
started_at: float | None = None,
|
|
30
30
|
codex_home: Path | None = None,
|
|
31
|
+
muse_home: Path | None = None,
|
|
31
32
|
cwd: Path | None = None,
|
|
32
33
|
session_id: str | None = None,
|
|
33
34
|
) -> None:
|
|
@@ -40,6 +41,11 @@ class ChannelTranscriptRepository:
|
|
|
40
41
|
if codex_home is not None
|
|
41
42
|
else None
|
|
42
43
|
)
|
|
44
|
+
self.scope["muse_home"] = (
|
|
45
|
+
Path(muse_home).expanduser()
|
|
46
|
+
if muse_home is not None
|
|
47
|
+
else None
|
|
48
|
+
)
|
|
43
49
|
self.scope["cwd"] = Path(cwd).expanduser() if cwd is not None else None
|
|
44
50
|
self.scope["session_id"] = str(session_id or "").strip()
|
|
45
51
|
self.scope["bound_path"] = None
|
|
@@ -182,6 +188,13 @@ class ChannelTranscriptRepository:
|
|
|
182
188
|
else self.home / ".codex"
|
|
183
189
|
)
|
|
184
190
|
codex_root = (codex_home / "sessions", "**/*.jsonl")
|
|
191
|
+
configured_muse_home = self.scope.get("muse_home")
|
|
192
|
+
muse_home = (
|
|
193
|
+
Path(configured_muse_home)
|
|
194
|
+
if isinstance(configured_muse_home, Path)
|
|
195
|
+
else self.home / ".local" / "share" / "muse"
|
|
196
|
+
)
|
|
197
|
+
muse_root = (muse_home / "sessions", "*/*/*/*/session.jsonl")
|
|
185
198
|
if runtime == "codex":
|
|
186
199
|
return (codex_root,)
|
|
187
200
|
if runtime == "claude":
|
|
@@ -191,7 +204,9 @@ class ChannelTranscriptRepository:
|
|
|
191
204
|
if project_dir.is_dir():
|
|
192
205
|
return ((project_dir, "*.jsonl"),)
|
|
193
206
|
return (claude_root,)
|
|
194
|
-
|
|
207
|
+
if runtime == "muse":
|
|
208
|
+
return (muse_root,)
|
|
209
|
+
return claude_root, codex_root, muse_root
|
|
195
210
|
|
|
196
211
|
def latest(self, ttl_seconds: float = 2.0) -> Path | None:
|
|
197
212
|
now = self.now()
|
|
@@ -122,25 +122,6 @@ class ChannelWakeClaimRepository:
|
|
|
122
122
|
self._write_locked(claims)
|
|
123
123
|
return True
|
|
124
124
|
|
|
125
|
-
def body_fallback(self, message_id: int) -> bool:
|
|
126
|
-
if message_id <= 0:
|
|
127
|
-
return False
|
|
128
|
-
with self.file_lock():
|
|
129
|
-
claim = self._read_locked().get(str(message_id))
|
|
130
|
-
return bool(isinstance(claim, dict) and claim.get("body_fallback"))
|
|
131
|
-
|
|
132
|
-
def mark_body_fallback(self, message_id: int, reason: str) -> None:
|
|
133
|
-
if message_id <= 0:
|
|
134
|
-
return
|
|
135
|
-
with self.file_lock():
|
|
136
|
-
claims = self._read_locked()
|
|
137
|
-
claims[str(message_id)] = {
|
|
138
|
-
"claimed_at": self.now(),
|
|
139
|
-
"body_fallback": True,
|
|
140
|
-
"reason": str(reason or "windows_console_wake_failed"),
|
|
141
|
-
}
|
|
142
|
-
self._write_locked(claims)
|
|
143
|
-
|
|
144
125
|
def clear(self, message_id: int) -> None:
|
|
145
126
|
if message_id <= 0:
|
|
146
127
|
return
|
|
@@ -134,9 +134,7 @@ class ChannelPendingDeliveryPorts:
|
|
|
134
134
|
format_standard: Callable[[list[dict[str, Any]]], str]
|
|
135
135
|
enter_label: Callable[[bytes], str]
|
|
136
136
|
release_stale: Callable[[int, bool], None]
|
|
137
|
-
|
|
138
|
-
record_prompts: Callable[[list[dict[str, Any]], str], None]
|
|
139
|
-
rollback: Callable[[list[dict[str, Any]], list[int]], None]
|
|
137
|
+
lifecycle: ChannelWakeDeliveryRepository
|
|
140
138
|
commit_cursor: Callable[[int | None], None]
|
|
141
139
|
|
|
142
140
|
|
|
@@ -149,6 +147,7 @@ class ChannelPendingIoPorts:
|
|
|
149
147
|
compact_clear: Callable[[], None]
|
|
150
148
|
messages_path: Path
|
|
151
149
|
log: Callable[[str, str], None]
|
|
150
|
+
write_session_socket: Callable[[str, list[dict[str, Any]]], bool] | None = None
|
|
152
151
|
|
|
153
152
|
|
|
154
153
|
@dataclass(frozen=True, slots=True)
|
|
@@ -195,12 +194,6 @@ class ChannelWakeContext:
|
|
|
195
194
|
def clear_wake_claim(self, message_id: int) -> None:
|
|
196
195
|
self.claim_repository().clear(message_id)
|
|
197
196
|
|
|
198
|
-
def wake_uses_body_fallback(self, message_id: int) -> bool:
|
|
199
|
-
return self.claim_repository().body_fallback(message_id)
|
|
200
|
-
|
|
201
|
-
def mark_wake_body_fallback(self, message_id: int, reason: str) -> None:
|
|
202
|
-
self.claim_repository().mark_body_fallback(message_id, reason)
|
|
203
|
-
|
|
204
197
|
def prompt_references_message_id(
|
|
205
198
|
self,
|
|
206
199
|
text: str,
|
|
@@ -376,6 +369,7 @@ class ChannelWakeContext:
|
|
|
376
369
|
*,
|
|
377
370
|
started_at: float | None = None,
|
|
378
371
|
codex_home: Path | None = None,
|
|
372
|
+
muse_home: Path | None = None,
|
|
379
373
|
cwd: Path | None = None,
|
|
380
374
|
session_id: str | None = None,
|
|
381
375
|
) -> None:
|
|
@@ -383,6 +377,7 @@ class ChannelWakeContext:
|
|
|
383
377
|
runtime,
|
|
384
378
|
started_at=started_at,
|
|
385
379
|
codex_home=codex_home,
|
|
380
|
+
muse_home=muse_home,
|
|
386
381
|
cwd=cwd,
|
|
387
382
|
session_id=session_id,
|
|
388
383
|
)
|
|
@@ -547,17 +542,15 @@ class ChannelWakeContext:
|
|
|
547
542
|
claim_prompt=self.claim_wake_prompt,
|
|
548
543
|
clear_claim=self.clear_wake_claim,
|
|
549
544
|
release_stale=self.pending_delivery.release_stale,
|
|
550
|
-
|
|
551
|
-
record_prompts=self.pending_delivery.record_prompts,
|
|
552
|
-
rollback=self.pending_delivery.rollback,
|
|
545
|
+
lifecycle=self.pending_delivery.lifecycle,
|
|
553
546
|
commit_cursor=self.pending_delivery.commit_cursor,
|
|
554
|
-
body_fallback=self.wake_uses_body_fallback,
|
|
555
547
|
),
|
|
556
548
|
io=ChannelInjectionIO(
|
|
557
549
|
inject_lock=self.pending_io.inject_lock,
|
|
558
550
|
read_messages=self.pending_io.read_messages,
|
|
559
551
|
write_prompt=self.pending_io.write_prompt,
|
|
560
552
|
log=self.pending_io.log,
|
|
553
|
+
write_session_socket=self.pending_io.write_session_socket,
|
|
561
554
|
),
|
|
562
555
|
policy=ChannelInjectionPolicy(
|
|
563
556
|
wake_batch_limit=self.pending_policy.wake_batch_limit,
|
|
@@ -22,6 +22,8 @@ class ChannelWakeDeliveryRepository:
|
|
|
22
22
|
batches: dict[int, frozenset[int]]
|
|
23
23
|
clear_claim: Callable[[int], None]
|
|
24
24
|
commit_cursor: Callable[[int], None]
|
|
25
|
+
failed: dict[int, str] | None = None
|
|
26
|
+
status: Any = None
|
|
25
27
|
retained_limit: int = 1000
|
|
26
28
|
prune_count: int = 500
|
|
27
29
|
|
|
@@ -33,15 +35,30 @@ class ChannelWakeDeliveryRepository:
|
|
|
33
35
|
with self.lock:
|
|
34
36
|
return message_id in self.delivered
|
|
35
37
|
|
|
38
|
+
def failure_reason(self, message_id: int) -> str:
|
|
39
|
+
with self.lock:
|
|
40
|
+
memory_reason = str((self.failed or {}).get(message_id) or "")
|
|
41
|
+
if memory_reason:
|
|
42
|
+
return memory_reason
|
|
43
|
+
status = self.status.get(message_id) if self.status is not None else None
|
|
44
|
+
if isinstance(status, dict) and status.get("status") == "failed":
|
|
45
|
+
return str(status.get("reason") or "submission_failed")
|
|
46
|
+
return ""
|
|
47
|
+
|
|
36
48
|
def release_stale(self, message_id: int, commit_cursor: bool) -> None:
|
|
37
49
|
with self.lock:
|
|
38
50
|
message_ids = self._prompt_group(message_id)
|
|
51
|
+
if self.failed is None:
|
|
52
|
+
self.failed = {}
|
|
39
53
|
for grouped_id in sorted(message_ids):
|
|
40
54
|
self.delivered.discard(grouped_id)
|
|
41
55
|
self.prompts.pop(grouped_id, None)
|
|
42
56
|
self.batches.pop(grouped_id, None)
|
|
57
|
+
self.failed[grouped_id] = "stale_unconfirmed"
|
|
58
|
+
self._prune(self.failed)
|
|
43
59
|
for grouped_id in sorted(message_ids):
|
|
44
60
|
self.clear_claim(grouped_id)
|
|
61
|
+
self._transition(grouped_id, "failed", reason="stale_unconfirmed")
|
|
45
62
|
if commit_cursor:
|
|
46
63
|
self.commit_cursor(max(message_ids))
|
|
47
64
|
|
|
@@ -53,6 +70,7 @@ class ChannelWakeDeliveryRepository:
|
|
|
53
70
|
self.batches.pop(grouped_id, None)
|
|
54
71
|
for grouped_id in sorted(message_ids):
|
|
55
72
|
self.clear_claim(grouped_id)
|
|
73
|
+
self._transition(grouped_id, "replied")
|
|
56
74
|
|
|
57
75
|
def mark_delivered(self, message_id: int) -> bool:
|
|
58
76
|
with self.lock:
|
|
@@ -74,6 +92,38 @@ class ChannelWakeDeliveryRepository:
|
|
|
74
92
|
self.batches[message_id] = message_ids
|
|
75
93
|
self._prune(self.prompts)
|
|
76
94
|
self._prune(self.batches)
|
|
95
|
+
for message_id in sorted(message_ids):
|
|
96
|
+
self._transition(message_id, "submitted")
|
|
97
|
+
|
|
98
|
+
def fail(
|
|
99
|
+
self,
|
|
100
|
+
messages: list[dict[str, Any]],
|
|
101
|
+
claimed_ids: list[int],
|
|
102
|
+
reason: str,
|
|
103
|
+
) -> None:
|
|
104
|
+
message_ids: list[int] = []
|
|
105
|
+
with self.lock:
|
|
106
|
+
if self.failed is None:
|
|
107
|
+
self.failed = {}
|
|
108
|
+
for message in messages:
|
|
109
|
+
message_id = _message_id(message)
|
|
110
|
+
if message_id <= 0:
|
|
111
|
+
continue
|
|
112
|
+
message_ids.append(message_id)
|
|
113
|
+
self.delivered.discard(message_id)
|
|
114
|
+
self.prompts.pop(message_id, None)
|
|
115
|
+
self.batches.pop(message_id, None)
|
|
116
|
+
self.failed[message_id] = str(reason)
|
|
117
|
+
self.clear_claim(message_id)
|
|
118
|
+
self._prune(self.failed)
|
|
119
|
+
for message_id in claimed_ids:
|
|
120
|
+
self.clear_claim(message_id)
|
|
121
|
+
for message_id in sorted(set(message_ids)):
|
|
122
|
+
self._transition(message_id, "failed", reason=reason)
|
|
123
|
+
|
|
124
|
+
def _transition(self, message_id: int, state: str, **kwargs: Any) -> None:
|
|
125
|
+
if self.status is not None:
|
|
126
|
+
self.status.transition(message_id, state, **kwargs)
|
|
77
127
|
|
|
78
128
|
def rollback(
|
|
79
129
|
self, messages: list[dict[str, Any]], claimed_ids: list[int]
|
|
@@ -16,7 +16,7 @@ from typing import Any, Callable
|
|
|
16
16
|
|
|
17
17
|
CHAT_FILE_STREAM_CHUNK_BYTES = 1024 * 1024
|
|
18
18
|
CHAT_INPUT_MODES = frozenset({"structured", "tty"})
|
|
19
|
-
CHAT_INPUT_TRANSPORTS = frozenset({"tty", "router"})
|
|
19
|
+
CHAT_INPUT_TRANSPORTS = frozenset({"session_socket", "tty", "router"})
|
|
20
20
|
CHAT_RESPONSE_MODES = frozenset({"web_chat", "tty", "mcp"})
|
|
21
21
|
MCP_HINT_FIELD_LIMITS = {"server": 160, "tool": 240, "hint": 1200}
|
|
22
22
|
|
|
@@ -46,6 +46,8 @@ class ChatHttpReadServices:
|
|
|
46
46
|
condition: Condition
|
|
47
47
|
safe_segment: Callable[[str, str], str]
|
|
48
48
|
files_dir: Path
|
|
49
|
+
request_status: Callable[[int], dict[str, Any] | None] = lambda _request_id: None
|
|
50
|
+
request_statuses: Callable[..., list[dict[str, Any]]] = lambda **_kwargs: []
|
|
49
51
|
|
|
50
52
|
|
|
51
53
|
@dataclass(frozen=True, slots=True)
|
|
@@ -56,6 +58,7 @@ class ChatHttpWriteServices:
|
|
|
56
58
|
submit_message: Callable[[dict[str, Any], dict[str, Any]], dict[str, Any]] | None = None
|
|
57
59
|
submit_notify: Callable[[dict[str, Any]], dict[str, Any]] | None = None
|
|
58
60
|
submit_tty: Callable[..., dict[str, Any]] | None = None
|
|
61
|
+
default_input_transport: Callable[[], str] = lambda: "session_socket"
|
|
59
62
|
|
|
60
63
|
|
|
61
64
|
@dataclass(frozen=True, slots=True)
|
|
@@ -85,6 +88,29 @@ class ChatHttpController:
|
|
|
85
88
|
mode = str(value or "").strip().lower().replace("-", "_")
|
|
86
89
|
return aliases.get(mode, mode)
|
|
87
90
|
|
|
91
|
+
def _raw_injection(
|
|
92
|
+
self,
|
|
93
|
+
handler: BaseHTTPRequestHandler,
|
|
94
|
+
body: dict[str, Any],
|
|
95
|
+
) -> tuple[bool, dict[str, Any] | None]:
|
|
96
|
+
value = body.get("raw_injection")
|
|
97
|
+
if value is None:
|
|
98
|
+
value = self._first(self._params(handler), "raw_injection", "")
|
|
99
|
+
if value in (None, ""):
|
|
100
|
+
return False, None
|
|
101
|
+
if isinstance(value, bool):
|
|
102
|
+
return value, None
|
|
103
|
+
normalized = str(value).strip().lower()
|
|
104
|
+
if normalized in {"1", "true", "yes", "on"}:
|
|
105
|
+
return True, None
|
|
106
|
+
if normalized in {"0", "false", "no", "off"}:
|
|
107
|
+
return False, None
|
|
108
|
+
return False, {
|
|
109
|
+
"ok": False,
|
|
110
|
+
"error": "invalid_raw_injection",
|
|
111
|
+
"allowed": [True, False],
|
|
112
|
+
}
|
|
113
|
+
|
|
88
114
|
def _message_modes(
|
|
89
115
|
self,
|
|
90
116
|
handler: BaseHTTPRequestHandler,
|
|
@@ -108,10 +134,16 @@ class ChatHttpController:
|
|
|
108
134
|
|
|
109
135
|
requested_transport = body.get("input_transport")
|
|
110
136
|
if requested_transport is None:
|
|
111
|
-
requested_transport =
|
|
137
|
+
requested_transport = (
|
|
138
|
+
self._first(params, "input_transport", "")
|
|
139
|
+
or self.writes.default_input_transport()
|
|
140
|
+
)
|
|
112
141
|
input_transport = self._normalized_mode(
|
|
113
142
|
requested_transport,
|
|
114
143
|
{
|
|
144
|
+
"socket": "session_socket",
|
|
145
|
+
"claude_socket": "session_socket",
|
|
146
|
+
"messaging_socket": "session_socket",
|
|
115
147
|
"llm": "router",
|
|
116
148
|
"context": "router",
|
|
117
149
|
"inband": "router",
|
|
@@ -188,10 +220,41 @@ class ChatHttpController:
|
|
|
188
220
|
"wait": "/ca/channel/wait" if channel_alias else "/ca/chat/wait",
|
|
189
221
|
"stream": "/ca/channel/stream" if channel_alias else "/ca/chat/stream",
|
|
190
222
|
"notify": "/ca/channel/notify",
|
|
223
|
+
"request_status": "/ca/channel/requests/{request_id}" if channel_alias else "/ca/chat/requests/{request_id}",
|
|
224
|
+
"request_status_events": "/ca/events/stream?category=runtime_input.status",
|
|
191
225
|
"ownership_note": "External MCP servers are configured and owned by the active CLI.",
|
|
192
226
|
},
|
|
193
227
|
)
|
|
194
228
|
return True
|
|
229
|
+
if path == "/ca/chat/requests":
|
|
230
|
+
params = self._params(handler)
|
|
231
|
+
try:
|
|
232
|
+
after = max(0, int(self._first(params, "after", "0") or 0))
|
|
233
|
+
limit = max(1, min(500, int(self._first(params, "limit", "100") or 100)))
|
|
234
|
+
except ValueError:
|
|
235
|
+
self.writes.write_json(handler, {"ok": False, "error": "invalid_request_status_query"}, 400)
|
|
236
|
+
return True
|
|
237
|
+
rows = self.reads.request_statuses(
|
|
238
|
+
after_request_id=after,
|
|
239
|
+
status=self._first(params, "status", ""),
|
|
240
|
+
limit=limit,
|
|
241
|
+
)
|
|
242
|
+
self.writes.write_json(handler, {"ok": True, "requests": rows})
|
|
243
|
+
return True
|
|
244
|
+
if path.startswith("/ca/chat/requests/"):
|
|
245
|
+
raw_id = path[len("/ca/chat/requests/") :]
|
|
246
|
+
try:
|
|
247
|
+
request_id = int(raw_id)
|
|
248
|
+
except ValueError:
|
|
249
|
+
self.writes.write_json(handler, {"ok": False, "error": "invalid_request_id"}, 400)
|
|
250
|
+
return True
|
|
251
|
+
status = self.reads.request_status(request_id)
|
|
252
|
+
self.writes.write_json(
|
|
253
|
+
handler,
|
|
254
|
+
{"ok": status is not None, "request": status},
|
|
255
|
+
200 if status is not None else 404,
|
|
256
|
+
)
|
|
257
|
+
return True
|
|
195
258
|
if path in ("/ca/chat/messages", "/ca/chat/wait"):
|
|
196
259
|
return self._messages(handler, path)
|
|
197
260
|
if path == "/ca/chat/stream":
|
|
@@ -332,6 +395,10 @@ class ChatHttpController:
|
|
|
332
395
|
if path == "/ca/chat/notify":
|
|
333
396
|
return self._notify(handler, body)
|
|
334
397
|
if path == "/ca/chat/messages":
|
|
398
|
+
raw_injection, raw_error = self._raw_injection(handler, body)
|
|
399
|
+
if raw_error is not None:
|
|
400
|
+
self.writes.write_json(handler, raw_error, 400)
|
|
401
|
+
return True
|
|
335
402
|
input_mode, input_transport, response_mode, response_mcp, error = self._message_modes(
|
|
336
403
|
handler, body
|
|
337
404
|
)
|
|
@@ -347,6 +414,7 @@ class ChatHttpController:
|
|
|
347
414
|
meta["injection_mode"] = input_mode
|
|
348
415
|
meta["input_transport"] = input_transport
|
|
349
416
|
meta["response_mode"] = response_mode
|
|
417
|
+
meta["raw_injection"] = raw_injection
|
|
350
418
|
if response_mode == "web_chat":
|
|
351
419
|
admitted_body.setdefault("channel", "default")
|
|
352
420
|
admitted_body.setdefault("thread_id", admitted_body["channel"])
|
|
@@ -372,7 +440,7 @@ class ChatHttpController:
|
|
|
372
440
|
if response_mode == "web_chat"
|
|
373
441
|
else None
|
|
374
442
|
)
|
|
375
|
-
|
|
443
|
+
runtime_message = (
|
|
376
444
|
self.writes.submit_tty(admitted_body, public_message)
|
|
377
445
|
if public_message is not None
|
|
378
446
|
else self.writes.submit_tty(admitted_body)
|
|
@@ -384,14 +452,19 @@ class ChatHttpController:
|
|
|
384
452
|
"input_mode": "tty",
|
|
385
453
|
"input_transport": input_transport,
|
|
386
454
|
"response_mode": response_mode,
|
|
455
|
+
"raw_injection": raw_injection,
|
|
387
456
|
"injection_mode": "tty",
|
|
388
|
-
"message": public_message or
|
|
457
|
+
"message": public_message or runtime_message,
|
|
458
|
+
"request_id": runtime_message.get("id"),
|
|
459
|
+
"request": self.reads.request_status(int(runtime_message.get("id") or 0))
|
|
460
|
+
or {"request_id": runtime_message.get("id"), "status": "queued"},
|
|
389
461
|
},
|
|
390
462
|
)
|
|
391
463
|
return True
|
|
392
464
|
message = self.writes.append_message(admitted_body)
|
|
465
|
+
runtime_message = None
|
|
393
466
|
if self.writes.submit_message is not None and _delivery_requests_llm(admitted_body):
|
|
394
|
-
self.writes.submit_message(message, admitted_body)
|
|
467
|
+
runtime_message = self.writes.submit_message(message, admitted_body)
|
|
395
468
|
self.writes.write_json(
|
|
396
469
|
handler,
|
|
397
470
|
{
|
|
@@ -399,8 +472,18 @@ class ChatHttpController:
|
|
|
399
472
|
"input_mode": "structured",
|
|
400
473
|
"input_transport": input_transport,
|
|
401
474
|
"response_mode": response_mode,
|
|
475
|
+
"raw_injection": raw_injection,
|
|
402
476
|
"injection_mode": "web_chat",
|
|
403
477
|
"message": message,
|
|
478
|
+
**(
|
|
479
|
+
{
|
|
480
|
+
"request_id": runtime_message.get("id"),
|
|
481
|
+
"request": self.reads.request_status(int(runtime_message.get("id") or 0))
|
|
482
|
+
or {"request_id": runtime_message.get("id"), "status": "queued"},
|
|
483
|
+
}
|
|
484
|
+
if isinstance(runtime_message, dict)
|
|
485
|
+
else {}
|
|
486
|
+
),
|
|
404
487
|
},
|
|
405
488
|
)
|
|
406
489
|
return True
|
|
@@ -13,6 +13,7 @@ import re
|
|
|
13
13
|
from typing import Any, Callable
|
|
14
14
|
|
|
15
15
|
from .architecture import ProviderRuntimeCompactionPolicy
|
|
16
|
+
from .anthropic_model_policy import defaults_to_one_million_context
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
CLAUDE_PROJECTED_ENV_KEYS = (
|
|
@@ -37,10 +38,34 @@ CLAUDE_PROJECTED_ENV_KEYS = (
|
|
|
37
38
|
"ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTS",
|
|
38
39
|
"ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES",
|
|
39
40
|
"CLAUDE_CODE_SUBAGENT_MODEL",
|
|
41
|
+
"CLAUDE_CODE_SUBAGENT_MODEL_FORCE",
|
|
40
42
|
"CIEL_RUNTIME_MODEL_ALIAS",
|
|
41
43
|
"CIEL_RUNTIME_PROVIDER",
|
|
42
44
|
)
|
|
43
45
|
CLAUDE_AUTO_COMPACT_WINDOW_MAX = 1_000_000
|
|
46
|
+
ANTHROPIC_STANDARD_CONTEXT_TOKENS = 200_000
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def anthropic_routed_mode(
|
|
50
|
+
provider: str,
|
|
51
|
+
config: dict[str, Any],
|
|
52
|
+
) -> bool:
|
|
53
|
+
routed = config.get("route_through_router") is True or str(
|
|
54
|
+
config.get("route_through_router") or ""
|
|
55
|
+
).strip().lower() in {"1", "true", "yes", "on"}
|
|
56
|
+
return provider == "anthropic" and routed
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def anthropic_routed_standard_context(
|
|
60
|
+
provider: str,
|
|
61
|
+
config: dict[str, Any],
|
|
62
|
+
) -> bool:
|
|
63
|
+
model = str(config.get("current_model") or "").strip().lower()
|
|
64
|
+
return (
|
|
65
|
+
anthropic_routed_mode(provider, config)
|
|
66
|
+
and "[1m]" not in model
|
|
67
|
+
and not defaults_to_one_million_context(model)
|
|
68
|
+
)
|
|
44
69
|
|
|
45
70
|
|
|
46
71
|
@dataclass(frozen=True)
|
|
@@ -89,6 +114,8 @@ class ClaudeLimitPolicy:
|
|
|
89
114
|
) -> ClaudeCompactionSnapshot:
|
|
90
115
|
configured = self._ports.positive_int(config.get("auto_compact_window"))
|
|
91
116
|
limit = self._ports.context_limit(provider, config)
|
|
117
|
+
if anthropic_routed_standard_context(provider, config):
|
|
118
|
+
limit = min(limit or ANTHROPIC_STANDARD_CONTEXT_TOKENS, ANTHROPIC_STANDARD_CONTEXT_TOKENS)
|
|
92
119
|
if configured:
|
|
93
120
|
window = min(
|
|
94
121
|
configured,
|
|
@@ -181,7 +208,23 @@ class ClaudeModelAliasPolicy:
|
|
|
181
208
|
*,
|
|
182
209
|
context_limit: int | None = None,
|
|
183
210
|
) -> str:
|
|
211
|
+
original_model = str(model or "")
|
|
212
|
+
original_upstream = str(upstream_model or "")
|
|
184
213
|
model = self._ports.strip_context_suffix(model)
|
|
214
|
+
if anthropic_routed_mode(provider, config):
|
|
215
|
+
current_model = str(config.get("current_model") or "")
|
|
216
|
+
context_candidates = [original_model, original_upstream]
|
|
217
|
+
if upstream_model is None:
|
|
218
|
+
context_candidates.append(current_model)
|
|
219
|
+
explicit_one_million = any(
|
|
220
|
+
"[1m]" in candidate.lower() for candidate in context_candidates
|
|
221
|
+
)
|
|
222
|
+
documented_one_million = any(
|
|
223
|
+
defaults_to_one_million_context(candidate)
|
|
224
|
+
for candidate in context_candidates
|
|
225
|
+
if candidate
|
|
226
|
+
)
|
|
227
|
+
return f"{model}[1m]" if explicit_one_million or documented_one_million else model
|
|
185
228
|
probe_model = upstream_model if upstream_model is not None else model
|
|
186
229
|
include_current = upstream_model is None
|
|
187
230
|
claims_one_million = self.claims_one_million_context(
|
|
@@ -202,6 +245,28 @@ class ClaudeModelAliasPolicy:
|
|
|
202
245
|
return False
|
|
203
246
|
return bool(re.search(rf"(?:^|[-_./]){re.escape(family)}(?:[-_./]|$)", normalized))
|
|
204
247
|
|
|
248
|
+
def routed_model_alias(
|
|
249
|
+
self,
|
|
250
|
+
provider: str,
|
|
251
|
+
config: dict[str, Any],
|
|
252
|
+
model: str,
|
|
253
|
+
*,
|
|
254
|
+
context_limit: int | None = None,
|
|
255
|
+
) -> str:
|
|
256
|
+
upstream = self._ports.normalize_model_id(provider, model)
|
|
257
|
+
alias = self._ports.alias_for(provider, upstream)
|
|
258
|
+
current_upstream = self._ports.normalize_model_id(
|
|
259
|
+
provider,
|
|
260
|
+
self._ports.current_upstream_model(provider, config),
|
|
261
|
+
)
|
|
262
|
+
return self.context_model_alias(
|
|
263
|
+
provider,
|
|
264
|
+
config,
|
|
265
|
+
alias,
|
|
266
|
+
None if upstream == current_upstream else upstream,
|
|
267
|
+
context_limit=context_limit,
|
|
268
|
+
)
|
|
269
|
+
|
|
205
270
|
def default_model_aliases(
|
|
206
271
|
self,
|
|
207
272
|
provider: str,
|
|
@@ -233,7 +298,21 @@ class ClaudeModelAliasPolicy:
|
|
|
233
298
|
if not selected and self.matches_family(current_upstream, family):
|
|
234
299
|
selected = current_upstream
|
|
235
300
|
if not selected:
|
|
236
|
-
|
|
301
|
+
family_candidates = [
|
|
302
|
+
item for item in candidates if self.matches_family(item, family)
|
|
303
|
+
]
|
|
304
|
+
if provider == "anthropic":
|
|
305
|
+
selected = next(
|
|
306
|
+
(
|
|
307
|
+
item
|
|
308
|
+
for item in family_candidates
|
|
309
|
+
if "[1m]" in item.lower()
|
|
310
|
+
and defaults_to_one_million_context(item)
|
|
311
|
+
),
|
|
312
|
+
"",
|
|
313
|
+
)
|
|
314
|
+
if not selected:
|
|
315
|
+
selected = next(iter(family_candidates), "")
|
|
237
316
|
alias = self._ports.alias_for(provider, selected) if selected else current_model_alias
|
|
238
317
|
result[key] = self.context_model_alias(
|
|
239
318
|
provider,
|
|
@@ -413,6 +492,17 @@ class ClaudeEnvironmentProjection:
|
|
|
413
492
|
claude_model,
|
|
414
493
|
context_limit=context_limit,
|
|
415
494
|
)
|
|
495
|
+
configured_subagent = str(provider_config.get("subagent_model") or "").strip()
|
|
496
|
+
subagent_model = (
|
|
497
|
+
self._aliases.routed_model_alias(
|
|
498
|
+
provider,
|
|
499
|
+
provider_config,
|
|
500
|
+
configured_subagent,
|
|
501
|
+
context_limit=context_limit,
|
|
502
|
+
)
|
|
503
|
+
if configured_subagent
|
|
504
|
+
else claude_model
|
|
505
|
+
)
|
|
416
506
|
env = {
|
|
417
507
|
"CIEL_RUNTIME_PROVIDER": provider,
|
|
418
508
|
"ANTHROPIC_BASE_URL": self._router_base,
|
|
@@ -423,10 +513,12 @@ class ClaudeEnvironmentProjection:
|
|
|
423
513
|
"ANTHROPIC_DEFAULT_HAIKU_MODEL": defaults["ANTHROPIC_DEFAULT_HAIKU_MODEL"],
|
|
424
514
|
"ANTHROPIC_DEFAULT_OPUS_MODEL": defaults["ANTHROPIC_DEFAULT_OPUS_MODEL"],
|
|
425
515
|
"ANTHROPIC_DEFAULT_SONNET_MODEL": defaults["ANTHROPIC_DEFAULT_SONNET_MODEL"],
|
|
426
|
-
"CLAUDE_CODE_SUBAGENT_MODEL":
|
|
516
|
+
"CLAUDE_CODE_SUBAGENT_MODEL": subagent_model,
|
|
427
517
|
"CIEL_RUNTIME_MODEL_ALIAS": claude_model,
|
|
428
518
|
"CIEL_RUNTIME_BYPASS_PERMISSIONS": "1",
|
|
429
519
|
}
|
|
520
|
+
if configured_subagent:
|
|
521
|
+
env["CLAUDE_CODE_SUBAGENT_MODEL_FORCE"] = "1"
|
|
430
522
|
auth_token = self._features.router_auth_token(provider, provider_config)
|
|
431
523
|
if auth_token:
|
|
432
524
|
env["ANTHROPIC_AUTH_TOKEN"] = auth_token
|
|
@@ -445,7 +537,12 @@ class ClaudeRuntimeSettingsPolicy:
|
|
|
445
537
|
self._ports = ports
|
|
446
538
|
|
|
447
539
|
def settings(self, provider: str, config: dict[str, Any]) -> dict[str, Any]:
|
|
448
|
-
|
|
540
|
+
settings: dict[str, Any] = {}
|
|
541
|
+
if self._ports.ultracode_enabled(provider, config):
|
|
542
|
+
settings["ultracode"] = True
|
|
543
|
+
if config.get("_ciel_session_socket_enabled") is True:
|
|
544
|
+
settings["crossSessionInbound"] = "accept"
|
|
545
|
+
return settings
|
|
449
546
|
|
|
450
547
|
def append_args(
|
|
451
548
|
self,
|
|
@@ -95,6 +95,8 @@ class ClaudeLaunchDeliveryPorts:
|
|
|
95
95
|
wake_submit_delay_seconds: Callback
|
|
96
96
|
wake_submit_retries: Callback
|
|
97
97
|
set_transcript_scope: Callback
|
|
98
|
+
prepare_session_socket: Callback
|
|
99
|
+
configure_session_socket: Callback
|
|
98
100
|
|
|
99
101
|
|
|
100
102
|
@dataclass(frozen=True, slots=True)
|
|
@@ -189,6 +191,8 @@ class ClaudeLaunchAssembly:
|
|
|
189
191
|
channel_wake_submit_delay_seconds=self.delivery.wake_submit_delay_seconds,
|
|
190
192
|
channel_wake_submit_retries=self.delivery.wake_submit_retries,
|
|
191
193
|
set_channel_transcript_scope=self.delivery.set_transcript_scope,
|
|
194
|
+
prepare_session_socket=self.delivery.prepare_session_socket,
|
|
195
|
+
configure_session_socket=self.delivery.configure_session_socket,
|
|
192
196
|
),
|
|
193
197
|
mcp_config=runtime_launch.ClaudeLaunchMcpConfig(
|
|
194
198
|
write_duckduckgo_mcp_config=self.mcp_config.write_duckduckgo,
|