Neeze commited on
Commit
92e2a33
·
verified ·
1 Parent(s): 04045e6

DSH-AIO: DeepSeek Harness portable (offline, Windows x64)

Browse files
DSH-AIO-portable-win-x64.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:15f0ce17fe4a1ba27b135b514e5ec1cf3d43321afa27610dfdea8bcdfa1c4cdb
3
+ size 344356530
DSH-AIO-portable-win-x64.zip.sha256 ADDED
@@ -0,0 +1 @@
 
 
1
+ 15f0ce17fe4a1ba27b135b514e5ec1cf3d43321afa27610dfdea8bcdfa1c4cdb DSH-AIO-portable-win-x64.zip
README.md ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ license_name: mixed-third-party
4
+ language:
5
+ - vi
6
+ - en
7
+ tags:
8
+ - deepseek-harness
9
+ - dsh
10
+ - portable
11
+ - offline
12
+ - windows
13
+ - agent
14
+ ---
15
+
16
+ # DSH-AIO — DeepSeek Harness portable (Windows x64, offline)
17
+
18
+ **Tải về:** `DSH-AIO-portable-win-x64.zip` (329M) — SHA256 `15f0ce17fe4a1ba27b135b514e5ec1cf3d43321afa27610dfdea8bcdfa1c4cdb`
19
+
20
+ Giải nén vào đường dẫn ngắn (ví dụ `D:\dsh`), sửa `config\internal-api.env` (lần đầu launcher tự mở Notepad), rồi double-click `Start-DeepseekHarness.cmd`. Không cần quyền admin, không cần Internet.
21
+
22
+ Các file khác trong repo (`scripts/`, `docs/`, `config/`, `package.json`, `package-lock.json`) là mã nguồn/tài liệu dùng để **dựng lại** gói từ đầu (`scripts\build-portable.ps1`, cần Internet).
23
+
24
+ ---
25
+
26
+ > **Bản công khai (Hugging Face).** Vì giấy phép, bản này **không kèm**:
27
+ > - `dsh-deepseek-splash` (repo gốc không có LICENSE) - bỏ qua các dòng nói về splash bên dưới;
28
+ > - `@anthropic-ai/claude-agent-sdk` + `claude.exe` (bản quyền Anthropic, không được phát hành lại) - plugin `@deepseek-ai/dsh-subagent-claude-code` vẫn có trong `node_modules\` nhưng **tắt** và sẽ không chạy được nếu thiếu SDK này.
29
+ >
30
+ > Dựng lại đầy đủ phần còn lại (cần Internet, trên máy build): `scripts\build-portable.ps1` (dùng `package-lock.json` đi kèm; SDK của Anthropic sẽ được tải từ npm theo điều khoản của họ).
31
+
32
+ # DeepSeek Harness (dsh) - bản Portable
33
+
34
+ Chạy [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) **offline, không cần quyền admin, không cài gì**.
35
+ Gói chứa sẵn Node.js portable (win-x64), toàn bộ dependencies của `@deepseek-ai/dsh@0.2.1-rc.5` và các plugin (xem bên dưới).
36
+
37
+ Yêu cầu: **Windows 10 22H2 / Windows 11 x64**, Windows PowerShell 5.1 (có sẵn), trình duyệt bất kỳ (Edge/Chrome).
38
+
39
+ ## Dùng
40
+
41
+ 1. Giải nén vào **đường dẫn ngắn**, ví dụ `D:\dsh` (đường dẫn tương đối dài nhất trong gói là ~180 ký tự; tổng không được quá 259 ký tự nếu Windows chưa bật LongPaths).
42
+ 2. Double-click **`Start-DeepseekHarness.cmd`**.
43
+ - Lần đầu, Notepad mở file `config\internal-api.env`: điền `BASE_URL`, `API_KEY`, `MODEL_ID` (và `API_PROTOCOL` nếu cần), lưu, đóng Notepad.
44
+ 3. Trình duyệt tự mở Web UI (`http://127.0.0.1:3080`). Đóng cửa sổ console để dừng.
45
+
46
+ ### `config\internal-api.env`
47
+
48
+ | Biến | Ý nghĩa |
49
+ |---|---|
50
+ | `BASE_URL` | Địa chỉ API, thường kết thúc `/v1` |
51
+ | `API_KEY` | Key của gateway (chỉ truyền qua biến môi trường, không ghi vào YAML) |
52
+ | `MODEL_ID` | Tên model; nhiều model cách nhau dấu phẩy, model đầu là mặc định |
53
+ | `API_PROTOCOL` | `openai-completions` (mặc định) · `openai-responses` · `anthropic-messages` |
54
+ | `HTTPS_PROXY`, `NO_PROXY` | Tuỳ chọn (không hỗ trợ SOCKS; NO_PROXY không hỗ trợ CIDR) |
55
+ | `NODE_EXTRA_CA_CERTS` | Tuỳ chọn: file CA nội bộ nếu gateway dùng HTTPS tự ký |
56
+
57
+ Model OpenAI mới (vd `gpt-6-*`) **không** cho dùng function tools qua chat/completions → đặt `API_PROTOCOL=openai-responses`.
58
+ Gateway nội bộ kiểu vLLM/LiteLLM/one-api thường dùng `openai-completions`.
59
+
60
+ Đổi API: sửa file env rồi chạy **`Reset-Config.cmd`** (sinh lại `data\dsh-home\profiles\web\cordis.patch.yml`, bản cũ được sao lưu `.bak-*`; các tuỳ chọn đã đổi trong UI Settings sẽ về mặc định), sau đó khởi động lại. Có thể đổi trực tiếp trong UI: *Settings → Models*.
61
+
62
+ ## Plugin đi kèm (đã kiểm tra mã nguồn: không gọi mạng ngoài khi chạy)
63
+
64
+ Bật qua `config\plugins.txt` (profile `web`), cài sẵn trong `node_modules\`, version ghim cứng:
65
+
66
+ | Plugin | Version | Ghi chú |
67
+ |---|---|---|
68
+ | `dsh-better-sidebar` | 0.28.2 | Sidebar: file tree, editor, terminal, Git, side chat. Các link github.com trong UI chỉ là link tĩnh. |
69
+ | `@akshaydev17/dsh-spotlight` | 0.0.3 | Command palette (`Ctrl+K`). Dùng **bản fork** vì bản gốc `@0xsline/dsh-spotlight` import `schemastery` không scope nên không chạy với dsh (diff chỉ khác tên package + import này). |
70
+ | `@nanmicoder/dsh-agent-teams` | 0.1.28 | Multi-agent. Chỉ gọi API cục bộ. (Tác giả khai báo host khuyến nghị là rc.2/rc.3; đã test chạy với rc.5.) |
71
+ | `dsh-browser-cdp` | 1.7.5 | Điều khiển Chrome/Edge **có sẵn trên máy** qua CDP (cổng cục bộ). Tuỳ chọn: tải FFmpeg từ GitHub *chỉ khi bạn bấm cài* — **đừng bật** trong môi trường kín (backend mặc định là CDP, không cần FFmpeg). |
72
+
73
+ | `dsh-deepseek-splash` | 1.0.2 (commit `f9e8fbe`) | Animation khởi động 4K (video 11 MB). Chỉ phục vụ 2 file tĩnh cục bộ + chèn 1 script hiển thị video; bấm "Skip" hoặc `Esc` để bỏ qua. Cài từ `vendor\dsh-deepseek-splash-1.0.2.tgz` (đúng bản đã đọc mã). **Không chạy** `安装开屏.ps1` / `还原原版.ps1` trong repo gốc: đó là script vá `app.asar` của bản Desktop. |
74
+
75
+ **Đã cài sẵn nhưng TẮT mặc định:**
76
+
77
+ - `@deepseek-ai/dsh-subagent-claude-code` 0.2.1-rc.5 (chính thức): chạy **Claude Code native** (`claude.exe`, ~209 MB, tự đăng nhập/gọi API Anthropic) làm subagent. Bản chất cần truy cập Anthropic, hoặc một gateway tương thích Anthropic Messages (đặt `env: ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY` trong `cordis.patch.yml`; nên thêm `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`). Bật lên sẽ thêm tool `subagent_claude_code` vào **mọi** agent (đã đo: 31 → 47 tool cùng agent-teams) nhưng không khởi chạy tiến trình nào cho tới khi được gọi. Để bật: bỏ dấu `#` dòng tương ứng trong `config\plugins.txt` rồi chạy `Reset-Config.cmd`. Chưa test gọi thật (không có API Anthropic).
78
+
79
+ **Đã loại** (không đưa vào gói):
80
+
81
+ - `dsh-market` (`@dsh-market/plugin`): bản chất cần mạng — tải danh mục từ `*.github.io`/jsDelivr, GitHub OAuth, npm registry, rồi cài bằng pnpm/npm. Vô dụng và gây lỗi kết nối trong mạng kín.
82
+ - `TencentDB-Agent-Memory`: không phải plugin dsh mà là **proxy/gateway riêng** (dsh phải nói giao thức Anthropic Messages tới proxy; proxy lại gọi LLM + embedding mặc định ở dịch vụ ngoài, cần Docker/server riêng). Có chế độ standalone SQLite nhưng không phù hợp "một thư mục, một click".
83
+ - `dsh-tui` (0.2.19): không tương thích dsh 0.2.1-rc.5 (gọi `/api/session.create`, server dùng `/api/session/create`; đồng thời không gửi token xác thực → HTTP 401/404).
84
+
85
+ Để bật/tắt plugin: sửa `config\plugins.txt`, chạy `Reset-Config.cmd`. **Thêm plugin mới** (kiểm tra mạng, cài offline, test, đóng gói, sự cố thường gặp): xem [`docs/THEM-PLUGIN.md`](docs/THEM-PLUGIN.md). Plugin chưa cài sẵn trong `node_modules\` sẽ bị bỏ qua (offline không tải được).
86
+
87
+ ## Dữ liệu nằm ở đâu
88
+
89
+ Mọi thứ nằm trong thư mục gói, không ghi vào `%USERPROFILE%`:
90
+
91
+ - `data\dsh-home` – cấu hình, session, credentials (`DSH_HOME`)
92
+ - `data\agents-home` – `DSH_AGENTS_HOME`
93
+ - `data\workspace` – workspace mặc định của agent (agent chỉ nên làm việc ở đây; đừng trỏ vào thư mục nhạy cảm)
94
+ - Telemetry của dsh bị tắt (`DSH_TELEMETRY_DISABLED=1`).
95
+
96
+ Di chuyển thư mục gói sau khi đã chạy: đường dẫn workspace lưu trong `data\dsh-home\storages\workspace.json` là tuyệt đối; xoá `data\dsh-home` (hoặc thêm lại workspace trong UI) nếu workspace báo lỗi đường dẫn.
97
+
98
+ ## Chia sẻ sang máy khác
99
+
100
+ Nén cả thư mục bằng `scripts\pack.ps1` (tạo `dist\DeepseekHarness-Portable.zip`; **không** kèm key, session, `data\`). Máy đích: giải nén → điền `config\internal-api.env` → double-click.
101
+
102
+ > Windows có thể hiện cảnh báo SmartScreen/“Mark of the Web” với file tải từ mạng. Chuột phải zip → Properties → **Unblock** trước khi giải nén để tránh.
103
+
104
+ ## Không dùng được khi offline
105
+
106
+ Plugin market / `dsh plugin` (cần pnpm + mạng), tool `web_fetch`/`web_search`, MCP server từ xa. Agent chạy lệnh qua **PowerShell** (không kèm bash/git).
107
+
108
+ ## Cập nhật phiên bản (cần Internet, trên máy build)
109
+
110
+ ```powershell
111
+ # dựng lại đúng cây package đã kiểm thử (npm ci theo package-lock.json, đã gồm plugin)
112
+ powershell -ExecutionPolicy Bypass -File scripts\build-portable.ps1
113
+ powershell -ExecutionPolicy Bypass -File scripts\verify-offline.ps1
114
+ powershell -ExecutionPolicy Bypass -File scripts\pack.ps1
115
+ ```
116
+
117
+ Nâng cấp/thêm plugin: dùng npm của runtime portable **với `--legacy-peer-deps --ignore-scripts`** (không có `--legacy-peer-deps`, npm có thể kéo bản cũ của `@deepseek-ai/*` do peer optional; có nó thì npm lại gỡ các peer mà dsh cần — các peer này đã được ghim tường minh trong `package.json`, đừng xoá). Sau khi cài, thêm tên vào `config\plugins.txt` và chạy `verify-offline.ps1`.
118
+
119
+ `node_modules\` nằm ở **thư mục gốc gói** (không phải `app\`): plugin được import từ `data\dsh-home\profiles\web\`, và Node tìm `node_modules` bằng cách đi lên các thư mục cha.
120
+
121
+ `dsh` đang ở giai đoạn developer preview (có thể đổi phá vỡ) nên phiên bản được ghim cứng, không tự nâng cấp.
122
+
123
+ ## Kiểm thử
124
+
125
+ `scripts\verify-offline.ps1` (21 kiểm tra): chạy một job headless với mock OpenAI API cục bộ, proxy chết (mọi kết nối ra ngoài đều hỏng), `PATH` tối thiểu; khởi động profile web + 5 plugin đang bật, kiểm tra plugin kích hoạt/tải được (splash chèn đúng và phục vụ asset cục bộ), **không có kết nối TCP ngoài loopback**, không ghi ra ngoài gói, không cần admin.
126
+
127
+ ## An toàn
128
+
129
+ Xem [SAFETY.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/SAFETY.md): agent có thể chạy lệnh và đọc/ghi file. Chỉ đặt API key đang dùng thật vào `config\internal-api.env` trên máy tin cậy.
130
+
131
+ ## Giấy phép
132
+
133
+ Gói chứa phần mềm bên thứ ba, mỗi thành phần theo giấy phép riêng (xem file `LICENSE` trong từng thư mục `node_modules\<package>` và `runtime\node\LICENSE`): DeepSeek Harness (`@deepseek-ai/*`, MIT), Node.js, và các plugin MIT liệt kê ở trên. Script và tài liệu hướng dẫn trong repo này chưa khai báo giấy phép riêng. Đây là phần mềm thử nghiệm (developer preview); xem [SAFETY.md](https://github.com/deepseek-ai/deepseek-harness/blob/master/SAFETY.md) của dsh trước khi dùng.
Reset-Config.cmd ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ @echo off
2
+ setlocal
3
+ "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\launch.ps1" -ResetConfig
4
+ echo.
5
+ pause
Start-DeepseekHarness.cmd ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ setlocal
3
+ title DeepSeek Harness (portable)
4
+ "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "%~dp0scripts\launch.ps1" %*
5
+ if errorlevel 1 (
6
+ echo.
7
+ pause
8
+ )
config/cordis.patch.template.yml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Template -> data\dsh-home\profiles\web\cordis.patch.yml (render boi scripts\launch.ps1)
2
+ # Cac placeholder dang BASE_URL va MODELS (trong dau ngoac nhon kep) duoc lay tu config\internal-api.env
3
+ # Neu gateway can them: compat / reasoningEfforts / headers -> xem docs llm-pi-ai (providers.md)
4
+ - id: llm-pi-ai
5
+ config:
6
+ providers:
7
+ internal:
8
+ displayName: "Internal API"
9
+ api: {{API_PROTOCOL}}
10
+ baseURL: {{BASE_URL}}
11
+ apiKeyEnv: INTERNAL_API_KEY
12
+ models:
13
+ {{MODELS}}
14
+
15
+ # Model mac dinh cho session moi = model dau tien trong MODEL_ID (thay vi deepseek-official)
16
+ - id: agent-default-model
17
+ config:
18
+ provider: internal
19
+ model: {{DEFAULT_MODEL}}
20
+
21
+ # Workspace mac dinh nam trong goi portable (thay vi Documents cua may). launch.ps1 dat DSH_DOCUMENTS_DIR.
22
+ - id: workspace-controller
23
+ config:
24
+ documentsDirectory: !!js process.env.DSH_DOCUMENTS_DIR
config/internal-api.env.sample ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cau hinh API noi bo (OpenAI Chat Completions). Sua 3 dong dau roi luu.
2
+ # Khong dung dau ngoac kep tru khi gia tri co khoang trang.
3
+
4
+ # Dia chi API, thuong ket thuc bang /v1
5
+ BASE_URL=http://10.0.0.5:8000/v1
6
+
7
+ # API key cua gateway noi bo
8
+ API_KEY=sk-CHANGE_ME
9
+
10
+ # Ten model tren gateway. Nhieu model: cach nhau dau phay (vd: qwen3-coder,deepseek-v3)
11
+ MODEL_ID=ten-model
12
+
13
+ # Giao thuc API cua gateway (mac dinh openai-completions):
14
+ # openai-completions = /v1/chat/completions (vLLM, LiteLLM, one-api, da so gateway noi bo)
15
+ # openai-responses = /v1/responses (OpenAI that, cac model moi dung function tools)
16
+ # anthropic-messages = /v1/messages
17
+ API_PROTOCOL=openai-completions
18
+
19
+ # --- Tuy chon (bo dau # de dung) ---
20
+ # HTTPS_PROXY=http://proxy.noibo:8080
21
+ # NO_PROXY=noibo.local,.corp.example.com
22
+ # CA noi bo neu gateway dung HTTPS tu ky (duong dan tuyet doi hoac tuong doi so voi thu muc goi):
23
+ # NODE_EXTRA_CA_CERTS=certs\noibo-ca.pem
config/plugins.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Plugin (bundle) bat cho profile web, theo thu tu. Moi dong = ten package da cai san trong node_modules\ (thu muc goc goi).
2
+ # Them/bot dong roi chay Reset-Config.cmd. KHONG them plugin chua cai san (offline khong tai duoc).
3
+ #
4
+ # Da kiem tra ma nguon: khong goi mang ngoai luc chay.
5
+ dsh-better-sidebar
6
+ @akshaydev17/dsh-spotlight
7
+ @nanmicoder/dsh-agent-teams
8
+ # Co the goi mang CHI KHI nguoi dung chu dong bat (tai FFmpeg tu GitHub); mac dinh khong tai.
9
+ dsh-browser-cdp
10
+
11
+ # --- DA CAI SAN NHUNG TAT MAC DINH ---
12
+ # Chay Claude Code native (@anthropic-ai/claude-agent-sdk) lam subagent => can truy cap API Anthropic
13
+ # (hoac gateway tuong thich Anthropic Messages qua config env ANTHROPIC_BASE_URL). Bat len se them tool
14
+ # "subagent_claude_code" vao MOI agent. Chi bat khi co gateway noi bo phu hop, xem README.
15
+ # @deepseek-ai/dsh-subagent-claude-code
docs/THEM-PLUGIN.md ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hướng dẫn thêm plugin vào DeepSeek Harness portable
2
+
3
+ Tài liệu này mô tả cách **kiểm tra, cài sẵn và bật** một plugin dsh trong gói portable, sao cho gói vẫn chạy offline, không cần admin và nén sang máy khác dùng được ngay.
4
+
5
+ > **Nguyên tắc:** mọi thứ phải được tải về **trước**, trên máy build có Internet. Máy đích (mạng kín) không tải thêm được gì. Plugin nào cần mạng ngoài để hoạt động thì **không đưa vào gói** (xem mục 7).
6
+
7
+ Các lệnh dưới đây chạy trong **Windows PowerShell 5.1** (có sẵn trong Win10/11), tại **thư mục gốc của gói** trên máy build. Thư mục gốc là nơi có `Start-DeepseekHarness.cmd`, `node_modules\`, `runtime\`.
8
+
9
+ ## 0. Tổng quan cách plugin hoạt động trong gói
10
+
11
+ - Plugin dsh là một package npm (hoặc repo GitHub) có khai báo `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }` trong `package.json`. Đó là **bundle**: `cordis.patch.yml` của nó chèn plugin vào cây dịch vụ của dsh.
12
+ - Plugin được cài vào **`node_modules\` ở thư mục gốc gói** (không phải thư mục con). Lý do: mã plugin được import từ `data\dsh-home\profiles\web\`, và Node tìm `node_modules` bằng cách đi lên các thư mục cha.
13
+ - Plugin nào được bật do **`config\plugins.txt`** quyết định: mỗi dòng là tên package; launcher ghi các tên đó vào `data\dsh-home\profiles\web\package.json` (`dsh.profile.bundles`) khi tạo profile hoặc khi chạy `Reset-Config.cmd`.
14
+ - Phiên bản được **ghim cứng** trong `package.json` + `package-lock.json`. Lockfile là thứ giúp `scripts\build-portable.ps1` dựng lại đúng cây package.
15
+
16
+ ## 1. Kiểm tra plugin trước khi cài
17
+
18
+ Plugin chạy với **toàn quyền của agent** (đọc/ghi file, chạy lệnh) nên phải đọc mã trước khi cài. Làm lần lượt:
19
+
20
+ ### 1.1 Thông tin cơ bản (không tải gì về máy)
21
+
22
+ ```powershell
23
+ $pkg = '<tên-package>@<version>' # ví dụ: dsh-better-sidebar@0.28.2
24
+ npm view $pkg name version license repository.url maintainers dist.unpackedSize
25
+ npm view $pkg dependencies peerDependencies optionalDependencies --json
26
+ npm view $pkg scripts --json # chú ý preinstall / install / postinstall / prepare
27
+ npm view $pkg dsh --json # phải có bundle.patch nếu là bundle
28
+ ```
29
+
30
+ Cờ đỏ cần cân nhắc kỹ:
31
+
32
+ | Dấu hiệu | Ý nghĩa |
33
+ |---|---|
34
+ | Có `install` / `postinstall` tải file | Cần mạng khi cài, có thể tải thêm nhị phân |
35
+ | Phụ thuộc `playwright`, `puppeteer`, `@puppeteer/browsers` | Thường tải Chromium từ Internet |
36
+ | Phụ thuộc SDK của dịch vụ đám mây (OpenAI, Anthropic, AWS…) | Gọi API ngoài khi chạy |
37
+ | Ít lượt tải, 1 tác giả, repo không rõ | Chưa được cộng đồng kiểm chứng |
38
+ | `dsh.engines` / peer không liệt kê phiên bản host đang dùng (`0.2.1-rc.5`) | Có thể không tương thích |
39
+
40
+ ### 1.2 Tải tarball về thư mục tạm và đọc mã (không chạy)
41
+
42
+ Dùng thư mục tạm riêng; **không** chạy bất kỳ script nào trong đó.
43
+
44
+ ```powershell
45
+ $audit = Join-Path $env:TEMP 'plugin-audit'
46
+ Remove-Item -Recurse -Force $audit -ErrorAction SilentlyContinue
47
+ New-Item -ItemType Directory -Force $audit | Out-Null
48
+ Push-Location $audit
49
+ npm pack $pkg --ignore-scripts # chỉ tải tarball
50
+ & "$env:SystemRoot\System32\tar.exe" -xzf (Get-ChildItem *.tgz).Name
51
+ Pop-Location
52
+
53
+ $code = Get-ChildItem "$audit\package" -Recurse -Include *.js,*.mjs,*.cjs -File
54
+
55
+ # (a) URL ngoài trong mã
56
+ $code | Select-String -Pattern 'https?://[A-Za-z0-9._/-]+' -AllMatches |
57
+ ForEach-Object { $_.Matches.Value } | Sort-Object -Unique |
58
+ Where-Object { $_ -notmatch 'w3\.org|json-schema|example\.|localhost|127\.0\.0\.1' }
59
+
60
+ # (b) lệnh gọi mạng và tiến trình con
61
+ $code | Select-String -Pattern 'fetch\(|XMLHttpRequest|new WebSocket|https?\.request|sendBeacon|child_process|spawn\(|execFile|exec\(' |
62
+ Select-Object -First 40 Path, LineNumber
63
+ ```
64
+
65
+ Cách đọc kết quả:
66
+
67
+ - `exec(` cũng khớp `RegExp.exec(...)` (không phải chạy lệnh) → bỏ qua dòng kiểu `/^…/.exec(chuỗi)`.
68
+ - `fetch('/api/...')` hoặc URL tương đối, `127.0.0.1`: gọi **máy cục bộ**, an toàn.
69
+ - Link `https://github.com/...` nằm trong chuỗi giao diện/locale: thường chỉ là liên kết tĩnh, kiểm tra ngữ cảnh.
70
+ - `fetch('https://...')`, gọi `api.github.com`, `registry.npmjs.org`, CDN, OAuth: **cần mạng ngoài** → loại hoặc vô hiệu hoá tính năng đó.
71
+ - `child_process`, `spawn`: plugin chạy tiến trình con; xem nó chạy gì (git, shell, trình duyệt…) và có hợp lý không.
72
+
73
+ Đọc cả `README.md` của plugin: mục "network", "telemetry", "offline". Đọc `cordis.patch.yml` xem nó chèn những gì.
74
+
75
+ ### 1.3 Plugin từ GitHub (không có trên npm)
76
+
77
+ Ghim vào **một commit cụ thể** (không dùng nhánh), tải về và đọc mã đúng commit đó:
78
+
79
+ ```powershell
80
+ $sha = '<commit-sha-40-ký-tự>'
81
+ $d = Join-Path $env:TEMP 'plugin-src'
82
+ Remove-Item -Recurse -Force $d -ErrorAction SilentlyContinue
83
+ New-Item -ItemType Directory -Force $d | Out-Null
84
+ Invoke-WebRequest -UseBasicParsing "https://codeload.github.com/<owner>/<repo>/tar.gz/$sha" -OutFile "$d\src.tgz"
85
+ & "$env:SystemRoot\System32\tar.exe" -xzf "$d\src.tgz" -C $d
86
+ ```
87
+
88
+ Sau khi đọc xong và chấp nhận, đóng thành tarball nằm trong gói (xem 2.2) để lần sau dựng lại không phụ thuộc GitHub.
89
+
90
+ > Repo GitHub có thể kèm script cài đặt riêng (`.ps1`, `.sh`). **Đừng chạy chúng**: ví dụ repo `deepseek-splash` có `安装开屏.ps1` vá file `app.asar` của bản Desktop, không liên quan tới gói này. Chỉ dùng cách cài bằng npm ở dưới.
91
+
92
+ ## 2. Cài plugin vào gói (máy build, có Internet)
93
+
94
+ ### 2.1 Chuẩn bị môi trường và chụp trạng thái trước
95
+
96
+ Luôn dùng Node/npm **của gói**, không dùng Node hệ thống.
97
+
98
+ ```powershell
99
+ $root = (Get-Location).Path # thư mục gốc của gói
100
+ $env:PATH = "$root\runtime\node;$env:PATH"
101
+ $env:npm_config_cache = "$root\data\npm-cache"
102
+ $env:NPM_CONFIG_UPDATE_NOTIFIER = 'false'
103
+ $npm = "$root\runtime\node\node_modules\npm\bin\npm-cli.js"
104
+
105
+ # sao lưu manifest để có thể quay lại
106
+ Copy-Item package.json package.json.bak
107
+ Copy-Item package-lock.json package-lock.json.bak
108
+
109
+ # chụp danh sách package cấp cao nhất trước khi cài
110
+ function Snap {
111
+ Get-ChildItem "$root\node_modules" -Directory | Where-Object { $_.Name -notlike '.*' } | ForEach-Object {
112
+ if ($_.Name -like '@*') { Get-ChildItem $_.FullName -Directory | ForEach-Object { "$($_.Parent.Name)/$($_.Name)" } }
113
+ else { $_.Name }
114
+ } | Sort-Object
115
+ }
116
+ $before = Snap
117
+ ```
118
+
119
+ ### 2.2 Lệnh cài
120
+
121
+ **Luôn dùng đủ 3 cờ** `--legacy-peer-deps --ignore-scripts --save-exact`:
122
+
123
+ ```powershell
124
+ # Plugin trên npm:
125
+ node $npm install --save-exact --legacy-peer-deps --ignore-scripts --omit=dev --no-audit --no-fund <tên-package>@<version>
126
+
127
+ # Plugin từ GitHub (đã đọc mã ở 1.3): đóng tarball vào vendor\ rồi cài từ file
128
+ New-Item -ItemType Directory -Force "$root\vendor" | Out-Null
129
+ node $npm pack "$d\<repo>-$sha" --ignore-scripts --pack-destination "$root\vendor"
130
+ node $npm install --save-exact --legacy-peer-deps --ignore-scripts --omit=dev --no-audit --no-fund ".\vendor\<tên-file>.tgz"
131
+ ```
132
+
133
+ Vì sao các cờ này:
134
+
135
+ | Cờ | Lý do |
136
+ |---|---|
137
+ | `--legacy-peer-deps` | Plugin thường khai báo peer *optional* kéo về bản **cũ** của `@deepseek-ai/*` → xung đột `ERESOLVE` hoặc nhân đôi phiên bản. Cờ này bỏ qua peer tự cài |
138
+ | `--ignore-scripts` | Không chạy `install`/`postinstall` của bên thứ ba |
139
+ | `--save-exact` | Ghim đúng phiên bản vào `package.json` |
140
+
141
+ ### 2.3 Kiểm tra cây package sau khi cài
142
+
143
+ ```powershell
144
+ $after = Snap
145
+ Compare-Object $before $after # chỉ nên thấy package MỚI (=>); không được có package bị gỡ (<=)
146
+ ```
147
+
148
+ - Có dòng `<=` (package bị gỡ): **dừng lại**. Cài lại các package đó với đúng phiên bản (lấy từ `$before`/`package-lock.json.bak`) rồi ghim bằng `npm install --save-exact --legacy-peer-deps --ignore-scripts <pkg>@<ver>`. Các peer mà dsh cần (ví dụ `@deepseek-ai/cordis-plugin-group`, `@deepseek-ai/dsh-fs`, …) đã được ghim sẵn trong `package.json`; không xoá chúng.
149
+ - Thấy package `@deepseek-ai/*` ở phiên bản **khác** `0.2.1-rc.5`: có nhân đôi phiên bản host → huỷ bằng cách khôi phục `package.json.bak` / `package-lock.json.bak` và chạy lại `scripts\build-portable.ps1`.
150
+
151
+ ## 3. Bật plugin
152
+
153
+ 1. Mở `config\plugins.txt`, thêm một dòng với **đúng tên package** (ví dụ `@scope/ten-plugin`).
154
+ - Muốn tắt tạm: thêm `#` ở đầu dòng.
155
+ - Plugin chưa có trong `node_modules\` sẽ bị launcher **bỏ qua** (và in cảnh báo).
156
+ 2. Chạy **`Reset-Config.cmd`** (hoặc `scripts\launch.ps1 -ResetConfig`). Lệnh này ghi lại `data\dsh-home\profiles\web\package.json` (danh sách bundle) và sao lưu bản cũ thành `.bak-<thời gian>`.
157
+ - Lưu ý: nó cũng ghi lại `cordis.patch.yml`; các tuỳ chọn bạn đã đổi trong UI Settings quay về mặc định (bản cũ nằm trong file `.bak-*`).
158
+ 3. Khởi động bằng `Start-DeepseekHarness.cmd` và xem console: không được có dòng `did not activate` hoặc `failed to import`.
159
+
160
+ Nếu plugin cần cấu hình (ví dụ `env`, đường dẫn…), thêm một mục vào `config\cordis.patch.template.yml` theo cú pháp của dsh (ghi đè theo `id` của entry trong `cordis.patch.yml` của plugin) rồi chạy lại `Reset-Config.cmd`. **Không đặt secret trong YAML**; dùng biến môi trường (`!!js process.env.TEN_BIEN`) như `apiKeyEnv` đang làm.
161
+
162
+ > Package **không** có `dsh.bundle.patch` thì không bật được bằng `plugins.txt`; cần tự thêm entry vào `cordis.patch.yml` của profile. Đây là trường hợp nâng cao, hãy đọc README của plugin.
163
+
164
+ ## 4. Kiểm thử
165
+
166
+ ### 4.1 Chạy bộ test tự động
167
+
168
+ ```powershell
169
+ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-offline.ps1
170
+ ```
171
+
172
+ Bộ test này chạy với proxy chết và `PATH` tối thiểu, rồi kiểm tra: plugin kích hoạt không lỗi, **không có kết nối TCP ra ngoài loopback**, không ghi ra `~\.dsh`, `~\.agents`, `Documents`.
173
+
174
+ Để test thêm plugin mới, mở `scripts\verify-offline.ps1` và thêm tên vào vòng `foreach ($pl in ...)` (mục "Plugin co trong roster va client tai duoc"). Plugin chỉ có phần server (không có `client.js`, như splash) thì thêm một kiểm tra riêng, giống mẫu "Splash".
175
+
176
+ ### 4.2 Test thủ công
177
+
178
+ 1. Chạy `Start-DeepseekHarness.cmd`, mở UI.
179
+ 2. Kiểm tra roster client chứa plugin: ở tab trình duyệt, DevTools Console chạy
180
+ `window.__DSH_BOOT__.entries.map(e => e.id).filter(x => /ten-plugin/.test(x))`.
181
+ 3. Gửi một tin nhắn có dùng tool để chắc plugin không làm hỏng luồng chat.
182
+ 4. Theo dõi mạng khi dùng thử tính năng của plugin:
183
+ ```powershell
184
+ Get-NetTCPConnection | Where-Object { $_.RemoteAddress -notin '127.0.0.1','::1','0.0.0.0','::' } |
185
+ Where-Object { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).Path -like "$root*" }
186
+ ```
187
+ Kết quả phải **rỗng**.
188
+
189
+ ### 4.3 Bật plugin ảnh hưởng thế nào tới agent
190
+
191
+ Một số plugin thêm tool vào **mọi** agent (tốn token ở mỗi request và mở rộng quyền). Đo trước/sau số tool bằng mock API nếu cần (`scripts\dev\mock-openai.mjs` ghi tên tool vào `data\tmp\mock-requests.jsonl`).
192
+
193
+ ## 5. Cập nhật tài liệu và đóng gói
194
+
195
+ 1. Ghi plugin vào bảng trong `README.md` (tên, phiên bản, kết quả đánh giá mạng). Nếu là bản fork hoặc cài từ GitHub, ghi rõ commit.
196
+ 2. Chạy lại `scripts\verify-offline.ps1` (phải đạt hết).
197
+ 3. Đóng gói: `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\pack.ps1` → `dist\DeepseekHarness-Portable.zip` (+ `.sha256`). Gói **không** chứa `config\internal-api.env` và `data\`.
198
+ 4. Khuyến nghị: giải nén bản zip vào một đường dẫn khác có dấu cách, rồi chạy lại `verify-offline.ps1` ở đó để giả lập máy đích.
199
+ 5. Xoá bản sao lưu nếu không cần: `package.json.bak`, `package-lock.json.bak`.
200
+
201
+ Dựng lại toàn bộ từ lockfile (kể cả plugin) trên máy có Internet:
202
+
203
+ ```powershell
204
+ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\build-portable.ps1
205
+ ```
206
+
207
+ ## 6. Gỡ plugin / hoàn tác
208
+
209
+ ```powershell
210
+ # gỡ hẳn
211
+ node $npm uninstall --legacy-peer-deps --ignore-scripts --no-audit --no-fund <tên-package>
212
+ # rồi xoá dòng tương ứng trong config\plugins.txt và chạy Reset-Config.cmd
213
+
214
+ # hoặc quay về trạng thái trước khi cài
215
+ Copy-Item package.json.bak package.json -Force
216
+ Copy-Item package-lock.json.bak package-lock.json -Force
217
+ node $npm ci --legacy-peer-deps --ignore-scripts --omit=dev --no-audit --no-fund
218
+ ```
219
+
220
+ Nếu `npm uninstall` báo `EBUSY`: đang có server dsh chạy hoặc cửa sổ terminal đang đứng trong thư mục của plugin; tắt dsh và `cd` ra ngoài rồi thử lại.
221
+
222
+ Chỉ **tắt** plugin (giữ cài sẵn): thêm `#` đầu dòng trong `config\plugins.txt` và chạy `Reset-Config.cmd`.
223
+
224
+ ## 7. Xử lý sự cố thường gặp
225
+
226
+ | Triệu chứng | Nguyên nhân | Cách xử lý |
227
+ |---|---|---|
228
+ | `Cannot find package '<plugin>' imported from ...\profiles\web\` | Plugin chưa cài vào `node_modules\` ở thư mục gốc gói, hoặc `node_modules` không nằm ở gốc | Cài lại theo mục 2; đảm bảo `node_modules\<plugin>` tồn tại cạnh `runtime\` |
229
+ | `Cannot find package '@deepseek-ai/cordis-plugin-group'` (hoặc package `@deepseek-ai/*` khác) khi khởi động | Cài plugin làm npm gỡ các peer mà dsh cần | Khôi phục `package.json.bak`/lockfile hoặc chạy `build-portable.ps1`; luôn dùng 3 cờ ở mục 2.2 |
230
+ | `npm error ERESOLVE ... Conflicting peer dependency` | Peer optional của plugin kéo bản cũ `@deepseek-ai/*` | Dùng `--legacy-peer-deps` |
231
+ | `failed to import: Cannot find package 'schemastery'` (hoặc `cordis`) | Plugin import package **không có scope**, dsh dùng `@deepseek-ai/schemastery` / `@deepseek-ai/cordis` | Tìm bản fork đã sửa import (như `@akshaydev17/dsh-spotlight`), đọc diff kỹ; nếu không có thì loại plugin |
232
+ | `skipping profile bundle "..."` | Package không có `dsh.bundle.patch`, hoặc không tương thích phiên bản dsh | Kiểm tra `dsh.engines` và README plugin |
233
+ | UI báo `HTTP 404/401` từ plugin dạng client riêng | Plugin viết cho phiên bản host khác (API khác) | Không tương thích với `0.2.1-rc.5` → loại (như `dsh-tui` 0.2.19) |
234
+ | Plugin kích hoạt nhưng tính năng báo lỗi mạng | Plugin cần dịch vụ ngoài | Mục 8: loại hoặc vô hiệu hoá tính năng đó |
235
+ | `scripts\verify-offline.ps1` báo có kết nối ngoài loopback | Plugin gọi mạng khi chạy | Xác định tiến trình/cổng đích bằng lệnh ở 4.2 rồi loại plugin |
236
+
237
+ ## 8. Plugin đã đánh giá
238
+
239
+ Kết quả cụ thể (cài / tắt / loại, kèm lý do) nằm trong bảng ở [`README.md`](../README.md) mục "Plugin đi kèm". Tóm tắt các trường hợp **không** đưa vào gói vì cần mạng ngoài hoặc không tương thích: `dsh-market` (tải danh mục từ github.io/jsDelivr, OAuth GitHub, npm registry), `TencentDB-Agent-Memory` (proxy riêng, gọi LLM/embedding ngoài), `dsh-tui` (không tương thích rc.5). `@deepseek-ai/dsh-subagent-claude-code` được cài sẵn nhưng **tắt** vì chạy Claude Code native (cần API Anthropic hoặc gateway tương thích).
240
+
241
+ Bật plugin đang tắt: bỏ `#` ở dòng của nó trong `config\plugins.txt`, chạy `Reset-Config.cmd`. Riêng `dsh-subagent-claude-code` cần thêm cấu hình `env` (`ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`, `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`) và gateway nội bộ nói giao thức Anthropic Messages; khi bật nó thêm tool `subagent_claude_code` vào mọi agent.
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"name":"dsh-portable","version":"1.0.0","private":true,"type":"module","dependencies":{"@akshaydev17/dsh-spotlight":"0.0.3","@deepseek-ai/cordis-plugin-group":"1.0.5","@deepseek-ai/dsh":"0.2.1-rc.5","@deepseek-ai/dsh-anonymous-user-id":"0.2.1-rc.5","@deepseek-ai/dsh-attachment":"0.2.1-rc.5","@deepseek-ai/dsh-bash-local":"0.2.1-rc.5","@deepseek-ai/dsh-compaction":"0.2.1-rc.5","@deepseek-ai/dsh-deepseek-account":"0.2.1-rc.5","@deepseek-ai/dsh-fs":"0.2.1-rc.5","@deepseek-ai/dsh-jobs":"0.2.1-rc.5","@deepseek-ai/dsh-llm-deepseek":"0.2.1-rc.5","@deepseek-ai/dsh-output-retention":"0.2.1-rc.5","@deepseek-ai/dsh-ptc-runtime":"0.2.1-rc.5","@deepseek-ai/dsh-sandbox":"0.2.1-rc.5","@deepseek-ai/dsh-scope":"0.2.1-rc.5","@deepseek-ai/dsh-sdk-protocol":"0.2.1-rc.5","@deepseek-ai/dsh-session-persistence":"0.2.1-rc.5","@deepseek-ai/dsh-session-query":"0.2.1-rc.5","@deepseek-ai/dsh-session-telemetry":"0.2.1-rc.5","@deepseek-ai/dsh-session-title-llm":"0.2.1-rc.5","@deepseek-ai/dsh-shell":"0.2.1-rc.5","@deepseek-ai/dsh-spill":"0.2.1-rc.5","@deepseek-ai/dsh-subagent-claude-code":"0.2.1-rc.5","@deepseek-ai/dsh-util-time":"0.2.1-rc.5","@deepseek-ai/dsh-util-workspace-path":"0.2.1-rc.5","@deepseek-ai/dsh-workflow":"0.2.1-rc.5","@nanmicoder/dsh-agent-teams":"0.1.28","dsh-better-sidebar":"0.28.2","dsh-browser-cdp":"1.7.5"}}
scripts/build-portable.ps1 ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # build-portable.ps1 - CHAY 1 LAN tren may CO Internet.
2
+ # Tai Node portable (win-x64) + cai dsh va cac plugin (theo package-lock.json) vao thu muc goc goi.
3
+ # Tuong thich Windows PowerShell 5.1. Khong can quyen admin.
4
+ #
5
+ # - Co package-lock.json -> "npm ci": dung lai CHINH XAC cay package da kiem thu (dsh + plugin).
6
+ # - Khong co lockfile -> cai moi dsh@$DshVersion (KHONG co plugin; them plugin bang tay, xem README).
7
+ # Luu y: node_modules nam o THU MUC GOC (khong phai app\) de Node tim thay plugin khi import tu
8
+ # data\dsh-home\profiles\web\ (Node di len cac thu muc cha de tim node_modules).
9
+ param(
10
+ [string]$NodeVersion = 'v24.22.2',
11
+ [string]$DshVersion = '0.2.1-rc.5',
12
+ [switch]$Force
13
+ )
14
+ $ErrorActionPreference = 'Stop'
15
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
16
+ $ProgressPreference = 'SilentlyContinue'
17
+
18
+ $Root = Split-Path -Parent $PSScriptRoot
19
+ $Runtime = Join-Path $Root 'runtime'
20
+ $NodeDir = Join-Path $Runtime 'node'
21
+ $Data = Join-Path $Root 'data'
22
+ $Dl = Join-Path $Data 'tmp\download'
23
+ foreach ($d in @($Runtime, $Dl, (Join-Path $Data 'npm-cache'))) {
24
+ New-Item -ItemType Directory -Force -Path $d | Out-Null
25
+ }
26
+
27
+ # ---- 1. Node portable -------------------------------------------------------
28
+ $nodeExe = Join-Path $NodeDir 'node.exe'
29
+ if ((Test-Path $nodeExe) -and -not $Force) {
30
+ Write-Host "[1/4] Node da co: $(& $nodeExe -v)"
31
+ } else {
32
+ $base = "https://nodejs.org/dist/$NodeVersion"
33
+ $zipName = "node-$NodeVersion-win-x64.zip"
34
+ $zip = Join-Path $Dl $zipName
35
+ Write-Host "[1/4] Tai $zipName ..."
36
+ Invoke-WebRequest -UseBasicParsing -Uri "$base/$zipName" -OutFile $zip
37
+ $sums = (Invoke-WebRequest -UseBasicParsing -Uri "$base/SHASUMS256.txt").Content
38
+ $line = ($sums -split "`n") | Where-Object { $_ -match [regex]::Escape($zipName) } | Select-Object -First 1
39
+ if (-not $line) { throw "Khong tim thay checksum cho $zipName" }
40
+ $expected = ($line.Trim() -split '\s+')[0].ToLower()
41
+ $actual = (Get-FileHash -Algorithm SHA256 $zip).Hash.ToLower()
42
+ if ($expected -ne $actual) { throw "SHA256 khong khop: expected $expected, got $actual" }
43
+ Write-Host " SHA256 OK"
44
+ $ex = Join-Path $Dl 'node-extract'
45
+ if (Test-Path $ex) { Remove-Item -Recurse -Force $ex }
46
+ New-Item -ItemType Directory -Force -Path $ex | Out-Null
47
+ & (Join-Path $env:SystemRoot 'System32\tar.exe') -xf $zip -C $ex
48
+ if ($LASTEXITCODE -ne 0) { throw 'Giai nen Node that bai' }
49
+ $inner = Get-ChildItem $ex -Directory | Select-Object -First 1
50
+ if (Test-Path $NodeDir) { Remove-Item -Recurse -Force $NodeDir }
51
+ Move-Item $inner.FullName $NodeDir
52
+ Remove-Item -Recurse -Force $ex
53
+ Write-Host " Node $(& $nodeExe -v)"
54
+ }
55
+
56
+ # ---- 2. npm ci / install ------------------------------------------------------
57
+ $npmCli = Join-Path $NodeDir 'node_modules\npm\bin\npm-cli.js'
58
+ $env:PATH = "$NodeDir;$env:PATH"
59
+ $env:npm_config_cache = Join-Path $Data 'npm-cache'
60
+ $env:NPM_CONFIG_UPDATE_NOTIFIER = 'false'
61
+ $lock = Join-Path $Root 'package-lock.json'
62
+ Push-Location $Root
63
+ try {
64
+ if (Test-Path $lock) {
65
+ Write-Host '[2/4] npm ci (theo package-lock.json) ...'
66
+ # --legacy-peer-deps: tranh keo ban cu cua @deepseek-ai/* qua peer optional cua plugin.
67
+ # Cac peer that su cua dsh da duoc ghim tuong minh trong package.json.
68
+ & $nodeExe $npmCli ci --legacy-peer-deps --ignore-scripts --omit=dev --no-audit --no-fund --loglevel=warn
69
+ } else {
70
+ Write-Host "[2/4] npm install @deepseek-ai/dsh@$DshVersion (khong co lockfile) ..."
71
+ [IO.File]::WriteAllText((Join-Path $Root 'package.json'), '{"name":"dsh-portable","version":"1.0.0","private":true,"type":"module"}', (New-Object Text.UTF8Encoding $false))
72
+ & $nodeExe $npmCli install "@deepseek-ai/dsh@$DshVersion" --save-exact --ignore-scripts --omit=dev --no-audit --no-fund --loglevel=warn
73
+ }
74
+ if ($LASTEXITCODE -ne 0) { throw 'npm that bai' }
75
+ } finally { Pop-Location }
76
+
77
+ # ---- 3. Kiem tra --------------------------------------------------------------
78
+ Write-Host "[3/4] Kiem tra ..."
79
+ $bin = Join-Path $Root 'node_modules\@deepseek-ai\dsh\lib\bin.js'
80
+ if (-not (Test-Path $bin)) { throw "Khong thay $bin" }
81
+ $native = Get-ChildItem (Join-Path $Root 'node_modules') -Directory -Filter 'node-addon-require-builtin-win32-x64-msvc' -ErrorAction SilentlyContinue
82
+ if (-not $native) { Write-Warning 'Thieu node-addon-require-builtin-win32-x64-msvc (koffi native)!' }
83
+ Push-Location $Root
84
+ try { Write-Host " dsh --version: $(& $nodeExe $bin --version)" } finally { Pop-Location }
85
+
86
+ # ---- 4. Thong ke ----------------------------------------------------------------
87
+ Write-Host "[4/4] Thong ke ..."
88
+ $files = Get-ChildItem (Join-Path $Root 'node_modules') -Recurse -File -Force -ErrorAction SilentlyContinue
89
+ $mb = [math]::Round((($files | Measure-Object Length -Sum).Sum) / 1MB, 1)
90
+ $longest = $files | Sort-Object { $_.FullName.Length } -Descending | Select-Object -First 1
91
+ $relLen = $longest.FullName.Length - $Root.Length
92
+ Write-Host " node_modules: $($files.Count) files, $mb MB"
93
+ Write-Host " duong dan tuong doi dai nhat: $relLen ky tu (+ goc giai nen phai < $(259 - $relLen))"
94
+ Write-Host " $($longest.FullName.Substring($Root.Length))"
95
+ Write-Host 'XONG.'
scripts/dev/mock-openai.mjs ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Mock OpenAI-compatible server de test offline (khong can API that).
2
+ // node scripts/dev/mock-openai.mjs [port]
3
+ // GET /v1/models, POST /v1/chat/completions (stream + non-stream).
4
+ // Tra loi "MOCK-REPLY: <tin nhan cuoi>". Ghi request vao data/tmp/mock-requests.jsonl.
5
+ import http from 'node:http'
6
+ import { appendFileSync, mkdirSync } from 'node:fs'
7
+ import { dirname, resolve } from 'node:path'
8
+ import { fileURLToPath } from 'node:url'
9
+
10
+ const port = Number(process.argv[2] || 8089)
11
+ const here = dirname(fileURLToPath(import.meta.url))
12
+ const logFile = resolve(here, '..', '..', 'data', 'tmp', 'mock-requests.jsonl')
13
+ mkdirSync(dirname(logFile), { recursive: true })
14
+
15
+ function textOf(content) {
16
+ if (typeof content === 'string') return content
17
+ if (Array.isArray(content)) return content.map((p) => p?.text ?? '').join('')
18
+ return ''
19
+ }
20
+
21
+ const server = http.createServer((req, res) => {
22
+ let body = ''
23
+ req.on('data', (c) => (body += c))
24
+ req.on('end', () => {
25
+ const url = req.url.split('?')[0]
26
+ const auth = req.headers['authorization'] || ''
27
+ if (req.method === 'GET' && url.endsWith('/models')) {
28
+ res.writeHead(200, { 'content-type': 'application/json' })
29
+ res.end(JSON.stringify({ object: 'list', data: [{ id: 'mock-model', object: 'model', owned_by: 'mock' }] }))
30
+ return
31
+ }
32
+ if (req.method === 'POST' && url.endsWith('/chat/completions')) {
33
+ let j = {}
34
+ try { j = JSON.parse(body) } catch {}
35
+ appendFileSync(logFile, JSON.stringify({ t: Date.now(), auth: auth ? auth.slice(0, 14) + '...' : '', model: j.model, stream: j.stream, nMsgs: j.messages?.length, tools: (j.tools || []).map((t) => t.function?.name) }) + '\n')
36
+ const last = [...(j.messages || [])].reverse().find((m) => m.role === 'user')
37
+ const reply = 'MOCK-REPLY: ' + textOf(last?.content).slice(0, 200)
38
+ const id = 'chatcmpl-mock-' + Date.now()
39
+ const created = Math.floor(Date.now() / 1000)
40
+ if (j.stream) {
41
+ res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-cache', connection: 'keep-alive' })
42
+ const send = (delta, finish, usage) =>
43
+ res.write('data: ' + JSON.stringify({ id, object: 'chat.completion.chunk', created, model: j.model, choices: [{ index: 0, delta, finish_reason: finish ?? null }], ...(usage ? { usage } : {}) }) + '\n\n')
44
+ send({ role: 'assistant', content: '' })
45
+ for (const w of reply.split(/(?<= )/)) send({ content: w })
46
+ send({}, 'stop')
47
+ if (j.stream_options?.include_usage) {
48
+ res.write('data: ' + JSON.stringify({ id, object: 'chat.completion.chunk', created, model: j.model, choices: [], usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 } }) + '\n\n')
49
+ }
50
+ res.write('data: [DONE]\n\n')
51
+ res.end()
52
+ } else {
53
+ res.writeHead(200, { 'content-type': 'application/json' })
54
+ res.end(JSON.stringify({ id, object: 'chat.completion', created, model: j.model, choices: [{ index: 0, message: { role: 'assistant', content: reply }, finish_reason: 'stop' }], usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 } }))
55
+ }
56
+ return
57
+ }
58
+ res.writeHead(404, { 'content-type': 'application/json' })
59
+ res.end(JSON.stringify({ error: { message: 'not found: ' + req.method + ' ' + url } }))
60
+ })
61
+ })
62
+ server.listen(port, '127.0.0.1', () => console.log(`mock-openai listening on http://127.0.0.1:${port}/v1`))
scripts/launch.ps1 ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # launch.ps1 - khoi dong DeepSeek Harness (Web UI) o che do portable, offline.
2
+ # Tuong thich Windows PowerShell 5.1 (Win10 22H2). Khong can quyen admin.
3
+ param(
4
+ [switch]$ResetConfig, # render lai cordis.patch.yml tu config roi thoat
5
+ [int]$Port = 3080,
6
+ [switch]$NoOpen, # khong tu mo trinh duyet (dung khi test)
7
+ [string]$Prompt = '', # chay 1 job headless (khong UI) roi in ket qua - dung de test tu dong
8
+ [string]$ConfigFile = '', # (test) file env thay cho config\internal-api.env
9
+ [string]$DataDir = '', # (test) thu muc data thay cho data\
10
+ [string]$PluginsFile = '' # (test) file danh sach plugin thay cho config\plugins.txt
11
+ )
12
+ $ErrorActionPreference = 'Stop'
13
+ try { [Console]::OutputEncoding = [Text.Encoding]::UTF8 } catch {}
14
+
15
+ $Root = Split-Path -Parent $PSScriptRoot
16
+ $Node = Join-Path $Root 'runtime\node\node.exe'
17
+ $Bin = Join-Path $Root 'node_modules\@deepseek-ai\dsh\lib\bin.js'
18
+ $Data = Join-Path $Root 'data'
19
+ $DshHome = Join-Path $Data 'dsh-home'
20
+ $EnvFile = Join-Path $Root 'config\internal-api.env'
21
+ $Sample = Join-Path $Root 'config\internal-api.env.sample'
22
+ $Template = Join-Path $Root 'config\cordis.patch.template.yml'
23
+ if ($DataDir) { $Data = $DataDir; $DshHome = Join-Path $Data 'dsh-home' }
24
+ if ($ConfigFile) { $EnvFile = $ConfigFile }
25
+ $Profile = 'web'
26
+ if ($Prompt) { $Profile = 'headless' }
27
+ $PatchDir = Join-Path $DshHome "profiles\$Profile"
28
+ $Patch = Join-Path $PatchDir 'cordis.patch.yml'
29
+
30
+ function Fail([string]$msg) {
31
+ Write-Host ''
32
+ Write-Host "LỖI: $msg" -ForegroundColor Red
33
+ exit 1
34
+ }
35
+ function Write-Utf8([string]$path, [string]$text) {
36
+ [IO.File]::WriteAllText($path, $text, (New-Object Text.UTF8Encoding $false))
37
+ }
38
+ function Read-EnvFile([string]$path) {
39
+ $h = @{}
40
+ foreach ($line in [IO.File]::ReadAllLines($path, [Text.Encoding]::UTF8)) {
41
+ $l = $line.Trim()
42
+ if ($l -eq '' -or $l.StartsWith('#')) { continue }
43
+ $i = $l.IndexOf('=')
44
+ if ($i -lt 1) { continue }
45
+ $k = $l.Substring(0, $i).Trim()
46
+ $v = $l.Substring($i + 1).Trim()
47
+ if ($v.Length -ge 2 -and (($v[0] -eq '"' -and $v[$v.Length - 1] -eq '"') -or ($v[0] -eq "'" -and $v[$v.Length - 1] -eq "'"))) {
48
+ $v = $v.Substring(1, $v.Length - 2)
49
+ }
50
+ $h[$k] = $v
51
+ }
52
+ return $h
53
+ }
54
+ function YamlQuote([string]$s) {
55
+ return '"' + ($s.Replace('\', '\\').Replace('"', '\"')) + '"'
56
+ }
57
+
58
+ if (-not (Test-Path $Node)) { Fail "Không thấy runtime Node ($Node). Gói chưa được build - chạy scripts\build-portable.ps1 (cần Internet)." }
59
+ if (-not (Test-Path $Bin)) { Fail "Không thấy dsh ($Bin). Gói chưa được build - chạy scripts\build-portable.ps1 (cần Internet)." }
60
+
61
+ # --- 1. Cau hinh API noi bo ---------------------------------------------------
62
+ if (-not (Test-Path $EnvFile)) {
63
+ Copy-Item $Sample $EnvFile
64
+ Write-Host 'Lần chạy đầu: hãy điền BASE_URL, API_KEY, MODEL_ID rồi lưu và đóng Notepad.' -ForegroundColor Yellow
65
+ Start-Process notepad.exe -ArgumentList ('"' + $EnvFile + '"') -Wait
66
+ }
67
+ $cfg = Read-EnvFile $EnvFile
68
+ foreach ($k in 'BASE_URL', 'API_KEY', 'MODEL_ID') {
69
+ if (-not $cfg.ContainsKey($k) -or [string]::IsNullOrWhiteSpace($cfg[$k]) -or $cfg[$k] -like '*CHANGE_ME*') {
70
+ Start-Process notepad.exe -ArgumentList ('"' + $EnvFile + '"')
71
+ Fail "Thiếu '$k' trong $EnvFile. Hãy điền rồi chạy lại."
72
+ }
73
+ }
74
+
75
+ # --- 2. Thu muc du lieu (nam trong goi, khong dung ~/.dsh) --------------------
76
+ foreach ($d in 'dsh-home', 'agents-home', 'workspace', 'tmp', 'npm-cache') {
77
+ New-Item -ItemType Directory -Force -Path (Join-Path $Data $d) | Out-Null
78
+ }
79
+ $env:DSH_HOME = $DshHome
80
+ $env:DSH_AGENTS_HOME = Join-Path $Data 'agents-home'
81
+ $env:TEMP = Join-Path $Data 'tmp'
82
+ $env:TMP = $env:TEMP
83
+ $env:npm_config_cache = Join-Path $Data 'npm-cache'
84
+ $env:NPM_CONFIG_UPDATE_NOTIFIER = 'false'
85
+ $env:DSH_TELEMETRY_DISABLED = '1'
86
+ $env:DSH_DOCUMENTS_DIR = Join-Path $Data 'workspace'
87
+ $env:PATH = (Join-Path $Root 'runtime\node') + ';' + (Join-Path $Root 'node_modules\.bin') + ';' + $env:PATH
88
+
89
+ # --- 3. Bien moi truong cho tien trinh con ------------------------------------
90
+ $env:INTERNAL_API_KEY = $cfg['API_KEY']
91
+ foreach ($k in 'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY', 'ALL_PROXY') {
92
+ if ($cfg.ContainsKey($k) -and $cfg[$k]) { Set-Item -Path "Env:$k" -Value $cfg[$k] }
93
+ }
94
+ if ($cfg.ContainsKey('NODE_EXTRA_CA_CERTS') -and $cfg['NODE_EXTRA_CA_CERTS']) {
95
+ $ca = $cfg['NODE_EXTRA_CA_CERTS']
96
+ if (-not [IO.Path]::IsPathRooted($ca)) { $ca = Join-Path $Root $ca }
97
+ if (-not (Test-Path $ca)) { Fail "NODE_EXTRA_CA_CERTS trỏ tới file không tồn tại: $ca" }
98
+ $env:NODE_EXTRA_CA_CERTS = $ca
99
+ }
100
+
101
+ # --- 3b. Manifest profile: bundles = dsh-base + web-app + plugin trong config\plugins.txt ----
102
+ $Manifest = Join-Path $PatchDir 'package.json'
103
+ if ($Profile -eq 'web' -and ($ResetConfig -or -not (Test-Path $Manifest))) {
104
+ $bundles = @('@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app')
105
+ if (-not $PluginsFile) { $PluginsFile = Join-Path $Root 'config\plugins.txt' }
106
+ if (Test-Path $PluginsFile) {
107
+ foreach ($l in [IO.File]::ReadAllLines($PluginsFile, [Text.Encoding]::UTF8)) {
108
+ $t = $l.Trim()
109
+ if ($t -and -not $t.StartsWith('#')) {
110
+ $pkgDir = Join-Path $Root ('node_modules\' + $t.Replace('/', '\'))
111
+ if (Test-Path $pkgDir) { $bundles += $t }
112
+ else { Write-Host "Bỏ qua plugin '$t' (chưa được cài sẵn trong node_modules)." -ForegroundColor Yellow }
113
+ }
114
+ }
115
+ }
116
+ $items = ($bundles | ForEach-Object { ' ' + (YamlQuote $_) }) -join ",`n"
117
+ $json = "{`n `"name`": `"dsh-profile-web`",`n `"private`": true,`n `"dependencies`": {},`n `"dsh`": {`n `"profile`": {`n `"bundles`": [`n$items`n ]`n }`n }`n}`n"
118
+ New-Item -ItemType Directory -Force -Path $PatchDir | Out-Null
119
+ if (Test-Path $Manifest) { Copy-Item $Manifest ($Manifest + '.bak-' + (Get-Date -Format 'yyyyMMdd-HHmmss')) }
120
+ Write-Utf8 $Manifest $json
121
+ Write-Host "Đã tạo $Manifest ($($bundles.Count) bundle)" -ForegroundColor Green
122
+ }
123
+
124
+ # --- 4. Render cordis.patch.yml (chi khi chua co hoac -ResetConfig) ------------
125
+ if ($ResetConfig -or -not (Test-Path $Patch)) {
126
+ $models = @($cfg['MODEL_ID'] -split '[,;]' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
127
+ $modelYaml = ($models | ForEach-Object { ' - id: ' + (YamlQuote $_) }) -join "`n"
128
+ $proto = 'openai-completions'
129
+ if ($cfg.ContainsKey('API_PROTOCOL') -and $cfg['API_PROTOCOL']) { $proto = $cfg['API_PROTOCOL'].Trim() }
130
+ if ($proto -notin 'openai-completions', 'openai-responses', 'anthropic-messages') {
131
+ Fail "API_PROTOCOL='$proto' không hợp lệ. Chọn: openai-completions | openai-responses | anthropic-messages."
132
+ }
133
+ $tpl = [IO.File]::ReadAllText($Template, [Text.Encoding]::UTF8)
134
+ $out = $tpl.Replace('{{BASE_URL}}', (YamlQuote $cfg['BASE_URL'])).Replace('{{API_PROTOCOL}}', $proto).Replace('{{DEFAULT_MODEL}}', (YamlQuote $models[0])).Replace('{{MODELS}}', $modelYaml)
135
+ New-Item -ItemType Directory -Force -Path $PatchDir | Out-Null
136
+ if (Test-Path $Patch) { Copy-Item $Patch ($Patch + '.bak-' + (Get-Date -Format 'yyyyMMdd-HHmmss')) }
137
+ Write-Utf8 $Patch $out
138
+ Write-Host "Đã tạo $Patch" -ForegroundColor Green
139
+ }
140
+ if ($ResetConfig) { Write-Host 'Đã reset cấu hình model từ config\internal-api.env.'; exit 0 }
141
+
142
+ if ($Prompt) {
143
+ Set-Location (Join-Path $Data 'workspace')
144
+ & $Node $Bin --profile headless $Prompt
145
+ exit $LASTEXITCODE
146
+ }
147
+
148
+ # --- 5. Chon cong ---------------------------------------------------------------
149
+ function Test-PortFree([int]$p) {
150
+ try {
151
+ $l = New-Object Net.Sockets.TcpListener([Net.IPAddress]::Loopback, $p)
152
+ $l.Start(); $l.Stop(); return $true
153
+ } catch { return $false }
154
+ }
155
+ $chosen = 0
156
+ for ($p = $Port; $p -lt $Port + 20; $p++) { if (Test-PortFree $p) { $chosen = $p; break } }
157
+ if ($chosen -eq 0) { Fail "Không tìm được cổng trống từ $Port đến $($Port + 19)." }
158
+
159
+ # --- 6. Chay dsh ---------------------------------------------------------------
160
+ Set-Location (Join-Path $Data 'workspace')
161
+ Write-Host ''
162
+ Write-Host "DeepSeek Harness (portable) - http://127.0.0.1:$chosen" -ForegroundColor Cyan
163
+ Write-Host " Dữ liệu : $DshHome"
164
+ Write-Host " Workspace: $(Join-Path $Data 'workspace')"
165
+ Write-Host ' Đóng cửa sổ này (hoặc Ctrl+C) để dừng.'
166
+ Write-Host ''
167
+ $dshArgs = @($Bin, 'web', '--port', $chosen)
168
+ if ($NoOpen) { $dshArgs += '--no-open' }
169
+ & $Node @dshArgs
170
+ exit $LASTEXITCODE
scripts/pack.ps1 ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pack.ps1 - nen goi portable thanh dist\*.zip
2
+ # KHONG kem: config\internal-api.env (key that), data\ (session, credentials, cache), dist\.
3
+ # Dung bsdtar cua Windows (System32\tar.exe), khong dung Compress-Archive (loi path dai / >2GB).
4
+ #
5
+ # pack.ps1 -> dist\DeepseekHarness-Portable.zip (ban day du, dung noi bo)
6
+ # pack.ps1 -Public -> dist\DSH-AIO-portable-win-x64.zip (ban co the phat hanh cong khai)
7
+ #
8
+ # -Public bo cac thanh phan KHONG duoc phep phat hanh lai:
9
+ # - @anthropic-ai/claude-agent-sdk (+ ban native claude.exe): "All rights reserved", chi dung theo dieu khoan Anthropic.
10
+ # - dsh-deepseek-splash: repo goc khong co LICENSE.
11
+ # va thay package.json / package-lock.json / config\plugins.txt / README.md bang ban da cat cac thanh phan do
12
+ # (de "npm ci" tren may khac khong loi do thieu vendor\ ).
13
+ param(
14
+ [string]$OutName = '',
15
+ [switch]$Public
16
+ )
17
+ $ErrorActionPreference = 'Stop'
18
+ $Root = Split-Path -Parent $PSScriptRoot
19
+ $Dist = Join-Path $Root 'dist'
20
+ $Tar = Join-Path $env:SystemRoot 'System32\tar.exe'
21
+ if (-not $OutName) { $OutName = if ($Public) { 'DSH-AIO-portable-win-x64.zip' } else { 'DeepseekHarness-Portable.zip' } }
22
+ New-Item -ItemType Directory -Force -Path $Dist | Out-Null
23
+
24
+ foreach ($need in 'runtime\node\node.exe', 'node_modules\@deepseek-ai\dsh\lib\bin.js', 'config\internal-api.env.sample') {
25
+ if (-not (Test-Path (Join-Path $Root $need))) { throw "Thieu $need - chay scripts\build-portable.ps1 truoc." }
26
+ }
27
+
28
+ # data\ rong: chi giu 1 file huong dan de thu muc ton tai
29
+ $dataStub = Join-Path $Root 'data\README.txt'
30
+ New-Item -ItemType Directory -Force -Path (Join-Path $Root 'data') | Out-Null
31
+ Set-Content -Encoding ASCII $dataStub 'Thu muc du lieu cua DeepSeek Harness portable (sessions, cau hinh, workspace). Tu dong tao khi chay.'
32
+
33
+ $excludes = @('--exclude', 'config/internal-api.env', '--exclude', 'config/*.bak*', '--exclude', 'scripts/dev/*.jsonl')
34
+ $items = @('Start-DeepseekHarness.cmd', 'Reset-Config.cmd', 'README.md', 'config', 'scripts', 'docs', 'runtime', 'vendor', 'node_modules', 'package.json', 'package-lock.json', 'data/README.txt')
35
+ $overlay = @()
36
+
37
+ if ($Public) {
38
+ $stage = Join-Path $Root 'data\tmp\public-stage'
39
+ if (Test-Path $stage) { Remove-Item -Recurse -Force $stage }
40
+ New-Item -ItemType Directory -Force -Path (Join-Path $stage 'config') | Out-Null
41
+ $utf8 = New-Object Text.UTF8Encoding $false
42
+
43
+ # plugins.txt: bo dsh-deepseek-splash (+ dong chu thich cua no)
44
+ $pl = [IO.File]::ReadAllLines((Join-Path $Root 'config\plugins.txt'), [Text.Encoding]::UTF8) |
45
+ Where-Object { $_ -notmatch 'dsh-deepseek-splash' -and $_ -notmatch '^# Anim khoi dong 4K' }
46
+ [IO.File]::WriteAllLines((Join-Path $stage 'config\plugins.txt'), $pl, $utf8)
47
+
48
+ # package.json + lockfile: go splash (khong can vendor\), khong dong den node_modules cua may build
49
+ Copy-Item (Join-Path $Root 'package.json') $stage
50
+ Copy-Item (Join-Path $Root 'package-lock.json') $stage
51
+ $npmCli = Join-Path $Root 'runtime\node\node_modules\npm\bin\npm-cli.js'
52
+ $nodeExe = Join-Path $Root 'runtime\node\node.exe'
53
+ $env:npm_config_cache = Join-Path $Root 'data\npm-cache'
54
+ $env:NPM_CONFIG_UPDATE_NOTIFIER = 'false'
55
+ Push-Location $stage
56
+ try {
57
+ & $nodeExe $npmCli uninstall dsh-deepseek-splash --package-lock-only --legacy-peer-deps --ignore-scripts --no-audit --no-fund --loglevel=error
58
+ if ($LASTEXITCODE -ne 0) { throw 'npm uninstall (stage) that bai' }
59
+ } finally { Pop-Location }
60
+ if ((Get-Content -Raw (Join-Path $stage 'package.json')) -match 'deepseek-splash') { throw 'splash van con trong package.json (stage)' }
61
+
62
+ # README: them thong bao ban cong khai
63
+ $notice = @(
64
+ '> **Bản công khai (Hugging Face).** Vì giấy phép, bản này **không kèm**:',
65
+ '> - `dsh-deepseek-splash` (repo gốc không có LICENSE) - bỏ qua các dòng nói về splash bên dưới;',
66
+ '> - `@anthropic-ai/claude-agent-sdk` + `claude.exe` (bản quyền Anthropic, không được phát hành lại) - plugin `@deepseek-ai/dsh-subagent-claude-code` vẫn có trong `node_modules\` nhưng **tắt** và sẽ không chạy được nếu thiếu SDK này.',
67
+ '>',
68
+ '> Dựng lại đầy đủ phần còn lại (cần Internet, trên máy build): `scripts\build-portable.ps1` (dùng `package-lock.json` đi kèm; SDK của Anthropic sẽ được tải từ npm theo điều khoản của họ).',
69
+ ''
70
+ )
71
+ $readme = [IO.File]::ReadAllText((Join-Path $Root 'README.md'), [Text.Encoding]::UTF8)
72
+ [IO.File]::WriteAllText((Join-Path $stage 'README.md'), (($notice -join "`n") + "`n" + $readme), $utf8)
73
+
74
+ # luu y: --exclude cua tar cung chan ca file overlay cung duong dan, nen liet ke tuong minh cac file trong config\
75
+ # (tru plugins.txt) thay vi exclude; plugins.txt ban cat nam o overlay.
76
+ $items = $items | Where-Object { $_ -notin 'README.md', 'vendor', 'package.json', 'package-lock.json', 'config' }
77
+ $items += Get-ChildItem (Join-Path $Root 'config') -File |
78
+ Where-Object { $_.Name -notin 'plugins.txt', 'internal-api.env' -and $_.Name -notlike '*.bak*' } |
79
+ ForEach-Object { 'config/' + $_.Name }
80
+ $excludes += @('--exclude', 'node_modules/dsh-deepseek-splash',
81
+ '--exclude', 'node_modules/@anthropic-ai/claude-agent-sdk',
82
+ '--exclude', 'node_modules/@anthropic-ai/claude-agent-sdk-win32-x64')
83
+ $overlay = @('-C', $stage, 'config/plugins.txt', 'package.json', 'package-lock.json', 'README.md')
84
+ }
85
+
86
+ $zip = Join-Path $Dist $OutName
87
+ if (Test-Path $zip) { Remove-Item -Force $zip }
88
+ Push-Location $Root
89
+ try {
90
+ & $Tar -a -cf $zip @excludes @items @overlay
91
+ if ($LASTEXITCODE -ne 0) { throw "tar that bai (exit $LASTEXITCODE)" }
92
+ } finally { Pop-Location }
93
+
94
+ $mb = [math]::Round((Get-Item $zip).Length / 1MB, 1)
95
+ $hash = (Get-FileHash -Algorithm SHA256 $zip).Hash.ToLower()
96
+ Set-Content -Encoding ASCII ($zip + '.sha256') "$hash $OutName"
97
+ Write-Host "Da tao $zip ($mb MB)"
98
+ Write-Host "SHA256: $hash"
scripts/verify-offline.ps1 ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # verify-offline.ps1 - kiem thu tu dong: chay offline (proxy chet), PATH toi thieu, khong admin,
2
+ # khong ghi ra ngoai goi. Dung mock OpenAI API cuc bo; KHONG dung config\internal-api.env that.
3
+ # Tuong thich Windows PowerShell 5.1.
4
+ $ErrorActionPreference = 'Stop'
5
+ $Root = Split-Path -Parent $PSScriptRoot
6
+ $Node = Join-Path $Root 'runtime\node\node.exe'
7
+ $Work = Join-Path $Root 'data\verify-tmp'
8
+ $results = New-Object System.Collections.ArrayList
9
+ function Check([string]$name, [bool]$ok, [string]$detail = '') {
10
+ [void]$results.Add([pscustomobject]@{ Name = $name; Ok = $ok; Detail = $detail })
11
+ $mark = if ($ok) { 'PASS' } else { 'FAIL' }
12
+ $color = if ($ok) { 'Green' } else { 'Red' }
13
+ Write-Host ("[{0}] {1} {2}" -f $mark, $name, $detail) -ForegroundColor $color
14
+ }
15
+
16
+ if (Test-Path $Work) { Remove-Item -Recurse -Force $Work }
17
+ New-Item -ItemType Directory -Force -Path $Work | Out-Null
18
+ $stamp = Get-Date
19
+
20
+ # 0. khong admin
21
+ $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
22
+ Check 'Khong chay voi quyen admin' (-not $isAdmin) $(if ($isAdmin) { '(dang elevated - hay chay lai o cmd thuong)' } else { '' })
23
+
24
+ # 1. mock API
25
+ $port = 18089
26
+ $mockLog = Join-Path $Root 'data\tmp\mock-requests.jsonl'
27
+ if (Test-Path $mockLog) { Remove-Item $mockLog -Force }
28
+ $mock = Start-Process -FilePath $Node -ArgumentList @('"' + (Join-Path $Root 'scripts\dev\mock-openai.mjs') + '"', $port) -PassThru -WindowStyle Hidden
29
+ Start-Sleep -Seconds 2
30
+
31
+ try {
32
+ # 2. config tam: proxy chet => moi ket noi ra ngoai (khong phai loopback) deu hong
33
+ $cfg = Join-Path $Work 'test.env'
34
+ @(
35
+ "BASE_URL=http://127.0.0.1:$port/v1",
36
+ 'API_KEY=sk-verify-offline',
37
+ 'MODEL_ID=mock-model',
38
+ 'API_PROTOCOL=openai-completions',
39
+ 'HTTP_PROXY=http://127.0.0.1:9',
40
+ 'HTTPS_PROXY=http://127.0.0.1:9'
41
+ ) | Set-Content -Encoding ASCII $cfg
42
+ $data = Join-Path $Work 'data'
43
+
44
+ # 3. chay headless voi PATH toi thieu (khong co node/git/npm cua he thong)
45
+ $minPath = "$env:SystemRoot\System32;$env:SystemRoot;$env:SystemRoot\System32\WindowsPowerShell\v1.0"
46
+ $launch = Join-Path $Root 'scripts\launch.ps1'
47
+ $outFile = Join-Path $Work 'out.txt'
48
+ $psi = New-Object Diagnostics.ProcessStartInfo
49
+ $psi.FileName = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
50
+ $psi.Arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$launch`" -ConfigFile `"$cfg`" -DataDir `"$data`" -Prompt `"say hello`""
51
+ $psi.UseShellExecute = $false
52
+ $psi.RedirectStandardOutput = $true
53
+ $psi.RedirectStandardError = $true
54
+ $psi.EnvironmentVariables['PATH'] = $minPath
55
+ $p = [Diagnostics.Process]::Start($psi)
56
+ $stdout = $p.StandardOutput.ReadToEnd()
57
+ $stderr = $p.StandardError.ReadToEnd()
58
+ if (-not $p.WaitForExit(120000)) { $p.Kill(); Check 'Headless job hoan thanh <120s' $false } else { Check 'Headless job thoat ma 0' ($p.ExitCode -eq 0) "exit=$($p.ExitCode)" }
59
+ Set-Content -Encoding UTF8 $outFile ($stdout + "`n--- STDERR ---`n" + $stderr)
60
+ Check 'Tra loi tu mock API xuat hien (MOCK-REPLY)' (($stdout + $stderr) -match 'MOCK-REPLY') "(xem $outFile)"
61
+
62
+ # 4. mock nhan request voi key dung (bien moi truong, khong nam trong YAML)
63
+ $log = if (Test-Path $mockLog) { Get-Content $mockLog } else { @() }
64
+ Check 'Mock API nhan >=1 request chat' ($log.Count -ge 1) "($($log.Count) request)"
65
+ Check 'Header Authorization mang key tu INTERNAL_API_KEY' (($log -join "`n") -match 'Bearer sk-veri') ''
66
+ $yaml = Get-Content -Raw (Join-Path $data 'dsh-home\profiles\headless\cordis.patch.yml')
67
+ Check 'API key khong nam trong cordis.patch.yml' ($yaml -notmatch 'sk-verify') ''
68
+
69
+ # 5. du lieu nam trong goi
70
+ Check 'DSH_HOME nam trong goi (data\dsh-home co sessions)' (Test-Path (Join-Path $data 'dsh-home\sessions')) ''
71
+
72
+ # 6. khong ghi ra ngoai goi
73
+ $up = $env:USERPROFILE
74
+ Check '~\.dsh khong bi tao moi' (-not ((Test-Path "$up\.dsh") -and ((Get-Item "$up\.dsh").LastWriteTime -gt $stamp))) ''
75
+ Check '~\.agents khong bi tao moi' (-not ((Test-Path "$up\.agents") -and ((Get-Item "$up\.agents").LastWriteTime -gt $stamp))) ''
76
+ $docs = [Environment]::GetFolderPath('MyDocuments')
77
+ Check 'Documents\deepseek-harness khong bi tao' (-not ((Test-Path "$docs\deepseek-harness") -and ((Get-Item "$docs\deepseek-harness").LastWriteTime -gt $stamp))) ''
78
+
79
+ # 7. profile web + plugin (better-sidebar, spotlight, agent-teams, browser-cdp)
80
+ $wport = 18090
81
+ $wdata = Join-Path $Work 'webdata'
82
+ $wout = Join-Path $Work 'web-out.txt'
83
+ $werr = Join-Path $Work 'web-err.txt'
84
+ $oldPath = $env:PATH
85
+ $env:PATH = $minPath
86
+ $web = Start-Process -FilePath $psi.FileName -PassThru -WindowStyle Hidden -RedirectStandardOutput $wout -RedirectStandardError $werr `
87
+ -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$launch`" -ConfigFile `"$cfg`" -DataDir `"$wdata`" -NoOpen -Port $wport"
88
+ $env:PATH = $oldPath
89
+ try {
90
+ $url = $null
91
+ for ($i = 0; $i -lt 60 -and -not $url; $i++) {
92
+ Start-Sleep -Seconds 1
93
+ $txt = (Get-Content -Raw $wout -ErrorAction SilentlyContinue) + (Get-Content -Raw $werr -ErrorAction SilentlyContinue)
94
+ if ($txt -match '(http://127\.0\.0\.1:\d+/\?token=[A-Za-z0-9_-]+)') { $url = $Matches[1] }
95
+ }
96
+ Check 'Web UI khoi dong (co URL token)' ([bool]$url) ''
97
+ $txt = (Get-Content -Raw $wout -ErrorAction SilentlyContinue) + (Get-Content -Raw $werr -ErrorAction SilentlyContinue)
98
+ Check 'Khong co plugin nao loi khi kich hoat' (($txt -notmatch 'did not activate') -and ($txt -notmatch 'failed to import') -and ($txt -notmatch 'skipping profile bundle')) '(xem web-out/web-err.txt)'
99
+ if ($url) {
100
+ $sess = New-Object Microsoft.PowerShell.Commands.WebRequestSession
101
+ $r = Invoke-WebRequest -UseBasicParsing -Uri $url -WebSession $sess -TimeoutSec 30
102
+ Check 'GET / voi token -> 200' ($r.StatusCode -eq 200) ''
103
+ $base = ([uri]$url).GetLeftPart('Authority')
104
+ foreach ($pl in 'dsh-better-sidebar', '@akshaydev17/dsh-spotlight', '@nanmicoder/dsh-agent-teams', 'dsh-browser-cdp') {
105
+ $code = 0
106
+ if ($r.Content -match ('"id":"' + [regex]::Escape($pl) + '","url":"([^"]+)"')) {
107
+ try { $code = (Invoke-WebRequest -UseBasicParsing -Uri "$base/$($Matches[1])" -WebSession $sess -TimeoutSec 30).StatusCode } catch { $code = [int]$_.Exception.Response.StatusCode }
108
+ Check "Plugin co trong roster va client tai duoc: $pl" ($code -eq 200) "(HTTP $code)"
109
+ } else {
110
+ Check "Plugin co trong roster va client tai duoc: $pl" $false '(khong co trong roster)'
111
+ }
112
+ }
113
+ }
114
+ # splash la tuy chon (ban cong khai khong kem): chi kiem tra neu plugin co trong goi
115
+ if ($url -and (Test-Path (Join-Path $Root 'node_modules\dsh-deepseek-splash'))) {
116
+ Check 'Splash (dsh-deepseek-splash) duoc chen vao index HTML' ($r.Content -match 'deepseek-splash') ''
117
+ $code = 0
118
+ try { $code = (Invoke-WebRequest -UseBasicParsing -Uri "$base/api/deepseek-splash/harness-poster.jpg" -WebSession $sess -TimeoutSec 30).StatusCode } catch { $code = [int]$_.Exception.Response.StatusCode }
119
+ Check 'Splash phuc vu asset cuc bo (poster)' ($code -eq 200) "(HTTP $code)"
120
+ }
121
+ # khong co ket noi TCP ra ngoai loopback tu bat ky tien trinh nao thuoc goi
122
+ $ids = @(Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -like "$Root*" } | ForEach-Object { $_.ProcessId })
123
+ $ext = @(Get-NetTCPConnection -ErrorAction SilentlyContinue | Where-Object { ($ids -contains $_.OwningProcess) -and ($_.RemoteAddress -notin '127.0.0.1', '::1', '0.0.0.0', '::') })
124
+ Check 'Khong co ket noi TCP ra ngoai loopback (web + plugin)' ($ext.Count -eq 0) "($($ext.Count) ket noi, $($ids.Count) tien trinh)"
125
+ Check '~\.dsh van khong bi tao moi sau khi chay plugin' (-not ((Test-Path "$up\.dsh") -and ((Get-Item "$up\.dsh").LastWriteTime -gt $stamp))) ''
126
+ } finally {
127
+ if ($web -and -not $web.HasExited) { & taskkill.exe /PID $web.Id /T /F | Out-Null }
128
+ }
129
+ } finally {
130
+ if ($mock -and -not $mock.HasExited) { Stop-Process -Id $mock.Id -Force }
131
+ Start-Sleep -Milliseconds 500
132
+ }
133
+
134
+ $fail = @($results | Where-Object { -not $_.Ok }).Count
135
+ Write-Host ''
136
+ if ($fail -eq 0) { Write-Host "TAT CA $($results.Count) KIEM TRA DAT." -ForegroundColor Green; Remove-Item -Recurse -Force $Work -ErrorAction SilentlyContinue; exit 0 }
137
+ Write-Host "$fail kiem tra that bai. Chi tiet: $Work" -ForegroundColor Red
138
+ exit 1