@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.
Files changed (142) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +5 -2
  3. package/ciel_runtime.py +94 -96
  4. package/ciel_runtime_support/anthropic_model_policy.py +20 -3
  5. package/ciel_runtime_support/architecture.py +5 -0
  6. package/ciel_runtime_support/channel_inflight.py +4 -4
  7. package/ciel_runtime_support/channel_injection.py +56 -1
  8. package/ciel_runtime_support/channel_mcp_tools.py +34 -0
  9. package/ciel_runtime_support/channel_message_policy.py +9 -1
  10. package/ciel_runtime_support/channel_message_prompt.py +23 -0
  11. package/ciel_runtime_support/channel_pending_injection.py +86 -32
  12. package/ciel_runtime_support/channel_pending_poll.py +6 -2
  13. package/ciel_runtime_support/channel_runtime_environment.py +0 -11
  14. package/ciel_runtime_support/channel_terminal_context.py +0 -6
  15. package/ciel_runtime_support/channel_terminal_proxy.py +3 -42
  16. package/ciel_runtime_support/channel_transcript_repository.py +16 -1
  17. package/ciel_runtime_support/channel_wake_claim_repository.py +0 -19
  18. package/ciel_runtime_support/channel_wake_context.py +6 -13
  19. package/ciel_runtime_support/channel_wake_delivery_repository.py +50 -0
  20. package/ciel_runtime_support/chat_http_controller.py +88 -5
  21. package/ciel_runtime_support/claude_environment.py +100 -3
  22. package/ciel_runtime_support/claude_launch_assembly.py +4 -0
  23. package/ciel_runtime_support/claude_session_socket.py +181 -0
  24. package/ciel_runtime_support/cli_application_context.py +18 -10
  25. package/ciel_runtime_support/cli_dispatch.py +19 -3
  26. package/ciel_runtime_support/cli_parser.py +2 -0
  27. package/ciel_runtime_support/cli_usage.py +3 -2
  28. package/ciel_runtime_support/codex_backend_context.py +8 -3
  29. package/ciel_runtime_support/codex_completion_gate.py +198 -0
  30. package/ciel_runtime_support/codex_turn_recovery.py +240 -83
  31. package/ciel_runtime_support/compatibility_protocol.py +5 -2
  32. package/ciel_runtime_support/config_migrations.py +79 -0
  33. package/ciel_runtime_support/config_repository.py +7 -0
  34. package/ciel_runtime_support/context_summary_policy.py +24 -4
  35. package/ciel_runtime_support/external_event_menu.py +107 -0
  36. package/ciel_runtime_support/external_event_receiver.py +19 -1
  37. package/ciel_runtime_support/launch_state.py +2 -0
  38. package/ciel_runtime_support/managed_tool_injection.py +81 -0
  39. package/ciel_runtime_support/muse_runtime_context.py +291 -0
  40. package/ciel_runtime_support/ollama_thinking.py +12 -26
  41. package/ciel_runtime_support/prelaunch.py +13 -0
  42. package/ciel_runtime_support/prelaunch_launch_panel.py +2 -1
  43. package/ciel_runtime_support/prompt_compaction.py +39 -0
  44. package/ciel_runtime_support/provider_files_proxy.py +248 -0
  45. package/ciel_runtime_support/provider_model_identity.py +10 -2
  46. package/ciel_runtime_support/provider_option_cli.py +1 -1
  47. package/ciel_runtime_support/provider_request_access.py +54 -1
  48. package/ciel_runtime_support/provider_responses_passthrough.py +152 -15
  49. package/ciel_runtime_support/providers/alibaba.py +34 -1
  50. package/ciel_runtime_support/providers/anthropic.py +18 -1
  51. package/ciel_runtime_support/providers/meta.py +252 -21
  52. package/ciel_runtime_support/pseudo_tool_parser.py +17 -1
  53. package/ciel_runtime_support/remote_bridge.py +4 -0
  54. package/ciel_runtime_support/remote_instructions.py +5 -0
  55. package/ciel_runtime_support/remote_memory.py +1 -1
  56. package/ciel_runtime_support/responses_cache_diagnostics.py +82 -0
  57. package/ciel_runtime_support/responses_custom_tool_bridge.py +282 -0
  58. package/ciel_runtime_support/responses_input_compatibility.py +19 -3
  59. package/ciel_runtime_support/router_http.py +301 -4
  60. package/ciel_runtime_support/router_observability_context.py +26 -0
  61. package/ciel_runtime_support/router_server_context.py +1 -0
  62. package/ciel_runtime_support/runtime_adapters.py +31 -0
  63. package/ciel_runtime_support/runtime_constants.py +23 -1
  64. package/ciel_runtime_support/runtime_input_gateway.py +85 -5
  65. package/ciel_runtime_support/runtime_input_status.py +150 -0
  66. package/ciel_runtime_support/runtime_launch.py +35 -4
  67. package/ciel_runtime_support/runtime_paths.py +1 -0
  68. package/ciel_runtime_support/speech_http_controller.py +3 -2
  69. package/ciel_runtime_support/streaming_anthropic.py +3 -0
  70. package/ciel_runtime_support/tool_call_events.py +98 -0
  71. package/ciel_runtime_support/transcript_delta_delivery.py +140 -1
  72. package/ciel_runtime_support/ui_text.py +1 -0
  73. package/ciel_runtime_support/web_search_result_events.py +130 -0
  74. package/ciel_runtime_support/web_ui.py +1 -0
  75. package/ciel_runtime_support/windows_conpty.py +92 -8
  76. package/ciel_runtime_support/workspace_mcp.py +4 -1
  77. package/ciel_runtime_support/workspace_state.py +1 -0
  78. package/docs/Configuration.md +20 -4
  79. package/docs/MCP-Channels.md +79 -6
  80. package/docs/Managed-Tool-Injection.md +25 -0
  81. package/docs/Module-Map.md +4 -0
  82. package/docs/Muse-Code.md +73 -0
  83. package/docs/Observability.md +39 -0
  84. package/docs/Providers.md +29 -2
  85. package/docs/Remote-Bridge.md +12 -0
  86. package/docs/Router.md +1 -0
  87. package/docs/journal/2026/09/01/diagnostics/anthropic/router/fable51-usage-credits.okf +70 -0
  88. package/docs/journal/2026/09/01/implementation/claude/session-socket/all-input-paths/default-delivery.okf +133 -0
  89. package/docs/journal/2026/09/01/implementation/claude/session-socket/windows-direct-input.okf +112 -0
  90. package/docs/journal/2026/09/02/diagnostics/cache/alibaba/qwen38-post-restart-hit-rate.okf +107 -0
  91. package/docs/journal/2026/09/02/diagnostics/cache/ollama-cloud/kimi-k3-codex-hit-rate.okf +91 -0
  92. package/docs/journal/2026/09/02/diagnostics/codex/tui/statusline-cache-metrics.okf +44 -0
  93. package/docs/journal/2026/09/02/diagnostics/input/transport/fallback-behavior.okf +31 -0
  94. package/docs/journal/2026/09/02/diagnostics/providers/ollama-cloud/kimi-k3-agent-turn-recovery.okf +125 -0
  95. package/docs/journal/2026/09/02/diagnostics/providers/ollama-cloud/kimi-k3-parameters.okf +128 -0
  96. package/docs/journal/2026/09/02/diagnostics/remote/mia/socket-tui-visibility.okf +47 -0
  97. package/docs/journal/2026/09/02/diagnostics/runtime/alibaba/token-plan/qwen38-latency.okf +108 -0
  98. package/docs/journal/2026/09/02/diagnostics/runtime/alibaba/token-plan/qwen38-vs-gpt56-latency.okf +92 -0
  99. package/docs/journal/2026/09/02/implementation/anthropic/context/one-million-defaults.okf +85 -0
  100. package/docs/journal/2026/09/02/implementation/cache/alibaba/qwen38-cache-hit-improvement.okf +104 -0
  101. package/docs/journal/2026/09/02/implementation/cache/alibaba/qwen38-cache-hit-improvement.png +0 -0
  102. package/docs/journal/2026/09/02/implementation/cache/alibaba/responses-session-cache.okf +97 -0
  103. package/docs/journal/2026/09/02/implementation/providers/alibaba/qwen38/0902-parameter-alignment.okf +97 -0
  104. package/docs/journal/2026/09/02/implementation/providers/meta/multimodal-tools/contributor-protocol-adoption.okf +140 -0
  105. package/docs/journal/2026/09/02/implementation/providers/meta/muse-spark-1.3-support.okf +53 -0
  106. package/docs/journal/2026/09/02/monitoring/cache/alibaba/cielarvis-qwen38-live-hit-rate.okf +858 -0
  107. package/docs/journal/2026/09/02/operations/release/main-merge-local-deploy.okf +36 -0
  108. package/docs/journal/2026/09/02/operations/release/nightly/local-cache-deployment.okf +54 -0
  109. package/docs/journal/2026/09/02/operations/release/nightly/ollama-kimi-recovery-model-audit-deployment.okf +58 -0
  110. package/docs/journal/2026/09/02/operations/release/nightly/qwen38-cache-improvement-deployment.okf +72 -0
  111. package/docs/journal/2026/09/02/research/cache/alibaba/qwen38-hit-rate-improvement.okf +233 -0
  112. package/docs/journal/2026/09/02/research/cache/codex/provider-scope.okf +77 -0
  113. package/docs/journal/2026/09/02/research/providers/ollama-cloud/desktop-model-exhaustive-audit.okf +159 -0
  114. package/docs/journal/2026/09/03/diagnostics/providers/meta/muse-spark-contributor-cache-hit.okf +148 -0
  115. package/docs/journal/2026/09/03/diagnostics/providers/meta/muse-spark-contributor-required-schema.okf +127 -0
  116. package/docs/journal/2026/09/03/diagnostics/providers/ollama-cloud/kimi-k3-resumed-session-stall.okf +160 -0
  117. package/docs/journal/2026/09/03/diagnostics/providers/ollama-cloud/kimi-k3-substantive-dangling-action.okf +94 -0
  118. package/docs/journal/2026/09/03/diagnostics/remote/kevin/codex-gpt-early-turn-completion.okf +162 -0
  119. package/docs/journal/2026/09/03/implementation/runtimes/meta/muse-code/default-yolo.okf +40 -0
  120. package/docs/journal/2026/09/03/implementation/runtimes/meta/muse-code/native-subscription-router.okf +86 -0
  121. package/docs/journal/2026/09/03/operations/deployment/local/session-socket-default-fallback.okf +41 -0
  122. package/docs/journal/2026/09/03/research/deployment/colab/tailscale/credential-storage.okf +41 -0
  123. package/docs/journal/2026/09/03/research/providers/meta/muse-code/subscription-billing-boundary.okf +64 -0
  124. package/docs/journal/2026/09/03/research/web-chat/voice/instruction-format.okf +135 -0
  125. package/docs/journal/2026/09/04/diagnostics/claude/context/early-auto-compaction.okf +75 -0
  126. package/docs/journal/2026/09/04/diagnostics/claude/context/fable-51-status-200k.okf +54 -0
  127. package/docs/journal/2026/09/04/diagnostics/runtime/codex/responses/replayed-item-id-validation.okf +74 -0
  128. package/docs/journal/2026/09/04/diagnostics/workspaces/onecieldmsui/codex/replay-stall.okf +100 -0
  129. package/docs/journal/2026/09/04/implementation/observability/tool-calls/websocket-stream.okf +49 -0
  130. package/docs/journal/2026/09/04/implementation/web-chat/input/raw-injection.okf +42 -0
  131. package/docs/journal/2026/09/05/diagnostics/tty/claude-raw-idle-render.okf +25 -0
  132. package/docs/journal/2026/09/05/implementation/events/search-response-urls.okf +23 -0
  133. package/docs/journal/2026/09/05/implementation/tools/native-injection.okf +19 -0
  134. package/docs/journal/2026/09/05/implementation/windows/conpty/prompt-delivery-lifecycle.okf +43 -0
  135. package/docs/journal/2026/09/05/release/nightly/conpty-native-tools.okf +13 -0
  136. package/docs/journal/2026/09/05/release/nightly/search-result-urls.okf +14 -0
  137. package/docs/journal/2026/09/06/implementation/codex/inherited-web-mcp.okf +22 -0
  138. package/docs/journal/2026/09/07/fixes/codex/native-web-transport.okf +39 -0
  139. package/docs/journal/2026/09/07/integration/main/nightly-merge.okf +13 -0
  140. package/docs/journal/2026/09/07/release/stable/0.2.38.okf +10 -0
  141. package/docs/journal/2026/09/07/release/stable/0.2.39.okf +10 -0
  142. 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
- return claude_root, codex_root
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
- mark_delivered: Callable[[int], None]
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
- mark_delivered=self.pending_delivery.mark_delivered,
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 = self._first(params, "input_transport", "") or "tty"
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
- message = (
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 message,
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
- selected = next((item for item in candidates if self.matches_family(item, family)), "")
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": claude_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
- return {"ultracode": True} if self._ports.ultracode_enabled(provider, config) else {}
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,