feat(gateway): self-service password change - #2
Closed
abeldantas wants to merge 22 commits into
Closed
Conversation
…ADME (sandbox-quantum#303) * Show Switch working in a chat app, with room for one recording per app * Add the Slack recording * Replace the overview diagram * Add the Mattermost recording * Use the cropped overview diagram
) * Update CLAUDE.md module layout and doc references to match the tree The module layout listed directories that do not exist and missed some that do, and the reference section said the synced docs/official documentation does not exist. * Remove tasks from the standalone feature lists CHOO-1418 removed the task protocol from the skills, but the codex README and the configure skills still list it as working, which can confuse agents. * Rename the generated docs index to TOC.md to fix a case collision The repo tracked both INDEX.md and index.md. On macOS and Windows these are the same path, so one of them wins, the docs/README.md link opens the wrong file, and git status is never clean. The sync script now writes TOC.md. * Fix stale references in the codex docs, CONTRIBUTING and console/AGENTS.md Small stale bits: a comparison with a Claude connector mechanism that no longer exists, mcp_servers where the file says mcpServers, an outdated docs row and a dead anchor in CONTRIBUTING, and connector paths in console/AGENTS.md that resolve from the wrong directory. * Add Mattermost to the architecture section's platform lists The architecture section names four platforms but the README supports five. Added Mattermost to the transport list and the image alt text. The diagram PNG still shows four icons. * Add release and CI badges to the readme
…4) (sandbox-quantum#304) Dismissing the "Setting up Switch" checklist wrote the setting straight over IPC instead of going through the settings mutation. The panel renders from the cached `onboarding` setting and nothing broadcasts a settings change back to the renderer, so the write persisted but the cache kept saying `showChecklist: true` — the ✕ looked dead until the next launch. Route the dismissal through the same mutation the Settings toggle uses, so the cache is updated optimistically and invalidated. This also drops the `if (!onboarding) return` guard, which silently did nothing when the setting had not loaded; the mutation merges from the cache itself. The existing tests mount the presentational panel with a stub `onDismiss`, so they never touched the wiring. Add a test that mounts the hook and asserts the cached setting flips, which fails against the old call.
…10 (sandbox-quantum#305) switch-console 0.31.0 → 0.31.1 (patch): - fix(console): the setup checklist's ✕ now actually dismisses it (sandbox-quantum#304) Connector plugins patched so the sandbox-quantum#302 configure-skill correction re-downloads: switch-connector 0.9.8 → 0.9.9, switch-connector-codex 0.3.9 → 0.3.10. The standalone feature list no longer claims the task protocol works (removed in CHOO-1418); codex skill also fixes an mcp_servers → mcpServers reference. opencode untouched (0.1.5). No switch-core release: the doc-sync PR (sandbox-quantum#295) only touched a Teams README and a test under core/, no shipping code — bundle pin stays at 0.21.0. agent-runtime (0.3.2) and sidecar (1.9.4) unchanged. Contracts stay 1/1. artifacts.yaml + generated modules regenerated; artifacts-check passes. Authored all changelog entries (every [Unreleased] was empty). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er (sandbox-quantum#307) * fix(agent-runtime): exit when the host does, instead of running forever The runtime intended to shut down with its host — `transport.onclose` stopped the stream, the heartbeat and the lease renewal, then exited. It never fired. The MCP SDK's stdio transport binds only 'data' and 'error' on stdin, so `onclose` is reached only by an orderly `close()`, never by a host that was killed, crashed or force-quit. That exit path was dead code in exactly the case it existed for. Nothing else stopped the process either: the hook listener's TCP server held the event loop open, the 2s heartbeat and lease timers kept running against a Switch that was gone, and `uncaughtException` swallowed the resulting EPIPE once `serving` was true rather than letting it escalate. Measured on one developer machine: 486 processes, 3.5 GB resident, ~198% CPU, 242 stale loopback listeners, across five runtime versions, the oldest 18 days. The two longest-lived had grown to 470 MB and 367 MB — consistent with writes queueing against a stderr pipe whose reader is gone but which was never closed. Four independent triggers now, because each covers a case the others miss: stdin 'end'/'close' for a dropped pipe, the termination signals for an orderly kill, a stdout/stderr 'error' for a peer that is gone but not closed, and a ppid watchdog as the backstop — reparenting is the one signal that survives every way a host can vanish, and SESSION_PPID was already recorded for the session directory. Any one of them would have prevented all 486. The hook listener is also unref'd, so the stdin reader is what holds the loop open: alive now means a host is attached rather than a port is bound. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(agent-runtime): clear up runtimes that already outlived their host The shutdown fix stops new orphans. It does nothing about the ones already running: every published version up to 0.3.2 lacks it, and those processes sit there until the machine reboots — 486 of them, 3.5 GB, on the machine that prompted this. A new runtime now clears them out on its way up, which reaches a user through the connector pin without waiting for an app release. The signal is the process tree, not anything on disk. When the host dies its `npm exec` wrapper is reparented to init while the runtime carries on pointing at a wrapper that is alive but orphaned — so "is the parent dead?" is the wrong question, and would have missed both of the worst offenders (15765 was very much alive; only its parent was gone). Reaching init without passing through a live host is the right one, and it needs no port file, no session directory and no hook. The match is anchored on argv[0] rather than a substring of the command line. A substring test also matches any shell that merely mentions the package — installing it, grepping for it — and an orphaned one of those would have been killed. An early draft did exactly that; the cases are pinned in the tests. The reaper excludes its own chain outright rather than relying on the walk to spare it. The walk does spare it, since a freshly spawned runtime has a live host by construction, but a reaper that can reach itself is one bad predicate away from killing the session it serves. Stale session directories go too — pure litter from runtimes that were killed outright and never ran `unpublishPort`, 8071 of them here. Verified end to end against a real orphan built the way the bug builds one (host killed with its stdin held open by a third party, so the runtime never sees EOF): a fresh runtime reported `reaping 2 runtime process(es) whose host is gone` and `removing 8016 stale session director(ies)`, and left itself running. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
agent-runtime 0.3.2 → 0.3.3 (patch): - fix(sandbox-quantum#307): the runtime exits when its host does instead of running forever. Its onclose-based shutdown never fired for a killed/crashed/force-quit host, and the hook listener + heartbeat/lease timers kept the process alive, leaving stale processes and loopback listeners accumulating. Shutdown is now driven by four independent triggers (stdin end/close, termination signals, stdout/stderr write error, parent-pid watchdog). Phase 1 of a two-phase runtime release: this publishes 0.3.3 to npm via the switch-agent-runtime-v0.3.3 tag. The connector runtime pins (claude/codex .mcp.json, opencode opencode.json, SWITCH_AGENT_RUNTIME_PIN), the plugin versions, and the sidecar are DELIBERATELY left at their current versions — a pin must never name a version npm does not have yet. Phase 2 (re-pin + plugin/sidecar bumps) follows once 0.3.3 is live, on request. artifacts.yaml + generated modules regenerated; artifacts-check passes. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-quantum#310) Phase 2 of the agent-runtime 0.3.3 release (0.3.3 is live on npm). Moves every runtime pin from 0.3.2 → 0.3.3 and bumps each consumer so the host-exit fix (sandbox-quantum#307) reaches users: - Runtime pin → 0.3.3 in all four places: claude & codex .mcp.json, opencode opencode.json, and SWITCH_AGENT_RUNTIME_PIN in console distribution.ts (the embedded opencode config derives from that constant, so it follows). - Plugin versions bumped so installs re-download: switch-connector 0.9.9 → 0.9.10, switch-connector-codex 0.3.10 → 0.3.11, switch-connector-opencode 0.1.5 → 0.1.6. - sidecar 1.9.4 → 1.9.5 (runs the new runtime; major stays 1, wire unchanged). claude/codex reach users on next marketplace Update; opencode + sidecar ride the next Switch Console release. artifacts.yaml + generated modules regenerated; artifacts-check passes; pins agree with each other (runtime-pin/connector-assets tests green). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) * feat(console): reap orphaned agent runtimes at boot The runtime learned to exit when its host dies, and to clear up predecessors on startup. Both reach a machine only through the connector pin, which moves when a user updates a marketplace plugin — so a user who never revisits Settings → Agents keeps their backlog indefinitely. This path reaches them when they update the app instead. The two populations do not overlap as much as they look: someone who updates the app but not the connector is reached only from here, and someone running Codex standalone with no Switch Console is reached only by the runtime. Neither alone covers the fleet. The predicate is imported from the runtime package, not reimplemented. Two copies of "is this runtime abandoned?" is a failure mode this repo already knows — where they disagree, one of them kills a live session — so `reapOrphanedRuntimes` moved to the package's public surface and both callers share it. It now returns what it did rather than taking a log callback. There were two callers with two ideas of a log line: the runtime writes prose to stderr, the app wants an enumerated event, and threading either through the sweep was the wrong shape. Runs unawaited after the RPC router is up and before session relaunch, so an abandoned runtime is gone before its replacement starts, without the window waiting on a full process scan. Verified against real orphans rather than mocks, twice: reaped: 4, removedSessionDirs: 3 — four genuine orphans, all gone reaped: 0 — with a healthy session running, its runtime, its wrapper and its session directory all untouched The second matters more than the first. Killing a live session would be far worse than leaving an orphan, and it is the case no unit test can fully stand in for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(console): bring installed Switch connectors up to date once A connector's pinned runtime version only moves when a user goes looking for it in Settings → Agents. Someone who never does keeps whatever they first installed, across every release — which is how an install ends up several runtime versions behind without anything being broken enough to notice. This is a one-shot catch-up, deliberately not a standing auto-updater. It latches a generation marker on success and does nothing on every later launch; the Update button stays the normal path. Bumping CATCH_UP_GENERATION — a deliberate edit, not a release — is what makes it run again. What it will not do: - install a connector that is not installed. Which agents have one is the user's decision, and this is a version catch-up, not an installer. - latch when the marketplace could not be refreshed. `updateAvailable` is false both for "no update" and "could not look", and spending the single shot on an answer that never arrived is the one way a one-shot fails silently. - retry forever. Three attempts, then it stops, because "runs every launch" is precisely what this is not. Runs unawaited before session relaunch, so a session started on the old pin picks the new one up next launch rather than holding up this one. Verified locally against a real gap. First launch: connector_catch_up_updated claude 0.9.8 -> 0.9.10 connector_catch_up_updated opencode 0.1.5 -> 0.1.6 connector_catch_up_complete updated: 2 Second launch on the same install: no catch-up activity, no marketplace call, nothing logged — latched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
switch-console 0.31.1 → 0.31.2 (patch, per request — contains sandbox-quantum#309): - reap orphaned agent runtimes at boot (sandbox-quantum#309) - one-shot catch-up to bring already-installed connectors up to date once (sandbox-quantum#309) Also ships the console-side Phase 2 rollout already on main: the bundled sidecar (1.9.5) and OpenCode connector (0.1.6) now run agent-runtime 0.3.3. Bundle pin stays at core 0.21.0 (no core release). Runtime version drift from sandbox-quantum#309 (runtime source changed but package.json still 0.3.3, matching npm) is deliberately left for the next runtime release — no user-facing runtime behaviour changed. artifacts.yaml + generated modules regenerated; artifacts-check passes. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…CHOO-2455) The New agent dialog greys out "Add agent" whenever the form is not ready but doesn't tell people why. This fixes that.
The previous commit keyed the tooltip on !runHostReady, which is true both while the readiness probe is still in flight and when the host is known to be missing setup. A blocked host got "Waiting for ... to be ready", telling the user to wait for something that never resolves. Branch on hostReadiness.checking and hostReadiness.blocked instead, and point the blocked case at the HostReadinessNotice that names what is missing. Also runs the file through oxfmt, which the previous commit skipped and which is what failed CI.
…disabled-reason Tell the user why "Add agent" is disabled, on hover over the button (CHOO-2455)
…ion-bump-rule Stop hand-bumping versions per commit; leave it to the release agent
A dashboard user who knows their current password can now set a new one without involving an admin. This is the first slice of f17 (self-service password management); admin-initiated resets are out of scope here. Backend: PUT /auth/me/password — verifies the current password, hashes the replacement, and commits. Rejects OIDC-only accounts (no hash to verify against) with 403. Frontend: ChangePasswordDialog accessible from the NavRail account menu, following the existing CreateUserDialog pattern (MUI Dialog, inline error/success alerts, confirm-password guard).
There was a problem hiding this comment.
🟡 Changes recommended
The new password-change flow has confirmed correctness/UX gaps (OIDC-only handling + dialog state) and lacks direct tests for the new auth endpoint.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a self-service password-change feature to the Gateway (API + dashboard UI), while also bundling a larger set of Switch Console/runtime maintenance and documentation/indexing updates.
Changes:
- Adds
PUT /auth/me/passwordplus a Gateway dashboard “Change password” dialog that calls it. - Improves Slack typed-command handling by translating Slack mentions in
!commands. - Updates Switch Console + agent runtime (shutdown/reaping behavior, connector catch-up, version pins) and refreshes docs/indexing + release metadata.
File summaries
| File | Description |
|---|---|
| signatures/version1/cla.json | Adds CLA signature entries. |
| scripts/sync_docs.py | Writes docs index to TOC.md instead of INDEX.md. |
| README.md | Updates marketing copy, badges, images, and embedded demo recordings. |
| gateway/src/layout/NavRail.tsx | Adds “Change password” entry + dialog wiring in account menu. |
| gateway/src/layout/ChangePasswordDialog.tsx | New dialog UI for changing password. |
| gateway/src/data/api.ts | Adds changePassword() API call (PUT /auth/me/password). |
| docs/README.md | Updates docs pointer from official/INDEX.md to official/TOC.md. |
| docs/official/TOC.md | Adds generated docs table-of-contents file. |
| core/tests/switch_core/bridges/collaboration/test_slack_adapter.py | Adds test ensuring typed !invite-agent <@U…> mention translation. |
| core/switch_core/gateway/schemas.py | Adds ChangePasswordRequest schema. |
| core/switch_core/gateway/auth_routes.py | Adds PUT /auth/me/password endpoint. |
| core/switch_core/bridges/collaboration/slack/adapter.py | Translates mentions for typed ! commands (aligns with slash command behavior). |
| core/switch_core/artifacts.py | Bumps artifact version constants. |
| CONTRIBUTING.md | Updates README anchor + clarifies docs/ structure. |
| console/packages/switch-agent-runtime/src/reap.ts | Adds orphaned-runtime and stale-session-dir reaper implementation. |
| console/packages/switch-agent-runtime/src/reap.test.ts | Adds unit tests for reaper predicate + directory sweeping. |
| console/packages/switch-agent-runtime/src/index.ts | Exports reaper utilities from runtime package. |
| console/packages/switch-agent-runtime/src/bin.ts | Adds shutdown triggers + runtime self-reaping; adjusts session root constant. |
| console/packages/switch-agent-runtime/src/bin.shutdown.test.ts | Adds tests for shutdown triggers/watchdog behavior. |
| console/packages/switch-agent-runtime/src/artifacts.ts | Bumps artifact versions in runtime package. |
| console/packages/switch-agent-runtime/package.json | Bumps runtime version to 0.3.3. |
| console/packages/shared/src/artifacts.ts | Bumps shared artifact versions. |
| console/packages/plugins/src/distribution.ts | Updates runtime pin to @sandboxaq/switch-agent-runtime@0.3.3. |
| console/apps/switch-console-desktop/src/renderer/tests/browser/onboarding-checklist-dismiss.test.tsx | Adds renderer test ensuring checklist dismissal updates cached state + persists. |
| console/apps/switch-console-desktop/src/renderer/features/onboarding/use-onboarding-checklist.ts | Fixes dismissal to use settings mutation (not raw IPC) so UI updates immediately. |
| console/apps/switch-console-desktop/src/renderer/features/locations/components/add-agent-modal/add-agent-modal.tsx | Adds tooltip explaining why “Add agent” is disabled. |
| console/apps/switch-console-desktop/src/main/index.ts | Runs orphaned-runtime reap + connector catch-up at boot (best-effort). |
| console/apps/switch-console-desktop/src/main/core/switch-setup/catch-up-connectors.ts | Adds one-shot connector catch-up with latch + retry cap. |
| console/apps/switch-console-desktop/src/main/core/switch-setup/catch-up-connectors.test.ts | Adds tests for catch-up behavior and latching/retry semantics. |
| console/apps/switch-console-desktop/src/main/core/agent-runtime/reap-orphaned-runtimes.ts | Adds app-level wrapper around runtime reaper at boot. |
| console/apps/switch-console-desktop/src/main/core/agent-runtime/reap-orphaned-runtimes.test.ts | Adds tests for app-level reaper wrapper + logging behavior. |
| console/apps/switch-console-desktop/package.json | Bumps Switch Console desktop version to 0.31.2. |
| console/AGENTS.md | Fixes connector-path references to be relative to console/. |
| connectors/opencode-plugin/package.json | Bumps OpenCode connector version to 0.1.6. |
| connectors/opencode-plugin/opencode.json | Pins runtime to @sandboxaq/switch-agent-runtime@0.3.3. |
| connectors/codex-plugin/skills/configure/SKILL.md | Fixes MCP key name + updates feature list wording. |
| connectors/codex-plugin/README.md | Updates wording around env forwarding + feature list. |
| connectors/codex-plugin/.mcp.json | Pins runtime to @sandboxaq/switch-agent-runtime@0.3.3. |
| connectors/codex-plugin/.codex-plugin/plugin.json | Bumps Codex plugin version to 0.3.11. |
| connectors/claude-code-plugin/skills/configure/SKILL.md | Updates feature list wording. |
| connectors/claude-code-plugin/.mcp.json | Pins runtime to @sandboxaq/switch-agent-runtime@0.3.3. |
| connectors/claude-code-plugin/.claude-plugin/plugin.json | Bumps Claude Code plugin version to 0.9.10. |
| CLAUDE.md | Updates repository guidance/docs section and connector notes. |
| CHANGELOG.md | Adds release notes for console/runtime/plugin version bumps and fixes. |
| artifacts.yaml | Bumps artifact versions for console/runtime/sidecar/connectors. |
Review details
Suppressed comments (1)
core/switch_core/gateway/auth_routes.py:188
PUT /auth/me/passwordcurrently treats OIDC-only accounts (user.password_hash is None) as an incorrect current password and returns a misleading 403 detail. The PR description calls out a distinct 403 rejection for OIDC-only accounts, so this route should check forpassword_hash is Noneexplicitly and return a clear message before callingverify_password.
- Files reviewed: 44/47 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+180
to
+185
| @router.put("/auth/me/password") | ||
| async def change_password( | ||
| req: ChangePasswordRequest, | ||
| session: Annotated[AsyncSession, Depends(get_session)], | ||
| user: Annotated[User, Depends(get_current_user)], | ||
| ) -> dict[str, bool]: |
Comment on lines
+41
to
+59
| const handleSubmit = useCallback(async () => { | ||
| if (newPassword !== confirmPassword) { | ||
| setError("New passwords do not match"); | ||
| return; | ||
| } | ||
| setSubmitting(true); | ||
| setError(null); | ||
| try { | ||
| await changePassword(currentPassword, newPassword); | ||
| setSuccess(true); | ||
| setCurrentPassword(""); | ||
| setNewPassword(""); | ||
| setConfirmPassword(""); | ||
| } catch (err) { | ||
| setError(err instanceof Error ? err.message : "Failed to change password"); | ||
| } finally { | ||
| setSubmitting(false); | ||
| } | ||
| }, [currentPassword, newPassword, confirmPassword]); |
Comment on lines
+180
to
+184
| @router.put("/auth/me/password") | ||
| async def change_password( | ||
| req: ChangePasswordRequest, | ||
| session: Annotated[AsyncSession, Depends(get_session)], | ||
| user: Annotated[User, Depends(get_current_user)], |
The change-password endpoint accepted any string, including empty — a curl call could set an account's password to "" and leave it unrecoverable from the login form. Add min_length=8 on ChangePasswordRequest.new_password so Pydantic rejects degenerate values before the handler runs. Also adds the missing test coverage: happy path, wrong current password → 403, short password → 422, empty password → 422.
Owner
Author
|
Superseded by sandbox-quantum#323 (relocated to the main repo). |
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
PUT /auth/me/passwordendpoint — verifies the caller's current password and sets a new bcrypt hash. Rejects OIDC-only accounts (no local hash) with 403.ChangePasswordDialogin the gateway dashboard, accessible from the NavRail account menu. Follows existing dialog patterns (MUI, inline alerts, confirm-password field).Test plan