Skip to content
BreachPilot

State

Query layer (api/hooks.ts:63, api/types.ts, api/client.ts)

queryKeys

hooks.ts:63 central factory:

keyvalueinvalidation fans
connections / connection(id) / connectionListener(id)["connections"], ["connections",id], ["connections",id,"listener"]useConnections adaptive poll + Layout badge; useCheckConnection invalidates lists + detail + listener
capabilities["capabilities"]capabilities guard
config/schema/secrets["config"], ["config","schema"], ["secrets"]settings
models / modelsLive / providers["models"] etc.usePatchConfig (hooks.ts:152) invalidates modelsLive/providers only when patch keys contain `models
plugins / goals / skills / skillsSearch(q) / skill(name)["skills"...]skills page
runs(limit,offset,sort,q,state)["runs",{limit,offset,sort,q,state}] (hooks.ts:78)useRuns 50/200
run(runId)["runs",runId]patchCaches on state event (ws.ts:68)
runDecisions/run(runId,decId)["runs",runId,"decisions"]approval event + answer mutation
runTools/Artifacts/Audit/Swarm/Campaign/runLog/runCredentials/runLoot/runWorkspace/runGraph/runWitness["runs",runId,...]narrow per-panel

defaultQueryOptions (hooks.ts:107): retry:DEFAULT_RETRY (no retry on 4xx except 408/429; 0 is network failureCount<3), staleTime 15s, gcTime 5m, meta.onErrorAuthClear. QueryClient in App.tsx:30 additionally refetchOnWindowFocus:false.

Hooks inventory

GroupHookEndpointNotes
SystemuseCapabilitiesGET /capabilities 60s
ConfiguseConfig 30s, useConfigSchema ∞, usePatchConfig PATCH /configsets cache directly + conditional model invalidation
SecretsuseSecrets 30s, usePutSecrets PUT /secrets {secrets}invalidates secrets
ModelsuseModels 60s, useLiveModels 30s (swallows 503 payload as data), useAddModel POST /models, useRemoveModel DELETE /models/<alias>, useSetModelProvider POST /models/providerproviders/modelsLive/models invalidation
ProvidersuseProviders 15s, useInvalidateProviders, useChatgptLogin POST /providers/chatgpt/login, useChatgptProxyStart/Stop POST /providers/chatgpt/proxy/{start,stop}invalidates providers+modelsLive+models on settle
MiscuseSystemInfo 60s, useTelemetry 15s, useMemory 15s, usePlugins 60s, useAttackModules 60s, useGoals ∞, useSkills 60s + useSkillSearch debounce 250ms + useSkillDetailuseGoals groups `safegated
DiagnosticsuseDiagnostics POST /diagnostics/{doctor,self-test} + useResetSystem POST /system/reset invalidates runs/telemetry/memory
RunsuseRuns(limit,offset,sort,q,state)adaptive refetchInterval 5s if any isActiveState else 60s (hooks.ts:441), keepPreviousData
RunuseRun(runId)5s while `runningqueued
Create/cancel/resume/delete/retitleuseCreateRun POST /runs, useCancelRun POST /runs/<id>/cancel, useResumeRun POST /…/resume, useDeleteRun DELETE /…?purge, `useRetitleRun POST /…/title {titleregen}`
DecisionsuseDecisions (5s while pending), useDecision, useAnswerDecision POST /runs/<id>/decisions/<decId> {answer} invalidates runDecisions+run+runs
Artifacts/tool gatewayuseRunTools GET /runs/<id>/tools 15s (enabled only while active), useCallTool POST /…/tools/<name>/calls {arguments}, useArtifacts GET /…/artifacts 30s while active else off (reads run cache), useAudit, useSwarmState (404 no retry), useCampaignState, useRunLog tail+attempt/target, useCredentials + useRevealCredential/ConfirmCredential POST /credentials/<i>/{reveal,confirm}, useLoot, useWorkspace + useWorkspaceFileUrl/useFetchWorkspaceFile raw, useArtifactUrl/useFetchArtifactBlob raw, useRunGraph (404 no retry), useWitness
ConnectionsuseConnections GET /connections?status&target adaptive 12s/15s/30s, useConnection GET /connections/<id> 10s while active/stale, useConnectionListener GET /connections/<id>/listener?lines 3s while drawer open + active (staleTime 2s), useCheckConnection POST /…/check, useRemoveConnection POST /…/removeInvalidates ["connections"] + detail + listener; ConnectionManager is sole persistence (operator_connections.json)
Graph explorerre-exported from features/graph/graphApi.ts — own graphKeys prefix, 10s polling while active

All mutating hooks set/ invalidate related queries — see table onSuccess.

Live event transport

eventBuffer.ts:4 + eventStore.ts:19

ModuleSymbolSemantics
eventBuffer.tsMAX_EVENTS_PER_RUN=1000, appendBounded(prev,batch):AppendResultbounded chronological append; over calc keeps only tail, batch>=MAX slices batch; pure, never mutates prev
eventStore.tsEventStore class, MAX_RUNS=10 LRU (Map reinsert on get), entry:{events,cursor,dropped}get(runId) re-orders LRU; set(runId,events,cursor) trims via appendBounded([]); append(runId,event) dedupes sequence<=cursor, appendBounded + dropped+=over; clear(evict beyond MAX_RUNS); singleton eventStore exported

sse.ts:1 — fetch-backed SSE

Native EventSource cannot send Authorization; token was leaking in URL (?token=). This module streams over fetch.

SymbolDetail
SseParser (sse.ts:76)incremental lineBuffer parser: push(chunk) splits on \n, processLine handles : comments, field: value strip leading space, cases data→push, event→set, id→set if !\0, retry→int, blank line → dispatch joining data with \n
streamSSE(options:StreamSseOptions) (sse.ts:155)`url: string
isAbortError (sse.ts:288)DOMException name AbortError guard

ws.ts:36 — primary transport

useRunEvents(runId, {after,enabled}) (ws.ts:36) returns {events,status,authError,transport,reconnect,lastSeq,dropped}.

ConcernDetail
Refs statelastSeqRef, eventsRef (mirror for pure append), droppedRef, wsRef, sseHandleRef/sseAbortRef, attemptRef, reconnectTimer, closedByUnmount, wsFailureCount, runIdRef, pendingRef, rafRef
SeedseedEvents(id, cancelled) (ws.ts:243): GET /runs/<id>/events?tail=MAX (EventReplayResponse types.ts:799 includes events,latest_sequence,oldest_sequence,has_more_before,first/last_returned_sequence,omitted_before,next_before) → eventStore.set(id,seeded,latest, older=omitted_before ?? (has_more_before?oldest-1:0)) + local setEvents/setDropped; latest stays the live cursor so new arrivals >latest are not skipped
WSconnectWs(id) (ws.ts:231): ws(s)://<host>/api/v1/ws/v1/runs/<id> (scheme from location.protocol), send {auth:token, after:lastSeq} on open, onmessage→JSON RunEvent→handleEvent, onclose handles WS_CLOSE_*: 4401 auth clear+error, 4403 origin, 4404 not found, 4400 invalid cursor→ resetSeq0+clear+seed, else wsFailureCount++ → if >=SSE_FALLBACK_THRESHOLD=3 → connectSse else setTimeout(backoffMs(attempt), connectWs)
SSE fallbackconnectSse(id) (ws.ts:158): streamSSE({url:()=>origin/api/v1/runs/<id>/events/stream?after=<lastSeq>, token, signal, onEvent:JSON.parse(data), onStatus, onFatal:auth→clear}), setTransport("sse")
Event handlinghandleEvent(event) (ws.ts:114): drop heartbeat (only bump lastSeq), dedupe sequence<=lastSeq, eventStore.append, patchCaches, then batch: IMMEDIATE_EVENT_TYPES = Set(state,approval,error,title) → flush immediate (cancel raf, appendBounded([...pending,event])); others → pendingRef.push + requestAnimationFrame(flushPending)appendBounded(eventsRef, batch) + dropped
Cache patchingpatchCaches(event) (ws.ts:62): on statesetQueryData run(runId) {state,result?}, invalidate ["runs"]; on approval → invalidate runDecisions; on artifact → invalidate runArtifacts + ["graphExplorer",id]
EffectuseEffect([runId,enabled]) (ws.ts:331): if cached eventStore.get(runId) reuse cursor/events else seed then connect; cleanup closedByUnmount=true, clear raf/reconnect/sse/ws
Types`WsStatus idle

Telemetry derivation (lib/deriveRun.ts:122)

deriveRunState(events):DerivedRun — single O(n) scan producing everything header/Now/telemetry/phase need. Tracks phase via phase|progress events, PHASES map + PHASE_ORDER (lib/deriveRun.ts:14), requestByAction/runningByAction across tool_request→tool_start→tool_result to compute currentTool/lastTool + toolErrors, bootSteps Map from boot|ok, lastTelemetry/telemetrySeries (cap 200) from progress.payload.telemetry:RunResultTelemetry, eventsPerMin from first/last timestamps. Returns phase,phaseIndex,lastReachedIndex,round,actions,elapsedSeconds,source,lastAssistant, currentTool/lastTool/toolCount/toolErrors, bootDone/bootTotal/bootFailed, artifacts,errorEvents, tokens,lastTelemetry,telemetrySeries, eventsPerMin,lastMeaningfulAt,lastEventType.

Client-only state

StoreKeyAPIUsed in
API tokensessionStorage breachpilot.apiToken.v1 (api/client.ts:6)getStoredToken/setStoredToken/clearStoredToken, injected Authorization: BearerTokenGate, ws.ts, sse.ts, Layout signOut
Onboarding dismissedsessionStorage breachpilot.onboarding.v1OnboardingGate
Welcome tour openevent breachpilot:open-welcome + sessionStorage dismiss flag (components/WelcomeScreen.tsx)HomePage Take the tour, App WelcomeGate
ThemelocalStorage breachpilot.theme + lib/useTheme.tstoggle dark↔light, script in index.html removes dark on lightLayout (Layout.tsx:59)
Permission modelocalStorage permissionMode (lib/permissionMode.ts)`read_onlyapprove
Run sortlocalStorage breachpilot.runSortRunListPage.tsx:54
Graph explorerAttackGraphPage.tsx:38 React local state (filters, selection, expansions) — no store

Decision + checkpoint wiring

campaignCheckpoint.ts:25 helpers: checkpointVisual(kind→borderClass/badgeClass), detectCheckpointKind(promptText first line VERIFIED ACCESS vs NO VERIFIED ACCESS → access|no_path), parseCheckpointOptions(options→CampaignNextStepOption[]), encodeCheckpointAnswer(option,goalName?,customText?) → sent as POST /decisions/<id> {answer} per docs/api.md: CAMPAIGN_NEXT_STEP spec.

DecisionCard.tsx renders all DecisionKind (start_confirm|goal_select|tool_approval|campaign_next_step) via CampaignCheckpointKind branching, with DecisionKind shape DecisionsListRow options_json/options merged from WS approval payloads.

source: repo docs (build sync)Edit this page on GitHub →