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 protoagent.plugin.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
id: spacetraders
name: SpaceTraders
version: 2.9.3
version: 2.9.4
description: >-
Play the live SpaceTraders v2 API (https://spacetraders.io) — a persistent,
shared galactic economy. A FULL-BUNDLE plugin (ADR 0027): 30 fleet/market/mining/
Expand Down
87 changes: 87 additions & 0 deletions test_register_faction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""st_register honors the CONFIGURED faction (issue #57).

The live 2026-07-12 wipe failure: `st_register` hardcoded `faction="COSMIC"`, so when the
agent auto-re-registered after the reset (the 4113 prompt says "re-register", no faction
passed) it minted a COSMIC agent — clobbering the `_recover` path's configured-DOMINION
registration (which correctly uses `faction()`). The token saved last won, and the faction
was doomed either way. Fix: st_register defaults to the configured faction, so EVERY register
path honors the operator's choice and the race is harmless.
"""

from __future__ import annotations

import asyncio
import importlib
import sys
from pathlib import Path

import pytest

testkit = pytest.importorskip("graph.plugins.testkit")
ROOT = Path(__file__).resolve().parent


@pytest.fixture(scope="module")
def pkgmods():
import graph
host_root = str(Path(graph.__file__).resolve().parents[1])
saved = sys.modules.pop("events", None)
sys.path.insert(0, host_root)
try:
pkg = testkit.load_plugin(ROOT, "spacetraders")
tools = importlib.import_module(f"{pkg.__name__}.tools")
client = importlib.import_module(f"{pkg.__name__}.client")
finally:
sys.path.remove(host_root)
sys.modules.pop("events", None)
if saved is not None:
sys.modules["events"] = saved
return tools, client


@pytest.fixture(autouse=True)
def _reset_cfg(pkgmods, monkeypatch):
# set_config_token only *updates* fields it's given, so reset the globals directly to
# keep tests independent, and drop any env faction override.
_, client = pkgmods
monkeypatch.delenv("SPACETRADERS_FACTION", raising=False)
for attr in ("_CONFIG_TOKEN", "_CONFIG_ACCOUNT_TOKEN", "_CONFIG_CALL_SIGN", "_CONFIG_FACTION"):
setattr(client, attr, None)
yield


def _capture_register(client, monkeypatch):
used = {}

async def fake_register(symbol, faction_, acct):
used.update(symbol=symbol, faction=faction_, acct=acct)
return {"agent": {"symbol": symbol, "credits": 175_000, "headquarters": "X1-BB67-A1"},
"ships": []}

monkeypatch.setattr(client, "register_agent", fake_register)
return used


def test_defaults_to_configured_faction_not_cosmic(pkgmods, monkeypatch):
tools, client = pkgmods
client.set_config_token(None, "acct-tok", call_sign="PROTOTRADER", faction="DOMINION")
used = _capture_register(client, monkeypatch)
out = asyncio.run(tools.st_register.ainvoke({"symbol": "PROTOTRADER"})) # NO faction passed
assert used["faction"] == "DOMINION" # honored config, NOT the old hardcoded COSMIC
assert "DOMINION" in out


def test_explicit_faction_overrides_config(pkgmods, monkeypatch):
tools, client = pkgmods
client.set_config_token(None, "acct-tok", call_sign="PROTOTRADER", faction="DOMINION")
used = _capture_register(client, monkeypatch)
asyncio.run(tools.st_register.ainvoke({"symbol": "PROTO", "faction": "ASTRO"}))
assert used["faction"] == "ASTRO" # an explicit arg still wins


def test_falls_back_to_cosmic_when_unconfigured(pkgmods, monkeypatch):
tools, client = pkgmods
client.set_config_token(None, "acct-tok", call_sign="PROTO") # no faction configured
used = _capture_register(client, monkeypatch)
asyncio.run(tools.st_register.ainvoke({"symbol": "PROTO"}))
assert used["faction"] == "COSMIC" # ultimate default preserved for new users
17 changes: 12 additions & 5 deletions tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,20 +117,27 @@ def _ship_line(s: dict) -> str:


@tool
async def st_register(symbol: str, faction: str = "COSMIC", account_token: str = "") -> str:
async def st_register(symbol: str, faction: str = "", account_token: str = "") -> str:
"""Register a NEW SpaceTraders agent and save its token for all later calls.

Needs an account token (from your spacetraders.io account) — pass it as
account_token, or set SPACETRADERS_ACCOUNT_TOKEN. Faction options include
COSMIC (default), GALACTIC, QUANTUM, DOMINION, ASTRO.
account_token, or set SPACETRADERS_ACCOUNT_TOKEN. Faction options: COSMIC,
GALACTIC, QUANTUM, DOMINION, ASTRO.

Args:
symbol: the agent call sign, 3–14 chars, alphanumeric/_/- (e.g. "PROTOTRADER").
faction: starting faction enum.
faction: starting faction enum. If omitted, uses the CONFIGURED Starting Faction
(Settings → SpaceTraders), then COSMIC. Omit on reset-recovery so a re-register
honors the operator's chosen faction — passing a literal default here is what
silently re-registered a COSMIC agent over a configured DOMINION one at a wipe (#57).
account_token: your account bearer token (required to register).
"""
from .client import account_token as _config_account_token, register_agent
from .client import account_token as _config_account_token, faction as _config_faction, register_agent
tok = (account_token or "").strip() or _config_account_token()
# Default to the CONFIGURED faction, never a hardcoded COSMIC: reset-recovery calls this
# WITHOUT a faction (the 4113 prompt says "re-register"), and a literal default there
# ignores the operator's Settings faction — the #57 race that clobbered a DOMINION switch.
faction = (faction or "").strip() or _config_faction()
if not tok:
return ("Error: registration needs an account token. Create an account at "
"spacetraders.io and paste its account token in the console "
Expand Down
Loading