49 file(s), +3702 / -602 lines
| Path | Role | + | - |
|---|---|---|---|
| openapi/app-api.json | Config | +511 | -27 |
| openapi/permission-map.json | Config | +28 | -1 |
| src/app/api/internal/agents/[agentKey]/route.ts | App route | +88 | -0 |
| src/gen/client/agentsLaunchAgent.ts | Generated contract | +0 | -70 |
| src/gen/client/agentsLaunchAgentUnknown.ts | Generated contract | +61 | -0 |
| src/gen/client/agentsLaunchAtlasDockChat.ts | Generated contract | +67 | -0 |
| src/gen/client/agentsLaunchAtlasIntakeReview.ts | Generated contract | +70 | -0 |
| src/gen/client/agentsLaunchMira.ts | Generated contract | +66 | -0 |
| src/gen/client/index.ts | Generated contract | +4 | -1 |
| src/gen/hooks/index.ts | Generated contract | +16 | -4 |
| src/gen/hooks/useAgentsLaunchAgent.ts | Generated contract | +0 | -156 |
| src/gen/hooks/useAgentsLaunchAgentUnknown.ts | Generated contract | +147 | -0 |
| src/gen/hooks/useAgentsLaunchAtlasDockChat.ts | Generated contract | +150 | -0 |
| src/gen/hooks/useAgentsLaunchAtlasIntakeReview.ts | Generated contract | +152 | -0 |
| src/gen/hooks/useAgentsLaunchMira.ts | Generated contract | +150 | -0 |
| src/gen/index.ts | Generated contract | +118 | -29 |
| src/gen/schemas/agentLaunchResponse.json | Generated contract | +3 | -5 |
| src/gen/schemas/atlasDockChatLaunchRequest.json | Generated contract | +225 | -0 |
| src/gen/schemas/atlasIntakeReviewLaunchRequest.json | Generated contract | +83 | -0 |
| src/gen/schemas/miraAgentLaunchRequest.json | Generated contract | +33 | -9 |
| src/gen/types/AgentLaunchResponse.ts | Generated contract | +2 | -3 |
| src/gen/types/AgentsLaunchAgentUnknown.ts | Generated contract | +70 | -0 |
| src/gen/types/AgentsLaunchAtlasDockChat.ts | Generated contract | +70 | -0 |
| src/gen/types/AgentsLaunchAtlasIntakeReview.ts | Generated contract | +70 | -0 |
| src/gen/types/{AgentsLaunchAgent.ts => AgentsLaunchMira.ts} | Generated contract | +18 | -26 |
| src/gen/types/AtlasDockChatLaunchRequest.ts | Generated contract | +32 | -0 |
| src/gen/types/AtlasIntakeReviewLaunchRequest.ts | Generated contract | +51 | -0 |
| src/gen/types/MiraAgentLaunchRequest.ts | Generated contract | +21 | -8 |
| src/gen/types/index.ts | Generated contract | +53 | -13 |
| src/gen/zod/agentLaunchResponseSchema.ts | Generated contract | +7 | -1 |
| src/gen/zod/{agentsLaunchAgentSchema.ts => agentsLaunchAgentUnknownSchema.ts} | Generated contract | +29 | -37 |
| src/gen/zod/agentsLaunchAtlasDockChatSchema.ts | Generated contract | +91 | -0 |
| src/gen/zod/agentsLaunchAtlasIntakeReviewSchema.ts | Generated contract | +91 | -0 |
| src/gen/zod/agentsLaunchMiraSchema.ts | Generated contract | +91 | -0 |
| src/gen/zod/atlasDockChatLaunchRequestSchema.ts | Generated contract | +43 | -0 |
| src/gen/zod/atlasIntakeReviewLaunchRequestSchema.ts | Generated contract | +58 | -0 |
| src/gen/zod/index.ts | Generated contract | +45 | -11 |
| src/gen/zod/miraAgentLaunchRequestSchema.ts | Generated contract | +31 | -5 |
| src/lib/agent/gateway.ts | Library | +35 | -0 |
| src/lib/agents/__tests__/atlas-rail-store.test.ts | Library | +338 | -60 |
| src/lib/agents/__tests__/atlas-rail.test.ts | Library | +11 | -10 |
| src/lib/agents/atlas-rail-store.test.ts | Library | +34 | -5 |
| src/lib/agents/atlas-rail-store.ts | Library | +99 | -44 |
| src/lib/agents/atlas-rail.ts | Library | +19 | -12 |
| src/lib/agents/mira-rail.test.ts | Library | +42 | -29 |
| src/lib/agents/mira-rail.ts | Library | +45 | -36 |
| src/lib/agents/rail-launch.ts | Library | +46 | -0 |
| src/lib/kubb-client.ts | Library | +10 | -0 |
| tests/unit/agent-launch-proxy.test.ts | Test | +178 | -0 |
Every agent run in the app used to LAUNCH over the WebSocket rail: the chat
message rode the socket itself. RFD-AGT-013 (T1b) moves the launch onto the
channel-neutral HTTP endpoint POST /app/agents/{agent_key}
(pramaan-functions Story-2840/2895, merged as #777/#785) so the app and
future channels share one guarded launch. This story rewires all
four app launch builders — add-matter wizard intake, Agent Dock
chat, Mira converse, Mira command — through the ONE shared rail-store path,
while the already-open WebSocket stays bidirectional for
streaming and query/replay.
Open the socket (as today) → launch via HTTP through the BFF → deltas and terminal frames keep arriving on the same socket exactly as before.
| Layer | Before | After |
|---|---|---|
Launch transport (src/lib/agents/atlas-rail-store.ts) |
socket.send(JSON.stringify(buildLaunch(…))) — the launch rode the WS |
await this.connect() (socket opens FIRST so the server-derived attach resolves it), then postAgentLaunch(agentKey, body) — an HTTP POST through the generated client |
| The four builders | Built camelCase WS client messages (type:"message", agentKey, …) |
Build HTTP request bodies: buildIntakeReviewLaunchRequest, buildDockChatLaunchRequest (atlas-rail.ts); buildMiraConverseLaunchRequest, buildMiraCommandLaunchRequest (mira-rail.ts) |
| Acceptance | The broker pushed a run.accepted frame |
Acceptance arrives on the 202 response; the store applies the SAME state transition via reduceFrame, so every downstream behaviour is unchanged |
| Blocked launch (consent / rate limit / breaker) | run.failed frame over the socket |
accepted:false in the response IS the terminal truth — the store fails the rail immediately with userVisibleMessage + retryable, keeping the run id as the support reference |
| Streaming + replay | Unchanged. The socket stays connected; the server attaches the caller's newest live socket to the run (functions Story-2896, RFD-AGT-013 D4 — the browser never learns its API-Gateway connectionId). query/replay still rides the socket (buildQueryMessage / buildDockChatQuery / buildMiraQuery untouched). |
|
src/app/api/internal/agents/[agentKey]/route.ts — new BFF
launch route. Validates the browser payload against the generated request
schema for the key (HR-21), forwards via
agentGateway.launchAgent, returns the upstream
AgentLaunchResponse with 202; unknown keys are a typed 404
mirroring the upstream static mapping.
src/lib/agent/gateway.ts — launchAgent() POSTs to
the gateway and parses the response against
agentLaunchResponseSchema (assertContract, HR-21).
src/lib/kubb-client.ts — toBrowserRoute maps
/app/agents/{mira,atlas_intake_review,atlas_dock_chat} onto
/api/internal/agents/<key>.
openapi/app-api.json + src/gen — mirror synced to
pramaan-functions main (post-#785) and regenerated per the openapi mirror
guard.
counsel/atlas; the Mira HTTP contract's
AgentLaunchHistoryTurn speaks counsel/mira.
buildMiraConverseLaunchRequest maps every non-counsel turn to
mira. Dock history keeps counsel/atlas
(the HistoryMessage contract's own vocabulary).
matter_meta is gone from the wire. The WS
broker's _translate_mira_launch already discarded it
server-side; the HTTP request carries only the matter UUID + the live turn.
pramaan.websocket.mira_command_unsupported);
over HTTP it launches with intent:"command" + scope
and the frame is server-derived (RFD-AGT-013 D4).
stampSurfaceContext
now stamps the HTTP body; query is still never stamped.
pnpm vitest run src/lib/agents/__tests__/atlas-rail-store.test.ts \ src/lib/agents/atlas-rail-store.test.ts \ src/lib/agents/mira-rail.test.ts \ src/lib/agents/__tests__/atlas-rail.test.ts \ tests/unit/agent-launch-proxy.test.ts
atlas-rail-store.test.ts
→ "HTTP launch for all four builders"): wizard →
/api/internal/agents/atlas_intake_review (formId + input);
dock → /api/internal/agents/atlas_dock_chat (dockChat
context); Mira converse → /api/internal/agents/mira
(intent:"converse" + mapped history); Mira command → same URL
(intent:"command" + scope, no message). Each asserts the
socket carried NOTHING at launch.
accepted:false)
→ immediate honest error with the server's message + retryable; failed POST
→ retryable transport error.
query replay tests, resume-on-rehydrate tests, and the
Story-2181 thinking-ceiling tests all stay green against the same frame
sequences.
tests/unit/agent-launch-proxy.test.ts):
generated clients map all three keys onto /api/internal/agents/*;
the route 202-forwards, 404s unknown keys, 400s bad JSON, 422s contract
violations, and passes GatewayError statuses through.
CC-54 is covered by the replay path, which this story deliberately does not
touch: a refresh drops the socket, the store's
resume(runId)/query reconnect-refetch replays the
run's persisted truth (turn.completed + run.done, or run.accepted while
still running), and thread hydration re-seeds committed turns from
agents.chat_turn (Story-2223). The rewire changes only WHERE
the launch goes; the run's truth is still persisted server-side and the
same replay frames flow through the same reduceFrame. The
replay tests above pin this. Manual UAT script for the human check: open a
matter, ask Mira a long question, refresh mid-stream — the panel reopens,
the thread hydrates, and the answer lands complete.