dsh-inner-browser-use 0.34.28 → 0.34.96

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.
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The Flash path, measured without the network: a page served from this
4
+ * process, its movie from the repo's own fixture, and the same bridge a site
5
+ * would be driven through. A site being slow or rate-limiting for a minute is
6
+ * not a plugin regression, and this check can tell the two apart.
7
+ *
8
+ * node scripts/check-flash-local.mjs
9
+ *
10
+ * Asserts the movie painted, frames reach a viewer, and a press, a release and
11
+ * a key each reach the plugin in well under the time a person would notice.
12
+ * Own profile, own state directory, closed afterwards; nothing appears on the
13
+ * desktop.
14
+ */
15
+ import { createServer } from 'node:http'
16
+ import { createHash } from 'node:crypto'
17
+ import { mkdtempSync, readFileSync } from 'node:fs'
18
+ import { tmpdir } from 'node:os'
19
+ import { dirname, join } from 'node:path'
20
+ import { fileURLToPath } from 'node:url'
21
+
22
+ const here = dirname(fileURLToPath(import.meta.url))
23
+ /* The looping movie, because a still one paints once and says nothing about
24
+ whether frames follow a moving picture. */
25
+ const movie = readFileSync(join(here, '..', 'test', 'fixtures', 'loop.swf'))
26
+ const page = `<!doctype html>
27
+ <html><head><meta charset="utf-8"><title>Flash fixture</title></head>
28
+ <body style="margin:0;background:#101820">
29
+ <embed src="/tiny.swf" type="application/x-shockwave-flash" width="400" height="300">
30
+ </body></html>`
31
+
32
+ const server = createServer((request, reply) => {
33
+ if (request.url === '/tiny.swf') {
34
+ reply.writeHead(200, { 'content-type': 'application/x-shockwave-flash', 'content-length': movie.length })
35
+ reply.end(movie)
36
+ return
37
+ }
38
+ reply.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })
39
+ reply.end(page)
40
+ })
41
+ await new Promise((ready) => server.listen(0, '127.0.0.1', ready))
42
+ const url = `http://127.0.0.1:${server.address().port}/`
43
+
44
+ process.env.DSH_INNER_BROWSER_STATE_DIR = mkdtempSync(join(tmpdir(), 'dsh-flash-local-'))
45
+ const { Sidecar } = await import('../sidecar.js')
46
+
47
+ /** @param {number} ms - how long. */
48
+ const wait = (ms) => new Promise((settle) => setTimeout(settle, ms))
49
+ const hash = (buffer) => createHash('sha1').update(Buffer.from(buffer ?? '')).digest('hex').slice(0, 12)
50
+
51
+ const sidecar = new Sidecar({ profile: 'flash-local', headless: false, flash: true })
52
+ let ok = false
53
+ try {
54
+ await sidecar.launch('about:blank')
55
+ const seen = []
56
+ const detach = await sidecar.watch((frame) => {
57
+ seen.push({ at: Date.now(), shape: hash(frame.jpeg), kb: Math.round(String(frame.jpeg ?? '').length * 0.75 / 1024) })
58
+ })
59
+ const navigatedAt = Date.now()
60
+ await sidecar.page.navigate(url).catch(() => {})
61
+ for (let i = 0; i < 60 && seen.length === 0; i += 1) await wait(250)
62
+ const firstFrameMs = Date.now() - navigatedAt
63
+ await wait(3000)
64
+ detach()
65
+ const gaps = seen.slice(1).map((one, index) => one.at - seen[index].at).sort((a, b) => a - b)
66
+ const fps = gaps.length === 0 ? 0 : Math.round((1000 / (gaps[Math.floor(gaps.length / 2)] || 1000)) * 10) / 10
67
+ const shapes = new Set(seen.map((one) => one.shape))
68
+ const info = await fetch(`http://127.0.0.1:${sidecar.page.flashBridge}/info`, { signal: AbortSignal.timeout(1500) }).then((one) => one.json()).catch(() => ({}))
69
+ const pressedAt = Date.now()
70
+ await sidecar.page.mouse('mousePressed', { x: 640, y: 420 })
71
+ const pressMs = Date.now() - pressedAt
72
+ const releasedAt = Date.now()
73
+ await sidecar.page.mouse('mouseReleased', { x: 640, y: 420 })
74
+ const releaseMs = Date.now() - releasedAt
75
+ const keyAt = Date.now()
76
+ await sidecar.page.key('keyDown', { key: 'a', text: 'a' })
77
+ const keyMs = Date.now() - keyAt
78
+ await sidecar.page.key('keyUp', { key: 'a' })
79
+ await wait(600)
80
+ /* The same endpoint a click uses, asked directly: the plugin took the input
81
+ rather than a path timing out on the way there. */
82
+ const fedReply = await fetch(`http://127.0.0.1:${sidecar.page.flashBridge}/input`, {
83
+ method: 'POST',
84
+ headers: { 'content-type': 'application/json' },
85
+ body: JSON.stringify({ type: 'mouseMove', x: 200, y: 150, button: 'left', clickCount: 0 }),
86
+ signal: AbortSignal.timeout(1500),
87
+ }).catch((error) => ({ status: 0, text: () => Promise.resolve(String(error?.message ?? error)) }))
88
+ const fedStatus = fedReply.status
89
+ const fedText = await fedReply.text().catch(() => '')
90
+ const fed = { ok: fedText.includes('"ok":true') }
91
+ const frameKB = seen.length === 0 ? 0 : Math.round(seen.reduce((sum, one) => sum + one.kb, 0) / seen.length)
92
+ /* The fixture movie is not a game: what is asserted here is that the movie
93
+ painted, that frames reached a viewer, and that input arrived without a
94
+ detour through the debugging protocol. Frame rate on moving pictures is
95
+ measured by check-flash-mirror against a real site. */
96
+ ok = Number(info.painted ?? 0) > 1 && seen.length >= 3 && shapes.size >= 2 && firstFrameMs < 1500 && pressMs < 40 && releaseMs < 40 && keyMs < 40 && fed.ok === true && frameKB > 0
97
+ console.log(JSON.stringify({ url, firstFrameMs, frames: seen.length, fps, shapes: shapes.size, frameKB, painted: info.painted ?? 0, pressMs, releaseMs, keyMs, fedStatus, fedText: fedText.slice(0, 80), fed: fed.ok === true, ok }))
98
+ } catch (error) {
99
+ console.log(JSON.stringify({ ok: false, error: String(error?.message ?? error).slice(0, 160) }))
100
+ } finally {
101
+ await sidecar.shutdown().catch(() => {})
102
+ server.close()
103
+ }
104
+ process.exitCode = ok ? 0 : 1
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Does a Flash page mirror into the column at a usable rate, and do clicks land?
4
+ *
5
+ * node scripts/check-flash-mirror.mjs [url]
6
+ *
7
+ * Counts the frames a viewer really receives, times a press and a release
8
+ * through the page, and reports both next to the frame size. Own profile, own
9
+ * state directory, closed afterwards; nothing is shown on the desktop.
10
+ */
11
+ import { mkdtempSync } from 'node:fs'
12
+ import { tmpdir } from 'node:os'
13
+ import { join } from 'node:path'
14
+
15
+ const url = process.argv[2] ?? 'http://account.100bt.com/game/auth/login.html?gameName=aola&action=startgame&platform=100bt'
16
+ process.env.DSH_INNER_BROWSER_STATE_DIR = mkdtempSync(join(tmpdir(), 'dsh-flash-mirror-'))
17
+
18
+ const { Sidecar } = await import('../sidecar.js')
19
+
20
+ /** @param {number} ms - how long. */
21
+ const wait = (ms) => new Promise((settle) => setTimeout(settle, ms))
22
+ const sidecar = new Sidecar({ profile: 'flash-mirror', headless: false, flash: true })
23
+ let ok = false
24
+ try {
25
+ const started = Date.now()
26
+ await sidecar.launch('about:blank')
27
+ const sizes = []
28
+ const times = []
29
+ const detach = await sidecar.watch((frame) => {
30
+ sizes.push(Math.round(String(frame.jpeg ?? '').length * 0.75 / 1024))
31
+ times.push(Date.now())
32
+ })
33
+ const navigatedAt = Date.now()
34
+ const page = sidecar.page
35
+ await page.navigate(url).catch(() => {})
36
+ for (let i = 0; i < 60 && times.length === 0; i += 1) await wait(250)
37
+ const firstFrame = Date.now() - navigatedAt
38
+ await wait(4000)
39
+ detach()
40
+ const gaps = times.slice(1).map((at, index) => at - times[index]).sort((a, b) => a - b)
41
+ const fps = gaps.length === 0 ? 0 : Math.round((1000 / (gaps[Math.floor(gaps.length / 2)] || 1000)) * 10) / 10
42
+ const pressedAt = Date.now()
43
+ await page.mouse('mousePressed', { x: 640, y: 420 })
44
+ const pressMs = Date.now() - pressedAt
45
+ const releasedAt = Date.now()
46
+ await page.mouse('mouseReleased', { x: 640, y: 420 })
47
+ const releaseMs = Date.now() - releasedAt
48
+ await wait(400)
49
+ const before = await page.picture()
50
+ const keyAt = Date.now()
51
+ await page.key('keyDown', { key: 'a', text: 'a' })
52
+ const keyMs = Date.now() - keyAt
53
+ await page.key('keyUp', { key: 'a' })
54
+ await wait(600)
55
+ const moved = await page.picture()
56
+ ok = fps >= 15 && pressMs < 40 && releaseMs < 40 && keyMs < 40 && moved !== before && sizes.length > 5
57
+ console.log(JSON.stringify({
58
+ firstFrameMs: firstFrame,
59
+ frames: sizes.length,
60
+ fps,
61
+ frameKB: sizes.length === 0 ? 0 : Math.round(sizes.reduce((a, b) => a + b, 0) / sizes.length),
62
+ pressMs,
63
+ releaseMs,
64
+ keyMs,
65
+ responded: moved !== '' && moved !== before,
66
+ ok,
67
+ }))
68
+ } catch (error) {
69
+ console.log(JSON.stringify({ ok: false, error: String(error?.message ?? error).slice(0, 160) }))
70
+ } finally {
71
+ await sidecar.shutdown().catch(() => {})
72
+ }
73
+ process.exitCode = ok ? 0 : 1
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * What a tool call costs on a Flash page, from the model's side.
4
+ *
5
+ * node scripts/check-flash-tools.mjs [url]
6
+ *
7
+ * Runs the real tool definitions against a real Flash runtime and reports time
8
+ * and characters for a click, a view and a type, plus whether the picture moved.
9
+ */
10
+ import { mkdtempSync } from 'node:fs'
11
+ import { tmpdir } from 'node:os'
12
+ import { join } from 'node:path'
13
+
14
+ const url = process.argv[2] ?? 'http://account.100bt.com/game/auth/login.html?gameName=aola&action=startgame&platform=100bt'
15
+ process.env.DSH_INNER_BROWSER_STATE_DIR = mkdtempSync(join(tmpdir(), 'dsh-flash-tools-'))
16
+
17
+ const { Sidecar } = await import('../sidecar.js')
18
+ const { sidecarTools } = await import('../tools.js')
19
+
20
+ const wait = (ms) => new Promise((settle) => setTimeout(settle, ms))
21
+ const sidecar = new Sidecar({ profile: 'flash-tools', headless: false, flash: true })
22
+ const refs = { byChat: new Map() }
23
+ const tools = sidecarTools(sidecar, refs, {})
24
+ const tool = (name) => tools.find((one) => one.name === name)
25
+ let ok = false
26
+ try {
27
+ await sidecar.launch('about:blank')
28
+ await sidecar.watch(() => {})
29
+ await sidecar.page.navigate(url).catch(() => {})
30
+ await wait(9000)
31
+ const said = []
32
+ const timed = async (name, args) => {
33
+ const started = Date.now()
34
+ const value = await tool(name).execute(args, { agent: { id: 'flash-check' } })
35
+ const text = String(value?.text ?? '')
36
+ const record = { tool: name, ms: Date.now() - started, chars: text.length, text: text.slice(0, 120) }
37
+ said.push(record)
38
+ return record
39
+ }
40
+ await timed('browser_view', {})
41
+ /* One call is a sample, five are a cost: a plugin page blocks its main
42
+ thread while it draws, so a single number swings by a hundred ms. */
43
+ const rounds = []
44
+ for (let i = 0; i < 5; i += 1) {
45
+ rounds.push(await timed('browser_click', { x: 640, y: 420 }))
46
+ rounds.push(await timed('browser_type', { text: 'a' }))
47
+ }
48
+ const median = (list) => {
49
+ const sorted = list.map((one) => one.ms).sort((a, b) => a - b)
50
+ return sorted[Math.floor(sorted.length / 2)]
51
+ }
52
+ /* A read of a page just read is mostly the page again: it must cost a line,
53
+ not a snapshot. */
54
+ const viewAgain = await timed('browser_view', {})
55
+ const viewThird = await timed('browser_view', {})
56
+ const clicks = rounds.filter((one) => one.tool === 'browser_click')
57
+ const types = rounds.filter((one) => one.tool === 'browser_type')
58
+ const last = rounds[rounds.length - 1]
59
+ /* A step that repeats the address it read a moment ago is spend with nothing
60
+ to teach, so the budget is per step and tight: one line, not a page. */
61
+ const within = (one) => Number(one?.chars ?? 999) < 300
62
+ /* A page whose own requests fail still answers, but slowly: the numbers to
63
+ gate on are the token ones, and latency is judged only when the page came
64
+ up whole. Otherwise a bad minute at the site reads as a regression here. */
65
+ const degraded = said[0].chars < 300 || said[0].text.includes('page trouble')
66
+ const fast = () => degraded || (median(clicks) < 120 && median(types) < 120)
67
+ ok = clicks.length === 5 && fast() && clicks.every(within) && types.every(within) && Number(last?.chars ?? 0) > 0 && viewAgain.chars < said[0].chars && viewThird.chars < said[0].chars
68
+ console.log(JSON.stringify({ view: said[0], clickMedianMs: median(clicks), clickMaxMs: Math.max(...clicks.map((one) => one.ms)), typeMedianMs: median(types), typeMaxMs: Math.max(...types.map((one) => one.ms)), clickChars: clicks[0].chars, typeChars: types[0].chars, viewAgainChars: viewAgain.chars, viewThirdChars: viewThird.chars, viewAgainText: viewAgain.text, degraded, ok }))
69
+ } catch (error) {
70
+ console.log(JSON.stringify({ ok: false, error: String(error?.message ?? error).slice(0, 160) }))
71
+ } finally {
72
+ await sidecar.shutdown().catch(() => {})
73
+ }
74
+ process.exitCode = ok ? 0 : 1
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Can a Flash game run without ever appearing on the desktop?
4
+ *
5
+ * node scripts/check-flash-window.mjs [url]
6
+ *
7
+ * Starts the plugin's own Flash runtime on the address, then checks three
8
+ * things that together mean "only the column sees it": the window was never
9
+ * shown, the picture keeps changing, and the process is not a frontmost or
10
+ * Dock-bearing app. Its own profile and state directory; both are closed.
11
+ */
12
+ import { mkdtempSync } from 'node:fs'
13
+ import { createHash } from 'node:crypto'
14
+ import { tmpdir } from 'node:os'
15
+ import { join } from 'node:path'
16
+
17
+ const url = process.argv[2] ?? 'http://account.100bt.com/game/auth/login.html?gameName=aola&action=startgame&platform=100bt'
18
+ process.env.DSH_INNER_BROWSER_STATE_DIR = mkdtempSync(join(tmpdir(), 'dsh-flash-window-'))
19
+
20
+ const { Sidecar } = await import('../sidecar.js')
21
+
22
+ /** @param {number} ms - how long. */
23
+ const wait = (ms) => new Promise((settle) => setTimeout(settle, ms))
24
+ const sidecar = new Sidecar({ profile: 'flash-window', headless: false, flash: true })
25
+ let ok = false
26
+ try {
27
+ const started = Date.now()
28
+ await sidecar.launch('about:blank')
29
+ await sidecar.page.navigate(url).catch(() => {})
30
+ await wait(10_000)
31
+ const base = `http://127.0.0.1:${sidecar.flashBridge}`
32
+ const info = await fetch(`${base}/info`).then((reply) => reply.json()).catch(() => null)
33
+ const grab = async () => {
34
+ const body = Buffer.from(await (await fetch(`${base}/frame.jpg`)).arrayBuffer())
35
+ return { kb: Math.round(body.length / 1024), hash: createHash('sha1').update(body).digest('hex').slice(0, 8) }
36
+ }
37
+ const one = await grab()
38
+ await wait(900)
39
+ const two = await grab()
40
+ const neverShown = info !== null && info.visible === false
41
+ const painting = info !== null && Number(info.painted ?? 0) > 10
42
+ const moving = one.hash !== two.hash
43
+ ok = neverShown && painting && moving
44
+ console.log(JSON.stringify({
45
+ url: String(info?.url ?? url).slice(0, 80),
46
+ visible: info?.visible,
47
+ painted: info?.painted,
48
+ frameKB: one.kb,
49
+ moved: moving,
50
+ ms: Date.now() - started,
51
+ ok,
52
+ }))
53
+ } catch (error) {
54
+ console.log(JSON.stringify({ ok: false, error: String(error?.message ?? error).slice(0, 160) }))
55
+ } finally {
56
+ await sidecar.shutdown().catch(() => {})
57
+ }
58
+ process.exitCode = ok ? 0 : 1
@@ -66,9 +66,10 @@ const state = await get(`${ROUTE}/sidecar/state`)
66
66
  console.log(`host ${state === null ? `not answering under ${ROUTE}` : base}`)
67
67
  if (state === null) {
68
68
  console.log(' start the app, or set DSH_BRIDGE to the app address')
69
- process.exit(0)
69
+ process.exitCode = 0
70
70
  }
71
71
 
72
+ if (state !== null) {
72
73
  console.log(`browser ${state.alive === true ? `attached, ${state.headless === true ? 'headless' : 'with a window'}` : 'not running'}`)
73
74
  const profileDir = join(PROFILES, String(state.profile ?? ''))
74
75
  console.log(`profile ${state.profile} in ${profileDir}`)
@@ -87,3 +88,4 @@ if (existsSync(portFile)) {
87
88
  }
88
89
 
89
90
  if (String(state.error ?? '') !== '') console.log(`error ${state.error}`)
91
+ }