From a83af2f33c542435e54d82c598c7e9c7ea67a29b Mon Sep 17 00:00:00 2001 From: owo <17620121969@163.com> Date: Fri, 14 Aug 2026 17:46:01 +0800 Subject: [PATCH] Simplify install with one-click scripts; honor HTTP(S)_PROXY for Telegram. Add install.ps1/install.sh, shrink README to script-first flow, register bot commands, and route API calls through undici EnvHttpProxyAgent when a proxy is set (v0.2.2). Co-authored-by: Cursor --- README.md | 316 ++++---- .../telegram-agent/cordis.patch.example.yml | 11 +- lib/bridge.js | 12 + lib/client.d.ts | 6 + lib/client.js | 27 +- lib/index.js | 14 +- package-lock.json | 693 ++++++++++++++++++ package.json | 20 +- scripts/install.ps1 | 155 ++++ scripts/install.sh | 124 ++++ src/bridge.ts | 12 + src/client.ts | 39 +- src/index.ts | 15 +- tests/apply.test.ts | 2 +- tests/bridge.test.ts | 13 +- 15 files changed, 1255 insertions(+), 204 deletions(-) create mode 100644 package-lock.json create mode 100644 scripts/install.ps1 create mode 100644 scripts/install.sh diff --git a/README.md b/README.md index fdf7c2f..7ef440f 100644 --- a/README.md +++ b/README.md @@ -2,201 +2,153 @@ [English](#english) · [中文](#中文) -**Keywords / 关键词:** Telegram · Bot · Mobile · Remote · Channel · Sessions · Bind · Unbind · Remove · Clear · DeepSeek Harness · DSH · Cordis · dsh-plugin · Agent · Live session · 手机 · 远程 · 遥控器 · 会话列表 · 附着 · 解绑 · 白名单 · 安装 +Telegram **手机遥控器** for DeepSeek Harness:附着本机正在跑的 Web 会话,与电脑 **同轨迹、双向可见**(Codex-style)。 -DeepSeek Harness **手机遥控器**:用 Telegram Bot **附着本机正在运行的 Web 会话**(与 Web **同轨迹、双向可见**)。对标 Codex phone remote——本机会话是真相源,手机不另开平行对话。 +**Keywords:** Telegram · Bot · Mobile · Remote · DSH · Cordis · dsh-plugin · sessions · bind --- ## 中文 -### 这是什么(30 秒看懂) +### 30 秒理解 -1. 电脑上先用 `dsh web` 打开一个对话(保持开着)。 -2. 手机 Telegram 找你的 Bot,发 `/sessions`,点选那个对话。 -3. 之后手机发的话,会进**同一个**电脑会话;电脑上的回复,手机会同步看到。 - -| | 旧「平行桥」 | **本插件** | -|---|---|---| -| 会话 | Telegram 另开一个 agent | 附着电脑里 **正在跑的** 会话 | -| Web | 不通 | 手机与 Web **同一条轨迹** | +1. 电脑 `dsh web` 开着,并打开一个对话 +2. 手机 Bot 发 `/sessions`,点选该对话 +3. 之后手机 ↔ Web 走**同一条**轨迹 --- -### 安装前请准备(缺一不可) +### 一键安装(推荐) -请用笔或备忘录记下三样东西: +**先准备两样东西:** -| # | 准备什么 | 怎么拿 | -|---|---|---| -| 1 | 电脑已能打开 DeepSeek Harness 网页 | 终端运行 `dsh web`,浏览器能打开(常见 `http://127.0.0.1:3080`) | -| 2 | Telegram **Bot Token** | 手机打开 Telegram → 搜 `@BotFather` → `/newbot` 按提示创建 → 复制一长串类似 `123456:AA...` 的 token | -| 3 | 你的 Telegram **数字 User ID** | 搜 `@userinfobot` → 点 Start → 它会回复一串**纯数字**(不是 @用户名) | +| 准备 | 怎么拿 | +|---|---| +| Bot Token | Telegram 搜 `@BotFather` → `/newbot` → 复制 token | +| 数字 User ID | 搜 `@userinfobot` → Start → 复制纯数字 | -> 安全提醒:Token 相当于钥匙,**不要发到公开群、不要提交到 GitHub**。若已泄露,去 BotFather 用 `/revoke` 作废并换新。 +> Token 不要发到公开群;泄露了去 BotFather `/revoke`。 + +#### Windows(PowerShell) + +**方式 A — 远程一键(交互输入 Token / User ID):** + +```powershell +irm https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | iex +``` + +**方式 B — 带参数(不交互):** + +```powershell +git clone https://github.com/hi-wenw/dsh-telegram-channel.git +cd dsh-telegram-channel +.\scripts\install.ps1 -Token '这里粘贴Token' -UserId '这里粘贴数字ID' +``` + +脚本会自动: + +1. 写入用户环境变量 `DSH_TELEGRAM_TOKEN`、`DSH_TELEGRAM_ALLOWED_USER_IDS` +2. 给 profile 补上 `allowBuilds`(防止 pnpm 拦截) +3. 执行 `dsh plugin --profile web add github:hi-wenw/dsh-telegram-channel` + +**不会**再 `insert` 同名插件行(避免 `duplicate loader entry id`)。 + +#### macOS / Linux + +```bash +export DSH_TELEGRAM_TOKEN='你的Token' +export DSH_TELEGRAM_ALLOWED_USER_IDS='你的数字ID' +curl -fsSL https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.sh | bash +``` + +或克隆后:`./scripts/install.sh --token '...' --user-id '...'` --- -### 安装步骤(照着做即可) - -下面假设你用 **Windows**,命令在 **PowerShell** 或 **CMD** 里执行。 - -#### 第 1 步:安装插件到 web 配置 - -打开终端,复制粘贴整行后回车: - -```powershell -dsh plugin --profile web add github:hi-wenw/dsh-telegram-channel -``` - -**成功时**大致会看到安装完成、没有红色大段报错。 - -**如果报错出现 `allowBuilds` / `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`:** - -1. 用记事本打开这个文件(没有就新建): - `C:\Users\wenw\.dsh\profiles\web\pnpm-workspace.yaml` -2. 在文件里加入(或合并)下面内容(路径以报错里打印的为准;若报错给了更长一行,用报错那一行): - -```yaml -packages: - - . - -nodeLinker: hoisted -autoInstallPeers: false - -allowBuilds: - dsh-telegram-channel: true -``` - -3. 保存文件,再重新执行第 1 步那条 `dsh plugin ... add ...` 命令。 - -> 说明:从 **v0.2.1** 起,仓库已自带编译好的 `lib/`,一般**不需要**再改 `allowBuilds`。若你装到的仍是旧版缓存,可先执行下面「第 1b 步」再装。 - -#### 第 1b 步(可选):清掉旧失败缓存后再装 - -若刚才失败过,可先: - -```powershell -dsh plugin --profile web remove dsh-telegram-channel -``` - -然后再执行第 1 步。仍失败时,用本地目录安装(最稳): - -```powershell -git clone https://github.com/hi-wenw/dsh-telegram-channel.git D:\gitData\dsh-telegram-channel -dsh plugin --profile web add D:\gitData\dsh-telegram-channel -``` - -#### 第 2 步:设置 Bot Token(推荐环境变量) - -**当前 PowerShell 窗口临时生效**(关窗口会丢,适合先试通): - -```powershell -$env:DSH_TELEGRAM_TOKEN = "这里粘贴你的BotFather_Token" -``` - -**想长期生效(用户级环境变量)**: - -1. Win 键搜索「编辑系统环境变量」→「环境变量」。 -2. 在「用户变量」点「新建」: - - 变量名:`DSH_TELEGRAM_TOKEN` - - 变量值:粘贴 BotFather 给你的 token -3. 确定保存后,**关掉所有旧终端**,新开一个终端。 - -#### 第 3 步:写入白名单(只允许你自己用) - -打开(没有就新建)profile 补丁文件,常见路径: - -`C:\Users\wenw\.dsh\profiles\web\cordis.patch.yml` - -写入下面内容,把 `123456789` 换成你的 **数字 User ID**: - -```yaml -- insert: - - id: dsh-telegram-channel - name: dsh-telegram-channel - config: - token: "" - allowedUserIds: [123456789] -``` - -注意: - -- `allowedUserIds` 里必须是**数字**,两边是方括号。 -- `token: ""` 表示继续用环境变量里的 `DSH_TELEGRAM_TOKEN`(推荐)。 -- 若插件安装时已经自动插入过同名行,不要重复两份;在已有行上改 `allowedUserIds` 即可。 - -也可用示例文件对照:仓库里的 `examples/telegram-agent/cordis.patch.example.yml`。 - -#### 第 4 步:启动(或重启)DSH Web - -**重要:改完环境变量 / 补丁后,必须重新开一次 profile。** +### 安装后只用三步 ```powershell +# 1. 关掉旧终端,新开一个,然后: dsh web + +# 2. 浏览器打开一个对话(保持运行) + +# 3. 手机对 Bot: +# /start → /sessions → 点选 → 正常聊天 ``` -等终端没有报错,浏览器能打开 Harness 页面。 - -#### 第 5 步:电脑上先开一个对话 - -在 Web 页面里 **新建或打开一个会话**,随便让它处于可用状态(这就是手机要遥控的「本机会话」)。 - -> 如果 Web 里一个会话都没有,手机发 `/sessions` 会提示「当前没有正在运行的本机会话」。 - -#### 第 6 步:手机连上 Bot - -1. 打开 Telegram,搜索你创建的 Bot(BotFather 给你的名字)。 -2. 点 **Start** / 发送 `/start`。 -3. 发送 `/sessions`。 -4. 点按钮,选择电脑上那个会话(「已附着…」)。 -5. 发一句普通话,例如:`你好,请回复收到`。 -6. 看手机是否收到回复,并回到电脑 Web:**应是同一条对话轨迹**。 - -常用命令: +输入框旁的 **/** 菜单应有:`start` `sessions` `status` `unbind` `help`。 | 命令 | 作用 | |---|---| -| `/sessions` 或 `/list` | 列出本机 live 会话并选择附着 | -| `/status` | 看当前绑了谁 | -| `/unbind` 或 `/disconnect` | 断开手机绑定(**不关闭**电脑会话) | +| `/sessions` | 列出并附着本机 live 会话 | +| `/status` | 当前绑定 | +| `/unbind` | 只断开手机,**不关**电脑会话 | | `/help` | 帮助 | --- -### 配置项一览 +### 手工安装(可选) -| 键 | 默认 | 含义 | -|---|---|---| -| `token` | 空 → 读 `DSH_TELEGRAM_TOKEN` | Bot token | -| `allowedUserIds` | `[]` | 白名单;空 = 谁都不能用 | -| `allowAllUsers` | `false` | `true` 时谁都能聊(仅调试) | -| `maxMessageLength` | `4096` | 单条消息分片长度 | -| `pollingTimeoutSec` | `30` | 长轮询超时(秒) | +若不想跑脚本: + +```powershell +# 用户环境变量(或当前会话 $env:...) +# DSH_TELEGRAM_TOKEN = BotFather token +# DSH_TELEGRAM_ALLOWED_USER_IDS = 数字ID + +dsh plugin --profile web add github:hi-wenw/dsh-telegram-channel +dsh web +``` + +本地目录安装: + +```powershell +dsh plugin --profile web add D:\path\to\dsh-telegram-channel +``` + +需要改 YAML 白名单时,**只能按 id 覆盖**,不要再 `insert` 同名 id: + +```yaml +- id: dsh-telegram-channel + config: + token: "" + allowedUserIds: [123456789] +``` + +示例:`examples/telegram-agent/cordis.patch.example.yml`。 --- -### 故障排查(对照表) +### 配置 -| 你看到的现象 | 怎么处理 | +| 键 / 环境变量 | 含义 | |---|---| -| `allowBuilds` / `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` | 见上文「第 1 步」补 `pnpm-workspace.yaml`,或改用本地目录安装;并确认装的是 **v0.2.1+** | -| 日志 `missing bot token`;Bot 完全不说话 | 检查 `DSH_TELEGRAM_TOKEN` 是否设置;**新开终端**再 `dsh web` | -| 回复「无权限。」 | `allowedUserIds` 是否写错;必须是 @userinfobot 给的数字 ID | -| `/sessions` 说没有会话 | 先在 Web 打开对话并保持 `dsh web` 运行 | -| 「尚未绑定」 | 先 `/sessions` 点选 | -| 「会话已不在本机运行」 | Web 已关掉该会话;重新开再 `/sessions` | -| Telegram 401 | Token 错了或被 revoke;去 BotFather 换新 | +| `token` / `DSH_TELEGRAM_TOKEN` | Bot token | +| `allowedUserIds` / `DSH_TELEGRAM_ALLOWED_USER_IDS` | 白名单;都空 = 谁都不能用 | +| `allowAllUsers` | `true` 仅调试 | +| `maxMessageLength` | 默认 4096 | +| `pollingTimeoutSec` | 默认 30 | + +若本机用了 HTTP(S)_PROXY 访问 Telegram,插件会自动走代理(无需再设 `NODE_USE_ENV_PROXY`)。 --- -### 安全 +### 故障排查 -- 白名单默认 **谁都不允许**,必须填你的 ID。 -- Token 优先放环境变量,不要写进 Git。 -- `/unbind` 只断开手机,不会结束电脑上的 agent。 +| 现象 | 处理 | +|---|---| +| `allowBuilds` / `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` | 再跑一遍安装脚本,或手工在 `~\.dsh\profiles\web\pnpm-workspace.yaml` 加 `allowBuilds.dsh-telegram-channel: true` | +| `duplicate loader entry id: dsh-telegram-channel` | 用户 patch **不要 insert** 同名 id;用上面的 `- id:` 覆盖,或只用环境变量白名单 | +| 手机完全没回复 / ConnectTimeout | 打开本地代理(如 7890),重启 `dsh web` | +| `missing bot token` | 检查环境变量;**新开终端**再 `dsh web` | +| 「无权限」 | User ID 必须是 `@userinfobot` 的数字 | +| `/sessions` 无会话 | 先在 Web 打开对话 | +| Telegram 401 | Token 错了或被 revoke | -### 贡献者开发 +--- + +### 开发 ```powershell git clone https://github.com/hi-wenw/dsh-telegram-channel.git @@ -216,36 +168,42 @@ npm run build ### What this is -Telegram **mobile remote** for **live** DeepSeek Harness Web agents in the same `dsh web` process. Desktop/Web is the source of truth (Codex-style). Telegram **attaches**; it does not create a parallel hidden agent. +Telegram **mobile remote** for **live** DeepSeek Harness Web agents. Desktop/Web is the source of truth; the phone **attaches** (no parallel hidden agent). -### Install (Windows) +### One-click install (Windows) -1. Prerequisites: working `dsh web`, BotFather **token**, numeric Telegram **user id** (`@userinfobot`). -2. Install: +```powershell +irm https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | iex +``` + +Or with params after clone: + +```powershell +.\scripts\install.ps1 -Token '...' -UserId '123456789' +``` + +The script sets user env vars, ensures `allowBuilds`, and runs `dsh plugin add`. Then: + +```powershell +dsh web +# open a Web chat → phone: /sessions → bind +``` + +### Unix + +```bash +export DSH_TELEGRAM_TOKEN='...' +export DSH_TELEGRAM_ALLOWED_USER_IDS='123456789' +curl -fsSL https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.sh | bash +``` + +### Manual ```powershell dsh plugin --profile web add github:hi-wenw/dsh-telegram-channel ``` -If pnpm prints `allowBuilds` / `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`, add under `C:\Users\\.dsh\profiles\web\pnpm-workspace.yaml`: - -```yaml -allowBuilds: - dsh-telegram-channel: true -``` - -Then re-run the install command. **v0.2.1+** ships prebuilt `lib/` so this is usually unnecessary. - -Fallback (most reliable): - -```powershell -git clone https://github.com/hi-wenw/dsh-telegram-channel.git -dsh plugin --profile web add -``` - -3. Set token: `$env:DSH_TELEGRAM_TOKEN = "..."`. -4. Patch `allowedUserIds` in `~\.dsh\profiles\web\cordis.patch.yml` (see example above). -5. Restart `dsh web`, open a Web conversation, then on phone: `/sessions` → bind → chat. +Allowlist via `DSH_TELEGRAM_ALLOWED_USER_IDS` (preferred) or id-targeted YAML override — **never** re-`insert` the same plugin id. ### License diff --git a/examples/telegram-agent/cordis.patch.example.yml b/examples/telegram-agent/cordis.patch.example.yml index e6e82e6..1370c2c 100644 --- a/examples/telegram-agent/cordis.patch.example.yml +++ b/examples/telegram-agent/cordis.patch.example.yml @@ -1,6 +1,5 @@ -- insert: - - id: dsh-telegram-channel - name: dsh-telegram-channel - config: - token: '' - allowedUserIds: [123456789] +# 插件安装时已自动 insert。用户 patch 只需按 id 覆盖 config(不要再 insert 同名 id)。 +- id: dsh-telegram-channel + config: + token: '' + allowedUserIds: [123456789] diff --git a/lib/bridge.js b/lib/bridge.js index 707ded7..a2cf36e 100644 --- a/lib/bridge.js +++ b/lib/bridge.js @@ -51,9 +51,21 @@ export class TelegramBridge { this.ctx.logger.error(this.redact(err)); }); }); + void this.client.setMyCommands([ + { command: 'start', description: '欢迎与用法' }, + { command: 'sessions', description: '列出并附着本机 live 会话' }, + { command: 'status', description: '查看当前绑定' }, + { command: 'unbind', description: '断开手机绑定(不关闭本机会话)' }, + { command: 'help', description: '显示帮助' }, + ]).then(() => { + this.ctx.logger.info('dsh-telegram-channel: bot commands registered'); + }).catch((err) => { + this.ctx.logger.warn(`dsh-telegram-channel: setMyCommands failed: ${this.redact(err)}`); + }); if (!this.polling) { this.polling = true; this.pollAbort = new AbortController(); + this.ctx.logger.info('dsh-telegram-channel: long-polling started'); this.pollPromise = this.pollLoop(); } } diff --git a/lib/client.d.ts b/lib/client.d.ts index 72e13a9..6448848 100644 --- a/lib/client.d.ts +++ b/lib/client.d.ts @@ -35,6 +35,10 @@ export interface InlineKeyboardButton { export interface InlineKeyboardMarkup { inline_keyboard: InlineKeyboardButton[][]; } +export interface TelegramBotCommand { + command: string; + description: string; +} export interface TelegramClientOptions { fetch?: typeof fetch; baseUrl?: string; @@ -46,6 +50,7 @@ export interface TelegramClientLike { sendMessage(chatId: number, text: string, parseMode?: string, replyMarkup?: InlineKeyboardMarkup): Promise; sendChatAction(chatId: number, action: string): Promise; answerCallbackQuery(callbackQueryId: string, text?: string): Promise; + setMyCommands(commands: TelegramBotCommand[]): Promise; } export declare class TelegramClient implements TelegramClientLike { private readonly token; @@ -60,4 +65,5 @@ export declare class TelegramClient implements TelegramClientLike { sendMessage(chatId: number, text: string, parseMode?: string, replyMarkup?: InlineKeyboardMarkup): Promise; sendChatAction(chatId: number, action: string): Promise; answerCallbackQuery(callbackQueryId: string, text?: string): Promise; + setMyCommands(commands: TelegramBotCommand[]): Promise; } diff --git a/lib/client.js b/lib/client.js index 4b3af29..fccb208 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1,3 +1,25 @@ +import { EnvHttpProxyAgent, fetch as undiciFetch } from 'undici'; +function resolveProxyUrl() { + return (process.env.HTTPS_PROXY + || process.env.HTTP_PROXY + || process.env.https_proxy + || process.env.http_proxy + || undefined); +} +/** + * Node's global `fetch` ignores HTTP(S)_PROXY unless `NODE_USE_ENV_PROXY=1`. + * Undici's EnvHttpProxyAgent honors the env vars (and NO_PROXY). + */ +function createDefaultFetch() { + if (!resolveProxyUrl()) + return globalThis.fetch; + const agent = new EnvHttpProxyAgent(); + const proxied = ((input, init) => undiciFetch(input, { + ...init, + dispatcher: agent, + })); + return proxied; +} export class TelegramClient { token; fetchImpl; @@ -8,7 +30,7 @@ export class TelegramClient { throw new Error('bot token is required'); } this.token = token; - this.fetchImpl = options.fetch ?? fetch; + this.fetchImpl = options.fetch ?? createDefaultFetch(); this.baseUrl = options.baseUrl ?? 'https://api.telegram.org'; this.pollingTimeoutSec = options.pollingTimeoutSec ?? 30; } @@ -69,4 +91,7 @@ export class TelegramClient { body.text = text; return this.call('answerCallbackQuery', body); } + async setMyCommands(commands) { + return this.call('setMyCommands', { commands }); + } } diff --git a/lib/index.js b/lib/index.js index b3aab0a..99133c5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -9,6 +9,18 @@ export const Config = Schema.object({ maxMessageLength: Schema.number().default(4096), pollingTimeoutSec: Schema.number().default(30), }); +function resolveAllowedUserIds(config) { + if (config.allowedUserIds && config.allowedUserIds.length > 0) { + return config.allowedUserIds; + } + const raw = process.env.DSH_TELEGRAM_ALLOWED_USER_IDS ?? ''; + return raw + .split(/[,;\s]+/) + .map((part) => part.trim()) + .filter(Boolean) + .map((part) => Number(part)) + .filter((n) => Number.isFinite(n) && n > 0); +} export function apply(ctx, config) { const token = (config.token && config.token.length > 0) ? config.token @@ -19,7 +31,7 @@ export function apply(ctx, config) { } const bridge = new TelegramBridge(ctx, { token, - allowedUserIds: config.allowedUserIds ?? [], + allowedUserIds: resolveAllowedUserIds(config), allowAllUsers: config.allowAllUsers ?? false, maxMessageLength: config.maxMessageLength ?? 4096, pollingTimeoutSec: config.pollingTimeoutSec ?? 30, diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..bad7726 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,693 @@ +{ + "name": "dsh-telegram-channel", + "version": "0.2.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dsh-telegram-channel", + "version": "0.2.1", + "license": "MIT", + "dependencies": { + "undici": "^7.29.0" + }, + "devDependencies": { + "@deepseek-ai/cordis": "^4.0.1", + "@deepseek-ai/dsh-agent": "^0.1.0-rc.6", + "@deepseek-ai/dsh-llm": "^0.0.1-rc.1", + "@deepseek-ai/dsh-session": "^0.0.1-rc.1", + "@deepseek-ai/schemastery": "^3.18.1", + "@types/node": "^22.0.0", + "tsx": "^4.19.0", + "typescript": "^5.9.0" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "^4.0.1", + "@deepseek-ai/dsh-agent": "^0.1.0-rc.6", + "@deepseek-ai/dsh-llm": "^0.0.1-rc.1", + "@deepseek-ai/dsh-session": "^0.0.1-rc.1", + "@deepseek-ai/schemastery": "^3.18.1" + } + }, + "node_modules/@deepseek-ai/cordis": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/cordis/-/cordis-4.0.1.tgz", + "integrity": "sha512-YBdskTU2Po1kru3GgcUWUbkTsPMA9LkSQDAY8rBkFJeajdgcQad3QPJZE26JyK99Xb6HaASvoXg2DSUTeN/0Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@deepseek-ai/cosmokit": "^1.8.2", + "@standard-schema/spec": "^1.1.0" + }, + "bin": { + "cordis": "bin.js" + }, + "peerDependencies": { + "@deepseek-ai/cordis-plugin-include": "^1.0.6", + "@deepseek-ai/cordis-plugin-loader": "^1.0.2" + }, + "peerDependenciesMeta": { + "@deepseek-ai/cordis-plugin-include": { + "optional": true + }, + "@deepseek-ai/cordis-plugin-loader": { + "optional": true + } + } + }, + "node_modules/@deepseek-ai/cosmokit": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@deepseek-ai/cosmokit/-/cosmokit-1.8.2.tgz", + "integrity": "sha512-muBOKtSrUKU5m/xpq8ZXWL6hQ/jgd4PhU2PqH97bcxIiLEJfNwZOGQEx4t/aS/GgxRAR+ra9pMHPMtTHU4sqqA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@deepseek-ai/dsh-agent": { + "version": "0.1.0-rc.6", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-agent/-/dsh-agent-0.1.0-rc.6.tgz", + "integrity": "sha512-vtqq2pWTrzn0dKfj5kREZRpP82AwtGjGx9V1lYnKvF+Uc/a8zyWbSvjDE7V1d3YQAQJzs2cWO31hURWDekDXIA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@deepseek-ai/cordis": "^4.0.1", + "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6", + "@deepseek-ai/dsh-llm": "^0.1.0-rc.6", + "@deepseek-ai/dsh-scope": "^0.1.0-rc.6", + "@deepseek-ai/dsh-session": "^0.1.0-rc.6", + "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6", + "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.6" + } + }, + "node_modules/@deepseek-ai/dsh-llm": { + "version": "0.0.1-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-llm/-/dsh-llm-0.0.1-rc.1.tgz", + "integrity": "sha512-mRJj07IQNfRReGrhpMpL8AONiYKqfwzxvepg/ut9Wkj8oH2BuoftPS4cMnnXFFCUlYql04/sezwDLriYks/bmw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@deepseek-ai/schemastery": "^3.18.1-rc.1" + }, + "peerDependencies": { + "@deepseek-ai/cordis": "^4.0.1-rc.1", + "@deepseek-ai/dsh-attachment": "^0.0.1-rc.1", + "@deepseek-ai/dsh-brand": "^0.0.1-rc.1", + "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1", + "@deepseek-ai/dsh-timeout": "^0.0.1-rc.1" + } + }, + "node_modules/@deepseek-ai/dsh-session": { + "version": "0.0.1-rc.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-session/-/dsh-session-0.0.1-rc.1.tgz", + "integrity": "sha512-P3xE+0jDPMImdWhRi3e5ByhkA6LFTiabQwcK+Ofy763tWajDsadWpvONkc5Imd61thpVtXzH3uu25rZFvT5cfw==", + "dev": true, + "license": "BSD-3-Clause", + "peerDependencies": { + "@deepseek-ai/cordis": "^4.0.1-rc.1", + "@deepseek-ai/dsh-brand": "^0.0.1-rc.1", + "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1", + "@deepseek-ai/dsh-llm": "^0.0.1-rc.1", + "@deepseek-ai/dsh-scope": "^0.0.1-rc.1", + "@deepseek-ai/dsh-type-meta": "^0.0.1-rc.1" + } + }, + "node_modules/@deepseek-ai/schemastery": { + "version": "3.18.1", + "resolved": "https://registry.npmjs.org/@deepseek-ai/schemastery/-/schemastery-3.18.1.tgz", + "integrity": "sha512-Qn0FCSwCQnpnj6SB31I6i2sIKgKWnkbJM8O0EU91Gv2UsYVvtZTl6IA0sCwk2e2MZf5S8w5hpq9QkeVvK9qwxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@deepseek-ai/cosmokit": "^1.8.2", + "@standard-schema/spec": "^1.1.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/tsx": { + "version": "4.23.12", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", + "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json index c19732c..d2c9183 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dsh-telegram-channel", - "version": "0.2.1", + "version": "0.2.2", "description": "DeepSeek Harness Telegram mobile remote: bind live Web sessions (Codex-style), same trajectory, /sessions picker, unbind/clear — dsh-plugin", "keywords": [ "telegram", @@ -41,6 +41,7 @@ }, "files": [ "lib", + "scripts", "cordis.patch.yml", "README.md", "LICENSE" @@ -59,22 +60,25 @@ }, "peerDependencies": { "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/schemastery": "^3.18.1", "@deepseek-ai/dsh-agent": "^0.1.0-rc.6", "@deepseek-ai/dsh-llm": "^0.0.1-rc.1", - "@deepseek-ai/dsh-session": "^0.0.1-rc.1" + "@deepseek-ai/dsh-session": "^0.0.1-rc.1", + "@deepseek-ai/schemastery": "^3.18.1" }, "devDependencies": { - "@types/node": "^22.0.0", - "tsx": "^4.19.0", - "typescript": "^5.9.0", "@deepseek-ai/cordis": "^4.0.1", - "@deepseek-ai/schemastery": "^3.18.1", "@deepseek-ai/dsh-agent": "^0.1.0-rc.6", "@deepseek-ai/dsh-llm": "^0.0.1-rc.1", - "@deepseek-ai/dsh-session": "^0.0.1-rc.1" + "@deepseek-ai/dsh-session": "^0.0.1-rc.1", + "@deepseek-ai/schemastery": "^3.18.1", + "@types/node": "^22.0.0", + "tsx": "^4.19.0", + "typescript": "^5.9.0" }, "engines": { "node": ">=22" + }, + "dependencies": { + "undici": "^7.29.0" } } diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 0000000..455e95e --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,155 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + One-click install for dsh-telegram-channel (DeepSeek Harness Telegram remote). + +.DESCRIPTION + 1) Writes user env: DSH_TELEGRAM_TOKEN + DSH_TELEGRAM_ALLOWED_USER_IDS + 2) Ensures allowBuilds for the plugin (belt-and-suspenders) + 3) Runs: dsh plugin --profile add + Does NOT edit cordis.patch.yml (avoids duplicate loader entry). + +.EXAMPLE + # Interactive (prompts for token + user id) + irm https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.ps1 | iex + +.EXAMPLE + .\scripts\install.ps1 -Token '123:AA...' -UserId '7057906059' + +.EXAMPLE + # Install from this cloned repo instead of GitHub + .\scripts\install.ps1 -Token '...' -UserId '...' -Local +#> +[CmdletBinding()] +param( + [string] $Token = $env:DSH_TELEGRAM_TOKEN, + [string] $UserId = $env:DSH_TELEGRAM_ALLOWED_USER_IDS, + [string] $ProfileName = 'web', + [string] $Source = 'github:hi-wenw/dsh-telegram-channel', + [switch] $Local, + [switch] $NoPersist +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Write-Step([string] $Message) { + Write-Host "" + Write-Host "==> $Message" -ForegroundColor Cyan +} + +function Ensure-Command([string] $Name) { + if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { + throw "找不到命令 '$Name'。请先安装并确保 DeepSeek Harness CLI(dsh)在 PATH 中。" + } +} + +function Set-UserEnv([string] $Name, [string] $Value) { + Set-Item -Path "Env:$Name" -Value $Value + if (-not $NoPersist) { + [Environment]::SetEnvironmentVariable($Name, $Value, 'User') + } +} + +function Ensure-AllowBuilds([string] $ProfileDir) { + $path = Join-Path $ProfileDir 'pnpm-workspace.yaml' + $block = @' +allowBuilds: + dsh-telegram-channel: true +'@ + if (-not (Test-Path -LiteralPath $path)) { + $content = @" +packages: + - . + +nodeLinker: hoisted +autoInstallPeers: false + +$block +"@ + Set-Content -LiteralPath $path -Value $content -Encoding utf8 + Write-Host "已创建 $path (allowBuilds)" + return + } + + $raw = Get-Content -LiteralPath $path -Raw + if ($raw -match '(?m)^\s*dsh-telegram-channel\s*:\s*true\s*$') { + Write-Host "allowBuilds 已存在,跳过" + return + } + + if ($raw -match '(?m)^allowBuilds\s*:') { + if ($raw -notmatch '(?m)^allowBuilds\s*:[\s\S]*?dsh-telegram-channel\s*:') { + $raw = $raw -replace '(?m)^(allowBuilds\s*:)', "`$1`r`n dsh-telegram-channel: true" + $out = ($raw.TrimEnd() + "`r`n") + Set-Content -LiteralPath $path -Value $out -Encoding utf8 + Write-Host "已写入 allowBuilds.dsh-telegram-channel → $path" + } + return + } + + Add-Content -LiteralPath $path -Value "`r`n$block`r`n" -Encoding utf8 + Write-Host "已追加 allowBuilds → $path" +} + +Write-Host "dsh-telegram-channel installer" -ForegroundColor Green +Write-Host "Profile: $ProfileName" + +Ensure-Command 'dsh' + +if (-not $Token) { + $Token = Read-Host '粘贴 BotFather 的 Bot Token(不要发到公开群)' +} +$Token = ([string]$Token).Trim() +if (-not $Token) { throw '缺少 Bot Token。' } + +if (-not $UserId) { + $UserId = Read-Host '粘贴你的 Telegram 数字 User ID(@userinfobot)' +} +$UserId = ([string]$UserId).Trim() +if ($UserId -notmatch '^\d+(?:\s*,\s*\d+)*$') { + throw "User ID 必须是数字(多个用逗号分隔),收到: '$UserId'" +} +$UserId = ($UserId -replace '\s+', '') + +Write-Step '写入用户环境变量(当前进程 + 用户级永久)' +Set-UserEnv 'DSH_TELEGRAM_TOKEN' $Token +Set-UserEnv 'DSH_TELEGRAM_ALLOWED_USER_IDS' $UserId +Write-Host "DSH_TELEGRAM_TOKEN = (已设置,长度 $($Token.Length))" +Write-Host "DSH_TELEGRAM_ALLOWED_USER_IDS = $UserId" +if ($NoPersist) { + Write-Host '(-NoPersist:未写入用户级环境变量)' -ForegroundColor Yellow +} else { + Write-Host '已写入用户级环境变量。请关闭旧终端后再开新的。' -ForegroundColor Yellow +} + +$dshHome = if ($env:DSH_HOME) { $env:DSH_HOME } else { Join-Path $env:USERPROFILE '.dsh' } +$profileDir = Join-Path $dshHome "profiles\$ProfileName" +if (-not (Test-Path -LiteralPath $profileDir)) { + throw "找不到 profile 目录: $profileDir (请先成功跑过一次 dsh web)" +} + +Write-Step '确保 pnpm allowBuilds(防止 git 依赖 prepare 被拦)' +Ensure-AllowBuilds $profileDir + +if ($Local) { + $scriptRoot = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path } + $repoRoot = Resolve-Path (Join-Path $scriptRoot '..') + $Source = $repoRoot.Path +} + +Write-Step "安装插件: dsh plugin --profile $ProfileName add $Source" +& dsh plugin --profile $ProfileName add $Source +if ($LASTEXITCODE -ne 0) { + throw "dsh plugin add 失败(exit=$LASTEXITCODE)。可尝试: dsh plugin --profile $ProfileName remove dsh-telegram-channel 后再跑本脚本。" +} + +Write-Host "" +Write-Host '安装完成。接下来:' -ForegroundColor Green +Write-Host ' 1. 关掉所有旧终端,新开 PowerShell' +Write-Host ' 2. 确认代理(若需要访问 Telegram)已开启' +Write-Host ' 3. 运行: dsh web' +Write-Host ' 4. 浏览器打开一个对话' +Write-Host ' 5. 手机对 Bot 发 /start → /sessions → 点选附着' +Write-Host "" +Write-Host '命令菜单应出现: /start /sessions /status /unbind /help' diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100644 index 0000000..cd826dc --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# One-click-ish install for dsh-telegram-channel (macOS / Linux). +# Usage: +# export DSH_TELEGRAM_TOKEN='123:AA...' +# export DSH_TELEGRAM_ALLOWED_USER_IDS='7057906059' +# curl -fsSL https://raw.githubusercontent.com/hi-wenw/dsh-telegram-channel/master/scripts/install.sh | bash +# Or: +# ./scripts/install.sh --token '...' --user-id '...' +set -euo pipefail + +PROFILE_NAME="${DSH_PROFILE:-web}" +SOURCE="${DSH_TELEGRAM_SOURCE:-github:hi-wenw/dsh-telegram-channel}" +TOKEN="${DSH_TELEGRAM_TOKEN:-}" +USER_ID="${DSH_TELEGRAM_ALLOWED_USER_IDS:-}" +LOCAL=0 +NO_PERSIST=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --token) TOKEN="${2:-}"; shift 2 ;; + --user-id) USER_ID="${2:-}"; shift 2 ;; + --profile) PROFILE_NAME="${2:-}"; shift 2 ;; + --source) SOURCE="${2:-}"; shift 2 ;; + --local) LOCAL=1; shift ;; + --no-persist) NO_PERSIST=1; shift ;; + -h|--help) + sed -n '2,12p' "$0" + exit 0 + ;; + *) echo "Unknown arg: $1" >&2; exit 1 ;; + esac +done + +if ! command -v dsh >/dev/null 2>&1; then + echo "dsh not found in PATH" >&2 + exit 1 +fi + +if [[ -z "$TOKEN" ]]; then + read -r -p "Bot Token: " TOKEN +fi +if [[ -z "$USER_ID" ]]; then + read -r -p "Telegram numeric User ID: " USER_ID +fi +USER_ID="$(echo "$USER_ID" | tr -d '[:space:]')" +if ! [[ "$USER_ID" =~ ^[0-9]+(,[0-9]+)*$ ]]; then + echo "User ID must be numeric (comma-separated ok)" >&2 + exit 1 +fi + +export DSH_TELEGRAM_TOKEN="$TOKEN" +export DSH_TELEGRAM_ALLOWED_USER_IDS="$USER_ID" + +persist_env() { + local name="$1" value="$2" + local line="export ${name}=$(printf %q "$value")" + for f in "$HOME/.bashrc" "$HOME/.zshrc"; do + [[ -f "$f" ]] || continue + if grep -q "^export ${name}=" "$f" 2>/dev/null; then + # portable-ish replace + tmp="$(mktemp)" + grep -v "^export ${name}=" "$f" >"$tmp" || true + echo "$line" >>"$tmp" + mv "$tmp" "$f" + else + echo "$line" >>"$f" + fi + done +} + +if [[ "$NO_PERSIST" -eq 0 ]]; then + persist_env DSH_TELEGRAM_TOKEN "$TOKEN" + persist_env DSH_TELEGRAM_ALLOWED_USER_IDS "$USER_ID" + echo "Wrote exports to ~/.bashrc and/or ~/.zshrc (if present)." +fi + +DSH_HOME="${DSH_HOME:-$HOME/.dsh}" +PROFILE_DIR="$DSH_HOME/profiles/$PROFILE_NAME" +if [[ ! -d "$PROFILE_DIR" ]]; then + echo "Profile dir missing: $PROFILE_DIR (run dsh web once first)" >&2 + exit 1 +fi + +WS="$PROFILE_DIR/pnpm-workspace.yaml" +if [[ ! -f "$WS" ]]; then + cat >"$WS" <<'EOF' +packages: + - . + +nodeLinker: hoisted +autoInstallPeers: false + +allowBuilds: + dsh-telegram-channel: true +EOF +elif ! grep -q 'dsh-telegram-channel:' "$WS"; then + if grep -q '^allowBuilds:' "$WS"; then + awk ' + BEGIN{done=0} + /^allowBuilds:/{print; print " dsh-telegram-channel: true"; done=1; next} + {print} + END{if(!done){print ""; print "allowBuilds:"; print " dsh-telegram-channel: true"}} + ' "$WS" >"$WS.tmp" && mv "$WS.tmp" "$WS" + else + printf '\nallowBuilds:\n dsh-telegram-channel: true\n' >>"$WS" + fi +fi + +if [[ "$LOCAL" -eq 1 ]]; then + SOURCE="$(cd "$(dirname "$0")/.." && pwd)" +fi + +echo "==> dsh plugin --profile $PROFILE_NAME add $SOURCE" +dsh plugin --profile "$PROFILE_NAME" add "$SOURCE" + +cat < { + this.ctx.logger.info('dsh-telegram-channel: bot commands registered') + }).catch((err) => { + this.ctx.logger.warn(`dsh-telegram-channel: setMyCommands failed: ${this.redact(err)}`) + }) if (!this.polling) { this.polling = true this.pollAbort = new AbortController() + this.ctx.logger.info('dsh-telegram-channel: long-polling started') this.pollPromise = this.pollLoop() } } diff --git a/src/client.ts b/src/client.ts index a4a9e1a..ff37acc 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,3 +1,5 @@ +import { EnvHttpProxyAgent, fetch as undiciFetch } from 'undici' + export interface TelegramUser { id: number is_bot?: boolean @@ -42,6 +44,11 @@ export interface InlineKeyboardMarkup { inline_keyboard: InlineKeyboardButton[][] } +export interface TelegramBotCommand { + command: string + description: string +} + export interface TelegramClientOptions { fetch?: typeof fetch baseUrl?: string @@ -59,6 +66,32 @@ export interface TelegramClientLike { ): Promise sendChatAction(chatId: number, action: string): Promise answerCallbackQuery(callbackQueryId: string, text?: string): Promise + setMyCommands(commands: TelegramBotCommand[]): Promise +} + +function resolveProxyUrl(): string | undefined { + return ( + process.env.HTTPS_PROXY + || process.env.HTTP_PROXY + || process.env.https_proxy + || process.env.http_proxy + || undefined + ) +} + +/** + * Node's global `fetch` ignores HTTP(S)_PROXY unless `NODE_USE_ENV_PROXY=1`. + * Undici's EnvHttpProxyAgent honors the env vars (and NO_PROXY). + */ +function createDefaultFetch(): typeof fetch { + if (!resolveProxyUrl()) return globalThis.fetch + const agent = new EnvHttpProxyAgent() + const proxied = ((input: RequestInfo | URL, init?: RequestInit) => + undiciFetch(input as string | URL, { + ...(init as Record), + dispatcher: agent, + })) as unknown as typeof fetch + return proxied } export class TelegramClient implements TelegramClientLike { @@ -72,7 +105,7 @@ export class TelegramClient implements TelegramClientLike { throw new Error('bot token is required') } this.token = token - this.fetchImpl = options.fetch ?? fetch + this.fetchImpl = options.fetch ?? createDefaultFetch() this.baseUrl = options.baseUrl ?? 'https://api.telegram.org' this.pollingTimeoutSec = options.pollingTimeoutSec ?? 30 } @@ -143,4 +176,8 @@ export class TelegramClient implements TelegramClientLike { if (text !== undefined) body.text = text return this.call('answerCallbackQuery', body) } + + async setMyCommands(commands: TelegramBotCommand[]): Promise { + return this.call('setMyCommands', { commands }) + } } diff --git a/src/index.ts b/src/index.ts index 3da850e..f6ae706 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,19 @@ export const Config: Schema = Schema.object({ pollingTimeoutSec: Schema.number().default(30), }) +function resolveAllowedUserIds(config: TelegramChannelConfig): number[] { + if (config.allowedUserIds && config.allowedUserIds.length > 0) { + return config.allowedUserIds + } + const raw = process.env.DSH_TELEGRAM_ALLOWED_USER_IDS ?? '' + return raw + .split(/[,;\s]+/) + .map((part) => part.trim()) + .filter(Boolean) + .map((part) => Number(part)) + .filter((n) => Number.isFinite(n) && n > 0) +} + export function apply(ctx: Context, config: TelegramChannelConfig): void { const token = (config.token && config.token.length > 0) ? config.token @@ -33,7 +46,7 @@ export function apply(ctx: Context, config: TelegramChannelConfig): void { } const bridge = new TelegramBridge(ctx, { token, - allowedUserIds: config.allowedUserIds ?? [], + allowedUserIds: resolveAllowedUserIds(config), allowAllUsers: config.allowAllUsers ?? false, maxMessageLength: config.maxMessageLength ?? 4096, pollingTimeoutSec: config.pollingTimeoutSec ?? 30, diff --git a/tests/apply.test.ts b/tests/apply.test.ts index 67d7f92..3f26bc6 100644 --- a/tests/apply.test.ts +++ b/tests/apply.test.ts @@ -9,7 +9,7 @@ test('apply without token does not throw and does not start effect', () => { effects.push(name) return () => {} }, - logger: { error() {}, warn() {} }, + logger: { info() {}, error() {}, warn() {} }, agents: {}, } as any const prev = process.env.DSH_TELEGRAM_TOKEN diff --git a/tests/bridge.test.ts b/tests/bridge.test.ts index 60892da..518efda 100644 --- a/tests/bridge.test.ts +++ b/tests/bridge.test.ts @@ -26,6 +26,7 @@ function fakeClient( }, sendChatAction: async () => true, answerCallbackQuery: async () => true, + setMyCommands: async () => true, ...overrides, } } @@ -56,7 +57,7 @@ function makeAgent(id: string, followups: UserMessage[]) { test('unauthorized user gets denied', async () => { const sent: SentMessage[] = [] const ctx = { - logger: { warn() {}, error() {} }, + logger: { info() {}, warn() {}, error() {} }, agents: { list: () => [], roots: () => [], get: () => undefined }, on() { return () => {} }, } @@ -75,7 +76,7 @@ test('plain text without bind prompts NEED_BIND and does not create', async () = const sent: SentMessage[] = [] let createCalled = false const ctx = { - logger: { warn() {}, error() {} }, + logger: { info() {}, warn() {}, error() {} }, agents: { list: () => [], roots: () => [], @@ -99,7 +100,7 @@ test('plain text without bind prompts NEED_BIND and does not create', async () = test('/sessions with no live agents shows NO_LIVE', async () => { const sent: SentMessage[] = [] const ctx = { - logger: { warn() {}, error() {} }, + logger: { info() {}, warn() {}, error() {} }, agents: { list: () => [], roots: () => [], get: () => undefined }, on() { return () => {} }, } @@ -119,7 +120,7 @@ test('/sessions lists live agents with bind callbacks', async () => { const followups: UserMessage[] = [] const agent = makeAgent('live-aaa', followups) const ctx = { - logger: { warn() {}, error() {} }, + logger: { info() {}, warn() {}, error() {} }, agents: { list: () => [agent], roots: () => [agent], @@ -145,7 +146,7 @@ test('callback bind then plain text followups live agent; mirror assistant to ch const agent = makeAgent('live-bbb', followups) let sessionListener: ((session: { id: ReturnType }, event: unknown) => void) | undefined const ctx = { - logger: { warn() {}, error() {} }, + logger: { info() {}, warn() {}, error() {} }, agents: { list: () => [agent], roots: () => [agent], @@ -202,7 +203,7 @@ test('/unbind clears binding without needing create/dispose', async () => { const followups: UserMessage[] = [] const agent = makeAgent('live-ccc', followups) const ctx = { - logger: { warn() {}, error() {} }, + logger: { info() {}, warn() {}, error() {} }, agents: { list: () => [agent], roots: () => [agent],