Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 32 additions & 0 deletions DEVLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,38 @@

> Append-only session log. Read at session start. Update at session end.

## 2026-10-19 — AXI-212 build labels and runtime-first setup

- Reused the shared system.buildInfo VersionChip in the admin header instead
of optional props with a vdev fallback. Header/footer show a bounded
seven-character SHA; their full build, release and timestamp remain in the
title/accessibility description. Removed false dev placeholders from other
admin version panels when data is unavailable.
- Managed setup now starts with an explicit choice between connecting a
runtime and configuring an agent identity. Host registration requires only
an authorized home workspace, not an existing agent. Existing runtime
browsing and personal/agent MCP credential paths remain separate. Runtime
registration opens the existing host form and preserves server permission
checks; opening the flow never registers a host or dispatches work.
- Added focused build-label, setup-rendering and routing/role regression
coverage. No schema changes, production configuration changes or release.
- Initial visual verification was blocked by the in-app browser and Docker's
Inference Manager startup during an operator update. After the update,
verified against a fresh local fixture database and an authorized isolated
Playwright browser. No Docker data was reset or deleted.
- Rendered review caught a related mobile admin sidebar clipping issue: its
navigation now scrolls independently and preserves the footer. Both build
chips fit in 129 CSS pixels with the full SHA retained in their descriptions;
the 390px page has no horizontal overflow. Verified runtime registration
(Hermes/Codex choices), existing-agent routing, and personal MCP token setup
without creating credentials, registering hosts, or dispatching runs.
- Focused regression suite: 19 tests, final TypeScript and focused ESLint all
passed. Desktop/mobile captures are kept
in local output/playwright; the final browser page reported zero console
errors or warnings. Operator accepted the local iteration and authorized
commit, PR, merge and a separately assembled v0.36.1 patch release. Hosted
checks must pass on each exact PR head before merge.

## 2026-10-15 — AXI-210 v0.36.0 release assembly

- Assembled the operator-approved release from implementation PR #104,
Expand Down
23 changes: 20 additions & 3 deletions docs/guide/navigation-and-agent-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,18 @@ Begin in **Agents** and choose the execution intent:
the work. Choose the workspace and either your personal identity or an agent.
Personal credentials act as the current user. Agent credentials identify the
agent and record the issuing operator. No managed runtime is required.
- **Let Forge run an agent**: choose the workspace and agent, then configure
its managed execution host. Workspace owner/admin authority is required for
managed setup; instance administration alone is not workspace membership.
- **Connect a managed runtime**: choose **Connect a runtime** and its home
workspace, then continue to the runtime-host form. Choose the supported
adapter (for example Hermes or Codex app server) and supply the host's
connection details. No existing agent identity is required. You can also
view existing runtimes without opening a new registration.
- **Configure an agent** inside managed setup: choose an existing identity or
create/request one, then review its workspace access and runtime. Agent
identities and hosts are separate: several identities can use one host.

Workspace owner/admin authority is required for managed setup; instance
administration alone is not workspace membership. Opening setup does not
create a credential, register a host, grant access, or dispatch work.

An existing agent with workspace access can continue directly to its scoped
MCP credential setup. Otherwise its detail page explains how an authorized
Expand All @@ -35,6 +44,14 @@ instance policy under Administration → Agent governance. The compatibility
default remains approval required; changing policy does not approve existing
requests, share identities, or grant workspace membership.

## Running build

The admin header and shell footers use the same `system.buildInfo` source.
They show the running version and a seven-character commit identifier in a
bounded single line. Hover/accessibility descriptions retain the full commit,
release and build timestamp; the link opens What's New. While build metadata
is unavailable the label says Forge, not an invented development version.

## Ownership, availability and authorization

