Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 161ea446f0 | |||
| a29acfb750 | |||
| 9e7f5a2acd | |||
| 680014c784 |
@@ -353,6 +353,17 @@ Only starts once Phase 1 has every built-in running *through* the seam.
|
||||
2.2 → 2.3 (web); the risk center is **PR 1.5**, where the `_supervise_*_bridges`
|
||||
invariants live.
|
||||
|
||||
### Implementation progress
|
||||
|
||||
Append-only ledger — one line per PR as it opens, updated to `landed` on merge.
|
||||
The plan tables above stay the stable target; this tracks what has actually
|
||||
shipped. **12 PRs total** (Phase 1: 1.1–1.8, Phase 2: 2.1–2.4).
|
||||
|
||||
| PR | Status | Link |
|
||||
|---|---|---|
|
||||
| 1.1 Provider model + resolver | in review | #3239 |
|
||||
| 1.2 Signature normalization | in review | (stacked on 1.1) |
|
||||
|
||||
## Risks and open questions
|
||||
|
||||
- **Runner extraction is the risk center.** The `_supervise_*_bridges` mirrors
|
||||
|
||||
@@ -134,6 +134,9 @@ from omnigent.native_terminal import (
|
||||
from omnigent.native_terminal import (
|
||||
bind_session_runner as _bind_session_runner,
|
||||
)
|
||||
from omnigent.native_terminal import (
|
||||
normalize_extra_args as _normalize_extra_args,
|
||||
)
|
||||
from omnigent.native_terminal import (
|
||||
terminal_attach_url as _attach_url,
|
||||
)
|
||||
@@ -198,7 +201,8 @@ def run_antigravity_native(
|
||||
*,
|
||||
server: str | None,
|
||||
session_id: str | None,
|
||||
antigravity_args: tuple[str, ...] = (),
|
||||
extra_args: tuple[str, ...] | None = None,
|
||||
antigravity_args: tuple[str, ...] | None = None,
|
||||
resume_picker: bool = False,
|
||||
command: str | None = None,
|
||||
model: str | None = None,
|
||||
@@ -237,6 +241,9 @@ def run_antigravity_native(
|
||||
:returns: None after the terminal attach session ends.
|
||||
:raises click.ClickException: If setup, launch, or attach fails.
|
||||
"""
|
||||
antigravity_args = _normalize_extra_args(
|
||||
extra_args=extra_args, legacy_args=antigravity_args, legacy_param="antigravity_args"
|
||||
)
|
||||
resolved_command = (command or agy_binary_path()).strip()
|
||||
if not resolved_command:
|
||||
raise click.ClickException("Antigravity command must not be empty.")
|
||||
|
||||
+6
-6
@@ -1162,7 +1162,7 @@ def _run_claude_native_resume_redirect(
|
||||
run_claude_native(
|
||||
server=base_url,
|
||||
session_id=conversation_id,
|
||||
claude_args=(),
|
||||
extra_args=(),
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
)
|
||||
|
||||
@@ -1196,7 +1196,7 @@ def _run_codex_native_resume_redirect(
|
||||
run_codex_native(
|
||||
server=base_url,
|
||||
session_id=conversation_id,
|
||||
codex_args=(),
|
||||
extra_args=(),
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
)
|
||||
|
||||
@@ -1228,7 +1228,7 @@ def _run_pi_native_resume_redirect(
|
||||
run_pi_native(
|
||||
server=base_url,
|
||||
session_id=conversation_id,
|
||||
pi_args=(),
|
||||
extra_args=(),
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
)
|
||||
|
||||
@@ -1252,7 +1252,7 @@ def _run_kiro_native_resume_redirect(
|
||||
run_kiro_native(
|
||||
server=base_url,
|
||||
session_id=conversation_id,
|
||||
kiro_args=(),
|
||||
extra_args=(),
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
)
|
||||
|
||||
@@ -1292,7 +1292,7 @@ def _run_cursor_native_resume_redirect(
|
||||
run_cursor_native(
|
||||
server=base_url,
|
||||
session_id=conversation_id,
|
||||
cursor_args=(),
|
||||
extra_args=(),
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
)
|
||||
|
||||
@@ -1330,7 +1330,7 @@ def _run_kimi_native_resume_redirect(
|
||||
run_kimi_native(
|
||||
server=base_url,
|
||||
session_id=conversation_id,
|
||||
kimi_args=(),
|
||||
extra_args=(),
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
)
|
||||
|
||||
|
||||
@@ -102,6 +102,9 @@ from omnigent.native_terminal import (
|
||||
from omnigent.native_terminal import (
|
||||
bind_session_runner as _bind_session_runner,
|
||||
)
|
||||
from omnigent.native_terminal import (
|
||||
normalize_extra_args as _normalize_extra_args,
|
||||
)
|
||||
from omnigent.native_terminal import (
|
||||
terminal_attach_url as _attach_url,
|
||||
)
|
||||
@@ -507,7 +510,8 @@ def run_claude_native(
|
||||
*,
|
||||
server: str | None,
|
||||
session_id: str | None,
|
||||
claude_args: tuple[str, ...],
|
||||
extra_args: tuple[str, ...] | None = None,
|
||||
claude_args: tuple[str, ...] | None = None,
|
||||
resume_picker: bool = False,
|
||||
command: str = _DEFAULT_CLAUDE_COMMAND,
|
||||
use_claude_config: bool = False,
|
||||
@@ -542,6 +546,9 @@ def run_claude_native(
|
||||
:returns: None after the attach session ends.
|
||||
:raises click.ClickException: If setup, launch, or attach fails.
|
||||
"""
|
||||
claude_args = _normalize_extra_args(
|
||||
extra_args=extra_args, legacy_args=claude_args, legacy_param="claude_args"
|
||||
)
|
||||
startup_profiler = startup_profiler or StartupProfiler.from_env(
|
||||
name="omnigent claude",
|
||||
env_var=_CLAUDE_STARTUP_PROFILE_ENV_VAR,
|
||||
|
||||
+11
-13
@@ -244,7 +244,7 @@ def register_native_commands(cli: click.Group) -> None:
|
||||
server=server,
|
||||
session_id=resolved_session_id,
|
||||
resume_picker=choice.picker,
|
||||
claude_args=_resolve_harness_startup_args(cfg, "claude-native", claude_args),
|
||||
extra_args=_resolve_harness_startup_args(cfg, "claude-native", claude_args),
|
||||
use_claude_config=use_claude_config,
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
startup_profiler=startup_profiler,
|
||||
@@ -358,7 +358,7 @@ def register_native_commands(cli: click.Group) -> None:
|
||||
server=server,
|
||||
session_id=resolved_session_id,
|
||||
resume_picker=choice.picker,
|
||||
codex_args=_resolve_harness_startup_args(cfg, "codex-native", codex_args),
|
||||
extra_args=_resolve_harness_startup_args(cfg, "codex-native", codex_args),
|
||||
model=model,
|
||||
prompt=prompt,
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
@@ -463,7 +463,7 @@ def register_native_commands(cli: click.Group) -> None:
|
||||
server=server,
|
||||
session_id=resolved_session_id,
|
||||
resume_picker=choice.picker,
|
||||
opencode_args=_resolve_harness_startup_args(cfg, "opencode-native", opencode_args),
|
||||
extra_args=_resolve_harness_startup_args(cfg, "opencode-native", opencode_args),
|
||||
model=model,
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
)
|
||||
@@ -553,7 +553,7 @@ def register_native_commands(cli: click.Group) -> None:
|
||||
server=server,
|
||||
session_id=resolved_session_id,
|
||||
resume_picker=choice.picker,
|
||||
pi_args=_resolve_harness_startup_args(cfg, "pi-native", pi_args),
|
||||
extra_args=_resolve_harness_startup_args(cfg, "pi-native", pi_args),
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
)
|
||||
|
||||
@@ -668,7 +668,7 @@ def register_native_commands(cli: click.Group) -> None:
|
||||
server=server,
|
||||
session_id=resolved_session_id,
|
||||
resume_picker=choice.picker,
|
||||
cursor_args=_resolve_harness_startup_args(cfg, "cursor-native", cursor_args),
|
||||
extra_args=_resolve_harness_startup_args(cfg, "cursor-native", cursor_args),
|
||||
model=model,
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
mode=mode,
|
||||
@@ -798,7 +798,7 @@ def register_native_commands(cli: click.Group) -> None:
|
||||
server=server,
|
||||
session_id=resolved_session_id,
|
||||
resume_picker=choice.picker,
|
||||
kiro_args=launch_args,
|
||||
extra_args=launch_args,
|
||||
model=model,
|
||||
prompt=prompt,
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
@@ -887,7 +887,7 @@ def register_native_commands(cli: click.Group) -> None:
|
||||
server=server,
|
||||
session_id=resolved_session_id,
|
||||
resume_picker=choice.picker,
|
||||
goose_args=_resolve_harness_startup_args(cfg, "goose-native", goose_args),
|
||||
extra_args=_resolve_harness_startup_args(cfg, "goose-native", goose_args),
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
)
|
||||
|
||||
@@ -974,7 +974,7 @@ def register_native_commands(cli: click.Group) -> None:
|
||||
server=server,
|
||||
session_id=resolved_session_id,
|
||||
resume_picker=choice.picker,
|
||||
hermes_args=_resolve_harness_startup_args(cfg, "hermes-native", hermes_args),
|
||||
extra_args=_resolve_harness_startup_args(cfg, "hermes-native", hermes_args),
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
)
|
||||
|
||||
@@ -1073,9 +1073,7 @@ def register_native_commands(cli: click.Group) -> None:
|
||||
server=server,
|
||||
session_id=resolved_session_id,
|
||||
resume_picker=choice.picker,
|
||||
antigravity_args=_resolve_harness_startup_args(
|
||||
cfg, "antigravity-native", antigravity_args
|
||||
),
|
||||
extra_args=_resolve_harness_startup_args(cfg, "antigravity-native", antigravity_args),
|
||||
model=model,
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
command=resolved_command or None,
|
||||
@@ -1164,7 +1162,7 @@ def register_native_commands(cli: click.Group) -> None:
|
||||
server=server,
|
||||
session_id=resolved_session_id,
|
||||
resume_picker=choice.picker,
|
||||
qwen_args=_resolve_harness_startup_args(cfg, "qwen-native", qwen_args),
|
||||
extra_args=_resolve_harness_startup_args(cfg, "qwen-native", qwen_args),
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
)
|
||||
|
||||
@@ -1260,6 +1258,6 @@ def register_native_commands(cli: click.Group) -> None:
|
||||
server=server,
|
||||
session_id=resolved_session_id,
|
||||
resume_picker=choice.picker,
|
||||
kimi_args=_resolve_harness_startup_args(cfg, "kimi-native", kimi_args),
|
||||
extra_args=_resolve_harness_startup_args(cfg, "kimi-native", kimi_args),
|
||||
auto_open_conversation=auto_open_conversation,
|
||||
)
|
||||
|
||||
@@ -85,6 +85,9 @@ from omnigent.native_terminal import (
|
||||
from omnigent.native_terminal import (
|
||||
bind_session_runner as _bind_session_runner,
|
||||
)
|
||||
from omnigent.native_terminal import (
|
||||
normalize_extra_args as _normalize_extra_args,
|
||||
)
|
||||
from omnigent.native_terminal import (
|
||||
terminal_attach_url as _attach_url,
|
||||
)
|
||||
@@ -328,7 +331,8 @@ def run_codex_native(
|
||||
*,
|
||||
server: str | None,
|
||||
session_id: str | None,
|
||||
codex_args: tuple[str, ...],
|
||||
extra_args: tuple[str, ...] | None = None,
|
||||
codex_args: tuple[str, ...] | None = None,
|
||||
resume_picker: bool = False,
|
||||
command: str = _DEFAULT_CODEX_COMMAND,
|
||||
model: str | None = None,
|
||||
@@ -352,6 +356,9 @@ def run_codex_native(
|
||||
:returns: None after the terminal attach session ends.
|
||||
:raises click.ClickException: If setup fails.
|
||||
"""
|
||||
codex_args = _normalize_extra_args(
|
||||
extra_args=extra_args, legacy_args=codex_args, legacy_param="codex_args"
|
||||
)
|
||||
resolved_command = command.strip()
|
||||
if not resolved_command:
|
||||
raise click.ClickException("Codex command must not be empty.")
|
||||
|
||||
@@ -52,6 +52,9 @@ from omnigent.native_terminal import (
|
||||
DAEMON_TERMINAL_READY_TIMEOUT_S as _DAEMON_TERMINAL_READY_TIMEOUT_S,
|
||||
)
|
||||
from omnigent.native_terminal import bind_session_runner as _bind_session_runner
|
||||
from omnigent.native_terminal import (
|
||||
normalize_extra_args as _normalize_extra_args,
|
||||
)
|
||||
from omnigent.native_terminal import url_component
|
||||
|
||||
_DEFAULT_CURSOR_COMMAND = "cursor-agent"
|
||||
@@ -272,7 +275,8 @@ def run_cursor_native(
|
||||
*,
|
||||
server: str | None,
|
||||
session_id: str | None,
|
||||
cursor_args: tuple[str, ...],
|
||||
extra_args: tuple[str, ...] | None = None,
|
||||
cursor_args: tuple[str, ...] | None = None,
|
||||
resume_picker: bool = False,
|
||||
model: str | None = None,
|
||||
auto_open_conversation: bool = False,
|
||||
@@ -294,6 +298,9 @@ def run_cursor_native(
|
||||
Injected as ``--mode <mode>`` unless already present in *cursor_args*.
|
||||
:returns: None after the terminal attach session ends.
|
||||
"""
|
||||
cursor_args = _normalize_extra_args(
|
||||
extra_args=extra_args, legacy_args=cursor_args, legacy_param="cursor_args"
|
||||
)
|
||||
_preflight_local_tools()
|
||||
if server is None:
|
||||
raise click.ClickException(
|
||||
|
||||
@@ -54,6 +54,9 @@ from omnigent.native_terminal import (
|
||||
DAEMON_TERMINAL_READY_TIMEOUT_S as _DAEMON_TERMINAL_READY_TIMEOUT_S,
|
||||
)
|
||||
from omnigent.native_terminal import bind_session_runner as _bind_session_runner
|
||||
from omnigent.native_terminal import (
|
||||
normalize_extra_args as _normalize_extra_args,
|
||||
)
|
||||
from omnigent.native_terminal import url_component
|
||||
|
||||
_DEFAULT_GOOSE_COMMAND = "goose"
|
||||
@@ -153,7 +156,8 @@ def run_goose_native(
|
||||
*,
|
||||
server: str | None,
|
||||
session_id: str | None,
|
||||
goose_args: tuple[str, ...],
|
||||
extra_args: tuple[str, ...] | None = None,
|
||||
goose_args: tuple[str, ...] | None = None,
|
||||
resume_picker: bool = False,
|
||||
auto_open_conversation: bool = False,
|
||||
) -> None:
|
||||
@@ -168,6 +172,9 @@ def run_goose_native(
|
||||
URL after launch.
|
||||
:returns: None after the terminal attach session ends.
|
||||
"""
|
||||
goose_args = _normalize_extra_args(
|
||||
extra_args=extra_args, legacy_args=goose_args, legacy_param="goose_args"
|
||||
)
|
||||
_preflight_local_tools()
|
||||
if server is None:
|
||||
raise click.ClickException(
|
||||
|
||||
@@ -67,6 +67,30 @@ class NativeCodingAgent:
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class NativeHarnessProvider:
|
||||
"""Import paths for a native harness's lifecycle hooks.
|
||||
|
||||
``NativeCodingAgent`` is pure identity data; behavior lives here as a
|
||||
sibling row keyed by the same ``key``. Every value is a dotted import path
|
||||
(``module:attr`` or ``module.attr``) resolved lazily at dispatch time via
|
||||
:mod:`omnigent.native_dispatch`, so building the registry never imports the
|
||||
runner / CLI / native-harness stack. Optional hooks are ``None`` when the
|
||||
behavior is not yet a module-level function the resolver can reach (e.g.
|
||||
interrupt/stop handlers that are still runner closures, or the inline
|
||||
spawn-env dispatch); those hubs migrate onto the seam in later phases.
|
||||
"""
|
||||
|
||||
key: str # matches NativeCodingAgent.key
|
||||
run_native: str # CLI + resume launch entry point
|
||||
auto_create_terminal: str # runner terminal builder
|
||||
spawn_env_builder: str | None = None
|
||||
interrupt_handler: str | None = None
|
||||
stop_handler: str | None = None
|
||||
materialize_agent_spec: str | None = None # built-in agent seeding
|
||||
bridge_dir: str | None = None # cost-popup bridge-dir lookup
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class HarnessContribution:
|
||||
"""One package's harness registry contribution."""
|
||||
@@ -77,6 +101,7 @@ class HarnessContribution:
|
||||
aliases: dict[str, str] = field(default_factory=dict)
|
||||
native_harnesses: frozenset[str] = frozenset()
|
||||
native_agents: tuple[NativeCodingAgent, ...] = ()
|
||||
native_providers: tuple[NativeHarnessProvider, ...] = ()
|
||||
install_specs: dict[str, HarnessInstallSpec] = field(default_factory=dict)
|
||||
harness_install_keys: dict[str, str] = field(default_factory=dict)
|
||||
model_env_keys: dict[str, str] = field(default_factory=dict)
|
||||
@@ -199,6 +224,48 @@ HERMES_NATIVE_CODING_AGENT = NativeCodingAgent(
|
||||
)
|
||||
|
||||
|
||||
def _builtin_native_provider(key: str) -> NativeHarnessProvider:
|
||||
"""Build a built-in provider row from the ``omnigent.<key>_native`` module.
|
||||
|
||||
The built-in native harnesses follow a uniform module layout: each exports
|
||||
``run_<key>_native`` (CLI + resume launch) and ``_materialize_<key>_agent_spec``
|
||||
(agent seeding), and re-exports ``_auto_create_<key>_terminal`` from
|
||||
``omnigent.runner.native``. The remaining hooks (spawn-env, interrupt, stop,
|
||||
bridge-dir) are still runner-local closures / inline dispatch, so they stay
|
||||
``None`` until those hubs migrate onto the seam.
|
||||
"""
|
||||
module = f"omnigent.{key}_native"
|
||||
return NativeHarnessProvider(
|
||||
key=key,
|
||||
run_native=f"{module}:run_{key}_native",
|
||||
auto_create_terminal=f"omnigent.runner.native:_auto_create_{key}_terminal",
|
||||
materialize_agent_spec=f"{module}:_materialize_{key}_agent_spec",
|
||||
)
|
||||
|
||||
|
||||
# Behavior side-channel for the built-in native agents. One row per
|
||||
# NativeCodingAgent above, keyed by the same ``key``; resolved lazily so this
|
||||
# module stays import-light. See designs/harness-modular-registry-proposal.md
|
||||
# (Phase 1). Populated uniformly because every built-in native harness shares
|
||||
# the omnigent.<key>_native module layout.
|
||||
_BUILTIN_NATIVE_PROVIDERS: tuple[NativeHarnessProvider, ...] = tuple(
|
||||
_builtin_native_provider(agent.key)
|
||||
for agent in (
|
||||
CLAUDE_NATIVE_CODING_AGENT,
|
||||
CODEX_NATIVE_CODING_AGENT,
|
||||
PI_NATIVE_CODING_AGENT,
|
||||
OPENCODE_NATIVE_CODING_AGENT,
|
||||
CURSOR_NATIVE_CODING_AGENT,
|
||||
KIRO_NATIVE_CODING_AGENT,
|
||||
GOOSE_NATIVE_CODING_AGENT,
|
||||
ANTIGRAVITY_NATIVE_CODING_AGENT,
|
||||
QWEN_NATIVE_CODING_AGENT,
|
||||
KIMI_NATIVE_CODING_AGENT,
|
||||
HERMES_NATIVE_CODING_AGENT,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Declared capabilities for the built-in harnesses. Each value is backed by the
|
||||
# module that implements it; the derivable axes (model_family, subagents) are
|
||||
# asserted against their source in tests/test_harness_capabilities.py so the
|
||||
@@ -617,6 +684,7 @@ _BUILTIN_CONTRIBUTION = HarnessContribution(
|
||||
KIMI_NATIVE_CODING_AGENT,
|
||||
HERMES_NATIVE_CODING_AGENT,
|
||||
),
|
||||
native_providers=_BUILTIN_NATIVE_PROVIDERS,
|
||||
model_env_keys={
|
||||
"acp": "HARNESS_ACP_MODEL",
|
||||
"antigravity": "HARNESS_ANTIGRAVITY_MODEL",
|
||||
@@ -846,6 +914,22 @@ def native_agents() -> tuple[NativeCodingAgent, ...]:
|
||||
return tuple(agents)
|
||||
|
||||
|
||||
def native_providers() -> tuple[NativeHarnessProvider, ...]:
|
||||
"""Return native-harness behavior provider rows, merged across contributions."""
|
||||
providers: list[NativeHarnessProvider] = []
|
||||
for contribution in plugin_state().contributions:
|
||||
providers.extend(contribution.native_providers)
|
||||
return tuple(providers)
|
||||
|
||||
|
||||
def native_provider_for_key(key: str) -> NativeHarnessProvider | None:
|
||||
"""Return the provider row whose ``key`` matches, or ``None``."""
|
||||
for provider in native_providers():
|
||||
if provider.key == key:
|
||||
return provider
|
||||
return None
|
||||
|
||||
|
||||
def harness_modules() -> dict[str, str]:
|
||||
"""Return runtime harness module mapping, aliases included."""
|
||||
modules = _merge_dict("harness_modules")
|
||||
|
||||
@@ -53,6 +53,9 @@ from omnigent.native_terminal import (
|
||||
DAEMON_TERMINAL_READY_TIMEOUT_S as _DAEMON_TERMINAL_READY_TIMEOUT_S,
|
||||
)
|
||||
from omnigent.native_terminal import bind_session_runner as _bind_session_runner
|
||||
from omnigent.native_terminal import (
|
||||
normalize_extra_args as _normalize_extra_args,
|
||||
)
|
||||
from omnigent.native_terminal import url_component
|
||||
|
||||
_DEFAULT_HERMES_COMMAND = "hermes"
|
||||
@@ -152,7 +155,8 @@ def run_hermes_native(
|
||||
*,
|
||||
server: str | None,
|
||||
session_id: str | None,
|
||||
hermes_args: tuple[str, ...],
|
||||
extra_args: tuple[str, ...] | None = None,
|
||||
hermes_args: tuple[str, ...] | None = None,
|
||||
resume_picker: bool = False,
|
||||
auto_open_conversation: bool = False,
|
||||
) -> None:
|
||||
@@ -167,6 +171,9 @@ def run_hermes_native(
|
||||
URL after launch.
|
||||
:returns: None after the terminal attach session ends.
|
||||
"""
|
||||
hermes_args = _normalize_extra_args(
|
||||
extra_args=extra_args, legacy_args=hermes_args, legacy_param="hermes_args"
|
||||
)
|
||||
_preflight_local_tools()
|
||||
if server is None:
|
||||
raise click.ClickException(
|
||||
|
||||
@@ -51,6 +51,9 @@ from omnigent.native_terminal import (
|
||||
DAEMON_TERMINAL_READY_TIMEOUT_S as _DAEMON_TERMINAL_READY_TIMEOUT_S,
|
||||
)
|
||||
from omnigent.native_terminal import bind_session_runner as _bind_session_runner
|
||||
from omnigent.native_terminal import (
|
||||
normalize_extra_args as _normalize_extra_args,
|
||||
)
|
||||
from omnigent.native_terminal import url_component
|
||||
|
||||
_DEFAULT_KIMI_COMMAND = "kimi"
|
||||
@@ -157,7 +160,8 @@ def run_kimi_native(
|
||||
*,
|
||||
server: str | None,
|
||||
session_id: str | None,
|
||||
kimi_args: tuple[str, ...],
|
||||
extra_args: tuple[str, ...] | None = None,
|
||||
kimi_args: tuple[str, ...] | None = None,
|
||||
resume_picker: bool = False,
|
||||
auto_open_conversation: bool = False,
|
||||
) -> None:
|
||||
@@ -172,6 +176,9 @@ def run_kimi_native(
|
||||
conversation URL after launch.
|
||||
:returns: None after the terminal attach session ends.
|
||||
"""
|
||||
kimi_args = _normalize_extra_args(
|
||||
extra_args=extra_args, legacy_args=kimi_args, legacy_param="kimi_args"
|
||||
)
|
||||
_preflight_local_tools()
|
||||
if server is None:
|
||||
raise click.ClickException(
|
||||
|
||||
@@ -39,6 +39,9 @@ from omnigent.native_terminal import (
|
||||
DAEMON_TERMINAL_READY_TIMEOUT_S as _DAEMON_TERMINAL_READY_TIMEOUT_S,
|
||||
)
|
||||
from omnigent.native_terminal import bind_session_runner as _bind_session_runner
|
||||
from omnigent.native_terminal import (
|
||||
normalize_extra_args as _normalize_extra_args,
|
||||
)
|
||||
from omnigent.native_terminal import url_component
|
||||
|
||||
_DEFAULT_KIRO_COMMAND = "kiro-cli"
|
||||
@@ -186,13 +189,17 @@ def run_kiro_native(
|
||||
*,
|
||||
server: str | None,
|
||||
session_id: str | None,
|
||||
kiro_args: tuple[str, ...],
|
||||
extra_args: tuple[str, ...] | None = None,
|
||||
kiro_args: tuple[str, ...] | None = None,
|
||||
resume_picker: bool = False,
|
||||
model: str | None = None,
|
||||
prompt: str | None = None,
|
||||
auto_open_conversation: bool = False,
|
||||
) -> None:
|
||||
"""Launch the Kiro TUI in an Omnigent terminal."""
|
||||
kiro_args = _normalize_extra_args(
|
||||
extra_args=extra_args, legacy_args=kiro_args, legacy_param="kiro_args"
|
||||
)
|
||||
_preflight_local_tools()
|
||||
if server is None:
|
||||
raise click.ClickException(
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Lazy resolver for native-harness provider hooks.
|
||||
|
||||
``NativeHarnessProvider`` rows hold dotted import *strings*, never live
|
||||
callables — building the harness registry must not import the runner / CLI /
|
||||
native-harness stack (see designs/harness-plugin-interface.md § import rules).
|
||||
This module is the single place those strings become callables, and only at
|
||||
dispatch time. Each dispatch hub (resume, CLI, runner launch/interrupt/stop,
|
||||
seeding) resolves its hook here instead of branching on ``key == "<x>"``.
|
||||
|
||||
Resolution is cached per import path so a hot dispatch loop imports each target
|
||||
module at most once.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from omnigent.harness_plugins import (
|
||||
NativeHarnessProvider,
|
||||
load_object,
|
||||
native_provider_for_key,
|
||||
)
|
||||
|
||||
_RESOLVE_CACHE: dict[str, Any] = {}
|
||||
|
||||
|
||||
def resolve(import_path: str) -> Any:
|
||||
"""Resolve a ``module:attr`` / ``module.attr`` path to its object, cached.
|
||||
|
||||
Thin caching wrapper over :func:`omnigent.harness_plugins.load_object`.
|
||||
"""
|
||||
cached = _RESOLVE_CACHE.get(import_path)
|
||||
if cached is None:
|
||||
cached = load_object(import_path)
|
||||
_RESOLVE_CACHE[import_path] = cached
|
||||
return cached
|
||||
|
||||
|
||||
def resolve_hook(provider: NativeHarnessProvider, hook: str) -> Any | None:
|
||||
"""Resolve one named hook on a provider, or ``None`` if it is unset.
|
||||
|
||||
``hook`` is a field name on :class:`NativeHarnessProvider` (e.g.
|
||||
``"run_native"``, ``"auto_create_terminal"``). Optional hooks that the
|
||||
provider leaves ``None`` resolve to ``None`` rather than raising, so callers
|
||||
can treat "no such hook yet" and "hook present" uniformly.
|
||||
"""
|
||||
import_path = getattr(provider, hook)
|
||||
if import_path is None:
|
||||
return None
|
||||
return resolve(import_path)
|
||||
|
||||
|
||||
def resolve_hook_for_key(key: str, hook: str) -> Any | None:
|
||||
"""Resolve a hook by native-agent ``key``, or ``None`` if unknown/unset."""
|
||||
provider = native_provider_for_key(key)
|
||||
if provider is None:
|
||||
return None
|
||||
return resolve_hook(provider, hook)
|
||||
|
||||
|
||||
def reset_resolve_cache_for_tests() -> None:
|
||||
"""Clear the per-path resolution cache."""
|
||||
_RESOLVE_CACHE.clear()
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import urllib.parse
|
||||
import warnings
|
||||
|
||||
import click
|
||||
import httpx
|
||||
@@ -14,6 +15,38 @@ DAEMON_RUNNER_ONLINE_TIMEOUT_S = 60.0
|
||||
DAEMON_TERMINAL_READY_TIMEOUT_S = 60.0
|
||||
|
||||
|
||||
def normalize_extra_args(
|
||||
*,
|
||||
extra_args: tuple[str, ...] | None,
|
||||
legacy_args: tuple[str, ...] | None,
|
||||
legacy_param: str,
|
||||
) -> tuple[str, ...]:
|
||||
"""Reconcile a native launcher's pass-through args from both spellings.
|
||||
|
||||
Every ``run_<x>_native`` entry point accepts a uniform keyword-only
|
||||
``extra_args`` (the spelling the provider seam calls with) plus its legacy
|
||||
``<x>_args`` alias. This collapses the two into one tuple:
|
||||
|
||||
- only ``extra_args`` set → use it;
|
||||
- only the legacy alias set → use it, emitting a ``DeprecationWarning``;
|
||||
- both set → ``extra_args`` wins (the legacy alias is ignored) with a warning;
|
||||
- neither set → empty tuple.
|
||||
|
||||
The ``<x>_args`` alias is deprecated and slated for removal in 0.9.0; callers
|
||||
should pass ``extra_args``.
|
||||
"""
|
||||
if legacy_args is not None:
|
||||
warnings.warn(
|
||||
f"{legacy_param!r} is deprecated; pass extra_args instead "
|
||||
"(the alias is removed in 0.9.0)",
|
||||
DeprecationWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
if extra_args is None:
|
||||
return tuple(legacy_args)
|
||||
return tuple(extra_args or ())
|
||||
|
||||
|
||||
def url_component(value: str) -> str:
|
||||
"""
|
||||
Percent-encode a value for a path component.
|
||||
|
||||
@@ -55,6 +55,9 @@ from omnigent.native_terminal import (
|
||||
DAEMON_TERMINAL_READY_TIMEOUT_S as _DAEMON_TERMINAL_READY_TIMEOUT_S,
|
||||
)
|
||||
from omnigent.native_terminal import bind_session_runner as _bind_session_runner
|
||||
from omnigent.native_terminal import (
|
||||
normalize_extra_args as _normalize_extra_args,
|
||||
)
|
||||
from omnigent.native_terminal import url_component
|
||||
from omnigent.opencode_native_state import read_launch_state, write_launch_state
|
||||
|
||||
@@ -155,7 +158,8 @@ def run_opencode_native( # pragma: no cover
|
||||
*,
|
||||
server: str | None,
|
||||
session_id: str | None,
|
||||
opencode_args: tuple[str, ...],
|
||||
extra_args: tuple[str, ...] | None = None,
|
||||
opencode_args: tuple[str, ...] | None = None,
|
||||
resume_picker: bool = False,
|
||||
model: str | None = None,
|
||||
auto_open_conversation: bool = False,
|
||||
@@ -177,6 +181,9 @@ def run_opencode_native( # pragma: no cover
|
||||
:param auto_open_conversation: Open the browser conversation URL on launch.
|
||||
:returns: None after the terminal attach session ends.
|
||||
"""
|
||||
opencode_args = _normalize_extra_args(
|
||||
extra_args=extra_args, legacy_args=opencode_args, legacy_param="opencode_args"
|
||||
)
|
||||
_preflight_local_tools()
|
||||
if server is None:
|
||||
raise click.ClickException(
|
||||
|
||||
@@ -40,6 +40,9 @@ from omnigent.native_terminal import (
|
||||
DAEMON_TERMINAL_READY_TIMEOUT_S as _DAEMON_TERMINAL_READY_TIMEOUT_S,
|
||||
)
|
||||
from omnigent.native_terminal import bind_session_runner as _bind_session_runner
|
||||
from omnigent.native_terminal import (
|
||||
normalize_extra_args as _normalize_extra_args,
|
||||
)
|
||||
from omnigent.native_terminal import url_component
|
||||
from omnigent.pi_native_bridge import bridge_dir_for_session_id
|
||||
|
||||
@@ -198,7 +201,8 @@ def run_pi_native(
|
||||
*,
|
||||
server: str | None,
|
||||
session_id: str | None,
|
||||
pi_args: tuple[str, ...],
|
||||
extra_args: tuple[str, ...] | None = None,
|
||||
pi_args: tuple[str, ...] | None = None,
|
||||
resume_picker: bool = False,
|
||||
auto_open_conversation: bool = False,
|
||||
) -> None:
|
||||
@@ -213,6 +217,9 @@ def run_pi_native(
|
||||
conversation URL after launch.
|
||||
:returns: None after the terminal attach session ends.
|
||||
"""
|
||||
pi_args = _normalize_extra_args(
|
||||
extra_args=extra_args, legacy_args=pi_args, legacy_param="pi_args"
|
||||
)
|
||||
_preflight_local_tools()
|
||||
if server is None:
|
||||
raise click.ClickException(
|
||||
|
||||
@@ -54,6 +54,9 @@ from omnigent.native_terminal import (
|
||||
DAEMON_TERMINAL_READY_TIMEOUT_S as _DAEMON_TERMINAL_READY_TIMEOUT_S,
|
||||
)
|
||||
from omnigent.native_terminal import bind_session_runner as _bind_session_runner
|
||||
from omnigent.native_terminal import (
|
||||
normalize_extra_args as _normalize_extra_args,
|
||||
)
|
||||
from omnigent.native_terminal import url_component
|
||||
|
||||
_DEFAULT_QWEN_COMMAND = "qwen"
|
||||
@@ -152,7 +155,8 @@ def run_qwen_native(
|
||||
*,
|
||||
server: str | None,
|
||||
session_id: str | None,
|
||||
qwen_args: tuple[str, ...],
|
||||
extra_args: tuple[str, ...] | None = None,
|
||||
qwen_args: tuple[str, ...] | None = None,
|
||||
resume_picker: bool = False,
|
||||
auto_open_conversation: bool = False,
|
||||
) -> None:
|
||||
@@ -167,6 +171,9 @@ def run_qwen_native(
|
||||
URL after launch.
|
||||
:returns: None after the terminal attach session ends.
|
||||
"""
|
||||
qwen_args = _normalize_extra_args(
|
||||
extra_args=extra_args, legacy_args=qwen_args, legacy_param="qwen_args"
|
||||
)
|
||||
_preflight_local_tools()
|
||||
if server is None:
|
||||
raise click.ClickException(
|
||||
|
||||
+10
-10
@@ -222,7 +222,7 @@ def _dispatch_wrapper(
|
||||
run_claude_native(
|
||||
server=server,
|
||||
session_id=session_id,
|
||||
claude_args=(),
|
||||
extra_args=(),
|
||||
)
|
||||
return True
|
||||
if native_agent.key == "codex":
|
||||
@@ -231,7 +231,7 @@ def _dispatch_wrapper(
|
||||
run_codex_native(
|
||||
server=server,
|
||||
session_id=session_id,
|
||||
codex_args=(),
|
||||
extra_args=(),
|
||||
)
|
||||
return True
|
||||
if native_agent.key == "pi":
|
||||
@@ -240,7 +240,7 @@ def _dispatch_wrapper(
|
||||
run_pi_native(
|
||||
server=server,
|
||||
session_id=session_id,
|
||||
pi_args=(),
|
||||
extra_args=(),
|
||||
)
|
||||
return True
|
||||
if native_agent.key == "cursor":
|
||||
@@ -249,7 +249,7 @@ def _dispatch_wrapper(
|
||||
run_cursor_native(
|
||||
server=server,
|
||||
session_id=session_id,
|
||||
cursor_args=(),
|
||||
extra_args=(),
|
||||
)
|
||||
return True
|
||||
if native_agent.key == "kiro":
|
||||
@@ -258,7 +258,7 @@ def _dispatch_wrapper(
|
||||
run_kiro_native(
|
||||
server=server,
|
||||
session_id=session_id,
|
||||
kiro_args=(),
|
||||
extra_args=(),
|
||||
)
|
||||
return True
|
||||
if native_agent.key == "goose":
|
||||
@@ -267,7 +267,7 @@ def _dispatch_wrapper(
|
||||
run_goose_native(
|
||||
server=server,
|
||||
session_id=session_id,
|
||||
goose_args=(),
|
||||
extra_args=(),
|
||||
)
|
||||
return True
|
||||
if native_agent.key == "antigravity":
|
||||
@@ -276,7 +276,7 @@ def _dispatch_wrapper(
|
||||
run_antigravity_native(
|
||||
server=server,
|
||||
session_id=session_id,
|
||||
antigravity_args=(),
|
||||
extra_args=(),
|
||||
)
|
||||
return True
|
||||
if native_agent.key == "qwen":
|
||||
@@ -285,7 +285,7 @@ def _dispatch_wrapper(
|
||||
run_qwen_native(
|
||||
server=server,
|
||||
session_id=session_id,
|
||||
qwen_args=(),
|
||||
extra_args=(),
|
||||
)
|
||||
return True
|
||||
if native_agent.key == "kimi":
|
||||
@@ -294,7 +294,7 @@ def _dispatch_wrapper(
|
||||
run_kimi_native(
|
||||
server=server,
|
||||
session_id=session_id,
|
||||
kimi_args=(),
|
||||
extra_args=(),
|
||||
)
|
||||
return True
|
||||
if native_agent.key == "hermes":
|
||||
@@ -303,7 +303,7 @@ def _dispatch_wrapper(
|
||||
run_hermes_native(
|
||||
server=server,
|
||||
session_id=session_id,
|
||||
hermes_args=(),
|
||||
extra_args=(),
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -97,7 +97,7 @@ def test_redirect_native_resume_routes_kiro_wrapper(monkeypatch: pytest.MonkeyPa
|
||||
assert captured == {
|
||||
"server": "https://example.com",
|
||||
"session_id": "conv_kiro",
|
||||
"kiro_args": (),
|
||||
"extra_args": (),
|
||||
"auto_open_conversation": True,
|
||||
}
|
||||
|
||||
@@ -3814,7 +3814,7 @@ def test_redirect_native_resume_handles_cursor(monkeypatch: pytest.MonkeyPatch)
|
||||
assert captured == {
|
||||
"server": "https://example.com",
|
||||
"session_id": "conv_abc123",
|
||||
"cursor_args": (),
|
||||
"extra_args": (),
|
||||
"auto_open_conversation": True,
|
||||
}
|
||||
|
||||
|
||||
@@ -432,7 +432,7 @@ def test_claude_command_resume_binds_session_and_passes_unknown_args(
|
||||
# there).
|
||||
assert captured["server"] == "https://example.com"
|
||||
assert captured["session_id"] == "conv_abc"
|
||||
assert captured["claude_args"] == ("--resume", "claude-session", "-p", "say hi")
|
||||
assert captured["extra_args"] == ("--resume", "claude-session", "-p", "say hi")
|
||||
# No picker requested when ``--resume`` carries a value.
|
||||
assert captured["resume_picker"] is False
|
||||
# Default: Databricks auth is active (``--use-native-config`` not set) —
|
||||
@@ -465,7 +465,7 @@ def test_claude_command_short_r_binds_omnigent_session(
|
||||
assert result.exit_code == 0, result.output
|
||||
assert captured["session_id"] == "conv_abc"
|
||||
# ``-r <conv_id>`` consumes both tokens; no leftover claude args.
|
||||
assert captured["claude_args"] == ()
|
||||
assert captured["extra_args"] == ()
|
||||
assert captured["resume_picker"] is False
|
||||
|
||||
|
||||
@@ -662,7 +662,7 @@ def test_codex_command_resume_binds_session_and_passes_unknown_args(
|
||||
assert result.exit_code == 0, result.output
|
||||
assert captured["server"] == "https://example.com"
|
||||
assert captured["session_id"] == "conv_abc"
|
||||
assert captured["codex_args"] == ("-c", "approval_policy=on-request")
|
||||
assert captured["extra_args"] == ("-c", "approval_policy=on-request")
|
||||
assert captured["model"] == "gpt-test"
|
||||
assert captured["prompt"] == "say hi"
|
||||
assert captured["resume_picker"] is False
|
||||
@@ -876,7 +876,7 @@ def test_codex_config_args_form_base_cli_args_append(
|
||||
result = CliRunner().invoke(cli, ["codex", "--dangerously-skip-permissions"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert captured["codex_args"] == (
|
||||
assert captured["extra_args"] == (
|
||||
"--config",
|
||||
"k=v",
|
||||
"--dangerously-skip-permissions",
|
||||
@@ -901,7 +901,7 @@ def test_codex_config_args_only_when_no_cli_args(
|
||||
result = CliRunner().invoke(cli, ["codex"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert captured["codex_args"] == ("--verbose",)
|
||||
assert captured["extra_args"] == ("--verbose",)
|
||||
|
||||
|
||||
def test_codex_args_no_config_is_cli_args_only(
|
||||
@@ -919,7 +919,7 @@ def test_codex_args_no_config_is_cli_args_only(
|
||||
result = CliRunner().invoke(cli, ["codex", "--flag"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert captured["codex_args"] == ("--flag",)
|
||||
assert captured["extra_args"] == ("--flag",)
|
||||
|
||||
|
||||
def test_pi_config_args_form_base_cli_args_append(
|
||||
@@ -940,7 +940,7 @@ def test_pi_config_args_form_base_cli_args_append(
|
||||
result = CliRunner().invoke(cli, ["pi", "--cli-flag"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert captured["pi_args"] == ("--base", "--cli-flag")
|
||||
assert captured["extra_args"] == ("--base", "--cli-flag")
|
||||
|
||||
|
||||
def test_kiro_command_is_registered_in_click_help() -> None:
|
||||
@@ -988,7 +988,7 @@ def test_kiro_command_parses_native_options_and_prompt(
|
||||
assert captured["resume_picker"] is False
|
||||
assert captured["model"] == "auto"
|
||||
assert captured["prompt"] == "hi"
|
||||
assert captured["kiro_args"] == (
|
||||
assert captured["extra_args"] == (
|
||||
"--effort",
|
||||
"high",
|
||||
"--agent",
|
||||
|
||||
@@ -216,3 +216,44 @@ def test_community_namespace_imports_external_harness_package(
|
||||
|
||||
module = importlib.import_module("omnigent.community.harness.foo")
|
||||
assert module.VALUE == "ok"
|
||||
|
||||
|
||||
def test_builtin_native_providers_cover_every_native_agent() -> None:
|
||||
"""Every native agent has exactly one provider row keyed by the same key."""
|
||||
agent_keys = sorted(agent.key for agent in hp.native_agents())
|
||||
provider_keys = sorted(provider.key for provider in hp.native_providers())
|
||||
assert provider_keys == agent_keys
|
||||
# No duplicate provider keys.
|
||||
assert len(provider_keys) == len(set(provider_keys))
|
||||
|
||||
|
||||
def test_native_provider_for_key_lookup() -> None:
|
||||
assert hp.native_provider_for_key("claude") is not None
|
||||
assert hp.native_provider_for_key("claude").key == "claude"
|
||||
assert hp.native_provider_for_key("does-not-exist") is None
|
||||
|
||||
|
||||
def test_builtin_native_providers_have_required_hooks() -> None:
|
||||
"""run_native and auto_create_terminal are mandatory on every built-in row."""
|
||||
for provider in hp.native_providers():
|
||||
assert provider.run_native, provider.key
|
||||
assert provider.auto_create_terminal, provider.key
|
||||
|
||||
|
||||
def test_builtin_native_provider_paths_resolve() -> None:
|
||||
"""Every populated built-in provider hook resolves to a real callable.
|
||||
|
||||
This is the guard that keeps the provider rows honest: a typo'd import path
|
||||
or a renamed run_<x>_native symbol fails here rather than at dispatch time.
|
||||
"""
|
||||
from omnigent import native_dispatch
|
||||
|
||||
native_dispatch.reset_resolve_cache_for_tests()
|
||||
for provider in hp.native_providers():
|
||||
for hook in (
|
||||
"run_native",
|
||||
"auto_create_terminal",
|
||||
"materialize_agent_spec",
|
||||
):
|
||||
resolved = native_dispatch.resolve_hook(provider, hook)
|
||||
assert callable(resolved), f"{provider.key}.{hook} did not resolve to a callable"
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
|
||||
import pytest
|
||||
|
||||
import omnigent.harness_plugins as hp
|
||||
from omnigent import native_dispatch
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_caches() -> Iterator[None]:
|
||||
hp.reset_plugin_state_for_tests()
|
||||
native_dispatch.reset_resolve_cache_for_tests()
|
||||
yield
|
||||
hp.reset_plugin_state_for_tests()
|
||||
native_dispatch.reset_resolve_cache_for_tests()
|
||||
|
||||
|
||||
def test_resolve_colon_and_dot_paths() -> None:
|
||||
# module:attr form
|
||||
assert native_dispatch.resolve("omnigent.harness_plugins:load_object") is hp.load_object
|
||||
# module.attr form
|
||||
assert native_dispatch.resolve("omnigent.harness_plugins.load_object") is hp.load_object
|
||||
|
||||
|
||||
def test_resolve_is_cached() -> None:
|
||||
first = native_dispatch.resolve("omnigent.harness_plugins:native_agents")
|
||||
second = native_dispatch.resolve("omnigent.harness_plugins:native_agents")
|
||||
assert first is second
|
||||
assert "omnigent.harness_plugins:native_agents" in native_dispatch._RESOLVE_CACHE
|
||||
|
||||
|
||||
def test_resolve_hook_returns_none_for_unset_optional_hook() -> None:
|
||||
provider = hp.native_provider_for_key("claude")
|
||||
assert provider is not None
|
||||
# interrupt_handler is not yet a module-level function, so it stays None.
|
||||
assert provider.interrupt_handler is None
|
||||
assert native_dispatch.resolve_hook(provider, "interrupt_handler") is None
|
||||
|
||||
|
||||
def test_resolve_hook_resolves_populated_hook() -> None:
|
||||
provider = hp.native_provider_for_key("pi")
|
||||
assert provider is not None
|
||||
run_native = native_dispatch.resolve_hook(provider, "run_native")
|
||||
assert callable(run_native)
|
||||
assert run_native.__name__ == "run_pi_native"
|
||||
|
||||
|
||||
def test_resolve_hook_for_key() -> None:
|
||||
run_native = native_dispatch.resolve_hook_for_key("codex", "run_native")
|
||||
assert callable(run_native)
|
||||
assert run_native.__name__ == "run_codex_native"
|
||||
|
||||
|
||||
def test_resolve_hook_for_unknown_key_returns_none() -> None:
|
||||
assert native_dispatch.resolve_hook_for_key("does-not-exist", "run_native") is None
|
||||
@@ -177,3 +177,34 @@ def test_terminal_attach_url_encodes_path_components_and_switches_scheme() -> No
|
||||
url == "wss://example.databricks.com/base/v1/sessions/conv%2Fa%20b"
|
||||
"/resources/terminals/terminal%2Fmain/attach"
|
||||
)
|
||||
|
||||
|
||||
def test_normalize_extra_args_prefers_extra_args() -> None:
|
||||
assert native_terminal.normalize_extra_args(
|
||||
extra_args=("--a",), legacy_args=None, legacy_param="pi_args"
|
||||
) == ("--a",)
|
||||
|
||||
|
||||
def test_normalize_extra_args_empty_when_neither_set() -> None:
|
||||
assert (
|
||||
native_terminal.normalize_extra_args(
|
||||
extra_args=None, legacy_args=None, legacy_param="pi_args"
|
||||
)
|
||||
== ()
|
||||
)
|
||||
|
||||
|
||||
def test_normalize_extra_args_legacy_alias_warns_and_is_used() -> None:
|
||||
with pytest.warns(DeprecationWarning, match="pi_args"):
|
||||
result = native_terminal.normalize_extra_args(
|
||||
extra_args=None, legacy_args=("--legacy",), legacy_param="pi_args"
|
||||
)
|
||||
assert result == ("--legacy",)
|
||||
|
||||
|
||||
def test_normalize_extra_args_extra_args_wins_over_legacy_with_warning() -> None:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
result = native_terminal.normalize_extra_args(
|
||||
extra_args=("--new",), legacy_args=("--old",), legacy_param="pi_args"
|
||||
)
|
||||
assert result == ("--new",)
|
||||
|
||||
@@ -121,7 +121,7 @@ def test_dispatch_by_runtime_claude_native_remote_routes_to_wrapper(
|
||||
# Trailing slash stripped — the wrapper expects a bare base URL.
|
||||
assert captured["server"] == "https://example.com"
|
||||
# No leaking claude args; the wrapper builds its own.
|
||||
assert captured["claude_args"] == ()
|
||||
assert captured["extra_args"] == ()
|
||||
|
||||
|
||||
def test_dispatch_by_runtime_codex_native_remote_routes_to_wrapper(
|
||||
@@ -158,7 +158,7 @@ def test_dispatch_by_runtime_codex_native_remote_routes_to_wrapper(
|
||||
|
||||
assert captured["session_id"] == "4e92b5a0c0ee6db3f874f9c4a3f855a5"
|
||||
assert captured["server"] == "https://example.com"
|
||||
assert captured["codex_args"] == ()
|
||||
assert captured["extra_args"] == ()
|
||||
|
||||
|
||||
def test_dispatch_by_runtime_codex_native_local_routes_to_wrapper(
|
||||
@@ -195,7 +195,7 @@ def test_dispatch_by_runtime_codex_native_local_routes_to_wrapper(
|
||||
|
||||
assert captured["session_id"] == "415c9954e2fe4b9276083a4d2c66f689"
|
||||
assert captured["server"] is None
|
||||
assert captured["codex_args"] == ()
|
||||
assert captured["extra_args"] == ()
|
||||
|
||||
|
||||
def test_dispatch_by_runtime_kiro_native_remote_routes_to_wrapper(
|
||||
@@ -221,7 +221,7 @@ def test_dispatch_by_runtime_kiro_native_remote_routes_to_wrapper(
|
||||
|
||||
assert captured["session_id"] == "823dbd1aab969b5a813fac59bb977a77"
|
||||
assert captured["server"] == "https://example.com"
|
||||
assert captured["kiro_args"] == ()
|
||||
assert captured["extra_args"] == ()
|
||||
|
||||
|
||||
def test_dispatch_by_runtime_antigravity_native_remote_routes_to_wrapper(
|
||||
@@ -262,7 +262,7 @@ def test_dispatch_by_runtime_antigravity_native_remote_routes_to_wrapper(
|
||||
|
||||
assert captured["session_id"] == "a8bcbee631c58ddb98fb5e3f54a1592a"
|
||||
assert captured["server"] == "https://example.com"
|
||||
assert captured["antigravity_args"] == ()
|
||||
assert captured["extra_args"] == ()
|
||||
|
||||
|
||||
def test_dispatch_by_runtime_antigravity_native_local_routes_to_wrapper(
|
||||
@@ -299,7 +299,7 @@ def test_dispatch_by_runtime_antigravity_native_local_routes_to_wrapper(
|
||||
|
||||
assert captured["session_id"] == "e85224ee39457def1d20bcce5b74ed8c"
|
||||
assert captured["server"] is None
|
||||
assert captured["antigravity_args"] == ()
|
||||
assert captured["extra_args"] == ()
|
||||
|
||||
|
||||
def test_dispatch_by_runtime_claude_native_local_still_routes_to_wrapper(
|
||||
@@ -336,7 +336,7 @@ def test_dispatch_by_runtime_claude_native_local_still_routes_to_wrapper(
|
||||
|
||||
assert captured["session_id"] == "64a784c3aa907d1774f44313546947c6"
|
||||
assert captured["server"] is None
|
||||
assert captured["claude_args"] == ()
|
||||
assert captured["extra_args"] == ()
|
||||
|
||||
|
||||
def test_dispatch_by_runtime_non_wrapper_local_raises_with_hint(
|
||||
|
||||
Reference in New Issue
Block a user