Rewrites CLAUDE.md as the current-architecture reference, drops the registry from INSTALL/PRODUCT/DESIGN and the web README, deletes the registry Dockerfile, and marks the dated design specs superseded where they describe the registry, JWT auth, or cross-host attach. The installer loses its configure step — every config field now has a default, so a daemon starts with no config file at all.
5.3 KiB
Spawn form: home-dir prefill + live directory autocomplete (NAP-329)
Partly superseded (2026-08-22). Maestro is now single-host — the endpoint is now
GET /api/dirswith no daemon id in the path. SeeCLAUDE.md.
Problem
The web spawn form's Working directory field is required but offers no
help filling it: no reliable prefill, no completion. The user wants
- the field prefilled with the selected host's home dir, updating when the host changes; and
- shell-style directory completion (
cd dir/sub<tab>) driven by real-time queries to each host's daemon, so it works across every federated host.
Applies to both the cwd field and the worktree Base repo field.
Architecture context
The web SPA talks to a single home daemon (served by maestro-web, which
reverse-proxies /api/*) — or, in dev, directly to the registry (vite
proxy). The home daemon owns its sessions and mirrors peers' catalogs; it
brokers cross-daemon operations (create/kill) to the owning daemon over
the registry control link (NAP-327). The registry can also drive a daemon RPC
directly (create_session/kill_session) for its own client_api front door.
Directory listing must reach the daemon whose host you're spawning on, which may be the home daemon itself or any peer. So we mirror the create/kill plumbing end to end rather than only the registry leg.
Design
Listing strategy
The daemon lists one directory's immediate subdirectories; the web caches
the result per (daemonId, dir) and filters the trailing path segment
client-side, re-querying only when the user crosses a /. Fewer round-trips,
instant filtering, still live. Directories only (cwd / base-repo are always
dirs); hidden dirs excluded; sorted case-insensitively; capped.
Wire protocol (maestro-protocol)
DirListing { path: PathBuf, entries: Vec<String>, truncated: bool }—pathis the resolved absolute dir (after~/relative expansion);entriesare immediate subdirectory names.DaemonInbound::ListDir { reply_to, path: String }— registry → owner daemon.DaemonOutbound::ListDirResult { reply_to, result: RpcResult<DirListing> }— owner → registry.DaemonOutbound::ForwardListDir { reply_to, daemon_id, path }— home daemon → registry (relay to owner).DaemonInbound::ForwardListDirResult { reply_to, result: RpcResult<DirListing> }— registry → home daemon.
Carried by the existing RpcResult + #[serde(other)] Unknown forward-compat
machinery, so older peers degrade gracefully.
Daemon (maestro-daemon)
- New pure module
dirlist:list_dir(home_dir, requested) -> Result<DirListing, ErrorInfo>. Expansion: empty → home; leading~→ home; relative → joined onto home. Reads the dir, keeps directories (resolving symlinks), sorts, caps (MAX_ENTRIES). Error codes:not_found,permission_denied,not_a_directory,io_error. registry_clienthandlesListDir(callsdirlist::list_dir, repliesListDirResult) andForwardListDirResult(routes to broker).brokergainsforward_list_dir(daemon_id, path)+complete_forward_list_dir, mirroringforward_kill(fail-fast while disconnected, RAII pending cleanup).local_api:GET /api/daemons/:id/dirs?path=<p>— self →dirlist::list_dir; peer →broker.forward_list_dir. Maps error codes to HTTP status.
Registry (maestro-registry)
Registry::list_dir(daemon_id, path)mirroringcreate_session(newpending_list_dirmap +PendingGuard,complete_list_dir); no catalog side-effects.forward_list_dir(home, reply_to, target, path)mirroringforward_kill.handlerdispatchesListDirResult→complete_list_dirandForwardListDir→forward_list_dir.client_api:GET /api/daemons/:id/dirs?path=<p>→registry.list_dir, same timeout/error mapping ascreate_session.
Web (web/src)
types.ts:DirListing.api.ts:listDir(daemonId, path) -> Promise<DirListing>→GET /api/daemons/:id/dirs?path=….- New
dirAutocomplete.ts: attaches to an input + a daemon-id getter. Debounced fetch of the current dir,(daemon,dir)cache, fixed-position dropdown with arrow-nav; Tab completes the longest common prefix / accepts the highlighted entry; Enter accepts (suppressing submit) or submits when closed;/drills into the next level; Esc closes. Failures degrade to no suggestions. spawn.ts: keep host-change home-dir prefill; wire the widget onto cwd + worktree base repo.
Testing
- Protocol: serde round-trip for the four new variants +
DirListing, and an unknown-variant decode (already covered generically). - Daemon
dirlist: temp-dir listing (dirs-only, hidden excluded, sorted),~and relative + empty expansion, andnot_found/not_a_directorycodes. - Daemon
broker:forward_list_dirround-trip + fail-fast-while-disconnected, mirroring the existing forward_create/kill tests. - Registry:
list_dirRPC pairing +complete_list_dir;forward_list_dirrelay, reusing the create/kill in-process channel harness. - Web: no test runner present; rely on
tsctypecheck + manual verification.
Scope
In: the full web→(home daemon|registry)→owner daemon path so completion works for any host. Out: listing files (dirs only); fuzzy matching (prefix only); persisting recently-used paths.