/ src / browser / target-errors.ts
target-errors.ts
 1  /**
 2   * Structured error types for the target resolution system.
 3   *
 4   * Every browser action (click, type, select, get) that targets a DOM element
 5   * goes through the unified resolver. When resolution fails, one of these
 6   * structured errors is thrown so that AI agents and adapter authors get
 7   * actionable diagnostics instead of a generic "Element not found".
 8   *
 9   * Numeric-ref codes (from snapshot indices):
10   *   - not_found: the ref no longer exists in the DOM
11   *   - stale_ref: the ref still exists but points to a different element
12   *
13   * CSS-selector codes (from `--selector <css>` entrypoints):
14   *   - invalid_selector:       selector syntax rejected by querySelectorAll
15   *   - selector_not_found:     0 matches
16   *   - selector_ambiguous:     >1 matches for a write op without --nth
17   *   - selector_nth_out_of_range: --nth beyond matches_n
18   */
19  
20  export type TargetErrorCode =
21    | 'not_found'
22    | 'stale_ref'
23    | 'invalid_selector'
24    | 'selector_not_found'
25    | 'selector_ambiguous'
26    | 'selector_nth_out_of_range';
27  
28  export interface TargetErrorInfo {
29    code: TargetErrorCode;
30    message: string;
31    hint: string;
32    candidates?: string[];
33    /** CSS-path match count, when the error was raised mid-resolution */
34    matches_n?: number;
35  }
36  
37  export class TargetError extends Error {
38    readonly code: TargetErrorCode;
39    readonly hint: string;
40    readonly candidates?: string[];
41    readonly matches_n?: number;
42  
43    constructor(info: TargetErrorInfo) {
44      super(info.message);
45      this.name = 'TargetError';
46      this.code = info.code;
47      this.hint = info.hint;
48      this.candidates = info.candidates;
49      this.matches_n = info.matches_n;
50    }
51  
52    /** Serialize for structured output to AI agents */
53    toJSON(): TargetErrorInfo {
54      return {
55        code: this.code,
56        message: this.message,
57        hint: this.hint,
58        ...(this.candidates && { candidates: this.candidates }),
59        ...(this.matches_n !== undefined && { matches_n: this.matches_n }),
60      };
61    }
62  }