Skip to content

(feat) apply improvements requested by ralph - #214

Merged
cardosofede merged 57 commits into
mainfrom
feat/ralph_improvemnts
Aug 21, 2026
Merged

(feat) apply improvements requested by ralph#214
cardosofede merged 57 commits into
mainfrom
feat/ralph_improvemnts

Conversation

@cardosofede

@cardosofede cardosofede commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

What this does

Follow-up pass on the DEX trade panel work from #203, applying the review feedback from Ralph. Three themes: the default server is now shared between Telegram and the dashboard, the DEX pool workspace stops offering choices it can't honour, and the LP form warns about Meteora's bin cap before Gateway rejects the open.

Default server, shared with Telegram

The Settings star was decorative: clicking it wrote chat_defaults but nothing in the UI ever reflected it, and the dashboard always auto-selected the first online server regardless.

  • ServerInfo gains is_default, populated in both /servers and /settings/servers from cm.get_chat_default_server(user.id) — the same entry Telegram reads, so a default set on either surface is honoured by the other.
  • ServerSelector seeds a fresh browser from that default (falling back to the first online server) and marks it with a star in the dropdown.
  • ServersSettings fills the star, shows a Default badge, disables the button on the row that is already default, spins while the mutation is in flight, and now also points this browser at the server it just made default — without that second half the click had no visible effect at all.

DEX pool workspace

  • Pool address moved next to the pair name. The address identifies the pool in a way the ticker cannot, so PoolStats was split: PoolAddress (truncated address + copy + open-on-DEX) sits in the header beside the pair, and the network moved into the stat row.
  • More DEX deep links. poolUrl now covers PumpSwap (via pump.fun/coin/<base>), Uniswap and PancakeSwap, with the GeckoTerminal chain-id → DEX-slug mappings those apps route on. GeckoTerminal stays the fallback.
  • Interval buttons trimmed to 1m/5m/15m. 1h/4h/1d candles read as duplicates of the 1h/1d lookback buttons next to them, and a 3-day window of 15m candles already fits GeckoTerminal's 1000-candle cap.
  • LP panel knows the pool is pinned. Opening this page is the pool choice, so LPConfigPanel takes lockedPoolAddress and just states the address instead of showing resolution status, an editable address field and a provider picker that would second-guess a decision already made.
  • Dropdowns escape their clipping container. The chain select and DEX filter in PoolSourceTabs render through the shared AnchoredMenu instead of each hand-rolling absolute positioning plus its own outside-click/Escape listeners.

Order panel

  • Spot shows BUY/SELL, not LONG/SHORT — a "short" without tokens to sell isn't something a swap can do.
  • SelectField gains disabled, used on the execution-strategy select when a DEX offers only market: a one-option dropdown reads as broken unless it says so.

Meteora bin-count warning

A DLMM position spans at most 69 bins; a wider range failed at open time with the only trace in the Gateway log.

  • dlmmBinSpan(lower, upper, binStep) computes the span from the geometric bin step, returning null for non-DLMM pools (only those report a bin step).
  • rangeWarnings takes the bin step and warns when the range exceeds DLMM_MAX_BINS. It's a warning, not a block — Gateway is the authority and its cap can move.
  • DexPool feeds it depth?.bin_step from Gateway's bins call; the create page passes nothing, since resolve-by-pair doesn't report one.

Empty-state fix

On /dex, a pasted address that resolved to a pool is the result — the empty token-search table underneath it stays hidden rather than saying "No pools found" directly below a pool row.

Tests

tests/test_web_servers_parallel_status.py covers the new flag: exactly one row carries is_default when a chat default is set, and nothing is flagged when there isn't one. Existing response-shape assertions updated.

# Conflicts:
#	frontend/src/pages/Dex.tsx
#	frontend/src/pages/DexPool.tsx
@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown

Greptile Summary

This PR improves DEX workspace controls and executor configuration while synchronizing each user's default server between the dashboard and Telegram.

  • Adds default-server metadata to server APIs and reflects it in server selectors and settings.
  • Refines pool navigation, statistics, address actions, interval controls, and anchored filtering menus.
  • Improves spot-order labels, locked-pool LP configuration, and Meteora DLMM range warnings.
  • Extends server endpoint tests to cover default-server flags.

Confidence Score: 5/5

The PR appears safe to merge with no concrete blocking or independently actionable non-blocking issue identified.

