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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ artifacts/design-system/**/*.jpg
artifacts/design-system/**/*.jpeg
artifacts/design-system/**/*.gif
artifacts/design-system/**/*.webp
docs/reports/usage-*.md
docs/reports/usage-*.query.json
1,701 changes: 1,686 additions & 15 deletions bin/remote-ui-smoke

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions docs/PROJECT_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ Key references:
| Agent attachments | Structured agent results can include PR/document/image attachments, persisted in `.attachments.json` and mirrored into `memory.jsonl`, surfaced from chat with a `ctrl+a` navigable list | Durable links to artifacts survive later runs and memory rebuilds instead of living only in a single assistant message |
| Remote artifact rendering | Render attached HTML in an origin-isolated iframe with a restrictive content policy and package explicitly referenced, allowlisted workspace web assets; render sanitized Markdown Mermaid fences with a pinned, conditional CDN loader in strict mode | Interactive lessons and shared course assets remain usable without granting generated HTML access to Tycho or browser storage, and ordinary Markdown does not pay the Mermaid download cost |
| Pull request review | Agent-scoped PR diff inspection remains available; the cross-agent Review Inbox is paused because its eager aggregation is too slow and unresponsive | Redesign inbox discovery and loading around bounded, incremental work before restoring its route; retain GitHub App and `gh` compatibility |
| Agent pull request catalog | Persist canonical PR references and origin title/status metadata in an agent-owned `<agent-stem>.pull_request_catalog.json` sidecar; keep ordinary agent PR listing network-free | Opening one agent reads only that agent's catalog; displayed titles and Open/Draft/Closed/Merged state come from cached GitHub metadata, refreshes remain explicit, and archiving moves the catalog with the agent |
| Agent pull request switching | Paint the route shell before asynchronously attaching its diff, give foreground navigation a reserved request slot, cancel stale background work outside the open PR route, preload at most six saved snapshots, retain at most twelve payloads plus six visited viewers and per-PR scroll positions, and render lines in 100-line containment chunks | Persistent snapshots remain authoritative; navigation never shares a frame with diff layout, while immutable parsed-store reuse and bounded browser caches preserve each PR's form, selection, and desktop/mobile scroll state |
| Managed-agent completion status | Let a validated structured result define Success, Partial, or Failed after a run; retain the process exit code as transport diagnostics | A usable structured result must not remain labeled Failed solely because its harness process exited nonzero, while missing or explicitly failed results still surface failure |
| Conversation block scrolling | Initial chat load bottom-aligns the latest block when it fits, oversized blocks start at row 1, and navigation scrolls only enough to reveal the selected block | The selected label/cursor must remain visible and predictable while keeping surrounding recent context on first open |
| Conversation viewport offsets | Block `line_offset` / `line_height` are derived from the final rendered rows; long unbroken preview tokens are hard-wrapped before entering the viewport, and footer debug is computed after viewport sync | Bubbles `Viewport` counts newline-separated lines, while terminals visually wrap long tokens; stale or mismatched offsets cause misleading `visible 0/0` debug and cropped selected blocks |
| Inquiry submission | Gated review step inside a rounded box | Prevents accidental structured submissions |
Expand Down Expand Up @@ -350,6 +353,7 @@ verify the bottle.
- [x] Dedicated mobile structured inquiry submission UI
- [x] Full-screen inquiry editor with trailing unstructured Leave feedback field
- [x] Poll-safe inline and full-screen Conversation/inquiry forms that remain attached and focused during shell refreshes
- [x] Focused Summary, Attachment, PR Diff, and agent-backed project Diff routes show that conversation polling is paused, poll only the lightweight resource catalog, preserve their workspace DOM, and reconcile status/lifecycle controls in place; normal route polling resumes on Conversation and top-level live views
- [x] Attachment detail context menus with exclusive Balanced/Widen/Full layouts, content/path copy, and forced cache refresh
- [x] Responsive desktop/mobile shell with persistent, header-aligned in-shell desktop navigation across top-level and detail routes, consistently named New agent action, shared control sizing, and fixed-region safe-area handling
- [x] Sticky Settings section navigator over one continuous page and copyable native session ID in Conversation Settings
Expand Down
6 changes: 6 additions & 0 deletions docs/PULL_REQUEST_DIFFS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ Tycho prefers a Tycho GitHub App user session obtained through OAuth device flow

Review posting remains off by default. Operators must also set `TYCHO_GITHUB_WRITE_ENABLED=true`, save a draft bound to the current base and head, and confirm the mutation. GitHub remains the final permission authority; a token without `Pull requests: write` receives a sanitized failure.

