diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 51a6aa9e..6c68d500 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -545,11 +545,14 @@ and native Anthropic Messages transports may receive them. Discord and Telegram messaging. Managed task invocations set `ANTHROPIC_API_URL`, `ANTHROPIC_BASE_URL`, gateway model discovery, non-interactive terminal settings, optional `--resume`, optional -`--fork-session`, and `--output-format stream-json`. The managed session parser -extracts persistent Claude session IDs and yields Claude stream-json events to -the messaging event parser. Managed Claude also owns subprocess stderr -diagnostic classification so known benign Claude Code notices do not become -messaging task errors, while unknown stderr remains fatal. +`--fork-session`, `--model opus`, and `--output-format stream-json`. Messaging +pins this Claude tier alias so phone sessions route through `MODEL_OPUS` or the +`MODEL` fallback instead of inheriting a user's interactive `/model` picker +state. The managed session parser extracts persistent Claude session IDs and +yields Claude stream-json events to the messaging event parser. Managed Claude +also owns subprocess stderr diagnostic classification so known benign Claude +Code notices do not become messaging task errors, while unknown stderr remains +fatal. Codex is supported through `fcc-codex` and Codex extensions. FCC does not keep an internal managed-Codex session runner because no user-facing messaging setting diff --git a/cli/managed/claude.py b/cli/managed/claude.py index ae3b9cb1..74825d1c 100644 --- a/cli/managed/claude.py +++ b/cli/managed/claude.py @@ -13,6 +13,8 @@ from cli.claude_env import ( claude_auth_token, ) +MANAGED_CLAUDE_MODEL_TIER = "opus" + @dataclass(frozen=True, slots=True) class ManagedClaudeTaskRequest: @@ -89,6 +91,7 @@ def build_managed_claude_invocation( "prompt": request.prompt, "cwd": config.workspace_path, "claude_binary": config.claude_bin, + "managed_model_tier": MANAGED_CLAUDE_MODEL_TIER, "cli_argv": cmd, }, ) @@ -134,6 +137,8 @@ def build_managed_claude_command( if fork_session: cmd.append("--fork-session") cmd += [ + "--model", + MANAGED_CLAUDE_MODEL_TIER, "-p", prompt, "--output-format", @@ -144,6 +149,8 @@ def build_managed_claude_command( else: cmd = [ claude_bin, + "--model", + MANAGED_CLAUDE_MODEL_TIER, "-p", prompt, "--output-format", diff --git a/pyproject.toml b/pyproject.toml index 829bceee..814efbb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "free-claude-code" -version = "3.4.9" +version = "3.4.10" description = "Middleware between Claude Code CLI (Anthropic API) and NVIDIA NIM" readme = "README.md" requires-python = ">=3.14.0" diff --git a/tests/cli/test_managed_claude.py b/tests/cli/test_managed_claude.py index 4fccc4f7..bd2d855a 100644 --- a/tests/cli/test_managed_claude.py +++ b/tests/cli/test_managed_claude.py @@ -1,6 +1,7 @@ import os from cli.managed.claude import ( + MANAGED_CLAUDE_MODEL_TIER, ManagedClaudeConfig, ManagedClaudeParseState, ManagedClaudeTaskRequest, @@ -51,7 +52,12 @@ def test_managed_claude_builds_new_task_command_and_env() -> None: base_env={"PATH": "keep", "ANTHROPIC_API_KEY": "official"}, ) - assert invocation.argv[:2] == ("claude", "-p") + assert invocation.argv[:4] == ( + "claude", + "--model", + MANAGED_CLAUDE_MODEL_TIER, + "-p", + ) assert "hello" in invocation.argv assert "--output-format" in invocation.argv assert "stream-json" in invocation.argv @@ -65,6 +71,7 @@ def test_managed_claude_builds_new_task_command_and_env() -> None: assert "ANTHROPIC_API_KEY" not in invocation.env assert invocation.trace_metadata["client_cli_id"] == "claude" assert invocation.trace_metadata["claude_binary"] == "claude" + assert invocation.trace_metadata["managed_model_tier"] == MANAGED_CLAUDE_MODEL_TIER def test_managed_claude_builds_resume_and_fork_commands() -> None: @@ -81,9 +88,24 @@ def test_managed_claude_builds_resume_and_fork_commands() -> None: base_env={}, ) - assert resume.argv[:3] == ("claude", "--resume", "sess_1") + assert resume.argv[:6] == ( + "claude", + "--resume", + "sess_1", + "--model", + MANAGED_CLAUDE_MODEL_TIER, + "-p", + ) assert "--fork-session" not in resume.argv - assert fork.argv[:3] == ("claude", "--resume", "sess_1") + assert fork.argv[:7] == ( + "claude", + "--resume", + "sess_1", + "--fork-session", + "--model", + MANAGED_CLAUDE_MODEL_TIER, + "-p", + ) assert "--fork-session" in fork.argv diff --git a/tests/contracts/test_import_boundaries.py b/tests/contracts/test_import_boundaries.py index 4f28c3f5..18e48e0e 100644 --- a/tests/contracts/test_import_boundaries.py +++ b/tests/contracts/test_import_boundaries.py @@ -608,6 +608,11 @@ def test_cli_surfaces_are_explicit_launchers_and_managed_claude() -> None: claude_env_text = (cli_root / "claude_env.py").read_text(encoding="utf-8") assert 'CLAUDE_CODE_AUTO_COMPACT_WINDOW = "190000"' in claude_env_text assert 'CLAUDE_NO_AUTH_SENTINEL = "fcc-no-auth"' in claude_env_text + managed_claude_text = (cli_root / "managed" / "claude.py").read_text( + encoding="utf-8" + ) + assert 'MANAGED_CLAUDE_MODEL_TIER = "opus"' in managed_claude_text + assert '"managed_model_tier": MANAGED_CLAUDE_MODEL_TIER' in managed_claude_text for path in { cli_root / "launchers" / "claude.py", cli_root / "managed" / "claude.py", diff --git a/uv.lock b/uv.lock index adc31cef..6187f62b 100644 --- a/uv.lock +++ b/uv.lock @@ -561,7 +561,7 @@ wheels = [ [[package]] name = "free-claude-code" -version = "3.4.9" +version = "3.4.10" source = { editable = "." } dependencies = [ { name = "aiohttp" },