The changed default-server, DEX workspace, and executor paths preserve their relevant server, pool, and interval contracts under the reachable scenarios reviewed.

Important Files Changed

Filename Overview
condor/web/routes/servers.py Exposes the user's effective default server alongside concurrently resolved server status.
condor/web/routes/settings.py Adds effective default-server metadata to the settings server response.
frontend/src/components/layout/ServerSelector.tsx Seeds an empty browser selection from the online default and marks it in the menu.
frontend/src/components/settings/ServersSettings.tsx Displays default state and synchronizes a successful default change with the active browser server.
frontend/src/components/dex/PoolSourceTabs.tsx Moves chain and venue controls into reusable anchored menus.
frontend/src/components/executor/lp-config.ts Calculates Meteora DLMM bin span and warns when a configured range exceeds the position cap.
frontend/src/pages/DexPool.tsx Refines pool identity, chart intervals, and locked-pool executor configuration.
tests/test_web_servers_parallel_status.py Updates response assertions and verifies default-server flag selection.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  CM[Config manager chat default] --> API[Server list APIs]
  API --> SS[Server settings]
  API --> SEL[Server selector]
  SS -->|Set default| CM
  SS -->|Activate server| SEL
  DP[DEX pool workspace] --> OC[Spot order configuration]
  DP --> LPC[Locked LP configuration]
  DP --> CH[Pool chart and statistics]
Loading

Reviews (1): Last reviewed commit: "(feat) apply improvements requested by r..." | Re-trigger Greptile

…Address

TradeChart hand-rebuilt the candle channel key without the pool segment in
its live-tick listener and setData signature, so pool-pinned charts
subscribed to a channel that never notifies and froze after the REST
backfill. Extract a canonical candleChannelKey() in candle-store.ts and use
it in useCandleStore and both TradeChart effects, with poolAddress in the
dependency arrays. CLOB keys stay byte-identical (pool segment appended
only when truthy).
… good cache

fetch_current_price, fetch_tickers and fetch_ticker_pool swallowed every
exception and returned empty shapes, so a transient timeout/500 during an SDS
poll was cached as a fresh success: last good TICKER_POOL/PRICES clobbered,
consecutive_errors reset, health.record_success during an outage, and a null
frame broadcast to prices WS subscribers. Add strict=False params that re-raise
on failure (404 'endpoint absent' stays a real empty answer) and register them
strict in register_default_fetches, matching the existing strict-fetcher
pattern. Direct callers keep the lenient default. Closes CORR-188 (backend).
…193)

BotLedger._save drops the fixed-temp-name tmp+replace dance for
atomic_write_json; config.yml, learnings.md, AGENT.md, strategy.md and
journal templates switch from plain write_text to atomic_write_text.
Append-only and write-once files (snapshots, experiments) unchanged.
sessions.py hand-rolled parse_custom_agent_key + find_custom_provider +
CUSTOM_LLM_* env fallbacks, duplicating the shared resolver and losing its
user_id fallback. Add strict=True to resolve_custom_endpoint so the session
path keeps its actionable RuntimeError for a missing named endpoint, and
call the one resolver from sessions.py.
/agents/notify, /delegate, /consult and the strategy start routes forwarded a
body-supplied chat_id to outbound Telegram sends with no ownership check, so
any approved user could speak with the bot's identity into anyone's chat.
_check_chat_access now allows the caller's own private chat for free, verifies
group membership against Telegram through the same bot ladder that delivers
(_HttpBot learns getChatMember), fails closed with 403, and exempts admins
(mirroring SEC-081/SEC-152).
Web login moved to one-time tokens from /web long ago; the widget path
(verify_telegram_login, _AUTH_WINDOW_SECONDS, LoginRequest) was fully
unreferenced yet looked like a live HMAC login path during security
review. Also drops the now-unused hashlib/hmac/TELEGRAM_TOKEN imports.
- stopMutation now invalidates [executors, server] (prefix covers main/altPair
  variants) and [dex-lp-executors, server] so stopped executors leave the pane
  within one refetch cycle; keeps [executors-infinite, server] for the
  Executors page
- drop dead [positions, server] invalidation (no useQuery ever reads it);
  [consolidated-positions, server] is the real key
One shared components/dex/format.ts replaces the three divergent private
copies in PoolBrowser, PoolStats and lp-position. PoolStats' 24h-change and
APR cells now use pct() with the K% cap, matching the table, and a genuine
$0 renders as $0.00 instead of an em-dash (dash is only for missing data).
…config_path, _sum_snapshot_value, get_session_dir (READ-203)
…ads (SEC-197)

GET /settings/gateway/networks/{id} shipped the full network config to any
trader-level seat, and paid RPC providers embed the API key in the nodeURL
(Helius query string, Infura/Alchemy path, or userinfo). The route now masks
every secret-bearing URL part server-side for callers below the OWNER line —
keeping scheme://host visible so a trader still sees which endpoint they trade
against — with the same admin bypass as _require_owner, and reports a
redacted flag. The frontend renders the Networks & RPC editor read-only for
non-owners (OWNER_ONLY_HINT) instead of a form whose save is refused, so a
masked value can never round-trip into a save; owners and admins keep the
full edit flow, and POST stays owner-gated (SEC-166).
fetch_ohlcv and fetch_liquidity_bins no longer take user_data/chat_id/context:
a pool's candles and bins are global answers, so both now cache process-wide
in pool_data's own TTL caches, keyed on the upstream arguments. fetch_ohlcv
gains use_cache=False (the live poll's freshness bypass, previously spelled
user_data=None); fetch_liquidity_bins requires an already-resolved client, so
client resolution moves to the callers like every fetchers/ module. The
'_cache'-namespace stay-in-step contract with handlers/dex/_shared is gone,
and dex_candles drops its module-dict-as-user_data workaround. (ARCH-194)
… (ARCH-190)

condor/ core no longer imports from handlers/: the platform-neutral pieces
of handlers/agents/_shared.py moved down — MCP toolset builders to
condor/runtime/toolsets.py, danger classification + confirmation summary to
condor/runtime/danger.py, context builders to condor/runtime/context.py,
the model catalog to condor/llm/options.py, TelegramChannel to
condor/runtime/channels.py — and readiness/openrouter_models/custom_models
moved wholesale to condor/llm/. clean_config_for_save moved to
condor/controller_configs.py. handlers modules re-export (or sys.modules-
alias) the moved names so Telegram callers are unchanged; the lazy
function-local imports across condor/runtime, condor/agents and condor/web
are now normal top-level imports, and the trade-gate danger lists no longer
live in a hot-reloaded handlers module.
…or/agents/attribution

The ownership-window tiling that attributes each bot's realized
PnL/volume/trades/fees across the sessions that operated it lived inside
web/routes/agents.py, hand-synchronized with a parallel merge in
agents/performance.py. Both now consume one implementation:
condor.agents.attribution owns session ownership resolution, owner-window
tiling, the slice-and-merge fold (fees_known heuristic) and the
cum_fees_quote fallback, plus the current-owner rule for the live open
book. Routes keep only HTTP concerns and the TTL caches. A parity test
pins that the web rollup and fetch_agent_performance report identical
figures for a session that adopted a long-running bot. (ARCH-191)
…ngbot stream families into condor/web/streams
@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown

Too many files changed for review (194 files, 100 file limit).

Bypass the limit by tagging @greptile-apps to review.

@rapcmia

rapcmia commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Commit fec6958

Default server, shared with Telegram

image
  • From telegram ✅
    • Set default Server A
    • Load condor webUI and check, Server A is selected as default
    • Repeat using Server B, same results occurred
  • From Condor WebUI ❌
    • Set server A as the default in Telegram.
    • Set server B as the default in Condor WebUI.
    • Run /servers again in the same Telegram chat.

DEX pool workspace ✅

image image image
  • Compared to the main branch, the PR now displays the pool address below the market pair
  • The external-link action opened the correct pool.
  • The copied address matched the pool found on the Orca website.
  • In the LP tab, the selected pool address was shown without an editable address field or LP provider selector.
  • The chain and DEX filter dropdowns remained fully visible instead of being clipped by their container.
  • After the expected GeckoTerminal rate-limit delay, the market list loaded and the dropdown options remained easy to select; on main, the clipped dropdown made those options difficult to use.

Order panel ✅

image
  • Spot orders showed Buy and Sell instead of Long and Short.
  • The execution strategy was fixed to Market and shown as disabled when it was the only supported option.
  • Leverage was disabled and remained at its default value of 1.
  • The swap completed as intended with close type position_hold.

