Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion __tests__/news-regressions.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ test('background agent reorder preserves concrete identities and rejects inherit
test('fetch.sh resolves and retries a configured fallback agent', () => {
const sh = readRepoFile('fetch.sh')
assert.ok(sh.includes('from app.background_agents import resolve_background_agents'))
assert.ok(sh.includes('resolve_background_agents(data_dir, app)'))
assert.ok(sh.includes('resolve_background_agents(data_dir, _override(app))'))
assert.ok(sh.includes('fallback_provider'))
assert.ok(sh.includes('fallback_effort'))
assert.ok(sh.includes('IFS=$\'\\t\' read -r PROVIDER MODEL EFFORT FALLBACK_PROVIDER FALLBACK_MODEL FALLBACK_EFFORT'))
Expand Down
56 changes: 51 additions & 5 deletions fetch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -672,20 +672,66 @@ def _effort(choice):
e = e.strip() if isinstance(e, str) and e.strip() else ""
return e if e in EFFORTS.get(c.get("provider"), set()) else ""

FALLBACK_KEYS = ("fallback_provider", "fallback_model", "fallback_effort")

def _override(app):
"""Translate News's OWN settings into the platform's uniform override shape.

News has two settings shapes for historical reasons, so News owns both:
the current Settings screen writes an explicit primary_agent_mode /
secondary_agent_mode, while installs predating it wrote a bare
{provider, model} where naming a provider IS the override. A bare default
provider with no model/effort is NOT an override — treating it as one would
replace the system primary's model with the SDK default.

An omitted key means "no preference". Normalization is deliberately NOT
done here: the platform cleans the choice for every background agent
identically, which is the copy that drifted when News last owned it.
"""
override = {}
mode = app.get("primary_agent_mode")
if mode == "app":
claims_primary = True
elif mode == "system":
claims_primary = False
else:
provider, model, effort = app.get("provider"), app.get("model"), app.get("effort")
claims_primary = bool(provider or model or effort) and not (
provider == "claude" and not model and not effort
)
if claims_primary:
override["primary"] = {
"provider": app.get("provider"),
"model": app.get("model"),
"effort": app.get("effort"),
}
secondary = app.get("secondary_agent_mode")
if secondary == "app" or (
secondary != "system" and any(app.get(k) for k in FALLBACK_KEYS)
):
override["fallback"] = {
"provider": app.get("fallback_provider"),
"model": app.get("fallback_model"),
"effort": app.get("fallback_effort"),
}
return override

try:
app_path, data_dir, agent_code = sys.argv[1:4]
app = load(app_path) if agent_code == "200" else {}
# Route through the platform's ONE canonical resolver (providers-list +
# per-app override + secondary_agent_mode) instead of the copy that used to
# live here, which had drifted from the runners'. The `except` below keeps
# News running on the Claude default if the platform hasn't been reconciled
# to a version carrying app.background_agents yet (deploy-order safety net).
# system ordering + normalization) instead of the copy that used to live
# here, which had drifted from the runners'. News contributes only its own
# declared pick; the platform no longer knows News's settings format. The
# `except` below keeps News running on the Claude default if the platform
# hasn't been reconciled to a version carrying app.background_agents yet
# (deploy-order safety net).
for _root in (Path("/data/platform/backend"), Path("/app")):
if (_root / "app" / "__init__.py").is_file():
sys.path.insert(0, str(_root))
break
from app.background_agents import resolve_background_agents
agents = resolve_background_agents(data_dir, app)
agents = resolve_background_agents(data_dir, _override(app))
p = agents.get("primary") or {"provider": "claude"}
f = agents.get("fallback")
values = [p.get("provider") or "claude", _model(p), _effort(p), "", "", ""]
Expand Down
Loading