Reload daemon configuration without restarting (#3365)

* feat(daemon): reload configuration without restarting

Keep config validation, reloadability classification, override handling, and atomic runtime application inside the daemon so local and remote clients share one authoritative workflow.

* fix(daemon): preserve reload resource ownership

Keep every materialized provider client under daemon-lifetime ownership and classify speech overrides from resolved providers. Restore the complete WebSocket config-store test contract exposed by CI.
This commit is contained in:
Mohamed Boudra
2026-08-14 22:46:32 +02:00
committed by GitHub
parent 75bbca4a1b
commit 4748aad103
36 changed files with 2590 additions and 246 deletions
+39
View File
@@ -23,6 +23,7 @@ import { createRequire } from "node:module";
import { mkdtemp, readFile, rm, writeFile } from "fs/promises";
import { tmpdir } from "os";
import { dirname, join } from "path";
import YAML from "yaml";
import { runLocalPaseo } from "./helpers/local-cli.ts";
console.log("=== Daemon Commands ===\n");
@@ -276,6 +277,44 @@ try {
assert.strictEqual(pairingPayload.relayEnabled, true, "pairing should use live relay state");
assert.match(pairingPayload.url, /#offer=/, "pairing should use the live daemon offer");
const reloadConfig = JSON.parse(await readFile(configPath, "utf-8"));
reloadConfig.daemon = {
...reloadConfig.daemon,
listen: process.platform === "win32" ? `${listen}-changed` : `${listen}.changed`,
browserTools: { enabled: true },
};
await writeFile(configPath, `${JSON.stringify(reloadConfig, null, 2)}\n`, "utf-8");
const nestedReload = await daemonCommand(["reload", "--host", listen, "--json"]);
assert.strictEqual(nestedReload.exitCode, 0, nestedReload.stderr);
assert.deepStrictEqual(JSON.parse(nestedReload.stdout), {
appliedPaths: ["daemon.browserTools.enabled"],
restartRequiredPaths: [],
overrideControlledPaths: ["daemon.listen"],
});
reloadConfig.daemon.browserTools.enabled = false;
await writeFile(configPath, `${JSON.stringify(reloadConfig, null, 2)}\n`, "utf-8");
const aliasReload = await runLocalPaseo(["reload", "--host", listen, "--json"], {
PASEO_HOME: paseoHome,
});
assert.strictEqual(aliasReload.exitCode, 0, aliasReload.stderr);
assert.deepStrictEqual(JSON.parse(aliasReload.stdout), {
appliedPaths: ["daemon.browserTools.enabled"],
restartRequiredPaths: [],
overrideControlledPaths: [],
});
const yamlReload = await daemonCommand(["reload", "--host", listen, "--format", "yaml"]);
assert.strictEqual(yamlReload.exitCode, 0, yamlReload.stderr);
assert.deepStrictEqual(YAML.parse(yamlReload.stdout), {
appliedPaths: [],
restartRequiredPaths: [],
overrideControlledPaths: [],
});
const humanReload = await daemonCommand(["reload", "--host", listen]);
assert.match(humanReload.stdout, /Configuration reloaded\./);
const foreignHome = await mkdtemp(join(tmpdir(), "paseo-test-foreign-home-"));
try {
await writeFile(