fix: steer agents to safe gateway config flow

This commit is contained in:
Peter Steinberger
2026-04-26 05:00:09 +01:00
parent 44da034516
commit 9ed11d6c49
12 changed files with 114 additions and 4 deletions

View File

@@ -88,6 +88,7 @@ Docs: https://docs.openclaw.ai
plain prompts instead of OpenClaw internal runtime-context envelopes, while
keeping those envelopes out of ACP transcripts.
- TTS/status: show configured TTS model, voice, and sanitized custom endpoint in `/status`, preserve OpenAI-compatible TTS instructions on custom endpoints, and retry empty Microsoft/Edge TTS output once. Addresses #46602, #47232, and #43936. Thanks @leekuangtao, @Huntterxx, and @rex993.
- Agents/Gateway: steer agent-driven config edits and restarts through the owner-only `gateway` tool, document `config.schema.lookup` as the field-doc source, and warn against using `gateway stop && gateway start` as a restart substitute on macOS. Fixes #71929. Thanks @ygc3817922006-sketch.
- Providers/vLLM: send Nemotron 3 chat-template kwargs when thinking is off
and honor configured `params.chat_template_kwargs` for OpenAI-compatible
completions, so vLLM/Nemotron replies stay visible instead of becoming

View File

@@ -324,6 +324,7 @@ Command options:
Notes:
- `gateway install` supports `--port`, `--runtime`, `--token`, `--force`, `--json`.
- Use `gateway restart` to restart a managed service. Do not chain `gateway stop` and `gateway start` as a restart substitute; on macOS, `gateway stop` intentionally disables the LaunchAgent before stopping it.
- When token auth requires a token and `gateway.auth.token` is SecretRef-managed, `gateway install` validates that the SecretRef is resolvable but does not persist the resolved token into service environment metadata.
- If token auth requires a token and the configured token SecretRef is unresolved, install fails closed instead of persisting fallback plaintext.
- For password auth on `gateway run`, prefer `OPENCLAW_GATEWAY_PASSWORD`, `--password-file`, or a SecretRef-backed `gateway.auth.password` over inline `--password`.

View File

