Skip to content

Commit f9a3f2d

Browse files
author
SqlRush
committed
Render web select option labels
1 parent 7d5d59c commit f9a3f2d

5 files changed

Lines changed: 43 additions & 14 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -831,7 +831,7 @@ M7 补充:prompt history `LogEntry` 读取现在接受 `sessionID`/`session`/`
831831

832832
本轮补充:`WebFetch` HTML-to-text rendering 现在会保留 anchor `href` 作为链接上下文,并把 `img``alt`/`title`/`aria-label``src` 渲染成可见图片说明;prompt-focused excerpt 可以命中图片说明文本,同时避免重复 URL 链接文本和 `javascript:` href。
833833

834-
本轮补充:`WebFetch` HTML-to-text rendering 现在会保留可见表单控件文本,包括 `input` placeholder/value/alt/accessibility label、icon-only `button` label、空 `textarea` placeholder,以及 `select` 的 selected/default option;hidden/password/file value 和未选中的 select option 会跳过,避免把非可见 secret 或菜单备选项当页面文本暴露。
834+
本轮补充:`WebFetch` HTML-to-text rendering 现在会保留可见表单控件文本,包括 `input` placeholder/value/alt/accessibility label、icon-only `button` label、空 `textarea` placeholder,以及 `select` 的 selected/default option`option label``select multiple` 已选列表;hidden/password/file value、option value 和未选中的 select option 会跳过,避免把非可见 secret 或菜单备选项当页面文本暴露。
835835

836836
本轮补充:`WebFetch` GET 会记录 redirect 后的 `final_url`,HTML rendering 会按 final URL 解析相对 anchor/image URL,确保重定向页面中的相对链接和图片说明指向浏览器实际可见的目标地址。
837837

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ test/parity/ # golden tests against TS/official behavior
193193
- PowerShell 初版已落地,覆盖 command/timeout/description/run_in_background 输入校验、`pwsh`/`powershell` 前台执行、后台启动、`PowerShellOutput` 输出读取、`KillPowerShell` 取消、stdout/stderr/exit code/timeout/cancel 结构化结果、动态 read-only/concurrency-safe/destructive 分类、常见 mutating alias canonicalization、文件读取类命令的基础相对路径 guard、path-free `git`/`git.exe`/`git.cmd` 等外部 Git 命令复用 Bash Git safety 分类、Docker `ps`/`images`/`logs`/`inspect` 只读外部命令分类和变量/未知 flag guard、只读 PowerShell cmdlet safe-flag allowlist 和路径参数 guard、数据转换/对象检查/系统信息类 cmdlet 只读 allowlist、pipeline-tail 格式化/对象选择类 cmdlet 只读 allowlist 和变量/hashtable/scriptblock guard、网络/事件/CIM 元数据类 cmdlet 只读 allowlist 和远程/XML/hashtable 风险参数排除、native/external 原生命令只读 allowlist(`ipconfig`/`netstat`/`systeminfo`/`tasklist`/`where.exe`/`hostname`/`whoami`/`route print`/`file`/`findstr`/`dotnet` 等)和写操作形态拒绝、前台/后台输出 tool-result 截断/落盘测试覆盖、缺失可执行文件结构化错误、默认工具注册和基本跨平台进程配置;完整 parser、完整权限/path validation、后台生命周期 edge cases、官方前台截断 golden、session 记录和官方 golden 仍需继续补。
194194
- 本轮补充:WebFetch HEAD preflight 现在记录 `Content-Disposition`,并会通过 attachment filename 的常见二进制扩展名(如 PDF/image/archive/office/media)跳过 GET,覆盖服务端缺失 `Content-Type` 但通过下载文件名暴露类型的二进制响应。
195195
- 本轮补充:WebFetch HTML-to-text rendering 现在会保留 anchor `href` 作为链接上下文,并把 `img``alt`/`title`/`aria-label``src`、首个可用 `img srcset` 候选、常见 lazy image `data-src`/`data-srcset` 候选或 `<picture><source srcset>` 候选渲染成可见图片说明;prompt-focused excerpt 可以命中图片说明文本,同时避免重复 URL 链接文本和 `javascript:` href、`data:` 图片占位。
196-
- 本轮补充:WebFetch HTML-to-text rendering 现在会保留可见表单控件文本,包括 `input` placeholder/value/alt/accessibility label、icon-only `button` label、空 `textarea` placeholder,以及 `select` 的 selected/default option;hidden/password/file value 和未选中的 select option 会跳过,避免把非可见 secret 或菜单备选项当页面文本暴露。
196+
- 本轮补充:WebFetch HTML-to-text rendering 现在会保留可见表单控件文本,包括 `input` placeholder/value/alt/accessibility label、icon-only `button` label、空 `textarea` placeholder,以及 `select` 的 selected/default option`option label``select multiple` 已选列表;hidden/password/file value、option value 和未选中的 select option 会跳过,避免把非可见 secret 或菜单备选项当页面文本暴露。
197197
- 本轮补充:WebFetch GET 会记录 redirect 后的 `final_url`,HTML rendering 会按 final URL 或文档首个有效 `<base href>` 解析相对 anchor/image URL,确保重定向页面中的相对链接和图片说明指向浏览器实际可见的目标地址。
198198
- 本轮补充:WebFetch 现在按官方 cross-host redirect 语义处理跨 host 跳转,HEAD preflight 和 GET 都不会自动触达新 host,而是返回包含 original URL、redirect URL 和 status 的 redirect notice;同 host redirect 仍继续跟随并保留 `final_url`
199199
- 本轮补充:WebFetch input schema 现在与官方对齐,`url``prompt` 都是必填字段;既有本地扩展 `timeout``max_bytes`/`maxBytes` 仍保持可选。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ Covered behavior:
145145
- `TodoWrite` initial todo state, including full-list writes, status/priority validation, duplicate-id rejection, one `in_progress` guard, structured result payloads, tool metadata state storage, and session-scoped local persistence/restore.
146146
- `WebFetch` initial URL fetch behavior, including URL/timeout/max-byte validation, HTTP GET, HEAD preflight, metadata/raw `skipWebFetchPreflight` skip-preflight, binary preflight GET skipping from content type or attachment filename, text/binary detection, truncation, non-2xx error marking, structured result payloads, HTML-to-text rendering, prompt-focused excerpts, prompt phrase scoring/metadata, and `WebFetch(domain:...)` permission-rule adaptation.
147147
- `WebFetch` HTML-to-text rendering now preserves anchor `href` values as link context and emits visible image text from `img` `alt`/`title`/`aria-label` plus `src`, the first usable `img srcset` candidate, common lazy image `data-src`/`data-srcset` candidates, or a `<picture><source srcset>` candidate, allowing prompt-focused excerpts to match image descriptions while avoiding duplicate URL link text, unsafe `javascript:`/`data:`/`blob:`/`vbscript:` hrefs, and `data:` image placeholders.
148-
- `WebFetch` HTML-to-text rendering now preserves visible form-control text from `input` placeholders, values, alt text, and accessibility labels, icon-only `button` labels, empty `textarea` placeholders, and selected/default `select` options while skipping hidden, password, and file values so prompt-focused excerpts can match form actions without leaking non-visible secrets or non-selected option text.
148+
- `WebFetch` HTML-to-text rendering now preserves visible form-control text from `input` placeholders, values, alt text, and accessibility labels, icon-only `button` labels, empty `textarea` placeholders, and selected/default `select` options including `option label` attributes and `select multiple` selected option lists, while skipping hidden, password, file, option-value, and non-selected option text so prompt-focused excerpts can match form actions without leaking non-visible secrets.
149149
- `WebFetch` GET records the redirect-resolved `final_url`, and HTML rendering resolves relative anchor/image URLs against that final URL or the document's first valid `<base href>` so redirected pages expose browser-visible link and image targets.
150150
- `WebFetch` text bodies now decode common web charsets from BOM, `Content-Type`, or HTML `<meta charset>`/`http-equiv` declarations, including UTF-8/UTF-16LE/UTF-16BE, Latin-1, and Windows-1252, and structured results expose the normalized `charset`.
151151
- `WebSearch` initial HTML/JSON-search adapter, including query/max-result/timeout/domain-filter validation, injectable search endpoint, DuckDuckGo HTML link parsing, HTML JSON-LD ItemList extraction, DuckDuckGo subdomain and common `/url`/`redirect`/`out` result redirect unwrapping, common JSON result shapes plus nested backend wrappers such as `web`/`response`/`hits`/`documents`, DuckDuckGo result snippet extraction, domain allow/block filtering, structured result payloads, and query-based permission-rule matching.

internal/tools/web/web_fetch.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ func stripHTMLWebFetchTags(body string, baseURL string) string {
560560
}
561561
} else {
562562
label := firstNonEmptyWebFetchAttr(rawTag, "aria-label", "title")
563-
selects = append(selects, htmlWebFetchSelectControl{Label: label})
563+
selects = append(selects, htmlWebFetchSelectControl{Label: label, Multiple: htmlWebFetchHasAttr(rawTag, "multiple")})
564564
}
565565
i += end + 1
566566
continue
@@ -573,6 +573,7 @@ func stripHTMLWebFetchTags(body string, baseURL string) string {
573573
selects[idx].InOption = true
574574
selects[idx].OptionSelected = htmlWebFetchHasAttr(rawTag, "selected")
575575
selects[idx].OptionText = ""
576+
selects[idx].OptionLabel = firstNonEmptyWebFetchAttr(rawTag, "label")
576577
}
577578
}
578579
i += end + 1
@@ -699,12 +700,15 @@ type htmlWebFetchLabeledControl struct {
699700
}
700701

701702
type htmlWebFetchSelectControl struct {
702-
Label string
703-
FirstOption string
704-
SelectedOption string
705-
InOption bool
706-
OptionSelected bool
707-
OptionText string
703+
Label string
704+
FirstOption string
705+
SelectedOption string
706+
SelectedOptions []string
707+
Multiple bool
708+
InOption bool
709+
OptionSelected bool
710+
OptionText string
711+
OptionLabel string
708712
}
709713

710714
func appendHTMLWebFetchAnchorHref(b *strings.Builder, anchors []htmlWebFetchAnchor) ([]htmlWebFetchAnchor, bool) {
@@ -760,23 +764,37 @@ func finishHTMLWebFetchSelectOption(control htmlWebFetchSelectControl) htmlWebFe
760764
if !control.InOption {
761765
return control
762766
}
763-
text := strings.Join(strings.Fields(control.OptionText), " ")
767+
text := strings.TrimSpace(control.OptionLabel)
768+
if text == "" {
769+
text = strings.Join(strings.Fields(control.OptionText), " ")
770+
}
764771
if text != "" {
765772
if control.FirstOption == "" {
766773
control.FirstOption = text
767774
}
768775
if control.OptionSelected {
769-
control.SelectedOption = text
776+
if control.Multiple {
777+
control.SelectedOptions = append(control.SelectedOptions, text)
778+
} else {
779+
control.SelectedOption = text
780+
}
770781
}
771782
}
772783
control.InOption = false
773784
control.OptionSelected = false
774785
control.OptionText = ""
786+
control.OptionLabel = ""
775787
return control
776788
}
777789

778790
func appendHTMLWebFetchSelectText(b *strings.Builder, control htmlWebFetchSelectControl) {
779-
text := strings.TrimSpace(control.SelectedOption)
791+
text := ""
792+
if control.Multiple && len(control.SelectedOptions) > 0 {
793+
text = strings.Join(control.SelectedOptions, ", ")
794+
}
795+
if text == "" {
796+
text = strings.TrimSpace(control.SelectedOption)
797+
}
780798
if text == "" {
781799
text = strings.TrimSpace(control.FirstOption)
782800
}

internal/tools/web/web_fetch_test.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,15 @@ func TestWebFetchHTMLRenderingPreservesVisibleFormControls(t *testing.T) {
330330
<option>US East</option>
331331
<option>EU West</option>
332332
</select>
333+
<select aria-label="Tier">
334+
<option label="Starter tier" value="starter-secret"></option>
335+
<option label="Enterprise tier" selected value="enterprise-secret"></option>
336+
</select>
337+
<select aria-label="Datacenters" multiple>
338+
<option selected>AMS</option>
339+
<option>LHR</option>
340+
<option selected>IAD</option>
341+
</select>
333342
<input type="hidden" value="csrf-secret">
334343
<input type="password" value="super-secret-password">
335344
<input type="file" value="/private/report.pdf">
@@ -364,6 +373,8 @@ func TestWebFetchHTMLRenderingPreservesVisibleFormControls(t *testing.T) {
364373
"Existing note",
365374
"Input: Environment: Production",
366375
"Input: Region: US East",
376+
"Input: Tier: Enterprise tier",
377+
"Input: Datacenters: AMS, IAD",
367378
} {
368379
if !strings.Contains(rendered, want) {
369380
t.Fatalf("rendered body missing %q: %#v", want, rendered)
@@ -372,7 +383,7 @@ func TestWebFetchHTMLRenderingPreservesVisibleFormControls(t *testing.T) {
372383
if strings.Contains(rendered, "Visible label should not duplicate") {
373384
t.Fatalf("rendered body duplicated non-empty button label: %#v", rendered)
374385
}
375-
for _, leaked := range []string{"Draft placeholder", "Development", "Staging", "EU West", "csrf-secret", "super-secret-password", "/private/report.pdf"} {
386+
for _, leaked := range []string{"Draft placeholder", "Development", "Staging", "EU West", "Starter tier", "starter-secret", "enterprise-secret", "LHR", "csrf-secret", "super-secret-password", "/private/report.pdf"} {
376387
if strings.Contains(rendered, leaked) {
377388
t.Fatalf("rendered body leaked %q: %#v", leaked, rendered)
378389
}

0 commit comments

Comments
 (0)