diff --git a/src/errors/index.tsx b/src/errors/index.tsx index 5f96f8766..bab6329e1 100644 --- a/src/errors/index.tsx +++ b/src/errors/index.tsx @@ -1 +1,2 @@ -export { AgentCoreCLIError, InputValidationError } from "./errors"; +export { AgentCoreCLIError, InputValidationError, type AgentCoreCLIErrorOptions } from "./errors"; +export { ERROR_SOURCE } from "./types"; diff --git a/src/globalConfig/accessor.tsx b/src/globalConfig/accessor.tsx index 18ea6d316..6210f285b 100644 --- a/src/globalConfig/accessor.tsx +++ b/src/globalConfig/accessor.tsx @@ -6,9 +6,10 @@ import { } from "./types"; import type { ReadWriteJson } from "../io"; import type { Logger } from "../logging"; -import z from "zod"; import { globalConfigFileSchema } from "./types"; import { DEFAULT_GLOBAL_CONFIG, applyOverrides } from "./config"; +import z from "zod"; +import { InputValidationError } from "../errors"; type DefaultGlobalConfigAccessorConfig = { logger: Logger; @@ -76,8 +77,9 @@ export class DefaultGlobalConfigAccessor implements GlobalConfigAccessor { private async writeToConfigFile(data: GlobalConfigFileData): Promise { const dataParseResult = globalConfigFileSchema.safeParse(data); if (!dataParseResult.success) { - // TODO: mark this as a client-source error. - throw new TypeError(z.prettifyError(dataParseResult.error)); + throw new InputValidationError(z.prettifyError(dataParseResult.error), { + cause: dataParseResult.error, + }); } await this.json.write(this.filePath, dataParseResult.data); diff --git a/src/tui/index.tsx b/src/tui/index.tsx index 6fe429e8b..6e423d503 100644 --- a/src/tui/index.tsx +++ b/src/tui/index.tsx @@ -11,6 +11,7 @@ import { import type { AppIO } from "../io"; import type { Core } from "../handlers/types"; import { JsonKey } from "../handlers/keys"; +import { AgentCoreCLIError, ERROR_SOURCE, type AgentCoreCLIErrorOptions } from "../errors"; // renderJson pretty-prints a value as indented JSON. It is the output // counterpart to renderTui: handlers call it to emit machine-readable results @@ -43,7 +44,7 @@ export async function renderTuiAt( io: AppIO, ): Promise { if (!io.stdin.isTTY || !io.stdout.isTTY) { - throw new TypeError("interactive mode requires a TTY on stdin and stdout"); + throw new InvalidEnvironmentError("interactive mode requires a TTY on stdin and stdout"); } // alternateScreen switches the terminal to its alternate buffer so the TUI @@ -74,3 +75,10 @@ export function renderTui(core: Core, io: AppIO): DefaultHandle { await renderTuiAt(ctx.require(PathKey), ctx, core, io); }; } + +/** Error raised when detecting an invalid environment */ +export class InvalidEnvironmentError extends AgentCoreCLIError { + constructor(message?: string, options?: Omit) { + super(message, { ...options, source: ERROR_SOURCE.USER }); + } +} diff --git a/src/tui/tui.test.tsx b/src/tui/tui.test.tsx index 4c261e2f7..aa176c97b 100644 --- a/src/tui/tui.test.tsx +++ b/src/tui/tui.test.tsx @@ -1,6 +1,6 @@ import { test, expect, describe } from "bun:test"; import { createRootHandler } from "../handlers"; -import { renderJson } from "./index"; +import { InvalidEnvironmentError, renderJson } from "./index"; import { createSilentLogger, TestCoreClient, @@ -89,9 +89,7 @@ describe("TUI stream boundary", () => { globalConfigAccessor: new TestGlobalConfigAccessor(), }); - await expect(root.route(["node", "agentcore"])).rejects.toThrow( - "interactive mode requires a TTY on stdin and stdout", - ); + await expect(root.route(["node", "agentcore"])).rejects.toThrow(InvalidEnvironmentError); }, );