diff --git a/backend/__tests__/service/agent-manage-settings.test.js b/backend/__tests__/service/agent-manage-settings.test.js
index e569d8a0f1..8c5c3a42e2 100644
--- a/backend/__tests__/service/agent-manage-settings.test.js
+++ b/backend/__tests__/service/agent-manage-settings.test.js
@@ -7,9 +7,11 @@ const Pod = require('../../models/Pod');
const { AgentInstallation } = require('../../models/AgentRegistry');
const Machine = require('../../models/Machine');
const AgentMemory = require('../../models/AgentMemory');
+const AgentRun = require('../../models/AgentRun');
const manageSettings = require('../../routes/registry/manage-settings');
const podAgents = require('../../routes/registry/pod-agents');
const agentConfig = require('../../routes/registry/agent-config');
+const tasksApi = require('../../routes/tasksApi');
const { projectSeatEnvironments, seatEnvironmentKey } = require('../../services/seatEnvironmentProjection');
const SECRET = 'agent-manage-settings-test';
@@ -27,6 +29,7 @@ describe('per-installation agent settings', () => {
app.use('/api/registry', manageSettings);
app.use('/api/registry', podAgents);
app.use('/api/registry', agentConfig);
+ app.use('/api/tasks', tasksApi);
owner = await User.create({ username: 'settings-owner', email: 'settings-owner@test.com', password: 'password123' });
other = await User.create({ username: 'settings-other', email: 'settings-other@test.com', password: 'password123' });
admin = await User.create({ username: 'settings-admin', email: 'settings-admin@test.com', password: 'password123', role: 'admin' });
@@ -38,6 +41,7 @@ describe('per-installation agent settings', () => {
beforeEach(async () => {
await AgentInstallation.deleteMany({ agentName: 'settings-agent' });
+ await AgentRun.deleteMany({ agentName: 'settings-agent' });
await AgentInstallation.create([
{ agentName: 'settings-agent', instanceId: 'same', podId: podA._id, version: '1', installedBy: owner._id,
config: new Map([['wakeOnMessage', { enabled: true, humanOnly: true }], ['boardWake', { enabled: false }], ['runtime', { runtimeType: 'wrapper' }]]) },
@@ -69,6 +73,87 @@ describe('per-installation agent settings', () => {
expect(otherResponse.body.writablePodId).toBe(String(podB._id));
});
+ it('reads the actual latest run model only within the managed owner and pod', async () => {
+ const base = { agentName: 'settings-agent', instanceId: 'same', trigger: 'manual', status: 'succeeded' };
+ await AgentRun.create([
+ { ...base, userId: String(owner._id), podId: podA._id, startedAt: new Date('2026-10-25T01:00:00Z'), turns: [{ turnIndex: 0, model: 'actual-model', elapsedMs: 1 }] },
+ { ...base, userId: String(other._id), podId: podA._id, startedAt: new Date('2026-10-25T02:00:00Z'), turns: [{ turnIndex: 0, model: 'private-owner-model', elapsedMs: 1 }] },
+ { ...base, userId: String(owner._id), podId: podB._id, startedAt: new Date('2026-10-25T03:00:00Z'), turns: [{ turnIndex: 0, model: 'private-pod-model', elapsedMs: 1 }] },
+ ]);
+ await AgentInstallation.updateOne({ podId: podA._id, agentName: 'settings-agent' }, {
+ $set: { 'config.runtime': { runtimeType: 'hosted', model: 'saved-but-never-run' } },
+ });
+ const response = await request(app).get('/api/registry/agents/settings-agent/manage-settings?instanceId=same')
+ .set('Authorization', `Bearer ${token(owner)}`);
+ expect(response.status).toBe(200);
+ expect(response.body.runtimeRun).toEqual(expect.objectContaining({ model: 'actual-model', status: 'succeeded' }));
+ expect(JSON.stringify(response.body.runtimeRun)).not.toContain('private-owner-model');
+ expect(JSON.stringify(response.body.runtimeRun)).not.toContain('private-pod-model');
+ await AgentRun.create({ ...base, userId: String(owner._id), podId: podA._id,
+ startedAt: new Date('2026-10-25T04:00:00Z'), status: 'failed', turns: [] });
+ const failed = await request(app).get('/api/registry/agents/settings-agent/manage-settings?instanceId=same')
+ .set('Authorization', `Bearer ${token(owner)}`);
+ expect(failed.body.runtimeRun.model).toBeNull();
+ await AgentRun.deleteMany({ agentName: 'settings-agent' });
+ });
+
+ it('reads the hosted engine meter and omits usage for non-hosted installs', async () => {
+ await AgentRun.create({ agentName: 'settings-agent', instanceId: 'same', trigger: 'manual',
+ status: 'succeeded', userId: String(owner._id), podId: podA._id, startedAt: new Date(), turns: [] });
+ const url = '/api/registry/agents/settings-agent/manage-settings?instanceId=same';
+ const read = () => request(app).get(url).set('Authorization', `Bearer ${token(owner)}`);
+ await AgentInstallation.updateOne({ podId: podA._id, agentName: 'settings-agent' }, {
+ $set: { 'config.runtime': { runtimeType: 'hosted' }, 'config.hosted': { engine: 'native', provisionedAt: new Date().toISOString() } },
+ });
+ const native = await read();
+ expect(native.status).toBe(200);
+ expect(native.body.usage).toEqual(expect.objectContaining({ used: 1, cap: 30 }));
+ await AgentInstallation.updateOne({ podId: podA._id, agentName: 'settings-agent' }, {
+ $set: { 'config.hosted': { engine: 'durable-object' } },
+ });
+ const worker = await read();
+ expect(worker.body.usage).toEqual(expect.objectContaining({ used: 0, cap: 30 }));
+ await AgentInstallation.updateOne({ podId: podA._id, agentName: 'settings-agent' }, {
+ $set: { 'config.runtime': { runtimeType: 'native' } },
+ });
+ expect((await read()).body.usage).toBeNull();
+ });
+
+ it.each(['runtime-token claim', 'instance-id claim'])('shows a held task to members only (%s)', async (claimForm) => {
+ const Task = require('../../models/Task');
+ await Task.deleteMany({ taskId: 'TASK-546' });
+ await Pod.updateOne({ _id: podB._id }, { $pull: { members: admin._id } });
+ await User.deleteMany({ username: 'settings-runtime-bot' });
+ const bot = await User.create({ username: 'settings-runtime-bot', email: 'settings-runtime-bot@test.com',
+ password: 'password123', isBot: true, botType: 'agent',
+ botMetadata: { agentName: 'settings-agent', instanceId: 'same' },
+ agentRuntimeTokens: [{ tokenHash: require('crypto').createHash('sha256').update('cm_agent_settings_claim_fixture').digest('hex'), label: 'test', createdAt: new Date() }] });
+ await Task.create({ podId: podA._id, taskNum: 546, taskId: 'TASK-546', title: 'Private title',
+ status: 'pending' });
+ await Pod.updateOne({ _id: podA._id }, { $addToSet: { members: bot._id } });
+ const claim = await request(app).post(`/api/tasks/${podA._id}/TASK-546/claim`)
+ .set('Authorization', 'Bearer cm_agent_settings_claim_fixture');
+ expect(claim.status).toBe(200);
+ expect((await Task.findOne({ podId: podA._id, taskId: 'TASK-546' })).claimedBy).toBe(String(bot._id));
+ if (claimForm === 'instance-id claim') {
+ await Task.updateOne({ podId: podA._id, taskId: 'TASK-546' }, { $set: { claimedBy: 'same' } });
+ }
+ const url = '/api/registry/agents/settings-agent/manage-settings?instanceId=same';
+ const member = await request(app).get(url).set('Authorization', `Bearer ${token(owner)}`);
+ expect(member.body.heldTask).toEqual({ taskId: 'TASK-546', podId: String(podA._id), podName: 'Settings A' });
+ expect(JSON.stringify(member.body.heldTask)).not.toContain('Private title');
+ // Keep memberPodIds nonempty: an all-or-nothing empty-list guard must not
+ // stand in for filtering each claimed task's own pod.
+ await Pod.updateOne({ _id: podB._id }, { $addToSet: { members: admin._id } });
+ expect((await Pod.findById(podA._id)).members.map(String)).not.toContain(String(admin._id));
+ const outsider = await request(app).get(url).set('Authorization', `Bearer ${token(admin)}`);
+ expect(outsider.status).toBe(200);
+ expect(outsider.body.installations.map((entry) => entry.podId)).toContain(String(podB._id));
+ expect(outsider.body.heldTask).toBeNull();
+ await Pod.updateOne({ _id: podB._id }, { $pull: { members: admin._id } });
+ await Task.deleteMany({ taskId: 'TASK-546' });
+ });
+
it('reads and writes the same effort declaration the daemon selects', async () => {
await Machine.create({ ownerUserId: owner._id, machineId: 'machine-a', name: 'Test computer',
inventoryAt: new Date(), runtimeInventory: [{ harness: 'claude', installed: true, version: '2.1', models: null, efforts: ['high', 'xhigh'] }] });
diff --git a/backend/routes/registry/manage-settings.ts b/backend/routes/registry/manage-settings.ts
index 4d8309ce09..bf74d20b05 100644
--- a/backend/routes/registry/manage-settings.ts
+++ b/backend/routes/registry/manage-settings.ts
@@ -1,11 +1,14 @@
// Owner/admin controls for one agent installation at a time.
import rateLimit from 'express-rate-limit';
import { cloudflareIpRateLimitKeyGenerator } from '../../middleware/ipRateLimit';
-import { hostedCapsForOwner, userRunCeilingForOwner } from '../../services/hostedRuntimeService';
+import { hostedCapsForOwner, userRunCeilingForOwner, meterAllowsTurn } from '../../services/hostedRuntimeService';
+import { hostedRunsNative } from '../../services/hostedEngine';
import { projectSeatEnvironments, seatEnvironmentKey } from '../../services/seatEnvironmentProjection';
const { ownKeyCapability } = require('../../services/agentProviderKey');
const Machine = require('../../models/Machine');
const AgentRuntimeObservation = require('../../models/AgentRuntimeObservation');
+const AgentRun = require('../../models/AgentRun');
+const Task = require('../../models/Task');
const express = require('express');
const auth = require('../../middleware/auth');
const { AgentInstallation } = require('../../models/AgentRegistry');
@@ -58,7 +61,7 @@ router.get('/agents/:name/manage-settings', settingsRateLimit, auth, async (req:
}).sort({ createdAt: 1, _id: 1 }).lean();
const manageable = installations.filter((entry: any) => canManage(entry, userId, isAdmin));
const pods = await Pod.find({ _id: { $in: manageable.map((entry: any) => entry.podId) } })
- .select('name').lean();
+ .select('name members').lean();
const names = new Map(pods.map((pod: any) => [String(pod._id), String(pod.name)]));
const visible = manageable.filter((entry: any) => names.has(String(entry.podId)));
const fallback = visible.find((entry: any) => entry.status === 'active') || visible[0];
@@ -89,7 +92,7 @@ router.get('/agents/:name/manage-settings', settingsRateLimit, auth, async (req:
isBot: true,
'botMetadata.agentName': String(req.params.name).toLowerCase(),
'botMetadata.instanceId': normalizeInstanceId(req.query.instanceId),
- }).select('botMetadata.machineId').lean();
+ }).select('botMetadata.machineId botMetadata.instanceId botMetadata.agentName').lean();
const machine = binding?.botMetadata?.machineId ? await Machine.findOne({
machineId: binding.botMetadata.machineId, ownerUserId: quotaOwner,
}).select('runtimeInventory inventoryAt lastSeenAt agentStates').lean() : null;
@@ -97,6 +100,23 @@ router.get('/agents/:name/manage-settings', settingsRateLimit, auth, async (req:
agentUserId: binding._id, machineId: binding.botMetadata.machineId,
}).select('connectedAt lastSeenAt lastSpawn lastSpawnOutcome turnRunning lastTurnAt -_id').lean() : null;
const runtimeManaged = Boolean(binding?.botMetadata?.machineId);
+ // A saved preference is not evidence of the model a turn actually used.
+ const run = !runtimeManaged && visible.length ? await AgentRun.findOne({
+ agentName: String(req.params.name).toLowerCase(),
+ instanceId: normalizeInstanceId(req.query.instanceId),
+ userId: String(quotaOwner),
+ podId: { $in: visible.map((entry: any) => entry.podId) },
+ }).sort({ startedAt: -1, _id: -1 }).select('startedAt completedAt status turns.model').lean() : null;
+ // Managing an installation does not grant access to its pod's task board.
+ const memberPodIds = pods.filter((pod: any) => (pod.members || [])
+ .some((member: any) => String(member) === String(userId))).map((pod: any) => pod._id);
+ // Claims persisted by seat callers may use the instance key; runtime-token
+ // claims at this boundary also use the bot ObjectId. Read both forms.
+ const claimKeys = binding ? [String(binding._id),
+ binding.botMetadata?.instanceId || binding.botMetadata?.agentName].filter(Boolean) : [];
+ const heldTask = binding && memberPodIds.length ? await Task.findOne({
+ podId: { $in: memberPodIds }, claimedBy: { $in: claimKeys }, status: 'claimed',
+ }).sort({ claimedAt: -1, _id: -1 }).select('taskId podId -_id').lean() : null;
if (runtimeManaged && !runtimeControls.runtimeType) runtimeControls.runtimeType = 'wrapper';
const runtimeSavedControls = { ...runtimeControls };
// A standalone `agent run` reads its local token record. Hosted/native
@@ -110,7 +130,11 @@ router.get('/agents/:name/manage-settings', settingsRateLimit, auth, async (req:
const [{ turnsPerDay }, userRunCeiling] = await Promise.all([
hostedCapsForOwner(quotaOwner), userRunCeilingForOwner(quotaOwner),
]);
+ const usage = normalizeConfigMap(fallback?.config)?.runtime?.runtimeType === 'hosted'
+ ? await meterAllowsTurn(req.params.name, normalizeInstanceId(req.query.instanceId), turnsPerDay,
+ { countRuns: hostedRunsNative(fallback) }) : null;
return res.json({ turnCap: turnsPerDay, userRunCeiling, podCount: installations.length,
+ usage,
activePodCount: installations.filter((entry: any) => entry.status === 'active').length,
writablePodId: writable ? String(writable.podId) : null,
runtimeSourcePodId: !runtimeManaged ? null
@@ -120,6 +144,10 @@ router.get('/agents/:name/manage-settings', settingsRateLimit, auth, async (req:
runtimeSavedControls,
ownKeyCapability: runtimeManaged ? ownKeyCapability(runtime, environment) : { available: false, provider: null },
runtimeObservation: observation ? { ...observation, confirmed: null } : null,
+ runtimeRun: run ? { model: run.turns?.at(-1)?.model || null,
+ startedAt: run.startedAt, completedAt: run.completedAt || null, status: run.status } : null,
+ heldTask: heldTask ? { taskId: heldTask.taskId, podId: String(heldTask.podId),
+ podName: names.get(String(heldTask.podId)) } : null,
processConnected: machine ? Boolean(machine.lastSeenAt && Date.now() - new Date(machine.lastSeenAt).getTime() < 90_000
&& machine.agentStates?.some((entry: any) => entry.agentName === String(req.params.name).toLowerCase()
&& entry.instanceId === normalizeInstanceId(req.query.instanceId) && entry.state === 'running')) : null,
diff --git a/docs/agents/daemon-seat-state-surfaces.md b/docs/agents/daemon-seat-state-surfaces.md
index 9a4370ee79..0150ed25f4 100644
--- a/docs/agents/daemon-seat-state-surfaces.md
+++ b/docs/agents/daemon-seat-state-surfaces.md
@@ -263,3 +263,10 @@ ps -axo pid=,ppid=,args= | grep -E '[c]ommonlyai/mcp|mcp/0\.3'
- [review-checklist.md](../development/review-checklist.md) — rule 38: a read of
the installed artifact is not a read of the process running it, and the two
disagree for as long as the process outlives the file
+
+
+## Settings rev3 readbacks (TASK-546)
+
+The owner/admin manage-settings response keeps evidence separate from saved preferences. Machine-managed agents use the existing `runtimeObservation.lastSpawn`; hosted/native agents use `runtimeRun`, projected from the newest `AgentRun` for the managed owner and visible installation pods. `runtimeRun.model` is the last recorded turn's model. A run with no recorded model returns null; the UI must not substitute its saved model or a display default. The DTO contains no prompt, completion or tool arguments.
+
+`heldTask` contains only task ID and pod identity, and only where the viewer is a pod member. Installation administration alone does not reveal a board claim. Claims can contain the bot ObjectId or its instance key; readbacks accept both persisted forms. The service regression claims through the actual runtime-token task endpoint (which writes the ObjectId at this head), then separately checks the instance-key form. Do not infer the stored key from `resolveAgentInstanceId` alone: that helper reads `req.user`, while runtime authentication populates `req.agentUser`. Service coverage is in `agent-manage-settings.test.js`, including a newer foreign-owner run and a non-member instance admin. The rev3 evidence directory under `docs/design/evidence/` contains fixture captures; these do not replace the required non-author production walk.
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/README.md b/docs/design/evidence/agent-settings-rev3-2026-10-25/README.md
new file mode 100644
index 0000000000..3a5ad6301c
--- /dev/null
+++ b/docs/design/evidence/agent-settings-rev3-2026-10-25/README.md
@@ -0,0 +1,49 @@
+# Agent Settings, D44 rev3 section 05
+
+These are real Chromium captures of the local application with synthetic API responses. They are not production evidence. No real credentials are issued, read or revoked. All network API requests are intercepted.
+
+The fixtures cover BYO, hosted, built-in Scout and never-connected agents in English and Chinese at 1440 and 390. Each has a page capture and separate Status/Runtime captures. The hosted fixture exercises Pause and waits for Resume after the persisted readback changes. Every capture reported no browser errors or horizontal document overflow. The final capture run waits for and verifies IBM Plex Sans/Mono loading. The English BYO fixture also opens Your Team at both widths, verifies the name stays ink and links back to Settings, then follows it. Non-owner names retain the public-profile destination.
+
+Reference: section 05 of the rev3 artboard attached to TASK-546. The review bundle shared in the task pod contains that reference beside these captures; the source artboard is not copied into this directory.
+
+## Readback contract
+
+- Hosted and built-in model: `manage-settings.runtimeRun.model`, from the newest owner-scoped AgentRun in a manageable pod. No saved preference or UI model name stands in for a missing run.
+- BYO model and effort: `runtimeObservation.lastSpawn`, the existing machine-bound spawn receipt. No receipt reads as “not reported”.
+- The held task includes only its ID and pod name, and only where the viewer is a pod member. A non-member installation admin gets no task.
+- Status Pause/Resume uses the existing installation PATCH with the exact instance ID. Existing machine reconnect, credential ownership checks, per-pod removal and memory conflict handling remain in their own components.
+- Hosted and Scout request no credential ledger and render neither an Access key section nor a provider-key section. Scout has no usage meter or scheduled-turn estimate.
+- A never-connected agent has one connect action and a folded Remove disclosure; the fixture opens it and captures the reachable removal control. Revoked runtime credentials remain folded. The credential endpoint already excludes spawn children.
+- Local model controls use the machine-reported CLI inventory, with the existing user-supplied ID input when a CLI cannot enumerate models. TASK-002 owner Kai confirmed the source split: the instance catalogue is for hosted override choices; local CLI inventories remain independent. The runbook in #2707 explicitly says native Settings displays runtime truth. This page keeps the hosted/Scout model read only per rev3 and introduces no hosted override picker. That interpretation of the task’s picker clause is called out for task-owner review.
+
+The existing OpenClaw Persona editor remains available inside the folded identity editor because its save path writes the runtime workspace’s `IDENTITY.md`. Other runtime kinds retain their existing name-only editor.
+
+## Status and Runtime review follow-up
+
+The capture fixture asserts that Pause, Copy and connect actions measure at least 44px at 390. Runtime without a spawn receipt says “On your machine, once connected” and omits the command hint. Placement is labelled “Bound to” so an Off agent does not claim to be running. Config flags are nonbreaking tokens; the desktop command aligns with the runtime value column. Last-turn dates use the selected language without seconds; Chinese location/model separators belong to the locale.
+
+Scout retains Pause: the existing installation status PATCH accepts active/paused without a runtime restriction, and the event routing selects active installations. This control pauses the installation while preserving memory and membership, as requested in TASK-546. The model remains the raw ID from the run receipt, preserving its exact readback rather than inventing a display name.
+
+## Reach per pod review follow-up
+
+Heartbeat off hides the interval and daily-turn estimate. The capture exercises off → on → off for BYO, hosted and Scout at both widths in both languages, saves separate Reach crops, and measures the enabled interval select at a minimum 44px on phone. Scout still omits the turn estimate when enabled. The Chinese toggle is “心跳”; “间隔” labels only the select.
+
+## What it can use review follow-up
+
+The catalog search inherits the page font and has a 44px minimum height on phone. The grant revoke confirmation uses the same ink first action as other page confirmations. Run the fixture with `TOOLS_PROOF=1` to cover BYO/Scout with grants and loaded/conflicted skills, and hosted with no grants/skills, in en/zh at both widths. This mode opens the revoke confirmation without submitting it, checks its computed ink fill, opens the catalog, and checks search height and inherited font.
+
+## Usage review follow-up
+
+The userRunCeiling row identifies the owner's daily limit across all agents, including when an admin opens another owner's installation. Status and Usage share `formatAgentTimestamp`, which uses the selected app language and omits seconds. The default fixture saves BYO/hosted Usage crops and asserts the hosted owner-wide label and localized reset line in both languages. Scout continues to omit Usage.
+
+## Access key review follow-up
+
+Created, Last used and Revoked dates use the shared app-language formatter without seconds. Missing and invalid timestamps retain their localized fallback labels. The default fixture captures folded and expanded key history at both widths in en/zh, then asserts all three historical date lines against localized formatting. No key is issued or revoked.
+
+## Reproduce
+
+Start the normal frontend dev server on `127.0.0.1:3546`, then run `node capture.cjs` in an environment with Playwright installed. `PLAYWRIGHT_MODULE` can point to an existing installation; `OUTPUT_DIR` changes the destination. The fixture defaults to the system Chrome executable on macOS. If node_modules is a worktree symlink, allow its real directory in the local Vite server’s `server.fs.allow`; otherwise font URLs are refused. The capture asserts the Signal font loads before taking images. The source uses fake identities and a fixed receipt, so the displayed values can be checked against the intercepted responses.
+
+## Completion holds
+
+This is a draft implementation. UX review proceeds section by section, beginning with Status and Runtime. Independent gates and a non-author production walk of BYO/hosted/Scout at both widths with every displayed value checked against its real last spawn remain required. Captures alone do not complete TASK-546.
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/capture.cjs b/docs/design/evidence/agent-settings-rev3-2026-10-25/capture.cjs
new file mode 100644
index 0000000000..6462151153
--- /dev/null
+++ b/docs/design/evidence/agent-settings-rev3-2026-10-25/capture.cjs
@@ -0,0 +1,124 @@
+const {chromium}=require(process.env.PLAYWRIGHT_MODULE || 'playwright');
+const fs=require('fs');
+const toolsProof=process.env.TOOLS_PROOF==='1';
+const outDir=process.env.OUTPUT_DIR || require('path').join(__dirname,'captures');
+(async()=>{
+const browser=await chromium.launch({executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',headless:true});
+fs.mkdirSync(outDir,{recursive:true});
+for(const lang of ['en','zh-CN']) for(const kind of (toolsProof?['byo','hosted','scout']:['byo','hosted','scout','never'])) for(const width of [1440,390]){
+ const context=await browser.newContext({viewport:{width,height:width===390?844:1000}}); const page=await context.newPage(); const errors=[];page.on('pageerror',e=>errors.push(e.message));
+ let paused=false;let heartbeatEnabled=false;
+ const runtime={runtimeType:kind==='scout'?'native':kind==='hosted'?'hosted':'wrapper',adapter:kind==='byo'?'codex':undefined};
+ const receipt={harness:'claude',version:'2.1',model:'claude-sonnet-4-5',effort:'high',at:'2026-10-25T16:40:00Z'};
+ const installation=()=>({name:kind,instanceId:'default',displayName:kind==='scout'?'Scout':kind==='hosted'?'Launch plan agent':'Maya agent',runtime,status:paused?'paused':'active',installedBy:'fixture-owner'});
+ await page.route('**/*',route=>['127.0.0.1','localhost'].includes(new URL(route.request().url()).hostname)?route.continue():route.abort());
+ await page.route('**/api/**',async route=>{const req=route.request();const u=new URL(req.url());let data={};
+ if(req.method()==='PATCH'){const patch=req.postDataJSON();if('status' in patch)paused=patch.status==='paused';if('heartbeatEnabled' in patch)heartbeatEnabled=patch.heartbeatEnabled;data={agent:installation()};}
+ else if(u.pathname==='/api/auth/user')data={_id:'fixture-owner',username:'alex',email:'fixture@example.invalid',verified:true};
+ else if(u.pathname==='/api/pods')data=[{_id:'fixture-pod',name:'Launch plan',type:'chat',createdBy:{_id:'fixture-owner'}}];
+ else if(u.pathname.includes('manage-settings'))data={writablePodId:'fixture-pod',installations:[{podId:'fixture-pod',podName:'Launch plan',status:paused?'paused':'active',heartbeatEnabled,heartbeatMinutes:60}],turnCap:30,userRunCeiling:120,usage:kind==='hosted'?{used:12,cap:30,resetsAt:'2026-10-26T00:00:00Z'}:null,processConnected:kind==='byo',runtimeManaged:kind==='byo',runtimeObservation:kind==='byo'?{lastSpawn:receipt,lastTurnAt:'2026-10-25T16:41:00Z',turnRunning:false}:null,runtimeRun:kind==='hosted'||kind==='scout'?{model:'deepseek-v4-flash',startedAt:'2026-10-25T16:40:00Z',completedAt:'2026-10-25T16:41:00Z',status:'succeeded'}:null,heldTask:kind==='hosted'?{taskId:'TASK-6',podId:'fixture-pod',podName:'Launch plan'}:null};
+ else if(u.pathname===`/api/registry/pods/fixture-pod/agents`)data={agents:[{...installation(),connection:{state:kind==='never'?'never':kind==='byo'?'stale':'available',lastSeenAt:'2026-10-25T16:40:00Z'}}]};
+ else if(u.pathname===`/api/registry/pods/fixture-pod/agents/${kind}`)data={agent:installation()};
+ else if(u.pathname==='/api/agent-binding/placement')data={boundMachineId:'fixture-machine',activeMachines:[{machineId:'fixture-machine',name:'Laptop'}],boundMachineRevoked:false};
+ else if(u.pathname==='/api/credentials')data=[{_id:'fixture-key',label:'Runtime token',status:'active',createdAt:'2026-10-01T00:00:00Z',lastUsedAt:'2026-10-25T16:40:00Z'},{_id:'fixture-old',label:'Runtime token',status:'revoked',createdAt:'2026-10-01T00:00:00Z',lastUsedAt:'2026-10-24T16:40:00Z',revokedAt:'2026-10-25T16:40:00Z'}];
+ else if(u.pathname.endsWith('/grants'))data={grants:toolsProof&&kind!=='hosted'?[{grantId:'fixture-grant',tools:['repo.read'],connector:'Repository',podName:'Launch plan',grantedBy:'Alex',createdAt:'2026-10-20T12:00:00Z',expiresAt:'2099-01-01T00:00:00Z',revokedAt:null,canRevoke:true}]:[]};
+ else if(u.pathname.endsWith('/editable'))data={sections:{long_term:{content:'Remembers the launch plan.',updatedAt:null},soul:{content:'',updatedAt:null},shared:{content:'',updatedAt:null}}};
+ else if(u.pathname.includes('attention'))data={items:[],count:0};
+ else if(u.pathname.includes('/skills'))data={items:toolsProof&&kind!=='hosted'?[{name:'Writing',description:'Draft release notes.'},{name:'Review',description:'Review a change.'}]:[],canEdit:toolsProof,skillLoad:toolsProof?{at:'2026-10-25T16:40:00Z',mounted:['Writing'],conflicted:['Review']}:null};
+ await route.fulfill({json:data});});
+ await page.addInitScript((lang)=>{localStorage.setItem('token','fixture-only-user');localStorage.setItem('v2.lang',lang);},lang);
+ await page.goto(`http://127.0.0.1:3546/v2/agents/${kind}/default`); await page.getByRole('heading',{name:lang==='en'?'Status':'状态',exact:true}).waitFor(); await page.locator('.v2-agent-status strong').filter({hasText:kind==='byo'?(lang==='en'?'Off':'离线'):kind==='never'?(lang==='en'?'Not connected yet':'尚未连接'):kind==='hosted'?(lang==='en'?'Working':'工作中'):(lang==='en'?'Ready':'就绪')}).waitFor();await page.evaluate(async()=>{
+ await document.fonts.ready;
+ for(const family of ['IBM Plex Sans','IBM Plex Mono']) {
+ const faces=await document.fonts.load(`400 12px "${family}"`);
+ if(!faces.some(face=>face.family.replaceAll('"','')===family && face.status==='loaded')) throw new Error('Signal font missing: '+family);
+ }
+ });
+ if(width===390) {
+ for(const el of await page.locator('.v2-agent-status button, .v2-agent-status a.v2-agent-settings__action, .v2-agent-runtime-summary__command button').all()) {
+ const box=await el.boundingBox(); if(box && box.height<44) throw new Error('Phone action below 44px: '+box.height);
+ }
+ }
+ if(kind==='byo') {
+ const flags=page.locator('.v2-agent-runtime-summary__command code span').filter({hasText:/^--/});
+ if(await flags.count()!==2) throw new Error('Config flags missing');
+ for(const flag of await flags.all()) if(await flag.evaluate(el=>getComputedStyle(el).whiteSpace)!=='nowrap') throw new Error('Config flag can split');
+ }
+ if(kind==='never' && await page.getByText(/To change it, run this there:/).count()) throw new Error('Missing command hint');
+ if(kind==='byo' && await page.getByText(/Running on Laptop/).count()) throw new Error('Binding claims a running process');
+ for (const title of (lang==='en'?['Status','Runtime']:['状态','运行方式'])) { const section=page.getByRole('heading',{name:title,exact:true}).locator('..'); await section.screenshot({path:`${outDir}/${lang}-${kind}-${width}-${title.toLowerCase()}.png`}); }
+ await page.locator('.v2-agent-settings').evaluate(el=>el.closest('.v2-feature__body').scrollTop=0);
+ await page.screenshot({path:`${outDir}/${lang}-${kind}-${width}.png`,fullPage:true});
+ console.log(lang,kind,width,JSON.stringify({errors,overflow:await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth),h2:await page.locator('h2').allTextContents()}));
+ if(kind==='byo') {
+ const keys=page.getByRole('heading',{name:lang==='en'?'Access key':'访问密钥',exact:true}).locator('..').locator('..');
+ await keys.screenshot({path:`${outDir}/${lang}-byo-keys-folded-${width}.png`});
+ const history=keys.locator('details');await history.locator('summary').click();
+ const labels=lang==='en'?['Created','Last used','Revoked']:['创建于','上次使用','撤销于'];
+ for(const [index,value] of ['2026-10-01T00:00:00Z','2026-10-24T16:40:00Z','2026-10-25T16:40:00Z'].entries()) {
+ const date=await page.evaluate(({value,lang})=>new Date(value).toLocaleString(lang,{year:'numeric',month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}),{value,lang});
+ await history.getByText(`${labels[index]} ${date}`,{exact:true}).waitFor();
+ }
+ await keys.screenshot({path:`${outDir}/${lang}-byo-keys-expanded-${width}.png`});
+ await history.locator('summary').click();
+ }
+ if(kind!=='never' && kind!=='scout') {
+ const usage=page.getByRole('heading',{name:lang==='en'?'Usage':'用量',exact:true}).locator('..');
+ if(kind==='hosted') {
+ await usage.getByText(lang==='en'?'Owner’s daily limit, all agents':'所有者全部智能体的每日运行上限',{exact:true}).waitFor();
+ const date=await page.evaluate(lang=>new Date('2026-10-26T00:00:00Z').toLocaleString(lang,{year:'numeric',month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}),lang);
+ await usage.getByText(`${lang==='en'?'Resets':'重置时间'} ${date}`,{exact:true}).waitFor();
+ }
+ await usage.screenshot({path:`${outDir}/${lang}-${kind}-usage-${width}.png`});
+ }
+ if(toolsProof) {
+ if(kind!=='hosted') {
+ await page.locator('.v2-agent-runtime-grants').getByRole('button',{name:lang==='en'?'Revoke':'撤销',exact:true}).click();
+ const confirm=page.locator('.v2-agent-runtime-grants__actions button').first();
+ await page.mouse.move(0,0);
+ await page.waitForFunction(()=>getComputedStyle(document.querySelector('.v2-agent-runtime-grants__actions button')).backgroundColor==='rgb(16, 24, 40)');
+ const ink=await confirm.evaluate(el=>getComputedStyle(el).backgroundColor);
+ if(ink!=='rgb(16, 24, 40)')throw new Error('Revoke confirm is not ink: '+ink);
+ await confirm.locator('..').screenshot({path:`${outDir}/${lang}-${kind}-revoke-${width}.png`});
+ await page.locator('.v2-agent-runtime-grants__actions button').last().click();
+ }
+ await page.getByRole('button',{name:lang==='en'?'Add skill':'添加技能',exact:true}).click();
+ const input=page.locator('#v2-agent-skills-search');await input.waitFor();
+ const metrics=await input.evaluate(el=>({height:el.getBoundingClientRect().height,font:getComputedStyle(el).fontFamily,parentFont:getComputedStyle(el.parentElement).fontFamily}));
+ if(width===390&&metrics.height<44)throw new Error('Catalog input below 44px');
+ if(metrics.font!==metrics.parentFont)throw new Error('Catalog input font not inherited');
+ await page.locator('.v2-agent-skills').screenshot({path:`${outDir}/${lang}-${kind}-catalog-${width}.png`});
+ console.log('tools',lang,kind,width,JSON.stringify(metrics));
+ await page.getByRole('button',{name:lang==='en'?'Close catalog':'关闭目录',exact:true}).click();
+ }
+ if(kind!=='never') {
+ const reach=page.getByRole('heading',{name:lang==='en'?'Reach per pod':'各 Pod 的唤醒设置',exact:true}).locator('..');
+ const toggle=reach.getByRole('checkbox',{name:lang==='en'?'Heartbeat':'心跳',exact:true});
+ if(await reach.locator('select').count())throw new Error('Heartbeat off exposes interval');
+ if(await reach.getByText(/turns\/day|次运行.*天/).count())throw new Error('Heartbeat off exposes estimate');
+ await reach.screenshot({path:`${outDir}/${lang}-${kind}-reach-off-${width}.png`});
+ await toggle.click();const interval=reach.locator('select');await interval.waitFor();
+ const box=await interval.boundingBox();if(width===390 && (!box || box.height<44))throw new Error('Interval below 44px');
+ await reach.screenshot({path:`${outDir}/${lang}-${kind}-reach-on-${width}.png`});
+ console.log('heartbeat',lang,kind,width,box.height);
+ await toggle.click();await interval.waitFor({state:'hidden'});
+ }
+ if(kind==='byo' && lang==='en'){
+ await page.goto('http://127.0.0.1:3546/v2/agents');
+ const name=page.getByRole('link',{name:'Maya agent',exact:true}); await name.waitFor();
+ if(await name.getAttribute('href')!=='/v2/agents/byo/default') throw new Error('Wrong Settings link');
+ const color=await name.evaluate(el=>getComputedStyle(el).color);if(color!=='rgb(16, 24, 40)')throw new Error('Changed name color: '+color);
+ await page.screenshot({path:`${outDir}/en-team-${width}.png`,fullPage:true});
+ await name.click(); await page.getByRole('heading',{name:'Status',exact:true}).waitFor();
+ }
+ if(kind==='never') {
+ const disclosure=page.locator('details').filter({has:page.getByRole('heading',{name:lang==='en'?'Remove':'移除',exact:true,includeHidden:true})});
+ if(await disclosure.getAttribute('open')!==null) throw new Error('Removal must start folded');
+ await disclosure.locator('summary').click();
+ await disclosure.getByRole('button').waitFor();
+ await page.screenshot({path:`${outDir}/${lang}-never-remove-${width}.png`,fullPage:true});
+ }
+ if(kind==='hosted'){await page.getByRole('button',{name:lang==='en'?'Pause':'暂停',exact:true}).click();await page.getByRole('button',{name:lang==='en'?'Resume':'恢复',exact:true}).waitFor();}
+ await context.close();
+} await browser.close();
+})();
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-1440-runtime.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-1440-runtime.png
new file mode 100644
index 0000000000..0edfda4f7a
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-1440-runtime.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-1440-status.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-1440-status.png
new file mode 100644
index 0000000000..577719112b
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-1440-status.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-1440.png
new file mode 100644
index 0000000000..e6733d51d6
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-390-runtime.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-390-runtime.png
new file mode 100644
index 0000000000..39fe99ddb5
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-390-runtime.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-390-status.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-390-status.png
new file mode 100644
index 0000000000..dc75fdf70c
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-390-status.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-390.png
new file mode 100644
index 0000000000..47d635060a
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-catalog-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-catalog-1440.png
new file mode 100644
index 0000000000..58325e6564
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-catalog-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-catalog-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-catalog-390.png
new file mode 100644
index 0000000000..c33580a438
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-catalog-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-keys-expanded-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-keys-expanded-1440.png
new file mode 100644
index 0000000000..4a9c5e4b45
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-keys-expanded-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-keys-expanded-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-keys-expanded-390.png
new file mode 100644
index 0000000000..e56aaafdb7
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-keys-expanded-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-keys-folded-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-keys-folded-1440.png
new file mode 100644
index 0000000000..4d6072b7b8
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-keys-folded-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-keys-folded-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-keys-folded-390.png
new file mode 100644
index 0000000000..f2633a6279
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-keys-folded-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-reach-off-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-reach-off-1440.png
new file mode 100644
index 0000000000..f440f34781
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-reach-off-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-reach-off-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-reach-off-390.png
new file mode 100644
index 0000000000..5dd35d062a
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-reach-off-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-reach-on-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-reach-on-1440.png
new file mode 100644
index 0000000000..5533d16566
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-reach-on-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-reach-on-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-reach-on-390.png
new file mode 100644
index 0000000000..1a735e969a
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-reach-on-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-revoke-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-revoke-1440.png
new file mode 100644
index 0000000000..bdc2c7f27b
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-revoke-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-revoke-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-revoke-390.png
new file mode 100644
index 0000000000..ae40e789cf
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-revoke-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-usage-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-usage-1440.png
new file mode 100644
index 0000000000..3d626c365a
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-usage-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-usage-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-usage-390.png
new file mode 100644
index 0000000000..aec92b6d2c
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-byo-usage-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-1440-runtime.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-1440-runtime.png
new file mode 100644
index 0000000000..64f5ff2264
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-1440-runtime.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-1440-status.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-1440-status.png
new file mode 100644
index 0000000000..1bb7e6116d
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-1440-status.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-1440.png
new file mode 100644
index 0000000000..52dff954d8
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-390-runtime.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-390-runtime.png
new file mode 100644
index 0000000000..c5c0b7bb8c
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-390-runtime.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-390-status.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-390-status.png
new file mode 100644
index 0000000000..199c4235ad
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-390-status.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-390.png
new file mode 100644
index 0000000000..623fc51893
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-catalog-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-catalog-1440.png
new file mode 100644
index 0000000000..d11780257f
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-catalog-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-catalog-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-catalog-390.png
new file mode 100644
index 0000000000..f5576a6342
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-catalog-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-reach-off-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-reach-off-1440.png
new file mode 100644
index 0000000000..f440f34781
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-reach-off-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-reach-off-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-reach-off-390.png
new file mode 100644
index 0000000000..5dd35d062a
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-reach-off-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-reach-on-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-reach-on-1440.png
new file mode 100644
index 0000000000..5533d16566
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-reach-on-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-reach-on-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-reach-on-390.png
new file mode 100644
index 0000000000..80510d3112
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-reach-on-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-usage-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-usage-1440.png
new file mode 100644
index 0000000000..ed4284ffbb
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-usage-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-usage-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-usage-390.png
new file mode 100644
index 0000000000..f0793ac0c1
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-hosted-usage-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-1440-runtime.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-1440-runtime.png
new file mode 100644
index 0000000000..33867727f9
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-1440-runtime.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-1440-status.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-1440-status.png
new file mode 100644
index 0000000000..6e2e79a39b
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-1440-status.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-1440.png
new file mode 100644
index 0000000000..65ccead477
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-390-runtime.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-390-runtime.png
new file mode 100644
index 0000000000..3726dc5161
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-390-runtime.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-390-status.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-390-status.png
new file mode 100644
index 0000000000..428138d6c9
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-390-status.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-390.png
new file mode 100644
index 0000000000..50884ee182
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-remove-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-remove-1440.png
new file mode 100644
index 0000000000..2e1007af02
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-remove-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-remove-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-remove-390.png
new file mode 100644
index 0000000000..15c72be180
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-never-remove-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-1440-runtime.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-1440-runtime.png
new file mode 100644
index 0000000000..7a4a18020c
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-1440-runtime.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-1440-status.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-1440-status.png
new file mode 100644
index 0000000000..90e8378280
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-1440-status.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-1440.png
new file mode 100644
index 0000000000..7a0dbd7274
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-390-runtime.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-390-runtime.png
new file mode 100644
index 0000000000..8141cfc507
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-390-runtime.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-390-status.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-390-status.png
new file mode 100644
index 0000000000..758a5c5c54
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-390-status.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-390.png
new file mode 100644
index 0000000000..82eb340244
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-catalog-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-catalog-1440.png
new file mode 100644
index 0000000000..58325e6564
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-catalog-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-catalog-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-catalog-390.png
new file mode 100644
index 0000000000..c33580a438
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-catalog-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-reach-off-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-reach-off-1440.png
new file mode 100644
index 0000000000..f440f34781
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-reach-off-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-reach-off-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-reach-off-390.png
new file mode 100644
index 0000000000..5dd35d062a
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-reach-off-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-reach-on-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-reach-on-1440.png
new file mode 100644
index 0000000000..1ded241739
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-reach-on-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-reach-on-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-reach-on-390.png
new file mode 100644
index 0000000000..c6d1396174
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-reach-on-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-revoke-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-revoke-1440.png
new file mode 100644
index 0000000000..bdc2c7f27b
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-revoke-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-revoke-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-revoke-390.png
new file mode 100644
index 0000000000..ae40e789cf
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-scout-revoke-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-team-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-team-1440.png
new file mode 100644
index 0000000000..a4882c3feb
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-team-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/en-team-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-team-390.png
new file mode 100644
index 0000000000..18b9840996
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/en-team-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/tools-measurements.txt b/docs/design/evidence/agent-settings-rev3-2026-10-25/tools-measurements.txt
new file mode 100644
index 0000000000..ddbc6708cf
--- /dev/null
+++ b/docs/design/evidence/agent-settings-rev3-2026-10-25/tools-measurements.txt
@@ -0,0 +1,36 @@
+en byo 1440 {"errors":[],"overflow":false,"h2":["Identity","Status","Runtime","Reach per pod","Memory","What it can use","Usage","Access key","Remove"]}
+tools en byo 1440 {"height":40,"font":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif","parentFont":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif"}
+heartbeat en byo 1440 33
+en byo 390 {"errors":[],"overflow":false,"h2":["Identity","Status","Runtime","Reach per pod","Memory","What it can use","Usage","Access key","Remove"]}
+tools en byo 390 {"height":44,"font":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif","parentFont":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif"}
+heartbeat en byo 390 44
+en hosted 1440 {"errors":[],"overflow":false,"h2":["Identity","Status","Runtime","Reach per pod","Memory","What it can use","Usage","Remove"]}
+tools en hosted 1440 {"height":40,"font":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif","parentFont":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif"}
+heartbeat en hosted 1440 33
+en hosted 390 {"errors":[],"overflow":false,"h2":["Identity","Status","Runtime","Reach per pod","Memory","What it can use","Usage","Remove"]}
+tools en hosted 390 {"height":44,"font":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif","parentFont":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif"}
+heartbeat en hosted 390 44
+en scout 1440 {"errors":[],"overflow":false,"h2":["Identity","Status","Runtime","Reach per pod","Memory","What it can use","Remove"]}
+tools en scout 1440 {"height":40,"font":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif","parentFont":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif"}
+heartbeat en scout 1440 33
+en scout 390 {"errors":[],"overflow":false,"h2":["Identity","Status","Runtime","Reach per pod","Memory","What it can use","Remove"]}
+tools en scout 390 {"height":44,"font":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif","parentFont":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif"}
+heartbeat en scout 390 44
+zh-CN byo 1440 {"errors":[],"overflow":false,"h2":["身份","状态","运行方式","各 Pod 的唤醒设置","记忆","可用能力","用量","访问密钥","移除"]}
+tools zh-CN byo 1440 {"height":40,"font":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif","parentFont":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif"}
+heartbeat zh-CN byo 1440 33
+zh-CN byo 390 {"errors":[],"overflow":false,"h2":["身份","状态","运行方式","各 Pod 的唤醒设置","记忆","可用能力","用量","访问密钥","移除"]}
+tools zh-CN byo 390 {"height":44,"font":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif","parentFont":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif"}
+heartbeat zh-CN byo 390 44
+zh-CN hosted 1440 {"errors":[],"overflow":false,"h2":["身份","状态","运行方式","各 Pod 的唤醒设置","记忆","可用能力","用量","移除"]}
+tools zh-CN hosted 1440 {"height":40,"font":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif","parentFont":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif"}
+heartbeat zh-CN hosted 1440 33
+zh-CN hosted 390 {"errors":[],"overflow":false,"h2":["身份","状态","运行方式","各 Pod 的唤醒设置","记忆","可用能力","用量","移除"]}
+tools zh-CN hosted 390 {"height":44,"font":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif","parentFont":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif"}
+heartbeat zh-CN hosted 390 44
+zh-CN scout 1440 {"errors":[],"overflow":false,"h2":["身份","状态","运行方式","各 Pod 的唤醒设置","记忆","可用能力","移除"]}
+tools zh-CN scout 1440 {"height":40,"font":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif","parentFont":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif"}
+heartbeat zh-CN scout 1440 33
+zh-CN scout 390 {"errors":[],"overflow":false,"h2":["身份","状态","运行方式","各 Pod 的唤醒设置","记忆","可用能力","移除"]}
+tools zh-CN scout 390 {"height":44,"font":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif","parentFont":"\"IBM Plex Sans\", \"SF Pro Text\", -apple-system, \"system-ui\", \"Segoe UI\", Roboto, sans-serif"}
+heartbeat zh-CN scout 390 44
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-1440-\347\212\266\346\200\201.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-1440-\347\212\266\346\200\201.png"
new file mode 100644
index 0000000000..7ca92ae171
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-1440-\347\212\266\346\200\201.png" differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-1440-\350\277\220\350\241\214\346\226\271\345\274\217.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-1440-\350\277\220\350\241\214\346\226\271\345\274\217.png"
new file mode 100644
index 0000000000..e91e5c9dd7
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-1440-\350\277\220\350\241\214\346\226\271\345\274\217.png" differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-1440.png
new file mode 100644
index 0000000000..e2c4a7ade2
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-1440.png differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-390-\347\212\266\346\200\201.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-390-\347\212\266\346\200\201.png"
new file mode 100644
index 0000000000..bfeb772e02
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-390-\347\212\266\346\200\201.png" differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-390-\350\277\220\350\241\214\346\226\271\345\274\217.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-390-\350\277\220\350\241\214\346\226\271\345\274\217.png"
new file mode 100644
index 0000000000..626c6d19b7
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-390-\350\277\220\350\241\214\346\226\271\345\274\217.png" differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-390.png
new file mode 100644
index 0000000000..8ae1f8cf91
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-catalog-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-catalog-1440.png
new file mode 100644
index 0000000000..2717493df0
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-catalog-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-catalog-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-catalog-390.png
new file mode 100644
index 0000000000..79b01a31e1
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-catalog-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-keys-expanded-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-keys-expanded-1440.png
new file mode 100644
index 0000000000..eb570c16bb
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-keys-expanded-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-keys-expanded-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-keys-expanded-390.png
new file mode 100644
index 0000000000..8728818e8c
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-keys-expanded-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-keys-folded-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-keys-folded-1440.png
new file mode 100644
index 0000000000..8610cac285
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-keys-folded-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-keys-folded-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-keys-folded-390.png
new file mode 100644
index 0000000000..50f8c85abe
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-keys-folded-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-reach-off-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-reach-off-1440.png
new file mode 100644
index 0000000000..bdcf557e1f
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-reach-off-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-reach-off-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-reach-off-390.png
new file mode 100644
index 0000000000..9c807932f2
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-reach-off-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-reach-on-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-reach-on-1440.png
new file mode 100644
index 0000000000..c22864a75a
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-reach-on-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-reach-on-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-reach-on-390.png
new file mode 100644
index 0000000000..abe94ce711
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-reach-on-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-revoke-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-revoke-1440.png
new file mode 100644
index 0000000000..3b64f80c35
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-revoke-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-revoke-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-revoke-390.png
new file mode 100644
index 0000000000..db84395707
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-revoke-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-usage-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-usage-1440.png
new file mode 100644
index 0000000000..26e5ae3d45
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-usage-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-usage-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-usage-390.png
new file mode 100644
index 0000000000..0d8ceb240e
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-byo-usage-390.png differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-1440-\347\212\266\346\200\201.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-1440-\347\212\266\346\200\201.png"
new file mode 100644
index 0000000000..8b410796ab
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-1440-\347\212\266\346\200\201.png" differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-1440-\350\277\220\350\241\214\346\226\271\345\274\217.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-1440-\350\277\220\350\241\214\346\226\271\345\274\217.png"
new file mode 100644
index 0000000000..4535318d2b
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-1440-\350\277\220\350\241\214\346\226\271\345\274\217.png" differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-1440.png
new file mode 100644
index 0000000000..d36ddaa447
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-1440.png differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-390-\347\212\266\346\200\201.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-390-\347\212\266\346\200\201.png"
new file mode 100644
index 0000000000..79da997024
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-390-\347\212\266\346\200\201.png" differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-390-\350\277\220\350\241\214\346\226\271\345\274\217.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-390-\350\277\220\350\241\214\346\226\271\345\274\217.png"
new file mode 100644
index 0000000000..31be595eb2
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-390-\350\277\220\350\241\214\346\226\271\345\274\217.png" differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-390.png
new file mode 100644
index 0000000000..48d92034fa
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-catalog-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-catalog-1440.png
new file mode 100644
index 0000000000..bf531f605e
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-catalog-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-catalog-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-catalog-390.png
new file mode 100644
index 0000000000..ee975918f6
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-catalog-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-reach-off-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-reach-off-1440.png
new file mode 100644
index 0000000000..bdcf557e1f
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-reach-off-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-reach-off-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-reach-off-390.png
new file mode 100644
index 0000000000..9c807932f2
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-reach-off-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-reach-on-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-reach-on-1440.png
new file mode 100644
index 0000000000..c22864a75a
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-reach-on-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-reach-on-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-reach-on-390.png
new file mode 100644
index 0000000000..abe94ce711
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-reach-on-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-usage-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-usage-1440.png
new file mode 100644
index 0000000000..99bd36dbbc
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-usage-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-usage-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-usage-390.png
new file mode 100644
index 0000000000..4da0decde2
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-hosted-usage-390.png differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-1440-\347\212\266\346\200\201.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-1440-\347\212\266\346\200\201.png"
new file mode 100644
index 0000000000..fed652d573
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-1440-\347\212\266\346\200\201.png" differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-1440-\350\277\220\350\241\214\346\226\271\345\274\217.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-1440-\350\277\220\350\241\214\346\226\271\345\274\217.png"
new file mode 100644
index 0000000000..3823e07c73
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-1440-\350\277\220\350\241\214\346\226\271\345\274\217.png" differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-1440.png
new file mode 100644
index 0000000000..cca835971c
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-1440.png differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-390-\347\212\266\346\200\201.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-390-\347\212\266\346\200\201.png"
new file mode 100644
index 0000000000..a7c0c00a27
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-390-\347\212\266\346\200\201.png" differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-390-\350\277\220\350\241\214\346\226\271\345\274\217.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-390-\350\277\220\350\241\214\346\226\271\345\274\217.png"
new file mode 100644
index 0000000000..0fee9b0327
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-390-\350\277\220\350\241\214\346\226\271\345\274\217.png" differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-390.png
new file mode 100644
index 0000000000..cfa0aaee30
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-remove-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-remove-1440.png
new file mode 100644
index 0000000000..16fd4269fd
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-remove-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-remove-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-remove-390.png
new file mode 100644
index 0000000000..0f95bd52b7
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-never-remove-390.png differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-1440-\347\212\266\346\200\201.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-1440-\347\212\266\346\200\201.png"
new file mode 100644
index 0000000000..45884b9aaa
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-1440-\347\212\266\346\200\201.png" differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-1440-\350\277\220\350\241\214\346\226\271\345\274\217.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-1440-\350\277\220\350\241\214\346\226\271\345\274\217.png"
new file mode 100644
index 0000000000..539220c424
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-1440-\350\277\220\350\241\214\346\226\271\345\274\217.png" differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-1440.png
new file mode 100644
index 0000000000..3b4a9ae7fa
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-1440.png differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-390-\347\212\266\346\200\201.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-390-\347\212\266\346\200\201.png"
new file mode 100644
index 0000000000..12bd7bd10d
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-390-\347\212\266\346\200\201.png" differ
diff --git "a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-390-\350\277\220\350\241\214\346\226\271\345\274\217.png" "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-390-\350\277\220\350\241\214\346\226\271\345\274\217.png"
new file mode 100644
index 0000000000..9a1f533922
Binary files /dev/null and "b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-390-\350\277\220\350\241\214\346\226\271\345\274\217.png" differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-390.png
new file mode 100644
index 0000000000..027de059cc
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-catalog-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-catalog-1440.png
new file mode 100644
index 0000000000..2717493df0
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-catalog-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-catalog-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-catalog-390.png
new file mode 100644
index 0000000000..79b01a31e1
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-catalog-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-reach-off-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-reach-off-1440.png
new file mode 100644
index 0000000000..bdcf557e1f
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-reach-off-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-reach-off-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-reach-off-390.png
new file mode 100644
index 0000000000..9c807932f2
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-reach-off-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-reach-on-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-reach-on-1440.png
new file mode 100644
index 0000000000..988a1a5e3d
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-reach-on-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-reach-on-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-reach-on-390.png
new file mode 100644
index 0000000000..16464af998
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-reach-on-390.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-revoke-1440.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-revoke-1440.png
new file mode 100644
index 0000000000..3b64f80c35
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-revoke-1440.png differ
diff --git a/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-revoke-390.png b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-revoke-390.png
new file mode 100644
index 0000000000..db84395707
Binary files /dev/null and b/docs/design/evidence/agent-settings-rev3-2026-10-25/zh-CN-scout-revoke-390.png differ
diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index 6ab3cda51f..f265a4ea26 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -2674,7 +2674,7 @@
"settings": "Settings",
"key": "Key",
"regenerate": "Regenerate key",
- "wrapperReconnect": "Replace this computer's key from Control.",
+ "wrapperReconnect": "Reconnect this computer to replace its key.",
"copyNow": "Copy this key now. It will not be shown again.",
"done": "Done",
"empty": "No runtime keys yet.",
@@ -2695,7 +2695,51 @@
"revoke": "Revoke",
"updateError": "Could not update the key. Try again.",
"noPod": "This agent has no active pod.",
- "missingReplacement": "The replacement key was not returned."
+ "missingReplacement": "The replacement key was not returned.",
+ "rev3": {
+ "onceConnected": "On your machine, once connected",
+ "builtInLocation": "On Commonly, built in",
+ "model": "Model",
+ "modelEffort": "Model · effort",
+ "modelPrefix": "Commonly picks the model: ",
+ "boundMachine": "Bound to {{name}}",
+ "kindNotConnected": "not connected yet",
+ "status": "Status",
+ "builtIn": "built in",
+ "hosted": "run it here",
+ "notConnected": "Not connected yet",
+ "byo": "on your machine",
+ "where": "Where it runs",
+ "onCommonly": "On Commonly",
+ "onMachine": "On your machine",
+ "modelPicked": "Commonly picks the model",
+ "serverReported": "From the last recorded run.",
+ "machineReported": "As your machine last reported. To change it, run this there:",
+ "ready": "Ready",
+ "working": "Working",
+ "paused": "Paused",
+ "off": "Off",
+ "pause": "Pause",
+ "resume": "Resume",
+ "lastTurn": "Last turn {{date}}",
+ "builtInStatus": "Built into this workspace. Remove it from a pod under Remove.",
+ "connectHint": "Run one command on the computer where your agent lives.",
+ "offHint": "Start it on its machine to bring it back.",
+ "pauseHint": "Paused agents keep their memory and pods.",
+ "connect": "Get my connect command",
+ "usage": "Usage",
+ "machineUsage": "Runs on your machine, on your plan.",
+ "copyError": "Could not copy. Select the command and copy it.",
+ "editRuntime": "Change runtime",
+ "runtime": "Runtime",
+ "reach": "Reach per pod",
+ "remove": "Remove",
+ "tools": "What it can use",
+ "key": "Access key",
+ "turnsToday": "Turns today",
+ "resets": "Resets {{date}}",
+ "lastTurnUnknown": "Last turn not reported"
+ }
},
"agentPolicy": {
"title": "Policy",
@@ -2716,7 +2760,7 @@
"retry": "Try again",
"quotas": "Quotas",
"turnsPerDay": "Hosted turns per day",
- "userRunCeiling": "Runs per day",
+ "userRunCeiling": "Owner’s daily limit, all agents",
"loadError": "Could not load policy data.",
"saveError": "Could not save changes.",
"saved": "Saved"
diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json
index b580fed023..e039eb1e8f 100644
--- a/frontend/src/i18n/locales/zh-CN.json
+++ b/frontend/src/i18n/locales/zh-CN.json
@@ -2638,7 +2638,7 @@
"wakeOnMessage": "收到消息时唤醒",
"humanOnly": "仅限真人消息",
"boardWake": "看板变更时唤醒",
- "heartbeat": "心跳间隔",
+ "heartbeat": "心跳",
"heartbeatInterval": "间隔",
"turnsPerDay": "每天{{count}}次 · 上限{{cap}}次",
"capUnavailable": "每天{{count}}次 · 套餐上限暂不可用",
@@ -2665,7 +2665,7 @@
"settings": "设置",
"key": "密钥",
"regenerate": "重新生成密钥",
- "wrapperReconnect": "请在「控制」中替换这台电脑的密钥。",
+ "wrapperReconnect": "重新连接这台电脑以更换密钥。",
"copyNow": "请立即复制此密钥。之后不会再次显示。",
"done": "完成",
"empty": "暂无运行时密钥。",
@@ -2686,7 +2686,51 @@
"revoke": "撤销",
"updateError": "无法更新密钥,请重试。",
"noPod": "此智能体没有活跃的 Pod。",
- "missingReplacement": "未返回新密钥。"
+ "missingReplacement": "未返回新密钥。",
+ "rev3": {
+ "onceConnected": "连接后在你的电脑上运行",
+ "builtInLocation": "在 Commonly 上,内置",
+ "model": "模型",
+ "modelEffort": "模型 · 思考强度",
+ "modelPrefix": "Commonly 选择模型:",
+ "boundMachine": "已绑定到 {{name}}",
+ "kindNotConnected": "尚未连接",
+ "status": "状态",
+ "builtIn": "内置",
+ "hosted": "在这里运行",
+ "notConnected": "尚未连接",
+ "byo": "在你的电脑上",
+ "where": "运行位置",
+ "onCommonly": "在 Commonly 上",
+ "onMachine": "在你的电脑上",
+ "modelPicked": "Commonly 选择模型",
+ "serverReported": "来自最近一次运行记录。",
+ "machineReported": "以你的电脑最近上报的信息为准。要修改,请在该电脑运行:",
+ "ready": "就绪",
+ "working": "工作中",
+ "paused": "已暂停",
+ "off": "离线",
+ "pause": "暂停",
+ "resume": "恢复",
+ "lastTurn": "最近一次运行 {{date}}",
+ "builtInStatus": "此工作区的内置智能体。可在下方从 Pod 中移除。",
+ "connectHint": "在智能体所在的电脑上运行一条命令。",
+ "offHint": "在它的电脑上启动它即可重新连接。",
+ "pauseHint": "暂停后仍保留记忆和所在的 Pod。",
+ "connect": "获取连接命令",
+ "usage": "用量",
+ "machineUsage": "在你的电脑上运行,使用你的套餐。",
+ "copyError": "复制失败。请选中命令并复制。",
+ "editRuntime": "修改运行配置",
+ "runtime": "运行方式",
+ "reach": "各 Pod 的唤醒设置",
+ "remove": "移除",
+ "tools": "可用能力",
+ "key": "访问密钥",
+ "turnsToday": "今日回合数",
+ "resets": "重置时间 {{date}}",
+ "lastTurnUnknown": "尚未上报最近一次运行"
+ }
},
"agentPolicy": {
"title": "策略",
@@ -2707,7 +2751,7 @@
"retry": "重试",
"quotas": "配额",
"turnsPerDay": "托管运行次数/天",
- "userRunCeiling": "运行次数/天",
+ "userRunCeiling": "所有者全部智能体的每日运行上限",
"loadError": "无法加载策略数据。",
"saveError": "无法保存更改。",
"saved": "已保存"
diff --git a/frontend/src/v2/V2App.tsx b/frontend/src/v2/V2App.tsx
index ff7e6423f9..01817ac7de 100644
--- a/frontend/src/v2/V2App.tsx
+++ b/frontend/src/v2/V2App.tsx
@@ -286,7 +286,7 @@ const V2App: React.FC = () => {
/>
, false, false)}
+ element={feature('Agent settings', , false, false, 'v2-feature--agent-settings')}
/>
{
@@ -74,7 +75,7 @@ it('writes only the chosen trust mode and edits memory with its revision', async
it('shows server quota for hosted agents without editable trust controls', async () => {
renderPolicy({ ...installation, runtime: { runtimeType: 'hosted' } });
expect(await screen.findByText('Hosted turns per day')).toBeInTheDocument();
- expect(screen.getByText('Runs per day')).toBeInTheDocument();
+ expect(screen.getByText('Owner’s daily limit, all agents')).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Save trust mode' })).not.toBeInTheDocument();
});
@@ -97,3 +98,24 @@ it('shows a retry in Memory when loading fails, then opens the editor', async ()
fireEvent.click(screen.getByRole('button', { name: 'Try again' }));
expect(await screen.findByDisplayValue('Original')).toBeInTheDocument();
});
+
+
+it.each(['en', 'zh-CN'])('uses the app language for Usage resets (%s)', async (language) => {
+ await i18n.changeLanguage(language);
+ const resetsAt = '2026-10-26T00:00:00Z';
+ axios.get.mockImplementation((url) => url.endsWith('/editable')
+ ? Promise.resolve({ data: { sections: docs() } })
+ : Promise.resolve({ data: { turnCap: 30, userRunCeiling: 120, usage: { used: 12, cap: 30, resetsAt } } }));
+ const view = render();
+ try {
+ const date = new Date(resetsAt).toLocaleString(language, {
+ year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',
+ });
+ expect(await screen.findByText(`${language === 'en' ? 'Resets' : '重置时间'} ${date}`)).toBeInTheDocument();
+ expect(screen.getByText(language === 'en' ? 'Owner’s daily limit, all agents' : '所有者全部智能体的每日运行上限')).toBeInTheDocument();
+ } finally {
+ view.unmount();
+ await i18n.changeLanguage('en');
+ }
+});
diff --git a/frontend/src/v2/__tests__/V2AgentRuntimeGrants.test.tsx b/frontend/src/v2/__tests__/V2AgentRuntimeGrants.test.tsx
index d52879c317..030fb0ffa6 100644
--- a/frontend/src/v2/__tests__/V2AgentRuntimeGrants.test.tsx
+++ b/frontend/src/v2/__tests__/V2AgentRuntimeGrants.test.tsx
@@ -1,6 +1,7 @@
// @ts-nocheck
import React from 'react';
-import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
+import i18n from '../../i18n';
+import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import V2AgentRuntimeGrants, { describeRuntime } from '../components/V2AgentRuntimeGrants';
import { AuthContext } from '../../context/AuthContext';
import { daemonSetupCommand } from '../utils/daemonSetupCommand';
@@ -30,7 +31,7 @@ const response = {
const renderSection = (runtime = { runtimeType: 'wrapper', adapter: 'claude', model: 'sonnet' }) => render(
- ({ harness, installed: true, version: '1', models: null, efforts: ['low', 'medium', 'high', 'xhigh'] })), ...runtime }} onSaveRuntime={saveRuntime} />
+ ({ harness, installed: true, version: '1', models: ['sonnet', 'opus', 'gpt-5', 'gpt-5.3-codex', 'gpt-6-sol'], efforts: ['low', 'medium', 'high', 'xhigh'] })), ...runtime }} onSaveRuntime={saveRuntime} />
,
);
@@ -173,3 +174,41 @@ test('does not offer revoke when the server says this viewer cannot revoke', asy
expect(await screen.findByText(/Work · Granted by Owner/)).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Revoke' })).toBeNull();
});
+
+test('placement errors clear when the mounted runtime becomes unmanaged', async () => {
+ axios.get.mockImplementation((url) => url.includes('/agent-binding/placement')
+ ? Promise.reject(new Error('fixture placement failure')) : Promise.resolve({ data: response }));
+ const view = renderSection({ runtimeType: 'wrapper', adapter: 'claude' });
+ await screen.findByRole('alert');
+ view.rerender(
+
+ );
+ await waitFor(() => expect(screen.queryByRole('alert')).toBeNull());
+});
+
+test('a late placement error cannot follow a runtime switch', async () => {
+ let rejectPlacement;
+ axios.get.mockImplementation((url) => url.includes('/agent-binding/placement')
+ ? new Promise((resolve, reject) => { rejectPlacement = reject; }) : Promise.resolve({ data: response }));
+ const view = renderSection({ runtimeType: 'wrapper', adapter: 'claude' });
+ await waitFor(() => expect(rejectPlacement).toBeDefined());
+ view.rerender(
+
+ );
+ await act(async () => { rejectPlacement(new Error('fixture late error')); });
+ await screen.findByText('read only · runs on commonly');
+ await waitFor(() => expect(screen.queryByRole('alert')).toBeNull());
+});
+
+
+test.each(['en', 'zh-CN'])('grant dates use app language (%s)', async (language) => {
+ await i18n.changeLanguage(language);
+ const view = renderSection();
+ try {
+ const grant = response.grants[0];
+ const date = new Date(grant.createdAt).toLocaleDateString(language);
+ expect(await screen.findByText(`Work · ${i18n.t('agentManage.grants.by')} Owner · ${date}`)).toBeInTheDocument();
+ const expiry = new Date(grant.expiresAt).toLocaleDateString(language);
+ expect(screen.getByText(`${i18n.t('agentManage.grants.ends')} ${expiry}`)).toBeInTheDocument();
+ } finally { view.unmount(); await i18n.changeLanguage('en'); }
+});
diff --git a/frontend/src/v2/__tests__/V2AgentSettingsPage.test.tsx b/frontend/src/v2/__tests__/V2AgentSettingsPage.test.tsx
index 280e945a90..5752b0181a 100644
--- a/frontend/src/v2/__tests__/V2AgentSettingsPage.test.tsx
+++ b/frontend/src/v2/__tests__/V2AgentSettingsPage.test.tsx
@@ -1,13 +1,15 @@
// @ts-nocheck
import React from 'react';
-import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
+import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { MemoryRouter, Route, Routes, useNavigate } from 'react-router-dom';
+import i18n from '../../i18n';
import V2AgentSettingsPage from '../agents/V2AgentSettingsPage';
+import V2AgentProviderKey from '../components/V2AgentProviderKey';
import { AuthContext } from '../../context/AuthContext';
jest.mock('axios', () => {
const mock = {
- get: jest.fn(), post: jest.fn(), patch: jest.fn(), delete: jest.fn(),
+ get: jest.fn(), put: jest.fn(), post: jest.fn(), patch: jest.fn(), delete: jest.fn(),
defaults: { baseURL: '', headers: { common: {} } },
interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } },
};
@@ -78,7 +80,7 @@ test('wrapper key replacement cannot bypass the daemon reconnect flow', async ()
return Promise.resolve({ data: [] });
});
renderPage();
- await screen.findByText("Replace this computer's key from Control.");
+ await screen.findByText("Reconnect this computer to replace its key.");
expect(screen.queryByRole('button', { name: 'Regenerate key' })).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'Reconnect to regenerate key' })).not.toBeInTheDocument();
expect(axios.post).not.toHaveBeenCalled();
@@ -179,7 +181,11 @@ test('OpenClaw identity keeps the live Persona editor and saves its changes', as
});
axios.patch.mockResolvedValue({ data: { success: true } });
renderPage('/v2/agents/openclaw/scout');
- expect(await screen.findByRole('group', { name: 'Persona' })).toBeInTheDocument();
+ const persona = await screen.findByRole('group', { name: 'Persona' });
+ const disclosure = persona.closest('details');
+ expect(disclosure).not.toHaveAttribute('open');
+ fireEvent.click(disclosure.querySelector('summary'));
+ disclosure.open = true;
fireEvent.change(screen.getByRole('textbox', { name: 'Persona notes' }), { target: { value: 'Keep detail' } });
fireEvent.click(screen.getByRole('button', { name: 'Save identity' }));
await waitFor(() => expect(axios.patch).toHaveBeenCalledWith(
@@ -206,7 +212,8 @@ test('OpenClaw name-only edits leave Persona out of the PATCH', async () => {
});
axios.patch.mockResolvedValue({ data: { success: true } });
renderPage('/v2/agents/openclaw/scout');
- expect(await screen.findByRole('group', { name: 'Persona' })).toBeInTheDocument();
+ await screen.findByRole('button', { name: 'Save identity' });
+ expect(screen.getByRole('group', { name: 'Persona' })).toBeInTheDocument();
fireEvent.change(screen.getByRole('textbox', { name: 'Display name' }), { target: { value: 'Scout 2' } });
fireEvent.click(screen.getByRole('button', { name: 'Save identity' }));
await waitFor(() => expect(axios.patch).toHaveBeenCalledWith(
@@ -265,7 +272,7 @@ test('nondefault wrapper instances never link to a default-instance reconnect',
return Promise.resolve({ data: [] });
});
renderPage('/v2/agents/maya-claude/second');
- await screen.findByText("Replace this computer's key from Control.");
+ await screen.findByText("Reconnect this computer to replace its key.");
expect(screen.queryByRole('link', { name: 'Reconnect to regenerate key' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Regenerate key' })).not.toBeInTheDocument();
});
@@ -346,9 +353,9 @@ test('a partial installation owner cannot see identity-wide key controls', async
return Promise.reject({ response: { status: 403, data: { message: 'Access denied' } } });
});
renderPage();
- expect(await screen.findByRole('heading', { name: 'Policy' })).toBeInTheDocument();
+ expect(await screen.findByRole('heading', { name: 'Memory' })).toBeInTheDocument();
expect(await screen.findByText('Key management is unavailable for this agent. Ask an admin for access.')).toBeInTheDocument();
- expect(screen.getByRole('heading', { name: 'Pods' })).toBeInTheDocument();
+ expect(screen.getByRole('heading', { name: 'Remove' })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Regenerate key' })).not.toBeInTheDocument();
expect(screen.queryByText('Laptop')).not.toBeInTheDocument();
expect(axios.get.mock.calls.some(([url]) => url === '/api/agent-memory/maya-claude/default')).toBe(false);
@@ -421,10 +428,10 @@ test('selects an installation the owner can manage without exposing a foreign po
);
expect(screen.getByText('Pod · My pod')).toBeInTheDocument();
expect(screen.queryByText('Pod · Other pod')).not.toBeInTheDocument();
- expect(screen.getByRole('heading', { name: 'Key' }).compareDocumentPosition(
- screen.getByRole('heading', { name: 'Skills' }),
+ expect(screen.getByRole('heading', { name: 'Skills' }).compareDocumentPosition(
+ screen.getByRole('heading', { name: 'Access key' }),
) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
- expect(screen.getByRole('heading', { name: 'Wake' }).compareDocumentPosition(
+ expect(screen.getByRole('heading', { name: 'Reach per pod' }).compareDocumentPosition(
screen.getByRole('heading', { name: 'Skills' }),
) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: 'Regenerate key' }));
@@ -439,9 +446,9 @@ test('runtime editor writes environment controls through the shared settings hoo
axios.patch.mockResolvedValue({ data: { updated: true } });
axios.get.mockImplementation((url) => {
if (url.includes('/manage-settings')) return Promise.resolve({ data: {
- runtimeManaged: true, runtimeInventory: [{ harness: 'claude', installed: true, version: '1', models: null, efforts: ['low', 'medium', 'high', 'xhigh'] }], writablePodId: 'pod-1', installations: [{ podId: 'pod-1', podName: 'Workspace', status: 'active' }],
+ runtimeManaged: true, runtimeInventory: [{ harness: 'claude', installed: true, version: '1', models: ['old', 'new-model', 'selected-model', 'opus'], efforts: ['low', 'medium', 'high', 'xhigh'] }], writablePodId: 'pod-1', installations: [{ podId: 'pod-1', podName: 'Workspace', status: 'active' }],
} });
- if (url.endsWith('/manage-settings')) return Promise.resolve({ data: { runtimeManaged: true, runtimeInventory: [{ harness: 'claude', installed: true, version: '1', models: null, efforts: ['low', 'medium', 'high', 'xhigh'] }], writablePodId: 'pod-1' } });
+ if (url.endsWith('/manage-settings')) return Promise.resolve({ data: { runtimeManaged: true, runtimeInventory: [{ harness: 'claude', installed: true, version: '1', models: ['old', 'new-model', 'selected-model', 'opus'], efforts: ['low', 'medium', 'high', 'xhigh'] }], writablePodId: 'pod-1' } });
if (url.startsWith('/api/registry/pods/')) return Promise.resolve({ data: { agent: {
name: 'maya-claude', instanceId: 'default', displayName: 'Maya',
runtime: { runtimeType: 'wrapper' },
@@ -452,18 +459,12 @@ test('runtime editor writes environment controls through the shared settings hoo
});
renderPage();
const model = await screen.findByLabelText('model');
- await screen.findByRole('heading', { name: 'Wake' });
- await screen.findByRole('heading', { name: 'Policy' });
+ await screen.findByRole('heading', { name: 'Reach per pod' });
+ await screen.findByRole('heading', { name: 'Memory' });
await screen.findByRole('heading', { name: 'Skills' });
- const sections = Array.from(document.querySelectorAll('.v2-agent-settings h2')).map((node) => node.textContent);
- expect(sections).toEqual(expect.arrayContaining(['Identity', 'Policy', 'Skills']));
- expect(sections.indexOf('Key')).toBeLessThan(sections.indexOf('Identity'));
- expect(sections.indexOf('Identity')).toBeLessThan(sections.indexOf('Wake'));
- expect(sections.indexOf('Key')).toBeLessThan(sections.indexOf('How it runs'));
- expect(sections.indexOf('Wake')).toBeLessThan(sections.indexOf('How it runs'));
- expect(sections.indexOf('How it runs')).toBeLessThan(sections.indexOf('Connector grants'));
- expect(sections.indexOf('Connector grants')).toBeLessThan(sections.indexOf('Policy'));
- expect(sections.indexOf('Policy')).toBeLessThan(sections.indexOf('Skills'));
+ const sections = Array.from(document.querySelectorAll('.v2-agent-settings h2')).map((node) => node.textContent)
+ .filter((text) => text !== 'Identity');
+ expect(sections).toEqual(['Status', 'Runtime', 'Reach per pod', 'Memory', 'What it can use', 'Usage', 'Access key', 'Remove']);
fireEvent.change(model, { target: { value: 'new-model' } });
fireEvent.click(screen.getByRole('button', { name: 'Save runtime' }));
await waitFor(() => expect(axios.patch).toHaveBeenCalledWith(
@@ -475,7 +476,7 @@ test('runtime editor writes environment controls through the shared settings hoo
test('runtime editor keeps a wrapper model when no environment override exists', async () => {
axios.get.mockImplementation((url) => {
if (url.includes('/manage-settings')) return Promise.resolve({ data: {
- runtimeManaged: true, runtimeInventory: [{ harness: 'claude', installed: true, version: '1', models: null, efforts: ['low', 'medium', 'high', 'xhigh'] }], writablePodId: 'pod-1', installations: [{ podId: 'pod-1', podName: 'Workspace', status: 'active' }],
+ runtimeManaged: true, runtimeInventory: [{ harness: 'claude', installed: true, version: '1', models: ['old', 'new-model', 'selected-model', 'opus'], efforts: ['low', 'medium', 'high', 'xhigh'] }], writablePodId: 'pod-1', installations: [{ podId: 'pod-1', podName: 'Workspace', status: 'active' }],
} });
if (url.startsWith('/api/registry/pods/')) return Promise.resolve({ data: { agent: {
name: 'maya-claude', instanceId: 'default', displayName: 'Maya',
@@ -492,7 +493,7 @@ test('runtime editor shows the daemon-selected effort instead of a different pod
axios.patch.mockResolvedValue({ data: { updated: true } });
axios.get.mockImplementation((url) => {
if (url.includes('/manage-settings')) return Promise.resolve({ data: {
- runtimeManaged: true, runtimeInventory: [{ harness: 'claude', installed: true, version: '1', models: null, efforts: ['low', 'medium', 'high', 'xhigh'] }], writablePodId: 'skills-pod', runtimeSourcePodId: 'source-pod',
+ runtimeManaged: true, runtimeInventory: [{ harness: 'claude', installed: true, version: '1', models: ['old', 'new-model', 'selected-model', 'opus'], efforts: ['low', 'medium', 'high', 'xhigh'] }], writablePodId: 'skills-pod', runtimeSourcePodId: 'source-pod',
runtimeControls: { runtimeType: 'wrapper', adapter: 'claude', model: 'opus', effort: 'high' },
runtimeEditable: true, installations: [
{ podId: 'skills-pod', podName: 'Work', status: 'active' },
@@ -533,10 +534,11 @@ test('standalone CLI settings label the saved preference without claiming a runt
return Promise.resolve({ data: [] });
});
renderPage();
- expect(await screen.findByText('read only · this seat runs from the cli')).toBeInTheDocument();
- expect(screen.getByLabelText('effort')).toHaveValue('max');
- expect(screen.getByLabelText('effort')).toBeDisabled();
- expect(screen.getAllByText('unconfirmed')).toHaveLength(3);
+ await screen.findByRole('heading', { name: 'Runtime' });
+ expect(screen.queryByLabelText('effort')).toBeNull();
+ expect(screen.queryByDisplayValue('max')).toBeNull();
+ expect(screen.queryByDisplayValue('opus')).toBeNull();
+ expect(screen.getByText('not reported')).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Save runtime' })).not.toBeInTheDocument();
});
@@ -551,7 +553,7 @@ test('runner facts refresh without resetting unsaved controls, and a failed read
if (failed) return Promise.reject(new Error('offline'));
return Promise.resolve({ data: { writablePodId: 'pod-1', runtimeSourcePodId: 'pod-1',
runtimeManaged: true, runtimeEditable: true, runtimeControls: { runtimeType: 'wrapper', adapter: 'claude', model: 'saved' },
- runtimeInventory: [{ harness: 'claude', installed: true, version: '2.1', models: null, efforts: [] }],
+ runtimeInventory: [{ harness: 'claude', installed: true, version: '2.1', models: ['saved', 'unsaved-draft'], efforts: [] }],
processConnected: connected, runtimeObservation: { connectedAt: at, lastSeenAt: at, turnRunning: running,
lastTurnAt: at, lastSpawn: { harness: 'claude', version: '2.1', model: 'actual', effort: null, at }, confirmed: null },
installations: [{ podId: 'pod-1', podName: 'Work', status: 'active' }],
@@ -579,3 +581,156 @@ test('runner facts refresh without resetting unsaved controls, and a failed read
expect(input).toHaveValue('unsaved-draft');
} finally { view.unmount(); jest.useRealTimers(); }
});
+
+
+test.each(['hosted', 'native'])('rev3 %s shows the server run model and never requests credentials', async (runtimeType) => {
+ const defaultGet = axios.get.getMockImplementation();
+ axios.get.mockImplementation((url, options) => {
+ if (url.includes('/manage-settings')) return Promise.resolve({ data: {
+ writablePodId: 'pod-1', runtimeRun: { model: 'actual-server-model', startedAt: '2026-10-25T12:00:00Z', status: 'succeeded' },
+ runtimeControls: { model: 'saved-not-run' }, installations: [{ podId: 'pod-1', podName: 'Workspace', status: 'active' }],
+ } });
+ if (url === '/api/registry/pods/pod-1/agents') return Promise.resolve({ data: { agents: [{
+ name: 'maya-claude', instanceId: 'default', runtime: { runtimeType }, connection: { state: 'available' },
+ }] } });
+ if (url.startsWith('/api/registry/pods/')) return Promise.resolve({ data: { agent: {
+ name: 'maya-claude', instanceId: 'default', displayName: 'Maya', runtime: { runtimeType },
+ } } });
+ return defaultGet(url, options);
+ });
+ renderPage();
+ expect(await screen.findByText(/Commonly picks the model: actual-server-model/)).toBeInTheDocument();
+ expect(screen.queryByText('saved-not-run')).toBeNull();
+ expect(screen.queryByRole('heading', { name: 'Access key' })).toBeNull();
+ expect(screen.queryByText('Use my own API key')).toBeNull();
+ expect(axios.get.mock.calls.some(([url]) => url === '/api/credentials')).toBe(false);
+ if (runtimeType === 'native') {
+ expect(screen.getByText('built in')).toBeInTheDocument();
+ expect(screen.queryByRole('heading', { name: 'Usage' })).toBeNull();
+ } else {
+ const pause = await screen.findByRole('button', { name: 'Pause', exact: true });
+ axios.patch.mockResolvedValue({ data: {} });
+ fireEvent.click(pause);
+ await waitFor(() => expect(axios.patch).toHaveBeenCalledWith('/api/registry/pods/pod-1/agents/maya-claude',
+ { status: 'paused', instanceId: 'default' }));
+ }
+});
+
+test('the primary harness follows the last spawn, not a different saved choice', async () => {
+ const defaultGet = axios.get.getMockImplementation();
+ axios.get.mockImplementation((url, options) => {
+ if (url.includes('/manage-settings')) return Promise.resolve({ data: {
+ writablePodId: 'pod-1', runtimeManaged: true,
+ runtimeControls: { runtimeType: 'wrapper', adapter: 'codex', model: 'saved-model' },
+ runtimeObservation: { lastSpawn: { harness: 'claude', model: 'actual-model', effort: 'high', at: '2026-10-25T12:00:00Z' } },
+ installations: [{ podId: 'pod-1', podName: 'Workspace', status: 'active' }],
+ } });
+ return defaultGet(url, options);
+ });
+ renderPage();
+ const section = (await screen.findByRole('heading', { name: 'Runtime', exact: true })).closest('section');
+ expect(within(section).getByText('On your machine · Claude Code')).toBeInTheDocument();
+ expect(section).toHaveTextContent('actual-model · high');
+ expect(section).not.toHaveTextContent('saved-model');
+ expect(section).not.toHaveTextContent('On your machine · Codex');
+});
+
+
+test('managed runtimes keep the provider-key editor reachable', async () => {
+ const defaultGet = axios.get.getMockImplementation();
+ axios.get.mockImplementation((url, options) => {
+ if (url.includes('/manage-settings')) return Promise.resolve({ data: {
+ writablePodId: 'pod-1', runtimeManaged: true,
+ runtimeControls: { runtimeType: 'wrapper', adapter: 'claude' },
+ ownKeyCapability: { available: true, provider: 'anthropic' },
+ installations: [{ podId: 'pod-1', podName: 'Workspace', status: 'active' }],
+ } });
+ if (url.includes('/provider-key?')) return Promise.resolve({ data: { set: false } });
+ return defaultGet(url, options);
+ });
+ renderPage();
+ const button = await screen.findByRole('button', { name: 'Set key' });
+ await waitFor(() => expect(button).toBeEnabled());
+ const disclosure = button.closest('details');
+ expect(disclosure).toHaveTextContent('Change runtime');
+ fireEvent.click(disclosure.querySelector('summary'));
+ disclosure.open = true;
+ fireEvent.click(button);
+ expect(screen.getByLabelText('API key')).toHaveAttribute('type', 'password');
+ axios.put.mockResolvedValue({ data: { set: true, setAt: '2026-10-25T12:00:00Z' } });
+ fireEvent.change(screen.getByLabelText('API key'), { target: { value: 'provider-fixture-value' } });
+ fireEvent.click(screen.getByRole('button', { name: 'Save key' }));
+ await waitFor(() => expect(axios.put).toHaveBeenCalledWith(
+ '/api/registry/agents/maya-claude/provider-key?instanceId=default', { key: 'provider-fixture-value' },
+ expect.objectContaining({ headers: { Authorization: 'Bearer user-jwt' } }),
+ ));
+ expect(await screen.findByRole('button', { name: 'Replace key' })).toBeInTheDocument();
+ expect(screen.queryByLabelText('API key')).toBeNull();
+});
+
+test('never-connected agents keep removal folded and available', async () => {
+ const defaultGet = axios.get.getMockImplementation();
+ axios.get.mockImplementation((url, options) => {
+ if (url === '/api/registry/pods/pod-1/agents') return Promise.resolve({ data: { agents: [{
+ name: 'maya-claude', instanceId: 'default', runtime: { runtimeType: 'wrapper' }, connection: { state: 'never' },
+ }] } });
+ return defaultGet(url, options);
+ });
+ renderPage();
+ const heading = await screen.findByRole('heading', { name: 'Remove', hidden: true });
+ const disclosure = heading.closest('details');
+ expect(disclosure).not.toHaveAttribute('open');
+ fireEvent.click(disclosure.querySelector('summary'));
+ // jsdom does not implement the native summary toggle.
+ disclosure.open = true;
+ expect(within(disclosure).getByRole('button', { name: 'Remove from pod' })).toBeEnabled();
+});
+
+
+test('BYO without a receipt describes a future connection without a missing command', async () => {
+ renderPage();
+ const section = (await screen.findByRole('heading', { name: 'Runtime', exact: true })).closest('section');
+ expect(within(section).getByText('On your machine, once connected')).toBeInTheDocument();
+ expect(section).not.toHaveTextContent('To change it, run this there:');
+ expect(within(section).queryByRole('button', { name: 'Copy' })).toBeNull();
+});
+
+
+test.each(['en', 'zh-CN'])('key timestamps follow the selected language (%s)', async (language) => {
+ await i18n.changeLanguage(language);
+ const defaultGet = axios.get.getMockImplementation();
+ const dates = { createdAt: '2026-10-01T00:00:00Z', lastUsedAt: '2026-10-24T16:40:00Z', revokedAt: '2026-10-25T16:40:00Z' };
+ axios.get.mockImplementation((url, options) => url === '/api/credentials'
+ ? Promise.resolve({ data: [{ _id: 'old', label: 'Previous key', status: 'revoked', ...dates }] })
+ : defaultGet(url, options));
+ const view = renderPage();
+ try {
+ const text = await screen.findByText(language === 'en' ? '1 revoked' : '已撤销1个');
+ const disclosure = text.closest('details');
+ fireEvent.click(text);
+ disclosure.open = true;
+ const labels = language === 'en' ? ['Created', 'Last used', 'Revoked'] : ['创建于', '上次使用', '撤销于'];
+ for (const [index, value] of Object.values(dates).entries()) {
+ const date = new Date(value).toLocaleString(language, { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
+ expect(within(disclosure).getByText(`${labels[index]} ${date}`)).toBeInTheDocument();
+ }
+ } finally { view.unmount(); await i18n.changeLanguage('en'); }
+});
+
+
+test.each(['en', 'zh-CN'])('provider-key set timestamp follows the selected language (%s)', async (language) => {
+ await i18n.changeLanguage(language);
+ const setAt = '2026-10-25T16:40:00Z';
+ axios.get.mockResolvedValue({ data: { set: true, setAt, setBy: 'owner', lastUsedAt: null } });
+ const view = render(
+
+ );
+ try {
+ const date = new Date(setAt).toLocaleString(language, {
+ year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',
+ });
+ expect(await screen.findByText(`${i18n.t('agentManage.providerKey.set')} · ${date}`)).toBeInTheDocument();
+ expect(screen.queryByLabelText(i18n.t('agentManage.providerKey.input'))).toBeNull();
+ } finally { view.unmount(); await i18n.changeLanguage('en'); }
+});
diff --git a/frontend/src/v2/__tests__/V2AgentSkills.test.tsx b/frontend/src/v2/__tests__/V2AgentSkills.test.tsx
index 96cf09ada9..03959f6f7c 100644
--- a/frontend/src/v2/__tests__/V2AgentSkills.test.tsx
+++ b/frontend/src/v2/__tests__/V2AgentSkills.test.tsx
@@ -1,5 +1,6 @@
// @ts-nocheck
import React from 'react';
+import i18n from '../../i18n';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import axios from '../../utils/axiosConfig';
import V2AgentSkills from '../components/V2AgentSkills';
@@ -58,3 +59,15 @@ test('shows unavailable rather than not loaded when the last skill read failed',
expect(await screen.findAllByText('Skill status unavailable at last spawn')).toHaveLength(2);
expect(screen.queryByText('Not loaded at last spawn')).not.toBeInTheDocument();
});
+
+
+test.each(['en', 'zh-CN'])('skill report date uses app language (%s)', async (language) => {
+ await i18n.changeLanguage(language);
+ const view = render();
+ try {
+ const date = new Date(installed.skillLoad.at).toLocaleString(language, {
+ year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',
+ });
+ expect(await screen.findByText(i18n.t('agentSkills.reported', { date }))).toBeInTheDocument();
+ } finally { view.unmount(); await i18n.changeLanguage('en'); }
+});
diff --git a/frontend/src/v2/__tests__/V2AgentWakePods.test.tsx b/frontend/src/v2/__tests__/V2AgentWakePods.test.tsx
index a3ccb6e1ba..71a634826d 100644
--- a/frontend/src/v2/__tests__/V2AgentWakePods.test.tsx
+++ b/frontend/src/v2/__tests__/V2AgentWakePods.test.tsx
@@ -76,8 +76,25 @@ describe('per-agent Wake and Pods settings', () => {
expect(screen.getAllByText('New pod')).toHaveLength(2);
});
+ it('hides interval and turn estimates while heartbeat is off', async () => {
+ renderSettings();
+ const toggle = await screen.findByLabelText('Heartbeat');
+ expect(screen.queryByLabelText('Interval')).toBeNull();
+ expect(screen.queryByText(/turns\/day/)).toBeNull();
+ fireEvent.click(toggle);
+ expect(await screen.findByLabelText('Interval')).toBeInTheDocument();
+ expect(screen.getByText('24 turns/day · 30 available')).toBeInTheDocument();
+ await waitFor(() => expect(toggle).toBeEnabled());
+ fireEvent.click(toggle);
+ await waitFor(() => expect(screen.queryByLabelText('Interval')).toBeNull());
+ expect(screen.queryByText(/turns\/day/)).toBeNull();
+ });
+
it('previews heartbeat turns against the plan cap before saving', async () => {
renderSettings();
+ fireEvent.click(await screen.findByLabelText('Heartbeat'));
+ await screen.findByLabelText('Interval');
+ updatePod.mockClear();
expect(await screen.findByText('24 turns/day · 30 available')).toBeInTheDocument();
fireEvent.change(screen.getByLabelText('Interval'), { target: { value: '30' } });
expect(screen.getByText(/48 turns\/day · 30 available/)).toBeInTheDocument();
@@ -88,7 +105,7 @@ describe('per-agent Wake and Pods settings', () => {
it('states the daily turn count clearly when the cap is unavailable', async () => {
client.get.mockResolvedValue({ data: { activePodCount: 1, podCount: 1, installations: [{
podId: 'pod-1', podName: 'Launch', status: 'active', wakeOnMessage: false, humanOnly: false,
- boardWake: false, heartbeatEnabled: false, heartbeatMinutes: 60,
+ boardWake: false, heartbeatEnabled: true, heartbeatMinutes: 60,
}] } });
renderSettings();
expect(await screen.findByText('24 turns/day · plan cap unavailable')).toBeInTheDocument();
diff --git a/frontend/src/v2/__tests__/V2RuntimeControls.test.tsx b/frontend/src/v2/__tests__/V2RuntimeControls.test.tsx
index b82b17053e..3ba24ee5ad 100644
--- a/frontend/src/v2/__tests__/V2RuntimeControls.test.tsx
+++ b/frontend/src/v2/__tests__/V2RuntimeControls.test.tsx
@@ -1,4 +1,5 @@
import React from 'react';
+import i18n from '../../i18n';
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import Controls from '../components/V2RuntimeControls';
import Key from '../components/V2AgentProviderKey';
@@ -82,3 +83,20 @@ test('read-only inventory mismatch preserves the saved model without claiming a
expect(screen.queryByText(/saved-model · stored/)).toBeNull();
expect(screen.queryByText(/stored choice unsupported/)).toBeNull();
});
+
+
+test.each(['en', 'zh-CN'])('runtime observation dates use app language (%s)', async (language) => {
+ await i18n.changeLanguage(language);
+ const connectedAt = '2026-10-23T16:00:00Z';
+ const lastTurnAt = '2026-10-24T16:00:00Z';
+ const view = render();
+ try {
+ const formatted = (value: string) => new Date(value).toLocaleString(language, {
+ year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',
+ });
+ expect(screen.getByText(`${i18n.t('agentManage.controls.connected')} · ${formatted(connectedAt)}`)).toBeInTheDocument();
+ expect(screen.getByText(formatted(lastTurnAt))).toBeInTheDocument();
+ expect(screen.getByText(`${i18n.t('agentManage.controls.lastSpawn')} · ${formatted(at)}`)).toBeInTheDocument();
+ } finally { view.unmount(); await i18n.changeLanguage('en'); }
+});
diff --git a/frontend/src/v2/__tests__/V2YourTeamSignal.test.tsx b/frontend/src/v2/__tests__/V2YourTeamSignal.test.tsx
index f8a31fa8fd..e65b06e7c8 100644
--- a/frontend/src/v2/__tests__/V2YourTeamSignal.test.tsx
+++ b/frontend/src/v2/__tests__/V2YourTeamSignal.test.tsx
@@ -242,3 +242,12 @@ describe('Your Team (direction C)', () => {
expect(screen.getByPlaceholderText('Invitation code')).toBeInTheDocument();
});
});
+
+test.each([['u1', '/v2/agents/local/default'], ['another-owner', '/v2/agent/local/default']])(
+ 'agent names keep a usable destination for installer %s', async (installedBy, href) => {
+ renderPage({ pods: [{ _id: 'p1', name: 'Workspace' }], p1Agents: [{
+ name: 'local', instanceId: 'default', displayName: 'Local', installedBy,
+ }] });
+ expect(await screen.findByRole('link', { name: 'Local', exact: true })).toHaveAttribute('href', href);
+ },
+);
diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts
index 0f0ef26487..30fa89227b 100644
--- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts
+++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts
@@ -5151,3 +5151,23 @@ describe('v2 board on a phone (TASK-576)', () => {
expect(page).toContain('');
});
});
+
+describe('D44 rev3 agent Settings', () => {
+ const settings = read('../agents/v2-agent-settings.css');
+ test('does not stack feature-shell padding onto the phone Settings gutter', () => {
+ expect(ruleBody(settings, '.v2-feature--agent-settings > .v2-feature__body')).toContain('padding: 0');
+ });
+ test('runtime values and commands can wrap without widening the phone', () => {
+ expect(ruleBody(settings, '.v2-agent-runtime-summary dd')).toContain('overflow-wrap: anywhere');
+ expect(ruleBody(settings, '.v2-agent-runtime-summary__command code')).toContain('min-width: 0');
+ expect(ruleBody(settings, '.v2-agent-runtime-summary__command code')).toContain('overflow-wrap: normal');
+ expect(settings).toContain('.v2-root .v2-agent-settings .v2-agent-skills__catalog input,');
+ expect(settings).toContain('.v2-root .v2-agent-settings .v2-agent-wake-pods select,');
+ expect(ruleBody(settings, '.v2-root .v2-agent-settings .v2-agent-skills__catalog input')).toContain('font: inherit');
+ expect(settings).toContain('.v2-root .v2-agent-settings .v2-agent-runtime-grants__actions button:first-child,');
+ expect(settings).toContain('.v2-root .v2-agent-settings button:not(.MuiButtonBase-root),');
+ expect(settings).toContain('.v2-root .v2-agent-settings a.v2-agent-settings__action { min-height: 44px; }');
+ expect(settings).toContain('white-space: pre-wrap; overflow-x: auto');
+ expect(ruleBody(settings, '.v2-agent-runtime-summary__command code span')).toContain('white-space: nowrap');
+ });
+});
diff --git a/frontend/src/v2/agents/V2AgentPolicy.tsx b/frontend/src/v2/agents/V2AgentPolicy.tsx
index 69c1896846..0ee50efd26 100644
--- a/frontend/src/v2/agents/V2AgentPolicy.tsx
+++ b/frontend/src/v2/agents/V2AgentPolicy.tsx
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
import axios from '../../utils/axiosConfig';
import type { AgentSettingsInstallation, AgentSettingsPatch } from './useAgentSettings';
import './v2-agent-policy.css';
+import { formatAgentTimestamp } from './formatAgentTimestamp';
type MemorySection = 'soul' | 'long_term' | 'shared';
type MemoryDocument = { content: string; updatedAt: string | null; visibility: string };
@@ -19,10 +20,11 @@ interface Props {
instanceId: string;
installation: AgentSettingsInstallation | null;
update: (patch: AgentSettingsPatch) => Promise;
+ section?: 'memory' | 'usage' | 'all';
}
-const V2AgentPolicy: React.FC = ({ agentName, instanceId, installation, update }) => {
- const { t } = useTranslation();
+const V2AgentPolicy: React.FC = ({ agentName, instanceId, installation, update, section: visibleSection = 'all' }) => {
+ const { t, i18n } = useTranslation();
const environment = (installation?.config?.environment || {}) as { adapter?: string; sandbox?: { mode?: string } };
const runtime = installation?.runtime;
const adapter = String(environment.adapter || runtime?.adapter || runtime?.runtimeType || '')
@@ -30,7 +32,8 @@ const V2AgentPolicy: React.FC = ({ agentName, instanceId, installation, u
const canSetTrust = (runtime?.host === 'byo' || runtime?.runtimeType === 'wrapper')
&& trustAdapters.has(adapter);
const [trustMode, setTrustMode] = useState(environment.sandbox?.mode || '');
- const [quota, setQuota] = useState<{ turnCap: number; userRunCeiling: number | null } | null>(null);
+ const [quota, setQuota] = useState<{ turnCap: number; userRunCeiling: number | null;
+ usage?: { used: number; cap: number; resetsAt: string } | null } | null>(null);
const [documents, setDocuments] = useState(null);
const [section, setSection] = useState('long_term');
const [content, setContent] = useState('');
@@ -63,7 +66,7 @@ const V2AgentPolicy: React.FC = ({ agentName, instanceId, installation, u
setContent(response.data.sections.long_term?.content || '');
}
}).catch((requestError) => { if (current) setMemoryLoadError(message(requestError, t('agentPolicy.loadError'))); });
- void axios.get<{ turnCap: number; userRunCeiling: number | null }>(
+ void axios.get<{ turnCap: number; userRunCeiling: number | null; usage?: { used: number; cap: number; resetsAt: string } | null }>(
`/api/registry/agents/${encodeURIComponent(agentName)}/manage-settings`, { params: { instanceId } },
).then((response) => { if (current) setQuota(response.data); })
.catch((requestError) => { if (current) setQuotaError(message(requestError, t('agentPolicy.loadError'))); });
@@ -108,9 +111,9 @@ const V2AgentPolicy: React.FC = ({ agentName, instanceId, installation, u
className={feedback.kind === 'error' ? 'v2-agent-policy__error' : 'v2-agent-policy__saved'}>{feedback.text}
);
- return
- {t('agentPolicy.title')}
- {canSetTrust &&
+ return
+ {t(visibleSection === 'memory' ? 'agentPolicy.memory' : visibleSection === 'usage' ? 'agentSettings.rev3.usage' : 'agentPolicy.title')}
+ {visibleSection === 'all' && canSetTrust &&
{t('agentPolicy.trust')}
}
-
-
{t('agentPolicy.memory')}
+ {visibleSection !== 'usage' &&
+ {visibleSection === 'all' &&
{t('agentPolicy.memory')}
}
{documents ? <>
{memorySections.map((entry) =>
> :
{t('agentPolicy.loadingMemory')}
}
{groupFeedback('memory')}
-
- {(installation?.runtime?.runtimeType === 'hosted' || installation?.runtime?.runtimeType === 'native') && (
+
}
+ {visibleSection !== 'memory' && (installation?.runtime?.runtimeType === 'hosted' || (visibleSection === 'all' && installation?.runtime?.runtimeType === 'native')) && (
{t('agentPolicy.quotas')}
+
{t('agentSettings.rev3.turnsToday')}{quota?.usage ? `${quota.usage.used} / ${quota.usage.cap}` : '—'}
+ {quota?.usage &&
{t('agentSettings.rev3.resets', { date: formatAgentTimestamp(quota.usage.resetsAt, i18n.language) })}
}
{t('agentPolicy.turnsPerDay')}{quota?.turnCap ?? '—'}
{t('agentPolicy.userRunCeiling')}{quota?.userRunCeiling ?? '—'}
{quotaError &&
{quotaError}
}
)}
+ {visibleSection === 'usage' && installation?.runtime?.runtimeType !== 'hosted'
+ &&
{t('agentSettings.rev3.machineUsage')}
}
;
};
diff --git a/frontend/src/v2/agents/V2AgentRuntimeSummary.tsx b/frontend/src/v2/agents/V2AgentRuntimeSummary.tsx
new file mode 100644
index 0000000000..0be8a5abd4
--- /dev/null
+++ b/frontend/src/v2/agents/V2AgentRuntimeSummary.tsx
@@ -0,0 +1,45 @@
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { describeRuntime, Runtime } from '../components/V2AgentRuntimeGrants';
+
+export const agentRuntimeKind = (runtime: Runtime, connected: boolean | null) => {
+ const details = describeRuntime(runtime);
+ if (details.mode === 'native') return 'builtIn';
+ if (details.mode === 'hosted') return 'hosted';
+ return connected === false && !runtime.observation?.lastSpawn ? 'notConnected' : 'byo';
+};
+
+const V2AgentRuntimeSummary: React.FC<{
+ name: string; runtime: Runtime; serverModel?: string | null;
+}> = ({ name, runtime, serverModel }) => {
+ const { t } = useTranslation();
+ const [copyError, setCopyError] = useState(false);
+ const details = describeRuntime(runtime);
+ const onCommonly = details.mode === 'native' || details.mode === 'hosted';
+ const last = runtime.observation?.lastSpawn;
+ const observedHarness = last ? describeRuntime({ ...runtime, adapter: last.harness }).adapter || last.harness : null;
+ const model = onCommonly ? serverModel : last?.model;
+ const configCommand = `commonly agent config ${name} --model
--effort `;
+ const copy = async () => {
+ setCopyError(false);
+ try { await navigator.clipboard.writeText(configCommand); }
+ catch { setCopyError(true); }
+ };
+ return
+
+ - {t('agentSettings.rev3.where')}
- {t(`agentSettings.rev3.${details.mode === 'native' ? 'builtInLocation' : onCommonly ? 'onCommonly' : last ? 'onMachine' : 'onceConnected'}`)}
+ {observedHarness ? ` · ${observedHarness}` : ''}
+ - {t(`agentSettings.rev3.${onCommonly ? 'model' : 'modelEffort'}`)}
+ - {onCommonly && t('agentSettings.rev3.modelPrefix')}{model || t('agentManage.controls.notReported')}
+ {!onCommonly && last?.effort ? ` · ${last.effort}` : ''}
+ {(onCommonly || last) && {t(`agentSettings.rev3.${onCommonly ? 'serverReported' : 'machineReported'}`)}}
+
+
+ {!onCommonly && last &&
{configCommand.split(' ').map((word, index) => {index > 0 && ' '}{word})}
+
+
}
+ {copyError &&
{t('agentSettings.rev3.copyError')}
}
+
;
+};
+
+export default V2AgentRuntimeSummary;
diff --git a/frontend/src/v2/agents/V2AgentSettingsPage.tsx b/frontend/src/v2/agents/V2AgentSettingsPage.tsx
index 068c98aa90..8212bdd14f 100644
--- a/frontend/src/v2/agents/V2AgentSettingsPage.tsx
+++ b/frontend/src/v2/agents/V2AgentSettingsPage.tsx
@@ -3,15 +3,20 @@ import { Link, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import axios from '../../utils/axiosConfig';
import V2PageHead from '../components/V2PageHead';
-import V2AgentRuntimeGrants, { Runtime } from '../components/V2AgentRuntimeGrants';
-import { AgentControlLoader } from '../components/V2AgentControlSection';
+import V2AgentRuntimeGrants, { Runtime, describeRuntime } from '../components/V2AgentRuntimeGrants';
import { AgentConnection } from '../utils/agentConnection';
import { useAgentSettings } from './useAgentSettings';
-import V2AgentIdentitySection from './V2AgentIdentitySection';
import V2AgentWakePods from './V2AgentWakePods';
import V2AgentPolicy from './V2AgentPolicy';
import V2AgentSkills from '../components/V2AgentSkills';
+import V2Avatar from '../components/V2Avatar';
+import V2AgentIdentitySection from './V2AgentIdentitySection';
+import V2RuntimeControls from '../components/V2RuntimeControls';
+import V2AgentProviderKey from '../components/V2AgentProviderKey';
+import V2AgentStatus from './V2AgentStatus';
+import V2AgentRuntimeSummary, { agentRuntimeKind } from './V2AgentRuntimeSummary';
import './v2-agent-settings.css';
+import { formatAgentTimestamp } from './formatAgentTimestamp';
interface RuntimeKey {
_id: string;
@@ -24,10 +29,10 @@ interface RuntimeKey {
status: 'active' | 'revoked';
}
-const dateLabel = (value: string | null | undefined, t: (key: string) => string): string => {
+const dateLabel = (value: string | null | undefined, t: (key: string) => string, language: string): string => {
if (!value) return t('agentSettings.never');
const date = new Date(value);
- return Number.isNaN(date.getTime()) ? t('agentSettings.unknown') : date.toLocaleString();
+ return Number.isNaN(date.getTime()) ? t('agentSettings.unknown') : formatAgentTimestamp(value, language);
};
const errorLabel = (error: unknown, fallback: string): string => (
@@ -37,9 +42,9 @@ const errorLabel = (error: unknown, fallback: string): string => (
);
const V2AgentSettingsPage: React.FC = () => {
- const { t } = useTranslation();
+ const { t, i18n } = useTranslation();
const { agentName = '', instanceId = '' } = useParams<{ agentName: string; instanceId: string }>();
- const { access, installation, podId, ownKeyCapability, runtimeControls, runtimeObservation, runtimeInventory, processConnected, runtimeEditable, runtimeManaged, loading,
+ const { access, installation, podId, ownKeyCapability, runtimeControls, runtimeObservation, runtimeInventory, processConnected, runtimeEditable, runtimeManaged, runtimeRun, heldTask, loading,
error: loadError, refresh, update, updatePod, updateRuntime } = useAgentSettings(agentName, instanceId);
const [removedLast, setRemovedLast] = useState(false);
const [keys, setKeys] = useState([]);
@@ -55,6 +60,9 @@ const V2AgentSettingsPage: React.FC = () => {
const currentIdentity = useRef(identityKey);
currentIdentity.current = identityKey;
const disconnected = connectionState === 'revoked' || connectionState === 'stale';
+ const inProcess = !runtimeManaged && (installation?.runtime?.runtimeType === 'native'
+ || installation?.runtime?.runtimeType === 'hosted' || installation?.runtime?.runtimeType === 'internal'
+ || installation?.runtime?.host === 'cloud');
const onConnectionState = useCallback((state: AgentConnection['state'] | null) => {
setConnectionState(state);
@@ -82,7 +90,7 @@ const V2AgentSettingsPage: React.FC = () => {
setPendingAction(null);
setBusy(false);
setConnectionState(null);
- if (access && podId) {
+ if (access && podId && !inProcess) {
void fetchKeys().then((rows) => {
if (active) {
setKeys(rows);
@@ -99,7 +107,7 @@ const V2AgentSettingsPage: React.FC = () => {
});
}
return () => { active = false; };
- }, [access, podId, fetchKeys, identityKey, t]);
+ }, [access, podId, fetchKeys, identityKey, inProcess, t]);
const replaceKey = async () => {
if (!access || !podId || pendingAction !== 'replace' || busy || installation?.runtime?.runtimeType === 'wrapper') return;
@@ -157,6 +165,7 @@ const V2AgentSettingsPage: React.FC = () => {
setKeys([]);
setPendingAction(null);
setOneTimeKey(null);
+ setConnectionRefreshNonce((nonce) => nonce + 1);
if (wasLastPod) setRemovedLast(true);
else void refresh().catch((requestError) => setError(errorLabel(requestError, t('agentSettings.unavailable'))));
};
@@ -166,7 +175,7 @@ const V2AgentSettingsPage: React.FC = () => {
if (!access || !podId) return
{error || errorLabel(loadError, t('agentSettings.unavailable'))}
;
- if (keysReadyFor !== identityKey) return
+ if (!inProcess && keysReadyFor !== identityKey) return
{error || t('agentSettings.loading')}
;
@@ -188,6 +197,10 @@ const V2AgentSettingsPage: React.FC = () => {
...(runtimeManaged !== undefined ? { managed: runtimeManaged } : {}),
};
const activeKeys = keys.filter((key) => key.status === 'active');
+ const kind = agentRuntimeKind(runtime, connectionState === 'never' ? false : processConnected);
+ const harnessLabel = runtimeObservation?.lastSpawn
+ ? describeRuntime({ ...runtime, adapter: runtimeObservation.lastSpawn.harness }).adapter
+ || runtimeObservation.lastSpawn.harness : null;
const revokedKeys = keys.filter((key) => key.status === 'revoked');
const keyLabel = (key: RuntimeKey): string => {
const defaultLabel = !key.label || key.label === 'Runtime token' || key.label === 'Agent Settings replacement';
@@ -202,9 +215,9 @@ const V2AgentSettingsPage: React.FC = () => {
{key.status === 'revoked' ? t('agentSettings.revoked') : t('agentSettings.active')}
- {t('agentSettings.created', { date: dateLabel(key.createdAt, t) })}
- {t('agentSettings.lastUsed', { date: dateLabel(key.lastUsedAt, t) })}
- {key.status === 'revoked' && {t('agentSettings.revokedAt', { date: dateLabel(key.revokedAt, t) })}}
+ {t('agentSettings.created', { date: dateLabel(key.createdAt, t, i18n.language) })}
+ {t('agentSettings.lastUsed', { date: dateLabel(key.lastUsedAt, t, i18n.language) })}
+ {key.status === 'revoked' && {t('agentSettings.revokedAt', { date: dateLabel(key.revokedAt, t, i18n.language) })}}
{key.status === 'active' && pendingAction === key._id && (
@@ -222,21 +235,64 @@ const V2AgentSettingsPage: React.FC = () => {
)}
);
+ const runtimeSummary =
;
return (
- {t('agentSettings.yourTeam')}} />
+
+ {access.displayName || agentName}}
+ actions={{t('agentSettings.yourTeam')}} />
+ {kind === 'byo' ? harnessLabel || t('agentSettings.rev3.onMachine') : t(`agentSettings.rev3.${kind === 'notConnected' ? 'kindNotConnected' : kind}`)}
-
+ {t(installation.name.toLowerCase() === 'openclaw' ? 'agentSettings.identityTitle' : 'agentSettings.displayName')}
+
+ }
+
-
+ {connectionState === 'never' ?
+ {t('agentSettings.rev3.runtime')}
{runtimeSummary}
+ : }
+ {runtimeManaged && runtimeEditable &&
+ {t('agentSettings.rev3.editRuntime')}
+
+
+ }
+
+ {connectionState !== 'never' && <>
+
+
+
+
+ pod.id === podId)?.name || podId} agentName={agentName} instanceId={instanceId} />
+
+ {kind !== 'builtIn' && }
+ {!inProcess &&
-
{t('agentSettings.key')}
+
{t('agentSettings.rev3.key')}
{keysForbidden ? null : installation?.runtime?.runtimeType === 'wrapper' ? (
-
{t('agentSettings.wrapperReconnect')}
+ connectionState === 'connected' ?
+ {t('agentSettings.regenerate')}
+ :
{t('agentSettings.wrapperReconnect')}
) : !disconnected && (
: {t('agentSkills.empty')}
)}
- {skillLoad?.at && {t('agentSkills.reported', { date: new Date(skillLoad.at).toLocaleString() })}
}
+ {skillLoad?.at && {t('agentSkills.reported', { date: formatAgentTimestamp(skillLoad.at, i18n.language) })}
}
{catalogOpen && canEdit &&
setQuery(event.target.value)} placeholder={t('agentSkills.search')} />
diff --git a/frontend/src/v2/components/V2RuntimeControls.tsx b/frontend/src/v2/components/V2RuntimeControls.tsx
index 4231d5f4ed..eff02d2c24 100644
--- a/frontend/src/v2/components/V2RuntimeControls.tsx
+++ b/frontend/src/v2/components/V2RuntimeControls.tsx
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
+import { formatAgentTimestamp } from '../agents/formatAgentTimestamp';
import './V2RuntimeControls.css';
export interface RuntimeReceipt { harness: string; version: string | null; model: string | null; effort: string | null; at: string }
@@ -18,7 +19,7 @@ export type RuntimeUpdate = { adapter?: string; model?: string; effort?: string
const LABELS: Record
= { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', hermes: 'Hermes', dsh: 'DeepSeek Harness' };
const canonical = (value?: string) => value === 'claude-code' ? 'claude' : value || '';
const V2RuntimeControls: React.FC<{ runtime: RuntimeSettings; onSave?: (update: RuntimeUpdate) => Promise }> = ({ runtime, onSave }) => {
- const { t } = useTranslation();
+ const { t, i18n } = useTranslation();
const k = (key: string, values?: Record) => t(`agentManage.controls.${key}`, values);
const savedHarness = canonical(runtime.adapter || runtime.runtimeType);
const [harness, setHarness] = useState(savedHarness);
@@ -100,10 +101,10 @@ const V2RuntimeControls: React.FC<{ runtime: RuntimeSettings; onSave?: (update:
{error && {error}
}
{editable && { void save(); }}>{k('save')}}
- - {k('process')}
- {runtime.processConnected === true ? k('connected') : runtime.processConnected === false ? k('disconnected') : k('notReported')}{runtime.processConnected && runtime.observation?.connectedAt ? ` · ${new Date(runtime.observation.connectedAt).toLocaleString()}` : ''}
- - {k('turn')}
- {runtime.processConnected == null ? k('notReported') : runtime.processConnected && runtime.observation?.turnRunning ? k('running') : runtime.observation?.lastTurnAt ? new Date(runtime.observation.lastTurnAt).toLocaleString() : k('noTurn')}
+ - {k('process')}
- {runtime.processConnected === true ? k('connected') : runtime.processConnected === false ? k('disconnected') : k('notReported')}{runtime.processConnected && runtime.observation?.connectedAt ? ` · ${formatAgentTimestamp(runtime.observation.connectedAt, i18n.language)}` : ''}
+ - {k('turn')}
- {runtime.processConnected == null ? k('notReported') : runtime.processConnected && runtime.observation?.turnRunning ? k('running') : runtime.observation?.lastTurnAt ? formatAgentTimestamp(runtime.observation.lastTurnAt, i18n.language) : k('noTurn')}
- {last && {k('lastSpawn')} · {new Date(last.at).toLocaleString()}
}
+ {last && {k('lastSpawn')} · {formatAgentTimestamp(last.at, i18n.language)}
}
;
};
export default V2RuntimeControls;
diff --git a/frontend/src/v2/components/V2YourTeamPage.tsx b/frontend/src/v2/components/V2YourTeamPage.tsx
index 14bee1a57c..1804899563 100644
--- a/frontend/src/v2/components/V2YourTeamPage.tsx
+++ b/frontend/src/v2/components/V2YourTeamPage.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react';
-import { useNavigate } from 'react-router-dom';
+import { Link, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import axios from 'axios';
import { useAuth } from '../../context/AuthContext';
@@ -364,7 +364,7 @@ const V2YourTeamPage: React.FC = () => {
-
{display}
+
{display}
{(!a.connection || a.connection.state === 'connected') && state !== 'idle' && {STATE_DOT}}
{(!a.connection || a.connection.state === 'connected') && state !== 'idle' ? ' ' : ''}
diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css
index 23f9de9860..2ef0a83ad2 100644
--- a/frontend/src/v2/v2.css
+++ b/frontend/src/v2/v2.css
@@ -13159,6 +13159,7 @@ body.modern-ui.v2-canvas {
overflow: hidden;
min-width: 0;
}
+.v2-root a.v2-team-card__name { color: var(--v2-text-primary); text-decoration: none; }
.v2-team-card__status { margin-top: 2px; font: 500 11px/16px var(--v2-font-mono); color: var(--v2-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.v2-team-card__status--needsYou,
.v2-team-card__status--working { color: var(--v2-accent-text); }