@@ -214,6 +214,10 @@ stale. The prompt also notes the public docs mirror, community Discord, and Claw
([https://clawhub.ai](https://clawhub.ai)) for skills discovery. It tells the model to
consult docs first for OpenClaw behavior, commands, configuration, or architecture, and to
run `openclaw status` itself when possible (asking the user only when it lacks access).
For configuration specifically, it points agents to the `gateway` tool action
`config.schema.lookup` for exact field-level docs and constraints, then to
`docs/gateway/configuration.md` and `docs/gateway/configuration-reference.md`
for broader guidance.
## Related

View File

@@ -16,6 +16,11 @@ Code truth:
- `config.schema.lookup` returns one path-scoped schema node for drill-down tooling
- `pnpm config:docs:check` / `pnpm config:docs:gen` validate the config-doc baseline hash against the current schema surface
Agent lookup path: use the `gateway` tool action `config.schema.lookup` for
exact field-level docs and constraints before edits. Use
[Configuration](/gateway/configuration) for task-oriented guidance and this page
for the broader field map, defaults, and links to subsystem references.
Dedicated deep references:
- [Memory configuration reference](/reference/memory-config) for `agents.defaults.memorySearch.*`, `memory.qmd.*`, `memory.citations`, and dreaming config under `plugins.entries.memory-core.config.dreaming`

View File

@@ -21,6 +21,11 @@ If the file is missing, OpenClaw uses safe defaults. Common reasons to add a con
See the [full reference](/gateway/configuration-reference) for every available field.
Agents and automation should use `config.schema.lookup` for exact field-level
docs before editing config. Use this page for task-oriented guidance and
[Configuration reference](/gateway/configuration-reference) for the broader
field map and defaults.
<Tip>
**New to configuration?** Start with `openclaw onboard` for interactive setup, or check out the [Configuration Examples](/gateway/configuration-examples) guide for complete copy-paste configs.
</Tip>
@@ -575,6 +580,11 @@ For tooling that writes config over the gateway API, prefer this flow:
- `config.apply` only when you intend to replace the entire config
- `update.run` for explicit self-update plus restart
Agents should treat `config.schema.lookup` as the first stop for exact
field-level docs and constraints. Use [Configuration reference](/gateway/configuration-reference)
when they need the broader config map, defaults, or links to dedicated
subsystem references.
<Note>
Control-plane writes (`config.apply`, `config.patch`, `update.run`) are
rate-limited to 3 requests per 60 seconds per `deviceId+clientIp`. Restart

View File

@@ -251,6 +251,8 @@ openclaw gateway restart
openclaw gateway stop
```
Use `openclaw gateway restart` for restarts. Do not chain `openclaw gateway stop` and `openclaw gateway start`; on macOS, `gateway stop` intentionally disables the LaunchAgent before stopping it.
LaunchAgent labels are `ai.openclaw.gateway` (default) or `ai.openclaw.<profile>` (named profile). `openclaw doctor` audits and repairs service config drift.
</Tab>

View File

@@ -95,6 +95,8 @@ active runtime model label from the latest transcript usage entry.
For partial changes, prefer `config.schema.lookup` then `config.patch`. Use
`config.apply` only when you intentionally replace the entire config.
For broader config docs, read [Configuration](/gateway/configuration) and
[Configuration reference](/gateway/configuration-reference).
The tool also refuses to change `tools.exec.ask` or `tools.exec.security`;
legacy `tools.bash.*` aliases normalize to the same protected exec paths.

View File

@@ -25,6 +25,29 @@ function requireGatewayTool(agentSessionKey?: string) {
});
}
function collectActionValues(schema: unknown, values: Set<string>): void {
if (!schema || typeof schema !== "object") {
return;
}
const record = schema as Record<string, unknown>;
if (typeof record.const === "string") {
values.add(record.const);
}
if (Array.isArray(record.enum)) {
for (const value of record.enum) {
if (typeof value === "string") {
values.add(value);
}
}
}
if (Array.isArray(record.anyOf)) {
for (const variant of record.anyOf) {
collectActionValues(variant, values);
}
}
}
function expectConfigMutationCall(params: {
callGatewayTool: {
mock: {
@@ -96,6 +119,19 @@ describe("gateway tool", () => {
expect(tool.ownerOnly).toBe(true);
});
it("exposes restart and config actions in the gateway tool schema", async () => {
const tool = requireGatewayTool();
const parameters = tool.parameters as {
properties?: Record<string, unknown>;
};
const values = new Set<string>();
collectActionValues(parameters.properties?.action, values);
expect([...values]).toEqual(
expect.arrayContaining(["restart", "config.get", "config.patch", "config.apply"]),
);
});
it("schedules SIGUSR1 restart", async () => {
const kill = vi.spyOn(process, "kill").mockImplementation(() => true);
const restartSignalKillCalls = () =>

View File

@@ -86,6 +86,25 @@ function expectNoSubagentControlTools(tools: ReturnType<typeof createOpenClawCod
describe("createOpenClawCodingTools", () => {
const testConfig: OpenClawConfig = {};
it("exposes gateway config and restart actions to owner sessions", () => {
const tools = createOpenClawCodingTools({ config: testConfig, senderIsOwner: true });
const gateway = tools.find((tool) => tool.name === "gateway");
expect(gateway).toBeDefined();
const parameters = gateway?.parameters as {
properties?: Record<string, unknown>;
};
const action = parameters.properties?.action as
| { const?: unknown; enum?: unknown[] }
| undefined;
const values = new Set<string>();
collectActionValues(action, values);
expect([...values]).toEqual(
expect.arrayContaining(["restart", "config.get", "config.patch", "config.apply"]),
);
});
it("preserves action enums in normalized schemas", () => {
const defaultTools = createOpenClawCodingTools({ config: testConfig, senderIsOwner: true });
const toolNames = ["canvas", "nodes", "cron", "gateway", "message"];

View File

@@ -226,10 +226,27 @@ describe("buildAgentSystemPrompt", () => {
});
expect(prompt).toContain("## OpenClaw CLI Quick Reference");
expect(prompt).toContain("use the first-class `gateway` tool");
expect(prompt).toContain(
"Only use CLI service lifecycle commands when the user explicitly asks",
);
expect(prompt).toContain("openclaw gateway restart");
expect(prompt).toContain("Do not chain `openclaw gateway stop`");
expect(prompt).toContain("Do not invent commands");
});
it("points agents to config field docs and broader configuration docs", () => {
const prompt = buildAgentSystemPrompt({
workspaceDir: "/tmp/openclaw",
docsPath: "/tmp/openclaw/docs",
});
expect(prompt).toContain("For config field docs");
expect(prompt).toContain("`gateway` tool action `config.schema.lookup`");
expect(prompt).toContain("docs/gateway/configuration.md");
expect(prompt).toContain("docs/gateway/configuration-reference.md");
});
it("guides runtime completion events without exposing internal metadata", () => {
const prompt = buildAgentSystemPrompt({
workspaceDir: "/tmp/openclaw",
@@ -559,6 +576,7 @@ describe("buildAgentSystemPrompt", () => {
expect(prompt).toContain("config.schema.lookup");
expect(prompt).toContain("config.apply");
expect(prompt).toContain("config.patch");
expect(prompt).toContain("Config writes hot-reload when possible");
expect(prompt).toContain("update.run");
expect(prompt).not.toContain("Use config.schema to");
expect(prompt).not.toContain("config.schema, config.apply");

View File

@@ -422,6 +422,7 @@ function buildDocsSection(params: {
docsPath
? "For OpenClaw behavior, commands, config, or architecture: consult local docs first."
: "For OpenClaw behavior, commands, config, or architecture: consult the docs mirror first.",
"For config field docs, prefer the `gateway` tool action `config.schema.lookup`; for broader config guidance, read `docs/gateway/configuration.md` and `docs/gateway/configuration-reference.md`.",
sourcePath
? "If docs are incomplete or stale, inspect the local OpenClaw source code before answering."
: "If docs are incomplete or stale, review the OpenClaw source on GitHub before answering.",
@@ -784,11 +785,15 @@ export function buildAgentSystemPrompt(params: {
...safetySection,
"## OpenClaw CLI Quick Reference",
"OpenClaw is controlled via subcommands. Do not invent commands.",
"To manage the Gateway daemon service (start/stop/restart):",
"For config changes, use the first-class `gateway` tool (`config.schema.lookup`, `config.get`, `config.patch`, `config.apply`) instead of editing config through exec; the gateway tool hot-reloads config when possible and uses a safe restart only when required.",
"Use the `gateway` tool action `restart` for Gateway restarts. Only use CLI service lifecycle commands when the user explicitly asks for them.",
"Gateway service lifecycle quick reference:",
"- openclaw gateway status",
"- openclaw gateway restart",
"Operator-only, explicit user request:",
"- openclaw gateway start",
"- openclaw gateway stop",
"- openclaw gateway restart",
"Do not chain `openclaw gateway stop` and `openclaw gateway start` as a restart substitute.",
"If unsure, ask the user to run `openclaw help` (or `openclaw gateway --help`) and paste the output.",
"",
...skillsSection,
@@ -800,7 +805,7 @@ export function buildAgentSystemPrompt(params: {
"Get Updates (self-update) is ONLY allowed when the user explicitly asks for it.",
"Do not run config.apply or update.run unless the user explicitly requests an update or config change; if it's not explicit, ask first.",
"Use config.schema.lookup with a specific dot path to inspect only the relevant config subtree before making config changes or answering config-field questions; avoid guessing field names/types.",
"Actions: config.schema.lookup, config.get, config.apply (validate + write full config, then restart), config.patch (partial update, merges with existing), update.run (update deps or git, then restart).",
"Actions: config.schema.lookup, config.get, config.patch (partial update, merges with existing), config.apply (validate + write full config), update.run (update deps or git, then restart). Config writes hot-reload when possible and use a safe restart only when required.",
"After restart, OpenClaw pings the last active session automatically.",
].join("\n")
: "",

View File

@@ -22,7 +22,14 @@ const coreTools = [
stubActionTool("nodes", ["list", "invoke"]),
stubActionTool("cron", ["schedule", "cancel"]),
stubActionTool("message", ["send", "reply"]),
stubActionTool("gateway", ["status"]),
stubActionTool("gateway", [
"restart",
"config.get",
"config.schema.lookup",
"config.apply",
"config.patch",
"update.run",
]),
stubActionTool("agents_list", ["list", "show"]),
stubActionTool("sessions_list", ["list", "show"]),
stubActionTool("sessions_history", ["read", "tail"]),