opencode-pair-autonomy 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +90 -0
  2. package/bin/opencode-pair-autonomy.js +20 -0
  3. package/dist/__tests__/comment-guard.test.d.ts +1 -0
  4. package/dist/__tests__/config.test.d.ts +1 -0
  5. package/dist/__tests__/learning.test.d.ts +1 -0
  6. package/dist/__tests__/plan-mode.test.d.ts +1 -0
  7. package/dist/agents.d.ts +2 -0
  8. package/dist/cli.d.ts +1 -0
  9. package/dist/cli.js +15351 -0
  10. package/dist/commands.d.ts +2 -0
  11. package/dist/config.d.ts +3 -0
  12. package/dist/hooks/comment-guard.d.ts +15 -0
  13. package/dist/hooks/file-edited.d.ts +7 -0
  14. package/dist/hooks/index.d.ts +46 -0
  15. package/dist/hooks/post-tool-use.d.ts +5 -0
  16. package/dist/hooks/pre-compact.d.ts +4 -0
  17. package/dist/hooks/pre-tool-use.d.ts +5 -0
  18. package/dist/hooks/prompt-refiner.d.ts +38 -0
  19. package/dist/hooks/runtime.d.ts +91 -0
  20. package/dist/hooks/sdk.d.ts +6 -0
  21. package/dist/hooks/session-end.d.ts +4 -0
  22. package/dist/hooks/session-start.d.ts +19 -0
  23. package/dist/hooks/stop.d.ts +5 -0
  24. package/dist/i18n/index.d.ts +15 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.js +17823 -0
  27. package/dist/installer.d.ts +12 -0
  28. package/dist/learning/analyzer.d.ts +15 -0
  29. package/dist/learning/store.d.ts +4 -0
  30. package/dist/learning/types.d.ts +32 -0
  31. package/dist/mcp.d.ts +4 -0
  32. package/dist/project-facts.d.ts +8 -0
  33. package/dist/prompts/coordinator.d.ts +2 -0
  34. package/dist/prompts/shared.d.ts +5 -0
  35. package/dist/prompts/workers.d.ts +8 -0
  36. package/dist/types.d.ts +81 -0
  37. package/dist/utils.d.ts +6 -0
  38. package/examples/opencode-pair-autonomy.jsonc +35 -0
  39. package/examples/opencode.jsonc +17 -0
  40. package/package.json +103 -0
  41. package/vendor/mcp/pg-mcp/README.md +91 -0
  42. package/vendor/mcp/pg-mcp/config.example.json +26 -0
  43. package/vendor/mcp/pg-mcp/config.json +15 -0
  44. package/vendor/mcp/pg-mcp/package-lock.json +1288 -0
  45. package/vendor/mcp/pg-mcp/package.json +18 -0
  46. package/vendor/mcp/pg-mcp/src/config.js +71 -0
  47. package/vendor/mcp/pg-mcp/src/db.js +85 -0
  48. package/vendor/mcp/pg-mcp/src/index.js +203 -0
  49. package/vendor/mcp/pg-mcp/src/sqlGuard.js +75 -0
  50. package/vendor/mcp/pg-mcp/src/tools.js +89 -0
  51. package/vendor/mcp/ssh-mcp/README.md +46 -0
  52. package/vendor/mcp/ssh-mcp/config.example.json +23 -0
  53. package/vendor/mcp/ssh-mcp/config.json +6 -0
  54. package/vendor/mcp/ssh-mcp/package-lock.json +1142 -0
  55. package/vendor/mcp/ssh-mcp/package.json +18 -0
  56. package/vendor/mcp/ssh-mcp/src/config.js +140 -0
  57. package/vendor/mcp/ssh-mcp/src/index.js +130 -0
  58. package/vendor/mcp/ssh-mcp/src/ssh.js +163 -0
  59. package/vendor/mcp/sudo-mcp/README.md +51 -0
  60. package/vendor/mcp/sudo-mcp/config.example.json +28 -0
  61. package/vendor/mcp/sudo-mcp/config.json +28 -0
  62. package/vendor/mcp/sudo-mcp/package-lock.json +1145 -0
  63. package/vendor/mcp/sudo-mcp/package.json +18 -0
  64. package/vendor/mcp/sudo-mcp/src/config.js +57 -0
  65. package/vendor/mcp/sudo-mcp/src/index.js +267 -0
  66. package/vendor/mcp/sudo-mcp/src/runner.js +168 -0
  67. package/vendor/mcp/web-agent-mcp/package-lock.json +2886 -0
  68. package/vendor/mcp/web-agent-mcp/package.json +28 -0
  69. package/vendor/mcp/web-agent-mcp/src/adapters/cloakbrowser/adapter.ts +335 -0
  70. package/vendor/mcp/web-agent-mcp/src/adapters/cloakbrowser/auth-heuristics.ts +324 -0
  71. package/vendor/mcp/web-agent-mcp/src/adapters/cloakbrowser/launcher.ts +1340 -0
  72. package/vendor/mcp/web-agent-mcp/src/config/env.ts +107 -0
  73. package/vendor/mcp/web-agent-mcp/src/core/action-flow.ts +82 -0
  74. package/vendor/mcp/web-agent-mcp/src/core/artifact-store.ts +109 -0
  75. package/vendor/mcp/web-agent-mcp/src/core/errors.ts +108 -0
  76. package/vendor/mcp/web-agent-mcp/src/core/observation-flow.ts +38 -0
  77. package/vendor/mcp/web-agent-mcp/src/core/policy-engine.ts +113 -0
  78. package/vendor/mcp/web-agent-mcp/src/core/retry-policy.ts +42 -0
  79. package/vendor/mcp/web-agent-mcp/src/core/session-manager.ts +670 -0
  80. package/vendor/mcp/web-agent-mcp/src/core/session-restart-policy.ts +34 -0
  81. package/vendor/mcp/web-agent-mcp/src/core/task-history.ts +97 -0
  82. package/vendor/mcp/web-agent-mcp/src/index.ts +3 -0
  83. package/vendor/mcp/web-agent-mcp/src/schemas/act.ts +167 -0
  84. package/vendor/mcp/web-agent-mcp/src/schemas/common.ts +56 -0
  85. package/vendor/mcp/web-agent-mcp/src/schemas/observe.ts +214 -0
  86. package/vendor/mcp/web-agent-mcp/src/schemas/page.ts +21 -0
  87. package/vendor/mcp/web-agent-mcp/src/schemas/policy.ts +42 -0
  88. package/vendor/mcp/web-agent-mcp/src/schemas/runtime.ts +21 -0
  89. package/vendor/mcp/web-agent-mcp/src/schemas/session.ts +63 -0
  90. package/vendor/mcp/web-agent-mcp/src/server.ts +75 -0
  91. package/vendor/mcp/web-agent-mcp/src/tools/act/click.ts +68 -0
  92. package/vendor/mcp/web-agent-mcp/src/tools/act/drag.ts +57 -0
  93. package/vendor/mcp/web-agent-mcp/src/tools/act/enter-code.ts +78 -0
  94. package/vendor/mcp/web-agent-mcp/src/tools/act/fill.ts +65 -0
  95. package/vendor/mcp/web-agent-mcp/src/tools/act/pinch.ts +58 -0
  96. package/vendor/mcp/web-agent-mcp/src/tools/act/press.ts +67 -0
  97. package/vendor/mcp/web-agent-mcp/src/tools/act/shared.ts +73 -0
  98. package/vendor/mcp/web-agent-mcp/src/tools/act/swipe.ts +59 -0
  99. package/vendor/mcp/web-agent-mcp/src/tools/act/wait-for.ts +56 -0
  100. package/vendor/mcp/web-agent-mcp/src/tools/act/wheel.ts +59 -0
  101. package/vendor/mcp/web-agent-mcp/src/tools/observe/a11y.ts +60 -0
  102. package/vendor/mcp/web-agent-mcp/src/tools/observe/auth-state.ts +92 -0
  103. package/vendor/mcp/web-agent-mcp/src/tools/observe/boxes.ts +66 -0
  104. package/vendor/mcp/web-agent-mcp/src/tools/observe/console.ts +67 -0
  105. package/vendor/mcp/web-agent-mcp/src/tools/observe/dom.ts +60 -0
  106. package/vendor/mcp/web-agent-mcp/src/tools/observe/network.ts +67 -0
  107. package/vendor/mcp/web-agent-mcp/src/tools/observe/page-state.ts +93 -0
  108. package/vendor/mcp/web-agent-mcp/src/tools/observe/screenshot.ts +73 -0
  109. package/vendor/mcp/web-agent-mcp/src/tools/observe/text.ts +70 -0
  110. package/vendor/mcp/web-agent-mcp/src/tools/observe/wait-for-network.ts +70 -0
  111. package/vendor/mcp/web-agent-mcp/src/tools/page/navigate.ts +59 -0
  112. package/vendor/mcp/web-agent-mcp/src/tools/policy/recommend-observation.ts +40 -0
  113. package/vendor/mcp/web-agent-mcp/src/tools/register-tools.ts +55 -0
  114. package/vendor/mcp/web-agent-mcp/src/tools/runtime/evaluate-js.ts +83 -0
  115. package/vendor/mcp/web-agent-mcp/src/tools/session/close.ts +41 -0
  116. package/vendor/mcp/web-agent-mcp/src/tools/session/create.ts +86 -0
  117. package/vendor/mcp/web-agent-mcp/src/tools/session/restart.ts +72 -0
  118. package/vendor/mcp/web-agent-mcp/src/utils/fs.ts +28 -0
  119. package/vendor/mcp/web-agent-mcp/src/utils/ids.ts +9 -0
  120. package/vendor/mcp/web-agent-mcp/src/utils/time.ts +7 -0
  121. package/vendor/mcp/web-agent-mcp/tsconfig.json +22 -0
  122. package/vendor/skills/editorial-technical-ui/SKILL.md +84 -0
  123. package/vendor/skills/figma-console/SKILL.md +839 -0
  124. package/vendor/skills/go-fiber-postgres/SKILL.md +31 -0
  125. package/vendor/skills/opencode-plugin-dev/SKILL.md +31 -0
  126. package/vendor/skills/rust-media-desktop/SKILL.md +30 -0
  127. package/vendor/skills/vue-vite-ui/SKILL.md +31 -0
  128. package/vendor/skills/web-agent-browser/SKILL.md +140 -0
