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.
Register `SUPPORTED_DRIVER_TYPES["uiautomator2"]` backed by the new
`AndroidDriver`, mirroring `WDADriver` method-for-method. tap/swipe/home use
the confirmed UiAutomator2 mobile commands (`clickGesture`, `dragGesture`,
`pressKey`); swipe converts `duration_ms` to a drag speed with a zero-distance
guard. Adds 34 mocked unit tests, an env-var-gated integration test, and
corrects the outdated "Android not registered" note in the iPhone setup doc.
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>
Replaces the stub Planner's fixed describe_screen/[] behavior with a real
decision-maker: AIPlanner uses native tool/function calling (Anthropic or
OpenAI, pluggable via AI_PLANNER_PROVIDER) to select exactly one grounded
action per turn, with an explicit finish_task(success, reason) tool for
completion/failure instead of an ambiguous "no tool call" signal. Default
disabled (AI_PLANNER_ENABLED=false) and additive; TaskRunner falls back to
the existing stub Planner unchanged when disabled.
Amends CONSTITUTION.md's Perception Boundary with one narrow exception:
only the AI Planner may receive the current step's raw screenshot bytes
alongside Scene, for vision-grounded coordinate grounding. Also fixes a
latent gap in TaskRunner.run(): observe/plan exceptions are now caught per
iteration and turned into a failed task with a failure_reason, instead of
propagating uncaught.
openspec change: ai-planner-runtime.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Consumes the external Subscription Platform as source of truth for skill
content; reuses skills_learning domain models (extended with KnowledgeSkill)
and workflow.skill_exec resolver. HTTP/MCP deps land in api/ per
CONSTITUTION.md; synced skills use a physically separate SQLite file
(tasks/skills.sqlite3) to preserve the skill-authoring capability boundary.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Existing tests only exercised perception.scene_builder.build_scene()
directly or substituted a FakePerceptionProvider, so the real provider
port classes had no direct coverage. Both were already correct on
manual inspection; this closes the coverage gap.
openspec: perception-provider capability, archived change device-agent-runtime-foundation
- divergence_tolerance was loaded from config but diff_flow_versions
never consulted it, so structural_divergence was always plain
sequence-equality regardless of the configured tolerance. Now
actually applied per design.md D4.
- The skill-catalog-subscription isolation guard test asserted
'skills.catalog' not in sys.modules, which is vacuously true since
that module doesn't exist anywhere yet. Replaced with a real check
against store.py's actual imports.
openspec: skill-versioning capability, archived change skill-learning-runtime
- _with_known_widgets_only used to discard the entire SemanticScene
(returning None) when filtering dangling-element-id widgets left an
empty list, losing valid page/intents. Now returns a SemanticScene
with widgets=[] instead, matching design.md's 'dropped, not a hard
failure' intent.
- Removed _SDK_UNAVAILABLE_EXCEPTION_NAMES, dead code left over from an
abandoned name-based exception-matching approach; the blanket except
Exception already maps every SDK failure correctly.
openspec: semantic-scene capability, archived change semantic-scene-runtime
- driver/registry.py never actually defined register_driver_type, so
the plugin-system's 'driver plugin registered successfully' scenario
was unreachable in production (only simulated via test monkeypatch).
Added a real implementation wired into the existing driver factory
registry.
- PluginRegistry.discover() only caught PluginValidationError/
DuplicatePluginError, so a driver-kind manifest that failed wiring
(DriverRegistryUnavailableError/PluginTargetResolutionError) aborted
the whole scan, silently skipping co-located tool/skill manifests.
Now caught and skipped per manifest instead.
openspec: plugin-system capability, archived change cloud-runtime
CollaborativeTaskRunner hand-rolled its own step loop instead of
composing runtime/task.py's TaskRunner as design.md D3 requires,
silently dropping Timeline recording, WorldModel wiring,
TaskMetadataStore sync, and the on_task_succeeded/skill-synthesis
hook. Now calls TaskRunner's shared _start_world_view/
_record_step_result helpers for that bookkeeping. Also fixes
pre_observation being reused stale across steps in a multi-step plan
instead of refreshing to the prior step's post-observation.
openspec: multi-agent-collaboration capability, archived change multi-agent-runtime
tools/ is a Hexagonal inner layer that must never depend on LLM
concerns (ADR 0002), but describe_screen_semantic.py imported
semantic.enricher, which pulls in the Anthropic client by default.
Relocated the wrapper to runtime/, which is where LLM-dependent code
is allowed to live; updated the tool registry and all test imports
accordingly. No behavior change.
openspec: semantic-scene capability, archived change semantic-scene-runtime
- pooled_devices primary key changed from device_id alone to
(host_id, device_id), so two hosts reporting the same local
device_id no longer crash sync_host_devices() with an uncaught
sqlite3.IntegrityError.
- Device gained a capability_tags field so DevicePool.sync_host_devices()
can actually populate PooledDevice.capability_tags from a real host
sync instead of always falling back to an empty list.
openspec: device-pool capability, archived change cloud-runtime
- Resume no longer infinite-loops when a crash occurs between
append_step_result() and update_run() for a BranchStep: the recorded
branch target is now read from the persisted result instead of
recomputed as None.
- Resume no longer silently discards a recorded step failure that
happened right before the crash; the run correctly ends failed.
- A failed step's own next_step_id (e.g. routing to a BranchStep that
evaluates step_result_success) is now honored instead of
unconditionally forcing the run to failed, per spec.
openspec: workflow-orchestration capability, archived change workflow-orchestration-runtime