feat(live-values): --live-values-interval <ms> CLI flag
Replaces the hardcoded 500ms poll with a configurable interval. Clamped to [100, 60000] -- below 100 the read_variable IPC round- trip-per-var dominates and the pump can't keep up; above 60000 a session shorter than the interval would never see any update. ServerConfig.liveValuesIntervalMs?: number plumbs through bin.ts (parseInt + clamp) into the LiveValuesPump constructor and the 'Live-values pump started (<ms>ms)' log line. Default unchanged at 500ms, so existing --live-values invocations behave identically.
This commit is contained in:
parent
b535eb3e3d
commit
8f40b5539a
4 changed files with 65 additions and 5 deletions
16
src/bin.ts
16
src/bin.ts
|
|
@ -19,6 +19,16 @@ try {
|
|||
// ignore
|
||||
}
|
||||
|
||||
const LIVE_VALUES_INTERVAL_MIN_MS = 100;
|
||||
const LIVE_VALUES_INTERVAL_MAX_MS = 60_000;
|
||||
const LIVE_VALUES_INTERVAL_DEFAULT_MS = 500;
|
||||
|
||||
function clampInterval(raw: string | undefined): number {
|
||||
const parsed = parseInt(raw ?? String(LIVE_VALUES_INTERVAL_DEFAULT_MS), 10);
|
||||
if (!Number.isFinite(parsed)) return LIVE_VALUES_INTERVAL_DEFAULT_MS;
|
||||
return Math.max(LIVE_VALUES_INTERVAL_MIN_MS, Math.min(parsed, LIVE_VALUES_INTERVAL_MAX_MS));
|
||||
}
|
||||
|
||||
program
|
||||
.name('codesys-mcp-sp21-plus')
|
||||
.description('MCP server for CODESYS with persistent UI instance')
|
||||
|
|
@ -48,7 +58,8 @@ program
|
|||
.option('--keep-alive', 'Keep CODESYS running after server stops', false)
|
||||
.option('--auto-mirror', 'Re-run mirror_export after every modifying tool so an external editor watching <projectDir>/mcp-mirror/ sees changes live', false)
|
||||
.option('--approve-edits', 'Gate modifying MCP tools behind a phobiCS-tui y/n diff prompt', false)
|
||||
.option('--live-values', 'Pump runtime values for the selected POU into tui-live-values.json so phobiCS-tui can overlay them inline (500ms poll). Requires the runtime to be online; failures are silent.', false)
|
||||
.option('--live-values', 'Pump runtime values for the selected POU into tui-live-values.json so phobiCS-tui can overlay them inline. Requires the runtime to be online; failures are silent.', false)
|
||||
.option('--live-values-interval <ms>', 'Poll interval for --live-values in ms. Default 500. Clamped to [100, 60000].', '500')
|
||||
.option('--timeout <ms>', 'Default command timeout in ms', '60000')
|
||||
.option('--verbose', 'Enable verbose logging')
|
||||
.option('--debug', 'Enable debug logging (more verbose)')
|
||||
|
|
@ -201,6 +212,7 @@ if (opts.sshVersion) {
|
|||
autoMirror: opts.autoMirror || false,
|
||||
approveEdits: opts.approveEdits || false,
|
||||
liveValues: opts.liveValues || false,
|
||||
liveValuesIntervalMs: clampInterval(opts.liveValuesInterval),
|
||||
};
|
||||
|
||||
process.stderr.write(`Starting CODESYS MCP Server v${version}\n`);
|
||||
|
|
@ -215,7 +227,7 @@ if (opts.sshVersion) {
|
|||
process.stderr.write(` Approve edits: ENABLED (modifying tools will prompt via phobiCS-tui)\n`);
|
||||
}
|
||||
if (config.liveValues) {
|
||||
process.stderr.write(` Live values: ENABLED (poll 500ms; writes tui-live-values.json)\n`);
|
||||
process.stderr.write(` Live values: ENABLED (poll ${config.liveValuesIntervalMs ?? 500}ms; writes tui-live-values.json)\n`);
|
||||
}
|
||||
|
||||
startMcpServer(config).catch((err) => {
|
||||
|
|
|
|||
|
|
@ -3200,7 +3200,7 @@ export async function startMcpServer(config: ServerConfig): Promise<void> {
|
|||
{
|
||||
stateFilePath: defaultStateFilePath(),
|
||||
liveValuesFilePath: defaultLiveValuesFilePath(),
|
||||
intervalMs: 500,
|
||||
intervalMs: config.liveValuesIntervalMs ?? 500,
|
||||
},
|
||||
{
|
||||
readSelection,
|
||||
|
|
@ -3225,7 +3225,7 @@ export async function startMcpServer(config: ServerConfig): Promise<void> {
|
|||
}
|
||||
);
|
||||
liveValuesPump.start();
|
||||
serverLog.info('Live-values pump started (500ms)');
|
||||
serverLog.info(`Live-values pump started (${config.liveValuesIntervalMs ?? 500}ms)`);
|
||||
}
|
||||
|
||||
// ─── Graceful Shutdown ───────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -77,9 +77,14 @@ export interface ServerConfig extends LauncherConfig {
|
|||
* If true, run a background pump that reads runtime values for the
|
||||
* variables of the user's currently-selected POU and writes them to
|
||||
* tui-live-values.json so the TUI Viewer can overlay them inline.
|
||||
* Off by default. 500 ms interval (fixed in v0.3).
|
||||
* Off by default.
|
||||
*/
|
||||
liveValues?: boolean;
|
||||
/**
|
||||
* Poll interval (ms) for the live-values pump. Default 500.
|
||||
* Clamped to [100, 60000]; values outside the range are coerced.
|
||||
*/
|
||||
liveValuesIntervalMs?: number;
|
||||
}
|
||||
|
||||
/** Script template parameters */
|
||||
|
|
|
|||
43
tests/unit/clamp-interval.test.ts
Normal file
43
tests/unit/clamp-interval.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
// The clamp helper isn't exported (it's local to bin.ts which is the entry
|
||||
// point), but its behaviour is the contract this test pins down: the
|
||||
// production code's clamp must match these expectations or live-values
|
||||
// poll cadence is wrong.
|
||||
|
||||
const MIN = 100;
|
||||
const MAX = 60_000;
|
||||
const DEFAULT = 500;
|
||||
|
||||
function clampInterval(raw: string | undefined): number {
|
||||
const parsed = parseInt(raw ?? String(DEFAULT), 10);
|
||||
if (!Number.isFinite(parsed)) return DEFAULT;
|
||||
return Math.max(MIN, Math.min(parsed, MAX));
|
||||
}
|
||||
|
||||
describe('clampInterval', () => {
|
||||
it('returns default when raw is undefined', () => {
|
||||
expect(clampInterval(undefined)).toBe(DEFAULT);
|
||||
});
|
||||
|
||||
it('returns default when raw is unparseable', () => {
|
||||
expect(clampInterval('not-a-number')).toBe(DEFAULT);
|
||||
});
|
||||
|
||||
it('clamps below the minimum', () => {
|
||||
expect(clampInterval('50')).toBe(MIN);
|
||||
expect(clampInterval('0')).toBe(MIN);
|
||||
expect(clampInterval('-100')).toBe(MIN);
|
||||
});
|
||||
|
||||
it('clamps above the maximum', () => {
|
||||
expect(clampInterval('1000000')).toBe(MAX);
|
||||
});
|
||||
|
||||
it('passes through values inside the range', () => {
|
||||
expect(clampInterval('100')).toBe(100);
|
||||
expect(clampInterval('500')).toBe(500);
|
||||
expect(clampInterval('1000')).toBe(1000);
|
||||
expect(clampInterval('60000')).toBe(60_000);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue