DSH-AIO: DeepSeek Harness portable (offline, Windows x64)
Browse files- DSH-AIO-portable-win-x64.zip +3 -0
- DSH-AIO-portable-win-x64.zip.sha256 +1 -0
- README.md +133 -0
- Reset-Config.cmd +5 -0
- Start-DeepseekHarness.cmd +8 -0
- config/cordis.patch.template.yml +24 -0
- config/internal-api.env.sample +23 -0
- config/plugins.txt +15 -0
- docs/THEM-PLUGIN.md +241 -0
- package-lock.json +0 -0
- package.json +1 -0
- scripts/build-portable.ps1 +95 -0
- scripts/dev/mock-openai.mjs +62 -0
- scripts/launch.ps1 +170 -0
- scripts/pack.ps1 +98 -0
- scripts/verify-offline.ps1 +138 -0
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
|