- Host Agent now defaults AI_PLANNER_ENABLED=true (opt-out via env),
scoped to apps/device-host-agent/host_agent/execution.py only; the
shared runtime.planner_config default (disabled) is unchanged.
- Add openspec proposal for cloud-planner-proxy: centralize LLM
provider config/credentials on the Cloud Control Plane and let the
Host Agent proxy AI Planner decisions through it instead of holding
provider API keys locally. Proposal only, no implementation yet.
Host Agent:
- One-time local operator account bootstrap (PBKDF2-HMAC-SHA256, atomic
0600-permission write) gating the daemon's first unattended start via a
new `setup` CLI subcommand.
- Default control-plane URL now https://amcp.home.jerryyan.top (env var
override unchanged).
- Enrollment no longer requires a pre-issued token; falls back to
zero-token self-service enrollment when none is configured.
Cloud control plane:
- CLOUD_SELF_SERVICE_ENROLLMENT_ENABLED (default false) opt-in flag.
- SelfServiceEnrollmentAuthProvider + ChainedEnrollmentAuthProvider:
configured tokens still take priority; self-service only applies when
no token matches, preserving edge-host-enrollment's token-bound path.
- Fixed a latent bug in sql_repository.py::enroll_host: the token-conflict
lookup used `== enrollment_token_digest`, which SQLAlchemy compiles to
`IS NULL` when the value is None, so every self-service enrollment after
the first would have falsely collided with an existing NULL-digest host.
Skipped that lookup entirely when the digest is None.
Docs/deploy: .env.example, compose.yaml, compose.deploy.yaml,
CLOUD_DEPLOYMENT.md, MACOS_IPHONE_SETUP.md updated for the new flag,
URL default, and required `device-host-agent setup` step.
Verification: 494 non-integration tests pass; openspec validate --strict
passes. PostgreSQL-backed contract tests and full manual end-to-end
verification were not run (no Postgres/Docker or reachable cloud-api in
this environment); noted as unchecked in tasks.md 7.2/7.4.
Multi-stage Dockerfile: stage 1 (node:20-bookworm-slim) builds cloud-console
with vite base "/console/"; stage 2 (uv) copies dist/ to /app/console-static.
Cloud API mounts the SPA at /console via SpaStaticFiles (StaticFiles subclass
that falls back to index.html for deep-link refreshes) when the new
CLOUD_CONSOLE_STATIC_DIR env is set, and 307-redirects / to /console/. Static
files bypass bearer auth (the SPA shell is public; tokens are still required
for /v1/*). Compose enables the mount by default; local dev still uses
npm run dev + CLOUD_CONSOLE_CORS_ORIGINS.
Jenkinsfile passes mirror overrides (NODE_IMAGE, NPM_REGISTRY, UV_IMAGE,
APT_MIRROR, UV_INDEX_URL) as --build-arg, defaulting to CN mirrors
(registry.jerryyan.net, registry.npmmirror.com, registry-ghcr.jerryyan.top,
mirrors.aliyun.com) so CN builds don't time out; Dockerfile ARGs default to
official upstreams so `docker build .` still works anywhere.
Backend suite: 443 passed (-m "not integration"); cloud-console typecheck
and production build succeed with the new base path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements the cloud-console OpenSpec change: adds GET /v1/tasks (filterable,
bounded pagination, tasks:read) and GET /v1/tasks/{id}/attempts (404 on unknown
task) to the platform SDK, with matching CloudClient methods and a closed-by-
default CLOUD_CONSOLE_CORS_ORIGINS allow-list wired through CloudControlConfig.
Ships an independent Vue 3 + Vite SPA at cloud-console/ that authenticates with
an operator-supplied bearer token held in sessionStorage, renders tasks with
attempt history, device pool, host registry, and the plugin registry with a
registration form.
Backend test suite: 438 passed (-m "not integration"); cloud-console typecheck
and production build both succeed. PostgreSQL-backed repository tests and
manual end-to-end verification remain pending external infrastructure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>