Meteora bin-count warning ✅

image
Range spans ~251 bins; Meteora opens at most 69 per position — narrow it or the open will fail 
  • In an LP-supported Meteora DLMM pool, the Auto range button was used to speed up range selection.
  • A range above 69 bins showed a warning with the calculated bin count and Meteora's 69-bin limit.
  • A range at or below 69 bins showed no bin-count warning.
  • The over-limit result remained a warning and did not block LP position creation.

Empty-state fix ✅

image
  • Searched for the valid pool address 5rCf1DM8LjKTw4YqhnoLcngyZYeNnQqztScTogYHAS6 on the DEX page.
  • The correct pair and Open pool link were shown.
  • No second "No pools found" message appeared below the resolved pool.
  • An invalid pool address showed "No pools found" and did not show a pair, which is the expected empty state.

Commit 2340c08

LP chart line labels

image image image
  • Tested Range, Buy, and Sell on an LP-supported pool; the chart showed Upper and Lower instead of Start and End.
  • The chart's separate LONG label is an existing-position overlay, not the LP range direction or the newly renamed range lines.
  • HAPI confirms the overlay is a real solana-mainnet-beta SOL/USDC held position: LONG 0.115990048 at breakeven 86.4017516622, aggregated from three executors; the chart rounds these to LONG 0.1160 and 86.402.
    • The POSITION_HOLD overlay currently matches only the connector + trading pair. Since Orca and Meteora can share solana-mainnet-beta + SOL/USDC, the same position appears on both pool pages even though it originated from only one pool.
    • From a UX perspective, this can be misleading because it makes the held position appear associated with multiple pools/providers.
    • Suggestion: Match the position using its original pool address/provider and only display it on the correct pool. If that information isn’t available from HAPI, it may be better not to show the overlay on pool-specific pages?

Gateway token recovery banner

image image
#### condor logs 
2026-08-20 17:34:10,346 - condor.fetchers.gateway_tokens - WARNING - token EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v on solana-mainnet-beta not saved: its symbol is held by another address; balances for it will read 0

#### gateway logs
2026-08-20 01:34:10 | warn |    Token USDC already exists in solana/mainnet-beta
  • When a pool token is missing from Gateway’s local token list, the warning correctly provides an Add option.
  • Attempting to add USDC was safely rejected because the ticker is already used by another token entry. However, the UI does not show which token is conflicting or provide a Replace/resolve option, so token registration could not be completed.
  • HAPI portfolio verification still worked and returned the expected balance entries for solana-mainnet-beta.

@rapcmia

rapcmia commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Commit 84a2531

Default server shared with Telegram

From Condor WebUI ❌
Set server A as the default in Telegram.
Set server B as the default in Condor WebUI.
Run /servers again in the same Telegram chat.

  • Fixed, either telegram or condor setup default server is updated and sync
  • Default server change does not apply to /portfolio on Telegram ❌
    • Changing the default server sync correctly between telegram and condor webui, and /servers show the new default
    • Running /portfolio immediately after the change still loads the previous server. Opening the /servers first refreshes it, then /portfolio uses the new server
    • Reported to dev directly on discord pending fix

Gateway token listing ✅

  • Checked Gateway's local mainnet-beta.json token list and opened a market whose token was not yet listed.
  • No Add prompt or warning was shown; the missing token was added to the local list automatically.
  • Opened a TRUMP pool to confirm the automatic path with another token, then verified its token entry was added.
  • Returned to Portfolio and refreshed it; balances loaded normally after the automatic token registration.

DEX pool position scope ✅

image
  • Opened a new position on a Meteora SOL/USDC pool.
  • The position appeared on the Meteora pool workspace.
  • Opened the Orca SOL/USDC pool and confirmed the Meteora position was not shown there.

…h and the four producers

Everything Condor said when a background task finished went to Telegram and
nowhere else, so a user with only the dashboard open learned nothing.

condor/notifications.py is the other half: a per-user JSON store under data/
(atomic, capped, newest first, serialised behind a write lock) plus a push bus
that surfaces register themselves on, the same shape runtime/wake.py already
uses. chat_ws registers a sink emitting a `notification` event on the socket
every authenticated page already holds; GET/POST /api/v1/notifications is the
durable path for a notice raised while no tab was open.

