dsh-remote-bridge 2.3.7 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +6 -6
- package/README.md +6 -6
- package/docs/api/asyncapi.yaml +117 -5
- package/docs/api/openapi.yaml +36 -3
- package/kotlin-sdk/build.gradle.kts +1 -1
- package/lib/client.cjs +166 -1
- package/lib/index.d.ts +183 -1
- package/lib/index.js +912 -19
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -1026,6 +1026,16 @@ interface DshApiProxy {
|
|
|
1026
1026
|
}): Promise<RpcResponse<{
|
|
1027
1027
|
items: UserQuestionView[];
|
|
1028
1028
|
}>>;
|
|
1029
|
+
/**
|
|
1030
|
+
* 全部会话里**挂起中**的提问(连接重放用)。**可选**:老实现不提供时重放跳过提问,不报错。
|
|
1031
|
+
* 返回项带 `sessionId`,因为重放要按每条连接的授权逐条过滤。
|
|
1032
|
+
*/
|
|
1033
|
+
pendingAll?: () => Array<{
|
|
1034
|
+
rpcId: string;
|
|
1035
|
+
sessionId: string;
|
|
1036
|
+
callId: string;
|
|
1037
|
+
questions: unknown[];
|
|
1038
|
+
}>;
|
|
1029
1039
|
/** 提交答案;失败原因见 [UserQuestionAnswerOutcome]。 */
|
|
1030
1040
|
answer(request: {
|
|
1031
1041
|
rpcId: string;
|
|
@@ -1261,7 +1271,23 @@ declare class DshChatAdapter {
|
|
|
1261
1271
|
private readonly sessionRootLookups;
|
|
1262
1272
|
private readonly pendingApprovals;
|
|
1263
1273
|
constructor(api: DshApiProxy, database: WorkspaceDatabase, commandServices?: DshCommandServices | undefined);
|
|
1264
|
-
|
|
1274
|
+
/**
|
|
1275
|
+
* 列出会话。
|
|
1276
|
+
*
|
|
1277
|
+
* `options.archived === true` 时返回**已归档**的那批;默认返回未归档的。
|
|
1278
|
+
*
|
|
1279
|
+
* 为什么需要这个参数:内核把「归档」实现为**从所有分组界面隐藏**,本桥默认照做 ——
|
|
1280
|
+
* 于是用户在手机上一归档,那条会话就**再也看不到**(连找都找不到),
|
|
1281
|
+
* 「归档」变成了单向门。参数化之后客户端至少能提供一个「已归档」入口。
|
|
1282
|
+
*
|
|
1283
|
+
* **恢复(取消归档)不在本桥能力内**:适配层只声明了内核的 `archiveSession`,
|
|
1284
|
+
* 内核是否提供 unarchive 从本仓**不可证** —— `node_modules` 下没有 `@deepseek-ai/*`
|
|
1285
|
+
* (那些包随 DSH 主包安装)。按 `PITFALLS.md` §空结果的来源要验证,
|
|
1286
|
+
* 这里只能写「不可证」,不能写「没有」。
|
|
1287
|
+
*/
|
|
1288
|
+
listSessions(principal: Principal, options?: {
|
|
1289
|
+
archived?: boolean;
|
|
1290
|
+
}): Promise<ChatSessionView[]>;
|
|
1265
1291
|
listWorkspaces(principal: Principal): Promise<ChatWorkspaceView[]>;
|
|
1266
1292
|
createWorkspace(principal: Principal, rootId: string, relativePath: string): Promise<ChatWorkspaceView>;
|
|
1267
1293
|
renameWorkspace(principal: Principal, workspaceId: string, title: string): Promise<ChatWorkspaceView>;
|
|
@@ -1289,6 +1315,9 @@ declare class DshChatAdapter {
|
|
|
1289
1315
|
events: unknown[];
|
|
1290
1316
|
hasMore: boolean;
|
|
1291
1317
|
projections?: unknown;
|
|
1318
|
+
bytes?: number;
|
|
1319
|
+
truncated?: boolean;
|
|
1320
|
+
historyFormatVersion?: number;
|
|
1292
1321
|
}>;
|
|
1293
1322
|
prompt(principal: Principal, sessionId: string, parts: PromptContentPart[], mode: 'queue' | 'steer', clientTimeZone?: string, locale?: HandoffLocale): Promise<{
|
|
1294
1323
|
accepted: true;
|
|
@@ -1340,6 +1369,40 @@ declare class DshChatAdapter {
|
|
|
1340
1369
|
forkSession(principal: Principal, sessionId: string, atSeq?: number): Promise<string>;
|
|
1341
1370
|
archiveSession(principal: Principal, sessionId: string): Promise<string[]>;
|
|
1342
1371
|
listPendingApprovals(principal: Principal, sessionId: string): Promise<PendingApprovalView[]>;
|
|
1372
|
+
/**
|
|
1373
|
+
* 连接建立时重放的待处理审批事件。
|
|
1374
|
+
*
|
|
1375
|
+
* ## 为什么需要
|
|
1376
|
+
*
|
|
1377
|
+
* 「连接后要不要主动查一次 REST」是**客户端自己的选择**:我方 App 会
|
|
1378
|
+
* (`HarnessViewModel` 的 `connection` 回调里刷新历史/审批/提问/命令),
|
|
1379
|
+
* 但协议不该把「审批不丢」寄托在这一点上 —— 客户端若只在收到事件时才刷新,
|
|
1380
|
+
* 重连窗口里产生的审批就会静默消失:内核阻塞着等决定,手机上却一片安静。
|
|
1381
|
+
* 重放把这件事变成**服务端保证**;对已经在连接后刷新的客户端,它只是一次幂等冗余。
|
|
1382
|
+
*
|
|
1383
|
+
* 对标第三方移动网关:它在**连接建立**与**订阅**两个触发点重发仍待处理的提问/审批,
|
|
1384
|
+
* 客户端按 id 去重。我们这里给事件加 `data.replayed = true`,
|
|
1385
|
+
* 客户端可以据此跳过「新卡片」动画,也可以完全忽略(刷新本身是幂等的)。
|
|
1386
|
+
*
|
|
1387
|
+
* ## 为什么先只做审批
|
|
1388
|
+
*
|
|
1389
|
+
* 审批是**阻塞内核**的那一类:漏掉它,Agent 会一直等;提问不阻塞(Agent 已经继续跑)。
|
|
1390
|
+
* 提问侧的待处理数据在 `dsh-0.2-bridge` 的 `QuestionBridge.pending` 里,
|
|
1391
|
+
* 要重放得先通过 `apiProxy` 暴露一个「全部会话」入口 —— 那是独立的一小步,
|
|
1392
|
+
* 不塞进本次改动里凑数。
|
|
1393
|
+
*/
|
|
1394
|
+
replayPendingApprovals(principal: Principal): Promise<WorkspaceEvent[]>;
|
|
1395
|
+
/**
|
|
1396
|
+
* 连接建立时重放的**挂起提问**事件。
|
|
1397
|
+
*
|
|
1398
|
+
* 与 [replayPendingApprovals] 同一动机:把「新连接一定能拿到仍待处理的东西」
|
|
1399
|
+
* 变成**服务端保证**,而不是每个客户端各自的义务(论证与纠错记录见该方法的 KDoc)。
|
|
1400
|
+
*
|
|
1401
|
+
* 两者一个区别:提问**不阻塞内核**(Agent 已经继续跑),漏掉它的代价小一些;
|
|
1402
|
+
* 但「卡片不出现、用户以为没问题」同样是坏体验,所以对称补上。
|
|
1403
|
+
* 数据来自适配层的 `userQuestions.pendingAll()` —— 老实现不提供时跳过(可选方法),不报错。
|
|
1404
|
+
*/
|
|
1405
|
+
replayPendingQuestions(principal: Principal): Promise<WorkspaceEvent[]>;
|
|
1343
1406
|
decideApproval(principal: Principal, sessionId: string, approvalId: string, outcome: 'allowed-once' | 'rejected'): Promise<PendingApprovalView>;
|
|
1344
1407
|
/**
|
|
1345
1408
|
* 列出「agent 正在等、且还能回答」的用户问题。
|
|
@@ -1414,6 +1477,101 @@ declare class WorkspaceEventBus {
|
|
|
1414
1477
|
subscribe(listener: (event: WorkspaceEvent) => void): () => void;
|
|
1415
1478
|
}
|
|
1416
1479
|
//#endregion
|
|
1480
|
+
//#region src/host/cloudflared.d.ts
|
|
1481
|
+
/**
|
|
1482
|
+
* Cloudflare 隧道(cloudflared)管理。
|
|
1483
|
+
*
|
|
1484
|
+
* ## 它解决什么
|
|
1485
|
+
*
|
|
1486
|
+
* 用户在外网想连回家里的电脑。bridge 默认只监听本机/局域网,公网不可达。
|
|
1487
|
+
* Cloudflare Tunnel 让电脑**主动连出**到 Cloudflare 边缘,换取一个 `wss://` 地址,
|
|
1488
|
+
* 两个 Channel:
|
|
1489
|
+
*
|
|
1490
|
+
* - **命名隧道(Named Channel)**:需要 Cloudflare 账户 + 域名(DNS)→ 固定地址。
|
|
1491
|
+
* - **快速隧道(Quick Tunnel)**:一条命令即起、无需账户域名 → 临时地址。
|
|
1492
|
+
*
|
|
1493
|
+
* 本模块直接实现 **Quick Tunnel 的自动拉起**(「无账户、一键"国内优先 + 自动创建"」的最小
|
|
1494
|
+
* 闭环);命名隧道因需要交互式登录 + 域名 DNS,面板只负责指示用户手动配置后,
|
|
1495
|
+
* 用 `setNamed` 把地址喂进来,走同一套 `DSH1:` 生成。
|
|
1496
|
+
*
|
|
1497
|
+
* ## 为什么用子进程
|
|
1498
|
+
*
|
|
1499
|
+
* cloudflared 是 Cloudflare 官方 Go 二进制,本仓不自带(体积大、无原生 TS 等价物),
|
|
1500
|
+
* 必须下载到本机再 `spawn`。这也是「国内优先下载核心功能」的落点之一。
|
|
1501
|
+
*
|
|
1502
|
+
* ## 下载与版本
|
|
1503
|
+
*
|
|
1504
|
+
* cloudflared 官方二进制只在 GitHub Releases(`cloudflared-linux-amd64` 等)。
|
|
1505
|
+
* 国内裸连 GitHub 时好时坏。设计上:
|
|
1506
|
+
* - 架构按 `process.platform/arch` 判定(UOS20 x86_64 → linux-amd64),**不**写死;
|
|
1507
|
+
* - 下载源用环境变量 `CLOUDFLARED_MIRROR` 控制:裸二进制镜像基址(拼架构后缀)
|
|
1508
|
+
* 或 `deb+` 前缀的 .deb 完整地址(下载后 `dpkg-deb -x` 提取 `usr/bin/cloudflared`);
|
|
1509
|
+
* - 已存在的可执行文件直接复用,不重复下载。
|
|
1510
|
+
*
|
|
1511
|
+
* **真实择源**:`deb+https://pkg.cloudflare.com/cloudflared/pool/main/c/cloudflared/cloudflared_2026.10.1_amd64.deb`
|
|
1512
|
+
* 已在真机(GitHub 直连被墙的环境)验证可下载解包并成功拉起 Quick Tunnel——官方 `pkg.cloudflare.com`
|
|
1513
|
+
* 仓库比 GitHub 直连更稳。见 PITFALLS §国内下载 cloudflared 必须择源。
|
|
1514
|
+
*
|
|
1515
|
+
* ## 进程生命周期
|
|
1516
|
+
*
|
|
1517
|
+
* - [startQuick] spawn 一次;[stop] 结束进程。
|
|
1518
|
+
* - 进程非预期退出会通过 [onStatus] 回调把状态推进到 `stopped` 并留错误摘要。
|
|
1519
|
+
*
|
|
1520
|
+
* ## 地址归一
|
|
1521
|
+
*
|
|
1522
|
+
* Quick Tunnel 从进程输出抓到的原生地址是 `https://<random>.trycloudflare.com`。
|
|
1523
|
+
* 放进 `DSH1:` 配置文本时客户端只认 `http(s)://` 前缀(App 会自动拼 `/events` 并把 https
|
|
1524
|
+
* 升级成 wss),所以模块对外统一暴露 `https://` 原文 —— 见 PITFALLS §wss 端点必须归一成 https。
|
|
1525
|
+
*/
|
|
1526
|
+
/** 隧道当前状态(面板据此渲染按钮/提示)。 */
|
|
1527
|
+
type TunnelChannel = 'quick' | 'named';
|
|
1528
|
+
interface TunnelPublicState {
|
|
1529
|
+
channel: TunnelChannel | null;
|
|
1530
|
+
/** 对外地址,已统一为 `https://` 前缀原文,供配置文本直接使用。 */
|
|
1531
|
+
url: string | null;
|
|
1532
|
+
running: boolean;
|
|
1533
|
+
error?: string;
|
|
1534
|
+
}
|
|
1535
|
+
/** 「创建云隧道」进行中任务的阶段状态,供前端轮询显示进度。`percent=null` 表示该阶段无确定百分比(流动条)。 */
|
|
1536
|
+
interface TunnelActivity {
|
|
1537
|
+
/** 阶段标识:locating / downloading / unpacking / starting;`idle` 表示当前无进行中任务。 */
|
|
1538
|
+
stage: 'idle' | 'locating' | 'downloading' | 'unpacking' | 'starting';
|
|
1539
|
+
/** 人类可读的阶段文案(如「已找到本机二进制,直接复用」)。 */
|
|
1540
|
+
label: string;
|
|
1541
|
+
/** 0-100 的进度百分比;null = 该阶段无法量化(前端显示流动条)。 */
|
|
1542
|
+
percent: number | null;
|
|
1543
|
+
}
|
|
1544
|
+
/** 一次 cloudflared 子进程生命周期 + 对外状态机。 */
|
|
1545
|
+
declare class CloudflaredTunnelManager {
|
|
1546
|
+
private process;
|
|
1547
|
+
private url;
|
|
1548
|
+
private channel;
|
|
1549
|
+
private errorText;
|
|
1550
|
+
private listeners;
|
|
1551
|
+
private activity;
|
|
1552
|
+
/** 当前进行中任务的阶段进度(无任务时 stage 为 idle)。 */
|
|
1553
|
+
get activityState(): TunnelActivity;
|
|
1554
|
+
/** 上报一条阶段进度(前端轮询 /cloudflare/activity 可见)。 */
|
|
1555
|
+
reportActivity(a: TunnelActivity): void;
|
|
1556
|
+
/** 任务结束(成功或失败)时清空进行中进度。 */
|
|
1557
|
+
clearActivity(): void;
|
|
1558
|
+
get publicState(): TunnelPublicState;
|
|
1559
|
+
/** 订阅状态变化;返回取消函数。 */
|
|
1560
|
+
onStatus(cb: () => void): () => void;
|
|
1561
|
+
private emit;
|
|
1562
|
+
private setError;
|
|
1563
|
+
/**
|
|
1564
|
+
* 启动一次 Quick Tunnel。
|
|
1565
|
+
* @param exe cloudflared 可执行文件路径(来自 [ensureCloudflared])
|
|
1566
|
+
* @param port bridge 监听端口,cloudflared 把外网请求转发到 localhost:port
|
|
1567
|
+
*/
|
|
1568
|
+
startQuick(exe: string, port: number): Promise<TunnelPublicState>;
|
|
1569
|
+
/** 停止当前隧道。 */
|
|
1570
|
+
stop(): TunnelPublicState;
|
|
1571
|
+
/** 注入已抓到的地址 + 通道(命名隧道用)。 */
|
|
1572
|
+
setNamed(url: string): TunnelPublicState;
|
|
1573
|
+
}
|
|
1574
|
+
//#endregion
|
|
1417
1575
|
//#region src/host/file-service.d.ts
|
|
1418
1576
|
interface DirectoryPage {
|
|
1419
1577
|
path: string;
|
|
@@ -1495,6 +1653,14 @@ interface RouterOptions {
|
|
|
1495
1653
|
chat: DshChatAdapter;
|
|
1496
1654
|
settings: DshSettingsAdapter;
|
|
1497
1655
|
remote: RemoteControl;
|
|
1656
|
+
/**
|
|
1657
|
+
* Cloudflare 隧道管理器。可选:不注入则该端点不可用(返回 404),测试不必每条都补。
|
|
1658
|
+
*/
|
|
1659
|
+
tunnels?: CloudflaredTunnelManager;
|
|
1660
|
+
/**
|
|
1661
|
+
* cloudflared 可执行文件落盘目录(默认可由实现方推导;这里显式传入保证与插件安装目录一致)。
|
|
1662
|
+
*/
|
|
1663
|
+
cloudflaredBinDir?: string;
|
|
1498
1664
|
maxUploadBytes: number;
|
|
1499
1665
|
/**
|
|
1500
1666
|
* 解析不出会话工作目录时的图片兜底目录(设置面板里只读展示)。
|
|
@@ -1567,6 +1733,22 @@ declare class RemoteApiServer implements RemoteControl {
|
|
|
1567
1733
|
private createBoundServer;
|
|
1568
1734
|
private closeCurrent;
|
|
1569
1735
|
private handleUpgrade;
|
|
1736
|
+
/**
|
|
1737
|
+
* 把待处理的**审批与提问**补推给刚建立的这条连接。
|
|
1738
|
+
*
|
|
1739
|
+
* 为什么要有这一步:「连接后要不要主动查一次 REST」是**每个客户端自己的选择**。
|
|
1740
|
+
* 我方 App 会(`HarnessViewModel` 的 `connection` 回调里刷新历史/审批/提问/命令),
|
|
1741
|
+
* 但协议不该把「审批不丢」寄托在这一点上:客户端若只在收到事件时才刷新,
|
|
1742
|
+
* 重连窗口里产生的审批就会静默消失(内核阻塞着等决定、手机上却一片安静)。
|
|
1743
|
+
* 重放把「新连接一定能拿到仍待处理的审批」变成**服务端保证**,而不是客户端的义务。
|
|
1744
|
+
*
|
|
1745
|
+
* 三个刻意的取舍:
|
|
1746
|
+
* - **不 await**:重放要按会话做授权过滤(异步),握手不能为它排队;
|
|
1747
|
+
* - **只发这一条连接**:不走 `broadcast`,避免每次有人重连就骚扰所有在线设备;
|
|
1748
|
+
* - **失败静默**:重放是补救措施,它出错不该影响这条连接正常收事件。
|
|
1749
|
+
* 客户端侧「连接后/亮屏后主动刷新」仍是兜底,但不再是唯一依赖。
|
|
1750
|
+
*/
|
|
1751
|
+
private replayPendingInteractions;
|
|
1570
1752
|
private broadcast;
|
|
1571
1753
|
private eventAllowed;
|
|
1572
1754
|
private trimOperations;
|