traw 0.2.1 → 0.2.3
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 +1 -1
- package/src/agent/prompts.ts +28 -31
- package/src/browser/controller.ts +69 -80
package/package.json
CHANGED
package/src/agent/prompts.ts
CHANGED
|
@@ -1,34 +1,31 @@
|
|
|
1
|
-
export const systemPrompt = `You
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
OUTPUT (JSON only, no markdown wrapper):
|
|
31
|
-
{"thought":"English reasoning here","action":{"type":"click","index":0}}`
|
|
1
|
+
export const systemPrompt = `You are Traw - browser agent. You receive page as XML.
|
|
2
|
+
|
|
3
|
+
INPUT FORMAT:
|
|
4
|
+
<page>
|
|
5
|
+
<h1>Page Title</h1>
|
|
6
|
+
<a id="0" href="...">Link</a>
|
|
7
|
+
<input id="1" type="text" value="current"/>
|
|
8
|
+
<button id="2" disabled="true">Submit</button>
|
|
9
|
+
</page>
|
|
10
|
+
|
|
11
|
+
Use "id" attribute to target interactive elements.
|
|
12
|
+
Elements with disabled="true" cannot be clicked.
|
|
13
|
+
|
|
14
|
+
LANGUAGE:
|
|
15
|
+
- thought: English
|
|
16
|
+
- done reason: Same language as user query
|
|
17
|
+
|
|
18
|
+
ACTIONS:
|
|
19
|
+
- click: {"type":"click","index":N}
|
|
20
|
+
- type: {"type":"type","index":N,"text":"..."}
|
|
21
|
+
- scroll: {"type":"scroll","direction":"down"|"up"}
|
|
22
|
+
- goto: {"type":"goto","text":"url"}
|
|
23
|
+
- wait: {"type":"wait"}
|
|
24
|
+
- back: {"type":"back"}
|
|
25
|
+
- done: {"type":"done","reason":"answer"}
|
|
26
|
+
|
|
27
|
+
OUTPUT JSON:
|
|
28
|
+
{"thought":"...","action":{"type":"click","index":0}}`
|
|
32
29
|
|
|
33
30
|
export const planningPrompt = `Create short numbered plan to accomplish goal via browser. Start from DuckDuckGo search.
|
|
34
31
|
|
|
@@ -65,89 +65,78 @@ export class BrowserController {
|
|
|
65
65
|
const url = this.page.url()
|
|
66
66
|
const title = await this.page.title()
|
|
67
67
|
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
if (!text || text.length < 3) return
|
|
78
|
-
|
|
79
|
-
const tag = el.tagName.toLowerCase()
|
|
80
|
-
const prefix = tag.startsWith("h") ? `[${tag}]` : ""
|
|
81
|
-
texts.push(`${prefix} ${text}`)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const unique = [...new Set(texts)]
|
|
85
|
-
return unique.join("\n")
|
|
86
|
-
}).catch(() => "")
|
|
87
|
-
|
|
88
|
-
const elements = await this.page.evaluate(() => {
|
|
89
|
-
const items: string[] = []
|
|
90
|
-
const selector = 'a[href], button, input, textarea, select, [role="button"], [onclick]'
|
|
91
|
-
|
|
92
|
-
document.querySelectorAll(selector).forEach((el) => {
|
|
93
|
-
const node = el as HTMLElement
|
|
94
|
-
if (node.offsetParent === null) return
|
|
95
|
-
|
|
68
|
+
const xml = await this.page.evaluate(() => {
|
|
69
|
+
const esc = (s: string) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """)
|
|
70
|
+
const out: string[] = ["<page>"]
|
|
71
|
+
let idx = 0
|
|
72
|
+
|
|
73
|
+
document.querySelectorAll("[data-idx]").forEach(el => el.removeAttribute("data-idx"))
|
|
74
|
+
|
|
75
|
+
const walk = (node: Element, depth: number) => {
|
|
76
|
+
const el = node as HTMLElement
|
|
96
77
|
const tag = el.tagName.toLowerCase()
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
78
|
+
const indent = " ".repeat(depth)
|
|
79
|
+
|
|
80
|
+
const skipTags = ["script", "style", "noscript", "svg", "path", "meta", "link", "br", "hr"]
|
|
81
|
+
if (skipTags.includes(tag)) return
|
|
82
|
+
|
|
83
|
+
const interactiveTags = ["a", "button", "input", "textarea", "select"]
|
|
84
|
+
const hasRole = el.getAttribute("role")
|
|
85
|
+
const hasOnclick = el.hasAttribute("onclick")
|
|
86
|
+
const isInteractive = interactiveTags.includes(tag) || hasRole || hasOnclick
|
|
87
|
+
|
|
88
|
+
if (isInteractive) {
|
|
89
|
+
el.setAttribute("data-idx", String(idx))
|
|
90
|
+
|
|
91
|
+
const attrs: string[] = [`id="${idx}"`]
|
|
92
|
+
|
|
93
|
+
const type = (el as HTMLInputElement).type
|
|
94
|
+
if (type) attrs.push(`type="${type}"`)
|
|
95
|
+
|
|
96
|
+
const href = (el as HTMLAnchorElement).href
|
|
97
|
+
if (href && tag === "a") attrs.push(`href="${esc(href.slice(0, 80))}"`)
|
|
98
|
+
|
|
99
|
+
const val = (el as HTMLInputElement).value
|
|
100
|
+
if (val) attrs.push(`value="${esc(val)}"`)
|
|
101
|
+
|
|
102
|
+
if ((el as any).disabled) attrs.push(`disabled="true"`)
|
|
103
|
+
if ((el as any).checked) attrs.push(`checked="true"`)
|
|
104
|
+
if ((el as any).readOnly) attrs.push(`readonly="true"`)
|
|
105
|
+
if ((el as any).required) attrs.push(`required="true"`)
|
|
106
|
+
if (el.getAttribute("aria-expanded")) attrs.push(`expanded="${el.getAttribute("aria-expanded")}"`)
|
|
107
|
+
if (el.getAttribute("aria-selected") === "true") attrs.push(`selected="true"`)
|
|
108
|
+
|
|
109
|
+
const text = el.textContent?.trim() || ""
|
|
110
|
+
const ariaLabel = el.getAttribute("aria-label")
|
|
111
|
+
const placeholder = (el as HTMLInputElement).placeholder
|
|
112
|
+
const label = esc(ariaLabel || text || placeholder || "")
|
|
113
|
+
|
|
114
|
+
out.push(`${indent}<${tag} ${attrs.join(" ")}>${label}</${tag}>`)
|
|
115
|
+
idx++
|
|
133
116
|
} else {
|
|
134
|
-
|
|
117
|
+
const textTags = ["h1", "h2", "h3", "h4", "h5", "h6", "p", "li", "td", "th", "label"]
|
|
118
|
+
if (textTags.includes(tag)) {
|
|
119
|
+
const directText = Array.from(el.childNodes)
|
|
120
|
+
.filter(n => n.nodeType === 3)
|
|
121
|
+
.map(n => n.textContent?.trim())
|
|
122
|
+
.join(" ")
|
|
123
|
+
.trim()
|
|
124
|
+
|
|
125
|
+
if (directText.length > 2) {
|
|
126
|
+
out.push(`${indent}<${tag}>${esc(directText)}</${tag}>`)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
135
129
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
url,
|
|
148
|
-
title,
|
|
149
|
-
text: combined,
|
|
150
|
-
}
|
|
130
|
+
|
|
131
|
+
Array.from(el.children).forEach(child => walk(child, depth + 1))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
walk(document.body, 0)
|
|
135
|
+
out.push("</page>")
|
|
136
|
+
return out.join("\n")
|
|
137
|
+
}).catch(() => "<page></page>")
|
|
138
|
+
|
|
139
|
+
return { url, title, text: xml }
|
|
151
140
|
}
|
|
152
141
|
|
|
153
142
|
async execute(action: Action): Promise<string> {
|