yunti-browser-runtime 0.1.2 → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yunti-browser-runtime",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Local browser runtime and MCP tools for AI agents.",
6
6
  "license": "MIT",
@@ -39,11 +39,13 @@
39
39
  "scripts": {
40
40
  "mcp": "node mcp/server.js",
41
41
  "bridge": "YUNTI_BROWSER_BRIDGE_ONLY=1 node mcp/server.js",
42
+ "console": "YUNTI_BROWSER_BRIDGE_ONLY=1 node mcp/server.js",
42
43
  "doctor": "node scripts/doctor.js",
43
44
  "doctor:json": "node scripts/doctor.js 2>/dev/null",
44
45
  "print-config": "node scripts/print-config.js",
45
46
  "package:extension": "node scripts/package-extension.js",
46
47
  "check:metadata": "node scripts/check-package-metadata.js",
48
+ "check:action-results": "node scripts/check-action-result-coverage.js",
47
49
  "release:check": "node scripts/release-check.js",
48
50
  "release:prepublish": "npm run check:metadata && npm run release:check",
49
51
  "release:whoami": "npm whoami --registry=https://registry.npmjs.org/",
@@ -52,7 +54,7 @@
52
54
  "release:verify-published": "node scripts/check-published-package.js",
53
55
  "test": "node --test tests/*.test.js",
54
56
  "test:e2e": "node --test tests/e2e.test.js",
55
- "check": "node --check bin/yunti-browser-runtime.js && node --check lib/runtime-paths.js && node --check mcp/server.js && node --check mcp/http-server.js && node --check mcp/bridge-hub.js && node --check mcp/tools.js && node --check mcp/redaction.js && node --check mcp/memory.js && node --check mcp/json-rpc.js && node --check scripts/doctor.js && node --check scripts/print-config.js && node --check scripts/package-extension.js && node --check scripts/check-package-metadata.js && node --check scripts/check-published-package.js && node --check scripts/release-check.js && node --check tests/e2e.test.js && node --check extension/background.js && node --check extension/cdp.js && node --check extension/session-manager.js && node --check extension/tool-handlers.js && node --check extension/settings.js && node --check extension/network-monitor.js && node --check extension/content.js && node --check extension/popup.js"
57
+ "check": "node --check bin/yunti-browser-runtime.js && node --check lib/runtime-paths.js && node --check mcp/server.js && node --check mcp/http-server.js && node --check mcp/bridge-hub.js && node --check mcp/tools.js && node --check mcp/redaction.js && node --check mcp/memory.js && node --check mcp/json-rpc.js && node --check scripts/doctor.js && node --check scripts/print-config.js && node --check scripts/package-extension.js && node --check scripts/check-package-metadata.js && node --check scripts/check-published-package.js && node --check scripts/check-action-result-coverage.js && node --check scripts/release-check.js && node --check tests/e2e.test.js && node --check extension/background.js && node --check extension/cdp.js && node --check extension/session-manager.js && node --check extension/tool-handlers.js && node --check extension/settings.js && node --check extension/network-monitor.js && node --check extension/dom-observer.js && node --check extension/content.js && node --check extension/popup.js"
56
58
  },
