mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix: gate tui host footer behind config
This commit is contained in:
parent
479e2aaae3
commit
d48778994f
13 changed files with 131 additions and 14 deletions
|
|
@ -40,6 +40,7 @@ Notes:
|
|||
- `--local` cannot be combined with `--url`, `--token`, or `--password`.
|
||||
- `tui` resolves configured gateway auth SecretRefs for token/password auth when possible (`env`/`file`/`exec` providers).
|
||||
- When launched from inside a configured agent workspace directory, TUI auto-selects that agent for the session key default (unless `--session` is explicitly `agent:<id>:...`).
|
||||
- To show the Gateway hostname in the footer for non-local URL-backed connections, run `openclaw config set tui.footer.showRemoteHost true`. The host label is off by default and never appears for loopback or embedded local connections.
|
||||
- Local mode uses the embedded agent runtime directly. Most local tools work, but Gateway-only features are unavailable.
|
||||
- Local mode adds `/auth [provider]` inside the TUI command surface.
|
||||
- Plugin approval gates still apply in local mode. Tools that require approval prompt for a decision in the terminal; nothing is silently auto-approved because the Gateway is not involved.
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ Notes:
|
|||
- Header: connection URL, current agent, current session.
|
||||
- Chat log: user messages, assistant replies, system notices, tool cards.
|
||||
- Status line: connection/run state (connecting, running, streaming, idle, error).
|
||||
- Footer: connection host when available + agent + session + model + goal state + think/fast/verbose/trace/reasoning + token counts + deliver.
|
||||
- Footer: agent + session + model + goal state + think/fast/verbose/trace/reasoning + token counts + deliver. When `tui.footer.showRemoteHost` is enabled, remote Gateway connections also show the connection host.
|
||||
- Input: text editor with autocomplete.
|
||||
|
||||
## Mental model: agents + sessions
|
||||
|
|
@ -67,7 +67,15 @@ Notes:
|
|||
- Session scope:
|
||||
- `per-sender` (default): each agent has many sessions.
|
||||
- `global`: the TUI always uses the `global` session (the picker may be empty).
|
||||
- For URL-backed connections, the footer includes the connection host alongside the current agent and session.
|
||||
- The current agent + session are always visible in the footer.
|
||||
- To show the Gateway host for non-local URL-backed connections, opt in with:
|
||||
|
||||
```bash
|
||||
openclaw config set tui.footer.showRemoteHost true
|
||||
```
|
||||
|
||||
Loopback and embedded local connections never show a host label.
|
||||
|
||||
- If the session has a [goal](/tools/goal), the footer shows its compact state
|
||||
such as `Pursuing goal`, `Goal paused (/goal resume)`, or
|
||||
`Goal achieved`.
|
||||
|
|
|
|||
|
|
@ -479,6 +479,32 @@ describe("ui.seamColor", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("tui.footer.showRemoteHost", () => {
|
||||
it("accepts the TUI remote-host footer toggle", () => {
|
||||
const result = OpenClawSchema.safeParse({
|
||||
tui: {
|
||||
footer: {
|
||||
showRemoteHost: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects unknown TUI footer keys", () => {
|
||||
const result = OpenClawSchema.safeParse({
|
||||
tui: {
|
||||
footer: {
|
||||
showLocalHost: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("gateway.controlUi.embedSandbox", () => {
|
||||
it("accepts strict, scripts, and trusted modes", () => {
|
||||
for (const mode of ["strict", "scripts", "trusted"] as const) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ const ROOT_SECTIONS = [
|
|||
"commitments",
|
||||
"browser",
|
||||
"ui",
|
||||
"tui",
|
||||
"auth",
|
||||
"models",
|
||||
"nodeHost",
|
||||
|
|
|
|||
|
|
@ -1354,6 +1354,11 @@ export const FIELD_HELP: Record<string, string> = {
|
|||
"Display name shown for the assistant in UI views, chat chrome, and status contexts. Keep this stable so operators can reliably identify which assistant persona is active.",
|
||||
"ui.assistant.avatar":
|
||||
"Assistant avatar image source used in UI surfaces (URL, path, or data URI depending on runtime support). Use trusted assets and consistent branding dimensions for clean rendering.",
|
||||
tui: "Terminal UI display settings. Use this section for terminal-only presentation preferences without changing Gateway or other UI behavior.",
|
||||
"tui.footer":
|
||||
"Terminal UI footer display settings. Keep optional context compact so session, model, goal, and token information stay readable.",
|
||||
"tui.footer.showRemoteHost":
|
||||
"Show the remote Gateway hostname in the TUI footer for non-local URL-backed connections. Default: false. Loopback and embedded local connections never show a host label.",
|
||||
plugins:
|
||||
"Plugin system controls for enabling extensions, constraining load scope, configuring entries, and tracking installs. Keep plugin policy explicit and least-privilege in production environments.",
|
||||
"plugins.enabled":
|
||||
|
|
|
|||
|
|
@ -786,6 +786,9 @@ export const FIELD_LABELS: Record<string, string> = {
|
|||
"ui.assistant": "Assistant Appearance",
|
||||
"ui.assistant.name": "Assistant Name",
|
||||
"ui.assistant.avatar": "Assistant Avatar",
|
||||
tui: "Terminal UI",
|
||||
"tui.footer": "Terminal UI Footer",
|
||||
"tui.footer.showRemoteHost": "Show Remote Host in TUI Footer",
|
||||
"browser.evaluateEnabled": "Browser Evaluate Enabled",
|
||||
"browser.snapshotDefaults": "Browser Snapshot Defaults",
|
||||
"browser.snapshotDefaults.mode": "Browser Snapshot Mode",
|
||||
|
|
|
|||
|
|
@ -166,6 +166,14 @@ export type OpenClawConfig = {
|
|||
avatar?: string;
|
||||
};
|
||||
};
|
||||
/** Terminal UI display settings. */
|
||||
tui?: {
|
||||
/** Footer display settings for the terminal UI. */
|
||||
footer?: {
|
||||
/** Show the remote Gateway hostname in the footer for non-local URL-backed connections. */
|
||||
showRemoteHost?: boolean;
|
||||
};
|
||||
};
|
||||
/** Secret providers, defaults, and ref-resolution settings. */
|
||||
secrets?: SecretsConfig;
|
||||
/** Skill loading and bundled skill configuration. */
|
||||
|
|
|
|||
|
|
@ -709,6 +709,17 @@ export const OpenClawSchema = z
|
|||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
tui: z
|
||||
.object({
|
||||
footer: z
|
||||
.object({
|
||||
showRemoteHost: z.boolean().optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional(),
|
||||
secrets: SecretsConfigSchema,
|
||||
auth: z
|
||||
.object({
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import {
|
|||
extractContentFromMessage,
|
||||
extractTextFromMessage,
|
||||
extractThinkingFromMessage,
|
||||
formatConnectionHostFooter,
|
||||
formatGoalFooter,
|
||||
formatRemoteConnectionHostFooter,
|
||||
isCommandMessage,
|
||||
sanitizeRenderableText,
|
||||
} from "./tui-formatters.js";
|
||||
|
|
@ -46,16 +46,20 @@ describe("formatGoalFooter", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("formatConnectionHostFooter", () => {
|
||||
it("renders only the connection hostname", () => {
|
||||
expect(formatConnectionHostFooter("ws://gateway-host:18789")).toBe("host gateway-host");
|
||||
describe("formatRemoteConnectionHostFooter", () => {
|
||||
it("renders only the remote connection hostname", () => {
|
||||
expect(formatRemoteConnectionHostFooter("ws://gateway-host:18789")).toBe("host gateway-host");
|
||||
expect(
|
||||
formatConnectionHostFooter("wss://user:secret@example.com:443/path?token=redacted"),
|
||||
formatRemoteConnectionHostFooter("wss://user:secret@example.com:443/path?token=redacted"),
|
||||
).toBe("host example.com");
|
||||
});
|
||||
|
||||
it("skips non-url local connection labels", () => {
|
||||
expect(formatConnectionHostFooter("local embedded")).toBeNull();
|
||||
it("skips local and non-url connection labels", () => {
|
||||
expect(formatRemoteConnectionHostFooter("local embedded")).toBeNull();
|
||||
expect(formatRemoteConnectionHostFooter("ws://localhost:18789")).toBeNull();
|
||||
expect(formatRemoteConnectionHostFooter("ws://127.0.0.1:18789")).toBeNull();
|
||||
expect(formatRemoteConnectionHostFooter("ws://127.1:18789")).toBeNull();
|
||||
expect(formatRemoteConnectionHostFooter("ws://[::1]:18789")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { stripAnsi } from "../../packages/terminal-core/src/ansi.js";
|
||||
import { stripLeadingInboundMetadata } from "../auto-reply/reply/strip-inbound-meta.js";
|
||||
import type { SessionGoal } from "../config/sessions/types.js";
|
||||
import { isLoopbackHost } from "../gateway/net.js";
|
||||
import { formatRawAssistantErrorForUi } from "../shared/assistant-error-format.js";
|
||||
import { extractAssistantVisibleText } from "../shared/chat-message-content.js";
|
||||
import { formatTokenCount } from "../utils/usage-format.js";
|
||||
|
|
@ -443,10 +444,10 @@ export function formatTokens(total?: number | null, context?: number | null) {
|
|||
return `tokens ${totalLabel}/${formatTokenCount(context)}${pct !== null ? ` (${pct}%)` : ""}`;
|
||||
}
|
||||
|
||||
export function formatConnectionHostFooter(connectionUrl: string): string | null {
|
||||
export function formatRemoteConnectionHostFooter(connectionUrl: string): string | null {
|
||||
try {
|
||||
const hostname = new URL(connectionUrl.trim()).hostname.trim();
|
||||
return hostname ? `host ${hostname}` : null;
|
||||
return hostname && !isLoopbackHost(hostname) ? `host ${hostname}` : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -364,7 +364,7 @@ describe.sequential("TUI PTY harness", () => {
|
|||
|
||||
it("renders local ready on startup", () => {
|
||||
expect(fixture.run.output()).toContain("local ready");
|
||||
expect(fixture.run.output()).toContain("host local");
|
||||
expect(fixture.run.output()).not.toContain("host local");
|
||||
});
|
||||
|
||||
it(
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
resolveLocalAuthSpawnCwd,
|
||||
resolveLocalAuthSpawnOptions,
|
||||
resolveTuiCtrlCAction,
|
||||
resolveTuiFooterHostLabel,
|
||||
resolveTuiShutdownHardExitMs,
|
||||
resolveTuiSessionKey,
|
||||
scheduleProcessExitAfterTuiReturn,
|
||||
|
|
@ -64,6 +65,40 @@ describe("resolveFinalAssistantText", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("resolveTuiFooterHostLabel", () => {
|
||||
it("hides connection host by default", () => {
|
||||
expect(
|
||||
resolveTuiFooterHostLabel({
|
||||
config: {},
|
||||
connectionUrl: "wss://gateway.example.com/ws",
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("renders only remote hosts when explicitly enabled", () => {
|
||||
const config = { tui: { footer: { showRemoteHost: true } } } satisfies OpenClawConfig;
|
||||
|
||||
expect(
|
||||
resolveTuiFooterHostLabel({
|
||||
config,
|
||||
connectionUrl: "wss://user:secret@gateway.example.com/ws?token=hidden",
|
||||
}),
|
||||
).toBe("host gateway.example.com");
|
||||
expect(
|
||||
resolveTuiFooterHostLabel({
|
||||
config,
|
||||
connectionUrl: "ws://127.0.0.1:18789",
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(
|
||||
resolveTuiFooterHostLabel({
|
||||
config,
|
||||
connectionUrl: "local embedded",
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("tui slash commands", () => {
|
||||
it("treats /elev as an alias for /elevated", () => {
|
||||
expect(parseCommand("/elev on")).toEqual({ name: "elevated", args: "on" });
|
||||
|
|
|
|||
|
|
@ -35,7 +35,11 @@ import { editorTheme, theme } from "./theme/theme.js";
|
|||
import type { TuiBackend } from "./tui-backend.js";
|
||||
import { createCommandHandlers } from "./tui-command-handlers.js";
|
||||
import { createEventHandlers } from "./tui-event-handlers.js";
|
||||
import { formatConnectionHostFooter, formatGoalFooter, formatTokens } from "./tui-formatters.js";
|
||||
import {
|
||||
formatGoalFooter,
|
||||
formatRemoteConnectionHostFooter,
|
||||
formatTokens,
|
||||
} from "./tui-formatters.js";
|
||||
import {
|
||||
buildTuiLastSessionScopeKey,
|
||||
readTuiLastSessionKey,
|
||||
|
|
@ -171,6 +175,16 @@ export function resolveTuiSessionKey(params: {
|
|||
return `agent:${params.currentAgentId}:${normalizeLowercaseStringOrEmpty(trimmed)}`;
|
||||
}
|
||||
|
||||
export function resolveTuiFooterHostLabel(params: {
|
||||
config: OpenClawConfig;
|
||||
connectionUrl: string;
|
||||
}): string | null {
|
||||
if (params.config.tui?.footer?.showRemoteHost !== true) {
|
||||
return null;
|
||||
}
|
||||
return formatRemoteConnectionHostFooter(params.connectionUrl);
|
||||
}
|
||||
|
||||
export function resolveInitialTuiAgentId(params: {
|
||||
cfg: OpenClawConfig;
|
||||
fallbackAgentId: string;
|
||||
|
|
@ -1199,7 +1213,7 @@ export async function runTui(opts: RunTuiOptions): Promise<TuiResult> {
|
|||
const reasoning = sessionInfo.reasoningLevel ?? "off";
|
||||
const reasoningLabel =
|
||||
reasoning === "on" ? "reasoning" : reasoning === "stream" ? "reasoning:stream" : null;
|
||||
const hostLabel = formatConnectionHostFooter(client.connection.url);
|
||||
const hostLabel = resolveTuiFooterHostLabel({ config, connectionUrl: client.connection.url });
|
||||
const footerParts = [
|
||||
hostLabel,
|
||||
`agent ${agentLabel}`,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue