Skip to content

feat: wire the core workflows — daemon status, orchestrator attach, git rail, lifecycle#178

Open
ashish921998 wants to merge 6 commits into
mainfrom
feat/wire-core-workflows
Open

feat: wire the core workflows — daemon status, orchestrator attach, git rail, lifecycle#178
ashish921998 wants to merge 6 commits into
mainfrom
feat/wire-core-workflows

Conversation

@ashish921998

@ashish921998 ashish921998 commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

What

Closes the four core-workflow gaps found by driving the running app end to end:

1. Daemon status pill tells the truth

The Electron supervisor only knew about daemons it spawned; in dev (external ao start) the pill read "daemon stopped" forever while sessions were visibly served. The main process now discovers an external daemon by polling the running.json handshake and confirming /healthz identity (guarded against foreign processes squatting on a stale port), dedupes status broadcasts, and auto-starts supervision at launch when AO_DAEMON_COMMAND is configured.

2. Orchestrator view attaches a real terminal

The orchestrator pane was a placeholder ("No session selected"). Orchestrator sessions are now split out of the worker list, the view attaches the selected project's orchestrator PTY through the existing mux machinery, and a Start-orchestrator empty state spawns one via POST /orchestrators.

3. Git review rail works end to end

New backend surface (port → git-CLI adapter → session service → routes, spec + TS types regenerated):

  • GET /sessions/{id}/git — branch + changed files (adds/dels, staged)
  • POST /sessions/{id}/git/stage|discard|commit — stage all, discard all, commit-and-push with typed GIT_NOTHING_TO_COMMIT / GIT_NO_REMOTE errors
  • Session read model now carries the worktree branch (branch field)

The rail shows live files, wires Stage all / armed Discard all / Commit & Push, and explicitly disables Create PR (the PR action lane is still a stub — out of scope here).

4. Worker/project lifecycle from the sidebar

Right-click context menus: kill (armed confirm) / restore on workers; new worker, clean up finished workers, remove project (armed confirm) on projects — all wired to the existing daemon routes with inline error reporting.

Verified

  • npm run lint (backend tests + golangci) green; new gitops integration tests run real git repos
  • Frontend: 104 vitest tests green (new daemon-discovery/status cases), typecheck clean
  • Live E2E on the running app: external-daemon pill shows ready; orchestrator spawned + terminal attached and survived a daemon and app restart; spawned a worker on a throwaway repo with a local bare origin, edited its worktree, staged, committed and pushed from the rail, and verified the commit landed on the origin branch; discard-all, kill/restore, cleanup and remove-project exercised in the UI

Notes

  • Restore of a session with nothing to resume surfaces as a 500 (nothing to resume from) — pre-existing; should become a typed conflict (follow-up)
  • Spawn modal's worktree path preview (~/.rc/wt/…) doesn't match the real managed root — follow-up

🤖 Generated with Claude Code

Update 2026-06-11 — merged main's renderer redesign

main landed the agent-orchestrator-clone shell (e493de6, per the explicit
design decision recorded in DESIGN.md on 2026-06-10), which deleted/rewrote the
old emdash-style renderer this PR had wired. The merge keeps main's renderer
wholesale
and this PR's backend + Electron-main work:

  • Survives: WorkspaceGit port + gitworktree adapter, GET/POST /sessions/{id}/git[...] routes, branch on the Session read model,
    orchestrator spawn route, external-daemon discovery in Electron main
    (running.json + /healthz), regenerated OpenAPI/schema.ts combining the git +
    review routes.
  • Superseded (files deleted upstream): the old-shell UI wiring — daemon
    status pill in the sidebar footer, orchestrator attach in CenterPane, the git
    review rail (SideRail), and the sidebar right-click lifecycle menus. The new
    shell has no equivalents yet; re-implementing those four surfaces on the new
    Sidebar/SessionView is follow-up work.

ashish921998 and others added 2 commits June 11, 2026 16:18
…it rail, lifecycle

Four core-workflow gaps closed end to end:

Daemon status (Electron main):
- Discover an externally-started daemon by polling running.json and
  confirming /healthz identity, so the status pill reflects the daemon
  actually serving the renderer instead of reporting "stopped" forever.
- Auto-start supervision at launch when AO_DAEMON_COMMAND is set; dedupe
  status broadcasts.

Orchestrator (frontend):
- Split orchestrator sessions out of the worker list; the orchestrator
  view attaches the selected project's orchestrator terminal and offers
  a Start-orchestrator empty state wired to POST /orchestrators.

Git review rail (backend + frontend):
- New WorkspaceGit port + git-CLI adapter (status/stage/discard/commit+push)
  with integration tests against real repos.
- Session service + routes: GET /sessions/{id}/git, POST git/stage,
  git/discard, git/commit, with typed GIT_NOTHING_TO_COMMIT / GIT_NO_REMOTE
  errors; OpenAPI + TS types regenerated.
- Session read model now carries the worktree branch.
- GitRail shows live changed files, stage-all, armed discard-all,
  Commit & Push; Create PR is explicitly disabled until the PR lane exists.

Lifecycle (frontend):
- Right-click context menus: kill/restore workers, new-worker/cleanup/
  remove-project with two-step destructive confirm and inline errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR wires four core workflows onto the existing backend: external-daemon discovery via running.json + /healthz polling in the Electron main process, a complete git rail backend (GET/POST /sessions/{id}/git[/stage|discard|commit]) with a CLI adapter and integration tests, the branch field on the session read model, and a context-menu component for sidebar lifecycle actions — while keeping main's renderer wholesale after the merge of the agent-orchestrator-clone shell.

  • External daemon discovery (main.ts): startExternalDaemonObserver polls the handshake file and confirms identity via /healthz, deduplicates status broadcasts with daemonStatusEquals, and gates supervisor auto-start on AO_DAEMON_COMMAND being set.
  • Git rail backend (gitops.go, service/session/git.go, controllers/sessions.go): New WorkspaceGit port backed by the git CLI; four HTTP routes cleanly separate the adapter, service-layer sentinel mapping, and controller wire format; push-leg failures now return 200 with a PushError warning so a committed SHA is never silently lost.
  • Context menu (context-menu.tsx): Lightweight armed-confirm component with a 3-second decay timer, capture-phase Escape handling, and inline error reporting; the WorkspaceSummary.orchestrator optional field is added to the workspace type as a forward declaration for the planned orchestrator-split work.

Confidence Score: 5/5

Safe to merge; no regressions in the new backend surface or Electron main changes, and both previously flagged correctness issues are resolved in this diff.

The git-rail backend is complete end-to-end with integration tests covering the commit+push happy path, discard cleanup, and the push-only failure case (SHA preserved in a 200 response). The controller fix for the previously identified commit-succeeds-but-caller-sees-error issue is in place and covered by a dedicated test. The external-daemon observer in Electron main is guarded against reentrancy, supervised-daemon interference, and foreign-process squatting. The context-menu armed-confirm now decays after 3 s. The only observation is a documentation mismatch on the forward-declared orchestrator type field, which is intentional follow-up scaffolding.

No files require special attention. The frontend/src/renderer/types/workspace.ts orchestrator field is a forward declaration for follow-up work and is intentionally unpopulated.

Important Files Changed

Filename Overview
backend/internal/adapters/workspace/gitworktree/gitops.go New WorkspaceGit adapter over the git CLI; Status/StageAll/DiscardAll/CommitAll wired correctly, gitignored-files behavior explicitly documented, rename/copy NUL-separator parsing looks sound, and line-count fallbacks degrade to 0/0 without failing the whole call.
backend/internal/adapters/workspace/gitworktree/gitops_integration_test.go Integration tests cover the full commit+push happy path, discard-all cleanup, and no-remote push failure with SHA preservation; all run against a real git process using a temporary directory.
backend/internal/httpd/controllers/sessions.go Four new git route handlers; the commit handler correctly returns 200+SHA when push fails (result.SHA != ""), addressing the previously flagged issue where a push failure would silently swallow the committed SHA.
backend/internal/service/session/git.go Thin orchestration layer resolves session→workspace path and maps adapter sentinels to typed API errors; ErrGitNoRemote is correctly matched even through fmt.Errorf wrapping via errors.Is.
frontend/src/main.ts External-daemon observer polls the handshake file on a 2.5s interval, confirms identity via isDaemonHealthz, guards against overwriting a supervised-daemon's status, and deduplicates broadcasts with daemonStatusEquals.
frontend/src/renderer/components/ui/context-menu.tsx New armed-confirm context menu with a 3-second decay timer (addresses the previously flagged indefinite-armed issue), capture-phase Escape handling, busy spinner, and inline error rendering.
frontend/src/renderer/hooks/useSessionGit.ts Git-rail hook with 5s polling, unified mutation guard, and correct handling of the push-warning path (pushError surfaces as actionError without throwing, leaving the file list cleanly empty after refetch).
frontend/src/renderer/types/workspace.ts Adds orchestrator?: WorkspaceSession and updates the sessions comment to 'Worker sessions only', but fetchWorkspaces still includes orchestrators in sessions and never populates orchestrator — the type is a forward declaration for follow-up work, making the comment currently misleading.
frontend/src/shared/daemon-discovery.ts Adds isDaemonHealthz to guard against foreign processes squatting on a stale run-file port; validates both service name and status fields.
frontend/src/shared/daemon-status.ts Adds daemonStatusEquals for value equality comparison used to suppress no-op status broadcasts.

Sequence Diagram

sequenceDiagram
    participant Main as Electron main.ts
    participant RunFile as running.json
    participant Daemon as ao daemon (/healthz)
    participant Renderer as Renderer process
    participant GitAPI as GET/POST /sessions/{id}/git[/*]
    participant GitOps as GitOps adapter
    participant GitCLI as git CLI

    Note over Main: App launch
    alt AO_DAEMON_COMMAND set
        Main->>Daemon: spawn (startDaemon)
    end
    loop every 2.5s (no daemonProcess)
        Main->>RunFile: readFile running.json
        RunFile-->>Main: port info
        Main->>Daemon: GET /healthz (timeout 1.5s)
        Daemon-->>Main: "{service, status}"
        Main->>Main: isDaemonHealthz check
        Main->>Renderer: daemon:status (deduped via daemonStatusEquals)
    end

    Note over Renderer,GitCLI: Git Rail workflow
    Renderer->>GitAPI: "GET /sessions/{id}/git"
    GitAPI->>GitOps: Status(ctx, workspacePath)
    GitOps->>GitCLI: git rev-parse --abbrev-ref HEAD
    GitOps->>GitCLI: git status --porcelain -z
    GitOps->>GitCLI: git diff --numstat HEAD
    GitCLI-->>GitOps: branch + file list + line counts
    GitOps-->>GitAPI: "GitStatus{branch, files}"
    GitAPI-->>Renderer: SessionGitStatusResponse

    Renderer->>GitAPI: "POST /sessions/{id}/git/commit {message, push:true}"
    GitAPI->>GitOps: CommitAll(ctx, path, message, push)
    GitOps->>GitCLI: git add -A
    GitOps->>GitCLI: git commit -m message
    GitOps->>GitCLI: git push --set-upstream origin HEAD
    alt push succeeds
        GitOps-->>GitAPI: "GitCommitResult{SHA, branch, pushed:true}"
        GitAPI-->>Renderer: "200 GitCommitResponse{sha, pushed:true}"
    else push fails (no remote / rejected)
        GitOps-->>GitAPI: "(result{SHA}, ErrGitNoRemote)"
        GitAPI-->>Renderer: "200 GitCommitResponse{sha, pushed:false, pushError}"
    end
Loading

Reviews (4): Last reviewed commit: "fix(git): keep the commit SHA when the p..." | Re-trigger Greptile

Comment thread frontend/src/renderer/components/ui/context-menu.tsx
Comment thread backend/internal/adapters/workspace/gitworktree/gitops.go
Comment on lines +78 to +88
const commitAndPush = useCallback(
(message: string, description: string) =>
runAction(async () => {
const fullMessage = description.trim() ? `${message.trim()}\n\n${description.trim()}` : message.trim();
const { error } = await apiClient.POST("/api/v1/sessions/{sessionId}/git/commit", {
params: { path: { sessionId: sessionId ?? "" } },
body: { message: fullMessage, push: true },
});
if (error) throw new Error(apiErrorMessage(error, "Could not commit"));
}),
[runAction, sessionId],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 push is unconditionally true; no commit-only path exists in the UI

commitAndPush always sends body: { message: fullMessage, push: true }. For a session whose worktree has no remote (local-only repo), the operation will always commit successfully but return GIT_NO_REMOTE, which — per the controller issue above — surfaces as an error even though the commit landed. Even once that controller issue is fixed, there is currently no way for a user to commit without pushing. The API supports push: false, so wiring an option here (or at least exposing a separate "Commit only" path) would let the rail handle remoteless repos cleanly.

# Conflicts:
#	backend/internal/httpd/apispec/openapi.yaml
#	frontend/src/api/schema.ts
#	frontend/src/renderer/App.test.tsx
#	frontend/src/renderer/App.tsx
#	frontend/src/renderer/components/CenterPane.tsx
#	frontend/src/renderer/components/SideRail.tsx
#	frontend/src/renderer/components/Sidebar.tsx
#	frontend/src/renderer/hooks/useWorkspaceQuery.ts
…cardAll's -fd choice

Greptile flagged that an armed destructive item ("Confirm remove") stayed
armed until the menu closed, so a much-later mistaken second click could
fire it. The armed state now decays back to the resting label after 3s.

DiscardAll keeping gitignored files is intentional (discard means "drop my
changes", not "wipe node_modules and the AO session files") — now said in
the doc comment instead of being implicit in the -fd flag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread backend/internal/httpd/controllers/sessions.go
ashish921998 and others added 2 commits June 12, 2026 10:17
…sizing it

The Session read model exposes `branch` (from session metadata), but
useWorkspaceQuery clobbered it with a synthesized `session/<id>`, so workers
on custom branches were mislabeled everywhere the branch is shown (Topbar,
SessionInspector, board cards, PR rows, terminal prompt). Prefer the real
branch and keep the synthesized name only as a fallback for sessions without
branch metadata yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CommitAll resolves the SHA before it ever attempts to push, so a push-only
failure (GIT_NO_REMOTE or a rejected push) returns a non-empty SHA alongside
the error — the commit has already landed. The commit controller discarded the
result and forwarded the error, so the caller got a 409 with no SHA and the
committed work was invisible until the user checked the branch by hand.

Now a SHA-bearing error returns 200 GitCommitResponse{pushed:false, pushError}
so the commit is never lost; genuine pre-commit failures (nothing-to-commit,
no-workspace) still error as before. useSessionGit surfaces pushError as a
non-fatal warning instead of dropping it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant