Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
32b661e
Read Core's address from config.json on the Nodes page
SaladDay Sep 25, 2026
9f8e26f
Show how to call Core under a newly issued key
SaladDay Sep 25, 2026
d26ddd2
Show installation facts and startup settings on System
SaladDay Sep 25, 2026
9642ec5
Cover the public address and installation read in browser acceptance
SaladDay Sep 25, 2026
b2023f8
Describe the read-only public address and installation read in Web docs
SaladDay Sep 25, 2026
76948f5
Match the Web to the backend's installation and node address client
SaladDay Sep 25, 2026
11c1645
Name every model provider source on the how-to-call card
SaladDay Sep 25, 2026
39400cd
Name the nodes on an old Core address from the node list
SaladDay Sep 25, 2026
d8b620e
Export the how-to-call variables together and report failed copies
SaladDay Sep 25, 2026
e71c1db
Say why Save waits and when the address is loopback in the wizard
SaladDay Sep 25, 2026
aed8f99
Answer the fixture's address errors as Core does
SaladDay Sep 25, 2026
09acacd
Describe the public address without assuming HTTPS on System
SaladDay Sep 25, 2026
36f340a
Describe the E2B address rule as loopback, not HTTPS
SaladDay Sep 25, 2026
b6aa8de
Hold each System section's place while it loads, announced once
SaladDay Sep 25, 2026
3e9217d
Follow Core's deployment write order and counts in the fixture
SaladDay Sep 25, 2026
72b4743
Polish the stale-node list, copy labels and SDK pin
SaladDay Sep 25, 2026
30cbf38
Match Core's current E2B public URL message in the fixture
SaladDay Sep 25, 2026
697c6a9
Show a saved Agent's model provider in the console (#137)
SaladDay Sep 25, 2026
a1b623f
Merge origin/main (#137) into the 1a Web branch
SaladDay Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions apps/web/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,17 @@ request runs.
red underneath. The issued key appears in a read-only field with a copy button,
under a notice that it is shown once; only "I've saved this key" dismisses it.
Closing the dialog moves the key into a pending notice card on the page.
- **How to call**: wherever a new key is shown, a card under it gives three
copyable samples, each a Margin Gray block with a Hairline and its label and copy
button in a header row: a Shell block exporting `OPENAI_BASE_URL` (the
installation's API base URL) and `OPENAI_API_KEY` (the new key) together, a curl
request and a Python example with the pinned SDK. A copy the clipboard refuses
selects the sample and says so in red underneath. One Graphite line says running
an Agent needs a model provider: in each request, saved on the Agent, or the
deployment default. When the public address is loopback, a note above the
samples says the API is reachable only on the Core machine; without a public
address only a note to set one shows. Before the installation is read, a
skeleton holds the first sample's place.

### Navigation
Sidebar groups Monitor, Resources and Platform with 12px Graphite group labels;
Expand Down Expand Up @@ -567,7 +578,9 @@ data on screen, projects that could not be read, and other failed actions are
reported in an error toast with the reason. Only when a page or section has nothing to show
does an error state take the place of its content; errors inside a dialog or a form
stay beside what they concern. Coverage notes (Margin Gray, Hairline, 12px corners,
12.5px Graphite) state bounded aggregation. Partial-data chips are amber-tinted pills
12.5px Graphite) state bounded aggregation. A standing warning that needs action,
such as the Nodes page naming nodes still bound to an old Core address, is an
amber-tinted line at the top of the page body. Partial-data chips are amber-tinted pills
with a help tip. Safety notices (a key shown once, a destructive consequence) stay
visible in body text.

Expand Down Expand Up @@ -617,13 +630,29 @@ each sandbox takes the template build's size), then a review.
Choices are large cards that advance on a click; short indigo dashes show the
progress; pages slide and blur across. The review states where sandboxes run,
the size, the Runtime (taken from this console's distribution manifest) and the
Core address, asking for it only when the console's own address cannot serve.
Core address, read-only: it is config.json's `public_url`, and the console never
asks for it. A loopback address carries an amber line under it: only the Core
machine reaches it. When Core rejects the configuration for it (E2B with a
loopback `public_url`), a red-tinted block under the review keeps Core's message
and adds the config file and apply command as copyable values. A save attempt
clears the E2B key, so the review then says to enter it again, with a link to
that step.
Advanced settings, one link away, hold the complete form: resources (not for
E2B), the Core address, the Runtime release and the E2B template. A change keeps the saved size
E2B), the Runtime release and the E2B template. A change keeps the saved size
and Runtime while the backend stays the same (a saved size outside the presets is
offered as Current); another backend starts from its standard size and this
console's Runtime, and E2B always needs its key again. Rules sit behind help tips.

### System page
Read-only facts in three sections, each saying where it changes. Installation: the
public address, API base URL, installation ID and source commit as a fact card.
Sandboxes: the shared sandbox configuration, with a "Change on the Nodes page"
text action in the section header. Startup settings: a line naming the config file
and the apply command as copyable chips, with when they were last applied, over a
table of each setting, its value and the services a change restarts. Sensitive
settings show only Configured or Not set; Default and Fixed after install are
neutral pills beside the value.

### Loading and motion
The console has no spinners and no "Loading…" lines. Reads are cached (TanStack
Query) and prefetched on navigation hover, so revisits show data at once and
Expand Down
14 changes: 9 additions & 5 deletions apps/web/PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ workbench.
template build's), a review, and advanced settings
with the complete form — then the node list with each node's capacity, host
figures and allocations, enrollment, renaming, sandbox limits and removal; Add node
asks for the node's sandbox limits before it issues the one-time command), System (only the sandbox
configuration every project shares: where sandboxes run, each sandbox's size, the
Runtime or E2B template build, microsandbox's idle suspension, the Core address and maintenance).
asks for the node's sandbox limits before it issues the one-time command), System (read-only: the
installation's public address, API base URL, installation ID and source commit; Core's
startup settings from config.json, with the file and the apply command that change them;
and the sandbox configuration every project shares, with a link to Nodes where it changes).
- A node whose provider is not ready names the reason (Docker unreachable, no Docker
limits, missing Runtime image, no KVM, missing microsandbox components, a host too
small) and its fix in the help tip beside its status, wherever that status shows.
Expand Down Expand Up @@ -113,7 +114,8 @@ workbench.
its named API keys; projects do not see each other's assets. Issuing or revoking
a key never touches assets. Archiving a project revokes every key and keeps its
assets viewable and deletable. Key plaintext is shown once, at issuance, and never
stored by the console.
stored by the console. Beside it the console tells developers to set
`OPENAI_BASE_URL` (the installation's API base URL) and `OPENAI_API_KEY` (the key).
- **Web API only.** Every read and write goes through `/core/v1/**`. The console
holds no API key and sends nothing to `/v1`.
- **No asset writes except delete.** Assets are created and changed only by
Expand All @@ -122,7 +124,9 @@ workbench.
Sessions, send input or cancel work. Deletion follows the public deletion rules;
a busy Session is not deletable and the console never cancels work to make it so.
- **Secrets stay write-only.** Credential tokens, Template environment variables and
setup commands are never returned, to the administrator included.
setup commands are never returned, to the administrator included. An Agent's saved
model provider shows its protocol, base URL, limits and whether a key is configured,
never the key.
- **Creators.** Core records the key behind every write. The console shows the
creating key of each asset and a project's write history; an asset an
administrator copied in an earlier release shows as Admin copy and an asset
Expand Down
9 changes: 6 additions & 3 deletions apps/web/e2e/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ export const FIXTURE_CORE_KEY = "fixture-core-key-3f9a2c71";

/**
* Fixture state options: `fresh` is a new install (no project, Session or Runtime),
* `sandbox` the sandbox deployment, and `nodes: "none"` a deployment no node has joined.
* `sandbox` the sandbox deployment, `nodes: "none"` a deployment no node has joined, and
* `installation` how config.json's public_url is set: "public" (HTTPS, the default), "local"
* (loopback: only the Core machine reaches the API, and E2B is rejected) or "stale" (public,
* with a node enrolled with an earlier address).
*/
export interface FixtureOptions { fresh?: boolean; sandbox?: "configured" | "none" | "e2b"; nodes?: "none" }
export interface FixtureOptions { fresh?: boolean; sandbox?: "configured" | "none" | "e2b"; nodes?: "none"; installation?: "public" | "local" | "stale" }

/** Fresh fixture state: signed out ("login") or already signed in ("authenticated"). */
export async function resetFixture(request: APIRequestContext, auth: "login" | "authenticated" = "authenticated", options: FixtureOptions = {}) {
await request.post(`${fixture}/__fixture/reset?auth=${auth}${options.fresh ? "&projects=none" : ""}&sandbox=${options.sandbox ?? "configured"}${options.nodes ? `&nodes=${options.nodes}` : ""}`);
await request.post(`${fixture}/__fixture/reset?auth=${auth}${options.fresh ? "&projects=none" : ""}&sandbox=${options.sandbox ?? "configured"}${options.nodes ? `&nodes=${options.nodes}` : ""}&installation=${options.installation ?? "public"}`);
}

/** Opens the console already signed in, in English. */
Expand Down
24 changes: 22 additions & 2 deletions apps/web/e2e/data/agents.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,28 @@ export const agentDefinitions = [
},
];

/** Which project owns each definition (index into the demo projects). */
export const agentProject = [0, 1, 2, 0, 1, 0, 1, 1, 2, 3];
/**
* Saved Agents with a model provider, as Core reads them: whether a key is saved,
* never the key. One saved only a provider, so it has no harness. No Session uses
* them, so the demo Sessions stay the same.
*/
export const providerAgentDefinitions = [
{
id: "agent_11c4f2a8", name: "Spec drafter", model: "MiniMax-M2", reasoning: {},
instructions: "Draft a product spec from the linked discussion: problem, users, scope, open questions.",
tools: [], metadata: { team: "product" },
x_agents_core: { model_provider: { protocol: "anthropic", base_url: "https://api.minimaxi.com/anthropic", api_key_configured: true, context_window: 204800, max_output_tokens: 65536 } },
},
{
id: "agent_12d7e3b9", name: "Research assistant", model: "kimi-k2", reasoning: {},
instructions: "Answer research questions with cited sources and a one-paragraph summary.",
tools: [], metadata: { team: "research" },
x_agents_core: { harness: "codex", model_provider: { protocol: "responses", base_url: "https://api.moonshot.cn/v1", api_key_configured: true } },
},
];

/** Which project owns each definition (index into the demo projects); the provider Agents come last. */
export const agentProject = [0, 1, 2, 0, 1, 0, 1, 1, 2, 3, 0, 0];