@@ -0,0 +1,107 @@
1
+ import path from "node:path";
2
+ import { z } from "zod";
3
+
4
+ const envSchema = z.object({
5
+ WEB_AGENT_SERVER_NAME: z.string().default("web-agent-mcp"),
6
+ WEB_AGENT_SERVER_VERSION: z.string().default("0.1.0"),
7
+ WEB_AGENT_DATA_DIR: z.string().default(path.resolve(process.cwd(), ".data")),
8
+ WEB_AGENT_HEADLESS: z
9
+ .enum(["true", "false"])
10
+ .optional()
11
+ .transform((value) => value === "true"),
12
+ WEB_AGENT_DEFAULT_LOCALE: z.string().default("en-US"),
13
+ WEB_AGENT_DEFAULT_TIMEZONE_ID: z.string().optional(),
14
+ WEB_AGENT_CHROME_USER_DATA_DIR: z.string().optional(),
15
+ WEB_AGENT_CHROME_PROFILE_DIRECTORY: z.string().optional(),
16
+ WEB_AGENT_DEFAULT_HUMANIZE: z
17
+ .enum(["true", "false"])
18
+ .optional()
19
+ .transform((value) => value === "true"),
20
+ WEB_AGENT_DEFAULT_VIEWPORT_WIDTH: z.coerce
21
+ .number()
22
+ .int()
23
+ .positive()
24
+ .default(1440),
25
+ WEB_AGENT_DEFAULT_VIEWPORT_HEIGHT: z.coerce
26
+ .number()
27
+ .int()
28
+ .positive()
29
+ .default(960),
30
+ WEB_AGENT_DEFAULT_LAUNCH_ARGS: z.string().optional(),
31
+ WEB_AGENT_SESSION_MAX_CONSECUTIVE_ERRORS: z.coerce
32
+ .number()
33
+ .int()
34
+ .positive()
35
+ .default(3),
36
+ WEB_AGENT_SESSION_RESTART_COOLDOWN_MS: z.coerce
37
+ .number()
38
+ .int()
39
+ .positive()
40
+ .default(30000),
41
+ WEB_AGENT_HISTORY_DIR: z.string().optional(),
42
+ WEB_AGENT_ARTIFACTS_DIR: z.string().optional(),
43
+ WEB_AGENT_PROFILES_DIR: z.string().optional(),
44
+ });
45
+
46
+ export type WebAgentEnv = {
47
+ serverName: string;
48
+ serverVersion: string;
49
+ dataDir: string;
50
+ historyDir: string;
51
+ artifactsDir: string;
52
+ profilesDir: string;
53
+ headless: boolean;
54
+ defaultLocale: string;
55
+ defaultTimezoneId?: string;
56
+ chromeUserDataDir?: string;
57
+ chromeProfileDirectory?: string;
58
+ defaultHumanize: boolean;
59
+ defaultLaunchArgs: string[];
60
+ defaultViewport: {
61
+ width: number;
62
+ height: number;
63
+ };
64
+ sessionMaxConsecutiveErrors: number;
65
+ sessionRestartCooldownMs: number;
66
+ };
67
+
68
+ export function loadEnv(source: NodeJS.ProcessEnv = process.env): WebAgentEnv {
69
+ const parsed = envSchema.parse(source);
70
+ const historyDir =
71
+ parsed.WEB_AGENT_HISTORY_DIR ??
72
+ path.join(parsed.WEB_AGENT_DATA_DIR, "history");
73
+ const artifactsDir =
74
+ parsed.WEB_AGENT_ARTIFACTS_DIR ??
75
+ path.join(parsed.WEB_AGENT_DATA_DIR, "artifacts");
76
+ const profilesDir =
77
+ parsed.WEB_AGENT_PROFILES_DIR ??
78
+ path.join(parsed.WEB_AGENT_DATA_DIR, "profiles");
79
+ const defaultLaunchArgs = parsed.WEB_AGENT_DEFAULT_LAUNCH_ARGS
80
+ ? parsed.WEB_AGENT_DEFAULT_LAUNCH_ARGS.split(",")
81
+ .map((value) => value.trim())
82
+ .filter(Boolean)
83
+ : [];
84
+
85
+ return {
86
+ serverName: parsed.WEB_AGENT_SERVER_NAME,
87
+ serverVersion: parsed.WEB_AGENT_SERVER_VERSION,
88
+ dataDir: parsed.WEB_AGENT_DATA_DIR,
89
+ historyDir,
90
+ artifactsDir,
91
+ profilesDir,
92
+ headless: parsed.WEB_AGENT_HEADLESS,
93
+ defaultLocale: parsed.WEB_AGENT_DEFAULT_LOCALE,
94
+ defaultTimezoneId: parsed.WEB_AGENT_DEFAULT_TIMEZONE_ID,
95
+ chromeUserDataDir: parsed.WEB_AGENT_CHROME_USER_DATA_DIR,
96
+ chromeProfileDirectory: parsed.WEB_AGENT_CHROME_PROFILE_DIRECTORY,
97
+ defaultHumanize: parsed.WEB_AGENT_DEFAULT_HUMANIZE,
98
+ defaultLaunchArgs,
99
+ defaultViewport: {
100
+ width: parsed.WEB_AGENT_DEFAULT_VIEWPORT_WIDTH,
101
+ height: parsed.WEB_AGENT_DEFAULT_VIEWPORT_HEIGHT,
102
+ },
103
+ sessionMaxConsecutiveErrors:
104
+ parsed.WEB_AGENT_SESSION_MAX_CONSECUTIVE_ERRORS,
105
+ sessionRestartCooldownMs: parsed.WEB_AGENT_SESSION_RESTART_COOLDOWN_MS,
106
+ };
107
+ }
@@ -0,0 +1,82 @@
1
+ import { asWebAgentError } from "./errors.js";
2
+ import { buildFollowUpByGoal, toPolicyEnvelope } from "./observation-flow.js";
3
+ import { recommendObservation } from "./policy-engine.js";
4
+ import { getRetryPolicy } from "./retry-policy.js";
5
+ import { shouldRecommendSessionRestart } from "./session-restart-policy.js";
6
+
7
+ export function buildActionSuccessPayload(input: {
8
+ actionId: string;
9
+ appliedMode: "semantic" | "physical";
10
+ verificationHint?: string;
11
+ targetSelectorKnown: boolean;
12
+ }) {
13
+ return {
14
+ action_id: input.actionId,
15
+ applied_mode: input.appliedMode,
16
+ verification_hint: input.verificationHint,
17
+ follow_up_by_goal: buildFollowUpByGoal({
18
+ pageStateKnown: true,
19
+ targetSelectorKnown: input.targetSelectorKnown,
20
+ recentFailure: "none",
21
+ }),
22
+ };
23
+ }
24
+
25
+ export function buildActionFailurePayload(input: {
26
+ error: unknown;
27
+ actionId?: string;
28
+ appliedMode: "semantic" | "physical";
29
+ targetSelectorKnown: boolean;
30
+ sessionHealth?: {
31
+ consecutiveErrors: number;
32
+ maxConsecutiveErrors: number;
33
+ cooldownMs: number;
34
+ lastRestartAt?: string;
35
+ now: string;
36
+ };
37
+ }) {
38
+ const mapped = asWebAgentError(input.error);
39
+ const recentFailure =
40
+ mapped.code === "STATE_ELEMENT_NOT_FOUND"
41
+ ? "semantic_not_found"
42
+ : mapped.code === "STATE_TARGET_NOT_INTERACTABLE"
43
+ ? "semantic_failed"
44
+ : mapped.type === "BROWSER"
45
+ ? "runtime_error"
46
+ : "layout_uncertain";
47
+
48
+ const fallback = toPolicyEnvelope(
49
+ recommendObservation({
50
+ goal: input.appliedMode === "physical" ? "visual_verify" : "interact",
51
+ page_state_known: true,
52
+ target_selector_known: input.targetSelectorKnown,
53
+ needs_precise_coordinates: input.appliedMode === "physical",
54
+ needs_full_page_context: false,
55
+ recent_failure: recentFailure,
56
+ }),
57
+ );
58
+ const retryPolicy = getRetryPolicy(mapped);
59
+ const sessionRestart = input.sessionHealth
60
+ ? shouldRecommendSessionRestart({
61
+ consecutiveErrors: input.sessionHealth.consecutiveErrors,
62
+ maxConsecutiveErrors: input.sessionHealth.maxConsecutiveErrors,
63
+ cooldownMs: input.sessionHealth.cooldownMs,
64
+ lastRestartAt: input.sessionHealth.lastRestartAt,
65
+ now: input.sessionHealth.now,
66
+ browserError: mapped.type === "BROWSER",
67
+ })
68
+ : { recommended: false as const };
69
+
70
+ return {
71
+ action_id: input.actionId,
72
+ applied_mode: input.appliedMode,
73
+ retry_hint: retryPolicy.retryHint,
74
+ fallback_observation: fallback,
75
+ follow_up_by_goal: buildFollowUpByGoal({
76
+ pageStateKnown: true,
77
+ targetSelectorKnown: input.targetSelectorKnown,
78
+ recentFailure,
79
+ }),
80
+ session_restart: sessionRestart,
81
+ };
82
+ }
@@ -0,0 +1,109 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import type { WebAgentEnv } from "../config/env.js";
4
+ import {
5
+ appendJsonLine,
6
+ ensureDir,
7
+ readJsonFile,
8
+ writeJsonFile,
9
+ } from "../utils/fs.js";
10
+ import { createId } from "../utils/ids.js";
11
+ import { nowIso } from "../utils/time.js";
12
+
13
+ export type ArtifactKind =
14
+ | "a11y"
15
+ | "dom"
16
+ | "text"
17
+ | "markdown"
18
+ | "boxes"
19
+ | "screenshot"
20
+ | "console"
21
+ | "network"
22
+ | "eval";
23
+
24
+ export type ArtifactRecord = {
25
+ artifact_id: string;
26
+ session_id?: string;
27
+ page_id?: string;
28
+ task_id?: string;
29
+ action_id?: string;
30
+ kind: ArtifactKind;
31
+ created_at: string;
32
+ url?: string;
33
+ title?: string;
34
+ parent_artifact_id?: string;
35
+ storage_path?: string;
36
+ bytes?: number;
37
+ meta?: Record<string, unknown>;
38
+ };
39
+
40
+ export class ArtifactStore {
41
+ private readonly artifactPath: string;
42
+ private readonly indexPath: string;
43
+ private readonly rootDir: string;
44
+
45
+ constructor(env: WebAgentEnv) {
46
+ this.rootDir = env.artifactsDir;
47
+ this.artifactPath = path.join(env.historyDir, "artifacts.jsonl");
48
+ this.indexPath = path.join(env.artifactsDir, "index.json");
49
+ }
50
+
51
+ async writeText(
52
+ kind: ArtifactKind,
53
+ text: string,
54
+ refs: Omit<
55
+ ArtifactRecord,
56
+ "artifact_id" | "created_at" | "kind" | "storage_path" | "bytes"
57
+ > = {},
58
+ ) {
59
+ const artifactId = createId("artifact");
60
+ const filePath = path.join(this.rootDir, `${artifactId}.txt`);
61
+ await ensureDir(this.rootDir);
62
+ await fs.writeFile(filePath, text, "utf8");
63
+ const bytes = Buffer.byteLength(text, "utf8");
64
+ const record = await this.saveMetadata({
65
+ artifact_id: artifactId,
66
+ kind,
67
+ created_at: nowIso(),
68
+ storage_path: filePath,
69
+ bytes,
70
+ ...refs,
71
+ });
72
+ return record;
73
+ }
74
+
75
+ async writeBinary(
76
+ kind: ArtifactKind,
77
+ buffer: Buffer,
78
+ extension: string,
79
+ refs: Omit<
80
+ ArtifactRecord,
81
+ "artifact_id" | "created_at" | "kind" | "storage_path" | "bytes"
82
+ > = {},
83
+ ) {
84
+ const artifactId = createId("artifact");
85
+ const filePath = path.join(this.rootDir, `${artifactId}.${extension}`);
86
+ await ensureDir(this.rootDir);
87
+ await fs.writeFile(filePath, buffer);
88
+ const record = await this.saveMetadata({
89
+ artifact_id: artifactId,
90
+ kind,
91
+ created_at: nowIso(),
92
+ storage_path: filePath,
93
+ bytes: buffer.byteLength,
94
+ ...refs,
95
+ });
96
+ return record;
97
+ }
98
+
99
+ private async saveMetadata(record: ArtifactRecord) {
100
+ await appendJsonLine(this.artifactPath, record);
101
+ const index = await readJsonFile<Record<string, ArtifactRecord>>(
102
+ this.indexPath,
103
+ {},
104
+ );
105
+ index[record.artifact_id] = record;
106
+ await writeJsonFile(this.indexPath, index);
107
+ return record;
108
+ }
109
+ }
@@ -0,0 +1,108 @@
1
+ import type { ErrorType, ToolEnvelope } from "../schemas/common.js";
2
+ import { createToolFailure } from "../schemas/common.js";
3
+
4
+ export type WebAgentErrorCode =
5
+ | "INPUT_INVALID_URL"
6
+ | "INPUT_MISSING_SESSION"
7
+ | "STATE_PAGE_NOT_FOUND"
8
+ | "STATE_NAVIGATION_NOT_OBSERVED"
9
+ | "STATE_ELEMENT_NOT_FOUND"
10
+ | "STATE_TARGET_NOT_INTERACTABLE"
11
+ | "BROWSER_LAUNCH_FAILED"
12
+ | "BROWSER_DISCONNECTED"
13
+ | "NETWORK_TIMEOUT"
14
+ | "STORAGE_WRITE_FAILED"
15
+ | "INTERNAL_NOT_IMPLEMENTED"
16
+ | "INTERNAL_UNREACHABLE";
17
+
18
+ const errorTypeByCode: Record<WebAgentErrorCode, ErrorType> = {
19
+ INPUT_INVALID_URL: "INPUT",
20
+ INPUT_MISSING_SESSION: "INPUT",
21
+ STATE_PAGE_NOT_FOUND: "STATE",
22
+ STATE_NAVIGATION_NOT_OBSERVED: "STATE",
23
+ STATE_ELEMENT_NOT_FOUND: "STATE",
24
+ STATE_TARGET_NOT_INTERACTABLE: "STATE",
25
+ BROWSER_LAUNCH_FAILED: "BROWSER",
26
+ BROWSER_DISCONNECTED: "BROWSER",
27
+ NETWORK_TIMEOUT: "NETWORK",
28
+ STORAGE_WRITE_FAILED: "STORAGE",
29
+ INTERNAL_NOT_IMPLEMENTED: "INTERNAL",
30
+ INTERNAL_UNREACHABLE: "INTERNAL",
31
+ };
32
+
33
+ export class WebAgentError extends Error {
34
+ readonly code: WebAgentErrorCode;
35
+ readonly type: ErrorType;
36
+ readonly details?: Record<string, unknown>;
37
+
38
+ constructor(
39
+ code: WebAgentErrorCode,
40
+ message: string,
41
+ details?: Record<string, unknown>,
42
+ ) {
43
+ super(message);
44
+ this.name = "WebAgentError";
45
+ this.code = code;
46
+ this.type = errorTypeByCode[code];
47
+ this.details = details;
48
+ }
49
+ }
50
+
51
+ export function asWebAgentError(error: unknown) {
52
+ if (error instanceof WebAgentError) {
53
+ return error;
54
+ }
55
+
56
+ const message =
57
+ error instanceof Error ? error.message : "Unexpected internal error.";
58
+
59
+ if (
60
+ message.includes("locator(") ||
61
+ message.includes("waitForSelector") ||
62
+ message.includes("bounding box")
63
+ ) {
64
+ if (message.toLowerCase().includes("timeout")) {
65
+ return new WebAgentError("STATE_ELEMENT_NOT_FOUND", message);
66
+ }
67
+
68
+ if (
69
+ message.toLowerCase().includes("not visible") ||
70
+ message.toLowerCase().includes("not enabled")
71
+ ) {
72
+ return new WebAgentError("STATE_TARGET_NOT_INTERACTABLE", message);
73
+ }
74
+
75
+ return new WebAgentError("STATE_ELEMENT_NOT_FOUND", message);
76
+ }
77
+
78
+ if (
79
+ message
80
+ .toLowerCase()
81
+ .includes("target page, context or browser has been closed")
82
+ ) {
83
+ return new WebAgentError("BROWSER_DISCONNECTED", message);
84
+ }
85
+
86
+ if (message.toLowerCase().includes("timeout")) {
87
+ return new WebAgentError("NETWORK_TIMEOUT", message);
88
+ }
89
+
90
+ return new WebAgentError("INTERNAL_UNREACHABLE", message);
91
+ }
92
+
93
+ export function createFailureResult(
94
+ error: unknown,
95
+ envelope: Partial<ToolEnvelope> = {},
96
+ ) {
97
+ const mapped = asWebAgentError(error);
98
+ return createToolFailure({
99
+ ok: false,
100
+ code: mapped.code,
101
+ message: mapped.message,
102
+ ...envelope,
103
+ error: {
104
+ type: mapped.type,
105
+ details: mapped.details,
106
+ },
107
+ });
108
+ }
@@ -0,0 +1,38 @@
1
+ import { recommendObservation } from "./policy-engine.js";
2
+
3
+ type Goal = "interact" | "read" | "visual_verify" | "debug" | "gesture";
4
+
5
+ export function buildFollowUpByGoal(input: {
6
+ pageStateKnown: boolean;
7
+ targetSelectorKnown: boolean;
8
+ needsPreciseCoordinates?: boolean;
9
+ needsFullPageContext?: boolean;
10
+ recentFailure?: "none" | "semantic_not_found" | "semantic_failed" | "layout_uncertain" | "runtime_error";
11
+ }) {
12
+ const goals: Goal[] = ["interact", "read", "visual_verify", "debug", "gesture"];
13
+
14
+ return Object.fromEntries(
15
+ goals.map((goal) => [
16
+ goal,
17
+ toPolicyEnvelope(
18
+ recommendObservation({
19
+ goal,
20
+ page_state_known: input.pageStateKnown,
21
+ target_selector_known: input.targetSelectorKnown,
22
+ needs_precise_coordinates: goal === "gesture" ? true : Boolean(input.needsPreciseCoordinates),
23
+ needs_full_page_context: goal === "visual_verify" ? Boolean(input.needsFullPageContext) : false,
24
+ recent_failure: input.recentFailure ?? "none"
25
+ })
26
+ )
27
+ ])
28
+ ) as Record<Goal, ReturnType<typeof toPolicyEnvelope>>;
29
+ }
30
+
31
+ export function toPolicyEnvelope(result: ReturnType<typeof recommendObservation>) {
32
+ return {
33
+ recommended_tool: result.recommendedTool,
34
+ screenshot_mode: result.screenshotMode,
35
+ rationale: result.rationale,
36
+ escalation_order: result.escalationOrder
37
+ };
38
+ }
@@ -0,0 +1,113 @@
1
+ import type { RecommendObservationInput } from "../schemas/policy.js";
2
+
3
+ export type ObservationRecommendation = {
4
+ recommendedTool: "observe.a11y" | "observe.dom" | "observe.text" | "observe.boxes" | "observe.screenshot";
5
+ screenshotMode: "none" | "element" | "viewport" | "full";
6
+ rationale: string[];
7
+ escalationOrder: string[];
8
+ };
9
+
10
+ export function recommendObservation(input: RecommendObservationInput): ObservationRecommendation {
11
+ const rationale: string[] = [];
12
+ const escalationOrder = [
13
+ "observe.a11y",
14
+ "observe.dom",
15
+ "observe.text",
16
+ "observe.boxes",
17
+ "observe.screenshot"
18
+ ];
19
+
20
+ if (input.goal === "read") {
21
+ rationale.push("Reading tasks should prefer cheap textual context before any visual artifact.");
22
+ return {
23
+ recommendedTool: "observe.text",
24
+ screenshotMode: "none",
25
+ rationale,
26
+ escalationOrder
27
+ };
28
+ }
29
+
30
+ if (input.goal === "debug") {
31
+ rationale.push("Debug flows usually need structural context before expensive screenshots.");
32
+ rationale.push(
33
+ input.recent_failure === "runtime_error"
34
+ ? "Runtime errors often benefit from DOM plus later console/network review."
35
+ : "A screenshot is useful only after structural context is insufficient."
36
+ );
37
+ return {
38
+ recommendedTool: input.page_state_known ? "observe.dom" : "observe.a11y",
39
+ screenshotMode: "none",
40
+ rationale,
41
+ escalationOrder
42
+ };
43
+ }
44
+
45
+ if (input.goal === "visual_verify") {
46
+ rationale.push("Visual verification explicitly needs pixels, so screenshot escalation is justified.");
47
+ if (input.needs_full_page_context) {
48
+ rationale.push("Full-page context was requested, so prefer a full-page screenshot.");
49
+ return {
50
+ recommendedTool: "observe.screenshot",
51
+ screenshotMode: "full",
52
+ rationale,
53
+ escalationOrder
54
+ };
55
+ }
56
+
57
+ return {
58
+ recommendedTool: "observe.screenshot",
59
+ screenshotMode: input.target_selector_known ? "element" : "viewport",
60
+ rationale,
61
+ escalationOrder
62
+ };
63
+ }
64
+
65
+ if (input.goal === "gesture" || input.needs_precise_coordinates) {
66
+ rationale.push("Gesture execution needs precise spatial data before acting.");
67
+ rationale.push("Bounding boxes are cheaper than screenshots and usually sufficient for coordinates.");
68
+ return {
69
+ recommendedTool: "observe.boxes",
70
+ screenshotMode: "none",
71
+ rationale,
72
+ escalationOrder
73
+ };
74
+ }
75
+
76
+ if (input.recent_failure === "semantic_not_found" || input.recent_failure === "semantic_failed") {
77
+ rationale.push("A semantic failure suggests spatial fallback is needed.");
78
+ return {
79
+ recommendedTool: "observe.boxes",
80
+ screenshotMode: "none",
81
+ rationale,
82
+ escalationOrder
83
+ };
84
+ }
85
+
86
+ if (input.goal === "interact") {
87
+ rationale.push("Interaction tasks should start with structured page state, not screenshots.");
88
+ if (input.page_state_known && input.target_selector_known) {
89
+ rationale.push("Known page state plus known target favors a lightweight DOM check.");
90
+ return {
91
+ recommendedTool: "observe.dom",
92
+ screenshotMode: "none",
93
+ rationale,
94
+ escalationOrder
95
+ };
96
+ }
97
+
98
+ return {
99
+ recommendedTool: "observe.a11y",
100
+ screenshotMode: "none",
101
+ rationale,
102
+ escalationOrder
103
+ };
104
+ }
105
+
106
+ rationale.push("Default to structured accessibility context as the cheapest general-purpose observation.");
107
+ return {
108
+ recommendedTool: "observe.a11y",
109
+ screenshotMode: "none",
110
+ rationale,
111
+ escalationOrder
112
+ };
113
+ }
@@ -0,0 +1,42 @@
1
+ import type { WebAgentError } from "./errors.js";
2
+
3
+ export type RetryPolicyDecision = {
4
+ retryable: boolean;
5
+ maxAttempts: number;
6
+ retryHint?: string;
7
+ };
8
+
9
+ export function getRetryPolicy(error: WebAgentError): RetryPolicyDecision {
10
+ switch (error.code) {
11
+ case "STATE_ELEMENT_NOT_FOUND":
12
+ return {
13
+ retryable: true,
14
+ maxAttempts: 2,
15
+ retryHint:
16
+ "Refresh observation first, then retry with observe.boxes or an element screenshot.",
17
+ };
18
+ case "STATE_TARGET_NOT_INTERACTABLE":
19
+ return {
20
+ retryable: true,
21
+ maxAttempts: 2,
22
+ retryHint: "Re-check layout and visibility before retrying the action.",
23
+ };
24
+ case "NETWORK_TIMEOUT":
25
+ return {
26
+ retryable: true,
27
+ maxAttempts: 2,
28
+ retryHint: "Retry once if the page is still loading or unstable.",
29
+ };
30
+ case "BROWSER_DISCONNECTED":
31
+ return {
32
+ retryable: true,
33
+ maxAttempts: 1,
34
+ retryHint: "Restart the session before retrying.",
35
+ };
36
+ default:
37
+ return {
38
+ retryable: false,
39
+ maxAttempts: 0,
40
+ };
41
+ }
42
+ }