mirror of
https://github.com/Routstr/routstrd.git
synced 2026-09-14 02:55:07 +00:00
`routstrd clients` only exposed list/delete/add, so operators had no way to refresh client integrations on demand or to stop the daemon from rewriting their client configs every 21 minutes. - clients --manual-refresh: refresh routstr21 models from Nostr and re-run every registered client integration now. The old `routstrd refresh` body moves into a shared refreshModelsAndClientsAction() so both commands stay in sync. - clients --disable-automatic-refresh / --enable-automatic-refresh: toggle the daemon's scheduled refresh job via a new POST /settings/auto-refresh endpoint, so the toggle also works against a remote daemon where the config lives on the host. Persisted as autoRefresh.enabled in config.json. - The daemon refresh job now re-reads autoRefresh on every tick (like the NWC auto-refill getter), so toggling takes effect without a restart. While disabled it polls once a minute so re-enabling applies promptly. - Startup still fetches models (the proxy needs them) but skips the client integration pass when the job is disabled, so a restart cannot overwrite hand-edited client configs. Adds tests/daemon/auto-refresh.* covering the endpoint contract and the persisted flag. Co-authored-by: redshift <213178690+1ftredsh@users.noreply.github.com>
103 lines
3.0 KiB
TypeScript
103 lines
3.0 KiB
TypeScript
// Scenario runner for auto-refresh.test.ts — executed as a standalone bun
|
|
// process with ROUTSTRD_DIR set before module evaluation, because CONFIG_FILE
|
|
// is resolved at import time.
|
|
import { createServer } from "http";
|
|
import type { AddressInfo } from "net";
|
|
|
|
const { createDaemonRequestHandler } = await import("../../src/daemon/http/index");
|
|
const { loadDaemonConfigSync } = await import("../../src/daemon/config-store");
|
|
|
|
function assert(cond: unknown, msg: string): void {
|
|
if (!cond) {
|
|
console.error(`ASSERT-FAIL: ${msg}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Only the settings endpoints are exercised, so every daemon dependency is a
|
|
* stub that would throw loudly if the request reached the proxy path.
|
|
*/
|
|
const stubDeps = {
|
|
provider: null,
|
|
server: { close() {} },
|
|
store: {},
|
|
walletClient: {},
|
|
walletAdapter: {},
|
|
storageAdapter: {},
|
|
discoveryAdapter: {},
|
|
modelManager: {},
|
|
ensureProvidersBootstrapped: async () => {},
|
|
getRoutstr21Models: async () => [],
|
|
getModelProviders: async () => [],
|
|
refreshProvidersAndModels: async () => {},
|
|
mode: "apikeys" as const,
|
|
maxTokens: 64000,
|
|
usageTrackingDriver: {},
|
|
providerManager: {},
|
|
refundClient: {},
|
|
};
|
|
|
|
async function withServer(
|
|
run: (port: number) => Promise<void>,
|
|
): Promise<void> {
|
|
const server = createServer(
|
|
createDaemonRequestHandler(stubDeps as never),
|
|
);
|
|
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
try {
|
|
await run((server.address() as AddressInfo).port);
|
|
} finally {
|
|
server.close();
|
|
}
|
|
}
|
|
|
|
const scenario = process.argv[2];
|
|
|
|
switch (scenario) {
|
|
case "toggle-endpoint": {
|
|
await withServer(async (port) => {
|
|
const post = (body: unknown) =>
|
|
fetch(`http://127.0.0.1:${port}/settings/auto-refresh`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
// Missing config means "enabled" so existing installs are unaffected.
|
|
assert(
|
|
loadDaemonConfigSync().autoRefresh?.enabled === undefined,
|
|
"expected no autoRefresh in a fresh config",
|
|
);
|
|
|
|
const disabled = await post({ enabled: false });
|
|
assert(disabled.status === 200, `disable status ${disabled.status}`);
|
|
const disabledBody = (await disabled.json()) as {
|
|
output?: { autoRefresh?: { enabled?: boolean } };
|
|
};
|
|
assert(
|
|
disabledBody.output?.autoRefresh?.enabled === false,
|
|
"disable response did not report enabled=false",
|
|
);
|
|
assert(
|
|
loadDaemonConfigSync().autoRefresh?.enabled === false,
|
|
"disable was not persisted to config.json",
|
|
);
|
|
|
|
const enabled = await post({ enabled: true });
|
|
assert(enabled.status === 200, `enable status ${enabled.status}`);
|
|
assert(
|
|
loadDaemonConfigSync().autoRefresh?.enabled === true,
|
|
"enable was not persisted to config.json",
|
|
);
|
|
});
|
|
break;
|
|
}
|
|
|
|
default:
|
|
console.error(`unknown scenario: ${scenario}`);
|
|
process.exit(2);
|
|
}
|
|
|
|
console.log("SCENARIO-OK");
|