@dsh-workbench/shared 0.1.13 → 0.1.15
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/dist/components/CardHead.js +3 -2
- package/dist/components/Field.css +16 -0
- package/dist/components/Field.d.ts +8 -0
- package/dist/components/Field.js +8 -0
- package/dist/components/Link.css +22 -0
- package/dist/components/Link.d.ts +10 -0
- package/dist/components/Link.js +8 -0
- package/dist/components/LongTaskRow.css +0 -15
- package/dist/components/LongTaskRow.js +3 -4
- package/dist/components/TextField.css +5 -0
- package/dist/host/browser-find.d.ts +38 -0
- package/dist/host/browser-find.js +242 -0
- package/dist/host/browser-read.d.ts +26 -0
- package/dist/host/browser-read.js +240 -0
- package/dist/host/http.d.ts +6 -1
- package/dist/host/http.js +2 -1
- package/dist/host/opencli-runtime.d.ts +3 -0
- package/dist/host/opencli-runtime.js +10 -0
- package/dist/host/opencli-window.d.ts +2 -0
- package/dist/host/opencli-window.js +8 -0
- package/dist/host/opencli.js +2 -0
- package/dist/host.d.ts +2 -0
- package/dist/host.js +2 -0
- package/dist/i18n/en.js +3 -0
- package/dist/i18n/index.d.ts +6 -3
- package/dist/i18n/zh.d.ts +5 -2
- package/dist/i18n/zh.js +5 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
- package/src/components/CardHead.test.tsx +81 -0
- package/src/components/CardHead.tsx +4 -3
- package/src/components/Field.css +16 -0
- package/src/components/Field.test.tsx +27 -0
- package/src/components/Field.tsx +19 -0
- package/src/components/Link.css +22 -0
- package/src/components/Link.test.tsx +37 -0
- package/src/components/Link.tsx +26 -0
- package/src/components/LongTaskRow.css +0 -15
- package/src/components/LongTaskRow.tsx +3 -4
- package/src/components/TextField.css +5 -0
- package/src/host/__tests__/browser-find.test.ts +319 -0
- package/src/host/__tests__/browser-read.test.ts +197 -0
- package/src/host/browser-find.ts +311 -0
- package/src/host/browser-read.ts +308 -0
- package/src/host/http.ts +8 -1
- package/src/host/opencli-runtime.ts +13 -0
- package/src/host/opencli-window.ts +11 -0
- package/src/host/opencli.ts +2 -0
- package/src/host.ts +2 -0
- package/src/i18n/en.ts +3 -0
- package/src/i18n/zh.ts +5 -2
- package/src/index.ts +2 -0
- package/dist/components/StateDot.d.ts +0 -5
- package/dist/components/StateDot.js +0 -10
- package/dist/host/__tests__/fakes.d.ts +0 -41
- package/dist/host/__tests__/fakes.js +0 -76
- package/dist/host/__tests__/preflight-fixtures.d.ts +0 -19
- package/dist/host/__tests__/preflight-fixtures.js +0 -47
- package/dist/host/context-inject.d.ts +0 -24
- package/dist/host/context-inject.js +0 -74
- package/dist/host/events.d.ts +0 -9
- package/dist/host/events.js +0 -70
- package/dist/host/mount-units.d.ts +0 -52
- package/dist/host/mount-units.js +0 -186
- package/dist/host/preflight.d.ts +0 -58
- package/dist/host/preflight.js +0 -233
- package/dist/host/probe-cache.d.ts +0 -6
- package/dist/host/probe-cache.js +0 -19
- package/dist/host/probe-libs.d.ts +0 -45
- package/dist/host/probe-libs.js +0 -61
- package/dist/host/probe-units.d.ts +0 -17
- package/dist/host/probe-units.js +0 -102
- package/dist/host/probe-verdict.d.ts +0 -7
- package/dist/host/probe-verdict.js +0 -143
- package/dist/host/tool-scope.d.ts +0 -32
- package/dist/host/tool-scope.js +0 -84
- package/dist/host/unit-readiness.d.ts +0 -7
- package/dist/host/unit-readiness.js +0 -5
- package/dist/host/unit-registry.d.ts +0 -9
- package/dist/host/unit-registry.js +0 -33
- package/dist/host/version-range.d.ts +0 -2
- package/dist/host/version-range.js +0 -73
|
@@ -3,11 +3,12 @@ import { h } from '../lib/react.js';
|
|
|
3
3
|
import './CardHead.css';
|
|
4
4
|
export function CardHead({ t, title, meta, refreshing, onRefresh, gear, gearOpen, onToggleGear, platformHref, platform, platformTitle, }) {
|
|
5
5
|
const refreshLabel = refreshing ? t('rightbar.refreshing') : t('rightbar.refresh');
|
|
6
|
+
const platformLabel = platformTitle ?? platform ?? t('rightbar.openPlatform');
|
|
6
7
|
return (h("header", { className: "wb-cardHead" },
|
|
7
8
|
h("h2", { className: "wb-cardTitle" }, title),
|
|
8
9
|
meta === undefined ? null : h("span", { className: "wb-cardMeta" }, meta),
|
|
9
|
-
platformHref === undefined
|
|
10
|
-
h("a", { className: "wb-iconButton wb-cardOpen", href: platformHref, target: "_blank", rel: "noreferrer noopener", "aria-label": platform },
|
|
10
|
+
platformHref === undefined ? null : (h(Tooltip, { side: "bottom", align: "end", gap: 6, portal: true, label: platformLabel },
|
|
11
|
+
h("a", { className: "wb-iconButton wb-cardOpen", href: platformHref, target: "_blank", rel: "noreferrer noopener", "aria-label": platform ?? platformLabel },
|
|
11
12
|
h(IconRightUpOutlineRegular, { size: 14 })))),
|
|
12
13
|
h(Tooltip, { side: "bottom", align: "end", gap: 6, portal: true, label: refreshLabel },
|
|
13
14
|
h(Button, { type: "button", size: "sm", variant: "ghost", className: `wb-cardRefresh${refreshing ? ' wb-spinning' : ''}`, "aria-label": refreshLabel, disabled: refreshing, icon: h(IconRefreshOutlineRegular, { size: 14 }), onClick: onRefresh })),
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.wb-field {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--wb-len-6);
|
|
5
|
+
font-size: var(--dsw-font-xs-13-font-size);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.wb-fieldLabel {
|
|
9
|
+
color: var(--dsw-alias-label-secondary);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.wb-fieldHint {
|
|
13
|
+
font-size: var(--dsw-font-xxs-12-font-size);
|
|
14
|
+
line-height: var(--wb-line-18);
|
|
15
|
+
color: var(--dsw-alias-label-tertiary);
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReactNode } from '../lib/react.js';
|
|
2
|
+
import './Field.css';
|
|
3
|
+
export interface FieldProps {
|
|
4
|
+
readonly label: ReactNode;
|
|
5
|
+
readonly hint?: ReactNode | undefined;
|
|
6
|
+
readonly children?: ReactNode | undefined;
|
|
7
|
+
}
|
|
8
|
+
export declare function Field({ label, hint, children }: FieldProps): JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { h } from '../lib/react.js';
|
|
2
|
+
import './Field.css';
|
|
3
|
+
export function Field({ label, hint, children }) {
|
|
4
|
+
return (h("label", { className: "wb-field" },
|
|
5
|
+
h("span", { className: "wb-fieldLabel" }, label),
|
|
6
|
+
children,
|
|
7
|
+
hint === undefined ? null : h("span", { className: "wb-fieldHint" }, hint)));
|
|
8
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
:where(.wb-link) {
|
|
2
|
+
margin-left: var(--wb-len-2);
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: var(--wb-len-2);
|
|
6
|
+
font: inherit;
|
|
7
|
+
font-size: var(--dsw-font-xxs-12-font-size);
|
|
8
|
+
line-height: var(--wb-line-18);
|
|
9
|
+
color: var(--dsw-alias-label-primary);
|
|
10
|
+
text-decoration: none;
|
|
11
|
+
white-space: nowrap;
|
|
12
|
+
vertical-align: baseline;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
:where(.wb-link:hover) {
|
|
16
|
+
text-decoration: underline;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:where(.wb-link:focus-visible) {
|
|
20
|
+
outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
21
|
+
outline-offset: -2px;
|
|
22
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from '../lib/react.js';
|
|
2
|
+
import './Link.css';
|
|
3
|
+
export interface LinkProps {
|
|
4
|
+
readonly href: string;
|
|
5
|
+
readonly children?: ReactNode | undefined;
|
|
6
|
+
readonly title?: string | undefined;
|
|
7
|
+
readonly external?: boolean | undefined;
|
|
8
|
+
readonly className?: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
export declare function Link({ href, children, title, external, className }: LinkProps): JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { h } from '../lib/react.js';
|
|
2
|
+
import { IconRightUpOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
import './Link.css';
|
|
4
|
+
export function Link({ href, children, title, external = true, className }) {
|
|
5
|
+
return (h("a", { className: className === undefined ? 'wb-link' : `wb-link ${className}`, href: href, ...(title === undefined ? {} : { title }), ...(external ? { target: '_blank', rel: 'noopener noreferrer' } : {}) },
|
|
6
|
+
children,
|
|
7
|
+
external ? h(IconRightUpOutlineRegular, { size: 12 }) : null));
|
|
8
|
+
}
|
|
@@ -16,21 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
.wb-longTaskLink {
|
|
18
18
|
flex: none;
|
|
19
|
-
display: inline-flex;
|
|
20
|
-
align-items: center;
|
|
21
|
-
gap: var(--wb-len-2);
|
|
22
|
-
color: var(--dsw-alias-label-secondary);
|
|
23
|
-
text-decoration: none;
|
|
24
|
-
white-space: nowrap;
|
|
25
|
-
}
|
|
26
|
-
.wb-longTaskLink:hover,
|
|
27
|
-
.wb-longTaskLink:focus-visible {
|
|
28
|
-
color: var(--dsw-alias-label-primary);
|
|
29
|
-
text-decoration: none;
|
|
30
|
-
}
|
|
31
|
-
.wb-longTaskLink:focus-visible {
|
|
32
|
-
outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
33
|
-
outline-offset: -2px;
|
|
34
19
|
}
|
|
35
20
|
|
|
36
21
|
.wb-longTaskHeadline {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { longTaskDot } from '../api/long-task.js';
|
|
2
|
+
import { Link } from './Link.js';
|
|
2
3
|
import { Fragment, h, useState } from '../lib/react.js';
|
|
3
|
-
import { CodeBlock, DisclosureRow, IconCodeOutlineRegular, IconFlatListOutlineRegular,
|
|
4
|
+
import { CodeBlock, DisclosureRow, IconCodeOutlineRegular, IconFlatListOutlineRegular, StateDot, } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
5
|
import './LongTaskRow.css';
|
|
5
6
|
function RowLink({ href, text }) {
|
|
6
|
-
return (h(
|
|
7
|
-
text,
|
|
8
|
-
h(IconRightUpOutlineRegular, { size: 12 })));
|
|
7
|
+
return (h(Link, { href: href, className: "wb-longTaskLink" }, text));
|
|
9
8
|
}
|
|
10
9
|
export function LongTaskRow(props) {
|
|
11
10
|
const [stepsOpen, setStepsOpen] = useState(false);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { DshShell } from '../api/dsh.js';
|
|
2
|
+
export interface BrowserFindEntry {
|
|
3
|
+
readonly text: string;
|
|
4
|
+
readonly tag: string;
|
|
5
|
+
readonly nth: number;
|
|
6
|
+
}
|
|
7
|
+
export interface BrowserFindResult {
|
|
8
|
+
readonly ok: boolean;
|
|
9
|
+
readonly code: string;
|
|
10
|
+
readonly at: number;
|
|
11
|
+
readonly entries: readonly BrowserFindEntry[];
|
|
12
|
+
readonly count: number;
|
|
13
|
+
readonly message: string;
|
|
14
|
+
readonly hint?: string;
|
|
15
|
+
readonly detail?: string;
|
|
16
|
+
readonly helpUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface BrowserFindOptions {
|
|
19
|
+
readonly shell: DshShell;
|
|
20
|
+
readonly css: string;
|
|
21
|
+
readonly url?: string;
|
|
22
|
+
readonly command?: string;
|
|
23
|
+
readonly session?: string;
|
|
24
|
+
readonly limit?: number;
|
|
25
|
+
readonly textMax?: number;
|
|
26
|
+
readonly open?: boolean;
|
|
27
|
+
readonly timeoutMs?: number;
|
|
28
|
+
readonly settleMs?: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function cellRowsOf(entries: readonly {
|
|
31
|
+
readonly text?: string;
|
|
32
|
+
}[], minCells?: number): string[][];
|
|
33
|
+
export declare function cellRows(text: string): string[];
|
|
34
|
+
export declare function entriesOf(value: unknown): BrowserFindEntry[];
|
|
35
|
+
export declare function reasonOf(stdout: string, stderr: string): string;
|
|
36
|
+
export declare function sameOrigin(left: string, right: string): boolean;
|
|
37
|
+
export declare function urlOf(stdout: string): string;
|
|
38
|
+
export declare function browserFind(options: BrowserFindOptions): Promise<BrowserFindResult>;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { parseJsonLoose } from '../lib/text.js';
|
|
2
|
+
import { BROWSER_BRIDGE_SESSION } from './browser-read.js';
|
|
3
|
+
import { backgroundLine } from './opencli-window.js';
|
|
4
|
+
import { runShell } from './shell.js';
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 25000;
|
|
6
|
+
const DEFAULT_SETTLE_MS = 2500;
|
|
7
|
+
const URL_TIMEOUT_MS = 8000;
|
|
8
|
+
const DEFAULT_LIMIT = 50;
|
|
9
|
+
const DEFAULT_TEXT_MAX = 200;
|
|
10
|
+
export function cellRowsOf(entries, minCells = 2) {
|
|
11
|
+
const rows = [];
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
const text = typeof entry?.text === 'string' ? entry.text : '';
|
|
14
|
+
const cells = text
|
|
15
|
+
.split('\n')
|
|
16
|
+
.map((one) => one.trim())
|
|
17
|
+
.filter((one) => one !== '');
|
|
18
|
+
if (cells.length >= minCells)
|
|
19
|
+
rows.push(cells);
|
|
20
|
+
}
|
|
21
|
+
return rows;
|
|
22
|
+
}
|
|
23
|
+
export function cellRows(text) {
|
|
24
|
+
return text
|
|
25
|
+
.split('\n')
|
|
26
|
+
.map((one) => one.trim())
|
|
27
|
+
.filter((one) => one !== '');
|
|
28
|
+
}
|
|
29
|
+
function asRecord(value) {
|
|
30
|
+
return typeof value === 'object' && value !== null ? value : undefined;
|
|
31
|
+
}
|
|
32
|
+
function textOf(value) {
|
|
33
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
34
|
+
}
|
|
35
|
+
export function entriesOf(value) {
|
|
36
|
+
const record = asRecord(value);
|
|
37
|
+
const raw = record === undefined ? undefined : record['entries'];
|
|
38
|
+
if (!Array.isArray(raw))
|
|
39
|
+
return [];
|
|
40
|
+
const entries = [];
|
|
41
|
+
for (const one of raw) {
|
|
42
|
+
const item = asRecord(one);
|
|
43
|
+
if (item === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
const nth = typeof item['nth'] === 'number' ? item['nth'] : entries.length;
|
|
46
|
+
entries.push({ text: textOf(item['text']), tag: textOf(item['tag']), nth });
|
|
47
|
+
}
|
|
48
|
+
return entries;
|
|
49
|
+
}
|
|
50
|
+
export function reasonOf(stdout, stderr) {
|
|
51
|
+
const parsed = asRecord(parseJsonLoose(stdout));
|
|
52
|
+
const error = parsed === undefined ? undefined : asRecord(parsed['error']);
|
|
53
|
+
const message = error === undefined ? '' : textOf(error['message']);
|
|
54
|
+
if (message !== '')
|
|
55
|
+
return message;
|
|
56
|
+
const source = stderr.trim() === '' ? stdout : stderr;
|
|
57
|
+
const lines = source
|
|
58
|
+
.split('\n')
|
|
59
|
+
.map((one) => one.trim())
|
|
60
|
+
.filter((one) => one !== '');
|
|
61
|
+
return (lines[lines.length - 1] ?? '').slice(-200);
|
|
62
|
+
}
|
|
63
|
+
function hintOf(stdout) {
|
|
64
|
+
const parsed = asRecord(parseJsonLoose(stdout));
|
|
65
|
+
const error = parsed === undefined ? undefined : asRecord(parsed['error']);
|
|
66
|
+
const hint = error === undefined ? '' : textOf(error['hint']);
|
|
67
|
+
return hint === '' ? undefined : hint;
|
|
68
|
+
}
|
|
69
|
+
export function sameOrigin(left, right) {
|
|
70
|
+
try {
|
|
71
|
+
return new URL(left).origin === new URL(right).origin;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function urlOf(stdout) {
|
|
78
|
+
const lines = stdout
|
|
79
|
+
.split('\n')
|
|
80
|
+
.map((one) => one.trim())
|
|
81
|
+
.filter((one) => one !== '');
|
|
82
|
+
const first = (lines[0] ?? '').replace(/^url:\s*/i, '').trim();
|
|
83
|
+
const bare = first.replace(/^["']+|["']+$/g, '');
|
|
84
|
+
if (bare.startsWith('{')) {
|
|
85
|
+
const parsed = asRecord(parseJsonLoose(bare));
|
|
86
|
+
const url = parsed === undefined ? '' : textOf(parsed['url']);
|
|
87
|
+
if (url !== '')
|
|
88
|
+
return url;
|
|
89
|
+
}
|
|
90
|
+
return bare;
|
|
91
|
+
}
|
|
92
|
+
async function currentUrl(shell, command, session) {
|
|
93
|
+
const line = backgroundLine(command, ['browser', session, 'get', 'url', '--window', 'background']);
|
|
94
|
+
try {
|
|
95
|
+
const result = await runShell(shell, line, { timeoutMs: URL_TIMEOUT_MS });
|
|
96
|
+
if (result.exitCode !== 0)
|
|
97
|
+
return '';
|
|
98
|
+
return urlOf(result.stdout);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return '';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function sleep(ms) {
|
|
105
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
106
|
+
}
|
|
107
|
+
async function findOnce(shell, command, session, css, limit, textMax, timeoutMs) {
|
|
108
|
+
const line = backgroundLine(command, [
|
|
109
|
+
'browser',
|
|
110
|
+
session,
|
|
111
|
+
'find',
|
|
112
|
+
'--css',
|
|
113
|
+
css,
|
|
114
|
+
'--limit',
|
|
115
|
+
String(limit),
|
|
116
|
+
'--text-max',
|
|
117
|
+
String(textMax),
|
|
118
|
+
'--window',
|
|
119
|
+
'background',
|
|
120
|
+
]);
|
|
121
|
+
const result = await runShell(shell, line, { timeoutMs });
|
|
122
|
+
if (result.timedOut) {
|
|
123
|
+
return { kind: 'timeout', detail: `没在 ${Math.round(timeoutMs / 1000)} 秒内返回` };
|
|
124
|
+
}
|
|
125
|
+
if (result.exitCode !== 0) {
|
|
126
|
+
return {
|
|
127
|
+
kind: 'failed',
|
|
128
|
+
detail: reasonOf(result.stdout, result.stderr),
|
|
129
|
+
...(hintOf(result.stdout) === undefined ? {} : { hint: hintOf(result.stdout) }),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const entries = entriesOf(parseJsonLoose(result.stdout));
|
|
133
|
+
return { kind: 'ok', entries, stdout: result.stdout };
|
|
134
|
+
}
|
|
135
|
+
async function openOnce(shell, command, session, url, timeoutMs) {
|
|
136
|
+
const line = backgroundLine(command, ['browser', session, 'open', url, '--window', 'background']);
|
|
137
|
+
let result;
|
|
138
|
+
try {
|
|
139
|
+
result = await runShell(shell, line, { timeoutMs });
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return error instanceof Error ? error.message : String(error);
|
|
143
|
+
}
|
|
144
|
+
if (result.timedOut)
|
|
145
|
+
return `没在 ${Math.round(timeoutMs / 1000)} 秒内打开`;
|
|
146
|
+
if (result.exitCode !== 0) {
|
|
147
|
+
const reason = reasonOf(result.stdout, result.stderr);
|
|
148
|
+
return reason === '' ? `opencli 打开失败(退出码 ${String(result.exitCode ?? '?')})` : reason;
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
export async function browserFind(options) {
|
|
153
|
+
const { shell, css, url, open = true } = options;
|
|
154
|
+
const command = options.command ?? 'opencli';
|
|
155
|
+
const session = options.session ?? BROWSER_BRIDGE_SESSION;
|
|
156
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
157
|
+
const textMax = options.textMax ?? DEFAULT_TEXT_MAX;
|
|
158
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
159
|
+
const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
|
|
160
|
+
const at = Date.now();
|
|
161
|
+
const find = async () => {
|
|
162
|
+
try {
|
|
163
|
+
return await findOnce(shell, command, session, css, limit, textMax, timeoutMs);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
return {
|
|
167
|
+
kind: 'failed',
|
|
168
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
169
|
+
hint: '确认这台机器上 opencli 能跑(工作台会自己把它挂好)',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const canMove = open && url !== undefined;
|
|
174
|
+
if (canMove) {
|
|
175
|
+
const here = await currentUrl(shell, command, session);
|
|
176
|
+
if (here !== '' && !sameOrigin(here, url)) {
|
|
177
|
+
const opened = await openOnce(shell, command, session, url, timeoutMs);
|
|
178
|
+
if (opened !== undefined) {
|
|
179
|
+
return {
|
|
180
|
+
ok: false,
|
|
181
|
+
code: 'cli-exec-failed',
|
|
182
|
+
at,
|
|
183
|
+
entries: [],
|
|
184
|
+
count: 0,
|
|
185
|
+
message: '打不开那个页面',
|
|
186
|
+
hint: '在浏览器里手动打开一次,然后重试',
|
|
187
|
+
detail: opened,
|
|
188
|
+
helpUrl: url,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
await sleep(settleMs);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
let outcome = await find();
|
|
195
|
+
if (outcome === undefined) {
|
|
196
|
+
return { ok: false, code: 'cli-exec-failed', at, entries: [], count: 0, message: '没找到元素' };
|
|
197
|
+
}
|
|
198
|
+
if (outcome.kind === 'failed' && canMove) {
|
|
199
|
+
const opened = await openOnce(shell, command, session, url, timeoutMs);
|
|
200
|
+
if (opened === undefined) {
|
|
201
|
+
await sleep(settleMs);
|
|
202
|
+
const second = await find();
|
|
203
|
+
if (second !== undefined)
|
|
204
|
+
outcome = second;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (outcome.kind === 'timeout') {
|
|
208
|
+
return {
|
|
209
|
+
ok: false,
|
|
210
|
+
code: 'cli-timeout',
|
|
211
|
+
at,
|
|
212
|
+
entries: [],
|
|
213
|
+
count: 0,
|
|
214
|
+
message: '这次没找到元素(等超时了)',
|
|
215
|
+
hint: '稍后重试',
|
|
216
|
+
detail: outcome.detail,
|
|
217
|
+
...(url === undefined ? {} : { helpUrl: url }),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (outcome.kind === 'failed') {
|
|
221
|
+
const detail = outcome.detail;
|
|
222
|
+
return {
|
|
223
|
+
ok: false,
|
|
224
|
+
code: 'cli-exec-failed',
|
|
225
|
+
at,
|
|
226
|
+
entries: [],
|
|
227
|
+
count: 0,
|
|
228
|
+
message: detail === '' ? '没找到元素(opencli 命令失败)' : `没找到元素:${detail}`,
|
|
229
|
+
...(outcome.hint === undefined ? {} : { hint: outcome.hint }),
|
|
230
|
+
...(detail === '' ? {} : { detail }),
|
|
231
|
+
...(url === undefined ? {} : { helpUrl: url }),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
ok: true,
|
|
236
|
+
code: 'ok',
|
|
237
|
+
at,
|
|
238
|
+
entries: outcome.entries,
|
|
239
|
+
count: outcome.entries.length,
|
|
240
|
+
message: outcome.entries.length === 0 ? '页面上没有匹配的元素' : `找到 ${String(outcome.entries.length)} 个元素`,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DshShell } from '../api/dsh.js';
|
|
2
|
+
export declare const BROWSER_BRIDGE_SESSION = "workbench";
|
|
3
|
+
export interface BrowserReadResult {
|
|
4
|
+
readonly ok: boolean;
|
|
5
|
+
readonly code: string;
|
|
6
|
+
readonly at: number;
|
|
7
|
+
readonly value?: unknown;
|
|
8
|
+
readonly pageCode?: string;
|
|
9
|
+
readonly message: string;
|
|
10
|
+
readonly hint?: string;
|
|
11
|
+
readonly detail?: string;
|
|
12
|
+
readonly helpUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface BrowserReadOptions {
|
|
15
|
+
readonly shell: DshShell;
|
|
16
|
+
readonly script: string;
|
|
17
|
+
readonly url?: string;
|
|
18
|
+
readonly loginUrl?: string;
|
|
19
|
+
readonly command?: string;
|
|
20
|
+
readonly session?: string;
|
|
21
|
+
readonly open?: boolean;
|
|
22
|
+
readonly timeoutMs?: number;
|
|
23
|
+
readonly retryMs?: number;
|
|
24
|
+
readonly settleMs?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function browserRead(options: BrowserReadOptions): Promise<BrowserReadResult>;
|