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: 1 addition & 1 deletion packages/the-framework.ai/pages/index/Queues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function Queues() {
(important) decisions with subtle pros and cons.
</P>
<P>
<span style={leadStyle}>It's your cockpit</span> — it keeps humans under control.
<span style={leadStyle}>It's your cockpit</span> — it keeps humans in control.
</P>
</QueueCard>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/the-framework/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ flowchart TD

**Spending limits.** The whole quota policy is one line: unattended work may spend up to the pro-rated share of the account's week that has elapsed, rising continuously with the clock. Nothing to configure — the week is read from the account itself. Two properties fall out: nothing is left on the floor (the boundary reaches the full allowance exactly as the week resets), and background work cannot starve the user (unattended work stands down past the boundary). A slider moves that stand-down line — but for work the user asked for, the slider only ever *loosens* the gate, and it is re-read live, so raising it unparks a waiting agent without a restart. The two gates fail in opposite directions on purpose: no readable quota means unattended work does not start, while user-requested work carries on. The gate is on *starting*, and only on starting: an agent already going is never interrupted to economise, because by then the tokens are spent, the work is half-done, and what is saved is the cheap part while what is lost is the expensive part.

**Surfaces.** The daemon serves the dashboard and answers all its reads from the files agents write. Non-local binds demand a shared token, because a daemon that spawns processes on a reachable port is remote code execution. For a saved remote device, the local daemon — never the browser — talks to the device's daemon and streams its events back over the local origin; the device's token is saved only in the user's own browser and handed to the local daemon per call. A shared link re-serves one agent's event stream read-only, from the same daemon that owns it. An agent can also run elsewhere: on a Claude cloud session (fire-and-forget: it opens its own PR), or on GitHub Actions (dispatch, poll, read back the uploaded transcript; continuity between turns is the branch the previous turn pushed) — with a browser extension inside the user's own claude.ai tab bridging cloud sessions back, so a question a cloud agent parks on becomes a dashboard card. An agent can launch a real Chrome that both it and a watching human attach to at once; when it hits a login wall, captcha, or 2FA it parks on a gate and hands the browser over — it never types a password. On Discord, notification watchers post agent activity and what needs a human; Discord is a way out, not a way in.
**Surfaces.** The daemon serves the dashboard and answers all its reads from the files agents write. Non-local binds demand a shared token, because a daemon that spawns processes on a reachable port is remote code execution. For a saved remote device, the local daemon — never the browser — talks to the device's daemon and streams its events back over the local origin; the device's token is saved only in the user's own browser and handed to the local daemon per call. An agent can also run elsewhere: on a Claude cloud session (fire-and-forget: it opens its own PR), or on GitHub Actions (dispatch, poll, read back the uploaded transcript; continuity between turns is the branch the previous turn pushed) — with a browser extension inside the user's own claude.ai tab bridging cloud sessions back, so a question a cloud agent parks on becomes a dashboard card. An agent can launch a real Chrome that both it and a watching human attach to at once; when it hits a login wall, captcha, or 2FA it parks on a gate and hands the browser over — it never types a password. On Discord, notification watchers post agent activity and what needs a human; Discord is a way out, not a way in.

**What lands in git.** One record of what happened: each agent's own event log, archived under a per-user directory keyed by the git identity, so cleaning the repo cannot erase the past and two people on one repo do not conflict. The daemon commits those archives after an idle window, only those paths, skipping while someone holds the index. Tickets — `tickets/<DATE>_<SLUG>.md`, the human-facing roadmap, with optional plan and claim siblings, parsed tolerantly. And the queue file plus a human-readable log of what The Framework did to the project.

Expand Down
2 changes: 1 addition & 1 deletion packages/the-framework/dashboard/App.SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The entire dashboard is this one page: it reads the selection from the address,
- The page owns what the views share: the agent list, the project list (each project carrying what the daemon currently finds wrong with it), project files, the cross-project needs-you queue, and the one live event stream the main view and right rail both read.
- A just-started session shows live before its record exists; with no id known yet, the page follows the output and adopts the running session once it surfaces.
- Live and finished agents are the same view — only the "live" flag flips when an agent ends.
- A shared watch link renders that one agent read-only; a daemon that stops answering gets a banner, so a dead backend never looks like a quiet agent.
- A daemon that stops answering gets a banner, so a dead backend never looks like a quiet agent.

## Rationales

Expand Down
24 changes: 6 additions & 18 deletions packages/the-framework/dashboard/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { TicketPlanPage } from './components/TicketPlanPage.js'
import { AgentView } from './components/AgentView.js'
import { agentLabel } from './lib/agent-label.js'
import { RightRail } from './components/RightRail.js'
import { RelayView } from './components/RelayView.js'
import { NotFound } from './components/NotFound.js'
import { useLiveEvents } from './lib/use-live-events.js'
import { useAgents } from './lib/use-agents.js'
Expand Down Expand Up @@ -92,7 +91,7 @@ export function App() {
}, [projectId])

