dsh-mobilecode 0.7.0 → 0.7.1

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/README.md CHANGED
@@ -202,7 +202,9 @@ classified adb boundary (argv-based, quoted, replay-safe):
202
202
  - `device_backtrace` — a thread/crash dump without a debugger: sends SIGQUIT
203
203
  (`kill -3`), waits for ART to write the trace, reads the newest `/data/anr`
204
204
  entry, and falls back to the logcat crash buffer when `/data/anr` is unreadable
205
- (`engine: "anr-trace" | "logcat-crash"`). Pass `package_name` or `pid`.
205
+ (`engine: "anr-trace" | "logcat-crash"`). SIGQUIT refusal (system-uid or
206
+ non-debuggable process) degrades to the crash buffer with an explanatory note
207
+ instead of failing. Pass `package_name` or `pid`.
206
208
 
207
209
  **Conversation surface (v0.7.0)** — the transcript integration ported from
208
210
  dsh-android's UI/UX:
package/lib/index.js CHANGED
@@ -531,6 +531,10 @@ function makeRoutes(engine, config, stream) {
531
531
  const argv = DEVICE_ACTIONS[action]
532
532
  if (argv === undefined) { writeJson(res, 400, { code: 'unknown_action', error: `unknown device action "${action}"` }); return }
533
533
  if (!StreamAccess.SERIAL_PATTERN.test(serial)) { writeJson(res, 400, { code: 'bad_request', error: 'device must be an adb device serial' }); return }
534
+ if (serial !== stream.host.streamedSerial) {
535
+ const online = await stream.host.listDevices()
536
+ if (!online.some((device) => device.serial === serial)) { writeJson(res, 409, { code: 'device_not_found', error: `device ${serial} is not online` }); return }
537
+ }
534
538
  try {
535
539
  await DeviceBuild.adbRun(serial, ['shell', ...argv])
536
540
  writeJson(res, 200, { ok: true, action, device: serial })
@@ -1647,7 +1651,8 @@ function deviceBacktraceTool() {
1647
1651
  name: 'device_backtrace',
1648
1652
  description: 'Capture a native/ANR thread backtrace or crash log for an app on the device. Sends SIGQUIT (kill -3) to the process, ' +
1649
1653
  'waits for the ART runtime to write the thread dump, then reads the newest /data/anr entry. When /data/anr is unrunnable it falls ' +
1650
- 'back to the logcat crash buffer and says so (engine field: "anr-trace" | "logcat-crash"). Pass package_name or pid.',
1654
+ 'back to the logcat crash buffer and says so (engine field: "anr-trace" | "logcat-crash"). SIGQUIT refusal (system-uid or ' +
1655
+ 'non-debuggable process) degrades to the crash buffer with an explanatory note instead of failing. Pass package_name or pid.',
1651
1656
  parameters: {
1652
1657
  serial: { type: 'string', description: 'Android device serial. Omit to use the first attached device.' },
1653
1658
  package_name: { type: 'string', description: 'Package of the process to trace (e.g. com.simspoof.app).' },
@@ -1695,22 +1700,32 @@ function deviceBacktraceTool() {
1695
1700
  if (pid === undefined) {
1696
1701
  throw new Error(`device_backtrace could not resolve a running process for ${packageName ? `"${packageName}"` : 'the request'} on ${serial} — pass a package_name of a running app, or an explicit pid.`)
1697
1702
  }
1698
- // SIGQUIT: the ART runtime writes the thread dump to /data/anr/.
1699
- await DeviceBuild.adbRun(serial, ['shell', 'kill', '-3', String(pid)])
1700
- await new Promise((resolve) => setTimeout(resolve, 1200))
1701
- const listing = await DeviceBuild.adbRun(serial, ['shell', 'ls', '-t', '/data/anr']).catch(() => undefined)
1702
- const newest = (listing ?? '').split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== '' && !line.includes(' '))[0]
1703
- if (newest !== undefined) {
1704
- const content = await DeviceBuild.adbRun(serial, ['shell', 'cat', DeviceBuild.shQuoteDevice(`/data/anr/${newest}`)], { timeoutMs: 30_000 }).catch(() => undefined)
1705
- if (content !== undefined && content.trim() !== '') {
1706
- return DeviceBuild.jsonSafe({
1707
- serial,
1708
- engine: 'anr-trace',
1709
- ...(packageName ? { package_name: packageName } : {}),
1710
- pid,
1711
- trace_path: `/data/anr/${newest}`,
1712
- lines: content.split(/\r?\n/).slice(-250),
1713
- })
1703
+ // SIGQUIT: the ART runtime writes the thread dump to /data/anr/. The adb
1704
+ // shell user cannot signal system-uid / non-debuggable processes on
1705
+ // enforcing builds (EPERM) degrade honestly instead of throwing.
1706
+ let sigquit = true
1707
+ try {
1708
+ await DeviceBuild.adbRun(serial, ['shell', 'kill', '-3', String(pid)])
1709
+ } catch {
1710
+ sigquit = false
1711
+ }
1712
+ let newest
1713
+ if (sigquit) {
1714
+ await new Promise((resolve) => setTimeout(resolve, 1200))
1715
+ const listing = await DeviceBuild.adbRun(serial, ['shell', 'ls', '-t', '/data/anr']).catch(() => undefined)
1716
+ newest = (listing ?? '').split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== '' && !line.includes(' '))[0]
1717
+ if (newest !== undefined) {
1718
+ const content = await DeviceBuild.adbRun(serial, ['shell', 'cat', DeviceBuild.shQuoteDevice(`/data/anr/${newest}`)], { timeoutMs: 30_000 }).catch(() => undefined)
1719
+ if (content !== undefined && content.trim() !== '') {
1720
+ return DeviceBuild.jsonSafe({
1721
+ serial,
1722
+ engine: 'anr-trace',
1723
+ ...(packageName ? { package_name: packageName } : {}),
1724
+ pid,
1725
+ trace_path: `/data/anr/${newest}`,
1726
+ lines: content.split(/\r?\n/).slice(-250),
1727
+ })
1728
+ }
1714
1729
  }
1715
1730
  }
1716
1731
  // Fallback: the logcat crash buffer (FATAL/AndroidRuntime lines), filtered by package when known.
@@ -1724,7 +1739,9 @@ function deviceBacktraceTool() {
1724
1739
  engine: 'logcat-crash',
1725
1740
  ...(packageName ? { package_name: packageName } : {}),
1726
1741
  pid,
1727
- note: `SIGQUIT sent to pid ${pid}; /data/anr was ${newest === undefined ? 'empty/unreadable' : `read but produced no content for ${newest}`}; the logcat crash buffer has no matching FATAL lines now. The app may not have crashed — check device_log for the main buffer.`,
1742
+ note: sigquit
1743
+ ? `SIGQUIT sent to pid ${pid}; /data/anr was ${newest === undefined ? 'empty/unreadable' : `read but produced no content for ${newest}`}; the logcat crash buffer has no matching FATAL lines now. The app may not have crashed — check device_log for the main buffer.`
1744
+ : `SIGQUIT refused for pid ${pid} — the adb shell user cannot signal this process (system uid or non-debuggable app on an enforcing build), so no fresh thread dump exists; the logcat crash buffer has no matching FATAL lines either. Target a debuggable app for a live dump, or use an adb-rooted device.`,
1728
1745
  lines: [],
1729
1746
  })
1730
1747
  }
package/lib/list-rows.js CHANGED
@@ -140,6 +140,10 @@ export function parseCounters(label) {
140
140
  const rawKey = match[3].trim()
141
141
  if (rawKey === "") continue
142
142
  const key = normalizeCountKey(rawKey)
143
+ // A counter key must contain a letter: serial-ish strings like
144
+ // "sdk_gphone64_x86_64" backtrack to punctuation-only keys ("_") that are
145
+ // never real counters.
146
+ if (!/\p{L}/u.test(key)) continue
143
147
  if (seen.has(key)) continue
144
148
  seen.add(key)
145
149
  counters.push({ key, value: number * multiplier, raw: match[0].trim() })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-mobilecode",
3
- "description": "MobileCode for the dsh web GUI: detect iOS/Android projects, run preview servers, and drive the simulator/emulator from the session — 24 agent tools (device_run, device_screen, device_ui_tree, device_tap_element, device_wait_for, device_scroll_to, device_input, device_intent, device_connect, device_pair_qr, device_perf, device_app_info, device_install, device_uninstall, device_reboot, device_log, live screen stream, multimodal screenshots) with one classified adb boundary and a Wi-Fi connect/pair QR flow in the Connection settings tab. Hot-pluggable — mounted via the profile bundle list + cordis.patch.yml, no dsh source changes.",
4
- "version": "0.7.0",
3
+ "description": "MobileCode for the dsh web GUI: detect iOS/Android projects, run preview servers, and drive the simulator/emulator from the session — 28 agent tools (device_run, device_screen, device_ui_tree, device_ui_rows, device_tap_row, device_tap_element, device_wait_for, device_scroll_to, device_input, device_intent, device_connect, device_pair_qr, device_perf, device_meminfo, device_backtrace, device_app_info, device_install, device_uninstall, device_reboot, device_log, live screen stream, multimodal screenshots) with one classified adb boundary and a Wi-Fi connect/pair QR flow in the Connection settings tab. Hot-pluggable — mounted via the profile bundle list + cordis.patch.yml, no dsh source changes.",
4
+ "version": "0.7.1",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.22.0",
7
7
  "engines": {