feat(sessions): Sessions module + per-runner hashed pairing & targeted dispatch (M3)#25
Open
hoangsnowy wants to merge 8 commits into
Open
feat(sessions): Sessions module + per-runner hashed pairing & targeted dispatch (M3)#25hoangsnowy wants to merge 8 commits into
hoangsnowy wants to merge 8 commits into
Conversation
…geted dispatch (M3) Adds AgentOs.Modules.Sessions (schema `sessions`): RemoteSession (member x workspace) and Runner (a member's standing paired dev machine) aggregates, a tenant-filtered DbContext, an EF migration, repositories with no-op fallbacks, and /sessions + /runners REST endpoints. Rewires the RemoteAgent remote-execution path to close two trust-boundary gaps: - Pairing is now per-runner. The single shared static RemoteAgent:PairingToken is replaced by a high-entropy token per runner, stored only as a salted SHA-256 hash and verified in constant time (RunnerPairingService, Domain). The hub authenticates a connecting runner by id + token before trusting its tenant/owner. - Dispatch is now targeted, not broadcast. The broker resolves a request to exactly one runner connection scoped by (tenant, member); a tenant-A request can never reach a tenant-B runner. Replaces hub Clients.All with Clients.Client(connectionId). The standalone runner now pairs with REMOTE_AGENT_ID + REMOTE_AGENT_TOKEN. Sessions + RemoteAgent are wired into the Api host only (not Web). Full suite 318 passed / 5 skipped / 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ers/Sessions app (M3 UI) Wires the Workspaces + Sessions modules into the Web host and adds a "Spine" desktop app (AppCatalog + WindowHost) with three panes: connected Workspaces (list), Runners (register -> one-time pairing token, list, revoke), and Sessions (create, list, close). A Blazor Server circuit has no HttpContext, so ITenantContext is blank in a component; the app reads tenant + user from AuthenticationState and calls new tenant-explicit repository overloads (ListForTenantAsync / AddForTenantAsync / *ForTenantAsync) that bypass the ambient query filter and scope by the passed tenant id. Also adds a CLAUDE.md "Verification" convention: a user-facing change is not done until it is wired into the desktop and exercised in the running app, not unit-tests-only. Builds on the M3 model + hub rewire in this PR. Full suite 318 passed / 5 skipped / 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds SpineAppTests (open the app -> three panes render; register a runner -> the one-time REMOTE_AGENT_ID/TOKEN pairing panel appears) and a "Spine" row to the desktop-icon theory. Same gate as the other UI E2E: skipped unless RUN_AGENTOS_E2E=true with the Web at AGENTOS_URL. The register flow needs no DB -- the token is minted by the real IRunnerPairingService. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ifiable M3 UI) The AgentOS desktop is globally [Authorize], so `dotnet run --project src/AgentOs.Web` previously 500'd without a live Keycloak -- the app's own Web could not run on its own. Adds a Development-only auto-login (DevAutoAuthHandler) that signs in a fixed developer/tenant=default principal so the desktop boots with no Keycloak/Postgres (modules already fall back to no-op repos). Defaults ON in Development; hard-throws if ever enabled outside Development; the AppHost injects Auth__DevAutoLogin=false so the full stack keeps using real OIDC. Also makes WindowManagerService + ToastService scoped (per-circuit) -- as singletons on Blazor Server they bleed windows/toasts across users and broke E2E isolation. This makes the UI E2E runnable headless: SpineAppTests passes 2/2 against the standalone Web (open Spine -> three panes; register a runner -> one-time REMOTE_AGENT_ID/TOKEN panel). Updates the CLAUDE.md verification rule: every host must run standalone with one command, no external services. Full suite 318 passed / 5 skipped / 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds AgentOsRealAuthFixture (real OIDC login as the realm-seeded operator/operator against the AppHost Web on https://localhost:5180, IgnoreHTTPSErrors) + SpineAppRealAuthTests: - Login_RealAuth_RendersDesktop -- the real Keycloak round-trip lands on the authenticated desktop. - Spine_RealAuth_RegisterRunner_AppearsInList -- registers a runner through the real stack and asserts it persists (real Postgres + tenant scoping). Gated by RUN_AGENTOS_E2E_REAL=true (separate from the dev-auth RUN_AGENTOS_E2E suite). The login smoke is verified green against the live stack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ed connector) Extracts the connect-a-workspace flow into IWorkspaceConnector (validate via provider -> store the PAT encrypted -> persist), shared by the HTTP endpoint and the desktop. Adds the connect form to the Spine Workspaces tab so a repo can be connected without the API. Tenant-explicit throughout (a Blazor circuit has no HttpContext): new IAppConfigStore.SetForTenantAsync/DeleteForTenantAsync and IWorkspaceRepository.AddForTenantAsync -- EfAppConfigStore otherwise resolves the tenant from a fresh scope that falls back to the default tenant. WorkspaceEndpoints.ConnectAsync now delegates to the connector (one implementation, no duplication). Tests: WorkspaceConnectorTests (valid -> persists + stores token; validation-fail -> nothing persisted; missing fields; unknown provider) + a dev-auth E2E asserting the connect form renders. Unit suite 322 passed / 5 skipped / 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… desktop app (M1/M3c) Lands the deferred durable tool-invocation evidence: EfToolInvocationLog persists every governed tool call (success, error, policy denial) to a new `tools` schema (ToolsDbContext + Initial migration); ToolsModule wires it when a DB is configured, else the in-memory ring buffer (dev/CI). Adds SessionId to ToolInvocationRequest + ToolInvocationEvidence (M3c), threaded through DefaultToolGateway, so evidence ties to a remote session once M4 wires session context. UI: a new admin-only Evidence desktop app surfaces the recent tool-invocation trail per tenant (reads the already-tenant-explicit ListRecentAsync). Tools module wired into the Web host. Tests: EvidenceTests (gateway threads SessionId; EfToolInvocationLog appends + lists per tenant + respects limit + round-trips SessionId, via EF in-memory) + an Evidence-opens E2E row. Unit suite 325 passed / 5 skipped / 0 failed; dev-auth UI E2E 9/9 green (Evidence opens, connect form renders). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds BrandingTests: the desktop title + TopBar are "AgentOS" and the retired "Agent Studio" name does not surface in the desktop UI. Reworks LoginOverlay_ShowsOnFirstVisit (now Desktop_DevAuth_LoadsWithoutLoginWall): the standalone dev Web uses Auth:DevAutoLogin, so "/" renders the authenticated desktop directly with no login overlay/wall -- the old localStorage overlay was retired by dev-auto-login. Backfill new tests all green (14 UI E2E pass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Change description
M3 (model + hub rewire) of the remote repo-execution spine. Adds a new
AgentOs.Modules.Sessionsmodule and rewires the RemoteAgent path so dispatch is authenticated per-runner and routed to exactly one machine.New module
AgentOs.Modules.Sessions(schemasessions, Api host only):RemoteSession(member x workspace unit of work) +Runner(a member's standing paired dev machine) aggregates, a tenant-filteredSessionsDbContext, an Initial EF migration, EF repositories with no-op fallbacks for DB-less boot, and/sessions+/runnersREST endpoints (Member policy).POST /runners— never stored, never in any list/get DTO.Trust-boundary rewire of the RemoteAgent path (closes two known spine bugs):
RemoteAgent:PairingToken. The hub resolves a connecting runner by id (tenant-unfiltered, since the connection has no tenant yet), verifies the presented token against the stored hash in constant time (CryptographicOperations.FixedTimeEquals), and rejects unknown/revoked/bad-token connects.Clients.Allbroadcast. The broker resolves a request to one runner connection scoped by(tenant, member); a tenant-A request can never reach a tenant-B runner. Transport now usesClients.Client(connectionId).REMOTE_AGENT_ID+REMOTE_AGENT_TOKEN.Deferred to follow-ups:
EvidenceEntry.SessionId+ durable EF evidence (M3c); flipping Pending→Paired / LastSeen + force-disconnect on revoke (M4); AppHost wiring of a seed runner.Type of change
Related issues
Part of the H1 remote repo-execution spine (M3). Follows M2 Workspaces.
Checklist
dotnet buildpasses locally in Release mode (0 warnings, TreatWarningsAsErrors on)dotnet testpasses — 318 passed / 5 skipped (live-LLM smoke) / 0 failedTest plan
RunnerPairingServiceTests(6): issue/verify round-trip, wrong token, salt uniqueness + cross-verify rejection, malformed-hash never-throws, empty-token rejection.RemoteAgentTests(11): targeted dispatch hits the matching member's connection,HasRunnerFortenant+member matching, cross-tenant resolution returns false (leak closed), operator-mode empty-member matches any runner in tenant, timeout/disconnect/complete lifecycle, client zero-cost/no-runner/failure paths.🤖 Generated with Claude Code