The four producers -- a finished delegation, a finished routine, /agents/notify
and the boot notice -- record alongside their existing Telegram push, with the
same text, so the surfaces cannot tell different stories about one task. A
routine with no conversation behind it (the scheduler, the dashboard) now
reaches its owner for the first time.

NotifyBot is the last rung of resolve_bot(): with no TELEGRAM_TOKEN at all an
outbound message becomes a bell item instead of being dropped by a sender that
cannot deliver. It sits below both Telegram rungs, so a configured install is
unchanged and nothing is ever pushed to Telegram twice.

FEAT-048
A bell next to the Bug and Settings buttons, with an unread badge and a
dropdown of the last 50 notices; an item carrying a link navigates there, and
opening the list marks it read.

Two sources, one react-query key: history from GET /notifications on mount,
live arrivals written into the same cache by the chat socket's `notification`
handler. So a notice that arrived while the tab was open and one that arrived
an hour ago are indistinguishable, and a socket that was down costs nothing but
latency.

FEAT-048
… and /auth/local-login

Telegram was both the runtime dependency and the identity provider: main()
could not build an Application without a token, and the dashboard's only way
to get a session was the one-time token /web mints. CONDOR_MODE=local now
boots the process with no bot at all and logs the local admin straight in.

The mode is explicit and is never inferred from whether a token happens to be
present — telegram mode with an empty TELEGRAM_TOKEN exits at boot naming
`make setup`, so an install that loses its token stops instead of quietly
becoming a login-less dashboard. Local mode binds 127.0.0.1 only, with
WEB_HOST as the one documented opt-out. Those two controls are independent
and both required; tests pin each of them.

Boot skips the PTB lifecycle rather than stubbing it: the Application is built
on a placeholder token but never initialized, so nothing polls and no handler
can dispatch, and the job queue is started directly (scheduled routines,
update checks and signals all keep working). Outbound messages resolve to
NotifyBot, so a routine's context.bot.send_message lands on the dashboard bell.

Auth adds one endpoint. get_current_user, the WS handshake, check_server_access
and the 200-odd guarded endpoints are untouched — /auth/local-login mints an
ordinary JWT and 404s outside local mode, so nothing downstream ever learns
which mode it is in.
Setup opens with the choice — Telegram (today's prompts, verbatim, plus an
explicit CONDOR_MODE=telegram) or Local, which asks nothing: it writes
CONDOR_MODE=local, ADMIN_USER_ID=1 and the localhost WEB_URL, and prints what
local mode costs. An install from before this existed has a token but no
CONDOR_MODE; the next run records telegram for it, so the mode is always
explicit on disk.

The Login page asks the server how this install authenticates before it
renders, so the "run /web in Telegram" card never flashes on a machine that
has no Telegram, and local mode lands straight on the dashboard.

README documents local mode as what it is: no login, loopback only, and
WEB_HOST=0.0.0.0 as a deliberate act with a warning attached.
@rapcmia

rapcmia commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Commit ac0544b

Test default server shared with Telegram ✅

Default server change does not apply to /portfolio on Telegram ❌

  • This is fixed, changed the default server from A to B in the Condor WebUI.
  • Ran /portfolio directly in Telegram without opening /servers first.
  • Telegram returned the assets from server B, matching the new WebUI default.

Test make install ✅

image
  • If local, returns info about configured local mode
  • If telegram, prompts setup config for telegram path

Test CONDOR_MODE={local, telegram}

  • On telegram ✅
    image

    • With a Telegram bot token configured, Condor started normally in its usual Telegram mode.
    • Sending /web to the bot opened the normal dashboard login flow.
    • Leaving CONDOR_MODE unset kept this same Telegram behavior, so existing installations continue to work.
    • When Telegram mode had no bot token, Condor stopped at startup instead of opening the dashboard without protection.
  • On local ✅
    image
    image

    • Setting CONDOR_MODE=local started Condor without a Telegram bot token.
    • The dashboard opened at http://localhost:8088 and signed in automatically.
    • Telegram commands did not respond, which is expected because the bot is off in Local mode.
    • The dashboard stayed on the same machine by default at 127.0.0.1:8088.
    • An agent was selected from the Agents page, answered a query, and started a session normally.
    • Local mode uses admin user 1 by default. If you use another ADMIN_USER_ID, add a matching CONDOR_LOCAL_USER_ID so the dashboard can sign in.
  • What if i initially had telegram and decided to run local? ❌
    image

    • I changed the CONDOR_MODE to local and make run ok
    • Launch the condor website but got an error that local user mode 1 is not configured..
    • I ran make setup with existing .env but it did not update the value on ADMIN_USER_ID which was using my telegram user_id initially ❌
      image
      • User had to set ADMIN_USER_ID=1 manually on .env
      • Or delete .env and use make run

