148 Commits
Author SHA1 Message Date
Piotr Mlocek 3a328be853 feat(inference): verify endpoints before saving routes (#291)
Closes #273

Verify inference endpoints synchronously on the server during set/update, expose a --no-verify escape hatch in the CLI and Python helper, and return actionable failures when validation does not pass.
2026-03-15 19:36:23 -07:00
Drew Newberry 53b7ce7112 fix(core): harden file permissions for user config directory (#328) 2026-03-15 15:55:41 -07:00
Drew Newberry f6ae1da12d chore: remove remaining navigator and nemoclaw references (#279) 2026-03-15 12:44:08 -07:00
Drew Newberry 9fdffc72b2 feat(bootstrap): add Docker preflight check before gateway startup (#321) 2026-03-15 09:45:19 -07:00
Drew Newberry 0a7ffe1669 ci(release): gate python wheels on e2e for tagged releases (#319) 2026-03-15 00:39:00 -07:00
John T. Myers b0026fbd0e fix(router): stop dropping client-sent default headers like anthropic-version (#320)
The strip_headers list in send_backend_request included default_headers
names (e.g. anthropic-version), which removed the client's value. The
already_sent check then saw the original input still contained the header
and skipped injecting the route default. Neither value reached upstream.

Remove default_headers names from strip_headers so client values pass
through. The existing already_sent guard still injects defaults only when
the client omits them.
2026-03-14 23:53:36 -07:00
Drew Newberry 847bf4438c fix(cli): check port availability before starting SSH forward (#309) 2026-03-14 21:27:17 -07:00
Drew NewberryandPiotr Mlocek 83af7a245b feat(sandbox): inject host gateway hostAliases into sandbox pods (#306)
* feat(sandbox): inject host gateway hostAliases into sandbox pods

Sandbox pods running in the k3s cluster cannot resolve host.docker.internal
by default, preventing them from reaching services on the Docker host.

Detect the host gateway IP (default route) in the cluster entrypoint,
thread it through the Helm chart to the gateway server, and inject
hostAliases entries (host.docker.internal, host.openshell.internal)
into every sandbox pod spec. The injection is conditional -- when the
IP is empty (non-Docker deployments), no hostAliases are added.

---------

Co-authored-by: Piotr Mlocek <pmlocek@nvidia.com>
2026-03-14 16:54:03 -07:00
John T. Myers 97bad8b7d0 chore: derive build version from git tags for all components (#305)
Compute version strings from git describe in openshell-core's build.rs
using the guess-next-dev scheme (e.g. 0.0.4-dev.6+g2bf9969). All binary
crates and the TUI splash screen now use the shared openshell_core::VERSION
constant instead of CARGO_PKG_VERSION.

In Docker/CI builds where .git is absent, falls back to CARGO_PKG_VERSION
which is already set correctly by the sed-patch pipeline. Also adds
OPENSHELL_CARGO_VERSION support to the cluster image and fast-deploy
supervisor builds for parity with the gateway.
2026-03-14 14:58:33 -07:00
Piotr Mlocek 05eade959c feat(cli): add no-verify inference flag (#302) 2026-03-14 14:02:36 -07:00
Drew Newberry 4b23a7e242 feat(bootstrap): restore per-gateway Docker bridge networks (#303)
PR #281 removed the shared openshell-cluster Docker network in favor of
the default bridge. This restores custom bridge networking but makes each
gateway use its own isolated network named openshell-cluster-{name},
matching the existing container/volume naming convention.

Changes:
- Add network_name() to constants.rs for per-gateway network naming
- Add ensure_network() with retry/backoff and force_remove_network()
  parameterized by network name instead of a global constant
- Attach containers to their per-gateway network via network_mode
- Disconnect and remove the network during gateway destroy
- Wire ensure_network() into the deploy flow before ensure_volume()
- Update architecture docs to reflect per-gateway network isolation
2026-03-14 13:36:06 -07:00
Piotr Mlocek 26e540d8d6 fix(cli): show startup feedback for foreground forwards (#296) 2026-03-13 21:51:08 -07:00
Drew Newberry 1ba798f0fb ci(release): pin OPENSHELL_IMAGE_TAG to version for tagged releases (#297) 2026-03-13 19:28:35 -07:00
Drew Newberry 19c3230267 feat(ci): add automated release workflow with patch version bumping (#284) 2026-03-13 17:44:22 -07:00
Drew Newberry 2858bd662f fix(cli): use line-based stdin read for gateway recreate prompt (#292) 2026-03-13 15:51:09 -07:00
Piotr Mlocek 50ea7495c1 fix(sandbox): bypass proxy for localhost traffic (#290) 2026-03-13 15:30:08 -07:00
Drew Newberry 20c32716ad fix(bootstrap): detect missing sandbox supervisor binary during gateway health check (#281) 2026-03-13 15:07:50 -07:00
Piotr Mlocek 72e0268028 feat(sandbox): add gpu sandbox scheduling support (#257)
* feat(sandbox): add gpu sandbox scheduling support

Allow sandbox creation to request GPU resources explicitly or infer them from GPU image names. This wires GPU intent through bootstrap, validates gateway support, and adds dedicated GPU E2E coverage for follow-up cluster testing.
2026-03-13 11:32:43 -07:00
John T. Myers 764fac79aa feat(tui): add log copy and visual selection mode (#276)
* feat(tui): add log copy and visual selection mode

Add clipboard support for the TUI log viewer using OSC 52 escape
sequences. Users can copy individual log lines (y), the visible
viewport (Y), or enter visual selection mode (v) to select a range
of lines with j/k and yank with y.

- Add clipboard module with OSC 52 base64 encoding
- Add format_log_line_plain() for plain-text log rendering
- Add visual selection mode with highlight styling and status bar
- Context-switch nav bar hints between normal and visual modes
- Add 7 unit tests for plain-text log formatting

Closes #274

* fix(tui): write OSC 52 to /dev/tty instead of stdout

ratatui owns stdout via the alternate screen buffer, so OSC 52
escape sequences written to stdout are swallowed by the backend.
Write directly to /dev/tty to bypass the buffer and reach the
terminal emulator.
2026-03-13 09:34:54 -07:00
Drew Newberry fbd93a4632 refactor: rename navigator- crate prefix to openshell- (#277) 2026-03-13 02:02:18 -07:00
Drew Newberry 7b0a243304 ci: remove sandbox docker build from publish and e2e workflows (#275) 2026-03-13 00:56:07 -07:00
Drew Newberry 89d21d7852 refactor(sandbox): sandboxes are managed as separate community images (#267) 2026-03-12 22:06:52 -07:00
Piotr Mlocek b241237bde fix(sandbox): opt Node clients into proxy env support (#269) 2026-03-12 17:43:07 -07:00
Piotr Mlocek 14e296d318 fix(cli): add --no-keep for ephemeral sandbox create cleanup (#258) 2026-03-12 17:18:39 -07:00
Drew Newberry 6a40f2bed8 feat(gateway): support adding remote and local gateways (#262) 2026-03-12 16:19:47 -07:00
John T. Myers fcf12dff62 feat(tui): support light terminal backgrounds with adaptive theme (#265)
* feat(tui): support light terminal backgrounds with adaptive theme

Replace the hardcoded dark-only color constants in theme.rs with a
runtime Theme struct that has dark() and light() factory constructors.
The active theme is stored on App and threaded through all draw
functions, enabling the TUI to render correctly on both dark and light
terminal backgrounds.

- Add Theme struct with 16 semantic style fields and ThemeMode enum
- Detect terminal background via COLORFGBG env var at startup
- Add --theme dark|light|auto CLI flag and OPENSHELL_THEME env var
- Migrate all 499 styles:: references across 11 UI files to app.theme
- Clean up 9 inline Style constructions that bypassed the theme system
- Add unit tests verifying dark/light palettes and legacy regression

Closes #264

* fix(tui): use OSC 11 query for reliable light/dark terminal detection

Replace COLORFGBG env var heuristic with terminal-colorsaurus crate,
which sends an OSC 11 query to read the actual background color. This
fixes auto-detection in iTerm2 and other terminals that don't set
COLORFGBG.
2026-03-12 13:15:57 -07:00
John T. Myers 2e4c2fcc84 fix(proxy): stream inference responses instead of buffering entire body (#261)
* fix(proxy): stream inference responses instead of buffering entire body

The inference.local proxy path called response.bytes().await which
buffered the entire upstream response before sending anything to the
client. For streaming SSE responses this inflated TTFB from sub-second
to the full generation time, causing clients with TTFB timeouts to abort.

Add a streaming proxy variant that returns response headers immediately
and forwards body chunks incrementally using HTTP chunked transfer
encoding. Non-streaming responses and mock routes continue to work
through the existing buffered path.

Closes #260

* docs: update architecture docs and example for inference streaming

* test(example): add direct NVIDIA endpoint tests via L7 TLS intercept

Expand inference example to 4 test cases: inference.local and direct
endpoint, each streaming and non-streaming. The direct path exercises
the L7 REST relay (relay_chunked) to verify it already streams
correctly. NVIDIA_API_KEY is picked up from the sandbox env when
started with --provider nvidia.
2026-03-12 10:36:41 -07:00
Drew Newberry b9d10861b6 refactor(sandbox): move secrets to supervisor placeholders (#192) 2026-03-12 10:30:46 -07:00
John T. Myers 454327d890 feat(policy): add policy recommendation plumbing (#204) (#222)
* feat(policy): add policy recommendation plumbing — denial aggregation, transport, approval pipeline, and mechanistic recommendations

Implement the infrastructure layer for automated policy recommendations (#204):

- Proto: 9 new RPCs and messages for draft policy lifecycle (submit, get, approve, reject, approve-all, edit, undo, clear, history)
- Persistence: SQLite/Postgres migrations and store methods for draft_policy_chunks and denial_summaries tables
- Server: Full gRPC handler implementations with mechanistic mapper that auto-generates NetworkPolicyRule proposals from denial summaries
- Sandbox: DenialAggregator with MPSC channel, deduplication, periodic flush to gateway via SubmitPolicyAnalysis
- CLI: 'openshell draft' subcommand with get/approve/reject/approve-all/undo/clear/history operations
- TUI: Draft recommendations panel accessible from sandbox policy view
- Docs: Architecture documentation in architecture/policy-advisor.md

* feat(policy): add L7-aware mechanistic mapper and policy advisor CTF example

Add L7 rule generation to mechanistic mapper (build_l7_rules,
generalise_path, looks_like_id) with 3 new unit tests. Add
examples/policy-advisor/ with a 7-gate CTF script, restrictive
sandbox policy, and walkthrough README.

* fix(policy): use sandbox name for denial flush and add TUI draft badges

Fix denial aggregator passing sandbox UUID instead of name to
SubmitPolicyAnalysis, which caused 'sandbox not found' errors on
flush. Add notification badges to the TUI sandbox list and detail
header showing pending draft recommendation counts.

* fix(policy): deduplicate draft chunks and tolerate overlapping OPA rules

Skip draft chunk creation when a pending/approved chunk already covers
the same host:port endpoint, preventing duplicate rules across denial
aggregator flush cycles.

Rewrite three OPA complete rules (network_policy_for_request,
matched_network_policy, matched_endpoint_config) to tolerate multiple
matching policies without triggering a "complete rule conflict" error.
network_policy_for_request becomes a boolean, matched_network_policy
uses a set comprehension with min(), and matched_endpoint_config uses
an array comprehension with index-0 selection.

* feat(tui): interactive draft actions, highlight bar, and detail popup

Rework the draft recommendations panel to match the logs UX:
- Highlight bar (green accent + background) instead of arrow marker
- Viewport-aware j/k scrolling with g/G for top/bottom
- Enter opens a full-screen detail popup showing endpoints, binaries,
  rationale, security notes, and action hints

Add approve/reject/approve-all draft actions:
- [a] approve selected chunk, [x] reject, [A] approve all pending
- Actions work from both the list view and the detail popup
- gRPC calls run async; result updates status bar and refreshes data
- Nav bar shows all available keybindings

Fix draft count refresh: sandbox_draft_counts now refreshes on every
tick (not just Dashboard), so the detail header badge updates in
real time.

Improve badge labels: show 'N pending' instead of a bare number in
both the dashboard sandbox list and sandbox detail header.

* refactor(policy): DB-level draft chunk dedup with hit counter and timestamps

Replace the in-memory HashSet dedup in SubmitPolicyAnalysis with a
database-level upsert. New denormalized columns on draft_policy_chunks:
- host, port: extracted from proposed_rule at insert time
- hit_count: incremented on conflict (same sandbox + host + port)
- first_seen_ms, last_seen_ms: track when the endpoint was first and
  most recently proposed

A partial unique index (WHERE status IN ('pending','approved')) ensures
only one active chunk per endpoint per sandbox; rejected/superseded
chunks don't block new proposals.

Surface hit_count and first/last_seen in:
- CLI: 'openshell draft get' shows 'Hits: N (first ..., last ...)'
- TUI: detail popup shows hits row; list view shows 'Nx' suffix

* fix(policy): optimistic retry on policy version conflicts + structured logging

merge_chunk_into_policy and remove_chunk_from_policy now retry up to 5
times on UNIQUE constraint violations (version conflicts from concurrent
approvals). Each attempt re-reads the latest policy, re-merges the rule,
and increments the version. This eliminates the race condition where
rapid successive approvals would fail with a DB error.

Add structured tracing to all draft action handlers:
- ApproveDraftChunk: logs rule_name, host, port, hit_count before merge
  and version + policy_hash after success
- RejectDraftChunk: logs rule_name, host, port, reason
- ApproveAllDraftChunks: logs pending_count at start, per-chunk merge
  progress, and final summary with chunks_approved/skipped
- UndoDraftChunk: logs before/after with rule_name and version
- Retry attempts log as warnings with attempt number and conflicting
  version

* wip: forward proxy fix, mapper allowed_ips, TUI polish, CTF rewrite

* fix(tui): use correct --gateway flag for ssh-proxy ProxyCommand

* chore: add Docker cleanup script for stale images, volumes, and build cache

* feat(tui): approve-all confirmation modal and CTF cleanup

Add [A] confirmation popup that snapshots pending chunks, shows a
scrollable list, and approves each chunk individually on confirm.
This prevents approving chunks that arrived after the modal opened.

Remove transient issue #205 reference from CTF victory banner.

* fix(tui): correct import ordering for rustfmt

* wip: stateful toggle model, rename to network rules

Draft chunks now follow a toggle state machine:
  pending -> approved | rejected (initial decision)
  approved <-> rejected (toggle)

One row per (sandbox_id, host, port) via expanded unique index.
Rejecting an approved rule removes it from the active policy.
Re-approving a rejected rule merges it back.

Rename CLI from 'draft' to 'rule', TUI from 'Draft Recommendations'
to 'Network Rules'. State-aware keybindings: approved shows [x] Revoke,
rejected shows [a] Approve. Fix sandbox detail hiding delete confirmation
behind pending message.

* refactor(policy): move mapper sandbox-side, slim schema, per-binary granularity

Move mechanistic mapper from gateway to sandbox so all analysis runs
sandbox-side (N sandboxes = N independent pipelines). Gateway is now a
thin validate + persist + approval layer.

Architectural changes:
- Move mechanistic_mapper.rs from navigator-server to navigator-sandbox
- Sandbox flush flow: aggregator drains -> mapper runs -> proposals sent
- Gateway SubmitPolicyAnalysis: validate + persist only, no mapper
- Drop denial_summaries table (write-only, zero readers)
- Consolidate migrations 003+004+005 into single 003

Schema slimming:
- Drop 5 unused columns from draft_policy_chunks (stage, denial_refs,
  supersedes_chunk_id, analysis_mode, decided_by)
- Add per-binary granularity: binary column, widen unique index to
  (sandbox_id, host, port, binary)
- Mapper groups by (host, port, binary), one proposal per triple
- Merge appends binary to existing rule; revoke removes just that binary

CTF & UX:
- 7-gate CTF: add Gate 3 (curl -> ifconfig.me:80) for per-binary demo
- TUI shows binary short name in list, full path in detail popup
- CLI output shows binary field
- Idempotent rule names, hit_count accumulates real denial counts
- Rationale text no longer bakes in stale denial count
2026-03-12 08:28:26 -07:00
Drew Newberry 1535f806af feat(sandbox): add configurable imagePullPolicy for sandbox pods (#256)
Add OPENSHELL_SANDBOX_IMAGE_PULL_POLICY env/CLI flag that propagates
through Config -> SandboxClient -> pod spec generation. When set, the
policy is applied to both inline and pod-template sandbox containers.

Helm chart exposes server.sandboxImagePullPolicy (default empty = K8s
default behavior). cluster-deploy-fast sets it to Always so dev
clusters always pull fresh sandbox images.
2026-03-12 00:00:53 -07:00
Drew Newberry 95d7ae077e refactor(cli): remove kubeconfig port, add doctor llm-help, update debug docs (#252) 2026-03-11 21:25:25 -07:00
Drew Newberry bcc6dad1d0 feat(cli): launch sandbox editors via managed ssh include (#226) 2026-03-11 20:06:38 -07:00
Piotr Mlocek 3fe445ce05 fix(cli): improve completion coverage and gateway selection (#241)
* fix(cli): improve completion coverage and gateway selection

Closes #232

Add typed completion metadata for gateway, sandbox, and path arguments, make gateway selection interactive on TTYs, and expose root subcommands in completions while keeping non-interactive behavior stable.
2026-03-11 16:57:40 -07:00
Drew NewberryandPiotr Mlocek d94d4e1166 feat(cluster): add NVIDIA GPU passthrough support for gateway start (#234)
Add --gpu flag to 'openshell gateway start' that enables NVIDIA GPU
access inside the cluster container. When enabled, the Docker container
receives GPU device requests via bollard, k3s auto-detects the NVIDIA
runtime, and the NVIDIA k8s-device-plugin is deployed via HelmChart CR
so Kubernetes workloads can request nvidia.com/gpu resources.

- Add GPU device request to Docker container creation (bollard DeviceRequest)
- Set GPU_ENABLED=true env var to gate GPU manifest deployment
- Multi-stage Dockerfile build to bundle NVIDIA Container Toolkit binaries
- Conditional GPU manifest copy in cluster entrypoint
- HelmChart CR for nvidia-device-plugin v0.18.2 with GFD and NFD enabled
- Thread gpu bool through DeployOptions, CLI flag, and run dispatch

---------

Co-authored-by: Piotr Mlocek <pmlocek@nvidia.com>
2026-03-11 16:53:24 -07:00
John T. Myers 1ad45b4af0 fix(policy): enforce run_as_user/run_as_group must be 'sandbox' (#230)
Replace the clear_process_identity + maybe_infer_sandbox_user workaround
with strict enforcement: run_as_user and run_as_group must always be
'sandbox'. If omitted from YAML, they are defaulted server-side via
ensure_sandbox_process_identity(). Any other value is rejected.

This fixes 'policy set' failing with 'process policy cannot be changed
on a live sandbox' for custom-image sandboxes, where the CLI was
clearing identity fields but the YAML had 'sandbox' explicitly set.

- Remove clear_process_identity() from CLI and TUI
- Add ensure_sandbox_process_identity() to default missing fields
- Rename RootProcessIdentity -> InvalidProcessIdentity
- Replace maybe_infer_sandbox_user with validate_sandbox_user (fail-fast)
- Update OPA test policies to use run_as_user: sandbox
2026-03-11 12:46:59 -07:00
Drew Newberry 1d33d4c2a7 fix(cluster): skip DNS probe for IP-literal registry hosts (#229) 2026-03-11 09:20:56 -07:00
Drew Newberry 53b3cb76a2 feat(cli): detect port conflicts before gateway start, add sandbox delete --all, and improve spinner spacing (#225) 2026-03-11 01:06:23 -07:00
Drew Newberry 9099014115 fix(cli): improve sandbox provisioning progress indicator (#221) 2026-03-10 23:34:04 -07:00
Drew Newberry 756950140c refactor(python): rename navigator module to openshell and migrate config to gateway paths (#220) 2026-03-10 22:24:04 -07:00
Drew Newberry 36e8241293 fix: switch community sandbox registry to GHCR and align TLS paths (#218) 2026-03-10 19:38:22 -07:00
Piotr Mlocek 63a07bf167 feat(cli): group help flags and make help for commands consistent with groups (#216) 2026-03-10 18:29:40 -07:00
Drew Newberry f4af0ee848 chore: replace all nemoclaw references with openshell (#214)
- Update registry URLs from ghcr.io/nvidia/nemoclaw to ghcr.io/nvidia/openshell
- Rename NEMOCLAW_* environment variables to OPENSHELL_*
- Update CLI command references in documentation
- Update PyPI package name references
- Update GitHub repository URLs
- Rename .agents/skills/nemoclaw-cli directory to openshell-cli
2026-03-10 16:10:57 -07:00
John T. MyersandJohn Myers e373f7c056 feat(inference): add sandbox-system inference route for platform-level inference (#209)
* feat(inference): add sandbox-system inference route for platform-level inference

Add a separate system-level inference route that the sandbox supervisor
can use in-process for platform functions (e.g., embedded agent harness
for policy analysis), distinct from the user-facing inference.local
endpoint. The system route is accessed via an in-process API on the
supervisor, ensuring userland code in the sandbox netns cannot reach it.

- Extend proto with route_name fields on Set/Get inference messages
- Add ResolvedRoute.name field to the router for route segregation
- Server resolves both user and sandbox-system routes in bundles
- Sandbox partitions routes into user/system caches on refresh
- Expose InferenceContext::system_inference() in-process API
- CLI --sandbox flag targets the system route on set/get/update
- Integration tests using mock:// routes for the full in-process path

Closes #207

* refactor(cli): rename --sandbox flag to --system for inference commands

The --sandbox flag could be misread as targeting a user-level sandbox
operation. Rename to --system to clearly indicate it configures the
platform-level system inference route.

* style(cli): collapse short if-else per rustfmt 1.94

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-10 15:30:07 -07:00
Drew Newberry cd6bdd0cb8 fix(bootstrap): update hardcoded navigator namespace refs to openshell (#212)
The previous rename commit missed hardcoded namespace and workload
references in the bootstrap code. The Helm chart deploys to the
'openshell' namespace with 'openshell' workload names, but the
bootstrap code was still looking for 'navigator' namespace and
workloads, causing cluster startup to hang waiting for TLS secrets
that were never found.

Changes:
- lib.rs: Update secret fetch to use -n openshell
- runtime.rs: Rename functions and update all kubectl commands
  to reference openshell namespace and workloads
2026-03-10 15:18:55 -07:00
Drew Newberry d6c6e97679 chore: remove navigator references from codebase (#208) 2026-03-10 14:46:11 -07:00
John T. MyersandJohn Myers ddfe38b1c3 feat(tui): add OpenShell splash screen and rebrand title bar (#210)
* wip: feat(tui): OpenShell splash screen and branding

* chore(tui): fix rustfmt import ordering

---------

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-10 14:36:27 -07:00
Drew Newberry 984d1a6e5c chore: rename project from NemoClaw to OpenShell (#198) 2026-03-10 11:49:09 -07:00
John T. MyersandJohn Myers a3af9af2fe fix(server): merge provider credentials/config on update instead of replacing (#202)
UpdateProvider RPC was doing full replacement of credentials and config
maps, silently destroying data on partial updates. This changes the
semantics so empty maps preserve existing values, non-empty maps merge
(upsert), and empty-string values delete individual keys. Also makes
provider type immutable after creation and adds field validation on
update.

Closes #199

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-10 08:42:55 -07:00
Drew Newberry 01fe62c459 feat: CLI improvements and fixes (#201)
* feat(cli): reorganize help output like gh CLI

- Add custom help template with grouped command sections (SANDBOX, GATEWAY, ADDITIONAL)
- Add bold headers using ANSI escape codes
- Add USAGE section showing command/subcommand pattern
- Add consistent help template for subcommands with COMMANDS section
- Add examples for all subcommands (sandbox, gateway, forward, logs, policy, provider, inference)
- Show help when running a command without a subcommand
- Add ALIAS section to subcommand help for aliased commands
- Remove less useful aliases (prov, inf, st, t, comp), keep core ones (sb, gw, lg, fwd, pol)
- Add RequestingCompute provisioning step with elapsed time display

* fix(server): replay platform events for late WatchSandbox subscribers

The CLI was stuck on 'Requesting compute...' even when the pod was
already pulling the container image. This happened because Kubernetes
events (Scheduled, Pulling, etc.) that fired before the CLI subscribed
to WatchSandbox were lost.

Add a tail buffer to PlatformEventBus (mirroring TracingLogBus) so
platform events are buffered and replayed to late subscribers. This
ensures the CLI shows accurate provisioning status regardless of when
it connects.
2026-03-10 07:58:29 -07:00
John T. MyersandJohn Myers bffda60e92 feat(tui): auto-refresh sandbox policy view when new versions are detected (#200)
Closes #196

Piggyback on the existing 2-second sandbox list refresh to detect policy
version changes. When the user is on the sandbox screen and the listed
current_policy_version differs from the displayed version, fetch the
updated policy via GetSandboxPolicy. Scroll position is preserved across
auto-refreshes so the user is not disrupted.

Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-03-10 00:02:32 -07:00