@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
@@ -0,0 +1,181 @@
1
+ """Claude Code cross-session socket transport for admitted runtime inputs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import posixpath
10
+ import re
11
+ import secrets
12
+ import socket
13
+ import threading
14
+ import uuid
15
+ from typing import Any, Callable
16
+
17
+
18
+ SESSION_KEY_LIMIT = 4096
19
+ TOKEN_HEX_LENGTH = 32
20
+
21
+
22
+ def generated_socket_path(platform_name: str | None = None) -> str:
23
+ platform_name = platform_name or os.name
24
+ nonce = secrets.token_hex(16)
25
+ if platform_name == "nt":
26
+ return rf"\\.\pipe\LOCAL\cc-msg-{nonce}"
27
+ uid = os.getuid() if hasattr(os, "getuid") else 0
28
+ temp_root = os.environ.get("TMPDIR") or "/tmp"
29
+ if not temp_root.startswith("/"):
30
+ temp_root = "/tmp"
31
+ return posixpath.join(temp_root, f"cc-socks-{uid}", f"ciel-{nonce}.sock")
32
+
33
+
34
+ def canonical_socket_path(path: str, platform_name: str | None = None) -> str:
35
+ if (platform_name or os.name) == "nt":
36
+ match = re.fullmatch(
37
+ r"[\\/]{2}[.?][\\/]pipe[\\/](?:(LOCAL)[\\/])?([^\\/]+)",
38
+ path,
39
+ flags=re.IGNORECASE,
40
+ )
41
+ if match is None:
42
+ return path
43
+ local_prefix = "LOCAL\\" if match.group(1) else ""
44
+ canonical = rf"\\.\pipe\{local_prefix}{match.group(2)}"
45
+ return re.sub(r"[A-Z]", lambda value: value.group(0).lower(), canonical)
46
+ return str(Path(path).expanduser().resolve(strict=False))
47
+
48
+
49
+ def session_key_hash(path: str, platform_name: str | None = None) -> str:
50
+ canonical = canonical_socket_path(path, platform_name)
51
+ return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
52
+
53
+
54
+ def prepared_socket_path(config: dict[str, Any], passthrough: list[str]) -> str:
55
+ for index, value in enumerate(passthrough):
56
+ if value == "--messaging-socket-path" and index + 1 < len(passthrough):
57
+ return str(passthrough[index + 1])
58
+ text = str(value)
59
+ if text.startswith("--messaging-socket-path="):
60
+ return text.partition("=")[2]
61
+ claude_config = config.get("claude_code") if isinstance(config, dict) else {}
62
+ if isinstance(claude_config, dict) and claude_config.get("session_socket_input") is False:
63
+ return ""
64
+ return generated_socket_path()
65
+
66
+
67
+ class ClaudeSessionSocketClient:
68
+ def __init__(
69
+ self,
70
+ home: Path,
71
+ log: Callable[[str, str], None],
72
+ *,
73
+ platform_name: str | None = None,
74
+ ) -> None:
75
+ self._home = home
76
+ self._log = log
77
+ self._platform_name = platform_name or os.name
78
+ self._target = ""
79
+ self._lock = threading.Lock()
80
+
81
+ @property
82
+ def target(self) -> str:
83
+ with self._lock:
84
+ return self._target
85
+
86
+ def configure(self, path: str | None) -> None:
87
+ target = str(path or "").strip()
88
+ with self._lock:
89
+ self._target = target
90
+ self._log(
91
+ "INFO",
92
+ "claude_session_socket_%s%s"
93
+ % ("configured" if target else "cleared", f" target={target}" if target else ""),
94
+ )
95
+
96
+ def available(self) -> bool:
97
+ return bool(self.target)
98
+
99
+ def send(self, prompt: str, _messages: list[dict[str, Any]] | None = None) -> bool:
100
+ target = self.target
101
+ if not target:
102
+ self._log("INFO", "claude_session_socket_deferred reason=target_unconfigured")
103
+ return False
104
+ token = self._read_peer_token(target)
105
+ if not token:
106
+ self._log("INFO", "claude_session_socket_deferred reason=auth_key_unavailable")
107
+ return False
108
+ frames = (
109
+ json.dumps({"type": "auth", "token": token}, separators=(",", ":"))
110
+ + "\n"
111
+ + json.dumps(
112
+ {
113
+ "type": "user",
114
+ "uuid": str(uuid.uuid4()),
115
+ "message": {"role": "user", "content": prompt},
116
+ "priority": "next",
117
+ },
118
+ ensure_ascii=False,
119
+ separators=(",", ":"),
120
+ )
121
+ + "\n"
122
+ ).encode("utf-8")
123
+ try:
124
+ self._write(target, frames)
125
+ except OSError as exc:
126
+ self._log(
127
+ "INFO",
128
+ f"claude_session_socket_deferred reason=connect_failed error={type(exc).__name__}: {exc}",
129
+ )
130
+ return False
131
+ self._log(
132
+ "INFO",
133
+ f"claude_session_socket_injected bytes={len(frames)} target={target}",
134
+ )
135
+ return True
136
+
137
+ def _read_peer_token(self, target: str) -> str:
138
+ digest = session_key_hash(target, self._platform_name)
139
+ directory = self._home / ".claude" / "sessions"
140
+ try:
141
+ candidates = sorted(
142
+ directory.glob(f"*.{digest}.key"),
143
+ key=lambda item: item.stat().st_mtime_ns,
144
+ reverse=True,
145
+ )
146
+ except OSError:
147
+ return ""
148
+ for path in candidates:
149
+ try:
150
+ if path.stat().st_size > SESSION_KEY_LIMIT:
151
+ continue
152
+ payload = json.loads(path.read_text(encoding="utf-8"))
153
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError):
154
+ continue
155
+ token = str(payload.get("peerToken") or "") if isinstance(payload, dict) else ""
156
+ if len(token) == TOKEN_HEX_LENGTH and all(char in "0123456789abcdefABCDEF" for char in token):
157
+ return token
158
+ return ""
159
+
160
+ def _write(self, target: str, frames: bytes) -> None:
161
+ if self._platform_name == "nt":
162
+ with open(target, "wb", buffering=0) as stream:
163
+ stream.write(frames)
164
+ return
165
+ client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
166
+ try:
167
+ client.settimeout(2.0)
168
+ client.connect(target)
169
+ client.sendall(frames)
170
+ client.shutdown(socket.SHUT_WR)
171
+ finally:
172
+ client.close()
173
+
174
+
175
+ __all__ = [
176
+ "ClaudeSessionSocketClient",
177
+ "canonical_socket_path",
178
+ "generated_socket_path",
179
+ "prepared_socket_path",
180
+ "session_key_hash",
181
+ ]
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import argparse
6
6
  from dataclasses import dataclass
7
- from typing import Any, Callable, Sequence
7
+ from typing import Any, Callable, Mapping, Sequence
8
8
 
9
9
 
10
10
  @dataclass(frozen=True, slots=True)
@@ -17,8 +17,7 @@ class CliApplicationDispatchPorts:
17
17
  launch_agy: Callable[[list[str]], int]
18
18
  launch_kimi: Callable[[list[str]], int]
19
19
  kimi_login: Callable[[], int]
20
- launch_grok: Callable[[list[str]], int] = lambda _argv: 127
21
- launch_zcode: Callable[[list[str]], int] = lambda _argv: 127
20
+ extra_launchers: Mapping[str, Callable[[list[str]], int]]
22
21
 
23
22
 
24
23
  @dataclass(frozen=True, slots=True)
@@ -59,10 +58,13 @@ class CliApplicationContext:
59
58
  raise SystemExit(self.dispatch.launch_agy(args.argv))
60
59
 
61
60
  def cmd_launch_grok(self, args: argparse.Namespace) -> None:
62
- raise SystemExit(self.dispatch.launch_grok(args.argv))
61
+ raise SystemExit(self.dispatch.extra_launchers["grok"](args.argv))
63
62
 
64
63
  def cmd_launch_zcode(self, args: argparse.Namespace) -> None:
65
- raise SystemExit(self.dispatch.launch_zcode(args.argv))
64
+ raise SystemExit(self.dispatch.extra_launchers["zcode"](args.argv))
65
+
66
+ def cmd_launch_muse(self, args: argparse.Namespace) -> None:
67
+ raise SystemExit(self.dispatch.extra_launchers["muse"](args.argv))
66
68
 
67
69
  def cmd_version(self, _: argparse.Namespace) -> None:
68
70
  self.presentation.output(f"ciel-runtime {self.presentation.version}")
@@ -85,11 +87,14 @@ class CliApplicationContext:
85
87
  "kimi": self.dispatch.launch_kimi,
86
88
  "kimi-code": self.dispatch.launch_kimi,
87
89
  "launch-kimi": self.dispatch.launch_kimi,
88
- "grok": self.dispatch.launch_grok,
89
- "grok-build": self.dispatch.launch_grok,
90
- "launch-grok": self.dispatch.launch_grok,
91
- "zcode": self.dispatch.launch_zcode,
92
- "launch-zcode": self.dispatch.launch_zcode,
90
+ "grok": self.dispatch.extra_launchers["grok"],
91
+ "grok-build": self.dispatch.extra_launchers["grok"],
92
+ "launch-grok": self.dispatch.extra_launchers["grok"],
93
+ "zcode": self.dispatch.extra_launchers["zcode"],
94
+ "launch-zcode": self.dispatch.extra_launchers["zcode"],
95
+ "muse": self.dispatch.extra_launchers["muse"],
96
+ "muse-code": self.dispatch.extra_launchers["muse"],
97
+ "launch-muse": self.dispatch.extra_launchers["muse"],
93
98
  }
94
99
  if len(arguments) >= 2 and arguments[1] in routes:
95
100
  raise SystemExit(routes[arguments[1]](arguments[2:]))
@@ -133,6 +138,9 @@ class CliApplicationCompatibilityApi:
133
138
  def cmd_launch_zcode(self, args: argparse.Namespace) -> None:
134
139
  self.context().cmd_launch_zcode(args)
135
140
 
141
+ def cmd_launch_muse(self, args: argparse.Namespace) -> None:
142
+ self.context().cmd_launch_muse(args)
143
+
136
144
  def cmd_version(self, args: argparse.Namespace) -> None:
137
145
  self.context().cmd_version(args)
138
146
 
@@ -35,6 +35,7 @@ class CliRuntime:
35
35
  native_codex_enabled: Any
36
36
  launch_grok: Any = lambda *_args, **_kwargs: 127
37
37
  launch_zcode: Any = lambda *_args, **_kwargs: 127
38
+ launch_muse: Any = lambda *_args, **_kwargs: 127
38
39
 
39
40
 
40
41
  @dataclass(frozen=True)
@@ -123,6 +124,7 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
123
124
  launch_codex_app_server = services.runtime.launch_codex_app_server
124
125
  launch_grok = services.runtime.launch_grok
125
126
  launch_zcode = services.runtime.launch_zcode
127
+ launch_muse = services.runtime.launch_muse
126
128
  load_config = services.core.load_config
127
129
  native_agy_enabled = services.runtime.native_agy_enabled
128
130
  native_codex_enabled = services.runtime.native_codex_enabled
@@ -148,6 +150,8 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
148
150
  return launch_grok(rest)
149
151
  if head in ("zcode", "launch-zcode"):
150
152
  return launch_zcode(rest)
153
+ if head in ("muse", "muse-code", "launch-muse"):
154
+ return launch_muse(rest)
151
155
  if head in ("version", "--version", "-v"):
152
156
  print(f"ciel-runtime {VERSION}")
153
157
  return 0
@@ -278,6 +282,8 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
278
282
  return launch_codex(argv)
279
283
  if runtime == "agy":
280
284
  return launch_agy(argv)
285
+ if runtime == "muse":
286
+ return launch_muse(argv)
281
287
  if codex_passthrough_has_command(argv):
282
288
  cfg = load_config()
283
289
  provider, _ = get_current_provider(cfg)
@@ -324,8 +330,10 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
324
330
  runtime = str(value or "").strip().lower()
325
331
  if runtime in ("codex-app", "codex-appserver"):
326
332
  runtime = "codex-app-server"
327
- if runtime not in ("claude", "codex", "codex-app-server", "agy", "grok", "zcode", "last"):
328
- raise SystemExit("--ca-runtime must be claude, codex, codex-app-server, agy, grok, zcode, or last")
333
+ if runtime in ("muse-code", "musecode"):
334
+ runtime = "muse"
335
+ if runtime not in ("claude", "codex", "codex-app-server", "agy", "grok", "zcode", "muse", "last"):
336
+ raise SystemExit("--ca-runtime must be claude, codex, codex-app-server, agy, grok, zcode, muse, or last")
329
337
  elif arg in ("--ca-no-launch", "--ca-configure-only", "--ca-setup-only"):
330
338
  configure_only = True
331
339
  skip_menu = True
@@ -696,7 +704,7 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
696
704
  return 0
697
705
  if runtime == "last":
698
706
  remembered_runtime = str(last_launch_runtime() or "").strip().lower()
699
- runtime = remembered_runtime if remembered_runtime in {"claude", "codex", "agy", "grok", "zcode"} else "claude"
707
+ runtime = remembered_runtime if remembered_runtime in {"claude", "codex", "agy", "grok", "zcode", "muse"} else "claude"
700
708
  if runtime == "agy":
701
709
  return launch_agy(
702
710
  passthrough,
@@ -737,6 +745,14 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
737
745
  update_check=update_check,
738
746
  self_update_check=self_update_check,
739
747
  )
748
+ if runtime == "muse":
749
+ return launch_muse(
750
+ passthrough,
751
+ skip_menu=skip_menu,
752
+ force_menu=force_menu,
753
+ update_check=update_check,
754
+ self_update_check=self_update_check,
755
+ )
740
756
  return launch_claude(
741
757
  passthrough,
742
758
  skip_menu=skip_menu,
@@ -18,6 +18,7 @@ class CliParserLaunch:
18
18
  remote_bridge: CliHandler
19
19
  launch_grok: CliHandler = lambda _args: None
20
20
  launch_zcode: CliHandler = lambda _args: None
21
+ launch_muse: CliHandler = lambda _args: None
21
22
 
22
23
 
23
24
  @dataclass(frozen=True)
@@ -87,6 +88,7 @@ def build_cli_parser(services: CliParserServices) -> argparse.ArgumentParser:
87
88
  _add_remainder_command(commands, "launch-agy", services.launch.launch_agy)
88
89
  _add_remainder_command(commands, "launch-grok", services.launch.launch_grok)
89
90
  _add_remainder_command(commands, "launch-zcode", services.launch.launch_zcode)
91
+ _add_remainder_command(commands, "launch-muse", services.launch.launch_muse)
90
92
  commands.add_parser("serve").set_defaults(func=services.launch.serve)
91
93
  bridge = commands.add_parser("bridge")
92
94
  bridge.add_argument(
@@ -8,6 +8,7 @@ def cli_usage_text() -> str:
8
8
  ciel-runtime agy [args...] Launch Google Antigravity CLI through ciel-runtime
9
9
  ciel-runtime grok [args...] Launch the official Grok Build CLI
10
10
  ciel-runtime zcode [args...] Launch ZCode through ciel-runtime router
11
+ ciel-runtime muse [args...] Launch Muse Code with its subscription login
11
12
  ciel-runtime resume Resume Codex/AGY when that runtime provider is selected
12
13
 
13
14
  Control plane, runs before Claude Code and does not require LLM connectivity:
@@ -45,8 +46,8 @@ Headless setup flags, namespaced to avoid Claude CLI collisions:
45
46
  Publish localhost through Tailscale Serve HTTPS
46
47
  ciel-runtime --ca-provider PROVIDER Set provider, then launch
47
48
  ciel-runtime --ca-env-file PATH Load CIEL_RUNTIME_* values from a .env file
48
- ciel-runtime --ca-runtime claude|codex|codex-app-server|agy|grok|zcode
49
- Select Claude Code, Codex, Codex app-server, AGY, or Grok Build
49
+ ciel-runtime --ca-runtime claude|codex|codex-app-server|agy|grok|zcode|muse
50
+ Select Claude Code, Codex, Codex app-server, AGY, Grok Build, ZCode, or Muse Code
50
51
  ciel-runtime --ca-menu Apply setup values, then open the menu
51
52
  ciel-runtime --ca-language en|ko|ja|zh
52
53
  ciel-runtime --ca-base-url URL Set current provider base URL, then launch
@@ -236,16 +236,19 @@ class CodexBackendContext:
236
236
  provider: str,
237
237
  pcfg: dict[str, Any],
238
238
  body: dict[str, Any],
239
+ protocol: str | None = None,
239
240
  ) -> dict[str, Any]:
240
241
  return self.provider_projection.apply_request_policy(
241
- provider, pcfg, dict(body)
242
+ provider, pcfg, dict(body), protocol
242
243
  )
243
244
 
244
245
  def chat_passthrough(self) -> OpenAIChatPassthrough:
245
246
  return OpenAIChatPassthrough(
246
247
  OpenAIChatPassthroughPorts(
247
248
  normalize_model=self.normalize_model,
248
- normalize_request=self.normalize_request,
249
+ normalize_request=lambda provider, pcfg, body: self.normalize_request(
250
+ provider, pcfg, body, "openai_chat"
251
+ ),
249
252
  upstream_base=self.provider_transport.upstream_base,
250
253
  join_url=self.provider_transport.join_url,
251
254
  headers=self.provider_projection.chat_headers,
@@ -266,7 +269,9 @@ class CodexBackendContext:
266
269
  project_channel_context=self.project_channel_context,
267
270
  begin_channel_delivery=self.channel.begin_delivery,
268
271
  normalize_model=self.normalize_model,
269
- normalize_request=self.normalize_request,
272
+ normalize_request=lambda provider, pcfg, body: self.normalize_request(
273
+ provider, pcfg, body, "openai_responses"
274
+ ),
270
275
  upstream_base=self.provider_transport.upstream_base,
271
276
  join_url=self.provider_transport.join_url,
272
277
  headers=self.provider_projection.responses_headers,
@@ -0,0 +1,198 @@
1
+ """Language-independent completion validation for Responses agent turns."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import copy
6
+ import json
7
+ from dataclasses import dataclass, field
8
+ from typing import Any
9
+
10
+ from .codex_turn_recovery import (
11
+ CODEX_COMPLETION_TOOL_NAME,
12
+ CODEX_STRICT_CONTINUATION_NUDGE,
13
+ )
14
+
15
+
16
+ _MAX_SSE_EVENT_BYTES = 8 * 1024 * 1024
17
+
18
+
19
+ def _event_payload(block: bytes) -> dict[str, Any] | None:
20
+ data = b"\n".join(
21
+ line[5:].lstrip()
22
+ for line in block.splitlines()
23
+ if line.startswith(b"data:")
24
+ )
25
+ if not data or data == b"[DONE]":
26
+ return None
27
+ try:
28
+ decoded = json.loads(data)
29
+ except (UnicodeDecodeError, ValueError):
30
+ return None
31
+ return decoded if isinstance(decoded, dict) else None
32
+
33
+
34
+ def _message_text(item: dict[str, Any]) -> str:
35
+ if item.get("type") != "message":
36
+ return ""
37
+ parts: list[str] = []
38
+ for part in item.get("content") or []:
39
+ if not isinstance(part, dict):
40
+ continue
41
+ if part.get("type") not in {"output_text", "text", "refusal"}:
42
+ continue
43
+ value = part.get("text") or part.get("refusal")
44
+ if isinstance(value, str):
45
+ parts.append(value)
46
+ return "\n".join(parts)
47
+
48
+
49
+ @dataclass(slots=True)
50
+ class ResponsesCompletionObservation:
51
+ """Incrementally retain only terminal Responses protocol state."""
52
+
53
+ response_id: str = ""
54
+ status: str = ""
55
+ output: list[dict[str, Any]] = field(default_factory=list)
56
+ parseable: bool = True
57
+ _pending: bytearray = field(default_factory=bytearray, repr=False)
58
+ _items: dict[int, dict[str, Any]] = field(default_factory=dict, repr=False)
59
+
60
+ def feed(self, chunk: bytes) -> None:
61
+ if not self.parseable or not chunk:
62
+ return
63
+ self._pending.extend(chunk)
64
+ if (
65
+ len(self._pending) > _MAX_SSE_EVENT_BYTES
66
+ and b"\n\n" not in self._pending
67
+ and b"\r\n\r\n" not in self._pending
68
+ ):
69
+ self.parseable = False
70
+ self._pending.clear()
71
+ return
72
+ while True:
73
+ candidates = [
74
+ (index, len(separator))
75
+ for separator in (b"\n\n", b"\r\n\r\n")
76
+ if (index := self._pending.find(separator)) >= 0
77
+ ]
78
+ marker, marker_size = min(candidates, default=(-1, 0))
79
+ if marker < 0:
80
+ return
81
+ block = bytes(self._pending[:marker])
82
+ del self._pending[: marker + marker_size]
83
+ self._accept(_event_payload(block))
84
+
85
+ def finish(self) -> None:
86
+ if self.parseable and self._pending:
87
+ self._accept(_event_payload(bytes(self._pending)))
88
+ self._pending.clear()
89
+ if not self.output and self._items:
90
+ self.output = [self._items[index] for index in sorted(self._items)]
91
+
92
+ def _accept(self, event: dict[str, Any] | None) -> None:
93
+ if not event:
94
+ return
95
+ event_type = event.get("type")
96
+ if event_type == "response.output_item.done":
97
+ item = event.get("item")
98
+ index = event.get("output_index")
99
+ if isinstance(item, dict) and isinstance(index, int):
100
+ self._items[index] = item
101
+ return
102
+ if event_type not in {"response.completed", "response.incomplete", "response.failed"}:
103
+ return
104
+ response = event.get("response")
105
+ if not isinstance(response, dict):
106
+ return
107
+ self.response_id = str(response.get("id") or "")
108
+ self.status = str(response.get("status") or "")
109
+ output = response.get("output")
110
+ if isinstance(output, list):
111
+ self.output = [item for item in output if isinstance(item, dict)]
112
+
113
+ @property
114
+ def has_reasoning(self) -> bool:
115
+ return any(item.get("type") == "reasoning" for item in self.output)
116
+
117
+ @property
118
+ def has_action(self) -> bool:
119
+ # Responses output items other than messages/reasoning are protocol-level
120
+ # actions or state. Treat unknown future item types conservatively as work.
121
+ return any(item.get("type") not in {"message", "reasoning"} for item in self.output)
122
+
123
+ @property
124
+ def visible_text(self) -> str:
125
+ return "\n".join(filter(None, (_message_text(item) for item in self.output)))
126
+
127
+ @property
128
+ def completion_confirmed(self) -> bool:
129
+ actions = [
130
+ item for item in self.output if item.get("type") not in {"message", "reasoning"}
131
+ ]
132
+ return bool(actions) and all(
133
+ item.get("name") == CODEX_COMPLETION_TOOL_NAME for item in actions
134
+ )
135
+
136
+
137
+ def request_requires_completion_check(
138
+ body: dict[str, Any], observation: ResponsesCompletionObservation
139
+ ) -> bool:
140
+ """Use response structure only; never classify natural-language wording."""
141
+
142
+ return bool(
143
+ body.get("tools")
144
+ and observation.parseable
145
+ and observation.status == "completed"
146
+ and observation.has_reasoning
147
+ and not observation.has_action
148
+ and observation.visible_text.strip()
149
+ and observation.output
150
+ )
151
+
152
+
153
+ def completion_check_body(
154
+ body: dict[str, Any], observation: ResponsesCompletionObservation
155
+ ) -> dict[str, Any]:
156
+ """Continue from a candidate final response using the official state shapes."""
157
+
158
+ projected = copy.deepcopy(body)
159
+ prompt = {
160
+ "type": "message",
161
+ "role": "user",
162
+ "content": [{"type": "input_text", "text": CODEX_STRICT_CONTINUATION_NUDGE}],
163
+ }
164
+ if projected.get("conversation"):
165
+ projected["input"] = [prompt]
166
+ elif bool(projected.get("store")) and observation.response_id:
167
+ projected["previous_response_id"] = observation.response_id
168
+ projected["input"] = [prompt]
169
+ else:
170
+ current = projected.get("input")
171
+ items = list(current) if isinstance(current, list) else ([current] if current else [])
172
+ projected["input"] = [*items, *copy.deepcopy(observation.output), prompt]
173
+ projected["stream"] = True
174
+ tools = list(projected.get("tools") or [])
175
+ tools.append(
176
+ {
177
+ "type": "function",
178
+ "name": CODEX_COMPLETION_TOOL_NAME,
179
+ "description": "Confirm that every action requested by the user is complete.",
180
+ "parameters": {
181
+ "type": "object",
182
+ "properties": {},
183
+ "required": [],
184
+ "additionalProperties": False,
185
+ },
186
+ "strict": True,
187
+ }
188
+ )
189
+ projected["tools"] = tools
190
+ projected["tool_choice"] = "required"
191
+ return projected
192
+
193
+
194
+ __all__ = [
195
+ "ResponsesCompletionObservation",
196
+ "completion_check_body",
197
+ "request_requires_completion_check",
198
+ ]