// The selected project's files (git ls-files), handed to both the `#` picker and the tree.
// Empty when no project / on the relay (no checkout). Scoped to the selected session's
// Empty when no project (no checkout). Scoped to the selected session's
// worktree (#815), the same checkout the action bar's branch, Serve and open-folder act on;
// polled so a file the agent creates shows up rather than waiting for a reload.
const { value: files } = usePolled<string[]>(
Expand Down Expand Up @@ -244,7 +243,7 @@ export function App() {
}

// The live agent feed is owned here so both the main view and the right rail's views tab read
// one shared event stream. Hooks run before the relay early return below.
// one shared event stream.
// The agent whose feed and controls are in play is simply the one in the URL; in the no-id
// fallback there is none yet, and a null id resolves to the project root, as before.
const { events, lost } = useLiveEvents(projectId, agentId, agentStart.tick)
Expand All @@ -258,25 +257,14 @@ export function App() {
// from AgentView rather than being folded here: a finished agent's events live in its archived log,
// which that view is the one to read.

// On the relay (#426), the URL carries `?run=<id>` and there is no local registry or
// files — show that one agent read-only. Guarded on `window` so the module can be loaded
// without a browser at all, where it resolves to the full shell.
const relayAgent = typeof window === 'undefined' ? null : new URLSearchParams(window.location.search).get('run')

// Is an agent working (#875)? Drives the mark and the tab icon. Both off on the relay: there is
// no project registry behind it, so the cross-project read cannot answer, and RelayView owns
// both from its one agent's feed instead.
const local = relayAgent === null
const working = useWorking(local)
useFavicon(working, local)
// Is an agent working (#875)? Drives the mark and the tab icon.
const working = useWorking()
useFavicon(working)

// Whether the daemon answers at all (#948). Without this, a dead daemon froze every surface
// silently: the channels retry their transport without a verdict and the polls keep their
// last value, so "the agent went quiet" and "nothing on this page is live" looked identical.
const healthy = useDaemonHealth(local)

// Hooks above run unconditionally (rules of hooks); this early return is safe after them.
if (relayAgent) return <RelayView agentId={relayAgent} />
const healthy = useDaemonHealth()

// Route the main pane: the Overview dashboard when no project is selected (#471); else the
// project home/launcher, a running agent's live output, or a finished agent's replay. Each live
Expand Down
3 changes: 1 addition & 2 deletions packages/the-framework/dashboard/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,12 @@ The dashboard UI: a browser app served by the daemon that renders everything the
- The URL is the selection: the overview at `/`, a project at `/{projectId}`, one agent at `/{projectId}/{agentId}`, plus cross-project tickets, a per-ticket page and its plan page, and settings. An agent is a link you can paste, reload, and bookmark — there is no selection state to disagree with the address bar.
- An agent's events stream live over one channel bound to its own log; everything else polls. A finished agent reads from the archive instead, catching up whenever the live channel outgrew it.
- The dashboard is a plain client-side app: one static page the daemon serves for every address, and all the behaviour in the browser — no server rendering, no framework between the page and the app.
- Watch mode: opened against a shared link, the same app renders one agent read-only.

**The overview** is ordered by what governs what: the quota bar first (a week-track with pace and projection — the one figure that decides what agents may do next), then everything that needs *you* — the open-questions hub, every agent's unanswered question across all projects, answerable right there in one scrolling view — then the agents working now, the full AI queue of every project (uncollapsed: a plan you cannot read is not a plan), routine work, and the hottest tickets. An onboarding checklist sits on top until dismissed; each step's "done" is derived from a real fact (a registered project, a ticket on disk, a granted permission, stored credentials), so a step cannot be ticked by clicking it and work done outside the dashboard shows up ticked anyway.

**The composer** starts and steers agents. Typing a prompt starts an attended build; picking a preset starts an unattended one. In-editor triggers pull in presets and actions, files, projects, and macro tags; option menus write straight to the user's or project's preferences. Pre-flight checks warn before the agent is spent — a missing or logged-out GitHub CLI, a repo that can't auto-merge. On an agent, the composer is its control: a live one takes messages (options are baked at spawn and hidden), a stopped one offers to resume with reduced options, and the submit slot doubles as Stop while it works.

**The agent view** is a transcript with the controls inline: its questions render as answerable cards exactly where they happened (resolved ones collapse to a checkmark), and its live browser screencast renders inline too, degrading to a last still when the agent ends. Around the transcript: changed files with diffs, git status, the handoff panel (push, open PR, merge), agent-authored views, docs, and history rails, and an actions menu (stop, open in editor or on GitHub, remove worktree, delete it, copy a resume command, copy a shareable watch link).
**The agent view** is a transcript with the controls inline: its questions render as answerable cards exactly where they happened (resolved ones collapse to a checkmark), and its live browser screencast renders inline too, degrading to a last still when the agent ends. Around the transcript: changed files with diffs, git status, the handoff panel (push, open PR, merge), agent-authored views, docs, and history rails, and an actions menu (stop, open in editor or on GitHub, remove worktree, delete it, copy a resume command).

**Tickets** are the roadmap surface: a cross-project list with client-side faceted filtering (text, priority/effort/uncertainty buckets or ranges, topics, planning stage, project), sorting, and a group-by-project toggle — the whole view mirrored to the URL so it can be shared. Each ticket row leads with a start button that spins up an unattended agent implementing that one ticket, and shows whether a plan exists: a link to a page rendering the plan when it does, a button that starts an agent to write one when it doesn't. Queueing a ticket into the AI queue happens from the ticket's own page.

Expand Down
9 changes: 4 additions & 5 deletions packages/the-framework/dashboard/components/AgentFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import { EventList } from './EventList.js'
import { AgentOverview } from './AgentOverview.js'

// One agent's feed: the agent overview plus the live/replayed event log, or a waiting placeholder
// before anything has streamed. Shared by the agent's own view (AgentView, which shows the session
// link in its action bar instead — `showSessionLink={false}`) and the read-only relay watch view
// (RelayView, which keeps it since it has no action bar). `lost` is the live channel's health
// (#948): while the stream is down the feed is behind reality, and saying so beats letting
// "the agent went quiet" and "the connection died" look identical.
// before anything has streamed. Rendered by the agent's own view (AgentView, which shows the
// session link in its action bar instead — `showSessionLink={false}`). `lost` is the live
// channel's health (#948): while the stream is down the feed is behind reality, and saying so
// beats letting "the agent went quiet" and "the connection died" look identical.
export function AgentFeed({
events,
showSessionLink = true,
Expand Down
5 changes: 0 additions & 5 deletions packages/the-framework/dashboard/components/RelayView.SPEC.md

This file was deleted.

40 changes: 0 additions & 40 deletions packages/the-framework/dashboard/components/RelayView.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion packages/the-framework/dashboard/components/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The dashboard's React component catalog: every page, panel and control the brows

## Flows

- One shared shell frames every route: the left sidebar (brand, New launcher, Overview / Tickets / Projects navigation, recent agents, utility footer) and a right rail of agent-pushed views, surfaced docs and project history. Its pages are the Overview board, the project home/launcher, one agent's view, the cross-project tickets pages (list, per-ticket detail, per-ticket plan), Settings, a read-only shared watch view, and not-found.
- One shared shell frames every route: the left sidebar (brand, New launcher, Overview / Tickets / Projects navigation, recent agents, utility footer) and a right rail of agent-pushed views, surfaced docs and project history. Its pages are the Overview board, the project home/launcher, one agent's view, the cross-project tickets pages (list, per-ticket detail, per-ticket plan), Settings, and not-found.
- The agent surface is a transcript with its controls inline: an action bar carrying the branch / PR / handoff and the one menu of agent actions, the event feed rendering its questions as answerable cards and its browser screencast in place, the changes and handoff panels, and one composer that starts, steers, stops and resumes — in a stable frame, so an ending never blanks what you are reading.
- The Overview's widgets each show one slice of what the daemon knows: quota pace, agents working now, the Human Queue, the AI queue, routine work, hot tickets, activity and outcomes, and an onboarding checklist whose steps tick off real facts rather than clicks.
- The launcher's controls — presets, driver/model and option menus, the Context selector, the system-prompt preview — read and write the same preferences and mappings the agent itself uses, so no surface can disagree with the agent it configures.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The settings page: every setting in one findable place — appearance, driver an

## Rationales

- Every list here is static today, so the guard against empty dropdowns is for the next dynamic one.
- The editor dropdown is assembled at run time from the editors detected on the machine; the other lists are static, and the guard against empty dropdowns covers both.

## Before modifying/creating SPEC.md files

Expand Down
2 changes: 1 addition & 1 deletion packages/the-framework/dashboard/lib/preferences.SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The dashboard's settings: your own options with the open project's committed set
- A change shows instantly and saves in the background; the write sends only the keys it changed and adopts the daemon's merged answer, so a stale tab can neither revert other people's changes nor keep showing them wrong.
- Returning to the tab re-reads both tiers, making edits from another tab or from the repo's settings file on disk visible.
- Each control can also see which tier won its value — a repo-inherited value shows as not yours — and a project's shared, repo-committed custom presets ride along.
- The theme choice and the notification toggles read through here, with their defaults defined framework-side so daemon and dashboard cannot drift.
- The theme choice and the notification toggles read through here; the notification defaults are defined framework-side so daemon and dashboard cannot drift, while the theme's default lives here — absent means follow the OS.

## Rationales

Expand Down
Loading
Loading