Files
2026-07-14 07:31:31 +08:00

6.0 KiB

1. Config and dependencies

  • 1.1 Add fastapi and uvicorn[standard] as explicit direct dependencies in apps/device-host-agent/pyproject.toml (versions matching those already pinned in uv.lock)
  • 1.2 Add console_bind_host, console_port, console_allow_non_loopback, console_session_ttl_seconds, and console_history_limit fields to HostAgentConfig in host_agent/config.py, plus matching HOST_AGENT_CONSOLE_* env vars except an enable flag in load_host_agent_config, reusing the existing _positive_float/_positive_int validators
  • 1.3 Add validation that raises HostAgentConfigurationError when console_bind_host is non-loopback and console_allow_non_loopback is not set
  • 1.4 Add unit tests in apps/device-host-agent/tests/test_config.py for defaults, env var parsing, and the non-loopback-without-opt-in rejection

2. Shared device-registration helper

  • 2.1 Extract the device-add sequence (DeviceConfigStore.add/set_cloud_device_id + optional enrollment_client.enroll_device + manager.register_device) currently inlined in host_agent/app.py::_configured_device_manager into a small shared function usable by both startup and the console
  • 2.2 Add a matching shared function for device removal (DeviceConfigStore.remove + manager.unregister_device)
  • 2.3 Update _configured_device_manager to use the extracted add helper; confirm existing test_app.py startup tests still pass unchanged

3. Local history store

  • 3.1 Add a new host_agent/history.py module with a ConsoleHistoryStore backed by a small SQLite file (e.g. tasks/host_console_history.sqlite3), supporting record_assignment(...), record_heartbeat(...), and list_recent(limit), pruning beyond console_history_limit on write
  • 3.2 Add an optional recorder hook to AssignmentProcessor.process (host_agent/processor.py) invoked after a terminal result is reported, no-op when no recorder is configured
  • 3.3 Add an optional recorder hook to HeartbeatSynchronizer.sync_once (host_agent/heartbeat.py) invoked after each successful sync, no-op when no recorder is configured
  • 3.4 Unit tests for ConsoleHistoryStore (write, prune-on-overflow, ordering) and for the processor/heartbeat recorder hooks firing with the expected data and being skipped when absent

4. Session and authentication

  • 4.1 Add host_agent/web/auth.py with an in-memory session store (opaque token → session state with expiry), login verification against LocalAccountStore, and CSRF token issuance/validation bound to the session
  • 4.2 Implement session cookie handling (HttpOnly, SameSite=Strict, Secure when bind host is non-loopback) and sliding expiry per console_session_ttl_seconds
  • 4.3 Implement an auth dependency/middleware that redirects unauthenticated requests to /login and rejects mutating requests lacking a valid CSRF token
  • 4.4 Unit tests: successful login, wrong password, no-account-yet state, session expiry, CSRF rejection on a mutating route, redirect-to-login for an unauthenticated GET

5. Console pages and routes

  • 5.1 Add host_agent/web/app.py building a FastAPI sub-application with hand-written HTML responses (f-string templates + a shared escape() helper for every interpolated value) for: /login, / (status dashboard), /devices, /account, /history
  • 5.2 Implement /login (GET form, POST verify+establish session) per spec scenarios, including the "no local account exists" state
  • 5.3 Implement the status dashboard: last heartbeat outcome/time, enrollment/identity state, device list with status, current assignment/execution state, sanitized effective config (no token/password rendered); add a small JSON status-fragment endpoint polled via inline fetch() for refresh without full reload
  • 5.4 Implement /devices: list, add, edit, remove forms wired to the section-2 shared helpers, taking effect on the live DeviceManager immediately
  • 5.5 Implement /account: change-password form requiring current password re-entry, calling LocalAccountStore.create (or an equivalent update path) only after verifying the current credential
  • 5.6 Implement /history: read-only table of recent assignment/heartbeat entries from ConsoleHistoryStore
  • 5.7 Implement /logout (CSRF-protected POST) invalidating the session

6. Lifecycle wiring

  • 6.1 In host_agent/app.py::create_application, always construct the console app, session store, and ConsoleHistoryStore, and wire the recorder hooks from section 3 into the constructed AssignmentProcessor/HeartbeatSynchronizer
  • 6.2 In HostAgentApplication.run_async, start a uvicorn.Server task (bound to console_bind_host/console_port, install_signal_handlers=False) alongside the heartbeat task and stop it in the existing finally shutdown sequence
  • 6.3 Integration test exercising the full lifecycle: process starts, console responds on the configured loopback port, process shuts down cleanly and stops the console server
  • 6.4 Integration test confirming that the Console is constructed by default and existing test_app.py/test_e2e.py behavior is unaffected

7. Documentation

  • 7.1 Document the HOST_AGENT_CONSOLE_* environment variables excluding an enable flag, mandatory loopback-bound startup, and the SSH port-forward recommendation for remote access in docs/CLOUD_DEPLOYMENT.md
  • 7.2 Add a short section to docs/MACOS_IPHONE_SETUP.md describing mandatory Console startup on an edge machine and what it shows

8. Validation

  • 8.1 Run uv run --package device-host-agent pytest (full package suite) and the root non-integration suite; confirm no regressions
  • 8.2 Run Ruff check/format and python -m compileall over the changed files
  • 8.3 Manually verify in a browser: login, status dashboard auto-refresh, add/edit/remove a device, change password, view history, logout, and confirm the console refuses to bind non-loopback without the opt-in flag
  • 8.4 Run openspec validate host-agent-local-console --strict and confirm it passes