Release: tool permission system + webui refont + TON tx confirmations#22
Merged
Conversation
Replace the optional requiredMode flag with a required ToolMode field on ToolEntry, so the compiler rejects any tool that does not declare which Telegram mode(s) it runs in. The registry filters on the three-way mode in passesFilters/execute/setMode. Honest gap handling: getClient() and the bot bridge's getMessages() now throw a clear error in bot mode instead of a cryptic TypeError or a silently empty result. Move bot_inline_send out of the misnamed tools/bot/ directory into telegram/messaging/ and drop the now-empty folder.
getRawClient() returned the TelegramUserClient wrapper typed as unknown. pin.ts (unpin) and deals/executor.ts (gift transfer) cast it and called .invoke()/.getInputEntity() — methods the wrapper does not expose — which would throw at runtime. The as-any casts masked the mismatch. - drop getRawClient() and getPeer() from the ITelegramBridge interface - reach the GramJS client through the typed getClient() + isUserBridge guard - pin.ts and deals/executor.ts now use the getClient() helper (real client) - verify-payment / verification-poller / task-resolver use interface methods (getOwnUserId, sendMessage) instead of the loosely-typed raw client
…dup() capability The handler's offset-store dedup is needed when the bridge can redeliver the same message (GramJS user mode). Bot mode dedupes via update_id and does not need it. Express this as a bridge capability instead of checking getMode() at four sites in handlers.ts. The streaming gate also drops a redundant mode check — bridge.streamResponse alone is enough since it exists only on bridges that can stream. Declare streamDraft / clearDraft / finalizeDraft / resetDraft as optional methods on ITelegramBridge so future user-mode streaming (Phase 3) will not need new interface surface.
- npm update in-range: hono, vitest, @vitest/coverage-v8, tsx, jscpd,
@typescript-eslint/{eslint-plugin,parser} 8.60, @inquirer/prompts 8.5
- @ston-fi/api 0.31 -> 0.32
- @hono/node-server 1.19 -> 2.0.4 (drop Node18/Vercel, API serve inchangee)
- typescript 5.9 -> 6.0.3
- tsconfig: retrait baseUrl/paths (alias @/ inutilise, 0 usage repo) ;
ignoreDeprecations 6.0 sur les 2 tsconfig (tsup force baseUrl au build dts)
Verifie: tsc ✓, eslint ✓, build (sdk+backend+web) ✓, knip ✓,
dupcheck 4.9% ✓, madge ✓, 1588 tests ✓, npm audit --omit=dev 0 vuln,
overrides axios/fast-xml-parser/xml-builder intacts.
knip 5 -> 6.14.2 (dev-only): $schema -> knip@6, drop now-redundant ignoreDependencies (pino-pretty, @teleton-agent/sdk) per knip6 hints. Installing knip 6 surfaced a latent peer conflict introduced by f615dec: madge@8 (latest) declares peerOptional typescript@^5.4.4, incompatible with TS 6 — every future npm install would then need --legacy-peer-deps. TS 6 also forced ignoreDeprecations:6.0 (tsup injects a deprecated baseUrl at the dts build). TS 6 brings no functional gain here (tsconfig already strict). => revert typescript to ^5.7.0 (5.9.3), drop ignoreDeprecations. The baseUrl/paths cleanup from f615dec is kept (alias @/ unused, 0 usage). Verifie: tsc 5.9 OK, eslint OK, build (sdk+backend+web) OK, knip6 clean, dupcheck 4.9%, madge no cycle.
- prix dual $IN/$OUT (verifies registre pi-ai 0.73.1) sur anthropic/ openai/google/xai ; ex Opus $5/$25, Sonnet $3/$15, Gemini 2.5 Pro $1.25/$10 - label Opus 4.7 'Most capable' -> 'Most capable available' (4.8 hors pi-ai) - ajout gemini-3.1-flash-lite-preview ($0.25/$1.50, 1M) + grok-4-1-fast-non-reasoning ($0.20/$0.50, 2M) - consomme via getModelsForProvider() -> remonte auto dans WebUI + onboard ; schema.model = z.string() libre, IDs valides Verifie: tsc, eslint, prettier, build backend.
…es dont burn iterations
- pin.ts: remove silent/both_sides (never passed to bridge.pinMessage)
- send-dice.ts: remove reply_to (bridge.sendDice ignores it, SDK drops as _replyToId)
- gift bot-tools: wrap return in data:{} (runtime serializes result.data only —
total/gifts were invisible to the LLM) + category data-bearing (sibling parity)
- runtime loop: LLM retries (overflow/rate-limit/server) decrement iteration so
transient errors no longer consume the productive max_agentic_iterations budget
(still bounded by RATE_LIMIT/SERVER_ERROR_MAX_RETRIES)
Verifie: tsc, eslint, build backend, 1588 tests.
…/Conversations - Tools.tsx: anonymous <> with keys on children -> <Fragment key> on the mapped root (fixes React key warning); drop now-redundant inner tr keys - SearchInput: optional wrapperStyle prop (backward-compatible) - Memory/Conversations: replace raw flex filter inputs with shared SearchInput (adds clear button + Escape-to-clear, consolidates duplicated markup) Verifie: tsc --noEmit -p web/tsconfig.json (mes 4 fichiers OK), build:web. Note: 8 erreurs de type WebUI PRE-EXISTANTES non liees (AgentControl, Config, Dashboard, Setup, setup/*) — blind-spot #1, a traiter separement.
- parseRetryAfterMs() in runtime-utils (caps 60s, mirrors flood-retry.ts) - rate-limit retry: honor server Retry-After as a floor, else exponential backoff; add positive jitter (0-500ms) to de-sync concurrent retries - server-error retry: add jitter Verifie: tsc, eslint, 1588 tests.
… a11y, single SSE - App: Suspense fallback null -> loading div (no white flash); ErrorBoundary keyed off useLocation via resetKey so a crashed page clears on navigation - ErrorBoundary: resetKey prop + componentDidUpdate (no remount, no Layout flash) - Tasks: conditional poll (5s) only while a task is pending/in_progress - a11y: aria-labels on icon buttons (Tasks cancel/delete, Workspace rename/delete) + SearchInput input/clear-button aria-labels, type=button - SearchInput: optional wrapperStyle (already used by Memory/Conversations) - agentStatusStore: single shared EventSource (ref-counted) — useAgentStatus now consumes it via useSyncExternalStore (was 2 connections: badge + control) Verifie: tsc --noEmit -p web/tsconfig.json (8 erreurs PRE-EXISTANTES inchangees, 0 dans mes fichiers), build:web, build:backend.
…ge, drop dead getForProvider - move summarizeToolParams + enrichRAGQuery (pure) from runtime.ts to runtime-utils.ts (shrinks the 1.3k-line runtime; no cycle, dupcheck 4.89%) - import isBotBridge statically (bridge-guards is type-only -> no runtime cycle); drop the 2 repeated await import() calls - accumulate token usage before the error branch so retried error responses still count toward cost metrics - clamp max_agentic_iterations to >=1 (schema untouched) - remove dead getForProvider() (0 prod consumers; knip misses class methods) + its 3 tests Defere (risque > gain sur le chemin critique, prio 4-5): classifyLlmError (hook errorCode et branches retry ont des conditions server differentes), getModelResponse (intrique avec l'etat streaming). Verifie: tsc, eslint, circular OK, knip, dupcheck 4.89%, 1585 tests.
…allet tx) - formatDateTime (date + hour:minute, fr-FR) in lib/utils - Tasks detail panel: created/scheduled/started/completed -> formatDateTime (compact table cells keep date-only formatDate) - Wallet transactions -> formatDateTime Verifie: tsc --noEmit -p web/tsconfig.json (7 erreurs pre-existantes, 0 dans mes fichiers), build:web.
setActiveToolset() was never called at bootstrap — the toolset profiles were entirely dead (0 consumers of the setter/getters; config.agent.toolset never read). User chose removal over wiring. - registry: drop activeToolset field, TOOLSET_PROFILES, setActiveToolset, getActiveToolset, getAvailableToolsets, and the profile filter in passesFilters (per-tool tags kept — still surfaced as tool metadata) - schema: drop agent.toolset field (z.object strips unknown keys, so existing configs with toolset: are silently ignored — no break) - onboard: drop toolset:"full" from both generated config templates Verifie: tsc, eslint, knip, circular, dupcheck 4.9%, 1585 tests.
- Memory page: the search box now runs a debounced (300ms) semantic search via the existing GET /memory/search (api.searchKnowledge was dead code). Empty box browses sources as before; a query shows ranked SearchResult chunks (source + score) in the existing card/pre style. User chose wiring over deletion. - drop unused .skeleton / @Keyframes shimmer CSS (no consumers in web/src) Verifie: tsc --noEmit -p web/tsconfig.json (Memory clean; 7 pre-existing errors unchanged), build:web. Note: api.tasksGet/tasksCleanDone remain unused (not memory-search; left per user decision scope).
The WebUI is built with vite/esbuild (no type emit) and not typechecked in CI, so these latent errors had accumulated. tsc --noEmit -p web/tsconfig.json now passes clean, making it a usable local guard (blind-spot #1). - AgentControl: drop unused 'config' (badge computes its own) - setup/ConfigStep: drop unused Select import - setup/WelcomeStep: drop orphaned handleAccept + initDone (risk checkbox and initWorkspace moved to Setup.tsx — verified Setup.tsx:55-60 handles both) - Setup: drop unused STEPS import - Dashboard: bottomRef prop RefObject<HTMLDivElement|null> -> RefObject<HTMLDivElement> (matches useRef<HTMLDivElement>(null) under @types/react 18.3 + the div ref) - Config: drop dead '=== true' branch on getLocal() (returns string; TS2367) — behavior identical, '=== "true"' was the only effective check Verifie: tsc --noEmit -p web/tsconfig.json (0 erreur), build:web.
Hook errorCode and retry dispatch shared the same intent but used divergent string-matching: a 529/overloaded was reported errorCode=UNKNOWN to the response:error hook while being retried as a server error internally. Extract classifyLlmError() + isRateLimitError/isServerError predicates into runtime-utils (single source of truth, overflow->rate->server precedence). Hook and dispatch now consume one classification. Closes audit finding #1.
Collapse the claude-code and codex retry-on-401 blocks (verbatim except provider/refresh) into RETRY_401_PROVIDERS + isUnauthorizedError predicate. Closes audit finding #2.
Both paths duplicated the <think>-strip + persist + text-extract + context-append sequence (and the thinkRe literal). Hoist THINK_RE to module scope and extract finalizeResponse(response, context, options). Closes audit finding #3.
The agentic loop branched on provider===cocoon to wrap tool results as a user message. Move that into buildToolResultMessage() in the cocoon tool-adapter so the loop emits one message type without knowing the provider quirk. Closes audit finding #7.
Replace the inline 'user'|'bot' literal on the registry mode field, constructor and setMode with a named RuntimeMode = Exclude<ToolMode,'both'> alias. Closes audit finding #15.
The three insertion sites (register, registerPluginTools, replacePluginTools) each set the parallel tool/scope/mode/tags/module Maps by hand and had drifted (register set toolTags, plugin paths omitted them). Extract private insertTool() as the single insertion point; callers keep their collision policy and cache invalidation. Closes audit finding #9.
PluginModule.tools() already allows a per-tool mode, but registerPluginTools/ replacePluginTools dropped it and hardcoded 'both'. Thread the declared mode through (default 'both' when omitted) so mode restrictions apply to plugin tools. Closes audit finding #16.
loadConfigFromDB, registerPluginTools and replacePluginTools each repeated the seed-missing-then-reload-once loop. Extract private seedConfigs(names) as the single seeding path. Closes audit finding #10.
escapeFts5Query (3 sites) and bm25ToScore (2 sites) were byte-identical clones. Centralise both in memory/search/fts-utils.ts so the FTS5 escape list — a syntax-injection surface — has a single definition. Closes audit finding #13.
USERNAME_REGEX + the canonical error message + @-strip were triplicated across set/check/create-channel. Add cleanUsername + validateChannelUsername (with an allowEmpty option for the remove-username case) to sdk/telegram-utils. The intentionally-different profile/contacts validators are left untouched. Closes audit finding #18.
The getEntity + className!=='Channel' guard + 'as Api.Channel' cast was verbatim in 4 channel tools. Add resolveChannel(bridge, channelId) to sdk/telegram-utils (throws a readable Error caught by each executor's existing catch). Closes audit finding #19.
Add mapTelegramError + a canonical code->message table to sdk/telegram-utils and adopt it in the set/check-channel-username error tails, unifying the 3 divergent phrasings of CHANNELS_ADMIN_PUBLIC_TOO_MUCH. Success-mapped codes (USERNAME_NOT_MODIFIED, USERNAME_PURCHASE_AVAILABLE) stay explicit before the call. The ~30 heterogeneous remaining catch sites are deliberately NOT mechanically swept (each carries tool-specific success mappings) — the helper is the canonical path for them going forward. Closes audit finding #17.
Move toUnits/fromUnits to a dependency-free src/ton/units.ts (re-exported from dedust/asset-cache for compat) and replace the manual string-conversion copies in stonfi/quote, stonfi/swap and ton/jetton-send with toUnits() — financial decimal math now has one definition, importable without coupling to DeDust. Closes audit finding #24.
The pTON proxy address was hardcoded in 4 places under two names. Add STONFI_PTON_ADDRESS in the neutral src/ton/dex-constants.ts; dedust/constants, stonfi/quote, stonfi/swap and sdk/ton-dex now source it. Closes audit finding #22.
…step module, stale exports)
Balance hero with TON-diamond watermark, full address + copy/explorer icons, segmented In/Out filter, iOS list transactions with direction- colored icons. Token unit renamed TON→GRAM (Durov rebrand; network stays TON).
Blocklist card with header toggle + chip-field keywords, dimmed when off. Context triggers as an iOS list: tap a row to expand into an inline edit form (keyword/context, save/delete), plus a New-trigger add row.
Controls row (summary + refresh + add), cleaned add-server card with field labels and env-var rows. Servers as an iOS list: plug icon colored by connection, type/scope/disabled badges, tool count; expand for status, env-key/tool chips, and remove.
PillTabs status filter w/ counts, SearchBar + Clean menu + refresh. Tasks as an iOS list: status dot (pulsing when running), status-colored subtitle, priority dots; expand for a kv grid + CodeBlock payload/result/ error and cancel/delete actions. Confirms via useConfirm (drop bespoke modal).
Status hero (pulsing orb + model/Running + provider/uptime/platform), metrics as a grid of tappable stat-cards (navigate to their page) incl. GRAM balance, restyled live-logs panel. Keep settings panels. Drop dead status-bar/metric CSS.
Replace the stat-card grid (too tall) with a single thin pill strip of inline value+label items (macOS menu-bar style), keeping click-through navigation. Frees vertical space so hero + settings + logs fit one screen.
- Per-tool access level (all/allowlist/admin/off) replaces the 6-value scope + enabled flag; DB migration 1.19.0 backfills and keeps legacy columns for safety. Global DM/Group response policy shown as pills. - Dashboard: Provider/Model moved into the status hero; Access Policy + tabbed Allow Lists cards. - New Logs page (level filter, search, copy, pause-on-scroll); dropped the inline dashboard logs panel. - Single-line inputs use pill radius to match buttons; ArrayInput add button resized.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings
devup tomain— 151 commits, no divergence (clean fast-forward).Merge method: use Rebase and merge (linear history, keeps the individual commits) — do not squash (it would crush 151 commits into one).
Highlights
Tool permission system (core)
all/allowlist/admin/off) replaces the old 6-value scope +enabledflag, enforced in the runtime registry (gates every tool call).dm-only/group-onlycollapse toall— channel gating now lives only in the global DM/Group policy.scope_level+ backfills from the legacy(enabled, scope); legacy columns kept for downgrade safety. Runs automatically at startup.WebUI refont (Telegram-iOS design language)
TON
Fixes