惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
云风的 BLOG
云风的 BLOG
小众软件
小众软件
V
V2EX
博客园 - Franky
博客园 - 司徒正美
Apple Machine Learning Research
Apple Machine Learning Research
量子位
博客园 - 【当耐特】
雷峰网
雷峰网
WordPress大学
WordPress大学
Jina AI
Jina AI
Google DeepMind News
Google DeepMind News
N
Netflix TechBlog - Medium
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
阮一峰的网络日志
阮一峰的网络日志
IT之家
IT之家
Blog — PlanetScale
Blog — PlanetScale
Hugging Face - Blog
Hugging Face - Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
V
Visual Studio Blog
Microsoft Security Blog
Microsoft Security Blog

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
feat(browser): add doctor and richer inspection helpers ·...
steipete · 2026-04-25 · via Recent Commits to openclaw:main

@@ -1,4 +1,5 @@

11

import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";

2+

import type { Page } from "playwright-core";

23

import type { SsrFPolicy } from "../infra/net/ssrf.js";

34

import { type AriaSnapshotNode, formatAriaSnapshot, type RawAXNode } from "./cdp.js";

45

import { assertBrowserNavigationAllowed, withBrowserNavigationPolicy } from "./navigation-guard.js";

@@ -19,6 +20,46 @@ import {

1920

} from "./pw-session.js";

2021

import { withPageScopedCdpClient } from "./pw-session.page-cdp.js";

212223+

type SnapshotUrlEntry = {

24+

text: string;

25+

url: string;

26+

};

27+28+

async function collectSnapshotUrls(page: Page): Promise<SnapshotUrlEntry[]> {

29+

const urls = await page

30+

.evaluate(() => {

31+

const seen = new Set<string>();

32+

const out: SnapshotUrlEntry[] = [];

33+

for (const anchor of Array.from(document.querySelectorAll("a[href]"))) {

34+

const href = anchor instanceof HTMLAnchorElement ? anchor.href : "";

35+

if (!href || seen.has(href)) {

36+

continue;

37+

}

38+

const text =

39+

(anchor.textContent || anchor.getAttribute("aria-label") || "")

40+

.replace(/\s+/g, " ")

41+

.trim()

42+

.slice(0, 120) || href;

43+

seen.add(href);

44+

out.push({ text, url: href });

45+

if (out.length >= 100) {

46+

break;

47+

}

48+

}

49+

return out;

50+

})

51+

.catch(() => []);

52+

return Array.isArray(urls) ? urls : [];

53+

}

54+55+

function appendSnapshotUrls(snapshot: string, urls: SnapshotUrlEntry[]): string {

56+

if (urls.length === 0) {

57+

return snapshot;

58+

}

59+

const lines = urls.map((entry, index) => `${index + 1}. ${entry.text} -> ${entry.url}`);

60+

return `${snapshot}\n\nLinks:\n${lines.join("\n")}`;

61+

}

62+2263

export async function snapshotAriaViaPlaywright(opts: {

2364

cdpUrl: string;

2465

targetId?: string;

@@ -62,6 +103,7 @@ export async function snapshotAiViaPlaywright(opts: {

62103

targetId?: string;

63104

timeoutMs?: number;

64105

maxChars?: number;

106+

urls?: boolean;

65107

ssrfPolicy?: SsrFPolicy;

66108

}): Promise<{ snapshot: string; truncated?: boolean; refs: RoleRefMap }> {

67109

const page = await getPageForTargetId({

@@ -83,6 +125,9 @@ export async function snapshotAiViaPlaywright(opts: {

83125

mode: "ai",

84126

timeout: Math.max(500, Math.min(60_000, Math.floor(opts.timeoutMs ?? 5000))),

85127

});

128+

if (opts.urls) {

129+

snapshot = appendSnapshotUrls(snapshot, await collectSnapshotUrls(page));

130+

}

86131

const maxChars = opts.maxChars;

87132

const limit =

88133

typeof maxChars === "number" && Number.isFinite(maxChars) && maxChars > 0

@@ -112,6 +157,7 @@ export async function snapshotRoleViaPlaywright(opts: {

112157

frameSelector?: string;

113158

refsMode?: "role" | "aria";

114159

options?: RoleSnapshotOptions;

160+

urls?: boolean;

115161

ssrfPolicy?: SsrFPolicy;

116162

}): Promise<{

117163

snapshot: string;

@@ -142,6 +188,9 @@ export async function snapshotRoleViaPlaywright(opts: {

142188

timeout: 5000,

143189

});

144190

const built = buildRoleSnapshotFromAiSnapshot(snapshot, opts.options);

191+

const snapshotWithUrls = opts.urls

192+

? appendSnapshotUrls(built.snapshot, await collectSnapshotUrls(page))

193+

: built.snapshot;

145194

storeRoleRefsForTarget({

146195

page,

147196

cdpUrl: opts.cdpUrl,

@@ -150,9 +199,9 @@ export async function snapshotRoleViaPlaywright(opts: {

150199

mode: "aria",

151200

});

152201

return {

153-

snapshot: built.snapshot,

202+

snapshot: snapshotWithUrls,

154203

refs: built.refs,

155-

stats: getRoleSnapshotStats(built.snapshot, built.refs),

204+

stats: getRoleSnapshotStats(snapshotWithUrls, built.refs),

156205

};

157206

}

158207

@@ -168,6 +217,9 @@ export async function snapshotRoleViaPlaywright(opts: {

168217169218

const ariaSnapshot = await locator.ariaSnapshot();

170219

const built = buildRoleSnapshotFromAriaSnapshot(ariaSnapshot ?? "", opts.options);

220+

const snapshotWithUrls = opts.urls

221+

? appendSnapshotUrls(built.snapshot, await collectSnapshotUrls(page))

222+

: built.snapshot;

171223

storeRoleRefsForTarget({

172224

page,

173225

cdpUrl: opts.cdpUrl,

@@ -177,9 +229,9 @@ export async function snapshotRoleViaPlaywright(opts: {

177229

mode: "role",

178230

});

179231

return {

180-

snapshot: built.snapshot,

232+

snapshot: snapshotWithUrls,

181233

refs: built.refs,

182-

stats: getRoleSnapshotStats(built.snapshot, built.refs),

234+

stats: getRoleSnapshotStats(snapshotWithUrls, built.refs),

183235

};

184236

}

185237