… boot

Ralph's report on #214: an install configured for Telegram that flips
CONDOR_MODE=local boots fine, auto-logs-in, and 500s in the browser with
"user 1 is not configured in config.yml — re-run `make setup`" — and the
re-run then prints "already configured" and changes nothing.

Four causes, all of them "two things that must agree, and nothing binding
them":

* Local mode had its own identity knob. ADMIN_USER_ID decides who exists in
  config.yml; CONDOR_LOCAL_USER_ID decided who the dashboard logs in as, and
  they only coincided because `make setup` happened to write 1 to both. The
  knob is gone: local mode logs in as ADMIN_USER_ID, defaulting to 1 for an
  install that never had a Telegram id. A Telegram install that switches now
  logs in as *you*, keeping its servers, preferences and defaults. It also
  answers the question the wizard could not: 1 is not an identity, it is the
  admin id you do not have yet.

* A non-numeric ADMIN_USER_ID was swallowed, leaving an install with no admin
  at all — no admin panel, no approvals, no boot notification, no local login
  — each surfacing later as unrelated breakage. It is now a boot error naming
  the value.

* Local mode's user is checked at boot (check_local_user), where the .env is,
  instead of by the login endpoint with the browser already open.

* `make setup` could not repair or switch a configured install: it printed
  "already configured" and skipped the block, so the advice every one of those
  errors gives led nowhere. It now shows the current mode, offers the other
  one, and stops clobbering a usable ADMIN_USER_ID with 1. It also reads the
  mode with a parser instead of `source`, which executes .env — one value with
  shell metacharacters (an API key with parentheses will do) is a syntax error
  that silently drops every variable below it, including a hand-added
  CONDOR_MODE=local, which the legacy-install migration then rewrote back to
  telegram.

`make run` now prints the pane on a startup failure instead of "run make
run-fg to see the error", so these land where they are read.
@cardosofede

Copy link
Copy Markdown
Contributor Author

Thanks @rapcmia — the telegram→local report was exactly right, and it turned out to have four causes, not one. All fixed in 1bd487b.

1. Local mode had two identities for one user

ADMIN_USER_ID decides who exists in config.yml; CONDOR_LOCAL_USER_ID decided who the dashboard logs in as. They only ever agreed because make setup happened to write 1 to both — so any install that got its ADMIN_USER_ID from Telegram and flipped CONDOR_MODE=local broke, which is your case exactly.

CONDOR_LOCAL_USER_ID is removed. Local mode logs in as ADMIN_USER_ID. So your note in the test above:

Local mode uses admin user 1 by default. If you use another ADMIN_USER_ID, add a matching CONDOR_LOCAL_USER_ID so the dashboard can sign in.

is no longer needed — there is no second variable to keep in sync. Verified end-to-end against a config.yml carried over from a Telegram install:

CONDOR_MODE=local, ADMIN_USER_ID=<telegram id>   # unchanged from telegram mode
POST /auth/local-login -> 200 {'id': <telegram id>, 'role': 'admin'}
GET  /auth/me          -> 200 {'id': <telegram id>, 'role': 'admin'}
server access on 'local': True
default server: {<telegram id>: 'local'}

Your servers, permissions and defaults all come with you, because they were always keyed on that same id. And it answers the "why 1, can I use 2?" question: 1 is not an identity, it is the admin id you have when you never had a Telegram one.

2. make run now fails at startup, not in the browser — your suggestion

A local mode whose user does not resolve stops at boot, naming the .env line to fix, instead of booting, auto-logging-in and 500-ing. make run also prints the pane on a startup failure now, rather than only telling you to re-run with make run-fg.

An ADMIN_USER_ID that is not a positive integer is a boot error too. It used to be silently swallowed, which left the install with no admin at all — no admin panel, no approvals, no boot notification, no local login. That is why ADMIN_USER_ID=ralphsohandsome looked like it still worked: your browser had a valid session for the earlier numeric admin, who was still in config.yml. Your agent's read of that was correct — no session bug, get_current_user re-checks the role against config.yml on every request.