57
59
  "engines": {
58
60
  "node": ">=22"
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs"
3
+ import { dirname, join, resolve } from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "..")
7
+ const coveragePath = join(rootDir, "docs", "ACTION_RESULT_COVERAGE.md")
8
+ const text = readFileSync(coveragePath, "utf8")
9
+
10
+ const requiredRows = {
11
+ yunti_click: {
12
+ success: ["uid", "coordinate", "selector"],
13
+ failure: ["stale uid", "missing uid"],
14
+ compatibility: ["clicked", "browserSessionId"],
15
+ },
16
+ yunti_hover: {
17
+ success: ["uid", "coordinate", "selector"],
18
+ failure: ["stale uid", "missing uid"],
19
+ compatibility: ["hovered", "browserSessionId"],
20
+ },
21
+ yunti_fill: {
22
+ success: ["uid keyboard", "uid select", "uid contenteditable", "selector"],
23
+ failure: ["non-editable", "disabled", "readonly", "value not applied"],
24
+ compatibility: ["filled", "browserSessionId"],
25
+ },
26
+ yunti_select: {
27
+ success: ["selector value", "uid value", "uid text"],
28
+ failure: ["option miss", "disabled option", "non-select target"],
29
+ compatibility: ["selected", "browserSessionId"],
30
+ },
31
+ yunti_fill_form: {
32
+ success: ["aggregate success", "partial failure"],
33
+ failure: ["per-field"],
34
+ compatibility: ["filled", "failed", "results"],
35
+ },
36
+ yunti_wait_for: {
37
+ success: ["text", "selector", "urlContains"],
38
+ failure: ["timeout"],
39
+ compatibility: ["found", "waitedMs", "browserSessionId"],
40
+ },
41
+ yunti_scroll: {
42
+ success: ["document", "uid container", "coordinate container"],
43
+ failure: ["uid missing", "no movement", "partial movement"],
44
+ compatibility: ["scrolled", "before", "after"],
45
+ },
46
+ yunti_type_text: {
47
+ success: ["uid", "selector"],
48
+ failure: ["stale uid", "missing uid"],
49
+ compatibility: ["typed", "browserSessionId"],
50
+ },
51
+ yunti_press_key: {
52
+ success: ["uid", "selector"],
53
+ failure: ["stale uid", "missing uid"],
54
+ compatibility: ["pressed", "browserSessionId"],
55
+ },
56
+ yunti_upload_file: {
57
+ success: ["uid", "selector"],
58
+ failure: ["stale uid", "missing uid"],
59
+ compatibility: ["uploaded", "fileCount", "browserSessionId"],
60
+ },
61
+ yunti_drag: {
62
+ success: ["coordinate"],
63
+ failure: ["coordinate argument validation"],
64
+ compatibility: ["dragged", "browserSessionId"],
65
+ },
66
+ }
67
+
68
+ function normalize(value) {
69
+ return String(value || "")
70
+ .replace(/`/g, "")
71
+ .replace(/\s+/g, " ")
72
+ .trim()
73
+ }
74
+
75
+ function parseMatrixRows(markdown) {
76
+ const rows = new Map()
77
+ for (const line of markdown.split(/\r?\n/)) {
78
+ if (!line.startsWith("| `yunti_")) continue
79
+ const cells = line
80
+ .split("|")
81
+ .slice(1, -1)
82
+ .map((cell) => normalize(cell))
83
+ if (cells.length < 5) continue
84
+ rows.set(cells[0], {
85
+ tool: cells[0],
86
+ success: cells[1],
87
+ failure: cells[2],
88
+ compatibility: cells[3],
89
+ remaining: cells[4],
90
+ })
91
+ }
92
+ return rows
93
+ }
94
+
95
+ function includesAll(value, terms) {
96
+ const haystack = normalize(value).toLowerCase()
97
+ return terms.filter((term) => !haystack.includes(term.toLowerCase()))
98
+ }
99
+
100
+ const rows = parseMatrixRows(text)
101
+ const errors = []
102
+
103
+ for (const [tool, expectation] of Object.entries(requiredRows)) {
104
+ const row = rows.get(tool)
105
+ if (!row) {
106
+ errors.push(`missing coverage row for ${tool}`)
107
+ continue
108
+ }
109
+
110
+ for (const [column, terms] of Object.entries(expectation)) {
111
+ const missing = includesAll(row[column], terms)
112
+ if (missing.length) {
113
+ errors.push(`${tool} ${column} column missing: ${missing.join(", ")}`)
114
+ }
115
+ }
116
+
117
+ for (const [column, value] of Object.entries(row)) {
118
+ if (column === "tool") continue
119
+ if (!value || /\b(TBD|TODO|unknown)\b/i.test(value)) {
120
+ errors.push(`${tool} ${column} column is incomplete: ${value || "(empty)"}`)
121
+ }
122
+ }
123
+ }
124
+
125
+ const requiredSnippets = [
126
+ "Last audited phase: P6.2.7",
127
+ "npm run check:action-results",
128
+ "npm run release:check",
129
+ "Real browser closure: Pending",
130
+ "YUNTI_E2E=1 npm run test:e2e",
131
+ ]
132
+
133
+ for (const snippet of requiredSnippets) {
134
+ if (!text.includes(snippet)) {
135
+ errors.push(`coverage gate missing snippet: ${snippet}`)
136
+ }
137
+ }
138
+
139
+ if (errors.length) {
140
+ console.error("Action result coverage check failed:")
141
+ for (const error of errors) console.error(`- ${error}`)
142
+ process.exit(1)
143
+ }
144
+
145
+ console.error(`Action result coverage check passed (${rows.size} rows).`)
package/scripts/doctor.js CHANGED
@@ -68,6 +68,7 @@ async function checkBridge() {
68
68
  authRequired,
69
69
  status: health.status,
70
70
  url: bridgeUrl,
71
+ consoleUrl: `${bridgeUrl}/console`,
71
72
  tokenConfigured: Boolean(bridgeToken),
72
73
  tokenHeader: bridgeTokenHeader,
73
74
  userId: routeUserId,
@@ -85,6 +86,7 @@ async function checkBridge() {
85
86
  authorized: false,
86
87
  authRequired: Boolean(bridgeToken),
87
88
  url: bridgeUrl,
89
+ consoleUrl: `${bridgeUrl}/console`,
88
90
  tokenConfigured: Boolean(bridgeToken),
89
91
  tokenHeader: bridgeTokenHeader,
90
92
  userId: routeUserId,
@@ -119,6 +121,7 @@ function buildNextSteps(checks) {
119
121
  }
120
122
  if (checks.bridge.ok && !checks.bridge.extensionConnected) {
121
123
  steps.push("Load the extension, open an http/https page, then refresh the target page.")
124
+ steps.push(`Open the optional local console for live status: ${checks.bridge.consoleUrl}.`)
122
125
  }
123
126
  if (!checks.mcpServer.ok) {
124
127
  steps.push(`Restore the MCP server file at ${checks.mcpServer.path}.`)
@@ -134,6 +137,7 @@ function humanSummary(report) {
134
137
  `Yunti Browser Runtime doctor: ${report.ok ? "OK" : "needs attention"}`,
135
138
  `- Node: ${report.checks.node.version} (${report.checks.node.ok ? "ok" : "requires >=22"})`,
136
139
  `- Bridge: ${report.checks.bridge.reachable ? report.checks.bridge.url : "not reachable"}`,
140
+ `- Console: ${report.checks.bridge.reachable ? report.checks.bridge.consoleUrl : "not available until bridge starts"}`,
137
141
  `- Token: ${report.checks.bridge.authRequired ? report.checks.bridge.authorized ? "valid" : "missing or invalid" : "not required for local loopback"}`,
138
142
  `- Sessions: ${report.checks.bridge.visibleSessionCount} visible, active ${report.checks.bridge.activeSessionId || "none"}`,
139
143
  `- Extension: ${report.checks.bridge.extensionConnected ? "connected" : "not detected"}`,
@@ -19,6 +19,7 @@ const allowedFiles = [
19
19
  "background.js",
20
20
  "cdp.js",
21
21
  "content.css",
22
+ "dom-observer.js",
22
23
  "content.js",
23
24
  "network-monitor.js",
24
25
  "manifest.json",
@@ -95,7 +95,7 @@ function humanOutput(payload) {
95
95
  "",
96
96
  `Project root: ${payload.projectRoot}`,
97
97
  `MCP server: ${payload.mcpServerPath}`,
98
- `Bridge port: ${payload.bridge.port}`,
98
+ `Bridge port: ${payload.bridge.port} (started automatically by the MCP server)`,
99
99
  `Token: ${payload.bridge.tokenInstruction}`,
100
100
  `Skill: ${payload.skill.sourcePath}`,
101
101
  payload.ok ? "" : `Unsupported agent "${payload.agent}". Supported: ${payload.supportedAgents.join(", ")}`,
@@ -11,6 +11,7 @@ const requiredExtensionZipFiles = [
11
11
  "background.js",
12
12
  "cdp.js",
13
13
  "content.css",
14
+ "dom-observer.js",
14
15
  "content.js",
15
16
  "network-monitor.js",
16
17
  "manifest.json",
@@ -327,6 +328,7 @@ function checkPackageContents() {
327
328
  "extension/manifest.json",
328
329
  "extension/background.js",
329
330
  "extension/content.js",
331
+ "extension/dom-observer.js",
330
332
  "extension/cdp.js",
331
333
  "extension/session-manager.js",
332
334
  "extension/tool-handlers.js",
@@ -464,6 +466,7 @@ ok = checkVersionConsistency() && ok
464
466
  ok = checkCliSmoke() && ok
465
467
  ok = checkPrintConfigSmoke() && ok
466
468
  ok = checkDoctorSmoke() && ok
469
+ ok = run("npm", ["run", "check:action-results"]) && ok
467
470
  ok = run("npm", ["run", "check"]) && ok
468
471
  ok = run("npm", ["test"]) && ok
469
472
  ok = checkPackageContents() && ok
@@ -11,6 +11,61 @@ For any browser task, first call `yunti_get_tool_usage_hints` unless the user is
11
11
 
12
12
  Then call `yunti_list_browser_targets` to understand the live browser state before choosing a page or tab.
13
13
 
14
+ ## Default Page Operation Contract
15
+
16
+ 1. Call `yunti_get_tool_usage_hints` when tool usage is uncertain.
17
+ 2. Call `yunti_list_browser_targets` and choose the intended `browserSessionId`.
18
+ 3. Call `yunti_observe_page` before page actions.
19
+ 4. Prefer fresh uids for click, hover, fill, select, scroll, type, press, upload, and drag operations.
20
+ 5. After each action, verify by observing again or using snapshot, evaluate, screenshot, network, or console tools.
21
+ 6. For async rendering, validation, navigation, option loading, or infinite scroll, call `yunti_wait_for`, then `yunti_observe_page`, then continue with a fresh uid.
22
+ 7. If a result has `ok: false`, `code`, `recoveryHint`, or `nextStepHint`, follow that guidance before retrying.
23
+ 8. Use selector or coordinate fallback only when fresh uids are unavailable or as an explicit recovery/debugging path.
24
+
25
+ Ask the user before submitting, deleting, approving, purchasing, publishing, uploading sensitive files, changing production data, exposing secrets, or taking an action whose effect cannot be verified from page state.
26
+
27
+ ## Minimal Use Cases
28
+
29
+ ### Click
30
+
31
+ 1. Call `yunti_list_browser_targets` and select the intended `browserSessionId`.
32
+ 2. Call `yunti_observe_page` and choose the target element uid from the fresh observation.
33
+ 3. Call `yunti_click` with `browserSessionId` and `uid`.
34
+ 4. Read `ok`, `code`, `recoverable`, `recoveryHint`, and `nextStepHint`; if recoverable, follow the hint before retrying.
35
+ 5. Call `yunti_observe_page` again or use screenshot/evaluate to verify the expected page change.
36
+
37
+ ### Fill Form
38
+
39
+ 1. Call `yunti_observe_page` and inspect field state: `fillable`, `readOnly`, `disabled`, `fillBlockReason`, `selectedValue`, `selectedText`, and `options[]`.
40
+ 2. Fill editable fields with `yunti_fill` using fresh uids; select options with `yunti_select` using `uid` / `value` or `uid` / `text`.
41
+ 3. For multiple fields, use `yunti_fill_form` when fields are known and inspect per-field `results[]`.
42
+ 4. If async validation or option loading occurs, call `yunti_wait_for`, then `yunti_observe_page`, then continue with fresh uids.
43
+ 5. Before submitting or changing production data, ask the user for confirmation.
44
+
45
+ ### Scroll To Find
46
+
47
+ 1. Call `yunti_observe_page` and inspect `scrollableContainers[]`.
48
+ 2. Prefer `yunti_scroll` with a fresh scrollable container uid instead of document scroll for nested panels.
49
+ 3. After scrolling, call `yunti_observe_page` and search the new `textTree` / `elements` for the target.
50
+ 4. If `moved: false`, `partialMovement`, `edgeHint`, or `recoveryHint` appears, follow that guidance before repeating the same scroll.
51
+ 5. When async content loads after scrolling, call `yunti_wait_for`, then `yunti_observe_page`, then continue with a fresh scroll container uid.
52
+
53
+ ### Switch Tab
54
+
55
+ 1. Call `yunti_list_browser_targets` to inspect current tabs and browser targets.
56
+ 2. Choose the intended `browserSessionId` for Yunti page tools.
57
+ 3. Use `yunti_select_page` when switching to a registered Yunti page route.
58
+ 4. Use `yunti_cdp_send_command` with `Target.activateTarget` only for raw `targetId` / `tabId` browser target activation.
59
+ 5. After switching, call `yunti_observe_page` or `yunti_get_page_snapshot` to verify the active page before acting.
60
+
61
+ ### Wait For Async Result
62
+
63
+ 1. Call `yunti_wait_for` with expected `text`, `selector`, or `urlContains`.
64
+ 2. If `ok: true`, call `yunti_observe_page` and use fresh uids for follow-up actions.
65
+ 3. If `code: "WAIT_TIMEOUT"`, observe or inspect current page state before adjusting the condition or retrying.
66
+ 4. Do not reuse pre-wait uids for newly rendered content.
67
+ 5. Verify the final result with observe, snapshot, evaluate, screenshot, network, or console tools.
68
+
14
69
  ## Routing Rules
15
70
 
16
71
  - Treat `yunti_list_browser_targets` as the canonical live browser inventory.
@@ -24,7 +79,9 @@ Then call `yunti_list_browser_targets` to understand the live browser state befo
24
79
  ## Tool Choice
25
80
 
26
81
  - Use `yunti_get_page_snapshot` for lightweight page text, title, URL, selected text, and page state.
27
- - Use `yunti_take_snapshot` before uid-based clicks, fills, or hovers.
82
+ - Once available in the connected runtime, use `yunti_observe_page` as the normal page-operation refresh step, then act by fresh uid and observe again to verify.
83
+ - Observation elements may expose `editable`, `fillable`, `readOnly`, `fillBlockReason`, select `selectedIndex` / `selectedValue` / `selectedText`, and `options[]`; inspect those before filling or selecting when field state matters.
84
+ - Use `yunti_take_snapshot` as the compatibility path before uid-based clicks, fills, or hovers.
28
85
  - Use `yunti_click`, `yunti_fill`, `yunti_hover`, `yunti_press_key`, and `yunti_type_text` for normal page actions.
29
86
  - Use `yunti_take_screenshot` for visual verification.
30
87
  - Use `yunti_list_browser_targets` for tab counts, tab selection, target IDs, and whole-browser awareness.
@@ -56,22 +113,88 @@ Then call `yunti_list_browser_targets` to understand the live browser state befo
56
113
 
57
114
  ## Parameter Rules
58
115
 
59
- - `yunti_click` and `yunti_hover` need `uid`, `selector`, or both `x` and `y`; call `yunti_take_snapshot` first when possible.
116
+ - `yunti_click` and `yunti_hover` need `uid`, `selector`, or both `x` and `y`; prefer a fresh uid from `yunti_observe_page` once available, or call `yunti_take_snapshot` for the current compatibility path.
60
117
  - `yunti_fill` requires `value` plus `uid` or `selector`; coordinate-only fill is not supported.
61
118
  - `yunti_close_page` accepts `browserSessionId`, not raw `tabId` or `targetId`; use `yunti_cdp_send_command` with `Target.closeTarget` for raw browser targets.
62
119
  - `yunti_forget_learning_memory` needs a memory `id`, or `all=true` and `confirmed=true` for deleting everything.
120
+ - `yunti_remember_learning` is for durable patterns and gotchas, not transcripts or raw payloads. It sanitizes likely secrets and PII-like text before storage, but do not intentionally submit secrets, cookies, auth headers, private keys, payment data, or personal contact details.
121
+
122
+ ## Action Recovery
123
+
124
+ - After `yunti_click`, `yunti_fill`, or `yunti_hover`, observe again or read page state before assuming the action succeeded.
125
+ - If a uid is stale or missing, call `yunti_observe_page` again and retry with a fresh uid.
126
+ - If an element may be outside the viewport, use observe scroll hints and `yunti_scroll` before falling back to coordinates.
127
+ - If the page is loading or changing, wait or observe again instead of blindly repeating the same action.
128
+ - For async UI transitions, use `yunti_wait_for` for expected text, selector, or page state, then call `yunti_observe_page` and continue with a fresh uid instead of reusing an old target.
129
+ - Read `yunti_wait_for` structured fields when present: `ok: true` means the condition matched; `code: "WAIT_TIMEOUT"` means observe or adjust the condition before repeating the same wait.
130
+ - If the target tab is uncertain, call `yunti_list_browser_targets` and continue with the intended `browserSessionId`.
131
+
132
+ ## Action Results
133
+
134
+ - Current action results may use compatibility fields such as `clicked`, `hovered`, `filled`, `selected`, `scrolled`, `typed`, `pressed`, `uploaded`, `dragged`, aggregate counts like `failed`, per-field `results`, wait fields like `found` / `waitedMs`, `uid`, `selector`, coordinates, `method`, `valueLength`, `before`, `after`, and `browserSessionId`.
135
+ - Structured P6.2 fields are additive when present: prefer `action`, `target`, `ok`, `recoverable`, and `nextStepHint`, while still reading existing compatibility fields.
136
+ - Treat action results as execution evidence, then verify page state when the task depends on the result.
137
+ - Do not require agents to abandon existing result fields while structured action results are being introduced.
138
+
139
+ ## Wait Guidance
140
+
141
+ - Use `yunti_wait_for` when async rendering, navigation, validation, dynamic select options, or infinite-scroll content needs time to appear.
142
+ - Provide at least one of `text`, `selector`, or `urlContains`; set `timeoutMs` only when the default is not appropriate.
143
+ - Successful waits preserve compatibility fields such as `found`, `text`, `selector`, `condition`, `value`, `waitedMs`, and `browserSessionId`, and may include `action: "wait_for"`, `target`, `ok: true`, `recoverable: false`, and `nextStepHint`.
144
+ - Timeout waits return `found: false`, `ok: false`, `recoverable: true`, `code: "WAIT_TIMEOUT"`, `recoveryHint`, and `nextStepHint`.
145
+ - After a successful wait, call `yunti_observe_page` again and use fresh uids for newly rendered elements. After a timeout, observe or inspect current page state before changing the wait condition or retrying.
146
+
147
+ ## Fill Guidance
148
+
149
+ - Prefer a fresh `uid` from `yunti_observe_page` or `yunti_take_snapshot` when filling inputs, textareas, selects, or contenteditable targets.
150
+ - Before filling, inspect observation fields such as `fillable`, `readOnly`, `disabled`, `fillBlockReason`, and select `selectedValue` / `selectedText` / `options[]` when available.
151
+ - Uid-targeted contenteditable fills return `method: "contenteditable"` and may include `before` / `after` text length summaries.
152
+ - Treat contenteditable fill results as dispatch evidence, then verify with observe, snapshot, evaluate `textContent`, or a page-specific assertion when exact editor state matters.
153
+ - Selector-based fill remains compatible and may return content-script-shaped fields such as `element` or `valueLength`.
154
+ - Uid and selector fill failures may return structured `filled: false` diagnostics with `code`, `ok: false`, `recoverable: true`, `recoveryHint`, and `nextStepHint`; follow `recoveryHint.nextAction` / `decision` before repeating the same fill.
155
+ - If a field appears after async rendering or validation, wait with `yunti_wait_for`, observe again, and fill with a fresh editable uid.
156
+ - Non-editable, hidden, disabled, or readonly fill targets may return `code: "TARGET_NOT_EDITABLE"`; inspect the target, wait/unlock the field, or choose a different editable uid/selector before retrying.
157
+ - Uid keyboard/contenteditable fills may return `code: "VALUE_NOT_APPLIED"` when the post-fill value does not remain. Use the length-only diagnostics (`expectedValueLength`, `actualValueLength`) and `recoveryHint.decision` to inspect controlled/masked fields before retrying; raw field values are not echoed in this diagnostic.
158
+ - Select-option fill misses may include `availableValues` / `availableTexts`; inspect those options before retrying with `yunti_fill` or `yunti_select`.
159
+ - `yunti_fill_form` preserves per-field compatibility results and may carry the same structured failure details on failed `results[]` items, including `code`, `recoveryHint`, `availableValues` / `availableTexts`, and `nextStepHint`.
160
+
161
+ ## Scroll Guidance
162
+
163
+ - Prefer a fresh `scrollableContainers[]` uid from `yunti_observe_page` when scrolling nested app panels or sidebars.
164
+ - Uid-targeted scroll resolves the observed container center and reuses the existing coordinate/container scroll path, so coordinate recovery remains compatible.
165
+ - Uid scroll preserves the existing `target` compatibility field and adds `uid`, `method: "uid"`, and `scrollTarget` for structured interpretation.
166
+ - Coordinate scroll results may include `coordinateTarget`, `scrollContainerFound`, `coordinateScrollFallback: "document"`, and `coordinateFallbackHint`; when document fallback appears, observe again and prefer a fresh `scrollableContainers[]` uid for nested panels.
167
+ - When `before` / `after` positions are comparable, scroll results include `moved`; if positions do not change, the result reports `code: "NO_SCROLL_MOVEMENT"`, `ok: false`, `recoverable: true`, directional `edgeHint` values, and a structured `recoveryHint` with `nextAction` / `recommendedTools`, machine-readable `decision`, plus `suggestedRetry` when an opposite delta can be derived.
168
+ - If scroll moves less than requested, results may keep `ok: true` and include `partialMovement` with requested/actual deltas, axes, `edgeHint`, `decision: "observe-before-continuing-scroll"`, and `nextAction: "observe-again"`; observe again before repeating the same scroll.
169
+ - Uid scroll failures may return structured `scrolled: false` diagnostics with `code` such as `UID_NOT_FOUND` or `UID_COORDINATES_UNAVAILABLE`, plus `recoveryHint.decision: "refresh-scrollable-container-uid-before-retry"`; refresh observation and choose a fresh `scrollableContainers[]` uid before retrying.
170
+ - After scrolling, observe again and compare document or container `before` / `after` positions before assuming the needed element is visible. Stop repeating the same scroll when `moved: false` appears; use `recoveryHint`, `decision`, `edgeHint`, and `suggestedRetry` to choose a different container, direction, or recovery path.
171
+ - If scrolling depends on newly loaded content, use `yunti_wait_for`, observe again, and choose a fresh `scrollableContainers[]` uid before continuing.
172
+
173
+ ## Select Guidance
174
+
175
+ - Current `yunti_select` runtime behavior supports selector/value, uid/value, and uid/visible text.
176
+ - Use `text` with a fresh uid when the user-facing option label is clearer than the option value; selector path remains selector/value compatible.
177
+ - Uid and selector option misses, disabled options, plus non-select targets return structured `selected: false` diagnostics with `code`, `matchMode`, `targetOption`, and `recoveryHint`; option misses include `availableValues` / `availableTexts` when available, and disabled-option failures can include `disabledValue` / `disabledText`.
178
+ - Before retrying a failed select, inspect available options with observe, snapshot, evaluate, a stable selector, or `recoveryHint.decision` instead of blindly repeating it.
179
+ - If select options are populated asynchronously, wait with `yunti_wait_for`, observe again, and select with a fresh uid/value or uid/text.
63
180
 
64
181
  ## Safety
65
182
 
66
183
  - Read-only inspection is allowed by default.
67
184
  - Before submitting forms, deleting data, uploading sensitive files, approving workflows, making purchases, or changing production data, ask the user for explicit confirmation.
68
185
  - Do not expose raw cookies, passwords, authorization headers, or token-like values.
186
+ - Prefer sanitized network and console diagnostics before raw CDP events.
187
+ - Treat `yunti_get_cdp_events` as raw low-level diagnostics. Filter by `method`, keep `limit` small, clear it after debugging with `yunti_clear_cdp_events`, and do not copy raw CDP payloads into chat, docs, or learning memory.
188
+ - Use `yunti_observe_page` with `redaction: "strict"` when a page may contain personal information. Strict DOM redaction hides likely email, phone, Luhn-valid payment-card-like values, address-like text, page titles, labels, names, visible text, placeholders, values, and select option text.
189
+ - Keep `redaction: "off"` only for explicit local debugging.
69
190
  - If a tool output appears to include sensitive data, summarize only the safe parts.
191
+ - DOM observation redaction does not mean screenshots are redacted; treat screenshots as visible page pixels. Use screenshots only when visual proof is needed, prefer viewport captures when enough, and summarize safe visual findings instead of storing raw images in learning memory.
70
192
 
71
193
  ## Recovery
72
194
 
73
195
  - No connected tab: ask the user to open a page, load the extension, or refresh the page.
74
196
  - Stale session: call `yunti_list_browser_targets` and use the latest `browserSessionId`.
197
+ - Stale or missing page uid: observe again once `yunti_observe_page` is available, or take a fresh snapshot for compatibility workflows.
75
198
  - Wrong tab: use `yunti_list_browser_targets` to find the intended tab, then route CDP with that tab's `tabId` or `targetId`.
76
199
  - Parameter uncertainty: call `yunti_get_tool_usage_hints` with the specific tool name.
77
200
  - Missing memory id: call `yunti_get_learning_memory` first, then retry with a returned `id`.