These are separate facts:
Expand Down
191 changes: 124 additions & 67 deletions src/app/(app)/settings/agents/agent-setup-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { useState } from "react";
import { Button } from "@/components/ui/button";
import { CenterModal } from "@/components/ui/modal";
import { Combobox } from "@/components/ui/combobox";
import { agentSetupDestination, type AgentSetupMode } from "@/lib/agent-setup";
import {
agentSetupDestination,
type AgentSetupMode,
type ManagedSetupTask,
} from "@/lib/agent-setup";
import {
personalAgentCreationCopy,
type PersonalAgentCreationPolicy,
Expand Down Expand Up @@ -49,10 +53,12 @@ export function AgentSetupDialog({
: "",
);
const [profileId, setProfileId] = useState("");
const [managedTask, setManagedTask] = useState<ManagedSetupTask>("runtime");
const choosingRuntime = mode === "managed" && managedTask === "runtime";
const workspace = workspaces.find((item) => item.id === workspaceId);
const profile = profiles.find((item) => item.id === profileId);
const canManage = workspace?.role === "OWNER" || workspace?.role === "ADMIN";
const destination = agentSetupDestination({ mode, workspace, profile });
const destination = agentSetupDestination({ mode, workspace, profile, managedTask });
const binding = profile?.bindings.find((item) => item.workspace.id === workspaceId);
const creation = personalAgentCreationCopy(personalCreationPolicy);

Expand All @@ -63,11 +69,11 @@ export function AgentSetupDialog({
if (!open) onClose();
}}
size="md"
title={mode === "external" ? "Connect an external client" : "Let Forge run an agent"}
title={mode === "external" ? "Connect an external client" : "Set up managed execution"}
description={
mode === "external"
? "Use Codex Desktop, a CLI, or another MCP client. Your client executes the work; no Forge runtime is required."
: "Forge dispatches work to a managed execution runtime. Choose the workspace and agent before configuring its host."
: "Connect a Hermes or Codex execution host first, or configure an agent identity to use a runtime. These are separate steps."
}
footer={
<div className="flex justify-end gap-2">
Expand All @@ -79,19 +85,61 @@ export function AgentSetupDialog({
className="focus-ring inline-flex h-8 items-center rounded-md bg-ember px-3 text-xs font-medium text-ember-foreground"
href={destination}
>
Continue
{choosingRuntime ? "Connect runtime" : "Continue"}
</Link>
) : (
<Button variant="ember" size="sm" disabled>
Continue
{choosingRuntime ? "Connect runtime" : "Continue"}
</Button>
)}
</div>
}
>
<div className="space-y-4">
{mode === "managed" && (
<fieldset className="space-y-2">
<legend className="text-sm font-medium">What would you like to set up?</legend>
{(
[
[
"runtime",
"Connect a runtime",
"Register a Hermes, Codex, or other supported host. No agent identity is required yet.",
],
[
"agent",
"Configure an agent",
"Choose or create an identity, then give it workspace access and an execution runtime.",
],
] as const
).map(([value, label, hint]) => (
<label
key={value}
className="flex cursor-pointer items-start gap-2 rounded-md border border-border p-3 hover:bg-subtle/40"
>
<input
type="radio"
name="managed-setup-task"
value={value}
checked={managedTask === value}
onChange={() => {
setManagedTask(value);
setProfileId("");
}}
className="mt-0.5 accent-[hsl(var(--ember))]"
/>
<span className="min-w-0">
<span className="block text-sm font-medium">{label}</span>
<span className="text-meta block text-muted-foreground">{hint}</span>
</span>
</label>
))}
</fieldset>
)}
<div className="space-y-1.5">
<p className="text-sm font-medium">Workspace access</p>
<p className="text-sm font-medium">
{choosingRuntime ? "Runtime home workspace" : "Workspace access"}
</p>
<Combobox
ariaLabel="Setup workspace"
value={workspaceId || null}
Expand All @@ -108,50 +156,57 @@ export function AgentSetupDialog({
className="w-full"
/>
<p className="text-meta text-muted-foreground">
Credentials and permissions apply only to this workspace. You can add other workspaces
separately.
</p>
</div>
<div className="space-y-1.5">
<p className="text-sm font-medium">Acting identity</p>
<Combobox
ariaLabel="Setup agent identity"
value={profileId || null}
onChange={(value) => setProfileId(value ?? "")}
options={[
...(mode === "external"
? [{ value: "", label: "My account", secondary: "Personal MCP token" }]
: []),
...profiles
.filter(
(item) =>
item.availability.state === "AVAILABLE" &&
(isInstanceAdmin || item.ownedByMe || item.instanceShared),
)
.map((item) => ({
value: item.id,
label: item.name,
secondary:
item.ownerType === "SERVICE"
? "Service-owned"
: item.ownedByMe
? "Owned by you"
: "Available agent",
})),
]}
placeholder={
mode === "external" ? "My account · Personal MCP token" : "Choose an agent…"
}
className="w-full"
/>
<p className="text-meta text-muted-foreground">
{profile
? "The agent is the acting identity. The issued key also records the human operator; sharing an identity does not grant workspace access."
: mode === "external"
? "Actions are attributed to your user account. Choose an agent above for a separate agent identity."
: "Choose an existing agent or create/request a new identity below."}
{choosingRuntime
? "Choose where to register and manage the host. Registration alone does not grant agents access or start any work."
: "Credentials and permissions apply only to this workspace. You can add other workspaces separately."}
</p>
</div>
{!choosingRuntime && (
<div className="space-y-1.5">
<p className="text-sm font-medium">
{mode === "external" ? "Act as · agent identity is optional" : "Agent identity"}
</p>
<Combobox
ariaLabel="Setup agent identity"
value={profileId || null}
onChange={(value) => setProfileId(value ?? "")}
options={[
...(mode === "external"
? [{ value: "", label: "My account", secondary: "Personal MCP token" }]
: []),
...profiles
.filter(
(item) =>
item.availability.state === "AVAILABLE" &&
(isInstanceAdmin || item.ownedByMe || item.instanceShared),
)
.map((item) => ({
value: item.id,
label: item.name,
secondary:
item.ownerType === "SERVICE"
? "Service-owned"
: item.ownedByMe
? "Owned by you"
: "Available agent",
})),
]}
placeholder={
mode === "external" ? "My account · Personal MCP token" : "Choose an agent…"
}
className="w-full"
/>
<p className="text-meta text-muted-foreground">
{profile
? mode === "external"
? "The agent is the acting identity. The issued key also records the human operator; sharing an identity does not grant workspace access."
: "This is who does the work, not the host that executes it. Continue to review workspace access and its runtime."
: mode === "external"
? "Actions are attributed to your user account. Choose an agent above for a separate agent identity."
: "Choose an existing agent or create/request a new identity below."}
</p>
</div>
)}
{workspace?.role === "GUEST" && (
<p className="text-sm text-warning">
Guests cannot create credentials. Ask a workspace administrator for access.
Expand All @@ -175,26 +230,28 @@ export function AgentSetupDialog({
href={`/w/${encodeURIComponent(workspace.slug)}/settings/runtimes`}
className="focus-ring inline-flex text-sm text-ember hover:underline"
>
Register or inspect a runtime in {workspace.name} →
View existing runtimes in {workspace.name} →
</Link>
)}
<div className="border-t border-border pt-3">
<Button
size="sm"
variant="outline"
disabled={
!workspace || workspace.role === "GUEST" || (!isInstanceAdmin && !creation.enabled)
}
onClick={() => onCreate(workspaceId)}
>
{isInstanceAdmin ? "Create a new agent" : creation.label}
</Button>
<p className="text-meta mt-2 text-muted-foreground">
{isInstanceAdmin
? "Choose personal or service ownership when creating an identity. Workspace access and catalog sharing remain separate controls."
: creation.description}
</p>
</div>
{!choosingRuntime && (
<div className="border-t border-border pt-3">
<Button
size="sm"
variant="outline"
disabled={
!workspace || workspace.role === "GUEST" || (!isInstanceAdmin && !creation.enabled)
}
onClick={() => onCreate(workspaceId)}
>
{isInstanceAdmin ? "Create a new agent" : creation.label}
</Button>
<p className="text-meta mt-2 text-muted-foreground">
{isInstanceAdmin
? "Choose personal or service ownership when creating an identity. Workspace access and catalog sharing remain separate controls."
: creation.description}
</p>
</div>
)}
</div>
</CenterModal>
);
Expand Down
8 changes: 5 additions & 3 deletions src/app/(app)/settings/agents/agents-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,10 @@ export function AgentsContent({
</div>
<div className="rounded-lg border border-border bg-card/40 p-4">
<Server className="mb-2 h-4 w-4 text-ember" />
<h2 className="text-sm font-semibold">Let Forge run an agent</h2>
<h2 className="text-sm font-semibold">Connect a managed runtime</h2>
<p className="text-meta mb-3 mt-1 text-muted-foreground">
Choose an identity and runtime for work dispatched and monitored by Forge.
Register a Hermes or Codex host, then choose which agent identities use it. An
existing agent is not required to connect a runtime.
</p>
<Button size="sm" variant="outline" onClick={() => setSetupMode("managed")}>
Set up managed execution
Expand All @@ -202,7 +203,8 @@ export function AgentsContent({
<Link2 className="h-4 w-4 text-ember" /> Agent identities
</div>
<p className="text-meta mt-1 text-muted-foreground">
Ownership, catalog availability, and workspace access are separate.
These are identities, not runtime hosts or MCP clients. Multiple identities can use
the same runtime.
</p>
</div>
<div className="flex shrink-0 items-center gap-2 text-xs text-muted-foreground">
Expand Down
Loading
Loading