Merge origin/main; keep PM as plugin dependency owner

Reconcile plugin declarations and validation through PM's atomic generation publication; preserve external runtimes, target markers, and conflict refusal. Keep one source-update completion owner and port upstream lifecycle changes to the PM desktop/runtime paths.
This commit is contained in:
ethernet
2026-09-17 13:52:05 -04:00
2440 changed files with 120380 additions and 16107 deletions

View File

@@ -221,7 +221,7 @@ class _ModelCatalog:
f"Provider: {provider_name}" + (" • current" if is_current else ""),
)
def add_named_catalogs(self, catalogs: list, normalized_provider: str) -> None:
def add_named_catalogs(self, catalogs: list, current_choice_provider: str) -> None:
"""Named user-defined endpoints (providers: / custom_providers:) are invisible
to canonical enumeration — append them like the TUI /model picker. An empty
catalog marks that slug authoritative-empty."""
@@ -230,7 +230,7 @@ class _ModelCatalog:
self.empty_authoritative.add(str(named_slug).strip().lower())
continue
for named_model, named_desc in named_catalog:
is_current = named_slug == normalized_provider and named_model == self.current_model
is_current = named_slug.lower() == current_choice_provider and named_model == self.current_model
parts = [f"Provider: {named_label}", str(named_desc or "").strip(), "current" if is_current else ""]
self.add(named_slug, named_model, named_model, " • ".join(part for part in parts if part))
@@ -251,13 +251,38 @@ def build_model_state(model: str, provider: str, base_url: str) -> SessionModelS
probe_custom_providers=False, probe_current_custom_provider=False, max_models=ACP_MAX_MODELS_PER_PROVIDER,
)
named_catalogs = _named_custom_provider_catalogs()
named_slugs = {str(slug).strip().lower() for slug, _label, _models in named_catalogs}
current_choice_provider = str(provider or "").strip().lower()
current_base = base_url.strip().rstrip("/").lower()
# ``build_models_payload`` represents configured ``providers:`` entries by their raw
# config key. ACP ids must instead use the durable ``custom:<key>`` identity so the
# picker value round-trips through ``parse_model_input``. Only user-defined rows are
# replaced by the named catalogs: a ``providers:`` key that shadows a canonical name
# (``providers.openrouter:`` → proxy) must leave the canonical row — and a session that
# runs on the canonical endpoint — alone, or picking "current" re-routes to the proxy.
all_rows = payload.get("providers") or []
canonical_current = any(
str(r.get("slug") or "").strip().lower() == current_choice_provider and not r.get("is_user_defined")
for r in all_rows
)
inventory_rows: list = []
for row in all_rows:
slug = str(row.get("slug") or "").strip().lower()
if not row.get("is_user_defined") or not {slug, f"custom:{slug}"} & named_slugs:
inventory_rows.append(row)
continue
row_base = str(row.get("api_url") or "").strip().rstrip("/").lower()
if slug.removeprefix("custom:") == current_choice_provider and (current_base == row_base or not canonical_current):
current_choice_provider = f"custom:{current_choice_provider}"
cat = _ModelCatalog(
normalize_provider=normalize_provider, current_model=model,
current_choice_provider=str(provider or "").strip().lower(),
current_base_url=base_url.strip().rstrip("/").lower(),
current_choice_provider=current_choice_provider,
current_base_url=current_base,
)
cat.add_inventory_rows(payload.get("providers") or [], provider_label)
cat.add_named_catalogs(_named_custom_provider_catalogs(), normalized_provider)
cat.add_inventory_rows(inventory_rows, provider_label)
cat.add_named_catalogs(named_catalogs, current_choice_provider)
available_models = cat.models
def empty_applies(provider_id: str) -> bool:

View File

@@ -397,6 +397,7 @@ class SessionManager:
"platform": "acp", "quiet_mode": True, "session_id": session_id, "session_db": self._get_db(),
"enabled_toolsets": _expand_acp_enabled_toolsets(["hermes-acp"], mcp_server_names=configured_mcp_servers),
"model": model or default_model,
"cwd": cwd,
}
try:
runtime = resolve_runtime_provider(requested=requested_provider or config_provider)
@@ -424,9 +425,6 @@ class SessionManager:
logger.debug("ACP: bounded MCP discovery wait failed", exc_info=True)
agent = AIAgent(**kwargs)
# Codex app-server sessions spawn lazily on the first turn; stamp the ACP
# workspace so the Codex runtime starts from the editor cwd, not ours.
agent.session_cwd = cwd
# ACP stdio: stdout is protocol-only JSON-RPC; agent chatter goes to stderr.
agent._print_fn = _acp_stderr_print
return agent