Skip to content

feat(opencode): add /reload slash command with auto-resume - #43458

Open
AndyS77 wants to merge 1 commit into
anomalyco:devfrom
AndyS77:reload-command
Open

feat(opencode): add /reload slash command with auto-resume#43458
AndyS77 wants to merge 1 commit into
anomalyco:devfrom
AndyS77:reload-command

Conversation

@AndyS77

Copy link
Copy Markdown

Issue for this PR

Closes#43454

Type of change

  • New feature

What does this PR do?

Adds a /reload\ slash command that hot-reloads all configuration, plugins, MCP servers, skills, and agents without restarting the TUI. After reload, the interrupted session is automatically resumed via \SessionPrompt.loop().

Follow-up to the shelved PR #9871, with key improvements:

  • Auto-resume: the interrupted session is automatically resumed after reload completes — no synthetic continuation prompt, just \SessionPrompt.loop()\ which reloads history from DB and continues with fresh config
  • Error handling: \store.reload()\ failures are logged via \Effect.catchCause\ instead of silently swallowed
  • No dead code: removed \�oid ConfigReload.Event\ and \�s any\ casts from the original PR
  • Tests cover auto-resume: mocked \SessionPrompt.Service\ tracks \loop()\ calls; tests verify resume happens exactly once, after bootstrap completes, and not when no sessionID was provided

How it works

  1. /reload\ slash command or
    eload_config\ tool calls \POST /config/reload\
  2. \ConfigReload.request()\ records reload state per workspace (not globally)
  3. If sessions are active, reload stays pending until they become idle
  4. When idle, \InstanceStore.reload()\ disposes the old instance and boots a fresh one
  5. TUI receives \config.reload.executing, shows reload overlay, performs bootstrap refresh
  6. TUI acknowledges with \POST /config/bootstrap-complete?cycle=...\
  7. Server emits \config.reload.done\ with the \sessionID\
  8. \ConfigReload\ calls \SessionPrompt.loop(sessionID)\ to auto-resume the session

The
eload_config\ tool stops the current turn via \stopAfterToolResult\ (sets \ctx.blocked = true). The turn stops cleanly, the runner goes idle, reload executes, and the session is resumed.

What gets reloaded

  • Global + project config (\opencode.json\ / \opencode.jsonc)
  • Agents, skills, commands (from .opencode/\ and config)
  • MCP server connections (disconnect + reconnect)
  • Plugin registry (full re-import)
  • Provider configuration and auth
  • LSP connections, file watchers, permissions, formatters

How did you verify your code works?

  • TypeScript: \ sc --noEmit\ passes clean
  • 12 tests in \ est/config/reload.test.ts\ covering:
    • Per-workspace reload isolation
    • Bootstrap-cycle scoping
    • Coalescing repeated reloads
    • Multi-session queuing
    • Auto-resume after immediate reload (exactly once)
    • Auto-resume after deferred reload (only after bootstrap-complete)
    • No auto-resume when no sessionID provided
    • Status polling for non-TUI clients

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Co-Authored-By: zai-glm-52 noreply@ai.local
Agent: @Feature-dev
Scope: #43454

Add a /reload slash command that hot-reloads all configuration, plugins,
MCP servers, skills, and agents without restarting the TUI. After reload,
the interrupted session is automatically resumed via SessionPrompt.loop().
Closesanomalyco#6719
ConfigReload service:
- Per-workspace state tracking with pending/executing/done lifecycle
- Queues reload if sessions are active, executes when idle
- Coalesces repeated reload requests during an active reload
- Emits config.reload.pending/executing/done events for TUI sync
- Bootstrap cycle tracking for TUI reconnection after instance reload
- Auto-resume: after reload completes, calls SessionPrompt.loop() to
continue the interrupted session with fresh config
Server endpoints:
- POST /config/reload — triggers reload, returns immediate/queued status
- GET /config/reload/status — poll-based status for non-TUI clients
- POST /config/bootstrap-complete — TUI acknowledges reload completion
reload_config tool:
- Agent-callable tool that triggers reload and stops the current turn
- stopAfterToolResult stops the processor loop cleanly
- Session is auto-resumed after reload completes
Session integration:
- SessionRunState wires ConfigReload.start/finish into onBusy/onIdle
- Runner.onBusy called on Idle→Running transition
- Processor handles stopAfterToolResult via ctx.blocked
Workspace routing:
- URL-decode x-opencode-directory header for encoded paths
Co-Authored-By: opencode <opencode@anomalyco>
Agent: @Feature-dev
Model: glm/zai-glm-52
Scope: anomalyco#6719
@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference; please use your judgment.

The ConfigReload state machine itself is well designed — per-workspace states, cycle-numbered bootstrap acknowledgements that reject stale acks, coalescing, and genuinely good lifecycle test coverage. However:

  • Repo root: 8 accidental non-source files must be removed before merge — COMMIT_EDITMSG, HEAD, ORIG_HEAD, commondir, gitdir, hook-execution.log, index (binary), index.lock are git/worktree internals committed into the repository, almost certainly from running git add -A inside a linked worktree. Besides polluting the tree, gitdir leaks a local machine path (C:/Repos/opencode/.git/worktrees/reload-command/.git) and hook-execution.log embeds a timestamped internal log. Please drop all eight from this branch.

  • packages/opencode/src/config/reload.ts (executePending / continueOrDone) — Nothing in this PR ever releases the "tui-bootstrap" blocker automatically: there are no TUI changes, so POST /config/bootstrap-complete has no caller, and headless/API clients won't ack either. Why it matters: after the first immediate reload, reloadInFlight stays true forever, every later request queues as pending, and auto-resume never fires — the feature deadlocks on first use outside tests (tests call releaseBlocker directly). Suggestion: include the client-side ack wiring here, or add a timeout/fallback that releases the blocker when no client claims it.

  • packages/opencode/src/config/reload.ts (executePending) — store.reload failure is only logged; reloadInFlight and the blocker stay set with no error event, leaving the same permanent-stuck state after any reload error. Suggestion: on failure clear the blocker, emit an error/done event, and reset reloadInFlight so the system recovers.

  • Title/scope mismatch — the PR title says "add /reload slash command" but no slash command is registered anywhere in the diff; only the agent-callable reload_config tool exists. Either add the slash command or retitle to match what ships.

  • packages/opencode/src/server/routes/instance/httpapi/handlers/config.ts:613 — markInstanceForReload is imported from ../lifecycle but lifecycle.ts has no changes in this diff. If it doesn't already exist on the base branch this won't compile; please confirm it's present.

  • packages/opencode/src/effect/runner.ts:132 — Firing onBusy on every Idle→Running transition is a global behavior change for all runner consumers (status.set busy plus reload.start now run there). Worth double-checking existing subscribers can't see duplicate/extra transitions, since previously onBusy was not invoked from this path.

  • Nit: the per-workspace State entries in ConfigReload's states Map are never pruned; long-lived servers accumulate one entry per distinct directory/workspace forever. A small LRU or cleanup on instance dispose would bound it.

Sign up for freeto 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.

[FEATURE]: /reload slash command with auto-resume

2 participants

@AndyS77@Enough1122