Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/PROJECT_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,17 +229,17 @@ browser/TUI smoke verification.

- [x] Ability to start webserver (`tycho serve`)
- [x] Tailscale auto-bind, MagicDNS URL display, HTTPS Serve detection, and compact terminal QR for phone setup
- [x] Mobile Remote UI shell at `/` with `Now`, `Agents`, `Search`, `Projects`, and `Setup` tabs
- [x] Mobile Remote UI shell at `/` with simplified `Now`, `Agents`, and `Settings` tabs
- [x] Footer navigation sticks to the viewport, hides on downward scroll, and reappears on upward scroll
- [x] Read agent conversation (`GET /agents/{key}/conversation`)
- [x] Submit prompt to agent (`POST /agents/{key}/messages`)
- [x] Start / Stop an agent (`POST /agents/{key}/start`, `POST /agents/{key}/stop`)
- [x] Creates / Edit an agent (`POST /agents`, `PATCH /agents/{key}`)
- [x] Archive an agent (`DELETE /agents/{key}` or `POST /agents/{key}/archive`)
- [x] Archive one agent (`DELETE /agents/{key}` or `POST /agents/{key}/archive`) or bulk archive idle agents (`POST /agents/archive`)
- [x] Project list/detail endpoints and mobile project health/detail screens
- [x] Guarded deploy/maintenance/live preflight and start endpoints
- [x] Remote setup/readiness endpoint and Setup screen
- [x] Client-side Remote UI search across agents and projects
- [x] Remote setup/readiness endpoint and Settings screen
- [x] Client-side Remote UI filtering across agents and projects
- [x] Remote UI skill discovery for chat insertion
- [x] Browser push subscription/test-notification foundation, with HTTPS MagicDNS support and HTTP MagicDNS warnings ([WEB_PUSH_PLAN.md](./WEB_PUSH_PLAN.md))
- [x] Automatic browser push notifications when agents require response or finish
Expand Down
40 changes: 36 additions & 4 deletions docs/REMOTE_SERVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ The UI is plain server-served HTML/CSS/JavaScript, with no frontend build step o

Home-screen launches are treated as normal browser sessions, but mobile browsers can be more aggressive about reusing an old app shell. The root UI references `/ui.css` and `/ui.js` with a content digest query string, and `POST /server/restart` is the explicit cache-reset path: the restart response sends cache-reset headers, the browser clears Cache Storage when available, and the UI reloads itself with a restart query string after the replacement server is healthy.

The top-level mobile tabs are `Now`, `Agents`, `Search`, `Projects`, and `Setup`. Detail routes use hash navigation such as `#agent/{key}`, `#project/{key}`, and `#project/{key}/action/{action}`. The footer nav is fixed on top-level routes, hides while scrolling down, shows again while scrolling up, and is hidden on detail subpages.
The top-level mobile tabs are `Now`, `Agents`, and `Settings`. Agents is the canonical project-and-agent workspace: it filters agents and project metadata, keeps zero-agent projects reachable for first-agent creation, and links to project detail routes. Legacy `#search`, `#projects`, and `#setup` hashes are redirected to the closest surviving tab. Detail routes use hash navigation such as `#agent/{key}`, `#project/{key}`, and `#project/{key}/action/{action}`. The footer nav is fixed on top-level routes, hides while scrolling down, shows again while scrolling up, and is hidden on detail subpages.

Browser push notification work is tracked in [WEB_PUSH_PLAN.md](./WEB_PUSH_PLAN.md). Push can use a Tailscale MagicDNS domain when it is served over HTTPS, preferably with Tailscale Serve or Tailscale Funnel. Plain HTTP MagicDNS URLs show a soft warning, but the UI still lets the user try enabling notifications when the browser exposes the required push APIs.

Expand Down Expand Up @@ -161,7 +161,7 @@ http://100.x.y.z:7373/

Authentication is optional for localhost. If `TYCHO_REMOTE_TOKEN` is unset or blank, requests are accepted without auth.

