@oneciel-ai/ciel-runtime 0.2.37 → 0.2.38

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 (140) hide show
  1. package/CHANGELOG.md +78 -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 +33 -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 +27 -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/integration/main/nightly-merge.okf +13 -0
  139. package/docs/journal/2026/09/07/release/stable/0.2.38.okf +10 -0
  140. package/package.json +1 -1
@@ -0,0 +1,107 @@
1
+ """Prelaunch menu projection for the external event receiver."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from .external_event_receiver import ExternalEventReceiverService
8
+
9
+
10
+ def panel_rows(
11
+ service: ExternalEventReceiverService, router_base: str
12
+ ) -> tuple[list[str], list[str]]:
13
+ receiver = service.receiver_configs().get("default", {})
14
+ public_receiver = service.public_receiver("default", receiver)
15
+ environment_references = public_receiver.get("environment_references", {})
16
+ enabled = bool(receiver.get("enabled", False))
17
+ transport = str(receiver.get("transport") or "webhook")
18
+ input_transport = str(receiver.get("input_transport") or "auto")
19
+ event_types = (
20
+ receiver.get("event_types")
21
+ if isinstance(receiver.get("event_types"), list)
22
+ else []
23
+ )
24
+ secret_status = service.vault.status("default")
25
+
26
+ def secret_source(field_name: str, stored_key: str) -> str:
27
+ reference = environment_references.get(field_name, {})
28
+ if isinstance(reference, dict) and reference.get("name"):
29
+ availability = "available" if reference.get("available") else "missing"
30
+ return f"env:{reference['name']} {availability}"
31
+ return "stored" if secret_status[stored_key] else "unset"
32
+
33
+ rows = [
34
+ f"Enabled [{'on' if enabled else 'off'}]",
35
+ f"Transport [{transport}]",
36
+ f"Runtime input transport [{input_transport}]",
37
+ f"SSE URL [{str(receiver.get('url') or 'unset')}]",
38
+ "SSE content mode [CloudEvents 1.0 structured JSON]",
39
+ f"Allowed CloudEvent types [{', '.join(str(value) for value in event_types) if event_types else 'all'}]",
40
+ f"Cursor JSON pointer [{str(receiver.get('cursor_json_pointer') or 'SSE id field')}]",
41
+ f"Reconnect query parameter [{str(receiver.get('cursor_query_parameter') or 'Last-Event-ID header')}]",
42
+ f"Webhook signing secret [{secret_source('webhook_secret', 'stored_webhook_secret')}]",
43
+ f"SSE authorization [{secret_source('authorization', 'stored_authorization')}]",
44
+ f"Webhook endpoint [{router_base}/ca/events/webhooks/default]",
45
+ "Back",
46
+ ]
47
+ values = [
48
+ "enabled",
49
+ "transport",
50
+ "input_transport",
51
+ "url",
52
+ "__info__",
53
+ "event_types",
54
+ "cursor_json_pointer",
55
+ "cursor_query_parameter",
56
+ "webhook_secret",
57
+ "authorization",
58
+ "__info__",
59
+ "back",
60
+ ]
61
+ return rows, values
62
+
63
+
64
+ def update_config(
65
+ service: ExternalEventReceiverService, key: str, value: Any
66
+ ) -> list[str]:
67
+ current = service.receiver_configs().get("default", {})
68
+ body: dict[str, Any] = {
69
+ "enabled": bool(current.get("enabled", False)),
70
+ "transport": str(current.get("transport") or "webhook"),
71
+ "input_transport": str(current.get("input_transport") or "auto"),
72
+ "url": str(current.get("url") or ""),
73
+ "event_types": current.get("event_types")
74
+ if isinstance(current.get("event_types"), list)
75
+ else [],
76
+ "cursor_json_pointer": str(current.get("cursor_json_pointer") or ""),
77
+ "cursor_query_parameter": str(current.get("cursor_query_parameter") or ""),
78
+ }
79
+ if key == "enabled":
80
+ body["enabled"] = not body["enabled"]
81
+ elif key == "transport":
82
+ body["transport"] = "sse" if body["transport"] == "webhook" else "webhook"
83
+ elif key == "input_transport":
84
+ body["input_transport"] = str(value or "auto")
85
+ elif key == "event_types":
86
+ body["event_types"] = [
87
+ part.strip() for part in str(value or "").split(",") if part.strip()
88
+ ]
89
+ elif key in {
90
+ "url",
91
+ "cursor_json_pointer",
92
+ "cursor_query_parameter",
93
+ "webhook_secret",
94
+ "authorization",
95
+ }:
96
+ body[key] = str(value or "")
97
+ else:
98
+ raise ValueError(f"unsupported external event option: {key}")
99
+ updated = service.save_receiver("default", body)
100
+ return [
101
+ f"External event receiver updated: enabled={updated.get('enabled')} transport={updated.get('transport')} input_transport={updated.get('input_transport')}.",
102
+ "The router owns receiver connections; the prelaunch configuration process does not open a duplicate stream.",
103
+ "External events use the private Runtime Input Gateway and are never published to Web Chat.",
104
+ ]
105
+
106
+
107
+ __all__ = ["panel_rows", "update_config"]
@@ -418,6 +418,18 @@ class ExternalEventReceiverService:
418
418
  cursor_query_parameter = str(body.get("cursor_query_parameter") or "").strip()
419
419
  if len(cursor_query_parameter) > 80 or any(char in cursor_query_parameter for char in "&=?#/"):
420
420
  raise ValueError("cursor_query_parameter must be a single URL query parameter name")
421
+ input_transport = str(body.get("input_transport") or "auto").strip().lower().replace("-", "_")
422
+ input_transport = {
423
+ "socket": "session_socket",
424
+ "claude_socket": "session_socket",
425
+ "messaging_socket": "session_socket",
426
+ "terminal": "tty",
427
+ "stdin": "tty",
428
+ "llm": "router",
429
+ "context": "router",
430
+ }.get(input_transport, input_transport)
431
+ if input_transport not in {"auto", "session_socket", "tty", "router"}:
432
+ raise ValueError("input_transport must be auto, session_socket, tty, or router")
421
433
  cfg = self.load_config()
422
434
  root = cfg.setdefault("external_event_receivers", {})
423
435
  workspace = root.get(self.workspace_key)
@@ -433,6 +445,7 @@ class ExternalEventReceiverService:
433
445
  "event_types": [str(value) for value in event_types if str(value)],
434
446
  "cursor_json_pointer": cursor_json_pointer,
435
447
  "cursor_query_parameter": cursor_query_parameter,
448
+ "input_transport": input_transport,
436
449
  }
437
450
  )
438
451
  workspace[receiver_id] = current
@@ -525,7 +538,8 @@ class ExternalEventReceiverService:
525
538
  return True
526
539
 
527
540
  def _admit(self, receiver_id: str, transport: str, raw_text: str, event: dict[str, str], transport_event_id: str = "") -> dict[str, Any]:
528
- allowed = self.receiver_configs().get(receiver_id, {}).get("event_types", [])
541
+ receiver = self.receiver_configs().get(receiver_id, {})
542
+ allowed = receiver.get("event_types", [])
529
543
  if allowed and event["type"] not in allowed:
530
544
  raise ValueError("CloudEvent type is not admitted by this receiver")
531
545
  result = self.submit_event(
@@ -536,6 +550,10 @@ class ExternalEventReceiverService:
536
550
  event_type=event["type"],
537
551
  event_source=event["source"],
538
552
  transport_event_id=transport_event_id,
553
+ input_transport=(
554
+ "" if str(receiver.get("input_transport") or "auto") == "auto"
555
+ else str(receiver.get("input_transport") or "")
556
+ ),
539
557
  )
540
558
  self._set_status(receiver_id, state="connected", last_event_id=event["id"], last_event_at=time.time(), error="")
541
559
  return result
@@ -98,6 +98,8 @@ def last_launch_runtime(repository: LaunchStateRepository, cwd_key: str) -> str:
98
98
  return "agy"
99
99
  if mode.startswith("grok"):
100
100
  return "grok"
101
+ if mode.startswith("muse"):
102
+ return "muse"
101
103
  if mode.startswith("anthropic") or mode.startswith("router:"):
102
104
  return "claude"
103
105
  return ""
@@ -0,0 +1,33 @@
1
+ """Launch-mode policy for Ciel-owned tools, never user MCP definitions."""
2
+ from collections.abc import Mapping
3
+ from typing import Any
4
+
5
+
6
+ def should_inject_tool(*, native: bool, mode: str = "always") -> bool:
7
+ if mode not in {"always", "native", "non_native"}:
8
+ raise ValueError(f"Invalid managed tool injection mode: {mode}")
9
+ return mode == "always" or (mode == "native") == native
10
+
11
+
12
+ def codex_native_web_tool_overrides(*, native: bool) -> list[str]:
13
+ """Disable inherited replacement web MCPs for this launch, not on disk."""
14
+ if not native:
15
+ return []
16
+ return [
17
+ "-c", "mcp_servers.duckduckgo.enabled=false",
18
+ "-c", "mcp_servers.web_fetch.enabled=false",
19
+ ]
20
+
21
+
22
+ def select_managed_tools(
23
+ servers: Mapping[str, Any], *, native: bool,
24
+ ) -> dict[str, Any]:
25
+ selected = {}
26
+ for name, definition in servers.items():
27
+ if not isinstance(definition, dict):
28
+ continue
29
+ item = dict(definition)
30
+ mode = item.pop("injection_mode", "always")
31
+ if should_inject_tool(native=native, mode=mode):
32
+ selected[name] = item
33
+ return selected
@@ -0,0 +1,291 @@
1
+ """Native Muse Code CLI launch with subscription-safe authentication."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any, Callable
8
+
9
+
10
+ MUSE_INSTALL_URL = "https://dev.meta.ai/install.sh"
11
+ MUSE_SUBSCRIPTION_ENV_KEYS = ("META_API_KEY", "MODEL_API_KEY")
12
+
13
+
14
+ def has_option(argv: list[str], *names: str) -> bool:
15
+ return any(
16
+ value in names or any(value.startswith(f"{name}=") for name in names)
17
+ for value in argv
18
+ )
19
+
20
+
21
+ def option_value(argv: list[str], name: str) -> str:
22
+ for index, value in enumerate(argv):
23
+ if value.startswith(f"{name}="):
24
+ return value.split("=", 1)[1].strip()
25
+ if value == name and index + 1 < len(argv):
26
+ return str(argv[index + 1]).strip()
27
+ return ""
28
+
29
+
30
+ @dataclass(frozen=True, slots=True)
31
+ class MuseExecutable:
32
+ command: str
33
+ prefix_args: tuple[str, ...] = ()
34
+ platform: str = "native"
35
+ muse_path: str = ""
36
+ transcript_root: Path | None = None
37
+
38
+
39
+ @dataclass(frozen=True, slots=True)
40
+ class MuseProcessPorts:
41
+ find_executable: Callable[[str], str | None]
42
+ run: Callable[..., Any]
43
+ call: Callable[..., int]
44
+ print_line: Callable[..., None]
45
+ environment: dict[str, str]
46
+ augment_path: Callable[[dict[str, str]], str]
47
+ platform_name: str
48
+
49
+
50
+ @dataclass(frozen=True, slots=True)
51
+ class MuseConfigurationPorts:
52
+ load: Callable[[], dict[str, Any]]
53
+ current_provider: Callable[[dict[str, Any]], tuple[str, dict[str, Any]]]
54
+
55
+
56
+ @dataclass(frozen=True, slots=True)
57
+ class MuseLifecyclePorts:
58
+ materialize_command: Callable[..., tuple[list[str], dict[str, str]]]
59
+ start_router: Callable[[], Any]
60
+ run_with_router: Callable[[Callable[[], int], bool], int]
61
+ call_with_channel_proxy: Callable[..., int]
62
+ channel_delivery_mode: Callable[[dict[str, Any]], str]
63
+ web_backend_requested: Callable[[dict[str, Any]], bool]
64
+ record_launch: Callable[[str, str], None]
65
+ set_transcript_scope: Callable[..., None]
66
+
67
+
68
+ @dataclass(frozen=True, slots=True)
69
+ class MuseRuntimeContext:
70
+ process: MuseProcessPorts
71
+ config: MuseConfigurationPorts
72
+ lifecycle: MuseLifecyclePorts
73
+
74
+ def _native_executable(self) -> MuseExecutable | None:
75
+ executable = self.process.find_executable("muse")
76
+ if executable:
77
+ path = Path(str(executable)).expanduser()
78
+ home = path.parents[2] if len(path.parents) >= 3 else Path.home()
79
+ return MuseExecutable(
80
+ str(executable),
81
+ muse_path=str(executable),
82
+ transcript_root=home / ".local" / "share" / "muse",
83
+ )
84
+ return None
85
+
86
+ def _wsl_executable(self) -> MuseExecutable | None:
87
+ if self.process.platform_name != "nt":
88
+ return None
89
+ wsl = self.process.find_executable("wsl.exe") or self.process.find_executable("wsl")
90
+ if not wsl:
91
+ return None
92
+ result = self.process.run(
93
+ [wsl, "-e", "sh", "-lc", "command -v muse"],
94
+ check=False,
95
+ capture_output=True,
96
+ text=True,
97
+ )
98
+ if result.returncode:
99
+ return None
100
+ muse_path = str(result.stdout or "").strip().splitlines()
101
+ if not muse_path:
102
+ return None
103
+ path = muse_path[-1].strip()
104
+ if not path.startswith("/"):
105
+ return None
106
+ root_result = self.process.run(
107
+ [wsl, "-e", "sh", "-lc", 'wslpath -w "$HOME/.local/share/muse"'],
108
+ check=False,
109
+ capture_output=True,
110
+ text=True,
111
+ )
112
+ root_lines = str(root_result.stdout or "").strip().splitlines()
113
+ transcript_root = (
114
+ Path(root_lines[-1].strip())
115
+ if root_result.returncode == 0 and root_lines
116
+ else None
117
+ )
118
+ return MuseExecutable(
119
+ str(wsl),
120
+ (
121
+ "-e",
122
+ "env",
123
+ "-u",
124
+ "META_API_KEY",
125
+ "-u",
126
+ "MODEL_API_KEY",
127
+ path,
128
+ ),
129
+ "wsl",
130
+ path,
131
+ transcript_root,
132
+ )
133
+
134
+ def discover(self) -> MuseExecutable | None:
135
+ return self._native_executable() or self._wsl_executable()
136
+
137
+ def install_if_missing(self) -> MuseExecutable | None:
138
+ installed = self.discover()
139
+ if installed:
140
+ return installed
141
+ shell = self.process.find_executable("bash")
142
+ command = f"curl -fsSL {MUSE_INSTALL_URL} | bash"
143
+ if self.process.platform_name == "nt":
144
+ wsl = self.process.find_executable("wsl.exe") or self.process.find_executable("wsl")
145
+ if not wsl:
146
+ self.process.print_line(
147
+ "Muse Code supports macOS and Linux. Install WSL2, then run: "
148
+ f"wsl bash -lc '{command}'",
149
+ flush=True,
150
+ )
151
+ return None
152
+ install_command = [wsl, "-e", "bash", "-lc", command]
153
+ elif shell:
154
+ install_command = [shell, "-lc", command]
155
+ else:
156
+ self.process.print_line(
157
+ f"Muse Code is missing. Install it with: {command}", flush=True
158
+ )
159
+ return None
160
+ self.process.print_line("Installing Muse Code from Meta's official installer...", flush=True)
161
+ result = self.process.run(install_command, check=False)
162
+ if result.returncode:
163
+ self.process.print_line(
164
+ f"Muse Code installation failed (exit {result.returncode}).", flush=True
165
+ )
166
+ return None
167
+ return self.discover()
168
+
169
+ @staticmethod
170
+ def _model(provider: str, provider_config: dict[str, Any]) -> str:
171
+ configured = str(provider_config.get("current_model") or "").strip()
172
+ if provider == "meta" and configured.startswith("muse-"):
173
+ return configured
174
+ return "muse-spark-1.3"
175
+
176
+ @staticmethod
177
+ def _effort(provider: str, provider_config: dict[str, Any]) -> str:
178
+ if provider != "meta":
179
+ return ""
180
+ configured = str(provider_config.get("effort_level") or "").strip().lower()
181
+ configured = {"max": "ultra"}.get(configured, configured)
182
+ if configured in {"none", "minimal", "low", "medium", "high", "xhigh", "ultra"}:
183
+ return configured
184
+ return ""
185
+
186
+ def launch(self, passthrough: list[str]) -> int:
187
+ argv = list(passthrough)
188
+ executable = self.install_if_missing()
189
+ if executable is None:
190
+ return 127
191
+ config = self.config.load()
192
+ provider, provider_config = self.config.current_provider(config)
193
+ model = self._model(provider, provider_config)
194
+ options: dict[str, Any] = {"prefix_args": executable.prefix_args}
195
+ if not has_option(argv, "--yolo"):
196
+ options["yolo_args"] = ("--yolo",)
197
+ muse_provider = option_value(argv, "--provider").lower()
198
+ meta_launch = not muse_provider or muse_provider == "meta"
199
+ if meta_launch and model and not has_option(argv, "-m", "--model"):
200
+ options["model"] = model
201
+ effort = self._effort(provider, provider_config)
202
+ if meta_launch and effort and not has_option(argv, "--reasoning-effort"):
203
+ options["reasoning_effort"] = effort
204
+
205
+ env = self.process.environment.copy()
206
+ env["PATH"] = self.process.augment_path(env)
207
+ for name in MUSE_SUBSCRIPTION_ENV_KEYS:
208
+ env.pop(name, None)
209
+ command, child_env = self.lifecycle.materialize_command(
210
+ "muse",
211
+ executable.command,
212
+ env,
213
+ provider,
214
+ provider_config,
215
+ mode="native",
216
+ protocol="native",
217
+ cwd=Path.cwd(),
218
+ enable_channels=True,
219
+ passthrough=argv,
220
+ options=options,
221
+ )
222
+ non_session_commands = {
223
+ "--help", "-h", "--version", "-V", "-v", "version", "config", "export",
224
+ "trace", "skills", "sandbox", "schema", "serve", "session-message",
225
+ "auth", "login", "logout", "init",
226
+ }
227
+ is_session = not argv or argv[0] not in non_session_commands
228
+ interactive_session = is_session and (not argv or argv[0] != "exec")
229
+ if is_session:
230
+ self.lifecycle.record_launch(provider, model)
231
+ if interactive_session:
232
+ self.lifecycle.set_transcript_scope(
233
+ "muse",
234
+ cwd=Path.cwd(),
235
+ muse_home=executable.transcript_root,
236
+ )
237
+ manage_router = bool(
238
+ interactive_session
239
+ and (
240
+ self.lifecycle.channel_delivery_mode(config) == "llm"
241
+ or self.lifecycle.web_backend_requested(config)
242
+ )
243
+ and self.lifecycle.start_router()
244
+ )
245
+
246
+ def run() -> int:
247
+ if not interactive_session:
248
+ return self.process.call(command, env=child_env)
249
+ return self.lifecycle.call_with_channel_proxy(
250
+ command,
251
+ child_env,
252
+ wake_for_llm_delivery=False,
253
+ synthetic_enter_bytes=None,
254
+ normalize_bare_cr_for_synthetic_enter=False,
255
+ # Muse's session JSONL uses a different schema from the
256
+ # Claude/Codex turn-confirmation reader. Keep delivery
257
+ # fire-and-forget while the tool-event observer tails it.
258
+ channel_wake_submit_retries=1,
259
+ channel_wake_confirm_submit=False,
260
+ channel_wake_bracketed_paste=True,
261
+ )
262
+
263
+ return self.lifecycle.run_with_router(run, manage_router)
264
+
265
+
266
+ @dataclass(frozen=True, slots=True)
267
+ class MuseRuntimeCompatibilityApi:
268
+ context: Callable[[], MuseRuntimeContext]
269
+
270
+ def discover(self) -> MuseExecutable | None:
271
+ return self.context().discover()
272
+
273
+ def install_if_missing(self) -> MuseExecutable | None:
274
+ return self.context().install_if_missing()
275
+
276
+ def launch(self, passthrough: list[str]) -> int:
277
+ return self.context().launch(passthrough)
278
+
279
+
280
+ __all__ = [
281
+ "MUSE_INSTALL_URL",
282
+ "MUSE_SUBSCRIPTION_ENV_KEYS",
283
+ "MuseConfigurationPorts",
284
+ "MuseExecutable",
285
+ "MuseLifecyclePorts",
286
+ "MuseProcessPorts",
287
+ "MuseRuntimeCompatibilityApi",
288
+ "MuseRuntimeContext",
289
+ "has_option",
290
+ "option_value",
291
+ ]
@@ -25,22 +25,6 @@ class OllamaReasoningProfile:
25
25
  always_on: bool = False