/** Prompt and reply pairs per Agent so Session transcripts read like real work. */
export const agentConversations = {
Expand Down
8 changes: 7 additions & 1 deletion apps/web/e2e/data/routes.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Synthetic deployment data for the browser acceptance fixture: Sessions, Turns, Items, nodes.
import { agentConversations, agentDefinitions } from "./agents.mjs";
import { agentConversations, agentDefinitions, providerAgentDefinitions } from "./agents.mjs";
let seed = 42;
const rand = () => ((seed = (seed * 1664525 + 1013904223) % 4294967296) / 4294967296);
const pick = (list) => list[Math.floor(rand() * list.length)];
Expand Down Expand Up @@ -99,5 +99,11 @@ export function buildDemo(now = Math.floor(Date.now() / 1000)) {
const sleeping = allocation.compute_phase === "suspended";
return { ...base, environment_id: session.environment.id, mode: "openai_hosted", provider_type: "docker", instance: { kind: "managed_allocation", allocation_id: allocation.id, device_id: null, connection_generation: null }, lifecycle_state: sleeping ? "sleeping" : "active", status: "observed", reason: null, allocation_created_at: session.created_at, observed_at: now - 2, started_at: session.created_at, cpu: sleeping ? null : { usage_seconds_total: 600 + Math.floor(rand() * 4000), capacity_cores: 2, usage_cores: Number((rand() * 1.6).toFixed(2)), utilization_ratio: null }, memory: sleeping ? null : { usage_bytes: Math.floor((0.4 + rand() * 1.4) * 2 ** 30), limit_bytes: 2 * 2 ** 30 } };
});
// Added after everything else is generated, so the seeded data above does not change.
agents.push(...providerAgentDefinitions.map((definition, index) => ({
...definition, object: "agent", multi_agent: { enabled: false, max_concurrent_subagents: null }, service_tier: "auto",
text: { format: { type: "text" }, verbosity: "medium" },
created_at: now - 86400 * (3 + index), updated_at: now - 1800 * (index + 1),
})));
return { agents, sessions, turns, items, nodes, allocations, observations };
}
Loading
Loading