@dsh-workbench/shared 0.3.5 → 0.3.7
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/api/context-ref.d.ts +0 -12
- package/dist/api/context-ref.js +0 -40
- package/dist/api/facts.d.ts +0 -6
- package/dist/api/facts.js +0 -8
- package/dist/api/long-task.d.ts +33 -4
- package/dist/api/long-task.js +34 -2
- package/dist/components/ConfirmBar.css +26 -0
- package/dist/components/ConfirmBar.d.ts +36 -0
- package/dist/components/ConfirmBar.js +26 -0
- package/dist/components/Form.d.ts +7 -5
- package/dist/components/Form.js +5 -3
- package/dist/components/FormNodes.d.ts +17 -13
- package/dist/components/FormNodes.js +13 -5
- package/dist/components/FormRows.css +31 -0
- package/dist/components/FormTypes.d.ts +39 -14
- package/dist/components/LongTaskRow.css +52 -0
- package/dist/components/LongTaskRow.d.ts +18 -2
- package/dist/components/LongTaskRow.js +33 -5
- package/dist/components/MockStrip.css +11 -1
- package/dist/components/StateDot.d.ts +5 -0
- package/dist/components/StateDot.js +10 -0
- package/dist/config/constants.d.ts +2 -0
- package/dist/config/constants.js +2 -0
- package/dist/host/__tests__/fakes.d.ts +41 -0
- package/dist/host/__tests__/fakes.js +76 -0
- package/dist/host/__tests__/preflight-fixtures.d.ts +19 -0
- package/dist/host/__tests__/preflight-fixtures.js +47 -0
- package/dist/host/board-fact.d.ts +8 -0
- package/dist/host/board-fact.js +16 -0
- package/dist/host/browser-read.js +2 -1
- package/dist/host/context-inject.d.ts +24 -0
- package/dist/host/context-inject.js +74 -0
- package/dist/host/context-ref.d.ts +13 -0
- package/dist/host/context-ref.js +41 -0
- package/dist/host/diy-debug.d.ts +80 -0
- package/dist/host/diy-debug.js +88 -0
- package/dist/host/diy-record.d.ts +135 -0
- package/dist/host/diy-record.js +162 -0
- package/dist/host/events.d.ts +9 -0
- package/dist/host/events.js +70 -0
- package/dist/host/facts.d.ts +6 -0
- package/dist/host/facts.js +9 -0
- package/dist/host/git.js +1 -1
- package/dist/host/http.d.ts +2 -2
- package/dist/host/http.js +3 -3
- package/dist/host/long-task.d.ts +12 -0
- package/dist/host/long-task.js +34 -5
- package/dist/host/mount-units.d.ts +52 -0
- package/dist/host/mount-units.js +186 -0
- package/dist/host/opencli-runtime.js +1 -1
- package/dist/host/opencli-window.js +1 -1
- package/dist/host/opencli.js +2 -1
- package/dist/host/panel-state.d.ts +120 -0
- package/dist/host/panel-state.js +176 -0
- package/dist/host/plugin-source.js +1 -1
- package/dist/host/preflight.d.ts +58 -0
- package/dist/host/preflight.js +233 -0
- package/dist/host/probe-cache.d.ts +6 -0
- package/dist/host/probe-cache.js +19 -0
- package/dist/host/probe-libs.d.ts +45 -0
- package/dist/host/probe-libs.js +61 -0
- package/dist/host/probe-units.d.ts +17 -0
- package/dist/host/probe-units.js +102 -0
- package/dist/host/probe-verdict.d.ts +7 -0
- package/dist/host/probe-verdict.js +143 -0
- package/dist/host/shell-text.d.ts +13 -0
- package/dist/host/shell-text.js +20 -0
- package/dist/host/tool-approval.d.ts +1 -0
- package/dist/host/tool-scope.d.ts +32 -0
- package/dist/host/tool-scope.js +84 -0
- package/dist/host/unit-declaration.d.ts +65 -0
- package/dist/host/unit-declaration.js +213 -0
- package/dist/host/unit-face.d.ts +1 -0
- package/dist/host/unit-readiness.d.ts +7 -0
- package/dist/host/unit-readiness.js +5 -0
- package/dist/host/unit-registry.d.ts +9 -0
- package/dist/host/unit-registry.js +33 -0
- package/dist/host/version-range.d.ts +2 -0
- package/dist/host/version-range.js +73 -0
- package/dist/host.d.ts +7 -4
- package/dist/host.js +7 -4
- package/dist/i18n/en.js +6 -0
- package/dist/i18n/index.d.ts +7 -1
- package/dist/i18n/zh.d.ts +6 -0
- package/dist/i18n/zh.js +6 -0
- package/dist/index.d.ts +1 -4
- package/dist/index.js +1 -4
- package/dist/lib/board-fact.d.ts +0 -7
- package/dist/lib/board-fact.js +0 -15
- package/dist/lib/diy-record.d.ts +38 -1
- package/dist/lib/diy-record.js +66 -0
- package/dist/lib/text.d.ts +0 -13
- package/dist/lib/text.js +0 -20
- package/dist/testing/declaration.js +1 -1
- package/package.json +1 -1
- package/src/api/__tests__/facts.test.ts +0 -13
- package/src/api/__tests__/long-task.test.ts +41 -1
- package/src/api/context-ref.ts +0 -39
- package/src/api/facts.ts +0 -9
- package/src/api/long-task.ts +46 -4
- package/src/components/ConfirmBar.css +26 -0
- package/src/components/ConfirmBar.test.tsx +130 -0
- package/src/components/ConfirmBar.tsx +63 -0
- package/src/components/Form.test.tsx +15 -0
- package/src/components/Form.tsx +7 -5
- package/src/components/FormNodes.tsx +50 -26
- package/src/components/FormRows.css +31 -0
- package/src/components/FormRows.test.tsx +71 -1
- package/src/components/FormTypes.test.ts +31 -0
- package/src/components/FormTypes.ts +46 -15
- package/src/components/FormWiring.test.tsx +1 -0
- package/src/components/LongTaskRow.css +52 -0
- package/src/components/LongTaskRow.tsx +59 -4
- package/src/components/__tests__/long-task-row.test.ts +115 -0
- package/src/config/constants.ts +3 -0
- package/src/host/__tests__/board-fact.test.ts +47 -0
- package/src/{lib → host/__tests__}/diy-debug.test.ts +1 -1
- package/src/{lib → host/__tests__}/diy-record.test.ts +137 -1
- package/src/host/__tests__/facts.test.ts +13 -0
- package/src/host/__tests__/long-task.test.ts +306 -0
- package/src/{lib → host/__tests__}/panel-state.test.ts +1 -1
- package/src/host/__tests__/preflight-route.test.ts +33 -0
- package/src/host/__tests__/shell-text.test.ts +44 -0
- package/src/host/board-fact.ts +14 -0
- package/src/host/browser-read.ts +2 -1
- package/src/host/context-ref.ts +40 -0
- package/src/{lib → host}/diy-record.ts +95 -1
- package/src/host/facts.ts +10 -0
- package/src/host/git.ts +1 -1
- package/src/host/http.ts +4 -4
- package/src/host/long-task.ts +52 -7
- package/src/host/opencli-runtime.ts +1 -1
- package/src/host/opencli-window.ts +1 -1
- package/src/host/opencli.ts +2 -1
- package/src/host/plugin-source.ts +1 -1
- package/src/host/shell-text.ts +21 -0
- package/src/host/tool-approval.ts +1 -0
- package/src/{api → host}/unit-declaration.ts +1 -1
- package/src/host/unit-face.ts +1 -0
- package/src/host.ts +7 -4
- package/src/i18n/en.ts +7 -0
- package/src/i18n/zh.ts +6 -0
- package/src/index.ts +1 -4
- package/src/lib/board-fact.test.ts +1 -33
- package/src/lib/board-fact.ts +0 -13
- package/src/lib/text.test.ts +1 -43
- package/src/lib/text.ts +0 -22
- package/src/testing/declaration.ts +1 -1
- package/dist/lib/lint-probe.d.ts +0 -1
- package/dist/lib/lint-probe.js +0 -6
- /package/src/{api → host}/__tests__/unit-declaration.test.ts +0 -0
- /package/src/{lib → host}/diy-debug.ts +0 -0
- /package/src/{lib → host}/panel-state.ts +0 -0
|
@@ -45,4 +45,56 @@
|
|
|
45
45
|
margin: var(--wb-len-4) 0 0;
|
|
46
46
|
max-height: var(--wb-len-220);
|
|
47
47
|
overflow: auto;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.wb-longTaskStop {
|
|
51
|
+
display: inline-flex;
|
|
52
|
+
flex: none;
|
|
53
|
+
align-items: center;
|
|
54
|
+
align-self: center;
|
|
55
|
+
justify-content: center;
|
|
56
|
+
width: var(--wb-len-20);
|
|
57
|
+
height: var(--wb-len-20);
|
|
58
|
+
padding: 0;
|
|
59
|
+
margin-left: auto;
|
|
60
|
+
border: 0.5px solid var(--dsw-alias-border-l2);
|
|
61
|
+
border-radius: var(--dsw-radius-sm);
|
|
62
|
+
background: var(--dsw-specific-menu);
|
|
63
|
+
color: var(--dsw-alias-label-secondary);
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.wb-longTaskStop:hover:not(:disabled),
|
|
68
|
+
.wb-longTaskStop:focus-visible {
|
|
69
|
+
border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent);
|
|
70
|
+
color: var(--dsw-alias-state-error-primary);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.wb-longTaskStop:disabled {
|
|
74
|
+
cursor: default;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.wb-longTaskStopArmed,
|
|
78
|
+
.wb-longTaskStopArmed:hover:not(:disabled),
|
|
79
|
+
.wb-longTaskStopArmed:focus-visible {
|
|
80
|
+
gap: var(--wb-len-4);
|
|
81
|
+
width: auto;
|
|
82
|
+
padding: 0 var(--wb-len-7);
|
|
83
|
+
border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent);
|
|
84
|
+
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent);
|
|
85
|
+
color: var(--dsw-alias-state-error-primary);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.wb-longTaskStopLabel {
|
|
89
|
+
white-space: nowrap;
|
|
90
|
+
font-size: var(--dsw-font-xxxs-11-font-size);
|
|
91
|
+
line-height: var(--wb-line-18);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.wb-longTaskStopWhy {
|
|
95
|
+
margin: 0;
|
|
96
|
+
font-size: var(--dsw-font-xxxs-11-font-size);
|
|
97
|
+
line-height: var(--wb-line-16);
|
|
98
|
+
color: var(--dsw-alias-state-error-primary);
|
|
99
|
+
overflow-wrap: anywhere;
|
|
48
100
|
}
|
|
@@ -8,7 +8,21 @@ export interface LongTaskRowLink {
|
|
|
8
8
|
readonly text: string;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* `LongTaskRow`
|
|
11
|
+
* `LongTaskRow` 那颗两按停止钮的契约:确认之后真发的那一下、三句词、在飞与失败两态。
|
|
12
|
+
* `label` 是没上膛时那颗钮的 aria-label(dsh 的词:`停止任务 {label}`);`askLabel` 是上膛时那颗钮上显示的字(`确认停止`);
|
|
13
|
+
* `armedLabel` 是上膛时的 aria-label,不给就用 `askLabel`(`再次点击确认停止`);`busy` = 这一趟正在被停、钮禁用;
|
|
14
|
+
* `failed` 给了才在问题那行后面再画一行。
|
|
15
|
+
*/
|
|
16
|
+
export interface LongTaskRowStop {
|
|
17
|
+
readonly onStop: () => void;
|
|
18
|
+
readonly label: string;
|
|
19
|
+
readonly askLabel: string;
|
|
20
|
+
readonly armedLabel?: string | undefined;
|
|
21
|
+
readonly busy?: boolean | undefined;
|
|
22
|
+
readonly failed?: string | null | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `LongTaskRow` 的契约:状态、标题、问题、链接,两处可展开的原始输出,与最右边那颗可选的停止钮。
|
|
12
26
|
*/
|
|
13
27
|
export interface LongTaskRowProps {
|
|
14
28
|
readonly status: LongTaskStatus;
|
|
@@ -21,11 +35,12 @@ export interface LongTaskRowProps {
|
|
|
21
35
|
readonly rawTitle?: string | undefined;
|
|
22
36
|
readonly copyLabel?: string | undefined;
|
|
23
37
|
readonly copiedLabel?: string | undefined;
|
|
38
|
+
readonly stop?: LongTaskRowStop | undefined;
|
|
24
39
|
}
|
|
25
40
|
/**
|
|
26
41
|
* 长任务那一行:状态点 + 标题 + 链接,跑失败时把步骤与原始输出收在两处可展开里。
|
|
27
42
|
* 发布、扫描、迁移这类跑得久的事都用它,别自己画进度。
|
|
28
|
-
* @param props.status - `longTaskDot`
|
|
43
|
+
* @param props.status - `longTaskDot` 认的五态之一,决定状态点颜色与要不要画"失败"那两块。
|
|
29
44
|
* @param props.headline - 这一行在干什么(一句话)。
|
|
30
45
|
* @param props.problem - 失败原因;不给不画。
|
|
31
46
|
* @param props.links - 行尾的链接,通常指向产物或日志。
|
|
@@ -33,6 +48,7 @@ export interface LongTaskRowProps {
|
|
|
33
48
|
* @param props.raw - 原始输出;只在失败时画,且要同时给 `rawTitle` 才成一块可展开。
|
|
34
49
|
* @param props.stepsTitle - 步骤那块展开行的标题;`rawTitle` 同理是原始输出那块。
|
|
35
50
|
* @param props.copyLabel - 复制钮文案:给了才画复制钮;`copiedLabel` 是复制成功后那句。
|
|
51
|
+
* @param props.stop - 给了才在最右边画那颗两按停止钮;**不给就一个像素都不变**。只在生产方认 `signal` 时给。
|
|
36
52
|
* @returns 一行长任务。
|
|
37
53
|
*/
|
|
38
54
|
export declare function LongTaskRow(props: LongTaskRowProps): JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { longTaskDot } from '../api/long-task.js';
|
|
1
|
+
import { longTaskDot, longTaskStopPress, longTaskStopStage, LONG_TASK_STOP_ARM_MS } from '../api/long-task.js';
|
|
2
2
|
import { Link } from './Link.js';
|
|
3
|
-
import { Fragment, h, useState } from '../lib/react.js';
|
|
4
|
-
import { CodeBlock, DisclosureRow, IconCodeOutlineRegular, IconFlatListOutlineRegular, StateDot, } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
import { Fragment, h, useEffect, useState } from '../lib/react.js';
|
|
4
|
+
import { CodeBlock, DisclosureRow, IconCodeOutlineRegular, IconFlatListOutlineRegular, IconStopFillRegular, StateDot, } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
5
5
|
import './LongTaskRow.css';
|
|
6
6
|
function RowLink({ href, text }) {
|
|
7
7
|
return (h(Link, { href: href, className: "wb-longTaskLink" }, text));
|
|
@@ -9,7 +9,7 @@ function RowLink({ href, text }) {
|
|
|
9
9
|
/**
|
|
10
10
|
* 长任务那一行:状态点 + 标题 + 链接,跑失败时把步骤与原始输出收在两处可展开里。
|
|
11
11
|
* 发布、扫描、迁移这类跑得久的事都用它,别自己画进度。
|
|
12
|
-
* @param props.status - `longTaskDot`
|
|
12
|
+
* @param props.status - `longTaskDot` 认的五态之一,决定状态点颜色与要不要画"失败"那两块。
|
|
13
13
|
* @param props.headline - 这一行在干什么(一句话)。
|
|
14
14
|
* @param props.problem - 失败原因;不给不画。
|
|
15
15
|
* @param props.links - 行尾的链接,通常指向产物或日志。
|
|
@@ -17,21 +17,49 @@ function RowLink({ href, text }) {
|
|
|
17
17
|
* @param props.raw - 原始输出;只在失败时画,且要同时给 `rawTitle` 才成一块可展开。
|
|
18
18
|
* @param props.stepsTitle - 步骤那块展开行的标题;`rawTitle` 同理是原始输出那块。
|
|
19
19
|
* @param props.copyLabel - 复制钮文案:给了才画复制钮;`copiedLabel` 是复制成功后那句。
|
|
20
|
+
* @param props.stop - 给了才在最右边画那颗两按停止钮;**不给就一个像素都不变**。只在生产方认 `signal` 时给。
|
|
20
21
|
* @returns 一行长任务。
|
|
21
22
|
*/
|
|
22
23
|
export function LongTaskRow(props) {
|
|
23
24
|
const [stepsOpen, setStepsOpen] = useState(false);
|
|
24
25
|
const [rawOpen, setRawOpen] = useState(false);
|
|
26
|
+
const [armed, setArmed] = useState(false);
|
|
25
27
|
const failed = props.status === 'failed';
|
|
26
28
|
const problem = props.problem ?? '';
|
|
27
29
|
const raw = (props.raw ?? '').trim();
|
|
28
30
|
const steps = props.steps ?? [];
|
|
31
|
+
const stop = props.stop;
|
|
32
|
+
const busy = stop?.busy === true;
|
|
33
|
+
const stopWhy = stop?.failed ?? '';
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!armed)
|
|
36
|
+
return undefined;
|
|
37
|
+
const timer = setTimeout(() => setArmed(false), LONG_TASK_STOP_ARM_MS);
|
|
38
|
+
return () => clearTimeout(timer);
|
|
39
|
+
}, [armed]);
|
|
40
|
+
const onStopPress = () => {
|
|
41
|
+
if (stop === undefined)
|
|
42
|
+
return;
|
|
43
|
+
const press = longTaskStopPress(longTaskStopStage({ armed, busy }));
|
|
44
|
+
if (press === 'ignore')
|
|
45
|
+
return;
|
|
46
|
+
if (press === 'arm') {
|
|
47
|
+
setArmed(true);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
setArmed(false);
|
|
51
|
+
stop.onStop();
|
|
52
|
+
};
|
|
29
53
|
return (h("div", { className: "wb-longTask" },
|
|
30
54
|
h("p", { className: "wb-longTaskHead" },
|
|
31
55
|
h(StateDot, { state: longTaskDot(props.status), size: 10 }),
|
|
32
56
|
h("span", { className: "wb-longTaskHeadline" }, props.headline),
|
|
33
|
-
(props.links ?? []).map((link) => (h(RowLink, { href: link.href, key: `${link.text}@${link.href}`, text: link.text })))
|
|
57
|
+
(props.links ?? []).map((link) => (h(RowLink, { href: link.href, key: `${link.text}@${link.href}`, text: link.text }))),
|
|
58
|
+
stop === undefined ? null : (h("button", { type: "button", className: `wb-longTaskStop${armed ? ' wb-longTaskStopArmed' : ''}`, "data-stop-state": longTaskStopStage({ armed, busy }), "aria-label": armed ? stop.armedLabel ?? stop.askLabel : stop.label, disabled: busy, onClick: onStopPress, onBlur: () => setArmed(false) },
|
|
59
|
+
h(IconStopFillRegular, { size: 12 }),
|
|
60
|
+
armed ? h("span", { className: "wb-longTaskStopLabel" }, stop.askLabel) : null))),
|
|
34
61
|
problem === '' ? null : h("p", { className: "wb-longTaskProblem" }, problem),
|
|
62
|
+
stopWhy === '' ? null : h("p", { className: "wb-longTaskStopWhy" }, stopWhy),
|
|
35
63
|
!failed ? null : (h(Fragment, null,
|
|
36
64
|
steps.length === 0 || props.stepsTitle === undefined ? null : (h(DisclosureRow, { className: "wb-longTaskDump", icon: h(IconFlatListOutlineRegular, { size: 14 }), title: props.stepsTitle, open: stepsOpen, expandable: true, expandOnRowClick: true, previewChevron: true, onToggle: () => setStepsOpen(!stepsOpen) },
|
|
37
65
|
h(CodeBlock, { code: steps.join('\n'), className: "wb-longTaskDump", showHeader: props.copyLabel !== undefined, copyLabel: props.copyLabel ?? '', copiedLabel: props.copiedLabel ?? '' }))),
|
|
@@ -36,4 +36,14 @@
|
|
|
36
36
|
border-color: transparent;
|
|
37
37
|
background: var(--dsw-alias-brand-primary);
|
|
38
38
|
color: var(--dsw-alias-label-primary-foreground);
|
|
39
|
-
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.wb-mockChipOn:hover {
|
|
42
|
+
border-color: transparent;
|
|
43
|
+
background: var(--dsw-alias-brand-primary);
|
|
44
|
+
color: var(--dsw-alias-label-primary-foreground);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.wb-panelBody.wb-panelMany .wb-mockStrip {
|
|
48
|
+
display: none;
|
|
49
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { h } from '../lib/react.js';
|
|
2
|
+
export function StateDot({ state, size }) {
|
|
3
|
+
if (state === 'ongoing') {
|
|
4
|
+
return (h("svg", { className: "wb-loading", viewBox: "0 0 24 24", width: size, height: size, "aria-hidden": "true", style: { display: 'block' } },
|
|
5
|
+
h("g", { className: "wb-loadingMotion" },
|
|
6
|
+
h("circle", { className: "wb-loadingTrack", cx: 12, cy: 12, r: 9.5 }),
|
|
7
|
+
h("circle", { className: "wb-loadingArc", cx: 12, cy: 12, r: 9.5 }))));
|
|
8
|
+
}
|
|
9
|
+
return (h("span", { className: "wb-stateDot", "data-state": state, style: { width: size, height: size }, "aria-hidden": "true" }));
|
|
10
|
+
}
|
|
@@ -48,6 +48,8 @@ export declare const OPENCLI_TIMEOUT_MS = 120000;
|
|
|
48
48
|
export declare const OPENCLI_MAX_CONCURRENT = 4;
|
|
49
49
|
/** 启动预热站点那一跳的超时(毫秒);超时按未热对待,不挡面板。 */
|
|
50
50
|
export declare const WARM_TIMEOUT_MS = 20000;
|
|
51
|
+
/** 体检结果算「还新鲜」的时长(毫秒):期内再来的强制体检直接回上一次那份快照。 */
|
|
52
|
+
export declare const PREFLIGHT_FRESH_MS = 30000;
|
|
51
53
|
/** 装 / 卸模块包的超时(毫秒):pnpm 与取包内容都按它算。 */
|
|
52
54
|
export declare const INSTALL_TIMEOUT_MS = 300000;
|
|
53
55
|
/** 查 registry 版本那一次请求的超时(毫秒)。 */
|
package/dist/config/constants.js
CHANGED
|
@@ -51,6 +51,8 @@ export const OPENCLI_TIMEOUT_MS = 120_000;
|
|
|
51
51
|
export const OPENCLI_MAX_CONCURRENT = 4;
|
|
52
52
|
/** 启动预热站点那一跳的超时(毫秒);超时按未热对待,不挡面板。 */
|
|
53
53
|
export const WARM_TIMEOUT_MS = 20_000;
|
|
54
|
+
/** 体检结果算「还新鲜」的时长(毫秒):期内再来的强制体检直接回上一次那份快照。 */
|
|
55
|
+
export const PREFLIGHT_FRESH_MS = 30_000;
|
|
54
56
|
/** 装 / 卸模块包的超时(毫秒):pnpm 与取包内容都按它算。 */
|
|
55
57
|
export const INSTALL_TIMEOUT_MS = 300_000;
|
|
56
58
|
/** 查 registry 版本那一次请求的超时(毫秒)。 */
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { DshShell } from '../../api/dsh.js';
|
|
2
|
+
import type { Opencli } from '../../host/opencli.js';
|
|
3
|
+
export interface ShellCall {
|
|
4
|
+
command: string;
|
|
5
|
+
workdir?: string;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
onExpiry?: string;
|
|
8
|
+
stdoutMaxBytes?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ShellReply {
|
|
11
|
+
stdout?: string;
|
|
12
|
+
stderr?: string;
|
|
13
|
+
exitCode?: number | null;
|
|
14
|
+
timedOut?: boolean;
|
|
15
|
+
aborted?: boolean;
|
|
16
|
+
failWith?: Error;
|
|
17
|
+
}
|
|
18
|
+
export declare function createFakeShell(replies?: ShellReply[] | ((call: ShellCall, index: number) => ShellReply)): {
|
|
19
|
+
shell: DshShell;
|
|
20
|
+
calls: ShellCall[];
|
|
21
|
+
peak: () => number;
|
|
22
|
+
commands: () => string[];
|
|
23
|
+
};
|
|
24
|
+
export interface OpencliCall {
|
|
25
|
+
args: readonly string[];
|
|
26
|
+
key?: string;
|
|
27
|
+
workdir?: string;
|
|
28
|
+
refresh?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare function createFakeOpencli(replies?: Array<unknown | Error> | ((call: OpencliCall, index: number) => unknown | Error)): {
|
|
31
|
+
opencli: Opencli;
|
|
32
|
+
calls: OpencliCall[];
|
|
33
|
+
keys: () => (string | undefined)[];
|
|
34
|
+
};
|
|
35
|
+
export declare function createFakeLogger(): {
|
|
36
|
+
logger: {
|
|
37
|
+
info: () => void;
|
|
38
|
+
warn: (message: string) => void;
|
|
39
|
+
};
|
|
40
|
+
warnings: string[];
|
|
41
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
+
const tick = () => delay(0);
|
|
3
|
+
export function createFakeShell(replies = []) {
|
|
4
|
+
const calls = [];
|
|
5
|
+
let running = 0;
|
|
6
|
+
let peak = 0;
|
|
7
|
+
const replyAt = (index) => typeof replies === 'function' ? replies(calls[index], index) : (replies[index] ?? {});
|
|
8
|
+
const shell = {
|
|
9
|
+
resolve(request) {
|
|
10
|
+
calls.push(request);
|
|
11
|
+
return request;
|
|
12
|
+
},
|
|
13
|
+
async execute() {
|
|
14
|
+
const index = calls.length - 1;
|
|
15
|
+
const reply = replyAt(index);
|
|
16
|
+
if (reply.failWith !== undefined)
|
|
17
|
+
throw reply.failWith;
|
|
18
|
+
running += 1;
|
|
19
|
+
peak = Math.max(peak, running);
|
|
20
|
+
return {
|
|
21
|
+
async result() {
|
|
22
|
+
await tick();
|
|
23
|
+
running -= 1;
|
|
24
|
+
return {
|
|
25
|
+
exitCode: reply.exitCode ?? 0,
|
|
26
|
+
timedOut: reply.timedOut ?? false,
|
|
27
|
+
aborted: reply.aborted ?? false,
|
|
28
|
+
stdout: { text: reply.stdout ?? '', truncated: false },
|
|
29
|
+
stderr: { text: reply.stderr ?? '', truncated: false },
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
shell,
|
|
37
|
+
calls,
|
|
38
|
+
peak: () => peak,
|
|
39
|
+
commands: () => calls.map((call) => call.command),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function createFakeOpencli(replies = []) {
|
|
43
|
+
const calls = [];
|
|
44
|
+
const opencli = {
|
|
45
|
+
async json(args, options = {}) {
|
|
46
|
+
const call = {
|
|
47
|
+
args,
|
|
48
|
+
...(options.key === undefined ? {} : { key: options.key }),
|
|
49
|
+
...(options.workdir === undefined ? {} : { workdir: options.workdir }),
|
|
50
|
+
};
|
|
51
|
+
calls.push(call);
|
|
52
|
+
const index = calls.length - 1;
|
|
53
|
+
const reply = typeof replies === 'function' ? replies(call, index) : (replies[index] ?? []);
|
|
54
|
+
if (reply instanceof Error)
|
|
55
|
+
throw reply;
|
|
56
|
+
return { value: reply, at: Date.now() };
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
opencli,
|
|
61
|
+
calls,
|
|
62
|
+
keys: () => calls.map((call) => call.key),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function createFakeLogger() {
|
|
66
|
+
const warnings = [];
|
|
67
|
+
return {
|
|
68
|
+
logger: {
|
|
69
|
+
info: () => { },
|
|
70
|
+
warn: (message) => {
|
|
71
|
+
warnings.push(message);
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
warnings,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createFakeShell } from './fakes';
|
|
2
|
+
export declare const GOOD_BRIDGE_REPORT: string;
|
|
3
|
+
export declare const ALL_OK: {
|
|
4
|
+
stdout: string;
|
|
5
|
+
}[];
|
|
6
|
+
export declare const STALE_DAEMON_BRIDGE: string;
|
|
7
|
+
export declare const BROKEN_BRIDGE: string;
|
|
8
|
+
export declare function build(replies: Parameters<typeof createFakeShell>[0], install?: () => Promise<{
|
|
9
|
+
ok: boolean;
|
|
10
|
+
detail: string;
|
|
11
|
+
}>, warmReads?: () => readonly (readonly string[])[]): {
|
|
12
|
+
fake: {
|
|
13
|
+
shell: import("../..").DshShell;
|
|
14
|
+
calls: import("./fakes").ShellCall[];
|
|
15
|
+
peak: () => number;
|
|
16
|
+
commands: () => string[];
|
|
17
|
+
};
|
|
18
|
+
preflight: import("../preflight").Preflight;
|
|
19
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createPreflight } from '../preflight';
|
|
2
|
+
import { createFakeShell } from './fakes';
|
|
3
|
+
export const GOOD_BRIDGE_REPORT = [
|
|
4
|
+
'opencli v1.8.7 doctor (node v24.15.0)',
|
|
5
|
+
'',
|
|
6
|
+
'[OK] Daemon: running on port 19825 (v1.8.7)',
|
|
7
|
+
'[OK] Extension: connected (v1.0.24)',
|
|
8
|
+
'[OK] Connectivity: connected in 0.0s',
|
|
9
|
+
'',
|
|
10
|
+
'Everything looks good!',
|
|
11
|
+
].join('\n');
|
|
12
|
+
export const ALL_OK = [{ stdout: '1.8.8\n' }, { stdout: GOOD_BRIDGE_REPORT }];
|
|
13
|
+
export const STALE_DAEMON_BRIDGE = [
|
|
14
|
+
'opencli v1.8.7 doctor (node v24.15.0)',
|
|
15
|
+
'',
|
|
16
|
+
'[STALE] Daemon: running on port 19825 (v1.8.8 != CLI v1.8.7)',
|
|
17
|
+
'[OK] Extension: connected (v1.0.24)',
|
|
18
|
+
'[OK] Connectivity: connected in 0.0s',
|
|
19
|
+
'',
|
|
20
|
+
'Issues:',
|
|
21
|
+
' • Stale daemon detected: daemon v1.8.8 != CLI v1.8.7.',
|
|
22
|
+
' Run: opencli daemon restart',
|
|
23
|
+
].join('\n');
|
|
24
|
+
export const BROKEN_BRIDGE = [
|
|
25
|
+
'opencli v1.8.7 doctor (node v24.15.0)',
|
|
26
|
+
'',
|
|
27
|
+
'[MISSING] Daemon: not running',
|
|
28
|
+
'[MISSING] Extension: not connected',
|
|
29
|
+
'[FAIL] Connectivity: failed (daemon not reachable)',
|
|
30
|
+
'',
|
|
31
|
+
'Issues:',
|
|
32
|
+
" • Daemon is not running. It should start automatically when you run an opencli browser command.",
|
|
33
|
+
' • Daemon is running but the Chrome/Chromium extension is not connected.',
|
|
34
|
+
'If the extension is not installed:',
|
|
35
|
+
' 1. Download from https://github.com/jackwener/opencli/releases',
|
|
36
|
+
' 2. Open chrome://extensions/ → Enable Developer Mode',
|
|
37
|
+
' 3. Click "Load unpacked" → select the extension folder',
|
|
38
|
+
].join('\n');
|
|
39
|
+
export function build(replies, install, warmReads) {
|
|
40
|
+
const fake = createFakeShell(replies);
|
|
41
|
+
const preflight = createPreflight({
|
|
42
|
+
shell: fake.shell,
|
|
43
|
+
...(install === undefined ? {} : { install }),
|
|
44
|
+
...(warmReads === undefined ? {} : { warmReads }),
|
|
45
|
+
});
|
|
46
|
+
return { fake, preflight };
|
|
47
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type BoardFactState, type BoardProbe } from '../lib/board-fact.js';
|
|
2
|
+
/**
|
|
3
|
+
* 把一次探针读数归成椅面归属。
|
|
4
|
+
* @param probe - 探针读数。
|
|
5
|
+
* @param workshop - 当前工作区根路径;`null` 表示还不知道当前是哪个工作区。
|
|
6
|
+
* @returns 外壳不在、或板路径与工作区缺一 → `unknown`;右栏没开或没画出来 → `hidden`;板在本工作区 → `workshop`,否则 `other`。
|
|
7
|
+
*/
|
|
8
|
+
export declare function boardFactOf(probe: BoardProbe, workshop: string | null): BoardFactState;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { insideWorkshop } from '../lib/board-fact.js';
|
|
2
|
+
/**
|
|
3
|
+
* 把一次探针读数归成椅面归属。
|
|
4
|
+
* @param probe - 探针读数。
|
|
5
|
+
* @param workshop - 当前工作区根路径;`null` 表示还不知道当前是哪个工作区。
|
|
6
|
+
* @returns 外壳不在、或板路径与工作区缺一 → `unknown`;右栏没开或没画出来 → `hidden`;板在本工作区 → `workshop`,否则 `other`。
|
|
7
|
+
*/
|
|
8
|
+
export function boardFactOf(probe, workshop) {
|
|
9
|
+
if (!probe.workbench)
|
|
10
|
+
return 'unknown';
|
|
11
|
+
if (!probe.bar || !probe.open || !probe.drawn)
|
|
12
|
+
return 'hidden';
|
|
13
|
+
if (probe.board === '' || workshop === null)
|
|
14
|
+
return 'unknown';
|
|
15
|
+
return insideWorkshop(workshop, probe.board) ? 'workshop' : 'other';
|
|
16
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { parseJsonLoose
|
|
1
|
+
import { parseJsonLoose } from '../lib/text.js';
|
|
2
|
+
import { tailLines } from './shell-text.js';
|
|
2
3
|
import { backgroundLine } from './opencli-window.js';
|
|
3
4
|
import { runShell } from './shell.js';
|
|
4
5
|
export const BROWSER_SCREEN_SESSION = 'workbench';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ContextRef } from '@dsh-workbench/shared';
|
|
2
|
+
import type { ContextRenderer } from './unit-face';
|
|
3
|
+
export declare const CONTEXT_SOURCE_KIND = "workbench";
|
|
4
|
+
export interface ScannableMessage {
|
|
5
|
+
readonly source?: {
|
|
6
|
+
readonly kind?: string;
|
|
7
|
+
};
|
|
8
|
+
readonly content?: readonly {
|
|
9
|
+
readonly type?: string;
|
|
10
|
+
readonly text?: string;
|
|
11
|
+
}[];
|
|
12
|
+
}
|
|
13
|
+
export type { ContextRenderer };
|
|
14
|
+
export declare function collectContextRefs(messages: readonly ScannableMessage[]): ContextRef[];
|
|
15
|
+
export declare function buildContextMessage(text: string, summary: string): unknown;
|
|
16
|
+
export interface ContextInjector {
|
|
17
|
+
inject(messages: readonly ScannableMessage[], agent: unknown): Promise<unknown | undefined>;
|
|
18
|
+
}
|
|
19
|
+
export declare function createContextInjector(options: {
|
|
20
|
+
renderers: ReadonlyMap<string, ContextRenderer>;
|
|
21
|
+
logger?: {
|
|
22
|
+
warn?(message: string): void;
|
|
23
|
+
} | undefined;
|
|
24
|
+
}): ContextInjector;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { encodeRef, parseMarkers } from '@dsh-workbench/shared';
|
|
3
|
+
export const CONTEXT_SOURCE_KIND = 'workbench';
|
|
4
|
+
export function collectContextRefs(messages) {
|
|
5
|
+
const seen = new Set();
|
|
6
|
+
const refs = [];
|
|
7
|
+
for (const message of messages) {
|
|
8
|
+
if (message.source?.kind !== 'user')
|
|
9
|
+
continue;
|
|
10
|
+
for (const block of message.content ?? []) {
|
|
11
|
+
if (block.type !== 'text' || typeof block.text !== 'string')
|
|
12
|
+
continue;
|
|
13
|
+
for (const ref of parseMarkers(block.text)) {
|
|
14
|
+
const key = encodeRef(ref);
|
|
15
|
+
if (seen.has(key))
|
|
16
|
+
continue;
|
|
17
|
+
seen.add(key);
|
|
18
|
+
refs.push(ref);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return refs;
|
|
23
|
+
}
|
|
24
|
+
export function buildContextMessage(text, summary) {
|
|
25
|
+
return {
|
|
26
|
+
role: 'user',
|
|
27
|
+
id: randomUUID(),
|
|
28
|
+
content: [{ type: 'text', text }],
|
|
29
|
+
source: { kind: CONTEXT_SOURCE_KIND, form: 'notice', summary },
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function createContextInjector(options) {
|
|
33
|
+
const done = new WeakMap();
|
|
34
|
+
return {
|
|
35
|
+
async inject(messages, agent) {
|
|
36
|
+
const refs = collectContextRefs(messages);
|
|
37
|
+
if (refs.length === 0)
|
|
38
|
+
return undefined;
|
|
39
|
+
const key = typeof agent === 'object' && agent !== null ? agent : undefined;
|
|
40
|
+
const seen = key === undefined ? undefined : (done.get(key) ?? new Set());
|
|
41
|
+
if (key !== undefined && seen !== undefined)
|
|
42
|
+
done.set(key, seen);
|
|
43
|
+
const fresh = [];
|
|
44
|
+
for (const ref of refs) {
|
|
45
|
+
const encoded = encodeRef(ref);
|
|
46
|
+
if (seen?.has(encoded) === true)
|
|
47
|
+
continue;
|
|
48
|
+
seen?.add(encoded);
|
|
49
|
+
fresh.push(ref);
|
|
50
|
+
}
|
|
51
|
+
if (fresh.length === 0)
|
|
52
|
+
return undefined;
|
|
53
|
+
const texts = [];
|
|
54
|
+
for (const ref of fresh) {
|
|
55
|
+
const render = options.renderers.get(ref.kind);
|
|
56
|
+
if (render === undefined)
|
|
57
|
+
continue;
|
|
58
|
+
let text;
|
|
59
|
+
try {
|
|
60
|
+
text = await render(ref);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
options.logger?.warn?.(`[dsh-workbench] 渲染对话引用失败:${String(error)}`);
|
|
64
|
+
text = `工作台面板引用了一条内容(${encodeRef(ref)}),但当前渲染不出来(项目未选中或数据源不可用)。`;
|
|
65
|
+
}
|
|
66
|
+
if (text !== '')
|
|
67
|
+
texts.push(text);
|
|
68
|
+
}
|
|
69
|
+
if (texts.length === 0)
|
|
70
|
+
return undefined;
|
|
71
|
+
return buildContextMessage(texts.join('\n\n'), `工作台引用:${fresh.length} 条`);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ContextRef } from '../api/context-ref.js';
|
|
2
|
+
/**
|
|
3
|
+
* 把 kind:part/part 文本读回引用。
|
|
4
|
+
* @param text 待解析文本。
|
|
5
|
+
* @returns 没冒号、冒号在首尾、种类不是小写字母开头、或冒号后为空时给 undefined;不抛异常。
|
|
6
|
+
*/
|
|
7
|
+
export declare function decodeRef(text: string): ContextRef | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* 从一段文本里按出现顺序捞出所有标记引用,同一个引用只留第一次。
|
|
10
|
+
* @param text 任意文本;null / undefined 当空串。
|
|
11
|
+
* @returns 解析不出来的标记会被丢掉,所以结果只含合法引用,可能为空数组。
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseMarkers(text: string): ContextRef[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { CONTEXT_MARKER_NAME, encodeRef } from '../api/context-ref.js';
|
|
2
|
+
const MARKER = new RegExp(`@${CONTEXT_MARKER_NAME}:([a-z][a-z0-9-]*:[A-Za-z0-9._~%#/-]+)`, 'g');
|
|
3
|
+
const TRAILING_PUNCTUATION = /[.,;:!?]+$/;
|
|
4
|
+
/**
|
|
5
|
+
* 把 kind:part/part 文本读回引用。
|
|
6
|
+
* @param text 待解析文本。
|
|
7
|
+
* @returns 没冒号、冒号在首尾、种类不是小写字母开头、或冒号后为空时给 undefined;不抛异常。
|
|
8
|
+
*/
|
|
9
|
+
export function decodeRef(text) {
|
|
10
|
+
const at = text.indexOf(':');
|
|
11
|
+
if (at <= 0 || at === text.length - 1)
|
|
12
|
+
return undefined;
|
|
13
|
+
const kind = text.slice(0, at);
|
|
14
|
+
if (!/^[a-z][a-z0-9-]*$/.test(kind))
|
|
15
|
+
return undefined;
|
|
16
|
+
const parts = text.slice(at + 1).split('/').filter((part) => part !== '');
|
|
17
|
+
if (parts.length === 0)
|
|
18
|
+
return undefined;
|
|
19
|
+
return { kind, parts };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 从一段文本里按出现顺序捞出所有标记引用,同一个引用只留第一次。
|
|
23
|
+
* @param text 任意文本;null / undefined 当空串。
|
|
24
|
+
* @returns 解析不出来的标记会被丢掉,所以结果只含合法引用,可能为空数组。
|
|
25
|
+
*/
|
|
26
|
+
export function parseMarkers(text) {
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
const refs = [];
|
|
29
|
+
for (const match of String(text ?? '').matchAll(MARKER)) {
|
|
30
|
+
const raw = match[1].replace(TRAILING_PUNCTUATION, '');
|
|
31
|
+
const ref = decodeRef(raw);
|
|
32
|
+
if (ref === undefined)
|
|
33
|
+
continue;
|
|
34
|
+
const key = encodeRef(ref);
|
|
35
|
+
if (seen.has(key))
|
|
36
|
+
continue;
|
|
37
|
+
seen.add(key);
|
|
38
|
+
refs.push(ref);
|
|
39
|
+
}
|
|
40
|
+
return refs;
|
|
41
|
+
}
|