Bundles the working-tree state into one coherent commit covering the
upgrade-safety glue that complements today's earlier landings
(orphan-recovery, sf-db single-connection, drain-timer-not-unref'd,
forceShutdown drain, shutdown-state.ts, instrumentation.ts,
shutdown-signal.js, gate-deadlock-classifier).
Modified:
docker/Dockerfile.source-server — image build tweaks for the source-
server variant used by the in-container upgrader.
docker/docker-compose.vega.yaml — env passthroughs for host-side dirs
(SF_SOURCE_HOST_ROOT, SF_WORKSPACE_HOST_DIR, SF_WORKSPACES_HOST_DIR,
SF_HOME_HOST_DIR), docker socket mount, group_add for docker GID,
and SF_RPC_SHUTDOWN_GRACE_MS=600000 matching the 10-min drain.
scripts/run-vega-source-server.mjs — substantial rework supporting
the in-container upgrade flow.
scripts/upgrade-vega-source-server.mjs — buildEnv() + dockerBuildEnv()
helpers, probeBind via SF_VEGA_PROBE_HOST, containerExists()
pre-check before drainContainer, stop timeout now matches the
10-min RPC grace via SF_VEGA_DRAIN_STOP_TIME (default 610s).
src/web/project-discovery-service.ts — calls
recoverProjectRuntimeQueues() on each of the 3 discovery paths
(root monorepo, per-entry, nested SF projects). Closes the
cloud-volume mtime-lag window codex flagged.
web/app/api/ready/route.ts — calls recoverProjectRuntimeQueues() on
every readiness probe, and now also reads shutdown-state so the
probe returns 503 while draining.
web/components/sf/projects-view.tsx — UI wiring for the upgrade
trigger.
web/pages/api/projects.ts — backend API addition for the project
enumeration that feeds projects-view.
docs/specs/sf-self-deploy.md — docs update for the new flow.
package.json — script alias.
Added:
scripts/build-web-host.mjs — new build helper for the standalone web
host artifact consumed by the upgrade flow.
src/resources/extensions/sf/tests/auto-shutdown-signal.test.mjs —
unit test for the cooperative-shutdown signal module (registers /
requests / snapshot).
src/web/project-runtime-recovery.ts — thin wrapper around
recoverOrphanedFeedbackDrains for per-project use from web routes.
web/app/api/drain/route.ts — explicit drain endpoint for operator-
triggered queue flush.
web/app/api/server-upgrade/route.ts — auth-gated endpoint that
spawns the in-container upgrader via docker socket; passes through
host-dir env so the upgrader knows real bind-mount paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| app | ||
| components | ||
| hooks | ||
| lib | ||
| pages | ||
| public | ||
| styles | ||
| .gitignore | ||
| components.json | ||
| eslint.config.mjs | ||
| instrumentation.ts | ||
| next-env.d.ts | ||
| next.config.mjs | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| proxy.ts | ||
| README.md | ||
| tsconfig.json | ||
sf server
Next.js 15 (App Router) frontend for sf server. Ships as a standalone bundle
baked into the sf release; can also be run from source for development.
What this is
The web UI is a browser workspace for sf. It connects to a bridge service
(src/web/bridge-service.ts) that manages an sf subprocess per project CWD and
proxies RPC commands over stdio. The page is a single-page app: no server-side
rendering, client-only via dynamic(..., { ssr: false }).
How to run
Packaged (normal use)
sf server # launches Next.js standalone server and opens browser
sf server --port 3000 # pick a specific port
Source dev mode (requires the repo checked out)
npm --prefix web run dev
The dev server needs these env vars (set automatically by sf server; set
manually for source dev):
| Variable | Description |
|---|---|
SF_WEB_AUTH_TOKEN |
Bearer token for all API requests |
SF_WEB_PROJECT_CWD |
Absolute path of the project being served |
SF_WEB_HOST |
Host to bind (default 127.0.0.1) |
SF_WEB_PORT |
Port to bind |
Auth
On first page load the client reads the bearer token from the URL fragment
(#token=…), stores it in localStorage under sf-auth-token, and strips the
fragment from the URL.
All subsequent requests attach it:
- Fetch / API routes —
Authorization: Bearer <token>header (viaauthFetch/authHeadersinweb/lib/auth.ts). - SSE routes —
?_token=<token>query parameter (EventSource doesn't support custom headers).
Architecture
Browser
└─ page.tsx (dynamic, ssr:false)
└─ SFAppShell
├─ WorkspaceChrome — layout chrome, sidebar, status bar
│ └─ 7 views (see below)
└─ CommandSurface — slash-command palette
Next.js API routes (web/app/api/**/route.ts)
└─ delegate to *-service.ts files in src/web/
└─ bridge-service.ts — per-CWD singleton sf subprocess (RPC over stdio)
bridge-service.ts spawns sf as a child process, speaks JSON-RPC over stdio,
and multiplexes all API routes onto that single bridge. Auth is enforced before
requests reach the bridge via requireProjectCwd() (which validates the token
and resolves the CWD from SF_WEB_PROJECT_CWD).
The 7 views
| View key | Component | Purpose |
|---|---|---|
dashboard |
Dashboard |
Live project status, metrics, quick-start panel |
chat |
ChatMode |
Conversational agent interface |
power |
DualTerminal |
Full-screen split terminal (agent + shell) |
roadmap |
Roadmap |
Milestone and slice plan explorer |
files |
FilesView |
Project file browser with syntax highlighting |
activity |
ActivityView |
Event log and session history |
visualize |
VisualizerView |
Dependency graph and architecture visualizer |
Adding a new API route
- Create
web/app/api/<name>/route.tsthat callsrequireProjectCwd(request)for auth/CWD resolution, then delegates to a service:
// web/app/api/my-feature/route.ts
import { requireProjectCwd } from "../../../../src/web/bridge-service.ts";
import { collectMyFeatureData } from "../../../../src/web/my-feature-service.ts";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
export async function GET(request: Request): Promise<Response> {
const projectCwd = requireProjectCwd(request);
const data = await collectMyFeatureData(projectCwd);
return Response.json(data, { headers: { "Cache-Control": "no-store" } });
}
- Implement
src/web/my-feature-service.tswith the actual logic (may call the bridge or read disk directly).
Tests
Tests for web utilities live in web/lib/__tests__/ and run via Vitest:
npx vitest run web/lib --config vitest.config.ts
Note: co-located
*.test.tsfiles insideweb/outside of__tests__/subdirectories are silently skipped by the root Vitest config. Always place web tests underweb/lib/__tests__/.