26
26
 
27
27
 
28
- _GENERIC_CLOUD_LEVEL_MODELS = frozenset(
29
- {
30
- "gemma4:31b",
31
- "glm-5.1",
32
- "kimi-k2.6",
33
- "kimi-k2.7-code",
34
- "minimax-m2.7",
35
- "minimax-m3",
36
- "nemotron-3-nano:30b",
37
- "nemotron-3-super",
38
- "nemotron-3-ultra",
39
- "qwen3.5:397b",
40
- }
41
- )
42
-
43
-
44
28
  def _codex_level_description(level: str) -> str:
45
29
  return {
46
30
  "low": "Low reasoning effort",
@@ -107,6 +91,14 @@ def ollama_cloud_reasoning_profile(
107
91
  default_codex="high",
108
92
  always_on=True,
109
93
  )
94
+ if discovered == "glm_dsa_moe" or model == "glm-5.3":
95
+ return OllamaReasoningProfile(
96
+ native_levels=("low", "high", "max"),
97
+ codex_levels=("low", "high", "xhigh"),
98
+ default_native="max",
99
+ default_codex="xhigh",
100
+ always_on=True,
101
+ )
110
102
  if discovered == "kimi-k3" or model == "kimi-k3":
111
103
  return OllamaReasoningProfile(
112
104
  native_levels=("low", "high", "max"),
@@ -124,13 +116,6 @@ def ollama_cloud_reasoning_profile(
124
116
  default_native=native_default,
125
117
  default_codex="xhigh" if native_default == "max" else native_default,
126
118
  )
127
- if model in _GENERIC_CLOUD_LEVEL_MODELS:
128
- return OllamaReasoningProfile(
129
- native_levels=("low", "medium", "high", "max"),
130
- codex_levels=("low", "medium", "high", "xhigh"),
131
- default_native="medium",
132
- default_codex="medium",
133
- )
134
119
  return None
135
120
 
136
121
 
@@ -185,7 +170,7 @@ def ollama_cloud_model_config_updates(
185
170
  ),
186
171
  }
187
172
  return {
188
- "think": profile is not None,
173
+ "think": profile is not None or "thinking" in normalized_capabilities,
189
174
  "effort_level": profile.default_native if profile else "",
190
175
  "ollama_think_levels": list(profile.native_levels) if profile else [],
191
176
  "ollama_thinking_always_on": bool(profile and profile.always_on),
@@ -246,6 +231,8 @@ class OllamaThinkingPolicy:
246
231
  return "gptoss"
247
232
  if model in {"glm-5.2", "glm-5.2:cloud"}:
248
233
  return "glm5.2"
234
+ if model in {"glm-5.3", "glm-5.3:cloud"}:
235
+ return "glm_dsa_moe"
249
236
  return ""
250
237
 
251
238
  def value(
@@ -342,8 +329,7 @@ class OllamaThinkingPolicy:
342
329
  if "thinking" in capabilities:
343
330
  if thinking_disabled(request):
344
331
  return False
345
- if effort:
346
- return True
332
+ return True
347
333
  return None
348
334
 
349
335
 
@@ -82,6 +82,7 @@ class PrelaunchLaunchPolicy:
82
82
  launch_kimi: Callable[..., Any]
83
83
  launch_grok: Callable[..., Any] = lambda *_args, **_kwargs: 0
84
84
  launch_zcode: Callable[..., Any] = lambda *_args, **_kwargs: 0
85
+ launch_muse: Callable[..., Any] = lambda *_args, **_kwargs: 0
85
86
 
86
87
 
87
88
  @dataclass(frozen=True, slots=True)
@@ -223,6 +224,7 @@ def run_prelaunch_menu(passthrough: list[str] | None = None,
223
224
  launch_kimi = services.launch_policy.launch_kimi
224
225
  launch_grok = services.launch_policy.launch_grok
225
226
  launch_zcode = services.launch_policy.launch_zcode
227
+ launch_muse = services.launch_policy.launch_muse
226
228
  llm_option_current_bool = services.options.llm_option_current_bool
227
229
  llm_option_panel_rows = services.panel_rows.llm_option_panel_rows
228
230
  llm_option_prompt_default = services.options.llm_option_prompt_default
@@ -500,6 +502,10 @@ def run_prelaunch_menu(passthrough: list[str] | None = None,
500
502
  persist_launch_action(action)
501
503
  launch_zcode([])
502
504
  return PRELAUNCH_CANCEL
505
+ if action == "launch-muse":
506
+ persist_launch_action(action)
507
+ launch_muse([])
508
+ return PRELAUNCH_CANCEL
503
509
  continue
504
510
  if panel == "language" and value:
505
511
  cfg["language"] = value
@@ -770,6 +776,13 @@ def run_prelaunch_menu(passthrough: list[str] | None = None,
770
776
  continue
771
777
  if value in {"enabled", "transport"}:
772
778
  messages = set_external_event_config(value, "toggle")
779
+ elif value == "input_transport":
780
+ entered = prompt_menu_value(
781
+ "Runtime input transport (auto/session_socket/tty/router)",
782
+ restore_tty=restore_line_mode,
783
+ raw_tty=restore_raw_mode,
784
+ )
785
+ messages = set_external_event_config(value, entered or "auto")
773
786
  elif value == "url":
774
787
  entered = prompt_menu_value(
775
788
  "SSE URL (blank for webhook mode)",
@@ -28,10 +28,11 @@ def launch_panel_rows(
28
28
  f"Kimi{kimi_suffix}",
29
29
  "Grok Build",
30
30
  "ZCode",
31
+ "Muse Code (subscription)",
31
32
  f"Codex app server{codex_suffix}",
32
33
  "Back",
33
34
  ],
34
- ["launch", "launch-codex", "launch-agy", "launch-kimi", "launch-grok", "launch-zcode", "launch-codex-app-server", "back"],
35
+ ["launch", "launch-codex", "launch-agy", "launch-kimi", "launch-grok", "launch-zcode", "launch-muse", "launch-codex-app-server", "back"],
35
36
  )
36
37
 
37
38