3. make setup can now repair and switch

It printed "already configured" and skipped the whole block, so it could not fix ADMIN_USER_ID and could not change the mode — which made "re-run make setup" advice that led nowhere. It now shows the current mode and offers the other one, and it stops overwriting a usable ADMIN_USER_ID with 1. TELEGRAM_TOKEN is left in place when you switch to local, so switching back is one line.

4. Why it said "Telegram already configured"

The wizard read .env with source, which executes the file. One value containing shell metacharacters (an OpenAI key with parentheses is enough) is a syntax error that aborts the read and silently drops every variable below that line — including a hand-added CONDOR_MODE=local. The legacy-install migration then saw "token, no mode" and wrote CONDOR_MODE=telegram back into your .env, reverting your edit. Reproduced:

TELEGRAM_TOKEN=123:abc, ADMIN_USER_ID=555, OPENAI_API_KEY=sk-a(b)c, CONDOR_MODE=local
→ MODE=[] TOKEN=[123:abc] ADMIN=[555]

The mode is now read with a parser instead. Worth checking whether your .env has such a line above CONDOR_MODE — if so, that is the one that bit you.

Worth re-testing

  • Telegram install → set CONDOR_MODE=local by hand → make run. Should log in as your Telegram id with your servers intact, no .env edit needed.
  • make setup on a configured install → it should offer to switch modes, both directions.
  • ADMIN_USER_ID=notanumbermake run should refuse to start and print why.
  • CONDOR_MODE=local with a config.yml that has no matching user → boot error naming ADMIN_USER_ID, not a browser 500.

On decoupling the runtime

Local mode is already Telegram-free at runtime: nothing polls, no command menu is registered, and notifications go to the dashboard bell. What is still shared is PTB's job queue and callback context, which routines and scheduling ride on. Extracting those is a contained piece of work, but it changes no behaviour anyone can see — everything reported here was configuration UX. Leaving it for a separate PR rather than growing this one.

… position attribution

These cover work already on this branch but were never staged: the config.yml
-first default server (and the pinned-server case the inversion must not break),
the GeckoTerminal refusal counters and the 503-vs-404 the pool lookup answers
when it was throttled, and the executor_ids a held position carries so a pool
page can tell its own hold from another pool's.
…oard

Two halves of the same hole. `install` was only ever emitted from the Telegram
consent callback, so an install whose admin never tapped the prompt was never
counted at all — and a local-mode install, which has no bot to be prompted
through, could not be counted even in principle. Its consent stayed `unknown`
forever, pinned to the ping floor with no way to say otherwise.

Counting now happens in `telemetry.init()`, at the ping floor, without an
answer, guarded by `consent.mark_install_reported()` so it stays exactly once
per install whichever surface gets there first. That makes the claim the module
docstring already made — "every install is counted, from the first boot, with
no answer required" — true rather than aspirational.

Asking now happens in the dashboard too: a one-question banner in the shell for
an install that has never answered, and a permanent Settings → Privacy card
that every seat can read and only the admin can change. Both render the same
`DISCLOSURE` the Telegram message is built from, served by
`GET /settings/telemetry`, because a privacy claim written down twice is one
that will eventually disagree with itself. The Telegram copy is byte-identical
to before.

The envelope also carries `config.mode` now (`telegram` | `local`), which is
how many installs run without Telegram — the question local mode exists to
answer, and previously invisible.
fengtality added a commit that referenced this pull request Aug 21, 2026
Every agents/ change this branch carried on top of ralph's branch is lifted
out and re-applied on docs/lp-rebalancer-controller-guidance, which is based
on this one. agents/ here is now byte-identical to feat/ralph_improvemnts, so
this PR's diff no longer claims the backpack_mm and brigado deletions — those
are cardosofede's in 1cbc5dc and land with PR #214.

Four of the seven commits involved touched agents/ and other paths at once, so
the split is by path rather than by commit; the original commits stay in this
branch's history and net to zero here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cardosofede
cardosofede merged commit 11198d6 into main Aug 21, 2026
4 checks passed
fengtality added a commit that referenced this pull request Aug 21, 2026
Retargets this PR onto main now that feat/ralph_improvemnts is merged there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Under Review

Development

Successfully merging this pull request may close these issues.

2 participants