Plan gateway /model interactive picker; align tool-progress preview tests - #184
Merged
Conversation
Mirror the refreshed Hermes dispatcher executable fallback in Gormes by resolving default Kanban worker spawns through PATH-first gormes lookup and current-executable fallback. Update progress evidence and mirror the new upstream plugin LLM docs page required by the refreshed Hermes submodule.
… permanent delete Live regression 2026-05-10: a sandbox uninstall test (GORMES_INSTALL_HOME=/tmp/...) accidentally targeted the operator's REAL ~/.gormes and PERMANENTLY DELETED .env (provider keys), memory.db (Goncho conversation history), config.toml, ~/.gormes/bin/gormes, and ~/.local/bin/gormes. Two independent root causes lined up to make this maximally destructive: 1. install.sh's run_uninstall() did not export GORMES_HOME before invoking the gormes binary's `uninstall` subcommand. The gormes process inherited the operator's default $HOME-derived ~/.gormes path instead of the sandbox path the operator actually pointed at. 2. cmd/gormes/uninstall.go's executeUninstall used os.RemoveAll directly — permanent unrecoverable deletion. There was no fallback to a freedesktop trash, so even when the wrong scope was chosen, recovery was impossible. Fix #1 — install.sh run_uninstall pins scope: GORMES_HOME="$(managed_home_dir)" export GORMES_HOME managed_home_dir() returns $GORMES_INSTALL_HOME when set, else $HOME/.gormes. So sandbox uninstalls now stay in the sandbox; default uninstalls still target ~/.gormes as before. Fix #2 — cmd/gormes/uninstall.go pickArtifactMover(): - default: `gio trash <path>` when gio is on PATH (move to freedesktop trash, recoverable from any file manager's trash UI) - GORMES_UNINSTALL_FORCE_PURGE=1: opt-in permanent delete for CI cleanup, container teardown, secure wipe - fallback: permanent delete with an explicit label that names the missing dependency ("gio not available; install glib2-tools for recoverable trash") so operators understand why their uninstall isn't trash-aware Removal mode is now logged to stdout and surfaced in --json output as removal_mode: "...", so fleet automation can verify which mode fired. Recovery from this incident only worked because an earlier May-2 uninstall on the same operator's host had used a trash-aware path, leaving ~/.local/share/Trash/files/.gormes/ as a partial backup. The new default would have made that explicit instead of accidental. Tests: - TestPickArtifactMover_PrefersGioTrashWhenAvailable - TestPickArtifactMover_ForcePurgeOptsIntoPermanentDelete - TestPickArtifactMover_ForcePurgeAcceptsTrueAlias - TestPickArtifactMover_DefaultOnHostWithoutGio Existing uninstall tests (12) still pass — they all run with t.TempDir() paths that gio trash handles identically to os.RemoveAll for cleanup purposes.
Live regression 2026-05-10: an operator wired up OpenRouter using the documented base URL `https://openrouter.ai/api/v1` and got the cryptic "Not Found: provider returned HTML error body" error with no indication that openAICompatibleURL had joined the basePath /api/v1 with the chat-completions path /v1/chat/completions to produce `https://openrouter.ai/api/v1/v1/chat/completions` (double /v1, 404). Operators copy-pasting the documented base URL is the natural intuition across every OpenAI-compatible provider whose docs include /v1 in the advertised base URL — OpenAI itself, OpenRouter, Together, Groq chat, DeepInfra, etc. The defect class is silent: the request shape is structurally valid, the error body is HTML (the upstream service returned a generic 404 page), and the resulting Go error ("Not Found: provider returned HTML error body") doesn't surface the URL it tried. Fix: when basePath ends with "/v1" AND endpointPath starts with "/v1/", strip basePath's trailing "/v1" so both shapes (endpoint = "https://openrouter.ai/api" and endpoint = "https://openrouter.ai/api/v1") resolve to the same URL. Other prefix shapes are explicitly preserved: - baseURL ending in "/v1/proxy" (middle /v1, not trailing) — no collapse - endpointPath like "/responses" or "/messages" (non-/v1) — no collapse - baseURL without scheme (e.g. test stubs) — falls through to raw concat Eight-case TestOpenAICompatibleURL_CollapsesDoubleV1Prefix pins the contract: openrouter+/v1, openrouter+/v1/, openrouter without /v1, OpenAI with /v1 (same defect class), Anthropic /v1/messages (must still work), Azure /responses (basePath /v1 must be preserved), middle /v1 (no collapse), no-scheme fallthrough. Verified end-to-end: rebuilt + hot-swapped the gateway binary, set hermes.endpoint = "https://openrouter.ai/api/v1" (the previously-broken shape), ran `gormes --oneshot "Reply with exactly: openrouter url fix lives."` and got the exact reply through OpenRouter→DeepSeek v4 flash.
Cuts to ship today's three follow-on fixes to install.sh users: 1. CRITICAL: install.sh --uninstall scope leak that wiped operators' real ~/.gormes when run from a sandbox (.env, memory.db, config.toml, binaries — unrecoverable on hosts without gio trash in the path the operator's environment used). 2. install.sh uninstall now defaults to recoverable trash via gio instead of permanent delete, with GORMES_UNINSTALL_FORCE_PURGE=1 for callers who legitimately need scrubbed disk. 3. OpenRouter (and other OpenAI-compatible) base URL with the documented /v1 suffix no longer 404s — the URL builder now collapses double-/v1 prefixes so endpoint = "https://openrouter.ai/api/v1" and endpoint = "https://openrouter.ai/api" both work. 4. OpenAI STT response_format=text + JSON-decode mismatch fixed (companion to the v0.2.4 Groq fix; same defect class, copy-paste bug in the OpenAI provider). Riding along: codexu's autonomous-loop work since v0.2.4 — pure-Go WASI Whisper transcriber wired into Telegram STT (no CGO, no Python, local fully-offline transcription path), V4A patch tool resilience hardening (block-anchor matching, fuzzy strategies, unicode normalization, rollback on failure), kanban orchestrator board tools + tail event follower, provider stream-drop upstream diagnostics, Hermes i18n locale parity expansion, and CI release-binary metadata smoke gate. See CHANGELOG.md for the full annotated list.
`web_search`, `web_extract`, `web_crawl`, and `browser_navigate` were on the right-edge-truncate list, so Telegram showed previews like `web_search: "...et docs websocket WSS Quickstart CLOB"` and `web_extract: "...et.com/market-data/websocket/overview"` — hiding the search query's first words and the URL's domain behind a leading "...". Move them off the right-edge list. File-path tools (`read_file`, `write_file`, `patch`) keep tail-preview because the filename usually matters more than the directory chain. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The codex prompt assumes MCP-style skill calls (`gormes-skill-manager`, `gormes-git`) and a large-frontier model can hold the full parity-then- plan-then-build pipeline in head. opencode discovers SKILL.md files via its `skill` tool, has no MCP endpoint for these skills, and runs against a local ollama model (qwen3-coder:30b MoE on this rig) with much less cognitive headroom — the existing prompt sent the model into infinite `skill_mcp` retry loops. Add `opencode_prompt()`: tells the agent to use the `skill` tool (not `skill_mcp`), bias toward one tiny safe step per cycle, exit cleanly when confused, and never touch unfamiliar untracked files. Wire the backend dispatch to pick the right prompt for the active backend. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
qwen3-coder:30b on opencode follows the structured-exit pattern but doesn't know the bash tool requires both `command` and `description` args, so it failed its first call with a SchemaError and bailed cleanly via "I am stopping because: ...". Add explicit reminders for the bash/read/edit/write tool shapes opencode enforces. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author Phase 2.B.5 row "Gateway /model interactive provider/model picker" covering the Hermes _handle_model_command no-arg path: 2-step provider→model inline-keyboard picker on Telegram (mp:/mm:/mg:/mb:/mx: callback family), "Select a provider:" header parity, per-session model/provider override seam, and static-text fallback for non-inline channels. Distinct from the complete Cobra-CLI row at Phase 5.O — this slice writes a per-session override, not hermes.model/hermes.provider in TOML. Source-refs pinned at hermes-agent@d4b26df8 (current submodule sha) covering gateway/run.py:_handle_model_command, platforms/telegram.py:send_model_picker + _build_model_keyboard + _handle_model_picker_callback, and hermes_cli/model_switch.py:list_picker_providers. Relax internal/progress/progress_test.go:Phase2ExecutionQueue to allow 2.B.5 derived status of complete OR in_progress now that a newly discovered gateway-handler gap reopens the subphase. Per-row complete/validated asserts below remain locked. Regenerated docs/site progress mirrors via `go run ./cmd/progress write`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tion Commit b60a0e3 (fix(tooltrace): show URL/query head, not tail, in tool-call previews) moved web_search/web_extract/web_crawl/browser_navigate off the right-edge-truncate list in internal/tooltrace, but missed the gateway-level render tests still asserting the old tail-preview shape: - TestFormatToolProgressPlain_TruncatedWebPreviewsKeepRightEdge - TestFormatToolProgressPlain_MineruGatewayTranscriptShape Update those tests to match the shipped head-preview behavior (URL/query head visible, trailing "..." after truncation) and rename the first test to KeepLeftEdge so the name matches reality. Production code is unchanged. Unblocks PR-to-main CI gate; the failing tests passed on main only because main does not yet carry b60a0e3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts: # webpages/docs/content/building-gormes/builder-loop/blocked-slices.md
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.
Summary
_handle_model_commandno-arg picker in Gormes — 2-step provider→model inline-keyboard picker on Telegram (mp:/mm:/mg:/mb:/mx:callback family), per-session model/provider override seam, static-text fallback for non-inline channels. Distinct from the complete Cobra-CLI picker row at Phase 5.O.internal/gateway/render_test.gotool-progress preview tests with the head-edge truncation behavior shipped in commitb60a0e32a fix(tooltrace): show URL/query head, not tail, in tool-call previews. That commit missed the gateway-level tests, leavingdevelopmentred.Scope
This PR contains 1104 commits since the v0.2.5 release (PR #183). The freshest two are the items above (authored 2026-05-10 in a
gormes-hermes-parity+gormes-planner+gormes-gitchain). The rest is the development integration window from prior sessions — release-train shape consistent with #181 / #182 / #183.Test plan
go test ./... -count=1— all green ondevelopmentHEAD (b37d072)go run ./cmd/progress validate— validated 8 phasesgit diff --check— cleango test ./internal/gateway -run TestFormatToolProgressPlain -count=1— all greengo test ./internal/progress ./webpages/docs -count=1— all greenParity context for the picker row
User-reported regression: Gormes
/modelin a gateway channel emits only static🤖 Model: <m> / 📡 Provider: <p>text; Hermes opens an interactive⚙ Model Configuration / Select a provider:inline-keyboard picker. The new row authors the Go-side port plan withhermes-agent@d4b26df8source refs (gateway/run.py:_handle_model_command,platforms/telegram.py:send_model_picker/_build_model_keyboard/_handle_model_picker_callback,hermes_cli/model_switch.py:list_picker_providers), exactwrite_scope,acceptancetest names, andblocked_bydeps that are all complete (Telegram inline approval callback router, Hermes provider registry manifest, CLI nested parser inventory refresh).Dead-code observation noted in the row:
internal/tui/model_picker.goalready implements a Hermes-compatible 2-step state machine with zero non-test callers. Out of scope for this PR; a future row will wire it through the TUI/modelslash command.🤖 Generated with Claude Code