Files
callstack__agent-device/src/batch-contract.ts
T
Michał Pierzchała 83a1eec986 fix: surface typed error codes for metro, MCP, and batch validation failures (#1070)
Bare `throw new Error(...)` surfaces to users as code UNKNOWN with a
generic hint. Convert the remaining user-reachable clusters from the
July 2026 error audit to AppError with the right code, preserving
messages:

- metro/client-metro.ts: timeouts, bridge failures, and Metro start /
  not-ready errors are now COMMAND_FAILED (the not-ready error also
  carries logPath in details); createMetroBridgeRequestError builds on
  AppError so non-retryable bridge errors that escape via rethrow get
  the right code too
- mcp/command-tools.ts + mcp/router.ts: MCP input validation is now
  INVALID_ARGS (JSON-RPC responses unchanged; the router only reads
  .message)
- commands/batch/metadata.ts: step validation is now INVALID_ARGS;
  with the plain-Error opt-out gone, the now-unused
  BatchStepErrorFactory injection seam is removed from
  batch-contract.ts
- cloud-webdriver/webdriver-source.ts: xml.ts parser failures are
  wrapped at the consumer boundary into a single COMMAND_FAILED naming
  what was being parsed
2026-07-04 10:26:24 +02:00

49 lines
1.5 KiB
TypeScript

import { daemonRuntimeSchema, type SessionRuntimeHints } from './kernel/contracts.ts';
import { AppError } from './kernel/errors.ts';
import { isRecord } from './utils/parsing.ts';
export const DEFAULT_BATCH_MAX_STEPS = 100;
export function isValidBatchMaxSteps(maxSteps: number): boolean {
return Number.isInteger(maxSteps) && maxSteps >= 1 && maxSteps <= 1000;
}
export function assertBatchStepCount(stepCount: number, maxSteps: number): void {
if (stepCount > maxSteps) {
throw new AppError('INVALID_ARGS', `batch has ${stepCount} steps; max allowed is ${maxSteps}.`);
}
}
export function readBatchStepRecord(step: unknown, stepNumber: number): Record<string, unknown> {
if (!isRecord(step)) {
throw new AppError('INVALID_ARGS', `Invalid batch step ${stepNumber}.`);
}
return step;
}
export function readBatchStepInputObject(
record: Record<string, unknown>,
stepNumber: number,
): Record<string, unknown> {
const input = record.input;
if (!isRecord(input)) {
throw new AppError('INVALID_ARGS', `Batch step ${stepNumber} input must be an object.`);
}
return input;
}
export function parseBatchStepRuntime(
value: unknown,
stepNumber: number,
): SessionRuntimeHints | undefined {
if (value === undefined) return undefined;
try {
return daemonRuntimeSchema.parse(value);
} catch (error) {
throw new AppError(
'INVALID_ARGS',
`Batch step ${stepNumber} runtime is invalid: ${error instanceof Error ? error.message : String(error)}`,
);
}
}