Agent-scoped PR discovery persists canonical references and compact metadata in an agent-owned `~/.tycho/logs/agents/<agent-stem>.pull_request_catalog.json` sidecar. Opening an agent's PR list reads only that agent's catalog and does not issue one GitHub request per PR. The cached GitHub title replaces attachment-supplied display text, and the list/detail surfaces show Open, Draft, Closed, or Merged state. Existing saved snapshots seed missing catalog metadata without another patch fetch. **Refresh metadata** updates the catalog explicitly; fetching one or all diffs remains a separate patch operation. Archiving an agent moves its catalog, backup, and lock sidecars with its other logs.

The Remote UI preloads up to six saved diff snapshots for the open agent, cancels stale queued preloads when that route changes, retains at most twelve snapshot payloads, and keeps up to six visited diff viewers and scroll positions in bounded LRU caches. Foreground navigation has priority and one reserved request slot, so a click cannot wait behind background preloads; an explicit refresh that meets an in-flight preload follows it with the required refresh request. Single and bulk refresh results use the same bounded payload cache and protect the selected PR. Route changes paint the PR shell and loading state first, then attach even an already-cached diff on a later animation frame, keeping diff layout out of the navigation frame. Diff lines render in 100-line containment chunks so off-screen code does not force full-page layout. The persistent snapshot store remains authoritative, while the server reuses an immutable parsed document until the snapshot file's inode, size, or nanosecond mtime changes. Switching among visited PRs therefore avoids both JSON reparsing and rebuilding thousands of diff-line DOM controls; first visits use the preloaded snapshot and bounded layout.

Automatic Remote UI polling does not fetch or rebuild the conversation behind a focused PR Diff. It still reconciles lightweight agent status and lifecycle controls from the resource catalog; the cross-route policy is recorded in `docs/PROJECT_STATUS.md`.

## Workflow

```mermaid
Expand Down
3 changes: 2 additions & 1 deletion docs/REMOTE_SERVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,8 @@ Conversation entries are projected from `AgentChatLog#chat_blocks` when availabl
| `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. |
| `GET` | `/agents/{key}/pull-requests` | List GitHub pull request links detected from one agent's attachments with snapshot freshness metadata. |
| `GET` | `/agents/{key}/pull-requests` | List GitHub pull requests from that agent's persistent local catalog, including cached origin title and status, without waiting on GitHub. |
| `POST` | `/agents/{key}/pull-requests/metadata/refresh` | Explicitly refresh GitHub metadata for the agent's cataloged pull requests without fetching patches. |
| `GET` | `/agents/{key}/pull-requests/{id}/diff` | Read one saved pull request diff snapshot. |
| `POST` | `/agents/{key}/pull-requests/{id}/refresh` | Fetch current PR metadata and patch content, then save a fresh diff snapshot. |
| `POST` | `/agents/{key}/pull-requests/refresh` | Refresh every detected pull request diff for one agent. |
Expand Down
57 changes: 0 additions & 57 deletions docs/reports/usage-2026-07-06-to-2026-08-06.md

This file was deleted.

28 changes: 0 additions & 28 deletions docs/reports/usage-2026-07-06-to-2026-08-06.query.json

This file was deleted.

15 changes: 14 additions & 1 deletion lib/hq/domain/managed_agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -597,9 +597,14 @@ def status
return "blocked" if blocked?
return "idle" if @started_at.nil? && last_run.nil?
return "idle" if @last_exit_code.nil?
return "succeeded" if @last_exit_code.zero?
return "stopped" if stopped_exit_code?

structured_status = @structured_result&.dig("status").to_s.strip
return "succeeded" if %w[success succeeded no_action_needed].include?(structured_status)
return "partial" if structured_status == "partial"
return "failed" if structured_status == "failed"
return "succeeded" if @last_exit_code.zero?

"failed"
end

Expand Down Expand Up @@ -627,6 +632,10 @@ def attachments_path
derived_log_path("attachments.json")
end

def pull_request_catalog_path
derived_log_path("pull_request_catalog.json")
end

def invalidate_derived_logs!
[conversation_log_path, system_log_path].each do |path|
FileUtils.rm_f(path)
Expand All @@ -640,6 +649,9 @@ def log_files
system_log_path,
memory_path,
attachments_path,
pull_request_catalog_path,
"#{pull_request_catalog_path}.bak",
"#{pull_request_catalog_path}.lock",
invalid_structured_output_file_path,
status_file_path,
last_message_file_path,
Expand Down Expand Up @@ -1126,6 +1138,7 @@ def external_process_environment(environment)
{
"BUNDLE_BIN_PATH" => nil,
"BUNDLE_GEMFILE" => nil,
"BUNDLER_SETUP" => nil,
"BUNDLER_VERSION" => nil,
"GEM_HOME" => nil,
"GEM_PATH" => nil,
Expand Down
Loading
Loading