Configure agent thinking from the CLI (#2533)
* feat(cli): update agent thinking from the CLI * fix(cli): harden agent thinking updates * feat(cli): configure thinking for schedules * fix(cli): report applied thinking updates * fix(cli): report current agent thinking state
This commit is contained in:
@@ -35,6 +35,19 @@ describe("canonical CLI surface", () => {
|
||||
expect(run?.helpInformation()).not.toContain("--detach");
|
||||
});
|
||||
|
||||
it("offers thinking configuration when running, updating, and scheduling agents", () => {
|
||||
const cli = createCli();
|
||||
const run = cli.commands.find((command) => command.name() === "run");
|
||||
const agent = cli.commands.find((command) => command.name() === "agent");
|
||||
const update = agent?.commands.find((command) => command.name() === "update");
|
||||
const schedule = cli.commands.find((command) => command.name() === "schedule");
|
||||
const scheduleCreate = schedule?.commands.find((command) => command.name() === "create");
|
||||
|
||||
expect(run?.helpInformation()).toContain("--thinking <id>");
|
||||
expect(update?.helpInformation()).toContain("--thinking <id>");
|
||||
expect(scheduleCreate?.helpInformation()).toContain("--thinking <id>");
|
||||
});
|
||||
|
||||
it("offers opening an existing agent in the desktop app", () => {
|
||||
const agent = createCli().commands.find((command) => command.name() === "agent");
|
||||
const open = agent?.commands.find((command) => command.name() === "open");
|
||||
|
||||
@@ -92,9 +92,10 @@ export function createAgentCommand(): Command {
|
||||
addJsonAndDaemonHostOptions(
|
||||
agent
|
||||
.command("update")
|
||||
.description("Update an agent's metadata")
|
||||
.description("Update an agent's settings or metadata")
|
||||
.argument("<id>", "Agent ID (or prefix)")
|
||||
.option("--name <name>", "Update the agent's display name")
|
||||
.option("--thinking <id>", "Update the agent's thinking option ID")
|
||||
.option(
|
||||
"--label <label>",
|
||||
"Add/set label(s) on the agent (can be used multiple times or comma-separated)",
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { AgentProviderNotice } from "@getpaseo/protocol/agent-types";
|
||||
import {
|
||||
applyAgentChanges,
|
||||
toAgentUpdateResult,
|
||||
type AgentMetadataChanges,
|
||||
type AgentUpdateClient,
|
||||
} from "./update.js";
|
||||
|
||||
class RecordingAgentUpdateClient implements AgentUpdateClient {
|
||||
readonly metadataUpdates: Array<{
|
||||
agentId: string;
|
||||
updates: AgentMetadataChanges;
|
||||
}> = [];
|
||||
readonly thinkingUpdates: Array<{ agentId: string; thinkingOptionId: string }> = [];
|
||||
thinkingNotice: AgentProviderNotice | null = null;
|
||||
|
||||
constructor(private readonly supportsThinkingUpdate = true) {}
|
||||
|
||||
getLastServerInfoMessage() {
|
||||
return { features: { agentThinkingUpdate: this.supportsThinkingUpdate } };
|
||||
}
|
||||
|
||||
async updateAgent(agentId: string, updates: AgentMetadataChanges): Promise<void> {
|
||||
this.metadataUpdates.push({ agentId, updates });
|
||||
}
|
||||
|
||||
async setAgentThinkingOption(
|
||||
agentId: string,
|
||||
thinkingOptionId: string,
|
||||
): Promise<AgentProviderNotice | null> {
|
||||
this.thinkingUpdates.push({ agentId, thinkingOptionId });
|
||||
return this.thinkingNotice;
|
||||
}
|
||||
}
|
||||
|
||||
describe("applyAgentChanges", () => {
|
||||
it("updates an agent's thinking without issuing an empty metadata update", async () => {
|
||||
const client = new RecordingAgentUpdateClient();
|
||||
|
||||
const result = await applyAgentChanges(client, "agent-1", {
|
||||
type: "thinking",
|
||||
thinkingOptionId: "high",
|
||||
});
|
||||
|
||||
expect(client.metadataUpdates).toEqual([]);
|
||||
expect(client.thinkingUpdates).toEqual([{ agentId: "agent-1", thinkingOptionId: "high" }]);
|
||||
expect(result).toEqual({
|
||||
notice: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("requires a daemon that advertises thinking updates", async () => {
|
||||
const client = new RecordingAgentUpdateClient(false);
|
||||
|
||||
await expect(
|
||||
applyAgentChanges(client, "agent-1", { type: "thinking", thinkingOptionId: "high" }),
|
||||
).rejects.toMatchObject({
|
||||
code: "DAEMON_UPDATE_REQUIRED",
|
||||
message: "Update the host to use agent thinking updates.",
|
||||
});
|
||||
expect(client.metadataUpdates).toEqual([]);
|
||||
expect(client.thinkingUpdates).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns the provider notice from a thinking update", async () => {
|
||||
const client = new RecordingAgentUpdateClient();
|
||||
client.thinkingNotice = {
|
||||
type: "warning",
|
||||
message: "Thinking changes apply to the next turn.",
|
||||
};
|
||||
|
||||
const notice = await applyAgentChanges(client, "agent-1", {
|
||||
type: "thinking",
|
||||
thinkingOptionId: "high",
|
||||
});
|
||||
|
||||
expect(notice).toEqual({
|
||||
notice: {
|
||||
type: "warning",
|
||||
message: "Thinking changes apply to the next turn.",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("toAgentUpdateResult", () => {
|
||||
it("reports the current thinking option after a metadata update", () => {
|
||||
const result = toAgentUpdateResult(
|
||||
{
|
||||
id: "agent-1",
|
||||
title: "Renamed agent",
|
||||
labels: { team: "platform" },
|
||||
effectiveThinkingOptionId: "high",
|
||||
},
|
||||
{ notice: null },
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
agentId: "agent-1",
|
||||
name: "Renamed agent",
|
||||
labels: "team=platform",
|
||||
thinkingOptionId: "high",
|
||||
noticeType: null,
|
||||
notice: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { Command } from "commander";
|
||||
import type { AgentProviderNotice } from "@getpaseo/protocol/agent-types";
|
||||
import type { AgentSnapshotPayload } from "@getpaseo/protocol/messages";
|
||||
import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
||||
import type {
|
||||
CommandOptions,
|
||||
@@ -12,6 +14,9 @@ export interface AgentUpdateResult {
|
||||
agentId: string;
|
||||
name: string | null;
|
||||
labels: string;
|
||||
thinkingOptionId: string | null;
|
||||
noticeType: AgentProviderNotice["type"] | null;
|
||||
notice: string | null;
|
||||
}
|
||||
|
||||
/** Schema for update command output */
|
||||
@@ -21,17 +26,80 @@ export const updateSchema: OutputSchema<AgentUpdateResult> = {
|
||||
{ header: "AGENT ID", field: "agentId" },
|
||||
{ header: "NAME", field: "name" },
|
||||
{ header: "LABELS", field: "labels" },
|
||||
{ header: "THINKING", field: "thinkingOptionId" },
|
||||
{ header: "NOTICE", field: "notice" },
|
||||
],
|
||||
};
|
||||
|
||||
export interface AgentUpdateOptions extends CommandOptions {
|
||||
name?: string;
|
||||
label?: string[];
|
||||
thinking?: string;
|
||||
host?: string;
|
||||
}
|
||||
|
||||
export type AgentUpdateCommandResult = SingleResult<AgentUpdateResult>;
|
||||
|
||||
export interface AgentMetadataChanges {
|
||||
name?: string;
|
||||
labels?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface AgentUpdateServerInfo {
|
||||
features?: { agentThinkingUpdate?: boolean };
|
||||
}
|
||||
|
||||
export interface AgentUpdateClient {
|
||||
getLastServerInfoMessage(): AgentUpdateServerInfo | null;
|
||||
updateAgent(agentId: string, updates: AgentMetadataChanges): Promise<void>;
|
||||
setAgentThinkingOption(
|
||||
agentId: string,
|
||||
thinkingOptionId: string,
|
||||
): Promise<AgentProviderNotice | null>;
|
||||
}
|
||||
|
||||
export type AgentChanges =
|
||||
| { type: "metadata"; updates: AgentMetadataChanges }
|
||||
| { type: "thinking"; thinkingOptionId: string };
|
||||
|
||||
export interface AppliedAgentChanges {
|
||||
notice: AgentProviderNotice | null;
|
||||
}
|
||||
|
||||
export function toAgentUpdateResult(
|
||||
agent: Pick<AgentSnapshotPayload, "id" | "title" | "labels" | "effectiveThinkingOptionId">,
|
||||
appliedChanges: AppliedAgentChanges,
|
||||
): AgentUpdateResult {
|
||||
return {
|
||||
agentId: agent.id,
|
||||
name: agent.title,
|
||||
labels: formatLabels(agent.labels),
|
||||
thinkingOptionId: agent.effectiveThinkingOptionId ?? null,
|
||||
noticeType: appliedChanges.notice?.type ?? null,
|
||||
notice: appliedChanges.notice?.message ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function applyAgentChanges(
|
||||
client: AgentUpdateClient,
|
||||
agentId: string,
|
||||
changes: AgentChanges,
|
||||
): Promise<AppliedAgentChanges> {
|
||||
if (changes.type === "thinking") {
|
||||
// COMPAT(agentThinkingUpdate): added in v0.2.4, remove gate after 2027-01-28.
|
||||
if (client.getLastServerInfoMessage()?.features?.agentThinkingUpdate !== true) {
|
||||
throw {
|
||||
code: "DAEMON_UPDATE_REQUIRED",
|
||||
message: "Update the host to use agent thinking updates.",
|
||||
} satisfies CommandError;
|
||||
}
|
||||
const notice = await client.setAgentThinkingOption(agentId, changes.thinkingOptionId);
|
||||
return { notice };
|
||||
}
|
||||
await client.updateAgent(agentId, changes.updates);
|
||||
return { notice: null };
|
||||
}
|
||||
|
||||
function parseLabelOptions(labels: string[] | undefined): Record<string, string> {
|
||||
const parsed: Record<string, string> = {};
|
||||
if (!labels) {
|
||||
@@ -81,6 +149,55 @@ function formatLabels(labels: Record<string, string>): string {
|
||||
return entries.map(([key, value]) => `${key}=${value}`).join(",");
|
||||
}
|
||||
|
||||
function parseAgentChanges(options: AgentUpdateOptions): AgentChanges {
|
||||
const name = options.name?.trim();
|
||||
if (options.name !== undefined && !name) {
|
||||
throw {
|
||||
code: "INVALID_NAME",
|
||||
message: "Name cannot be empty",
|
||||
details: "Use --name <name> with a non-empty value",
|
||||
} satisfies CommandError;
|
||||
}
|
||||
|
||||
const labels = parseLabelOptions(options.label);
|
||||
const thinkingOptionId = options.thinking?.trim();
|
||||
if (options.thinking !== undefined && !thinkingOptionId) {
|
||||
throw {
|
||||
code: "INVALID_THINKING_OPTION",
|
||||
message: "--thinking cannot be empty",
|
||||
details:
|
||||
'Provide a thinking option ID. Use "paseo provider models <provider> --thinking" to list valid IDs.',
|
||||
} satisfies CommandError;
|
||||
}
|
||||
|
||||
const hasMetadataUpdates = Boolean(name) || Object.keys(labels).length > 0;
|
||||
if (hasMetadataUpdates && thinkingOptionId) {
|
||||
throw {
|
||||
code: "INVALID_OPTIONS",
|
||||
message: "--thinking cannot be combined with --name or --label",
|
||||
details: "Run separate agent update commands for runtime settings and metadata.",
|
||||
} satisfies CommandError;
|
||||
}
|
||||
if (!hasMetadataUpdates && !thinkingOptionId) {
|
||||
throw {
|
||||
code: "NO_CHANGES_PROVIDED",
|
||||
message: "Nothing to update",
|
||||
details: "Provide at least one of: --name <name>, --label <key=value>, --thinking <id>",
|
||||
} satisfies CommandError;
|
||||
}
|
||||
|
||||
if (thinkingOptionId) {
|
||||
return { type: "thinking", thinkingOptionId };
|
||||
}
|
||||
return {
|
||||
type: "metadata",
|
||||
updates: {
|
||||
...(name ? { name } : {}),
|
||||
...(Object.keys(labels).length > 0 ? { labels } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function runUpdateCommand(
|
||||
agentIdArg: string,
|
||||
options: AgentUpdateOptions,
|
||||
@@ -98,25 +215,7 @@ export async function runUpdateCommand(
|
||||
throw error;
|
||||
}
|
||||
|
||||
const name = options.name?.trim();
|
||||
if (options.name !== undefined && !name) {
|
||||
const error: CommandError = {
|
||||
code: "INVALID_NAME",
|
||||
message: "Name cannot be empty",
|
||||
details: "Use --name <name> with a non-empty value",
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
|
||||
const labels = parseLabelOptions(options.label);
|
||||
if (!name && Object.keys(labels).length === 0) {
|
||||
const error: CommandError = {
|
||||
code: "NO_CHANGES_PROVIDED",
|
||||
message: "Nothing to update",
|
||||
details: "Provide at least one of: --name <name>, --label <key=value>",
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
const changes = parseAgentChanges(options);
|
||||
|
||||
let client;
|
||||
try {
|
||||
@@ -143,10 +242,7 @@ export async function runUpdateCommand(
|
||||
}
|
||||
const agentId = fetchResult.agent.id;
|
||||
|
||||
await client.updateAgent(agentId, {
|
||||
...(name ? { name } : {}),
|
||||
...(Object.keys(labels).length > 0 ? { labels } : {}),
|
||||
});
|
||||
const appliedChanges = await applyAgentChanges(client, agentId, changes);
|
||||
|
||||
const updatedResult = await client.fetchAgent({ agentId });
|
||||
if (!updatedResult) {
|
||||
@@ -157,11 +253,7 @@ export async function runUpdateCommand(
|
||||
|
||||
return {
|
||||
type: "single",
|
||||
data: {
|
||||
agentId,
|
||||
name: updatedResult.agent.title,
|
||||
labels: formatLabels(updatedResult.agent.labels),
|
||||
},
|
||||
data: toAgentUpdateResult(updatedResult.agent, appliedChanges),
|
||||
schema: updateSchema,
|
||||
};
|
||||
} catch (err) {
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface ScheduleCreateOptions extends ScheduleCommandOptions {
|
||||
target?: string;
|
||||
provider?: string;
|
||||
mode?: string;
|
||||
thinking?: string;
|
||||
cwd?: string;
|
||||
maxRuns?: string;
|
||||
expiresIn?: string;
|
||||
@@ -40,6 +41,7 @@ export async function runCreateCommand(
|
||||
target: options.target,
|
||||
provider: options.provider,
|
||||
mode: options.mode,
|
||||
thinking: options.thinking,
|
||||
cwd: options.cwd,
|
||||
host: options.host,
|
||||
maxRuns: options.maxRuns,
|
||||
|
||||
@@ -32,6 +32,7 @@ export function createScheduleCommand(): Command {
|
||||
"--mode <mode>",
|
||||
"Provider-specific mode (e.g. claude bypassPermissions, opencode build)",
|
||||
)
|
||||
.option("--thinking <id>", "Thinking option ID for new-agent runs")
|
||||
.option("--cwd <path>", "Working directory (default: current; required with --host)")
|
||||
.option("--run-now", "Fire one immediate run on creation")
|
||||
.option("--max-runs <n>", "Maximum number of runs")
|
||||
|
||||
@@ -116,6 +116,25 @@ describe("parseScheduleCreateInput first-run timing", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseScheduleCreateInput thinking", () => {
|
||||
test("sets the thinking option for each scheduled new-agent run", () => {
|
||||
const input = parseScheduleCreateInput({
|
||||
...baseOptions,
|
||||
cwd: "/project",
|
||||
thinking: " high ",
|
||||
});
|
||||
|
||||
expect(input.target).toEqual({
|
||||
type: "new-agent",
|
||||
config: {
|
||||
provider: "claude",
|
||||
cwd: "/project",
|
||||
thinkingOptionId: "high",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseScheduleUpdateInput", () => {
|
||||
test("rejects calls with no fields to update", () => {
|
||||
expect(() => parseScheduleUpdateInput({ id: "abc" })).toThrow(
|
||||
|
||||
@@ -114,7 +114,7 @@ function resolveScheduleTarget(args: {
|
||||
if (hasExplicitNewAgentOption) {
|
||||
throw {
|
||||
code: "INVALID_TARGET",
|
||||
message: "--provider/--mode can only be used with a new-agent target",
|
||||
message: "--provider/--mode/--thinking can only be used with a new-agent target",
|
||||
details: "Use --target new-agent or omit --target to create a new agent schedule",
|
||||
} satisfies CommandError;
|
||||
}
|
||||
@@ -144,6 +144,7 @@ export function parseScheduleCreateInput(options: {
|
||||
target?: string;
|
||||
provider?: string;
|
||||
mode?: string;
|
||||
thinking?: string;
|
||||
cwd?: string;
|
||||
host?: string;
|
||||
maxRuns?: string;
|
||||
@@ -179,7 +180,15 @@ export function parseScheduleCreateInput(options: {
|
||||
|
||||
const targetValue = options.target?.trim();
|
||||
const modeId = options.mode?.trim();
|
||||
const hasExplicitNewAgentOption = options.provider !== undefined || options.mode !== undefined;
|
||||
const thinkingOptionId = options.thinking?.trim();
|
||||
if (options.thinking !== undefined && !thinkingOptionId) {
|
||||
throw {
|
||||
code: "INVALID_THINKING_OPTION",
|
||||
message: "--thinking cannot be empty",
|
||||
} satisfies CommandError;
|
||||
}
|
||||
const hasExplicitNewAgentOption =
|
||||
options.provider !== undefined || options.mode !== undefined || options.thinking !== undefined;
|
||||
const createNewAgentTarget = (): ScheduleTarget => {
|
||||
const resolvedProviderModel = resolveProviderAndModel({
|
||||
provider: options.provider,
|
||||
@@ -191,6 +200,7 @@ export function parseScheduleCreateInput(options: {
|
||||
cwd: cwdInput ?? process.cwd(),
|
||||
...(resolvedProviderModel.model ? { model: resolvedProviderModel.model } : {}),
|
||||
...(modeId ? { modeId } : {}),
|
||||
...(thinkingOptionId ? { thinkingOptionId } : {}),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@ try {
|
||||
assert.strictEqual(result.exitCode, 0, "agent update --help should exit 0");
|
||||
assert(result.stdout.includes("--name"), "help should mention --name flag");
|
||||
assert(result.stdout.includes("--label"), "help should mention --label flag");
|
||||
assert(result.stdout.includes("--thinking"), "help should mention --thinking flag");
|
||||
assert(result.stdout.includes("--host"), "help should mention --host option");
|
||||
assert(result.stdout.includes("<id>"), "help should mention required id argument");
|
||||
console.log("✓ agent update --help shows options\n");
|
||||
@@ -103,6 +104,30 @@ try {
|
||||
assert(result.stdout.includes("update"), "help should mention update subcommand");
|
||||
console.log("✓ agent --help shows update subcommand\n");
|
||||
}
|
||||
|
||||
// Test 7: agent update accepts thinking as an update field
|
||||
{
|
||||
console.log("Test 7: agent update accepts thinking as an update field");
|
||||
const result =
|
||||
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent update abc123 --thinking high`.nothrow();
|
||||
const output = result.stdout + result.stderr;
|
||||
assert(!output.includes("Nothing to update"), "should treat --thinking as an update field");
|
||||
console.log("✓ agent update accepts thinking as an update field\n");
|
||||
}
|
||||
|
||||
// Test 8: thinking cannot be combined with metadata updates
|
||||
{
|
||||
console.log("Test 8: thinking cannot be combined with metadata updates");
|
||||
const result =
|
||||
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent update abc123 --name renamed --thinking high`.nothrow();
|
||||
assert.notStrictEqual(result.exitCode, 0, "should reject a combined update");
|
||||
const output = result.stdout + result.stderr;
|
||||
assert(
|
||||
output.includes("--thinking cannot be combined with --name or --label"),
|
||||
"should explain that thinking and metadata updates are separate operations",
|
||||
);
|
||||
console.log("✓ thinking cannot be combined with metadata updates\n");
|
||||
}
|
||||
} finally {
|
||||
// Clean up temp directory
|
||||
await rm(paseoHome, { recursive: true, force: true });
|
||||
|
||||
@@ -100,6 +100,8 @@ try {
|
||||
"10m",
|
||||
"--provider",
|
||||
"codex/gpt-5.4",
|
||||
"--thinking",
|
||||
"high",
|
||||
"--json",
|
||||
],
|
||||
{ timeout: 30000 },
|
||||
@@ -113,6 +115,7 @@ try {
|
||||
const inspectedJson = JSON.parse(inspected.stdout);
|
||||
assert.strictEqual(inspectedJson.target.config.provider, "codex");
|
||||
assert.strictEqual(inspectedJson.target.config.model, "gpt-5.4");
|
||||
assert.strictEqual(inspectedJson.target.config.thinkingOptionId, "high");
|
||||
|
||||
const deleted = await ctx.paseo(["schedule", "delete", createdJson.id, "--json"]);
|
||||
assert.strictEqual(deleted.exitCode, 0, deleted.stderr);
|
||||
|
||||
@@ -168,4 +168,14 @@ describe("project command-center protocol", () => {
|
||||
expect(parsed.features?.projectGithubClone).toBeUndefined();
|
||||
expect(parsed.features?.projectCreateDirectory).toBeUndefined();
|
||||
});
|
||||
|
||||
it("parses the agent thinking update capability", () => {
|
||||
const parsed = parseServerInfoStatusPayload({
|
||||
status: "server_info",
|
||||
serverId: "server-new",
|
||||
features: { agentThinkingUpdate: true },
|
||||
});
|
||||
|
||||
expect(parsed.features?.agentThinkingUpdate).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2792,6 +2792,8 @@ export const ServerInfoStatusPayloadSchema = z
|
||||
providerUsageList: z.boolean().optional(),
|
||||
// COMPAT(agentDetach): added in v0.1.98, remove gate after 2026-12-19 once daemon floor >= v0.1.98.
|
||||
agentDetach: z.boolean().optional(),
|
||||
// COMPAT(agentThinkingUpdate): added in v0.2.4, remove gate after 2027-01-28.
|
||||
agentThinkingUpdate: z.boolean().optional(),
|
||||
// COMPAT(daemonDiagnostics): added in v0.1.100, remove gate after 2026-12-25 once daemon floor >= v0.1.100.
|
||||
daemonDiagnostics: z.boolean().optional(),
|
||||
// COMPAT(daemonSelfUpdate): added in v0.1.93, remove gate after 2026-12-13.
|
||||
|
||||
@@ -1540,6 +1540,8 @@ export class VoiceAssistantWebSocketServer {
|
||||
providerUsageList: true,
|
||||
// COMPAT(agentDetach): added in v0.1.98, remove gate after 2026-12-19 once daemon floor >= v0.1.98.
|
||||
agentDetach: true,
|
||||
// COMPAT(agentThinkingUpdate): added in v0.2.4, remove gate after 2027-01-28.
|
||||
agentThinkingUpdate: true,
|
||||
// COMPAT(daemonDiagnostics): added in v0.1.100, remove gate after 2026-12-25 once daemon floor >= v0.1.100.
|
||||
daemonDiagnostics: true,
|
||||
// COMPAT(daemonSelfUpdate): added in v0.1.93, remove gate after 2026-12-13.
|
||||
|
||||
Reference in New Issue
Block a user