When `tycho serve` binds to a non-loopback host without a token, startup logs print a warning. The Setup screen also marks public Remote UI URLs as `token recommended`.
When `tycho serve` binds to a non-loopback host without a token, startup logs print a warning. The Settings screen also marks public Remote UI URLs as `token recommended`.

Set `TYCHO_REMOTE_TOKEN` before using a Tailscale MagicDNS URL or another non-local interface:

Expand Down Expand Up @@ -255,6 +255,7 @@ Conversation entries are projected from `AgentChatLog#chat_blocks` when availabl
| `GET` | `/agents/{key}` | Read one managed agent. |
| `PATCH` / `PUT` | `/agents/{key}` | Edit one idle managed agent. |
| `DELETE` | `/agents/{key}` | Archive one idle managed agent. |
| `POST` | `/agents/archive` | Archive multiple idle managed agents from a `keys` array, returning archived, skipped, and failed keys. |
| `GET` | `/agents/{key}/conversation` | Read the rendered conversation blocks for one agent. |
| `PUT` | `/agents/{key}/reading` | Mark one agent as read after the user opens its conversation. |
| `POST` | `/agents/{key}/messages` | Append a user prompt to one agent. |
Expand All @@ -278,7 +279,7 @@ Conversation entries are projected from `AgentChatLog#chat_blocks` when availabl
| `GET` | `/attachments/{id}` | Read normalized attachment metadata and inline preview content when available. |
| `GET` | `/attachments/{id}/blob` | Stream the attachment file bytes for image and binary previews. |
| `GET` | `/setup` | Read Remote UI readiness, auth, Tailscale, config, log, and refresh metadata. |
| `GET` | `/search` | Return agent and project payloads for client-side search. |
| `GET` | `/search` | Return agent and project payloads for compatibility with older client-side search flows. |
| `GET` | `/`, `/ui`, `/ui.css`, `/ui.js` | Serve the Remote UI. `/ui` remains a compatibility alias. |
| `GET` | `/favicon.svg`, `/favicon.ico` | Serve the Remote UI favicon. |

Expand Down Expand Up @@ -545,6 +546,37 @@ Response:
}
```

### `POST /agents/archive`

Archives multiple idle agents from a `keys` array. Running agents are skipped and missing keys are reported without blocking idle agents in the same request.

```bash
curl -X POST http://127.0.0.1:7373/agents/archive \
-H "Content-Type: application/json" \
-d '{"keys":["web-charlie-agent-8","web-delta-agent-3"]}'
```

Response:

```json
{
"archived": [
{
"agent_key": "web-charlie-agent-8",
"archive_path": "/Users/example/.tycho/logs/agents/archive/20260508-001431-web-charlie-agent-8"
}
],
"skipped": [
{
"agent_key": "web-delta-agent-3",
"reason": "running"
}
],
"failed": [],
"archive_count": 1
}
```

### `POST /agents/{key}/clone`

Creates a fresh managed agent from an existing one with a new key, empty logs, no runs, and no native session id. Form fields such as `name`, `template_key`, `agent`, `workspace`, `prompt`, and `sandbox_mode` may be supplied to edit the clone before it is saved. Set `archive_source: true` to archive the source agent after the clone is created.
Expand All @@ -571,7 +603,7 @@ Response:

### `GET /push/config`

Returns browser push readiness for the Remote UI Setup screen. The public VAPID key is safe for the browser; the private key remains server-side.
Returns browser push readiness for the Remote UI Settings screen. The public VAPID key is safe for the browser; the private key remains server-side.

```json
{
Expand Down
13 changes: 6 additions & 7 deletions docs/REMOTE_UI_IMPLEMENTATION_HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,18 @@ Current implementation files:
- JavaScript: `lib/hq/remote_ui/assets/app.js`
- Static asset helper: `lib/hq/remote_ui.rb`

The old phase checklist has been retired because the shell, top-level screens, main detail routes, project payloads, setup/readiness payloads, skill discovery, client-side search, guarded project actions, audit fixes, and mobile nav polish are now implemented.
The old phase checklist has been retired because the shell, simplified top-level screens, main detail routes, project payloads, setup/readiness payloads, skill discovery, client-side filtering, guarded project actions, audit fixes, and mobile nav polish are now implemented.

## Implemented

- Five top-level destinations: `Now`, `Agents`, `Search`, `Projects`, `Setup`.
- Three top-level destinations: `Now`, `Agents`, `Settings`.
- Fixed bottom nav on top-level screens; it hides on downward scroll and reappears on upward scroll, focus, route changes, or near page top.
- Bottom nav is hidden on subpages with a back button.
- Deep links show loading states instead of transient not-found screens while initial data loads.
- Now screen shows attention count, paused/blocked agents, running agents, and search affordance.
- Agents screen filters and groups managed agents by project.
- Search screen searches agents and projects client-side, prioritizing unread agents on empty query.
- Projects screen lists health, latency, group, and maintenance/action state.
- Setup screen shows URL, Tailscale/MagicDNS state, auth state, harness readiness, schema/config readiness, logs/storage, refresh intervals, and safety defaults.
- Agents screen filters and groups managed agents by project, matches project metadata, keeps zero-agent projects reachable, and supports bulk archiving idle agents.
- Project detail routes remain reachable from Agents project headers.
- Settings screen shows URL, Tailscale/MagicDNS state, auth state, harness readiness, schema/config readiness, logs/storage, refresh intervals, and safety defaults.
- Agent detail supports conversation viewing, current activity, run metadata, skill insertion, prompt submission, start run, and stop confirmation.
- Project detail shows health, revision, deploy details, versions/templates, recent agent summary, and guarded project actions.
- Guarded deploy/maintenance/live action screens show consequences and preflight checks before starting a detached Kamal action.
Expand Down Expand Up @@ -84,7 +83,7 @@ Manual smoke:

- Start `bin/tycho serve`.
- Open `/` at mobile width.
- Check `Now`, `Agents`, `Search`, `Projects`, and `Setup`.
- Check `Now`, `Agents`, and `Settings`.
- Confirm the footer nav hides while scrolling down and shows while scrolling up.
- Deep-link to an agent and project detail route.
- Open a guarded project action preflight.
Expand Down
40 changes: 40 additions & 0 deletions lib/hq/remote_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ def route(service, method, path, body, request = nil)
return accepted(service.start_schedule_daemon(body)) if method == "POST" && parts == ["schedules", "daemon", "start"]
return accepted(service.stop_schedule_daemon) if method == "POST" && parts == ["schedules", "daemon", "stop"]
return accepted(service.restart_schedule_daemon(body)) if method == "POST" && parts == ["schedules", "daemon", "restart"]
return ok(service.archive_agents(body)) if method == "POST" && parts == ["agents", "archive"]
return ok(projects: service.projects) if method == "GET" && parts == ["projects"]
return ok(hidden: service.hidden_settings) if method == "GET" && parts == ["settings", "hidden"]
return ok(hidden: service.update_hidden_setting(body)) if %w[PATCH PUT].include?(method) && parts == ["settings", "hidden"]
Expand Down Expand Up @@ -1001,6 +1002,45 @@ def archive_agent(key)
}
end

def archive_agents(attrs)
payload = attrs || {}
keys = Array(payload["keys"]).map { |key| key.to_s.strip }.reject(&:empty?).uniq
raise Error.new("Missing agent keys") if keys.empty?

current = load_all_agents
agents_by_key = current.to_h { |agent| [agent.key, agent] }
archived = []
skipped = []
failed = []

keys.each do |key|
target = agents_by_key[key]
unless target
failed << { agent_key: key, error: "Agent not found" }
next
end

if target.running?
skipped << { agent_key: key, reason: "running" }
next
end

archived << { agent_key: target.key, archive_path: target.archive_logs! }
rescue StandardError => e
failed << { agent_key: key, error: e.message }
end

archived_keys = archived.map { |item| item.fetch(:agent_key) }
save_agents(current.reject { |agent| archived_keys.include?(agent.key) }) if archived_keys.any?

{
archived: archived,
skipped: skipped,
failed: failed,
archive_count: archived.length
}
end

private

def visible_projects
Expand Down
Loading
Loading