dsh-adb 0.2.0 → 1.0.0
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 +5 -1
- package/README.zh-CN.md +5 -1
- package/client.js +156 -0
- package/lib/index.js +6 -1
- package/lib/rpc.d.ts +23 -0
- package/lib/rpc.js +66 -0
- package/lib/tools/crash-report.js +1 -1
- package/lib/tools/perf-baseline.js +2 -2
- package/lib/tools/perf.d.ts +1 -2
- package/lib/tools/perf.js +3 -3
- package/package.json +11 -2
package/README.md
CHANGED
|
@@ -14,9 +14,13 @@ dsh plugin --profile web add dsh-adb
|
|
|
14
14
|
|
|
15
15
|
Or install directly from GitHub: `dsh plugin --profile web add github:SamXiaBing/dsh-adb`
|
|
16
16
|
|
|
17
|
+
## Web device panel (v1.0.0)
|
|
18
|
+
|
|
19
|
+
A "设备" tab in the conversation view ring (next to chat / trajectory / automation): device list with status, package-scoped performance snapshot (memory / frame stats / battery), and filtered logcat tail. Data flows over the package RPC channel; requires the plugin installed in a web profile and a GUI restart.
|
|
20
|
+
|
|
17
21
|
## Ecosystem
|
|
18
22
|
|
|
19
|
-
- ✅ [npm](https://www.npmjs.com/package/dsh-adb) — `dsh-adb` published (latest: 0.
|
|
23
|
+
- ✅ [npm](https://www.npmjs.com/package/dsh-adb) — `dsh-adb` published (latest: 1.0.0)
|
|
20
24
|
- ✅ [awesome-deepseek-harness#87](https://github.com/0xsline/awesome-deepseek-harness/pull/87) — **merged**
|
|
21
25
|
- ✅ [awesome-dsh-plugin#85](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/85) — **merged**
|
|
22
26
|
- ✅ [awesome-DSH-plugin#29](https://github.com/Alex-Yanggg/awesome-DSH-plugin/pull/29) — **merged**
|
package/README.zh-CN.md
CHANGED
|
@@ -14,9 +14,13 @@ dsh plugin --profile web add dsh-adb
|
|
|
14
14
|
|
|
15
15
|
或从 GitHub 直装:`dsh plugin --profile web add github:SamXiaBing/dsh-adb`
|
|
16
16
|
|
|
17
|
+
## Web 设备面板(v1.0.0)
|
|
18
|
+
|
|
19
|
+
会话视图页签「设备」(与 chat/轨迹/任务管理并列):设备列表/状态、按包名的性能快照(内存/帧率/电量)、过滤 logcat 尾部。数据走 Package RPC;需装入 web profile 并重启 GUI 生效。
|
|
20
|
+
|
|
17
21
|
## 生态收录
|
|
18
22
|
|
|
19
|
-
- ✅ [npm](https://www.npmjs.com/package/dsh-adb) — `dsh-adb` 已发布(latest: 0.
|
|
23
|
+
- ✅ [npm](https://www.npmjs.com/package/dsh-adb) — `dsh-adb` 已发布(latest: 1.0.0)
|
|
20
24
|
- ✅ [awesome-deepseek-harness#87](https://github.com/0xsline/awesome-deepseek-harness/pull/87) — **已合并**
|
|
21
25
|
- ✅ [awesome-dsh-plugin#85](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/85) — **已合并**
|
|
22
26
|
- ✅ [awesome-DSH-plugin#29](https://github.com/Alex-Yanggg/awesome-DSH-plugin/pull/29) — **已合并**
|
package/client.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/* dsh-adb Web device panel (client half) — plain JS, no build step.
|
|
2
|
+
* Registers a "设备" tab in conversation.view (beside chat/trajectory/automation)
|
|
3
|
+
* and talks to the Host half over the package RPC channel /dsh-adb.
|
|
4
|
+
* Uses only client builtins: React, ctx, styles, console.
|
|
5
|
+
*/
|
|
6
|
+
window.__ModuleLoader__.load({
|
|
7
|
+
id: 'dsh-adb',
|
|
8
|
+
factory: (require) => {
|
|
9
|
+
'use strict'
|
|
10
|
+
const module = { exports: {} }
|
|
11
|
+
|
|
12
|
+
const React = require('react')
|
|
13
|
+
const CHANNEL = '/dsh-adb'
|
|
14
|
+
|
|
15
|
+
function unwrap(value) {
|
|
16
|
+
if (typeof value !== 'object' || value === null || !('ok' in value)) {
|
|
17
|
+
throw new Error('dsh-adb host returned an invalid response.')
|
|
18
|
+
}
|
|
19
|
+
if (value.ok === true && 'value' in value) return value.value
|
|
20
|
+
if (value.ok === false && value.error) {
|
|
21
|
+
throw new Error(value.error.message ?? 'dsh-adb request failed.')
|
|
22
|
+
}
|
|
23
|
+
throw new Error('dsh-adb host returned an invalid response.')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createRuntime(rpc) {
|
|
27
|
+
return {
|
|
28
|
+
listDevices: () => rpc.call(CHANNEL, 'listDevices', {}).then(unwrap),
|
|
29
|
+
perfSnapshot: (payload) => rpc.call(CHANNEL, 'perfSnapshot', payload).then(unwrap),
|
|
30
|
+
logcatTail: (payload) => rpc.call(CHANNEL, 'logcatTail', payload).then(unwrap),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const h = React.createElement
|
|
35
|
+
const ROW = { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0' }
|
|
36
|
+
const BTN = { padding: '3px 10px', cursor: 'pointer' }
|
|
37
|
+
const INPUT = { padding: '3px 6px' }
|
|
38
|
+
|
|
39
|
+
function MetricRows({ rows }) {
|
|
40
|
+
if (!rows || rows.length === 0) return h('div', null, '(无数据)')
|
|
41
|
+
return h('table', { style: { borderCollapse: 'collapse' } },
|
|
42
|
+
rows.map((row) => h('tr', { key: row[0] },
|
|
43
|
+
h('td', { style: { padding: '2px 12px 2px 0', color: 'var(--dsh-text-secondary, #888)' } }, row[0]),
|
|
44
|
+
h('td', { style: { padding: '2px 0' } }, String(row[1])),
|
|
45
|
+
)),
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function DeviceView(props) {
|
|
50
|
+
const runtime = props.runtime
|
|
51
|
+
const [devices, setDevices] = React.useState([])
|
|
52
|
+
const [selected, setSelected] = React.useState(null)
|
|
53
|
+
const [pkg, setPkg] = React.useState('com.android.systemui')
|
|
54
|
+
const [snapshot, setSnapshot] = React.useState(null)
|
|
55
|
+
const [logEntries, setLogEntries] = React.useState([])
|
|
56
|
+
const [logTotal, setLogTotal] = React.useState(0)
|
|
57
|
+
const [level, setLevel] = React.useState('E')
|
|
58
|
+
const [tail, setTail] = React.useState('30')
|
|
59
|
+
const [error, setError] = React.useState(null)
|
|
60
|
+
const [busy, setBusy] = React.useState(false)
|
|
61
|
+
|
|
62
|
+
const refresh = () => {
|
|
63
|
+
setBusy(true); setError(null)
|
|
64
|
+
runtime.listDevices()
|
|
65
|
+
.then((value) => setDevices(value.devices ?? []))
|
|
66
|
+
.catch((e) => setError(String(e.message ?? e)))
|
|
67
|
+
.finally(() => setBusy(false))
|
|
68
|
+
}
|
|
69
|
+
React.useEffect(refresh, [])
|
|
70
|
+
|
|
71
|
+
const runSnapshot = () => {
|
|
72
|
+
if (!selected) return
|
|
73
|
+
setBusy(true); setError(null)
|
|
74
|
+
runtime.perfSnapshot({ serial: selected.serial, package: pkg })
|
|
75
|
+
.then(setSnapshot)
|
|
76
|
+
.catch((e) => setError(String(e.message ?? e)))
|
|
77
|
+
.finally(() => setBusy(false))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const runLogcat = () => {
|
|
81
|
+
if (!selected) return
|
|
82
|
+
setBusy(true); setError(null)
|
|
83
|
+
const n = parseInt(tail, 10)
|
|
84
|
+
runtime.logcatTail({ serial: selected.serial, level, tail: Number.isFinite(n) && n > 0 ? n : 30 })
|
|
85
|
+
.then((value) => { setLogEntries(value.entries ?? []); setLogTotal(value.total ?? 0) })
|
|
86
|
+
.catch((e) => setError(String(e.message ?? e)))
|
|
87
|
+
.finally(() => setBusy(false))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const snapshotRows = []
|
|
91
|
+
if (snapshot) {
|
|
92
|
+
const m = snapshot.meminfo; const g = snapshot.gfxinfo; const b = snapshot.battery
|
|
93
|
+
if (m) snapshotRows.push(['内存 PSS (KB)', m.totalPssKb], ['内存 RSS (KB)', m.totalRssKb], ['Java Heap (KB)', m.javaHeapKb], ['Native Heap (KB)', m.nativeHeapKb])
|
|
94
|
+
if (g) snapshotRows.push(['总帧数', g.totalFrames], ['卡顿帧 / %', `${g.jankyFrames} / ${g.jankyPercent}%`], ['P50/P90 (ms)', `${g.percentile50Ms} / ${g.percentile90Ms}`], ['P95/P99 (ms)', `${g.percentile95Ms} / ${g.percentile99Ms}`], ['Missed Vsync', g.missedVsync])
|
|
95
|
+
if (b) snapshotRows.push(['电量', `${b.levelPercent}%`], ['温度 (°C)', b.temperatureC])
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return h('div', { style: { padding: 12, fontFamily: 'inherit', fontSize: 13 } },
|
|
99
|
+
h('div', { style: { ...ROW, justifyContent: 'space-between' } },
|
|
100
|
+
h('strong', null, 'ADB 设备'),
|
|
101
|
+
h('button', { style: BTN, onClick: refresh, disabled: busy }, busy ? '…' : '刷新'),
|
|
102
|
+
),
|
|
103
|
+
error !== null && h('div', { style: { color: '#e5484d', margin: '6px 0' } }, String(error)),
|
|
104
|
+
devices.length === 0
|
|
105
|
+
? h('div', { style: { color: 'var(--dsh-text-secondary, #888)', margin: '8px 0' } }, '未连接设备')
|
|
106
|
+
: h('div', null, devices.map((d) =>
|
|
107
|
+
h('button', {
|
|
108
|
+
key: d.serial,
|
|
109
|
+
onClick: () => setSelected(d),
|
|
110
|
+
style: { ...BTN, display: 'block', width: '100%', textAlign: 'left', margin: '2px 0',
|
|
111
|
+
background: selected && selected.serial === d.serial ? 'var(--dsh-accent-soft, rgba(66,133,244,.15))' : 'transparent' },
|
|
112
|
+
}, `${d.serial} · ${d.state}${d.model ? ' · ' + d.model : ''}`),
|
|
113
|
+
)),
|
|
114
|
+
|
|
115
|
+
selected !== null && h('div', { style: { marginTop: 14, borderTop: '1px solid var(--dsh-border, #333)', paddingTop: 10 } },
|
|
116
|
+
h('div', { style: ROW },
|
|
117
|
+
h('label', null, '包名'),
|
|
118
|
+
h('input', { style: INPUT, value: pkg, onChange: (e) => setPkg(e.target.value), onKeyDown: (e) => { if (e.key === 'Enter') runSnapshot() } }),
|
|
119
|
+
h('button', { style: BTN, onClick: runSnapshot, disabled: busy }, '性能快照'),
|
|
120
|
+
),
|
|
121
|
+
snapshot && h('div', { style: { marginTop: 8 } }, h(MetricRows, { rows: snapshotRows })),
|
|
122
|
+
|
|
123
|
+
h('div', { style: { ...ROW, marginTop: 12 } },
|
|
124
|
+
h('label', null, '级别'),
|
|
125
|
+
h('select', { style: INPUT, value: level, onChange: (e) => setLevel(e.target.value) },
|
|
126
|
+
['V', 'D', 'I', 'W', 'E', 'F'].map((lv) => h('option', { key: lv, value: lv }, lv))),
|
|
127
|
+
h('label', null, '条数'),
|
|
128
|
+
h('input', { style: { ...INPUT, width: 56 }, value: tail, onChange: (e) => setTail(e.target.value) }),
|
|
129
|
+
h('button', { style: BTN, onClick: runLogcat, disabled: busy }, '获取日志'),
|
|
130
|
+
),
|
|
131
|
+
h('div', { style: { color: 'var(--dsh-text-secondary, #888)', margin: '4px 0' } }, `logcat 共 ${logTotal} 条(显示 ${logEntries.length})`),
|
|
132
|
+
h('div', { style: { maxHeight: 260, overflowY: 'auto', fontFamily: 'monospace', fontSize: 12, whiteSpace: 'pre-wrap' } },
|
|
133
|
+
logEntries.length === 0
|
|
134
|
+
? h('div', null, '(暂无)')
|
|
135
|
+
: logEntries.map((e) => h('div', { key: `${e.time}-${e.pid}-${e.tid}-${e.message}` },
|
|
136
|
+
`${e.time} ${e.pid} ${e.tid} ${e.level} ${e.tag}: ${e.message}`)),
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function apply(ctx) {
|
|
143
|
+
const slots = ctx.get('slots')
|
|
144
|
+
const connection = ctx.get('connection')
|
|
145
|
+
if (slots === undefined || connection === undefined || connection.rpc === undefined) return
|
|
146
|
+
const runtime = createRuntime(connection.rpc)
|
|
147
|
+
slots.inject('conversation.view', () => slots.register(
|
|
148
|
+
{ name: 'conversation.view', id: 'devices', order: 30, label: '设备' },
|
|
149
|
+
(props) => h(DeviceView, { ...props, runtime }),
|
|
150
|
+
))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = { apply }
|
|
154
|
+
return module.exports
|
|
155
|
+
},
|
|
156
|
+
})
|
package/lib/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { registerInstallTool } from './tools/install.js';
|
|
|
7
7
|
import { registerLogcatTool } from './tools/logcat.js';
|
|
8
8
|
import { registerPerfTool } from './tools/perf.js';
|
|
9
9
|
import { registerPerfBaselineTool } from './tools/perf-baseline.js';
|
|
10
|
+
import { registerRpc } from './rpc.js';
|
|
10
11
|
export const name = 'dsh-adb';
|
|
11
12
|
/** The tool registry is a hard dependency: every tool registers through it. */
|
|
12
13
|
export const inject = ['tools'];
|
|
@@ -25,5 +26,9 @@ export function apply(ctx, config) {
|
|
|
25
26
|
registerPerfTool(ctx, cfg);
|
|
26
27
|
registerPerfBaselineTool(ctx, cfg, config.baselineDir ?? DEFAULT_BASELINE_DIR);
|
|
27
28
|
registerCrashReportTool(ctx, cfg);
|
|
28
|
-
|
|
29
|
+
// The RPC channel needs the client connection, which mounts after this
|
|
30
|
+
// plugin starts in web compositions; register lazily so headless profiles
|
|
31
|
+
// (no connection) stay unaffected.
|
|
32
|
+
ctx.inject(['connection'], (readyCtx) => registerRpc(readyCtx, cfg));
|
|
33
|
+
ctx.logger.info('[dsh-adb] loaded: 9 tools + web device panel rpc');
|
|
29
34
|
}
|
package/lib/rpc.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { AdbConfig } from './adb.js';
|
|
3
|
+
export type RpcEndpointResult = {
|
|
4
|
+
ok: true;
|
|
5
|
+
value: unknown;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
error: {
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Package-private Client↔Host RPC endpoint dispatch for the Web device panel.
|
|
14
|
+
* Extracted from the connection handler so the endpoints are unit-testable
|
|
15
|
+
* against a fake adb backend.
|
|
16
|
+
*/
|
|
17
|
+
export declare function handleRpcEndpoint(ctx: Context, cfg: AdbConfig, endpoint: string, raw: unknown, signal: AbortSignal): Promise<RpcEndpointResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Register the Package-private Client↔Host RPC for the Web device panel
|
|
20
|
+
* (conversation.view tab "设备"). Called lazily once the client connection
|
|
21
|
+
* mounts; headless compositions (no connection) stay unaffected.
|
|
22
|
+
*/
|
|
23
|
+
export declare function registerRpc(ctx: Context, cfg: AdbConfig): void;
|
package/lib/rpc.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { classifyFailure, runAdb } from './adb.js';
|
|
2
|
+
import { parseDevices } from './parsers/devices.js';
|
|
3
|
+
import { matchesLevel, parseLogcat } from './parsers/logcat.js';
|
|
4
|
+
import { capturePerfSnapshot } from './tools/perf.js';
|
|
5
|
+
/**
|
|
6
|
+
* Package-private Client↔Host RPC endpoint dispatch for the Web device panel.
|
|
7
|
+
* Extracted from the connection handler so the endpoints are unit-testable
|
|
8
|
+
* against a fake adb backend.
|
|
9
|
+
*/
|
|
10
|
+
export async function handleRpcEndpoint(ctx, cfg, endpoint, raw, signal) {
|
|
11
|
+
try {
|
|
12
|
+
const payload = (raw ?? {});
|
|
13
|
+
switch (endpoint) {
|
|
14
|
+
case 'listDevices': {
|
|
15
|
+
const result = await runAdb(ctx, cfg, ['devices', '-l'], { signal });
|
|
16
|
+
if (result.exitCode !== 0)
|
|
17
|
+
throw classifyFailure(result);
|
|
18
|
+
return { ok: true, value: { server: 'ok', devices: parseDevices(result.stdout) } };
|
|
19
|
+
}
|
|
20
|
+
case 'perfSnapshot': {
|
|
21
|
+
const pkg = typeof payload.package === 'string' ? payload.package : undefined;
|
|
22
|
+
if (pkg === undefined)
|
|
23
|
+
throw new Error('perfSnapshot requires a string "package"');
|
|
24
|
+
const serial = typeof payload.serial === 'string' ? payload.serial : undefined;
|
|
25
|
+
const snapshot = await capturePerfSnapshot(ctx, cfg, signal, { package: pkg, serial });
|
|
26
|
+
return { ok: true, value: snapshot };
|
|
27
|
+
}
|
|
28
|
+
case 'logcatTail': {
|
|
29
|
+
const serial = typeof payload.serial === 'string' ? payload.serial : undefined;
|
|
30
|
+
const level = typeof payload.level === 'string' ? payload.level : undefined;
|
|
31
|
+
const tail = typeof payload.tail === 'number' && payload.tail > 0 ? Math.floor(payload.tail) : 30;
|
|
32
|
+
const output = await runAdb(ctx, cfg, ['logcat', '-v', 'threadtime', '-d'], {
|
|
33
|
+
signal,
|
|
34
|
+
serial,
|
|
35
|
+
maxBytes: 8 * 1024 * 1024,
|
|
36
|
+
});
|
|
37
|
+
if (output.exitCode !== 0)
|
|
38
|
+
throw classifyFailure(output);
|
|
39
|
+
let entries = parseLogcat(output.stdout);
|
|
40
|
+
if (level !== undefined)
|
|
41
|
+
entries = entries.filter((entry) => matchesLevel(entry, level));
|
|
42
|
+
const capped = entries.slice(-tail);
|
|
43
|
+
return { ok: true, value: { total: entries.length, truncated: entries.length > tail, entries: capped } };
|
|
44
|
+
}
|
|
45
|
+
default:
|
|
46
|
+
return { ok: false, error: { message: `unknown endpoint: ${endpoint}` } };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return { ok: false, error: { message: error instanceof Error ? error.message : String(error) } };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Register the Package-private Client↔Host RPC for the Web device panel
|
|
55
|
+
* (conversation.view tab "设备"). Called lazily once the client connection
|
|
56
|
+
* mounts; headless compositions (no connection) stay unaffected.
|
|
57
|
+
*/
|
|
58
|
+
export function registerRpc(ctx, cfg) {
|
|
59
|
+
const connection = ctx.get('connection');
|
|
60
|
+
const rpc = connection?.rpc;
|
|
61
|
+
if (rpc === undefined)
|
|
62
|
+
return;
|
|
63
|
+
ctx.effect(() => rpc.handle('/dsh-adb', (endpoint, raw, signal) => handleRpcEndpoint(ctx, cfg, endpoint, raw, signal),
|
|
64
|
+
// Browser-only channel: accept requests from the loopback web GUI.
|
|
65
|
+
{ authority: 'loopback' }));
|
|
66
|
+
}
|
|
@@ -96,7 +96,7 @@ export function registerCrashReportTool(ctx, cfg) {
|
|
|
96
96
|
if (args.package === undefined) {
|
|
97
97
|
throw new AdbError('ARGS_INVALID', 'meminfo section requires a package');
|
|
98
98
|
}
|
|
99
|
-
const snapshot = await capturePerfSnapshot(ctx, cfg, exec, {
|
|
99
|
+
const snapshot = await capturePerfSnapshot(ctx, cfg, exec.signal, {
|
|
100
100
|
package: args.package,
|
|
101
101
|
serial: args.serial,
|
|
102
102
|
metrics: ['meminfo'],
|
|
@@ -62,7 +62,7 @@ export function registerPerfBaselineTool(ctx, cfg, baselineDir) {
|
|
|
62
62
|
throw new AdbError('ARGS_INVALID', `${args.command} requires a package`);
|
|
63
63
|
}
|
|
64
64
|
if (args.command === 'save') {
|
|
65
|
-
const snapshot = await capturePerfSnapshot(ctx, cfg, exec, {
|
|
65
|
+
const snapshot = await capturePerfSnapshot(ctx, cfg, exec.signal, {
|
|
66
66
|
package: args.package,
|
|
67
67
|
serial: args.serial,
|
|
68
68
|
metrics: args.metrics,
|
|
@@ -83,7 +83,7 @@ export function registerPerfBaselineTool(ctx, cfg, baselineDir) {
|
|
|
83
83
|
if (baseline === undefined) {
|
|
84
84
|
throw new AdbError('BASELINE_NOT_FOUND', `no baseline for device ${device} package ${args.package}${args.id !== undefined ? ` (id ${args.id})` : ''}; save one with command=save first`);
|
|
85
85
|
}
|
|
86
|
-
const current = await capturePerfSnapshot(ctx, cfg, exec, {
|
|
86
|
+
const current = await capturePerfSnapshot(ctx, cfg, exec.signal, {
|
|
87
87
|
package: args.package,
|
|
88
88
|
serial: args.serial,
|
|
89
89
|
metrics: args.metrics,
|
package/lib/tools/perf.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
-
import type { ToolExecution } from '@deepseek-ai/dsh-tools';
|
|
3
2
|
import { type AdbConfig } from '../adb.js';
|
|
4
3
|
import { parseBattery, parseGfxinfo, parseMeminfo } from '../parsers/perf.js';
|
|
5
4
|
export type PerfMetric = 'meminfo' | 'gfxinfo' | 'battery';
|
|
@@ -11,7 +10,7 @@ export interface PerfSnapshot {
|
|
|
11
10
|
battery?: ReturnType<typeof parseBattery>;
|
|
12
11
|
}
|
|
13
12
|
/** Shared capture: dumpsys meminfo / gfxinfo / battery for one app. */
|
|
14
|
-
export declare function capturePerfSnapshot(ctx: Context, cfg: AdbConfig,
|
|
13
|
+
export declare function capturePerfSnapshot(ctx: Context, cfg: AdbConfig, signal: AbortSignal, args: {
|
|
15
14
|
package: string;
|
|
16
15
|
serial?: string;
|
|
17
16
|
metrics?: PerfMetric[];
|
package/lib/tools/perf.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { classifyFailure, jsonOutput, runAdb } from '../adb.js';
|
|
2
2
|
import { parseBattery, parseGfxinfo, parseMeminfo } from '../parsers/perf.js';
|
|
3
3
|
/** Shared capture: dumpsys meminfo / gfxinfo / battery for one app. */
|
|
4
|
-
export async function capturePerfSnapshot(ctx, cfg,
|
|
4
|
+
export async function capturePerfSnapshot(ctx, cfg, signal, args) {
|
|
5
5
|
const metrics = args.metrics ?? ['meminfo', 'gfxinfo', 'battery'];
|
|
6
6
|
const result = { package: args.package, metrics: [...metrics] };
|
|
7
7
|
for (const metric of metrics) {
|
|
@@ -10,7 +10,7 @@ export async function capturePerfSnapshot(ctx, cfg, exec, args) {
|
|
|
10
10
|
? ['shell', 'dumpsys', 'battery']
|
|
11
11
|
: ['shell', 'dumpsys', metric, args.package];
|
|
12
12
|
const output = await runAdb(ctx, cfg, argv, {
|
|
13
|
-
signal
|
|
13
|
+
signal,
|
|
14
14
|
serial: args.serial,
|
|
15
15
|
maxBytes: 4 * 1024 * 1024,
|
|
16
16
|
});
|
|
@@ -46,7 +46,7 @@ export function registerPerfTool(ctx, cfg) {
|
|
|
46
46
|
},
|
|
47
47
|
output: jsonOutput(),
|
|
48
48
|
async execute(args, exec) {
|
|
49
|
-
return capturePerfSnapshot(ctx, cfg, exec, args);
|
|
49
|
+
return capturePerfSnapshot(ctx, cfg, exec.signal, args);
|
|
50
50
|
},
|
|
51
51
|
});
|
|
52
52
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-adb",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "ADB device & bench operations for DeepSeek Harness: device discovery, structured logcat, apk install, file pull/push, performance snapshots, perf baselines, crash reports",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "ADB device & bench operations for DeepSeek Harness: device discovery, structured logcat, apk install, file pull/push, performance snapshots, perf baselines, crash reports, web device panel",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
7
12
|
"files": [
|
|
8
13
|
"lib",
|
|
14
|
+
"client.js",
|
|
9
15
|
"cordis.patch.yml"
|
|
10
16
|
],
|
|
11
17
|
"keywords": [
|
|
@@ -21,6 +27,9 @@
|
|
|
21
27
|
"dsh": {
|
|
22
28
|
"bundle": {
|
|
23
29
|
"patch": "./cordis.patch.yml"
|
|
30
|
+
},
|
|
31
|
+
"client": {
|
|
32
|
+
"platform": "web"
|
|
24
33
|
}
|
|
25
34
|
},
|
|
26
35
|
"scripts": {
|