Skip to content

Plan gateway /model interactive picker; align tool-progress preview tests - #184

Merged
XelHaku merged 1105 commits into
mainfrom
development
May 11, 2026
Merged

Plan gateway /model interactive picker; align tool-progress preview tests#184
XelHaku merged 1105 commits into
mainfrom
development

Conversation

@XelHaku

@XelHaku XelHaku commented May 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • docs(progress): Author Phase 2.B.5 progress row "Gateway /model interactive provider/model picker" planning the Hermes _handle_model_command no-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.
  • fix(gateway): Align internal/gateway/render_test.go tool-progress preview tests with the head-edge truncation behavior shipped in commit b60a0e32a fix(tooltrace): show URL/query head, not tail, in tool-call previews. That commit missed the gateway-level tests, leaving development red.

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-git chain). 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 on development HEAD (b37d072)
  • go run ./cmd/progress validate — validated 8 phases
  • git diff --check — clean
  • go test ./internal/gateway -run TestFormatToolProgressPlain -count=1 — all green
  • go test ./internal/progress ./webpages/docs -count=1 — all green
  • CI green on this PR before merge

Parity context for the picker row

User-reported regression: Gormes /model in 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 with hermes-agent@d4b26df8 source 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), exact write_scope, acceptance test names, and blocked_by deps 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.go already 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 /model slash command.

🤖 Generated with Claude Code

XelHaku and others added 29 commits May 10, 2026 03:55
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
@XelHaku
XelHaku merged commit 141bc32 into main May 11, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant