← Về thư mục
📄 / / profiles / r-and-d / raw_evidence / OpenAI4S / openai4s / server / README.md

Server

中文说明

The Web application lives here. This package wires the provider-neutral agent engine, the persistent Python and R kernels, the Host capability boundary, and the SQLite repositories into one HTTP/WebSocket server built out of nothing but the standard library. Domain logic belongs in the focused services in this directory; gateway.py is the compatibility and transport facade they are composed through.

Where this fits

browser
  |  REST requests + WebSocket events
  v
gateway.py
  |-- session-domain services and read projections
  |-- AgentEngine adapter (agent_run.py)
  |-- FIFO execution ownership (execution_coordinator.py)
  `-- lazy, session-owned Python and R kernel slots
         |
         `-- HostDispatcher -> permissions, tools, artifacts, data and delegation

The versioned surface

Completion, Notebook, and recovery boundaries

Files

File Responsibility
__init__.py The stable package facade. Exports build_server and serve.
action_timeline.py Projects the canonical Action Ledger into the Timeline the UI actually shows. An entry carries enough to say what ran, how it ended, which permissions it needed, what it cost, and which artifacts it referenced, all of it bounded and redacted. Provider wire_state and raw argument strings are left out on purpose, so a debugging endpoint cannot become a credential or protocol dump. A plan turn's user group is titled by its task, not by the plan-mode prompt in front of it.
attention.py Cross-session read-only "needs attention" aggregator. It unions running/queued executions, pending approvals, recoverable failures, view-only/blocked sessions, and live/unknown remote compute into a fixed card shape. Team visibility is applied before aggregation, sort, and limit. Targets are a closed set of surface/dock values so the server never returns an arbitrary URL. GET is side-effect free: no kernel spawn, no provider call, no retry/approve/harvest. There is no materialized table.
attention_routes.py GET /attention?limit&cursor as a validated RouteSpec. The cursor is a (updated_at, id) keyset bound to the caller's team-scope fingerprint; a cursor from another user or filter is 400 invalid_cursor. Retry/approve/restore stay on the existing mutation routes.
agent_run.py Adapts AgentEngine to the Web contracts. It streams safe prose and code drafts, emits Web events, honours cancellation, and runs native actions or Cells through injected ports.
artifact_refs.py Version-pinned @file references in a user message. @name#v-<version_id> sends the frozen bytes of that exact version, never the live path — the old resolver read whatever a later cell had left there. An unresolvable reference is reported instead of dropped, a binary artifact is named rather than pasted as replacement characters, and a same-project cross-session reference is materialised at send (D3) rather than read in place.
retrieval_source.py The client-safe projection of a version's retrieval provenance. The envelope is free-form JSON written by whatever performed the retrieval, including unaudited skills, and scientific APIs routinely take the key in the query string — so keys are allowlisted, values are bounded, and credentials are fingerprinted in the query, the path and the userinfo. Absent provenance returns None rather than an empty panel, which would read as a finding about the data.
artifacts.py A workspace file the agent writes becomes a versioned Artifact here. The same service backs edit, rename, upload, restore, and promote from the UI, keeping snapshots, provenance, and broadcasts in step as versions move. The trusted path streams, fsyncs, atomically freezes, and verifies bytes before registration; a same-head capture reuses the version while retaining an observation.
artifact_index.py Project-scoped Artifact index: keyset pages on (created_at, artifact_id), an opaque cursor bound to project_id + q + content_type + origin + team scope, and escaped filename LIKE. The legacy /projects/{pid}/artifacts array route is unchanged.
artifact_index_routes.py GET /projects/{pid}/artifact-index. RouteSpec feeds the contract inventory; filter changes invalidate the previous cursor with 400 invalid_cursor.
artifact_workbench.py Stage 9 official Artifact workbench: full-dataset table queries, version diffs, PDF/HTML locators, structure summaries, and the Ketcher 3.7.0 wrapper. It does not run when the flag is off.
artifact_workbench.py Stage 9 official Artifact workbench: full-dataset table queries, version diffs, PDF/HTML locators, structure summaries, and the Ketcher 3.7.0 wrapper. It does not run when the flag is off. GET .../table accepts an optional version_id that never degrades to latest.
artifact_workbench_routes.py The five Stage 9 Artifact workbench routes (/table, /diff, /structure, /pdf-text, /html-outline). Flag-off is 403.
artifact_workbench_routes.py Stage 9 Artifact workbench routes (/table, /table/profile, /table/export.csv, /diff, /structure, /pdf-text, /html-outline). Flag-off is 403.
table_profile.py Shared table query parser (locks the historical /table five-parameter contract), column profile (type/missing/unique/min/max/mean/histogram, bins≤50, approximate unique when exact tracking hits its bound), chunked CSV export (1 MiB chunks, 32 MiB total → 413), process LRU for profiles, and the resource-acceptance manifest gate. Statistics are never persisted.
auto_budget.py Atomic Auto Mode budget admission: consumer registry, canonical action fingerprints, durable-delta closed set, and fail-closed token measurement. Sinks reserve a stable admission_id before the action; only a never-started call may be released. Guardian fields are projected from Guardian history, never copied.
auto_mode.py Resolves the Stage 2 Auto Mode selection in its frozen quarantine → frame → project → explicit deployment → legacy result-review → built-in order, projects durable run/audit state through bounded allowlists, and forwards only newly-created canonical committed events as best-effort WebSocket hints. It invokes no model, reviewer, repair, or permission path.
auto_mode_portability.py The untrusted-safe reducer shared by Session packages and read-only shares. It validates Auto Mode scope/reference closure, emits only a closed audit DTO, downgrades claims portable evidence cannot prove, and never restores execution or permission authority.
auto_mode_routes.py The exact GET/PATCH /frames/{id}/auto-mode and read-only /auto-audits adapters. Their validated RouteSpec table feeds the contract inventory; there are deliberately no public run/review/repair/Guardian transition routes.
auto_repair.py Stage 5 bounded Repair Agent and re-review loop. The Reviewer stays read-only; Repair cannot self-certify; identical checksums reuse the prior version.
guardian_shadow.py Stage 6 exact-action Guardian shadow. It records allow/deny advice without executing and refuses standing grants.
guardian_enforce.py Stage 7 unattended enforcement. Only allow_once for non-dangerous exact actions; Guardian still cannot create a standing allow.
evidence_adapters.py Read-only PDF, image, structure, and table adapters for frozen Artifact versions. Filename-only coverage is incomplete.
evidence_snapshot.py Builds the immutable Stage 3 Evidence Snapshot, including plan, checksums, lineage, adapters, omissions, and resolvable evidence_refs. Hidden Agent reasoning is excluded.
review_scratch.py Isolated scratch for Reviewer verification: secret-scrubbed child env, no network, no formal-workspace write, no MCP, no submit_output.
scientific_review.py Stage 3 shadow orchestrator. It freezes reviewer identity, runs deterministic + independent model review, binds evidence refs, and records a non-gating shadow judgment.
cell_run.py Runs one Python/R Cell in a fixed order: readiness admission, identity/attempt allocation, safety, kernel execution, live output, artifact capture, execution logging, terminal projection. Stage 1's admission port fails before any Cell id or runtime exists. Cancellation is checked once more immediately before user code would start: preparing the runtime (a cold kernel's bootstrap) cannot be interrupted, so a Stop or daemon shutdown landing there finishes the Cell as interrupted with none of its code run, instead of starting it and interrupting it a poll later. Finishing the transaction is an observation; it never decides that the agent's task is done.
completion_gate.py Stage 4 candidate→review→promotion. It records a provisional candidate, waits for the Scientific Reviewer, then stamps Verified / completed_with_issues / review_unavailable. It does not start Repair.
completions.py Writes the narration a user sees. Progress and outcome prose are localized, and a structured completion is rendered against the real Artifact-version/capture delta rather than against a claim about it. Trusted links come only from the shared URL helper and exact version ids. Hidden reasoning never reaches it.
compute_tasks.py The read-only view of one session's remote compute work. A remote job outlives the turn, the kernel and the daemon, and the durable record was only reachable from inside a cell. The page cannot poll, for a reason specific to this system: the probe is the harvest — ComputeManager.result() is what contacts the remote, and contacting it pulls files back and closes the job, so a self-refreshing page would harvest into a session nobody was watching. This module takes a Store and has no import of ComputeManager, so that is structural rather than a promise about call order. Scoped by owner_key (the session workspace); another session's work is not listed, not counted, and not reported as hidden.
contract.py API_ROOT, the RouteSpec primitive, and the inventory of every routable path and WebSocket event — derived from the code rather than maintained by hand beside it. Two readings, unioned over every route module: the routing chain is parsed out of gateway.py and its *_routes.py siblings, and a module that has migrated also contributes its live ROUTES table. A hand-kept list is wrong the first time somebody adds a route in a hurry, and its being wrong is invisible — which is the failure a contract exists to prevent; the source read fails loudly, with an empty inventory, if the routing style ever changes enough to invalidate it. Selecting one reading per module reopened that hole one level down (a leftover re.fullmatch beside the declarations, or a table not named ROUTES, left silently), which is why both always run. It answers which paths exist, not what they return.
delivery.py Verifies an exact Artifact-version delivery manifest without trusting a mutable path: ownership scope, regular-file immutable snapshot, stable descriptor identity, size, and SHA-256 must all agree. Its output is path-free and is what the completion delivery repository binds atomically to a final or safely staged Session-package message.
local_auth.py The daemon's own access token: minted once under the data dir, owner-only, compared in constant time. A file rather than a Store row because the CLI must read it before any database exists, and openai4s doctor has to work when the database is the thing that is broken. It used to live in a closure and change on every restart, invalidating every cookie already issued. Minting publishes through os.link, the one operation that can only succeed once, so concurrent daemons settle on a single token instead of each holding its own.
errors.py GatewayError, the stable machine error codes, the additive public-failure envelope, and the exception projector that keeps an arbitrary str(e) out of a response body while the operator still gets the original through the structured log. Its own module because GatewayError sat ~5,800 lines below gateway's own import block, so a sibling module importing it from there is a circular import that fails the daemon at boot. Every route group carved out of Handler._api needs to raise it, so the cycle would otherwise be rediscovered once per extraction. Gateway re-exports the old names.
execution_coordinator.py The Web adapter over session-scoped FIFO execution ownership. Ticket state becomes WebSocket events. An admitted ticket is bound to its cancellation event and to the kernel lease current at that moment, and an interrupt reaches only the exact lease held by the exact execution id.
execution_sources.py The executed-code hierarchy in one place: GET /frames/{fid}/execution-sources projects the root frame plus every delegated child frame recursively (names, depth, per-frame counts, per-cell language/status/source SHA-256/generation/environment/artifact links — never the code text itself), and …/execution-sources/export renders the same rows as a byte-deterministic sources.zip of the actual executed source files, failed cells included and marked, with a new manifest and a bilingual persistent-kernel warning. Only execution_log fields and public metadata leave the store — no prompts, host payloads, output, or credentials.
execution_views.py Reads immutable cell history and answers what the Notebook asks of it: which runtime generation ran a cell, what it depended on, whether it has gone stale since, how it was retried, where its data came from.
gateway.py The main HTTP/WebSocket composition facade. Protocol framing, the hub and its resume buffers, SessionState and SessionRunner, REST routes, static serving, and the security checks all live here, and so does the wiring of every service in this table.
global_views.py Composes the project-wide research Timeline and the artifact-lineage views that reach across sessions.
governance_routes.py The /team/* admin surface (M2): users, project members, invites, usage, audit, quotas — a validated RouteSpec table, and the central admin-only declaration for the namespace (one _deny gate ahead of every route). Raw invite tokens appear exactly once, in the POST response; listings carry digest prefixes. Every mutation lands in the team audit log (INV-12). Stable team_off shapes with team mode off, which is what the contract capture freezes.
kernel_routes.py The twelve kernel routes, moved out of Handler._api as the first slice of its decomposition (2,100 lines / 261 branches → 1,887 / 237). Chosen because it was the only group that could be checked: it owns eleven frozen response shapes where memory, permissions, connectors and compute own none. Each method + path matcher is now a RouteSpec in the module's ROUTES table, validated where it is declared and read by the contract inventory. handle() is tri-state — True emitted a response, False means the group did not handle the request and the chain must continue to its 404; RouteSpec.match() takes the method precisely so a right-path/wrong-method request is not swallowed as handled, and return bool(regex_matched) would swallow twelve wrong-method 404s. Two position dependencies are load-bearing and tested rather than merely commented: the call site must stay after the frame_mutation guard (the only write-protection on the seven mutating routes, including code execution — every one of them parametrised into the quarantine test off spec.mutates) and after the workbench guard (which is the entire 404 for GET /frames/{id}/execution).
team_policy.py Who may touch what, as predicates over resources rather than over URLs. An external review named the real problem with team mode's first version: authorization was "does this path match one of two regexes", so every surface that is not /frames/{id} or /projects/{id}/… was still the single-user API — five separate isolation defects came out of that one shape. Point-fixing five call sites would have reproduced the cause. Two properties hold throughout: team mode off returns the permissive answer immediately, so a single-user install runs the code it always ran (INV-1); and a lookup that raises is a refusal, because treating an unreadable ownership row as unrestricted is how a version-addressed artifact walked past this product's checks once already. Call sites still have to ask — nothing can force a new route to — but there is one model to read and one place a rule changes.
compute_session_routes.py Where a session's kernel runs, and whether it is ready yet (M3b-6). The payload these exist for is readiness: a cluster session is four conditions, not one boolean (INV-5), and the response names the one still outstanding — "queued for a node", "waiting for the worker to dial in" and "starting the kernel" are three different waits with three different expected durations, and one spinner for all of them is how a user concludes the product is broken. location: "local" is an answer, not an error: it is what every install does. An unconfigured profile is refused rather than guessed at, because guessing is how a session lands on resources its owner never chose. And state_lost_epochs is what the UI turns into a banner (INV-11), because results produced after a recovery look exactly like results from the session that was lost.
orchestration_routes.py BatchJob submit/list/detail/cancel/logs plus the read-only profile list (M3a-8). Two deliberate absences: it never submits — a request writes a durable row and the reconciler does the talking, so a cancel survives a restart and a submission is never attempted twice from a request thread that may go away — and it never names a scheduler, so a response carries allocation_id and never a job id (INV-2). Ownership follows the session rule: someone else's job is 404, not 403, because which jobs exist says what a colleague is working on. A command given as a string is refused rather than split, since splitting a command line is exactly where quoting bugs become injection.
model_discovery.py Probes a small fixed catalogue of loopback URLs for OpenAI-compatible model servers, with proxies disabled and redirects refused, so no caller can turn it into a general SSRF primitive. catalog() returns that list with no sockets opened and no background refresh. The result is a profile suggestion only: it mutates no model settings and stores no credentials.
model_profiles.py A model-provider profile passes through here on the way in, where it is validated and migrated, and again when it is persisted, activated, or removed. Credentials are stripped out of anything that becomes public. probe() records a three-state capability receipt (tools are schema-checked and never executed; at most two tiny requests). It also builds the header model selector, which lists the live model and the saved profiles and nothing else — an endpoint nobody configured must not be offered, since choosing it would only fail at send time.
onboarding_routes.py First-run Web routes. GET /onboarding is redacted and contacts nobody; POST /onboarding/complete is an instance-global mutation (admin-only in team mode).
diagnostics_routes.py Web Diagnostics: GET /diagnostics/status is a passive security-posture read (no network, subprocess, or Store write); POST /diagnostics/checks runs the full doctor report; POST /diagnostics/bundle streams a deny-by-default zip off a temp file that is unlinked on complete, disconnect, and error. Team mode is admin-only with 403, not 404; one in-flight generation; 60 s per principal; 32 MiB cap.
project_listing.py GET /projects off the gateway: the parameter table (q, limit, cursor, offset) with its stable 400 codes, the opaque keyset page (limit + 1 to observe has_more), the ?offset compatibility window, and the unparameterized full dump — all through the Store facade. Team visibility stays a WHERE conjunct inside the repository SQL; this module never post-filters a page. Rows come back raw so the gateway applies its own projection and nothing here imports gateway.
notebook_export.py Deterministically exports raw immutable execution history in four read-only forms: a .ipynb per language, a checksum-described bundle of both, and one Markdown document. The first three are for re-running the work; the Markdown one is for reading it and pasting it into an issue or a methods section, so it keeps both languages in execution order — the interleaving is the record — and opens with an ## Inputs section naming every artifact version the branch's turns were pinned to. That section is omitted entirely when there were none, because an empty heading is a claim too. None of the four applies the Notebook projection's filter, so a protocol-only completion Cell can still show up in an export.
notebook_lineage.py Stage 8 official live Notebook flag plus host-side Python/R read→version mapping and write lineage. It does not instrument the kernel.
stage12_ga.py Stage 12 GA kill-switch declaration. It does not enable earlier stages.
plans.py Owns the structured-plan lifecycle. A planner response is parsed and normalized, the draft and its JSON artifact are persisted, the public review shape is exposed, and an approved plan is carried into execution. plan_mode_request_text finds the user's own words inside a plan-mode prompt, for the session title and the Timeline title only; model input and stored rows keep the prompt. Live host.plan_update mutations stay in HostDispatcher.
recovery_control.py Projects recovery journal and generation state, and composes the validated, redacted plan of recovery actions currently possible. It never calls a checkpoint restorable unless both a workspace tree and a complete bootstrap manifest are present.
recovery_execution.py Runs one recovery mutation under exact execution ownership. Every language candidate runs under a single recovery id, the run stops after the first incomplete candidate, and it ends in one durable session terminal event.
recovery_recipe.py Compiles immutable Cell facts, dependency closure, environment needs, sidecars, and determinism checks into a recovery recipe. It is conservative by design: a state-affecting Cell that cannot pass those checks stays in the recipe as a never replay step, so validation reports Partial instead of quietly claiming the old namespace survived.
recovery_runtime.py Where the recovery pipeline meets real infrastructure. For one session it brings up candidate Python and R kernels, probes the environment, bootstraps, verifies, then commits or rolls back.
renderers.py The registry from artifact kind, content type, and extension to a safe scientific renderer, plus the public renderer descriptors. Metadata only: it imports no scientific library and executes no artifact content.
response_capture.py Observes what routes really return and freezes it into docs/response-schemas.json. It wraps make_handler's _api from the outside rather than hooking _json, because the gateway tests replace handler._json with their own collector — a hook inside the real _json would have missed nearly every route while appearing to work. Nothing here runs in production. A subtree whose field types vary with the host (the kernel's sandbox block: backend is a string where one can be enforced and null where none can) is recorded opaquely rather than pinned — freezing it would pin the machine the capture ran on and call every other machine a breaking change. That list admits type-varies-with-host subtrees only; it is not somewhere to park a route whose shape is merely inconvenient.
response_schema.py A small, explicit shape algebra (types, required keys, element shapes) with no dependency, because the core is stdlib-only. It answers "did this response change shape"; it is not JSON Schema draft-2020-12 and does not pretend to be.
reviews.py Assembles the bounded evidence packet a scientific review runs on, then drives that review to a result. The run stays cancellable, and it lands in persistence, usage accounting, and the public review events.
sandbox_grants.py Signed read grants for executable Artifact previews. A grant binds a nonempty frame, expiry, minting app origin, and the exact alternate loopback origin where it may be spent; changing the Host back to the app is refused. Its path segment survives relative-resource resolution without setting a cookie. The daemon access token signs grants; the script-readable grant itself is a temporary bearer, with self-navigation disclosure risk documented in docs/security.md.
security_headers.py The static CSP and hardened response headers applied to every response. Executable UI code lives in same-origin files, so script-src needs neither 'unsafe-inline' nor a hash/nonce derived by reparsing HTML. artifact_content_disposition names served Artifact bytes inline after their own sanitised filename (ASCII filename + RFC 5987 filename*).
session_branching.py Everything that makes a session branch: taking a checkpoint, forking it in isolation, previewing a revert, activating a branch, and recording revert/undo history append-only. A revert never rewrites an old checkpoint. It first records the current state as the undo target, and if external files changed after the current head the operation is recorded as conflict and no bytes move.
session_deletion.py Cleans up after a durable session delete. Session aggregates, workspaces, the root-scoped kernel Artifact-input cache, snapshot/CAS references, and process-local state all go, and nothing outside that session's own scope is touched.
session_domain.py The high-level session-domain composition that route handlers call instead of assembling repositories themselves. It fronts checkpoints and cursor checkpoints, branches, timelines, export, renderers, package operations, and recovery.
session_package.py Creates and imports session ZIP packages, deterministically and with checksums. Secret filtering, traversal checks, and a quarantine staging area guard the transfer. Import validates the whole archive before it creates anything, and the imported session lands on an ended kernel generation, an explicit view-only/recovery boundary.
session_recovery.py Reconciles stale runtime state at startup and sweeps idle kernels deterministically, subject to activity and recovery blockers. A generation an older daemon left live is marked abandoned and stays auditable; nothing here deserializes objects or claims that memory survived.
session_runtime.py Holds the session's control-plane objects, such as the dispatcher, delegation trees, and dynamic capabilities, so a language worker can be started, replaced, or stopped without discarding them.
share_projection.py Builds one frozen, flattened ShareProjection of a session (single synthetic root, no checkpoints, no memories/policy) and serializes it two ways: an import_bytes-compatible bundle and a redacted viewer document. Reuses the session-package fail-closed secret gate.
share_router.py The read-only public request handler for one share: GET/HEAD only, exactly two read roots (in-memory viewer assets + the current leased snapshot), strict CSP, single-range support, and a uniform 404. It never touches the kernel, dispatcher, or a gateway route.
share_service.py Two-phase publish for web shares (DB state machine + immutable version dirs + a current.json pointer) with SnapshotLease refcount GC, crash recovery, expiry sweeping, and revoke. FIFO admission and the tunnel client are injected.
skill_sidecars.py Consumes private worker Skill-load diagnostics but refuses to treat same-interpreter claims as recovery evidence. It marks the exact generation's sidecar capture failed with compare-and-swap, so recovery stops instead of replaying an unproven sidecar. The Host process never imports or executes a sidecar.
skill_network_admission.py Binds loaded Skill network manifests to a session and admits the two execution sinks: the next Python/R Cell and shell capability issuance. Intersects manifest × measured sandbox posture × Host egress × caller bindings. Manifests never grant.
skills.py The Web Customize lifecycle for user-authored Skill documents. It covers CRUD and import, the catalog projection the UI reads, and capability enablement.
file_area.py The team file area's path policy (M1-8): OPENAI4S_DATA_ROOTS names the only reachable directories, and every operation goes through one resolver whose containment check runs on the resolved path — after symlinks and .. — so no spelling of an outside path passes, and "outside" answers the same sentence as "does not exist". Upload targets add a plain-filename rule (no separators, no dot-dot, no symlink hop), so an upload can create a file without being usable to escape.
file_routes.py GET /files, GET /files/download, POST /files/upload as a validated RouteSpec table. The upload genuinely streams: the gateway's whole-body pre-read is skipped for exactly this path and the handler copies rfile to a same-directory temp file in 1 MiB chunks before an atomic os.replace — 512 MiB never transits daemon memory, and a truncated body never leaves a half-written file under the final name. Guests are read-nothing here (D3: replay only).
team_auth.py Team-mode authentication (OPENAI4S_TEAM_MODE): the os_user login cookie, a per-(username, ip) token-bucket limiter on login attempts — consulted before the PBKDF2 work, so the limiter also bounds what an attacker can make the daemon compute — and the loopback-CLI service identity that keeps the daemon access token usable by the server-side management CLI without impersonating any human account. The gateway disables that machine identity when a trusted proxy topology makes all clients loopback peers. Every login outcome is audited (INV-12).
team_routes.py The auth routes (/auth/login, /auth/logout, /auth/me) in the kernel_routes shape: a validated RouteSpec table read by both the runtime dispatch and the contract inventory. Deterministic in both modes — with team mode off they answer stable disabled shapes, which is what the contract capture freezes. Set-Cookie rides _send's sanitized header path; the raw session token exists only there and in the client's jar.
titles.py Generates a session title in the background from the first message. Model configuration is bound late, and persistence and broadcast are race-safe.
trusted_capture.py Owns the Stage 1 per-session admission boundary among foreground Artifact capture, independent background kernels, and person-facing external workspace mutations. Same-thread capture and mutation lifetimes may each nest within their own class; another owner or any cross-class overlap fails closed before workspace work begins.
urls.py Canonical server-owned URLs for durable resources. Trusted completion accepts only a non-empty exact version id and percent-encodes it below the reserved /api/v1/artifacts/versions/{version_id} namespace, which has no Artifact-id/filename fallback; the flag-off legacy helper remains isolated here rather than duplicated in completion prose.
variable_inspector.py Reads a live, idle Python/R namespace through a narrow manager request and returns bounded, sanitized variable previews. It creates no session and no worker, and it never enters the Cell transaction.
volcengine_arkcli.py Runs the fixed official Ark CLI command set with an allowlisted child environment, bounded output, validated JSON, safe error projection, and no shell interpolation.
volcengine_connector.py Projects Volcengine identity, Plan/quota, API Key, and Endpoint state for the Web app; owns the browser OAuth session and the one-time handoff of a raw Ark key to ModelProfileService and the SecretBroker. Cloud identifiers are represented by process-local opaque choices in public responses.
workbench_state.py Projects the Context and Security panels from persistent and live state. It exposes no message content, and it does not claim an OS sandbox exists before a real worker has reported its self-test result.
ws_frames.py The hardened RFC 6455 frame codec shared by the gateway WebSocket and the share tunnel. Role-aware reads validate mask direction, FIN, RSV, opcode, canonical length, the 64-bit high bit, control-frame size, and a payload cap; the gateway keeps its old call sites through aliases.

Subdirectories

Directory Responsibility
webui/ Gateway static tree: committed Vite dist/ from ../../frontend/ (the default SPA shell), shared classic scripts (style.css, theme-bootstrap.js, scientific_renderers.js, favicon.js), vendored 3Dmol/Ketcher/fonts, satellite pages, and the frozen index.html + app.js hatch (OPENAI4S_WEBUI=legacy). Third-party libraries live only in webui/vendor/: 3Dmol for 3D structures, and a pinned Ketcher 3.7.0 standalone for 2D editing when Stage 9 is on. The retry against the 3Dmol.org CDN was removed, because it silently executed third-party script in the page that holds the session cookie. The read-only share viewer is a second, separate client under webui/share/.

Change guidance

See the repository architecture guide, Web application guide, and the webui/ README.