Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/host/host-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,24 @@ describe('parseTimeout', () => {
expect(() => parseTimeout('abc', 5000)).toThrow(/Invalid timeout/);
expect(() => parseTimeout('Infinity', 5000)).toThrow(/Invalid timeout/);
});

// The tenant-side hm.command dispatcher (agentic-hosting
// command-registry.ts) now rejects timeout_ms < 100 with
// `invalid_params`. Pre-flight at the CLI surface so the operator
// gets a clear local error instead of a confusing two-hop reply.
it('rejects values below MIN_TIMEOUT_MS=100ms', () => {
expect(() => parseTimeout('99', 5000)).toThrow(/minimum 100ms/);
expect(() => parseTimeout('50', 5000)).toThrow(/minimum 100ms/);
expect(() => parseTimeout('1', 5000)).toThrow(/minimum 100ms/);
// Decimal that floors below the floor still rejected.
expect(() => parseTimeout('99.9', 5000)).toThrow(/minimum 100ms/);
});

it('accepts values at and above MIN_TIMEOUT_MS=100ms', () => {
expect(parseTimeout('100', 5000)).toBe(100);
expect(parseTimeout('100.5', 5000)).toBe(100); // floor stays at 100
expect(parseTimeout('30000', 5000)).toBe(30000);
});
});

describe('targetPayload', () => {
Expand Down
14 changes: 13 additions & 1 deletion src/host/host-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,25 @@ function parseGlobalOpts(cmd: Command): GlobalOpts {
return cmd.optsWithGlobals<GlobalOpts>();
}

// Floor used by tenant-side hm.command dispatcher (agentic-hosting
// command-registry.ts MIN_TIMEOUT_MS). Pre-flighting at the CLI surface
// avoids the confusing two-hop error path: CLI → manager → tenant rejects
// with `invalid_params`. Keep aligned with agentic-hosting's constant.
const MIN_TIMEOUT_MS = 100;

function parseTimeout(raw: string | undefined, fallback: number): number {
if (!raw) return fallback;
const n = Number(raw);
if (!Number.isFinite(n) || n <= 0) {
throw new Error(`Invalid timeout: ${raw}`);
}
return Math.floor(n);
const floored = Math.floor(n);
if (floored < MIN_TIMEOUT_MS) {
throw new Error(
`Invalid timeout: ${raw} (minimum ${MIN_TIMEOUT_MS}ms — values below this are rejected by the tenant dispatcher)`,
);
}
return floored;
}

function parseEnvPairs(pairs: readonly string[] | undefined): Record<string, string> | undefined {
Expand Down
Loading