dsh-remote-bridge 2.7.1 → 2.8.0

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/lib/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { IncomingMessage, ServerResponse } from "node:http";
2
+ import { WebSocket } from "ws";
2
3
  //#region src/shared/contracts.d.ts
3
4
  declare const DEVICE_SCOPES: readonly ["chat.read", "chat.write", "files.read", "files.write", "files.delete", "settings.read", "settings.write"];
4
5
  type DeviceScope = (typeof DEVICE_SCOPES)[number];
@@ -1411,6 +1412,22 @@ interface DshApiProxy {
1411
1412
  }): Promise<RpcResponse<{
1412
1413
  selected: ModelSelectionView;
1413
1414
  }>>;
1415
+ /**
1416
+ * 读会话投影基线(内核 `sessionController.projections`)。
1417
+ *
1418
+ * 供 agent-team / subagents 等只读投影的 WS 帧使用:投影里的 `agentTeam` /
1419
+ * `subagentCatalog` 键给出团队成员、任务与子会话目录。会话不存在时内核返回 null。
1420
+ */
1421
+ projections(request: {
1422
+ rpcId: string;
1423
+ payload: {
1424
+ sessionId: string;
1425
+ };
1426
+ }): Promise<RpcResponse<{
1427
+ asOfSeq?: number;
1428
+ kind?: string;
1429
+ values: Record<string, unknown>;
1430
+ } | null>>;
1414
1431
  };
1415
1432
  workspace: {
1416
1433
  list(request: {
@@ -1525,6 +1542,24 @@ interface DshApiProxy {
1525
1542
  answer: UserQuestionAnswer;
1526
1543
  };
1527
1544
  }): Promise<RpcResponse<UserQuestionAnswerOutcome>>;
1545
+ /**
1546
+ * 取消本桥接管的前台提问(question-cancel)。内核 userQuestions 服务无 cancel,
1547
+ * 因此只能取消正阻塞 agent 的前台挂起项;带 timeout 的 continued 提问取消不了。
1548
+ */
1549
+ cancel(request: {
1550
+ rpcId: string;
1551
+ payload: {
1552
+ sessionId: string;
1553
+ callId?: string;
1554
+ rpcId?: string;
1555
+ };
1556
+ }): Promise<RpcResponse<{
1557
+ accepted: true;
1558
+ rpcId: string;
1559
+ } | {
1560
+ accepted: false;
1561
+ reason: 'not-pending';
1562
+ }>>;
1528
1563
  };
1529
1564
  events: {
1530
1565
  mux(request: {
@@ -1994,8 +2029,30 @@ declare class DshChatAdapter {
1994
2029
  * 映成 404/409,其余一律 400 —— 表达不了这几种区别。
1995
2030
  */
1996
2031
  answerUserQuestion(principal: Principal, sessionId: string, callId: string, answer: UserQuestionAnswer): Promise<void>;
2032
+ /**
2033
+ * 取消一条正挂在前台的用户提问(WS `question-cancel`)。
2034
+ *
2035
+ * 只能取消本桥接管、此刻正阻塞 agent 的前台提问;带 timeout 的 continued 提问
2036
+ * 内核没有取消通道。取消成功后事件总线会推 userQuestions/dismissed(见桥的竞速收尾),
2037
+ * 移动端据此关闭卡片。
2038
+ */
2039
+ cancelUserQuestion(principal: Principal, sessionId: string, ref: {
2040
+ callId?: string;
2041
+ rpcId?: string;
2042
+ }): Promise<void>;
1997
2043
  models(principal: Principal, sessionId: string): Promise<SessionModelsView>;
1998
2044
  selectModel(principal: Principal, sessionId: string, selection: ModelSelectionView): Promise<ModelSelectionView>;
2045
+ /**
2046
+ * 读会话投影基线(agent-team / subagents 帧用)。
2047
+ *
2048
+ * 会话不存在(内核返回 null)映射成 404 SESSION_NOT_FOUND;取不到投影不打 500,
2049
+ * 由调用方按 PROTOCOL 回 projectionState:'unavailable'。这里只做鉴权 + 透传。
2050
+ */
2051
+ sessionProjections(principal: Principal, sessionId: string): Promise<{
2052
+ asOfSeq?: number;
2053
+ kind?: string;
2054
+ values: Record<string, unknown>;
2055
+ }>;
1999
2056
  listCommands(principal: Principal, sessionId: string): Promise<CommandDescriptorView[]>;
2000
2057
  executeCommand(principal: Principal, sessionId: string, line: string): Promise<CommandExecutionView>;
2001
2058
  isSessionAuthorized(principal: Principal, sessionId: string): Promise<boolean>;
@@ -2110,6 +2167,32 @@ declare class AuthService {
2110
2167
  requireRoot(principal: Principal, rootId: string): void;
2111
2168
  }
2112
2169
  //#endregion
2170
+ //#region src/host/capabilities.d.ts
2171
+ /**
2172
+ * 运行时**不可用**的能力位:服务端此刻做不了这些事,就不该声明。
2173
+ *
2174
+ * ## 为什么必须过滤(这是本节最重要的一条)
2175
+ *
2176
+ * 能力位的全部意义是让客户端把「服务端不支持」与「本来就没有」分开
2177
+ * (见文件头的说明)。而 `jobController` 是**可选宿主服务**:0.2.0-rc.2 上没有它,
2178
+ * 三个 `/jobs` 端点在那套内核上根本不会被挂载(`index.ts` 直接不下发,路由返回 404)。
2179
+ * 若 `connection.ready` 仍然宣告 `job-list-v1`,客户端按位探测后就会去打一个必然
2180
+ * 404 的端点 —— 这恰好是这份清单本身想消灭的那种「静默少一个功能」。
2181
+ *
2182
+ * ## 为什么按「能力位 → 依赖的服务」映射,而不是在调用点手写过滤
2183
+ *
2184
+ * 声明与端点必须**同源**。把「哪个位依赖哪个可选服务」写在一处,将来再加一个
2185
+ * 可选服务只需要在这里补一行;散在调用点就会漂。
2186
+ */
2187
+ /** 运行时能力开关。缺省(不传键)一律当作可用,见 {@link availableCapabilities}。 */
2188
+ interface CapabilityFlags {
2189
+ jobs?: boolean;
2190
+ /** 宿主 cordis 服务 `ctx.schedule` 在不在(`dsh-schedule` 插件未装时为 false)。 */
2191
+ schedule?: boolean;
2192
+ /** 内核 typert Gateway 在不在(`speech` 目录与转写都要经它)。 */
2193
+ speech?: boolean;
2194
+ }
2195
+ //#endregion
2113
2196
  //#region src/host/event-bus.d.ts
2114
2197
  declare class WorkspaceEventBus {
2115
2198
  private readonly listeners;
@@ -2117,6 +2200,100 @@ declare class WorkspaceEventBus {
2117
2200
  subscribe(listener: (event: WorkspaceEvent) => void): () => void;
2118
2201
  }
2119
2202
  //#endregion
2203
+ //#region src/host/ws-host-services.d.ts
2204
+ /** 供 WS 域处理器调用的远程服务句柄。 */
2205
+ interface HostRemoteHandle {
2206
+ invoke(namespace: string, method: string, args?: Record<string, unknown>, signal?: AbortSignal): Promise<unknown>;
2207
+ stream(namespace: string, method: string, args?: Record<string, unknown>, signal?: AbortSignal): Promise<AsyncIterable<unknown>>;
2208
+ }
2209
+ //#endregion
2210
+ //#region src/host/ws-handler.d.ts
2211
+ declare class MobileGatewayWsHandler {
2212
+ private readonly database;
2213
+ private readonly chat;
2214
+ private readonly auth;
2215
+ private readonly capabilitiesFlags;
2216
+ private readonly sockets;
2217
+ private settingsService;
2218
+ private filesService;
2219
+ /**
2220
+ * 可选内核远程服务(typertGateway + owner peer)。存在时才启用定时任务与官方语音代理;
2221
+ * 缺失(老内核)时对应帧回结构化 unavailable,引导 REST 兜底。
2222
+ */
2223
+ private hostRemote;
2224
+ /** 语音管理器:跨连接共享(全局转写并发计数需要进程级单一实例)。 */
2225
+ private speech;
2226
+ /** 文件分块下载/上传管理器:跨连接共享(每连接并发计数 + spool 落盘需要进程级状态)。 */
2227
+ private fileTransfer;
2228
+ /**
2229
+ * 装配全局模型目录与文件浏览依赖。这两个域需要 DshSettingsAdapter / FileService,
2230
+ * 它们在 index.ts 里晚于 RemoteApiServer 构造(router 装配期),故用 setter 注入,
2231
+ * 而不是塞进构造函数(避免 server↔router 的构造顺序环)。
2232
+ */
2233
+ attachHostServices(services: {
2234
+ settings: MobileGatewayWsHandler['settingsService'];
2235
+ files: MobileGatewayWsHandler['filesService'];
2236
+ }): void;
2237
+ /**
2238
+ * 装配内核可选远程服务(定时任务 / 官方语音)。在 index.ts apply 期取到
2239
+ * typertGateway+connection 后注入;传入 undefined 表示当前内核不提供(优雅降级)。
2240
+ */
2241
+ attachRemoteServices(remote: HostRemoteHandle | undefined): void;
2242
+ constructor(database: WorkspaceDatabase, chat: DshChatAdapter, auth: AuthService, events: WorkspaceEventBus, capabilitiesFlags: CapabilityFlags);
2243
+ private readonly eventBridge;
2244
+ /**
2245
+ * 判定这次 upgrade 是否属于 /ws/mobile。server.ts 负责实际的 WebSocket 升级,
2246
+ * 升级完成后再把 WebSocket 交给 onConnection。
2247
+ */
2248
+ ownsPath(req: IncomingMessage): boolean;
2249
+ /**
2250
+ * 对已经升级好的 WebSocket 做子协议鉴权、状态登记、帧分发。
2251
+ * 返回 false 表示不属于此处理器(由调用方决定后续,通常是 socket.destroy())。
2252
+ */
2253
+ onConnection(webSocket: WebSocket, req: IncomingMessage): boolean;
2254
+ private handlePairingConnection;
2255
+ private handleTokenConnection;
2256
+ private acceptSocket;
2257
+ /**
2258
+ * 连接建立后补推待处理的审批/提问。逐连接按本连接身份做会话授权过滤(复用
2259
+ * chat.replayPending* 内部的 isSessionAuthorized),绝不因别的设备授权过就串推。
2260
+ * 失败静默:与 server.ts 同名方法一致,重放失败不应让连接崩掉(实时事件仍走桥)。
2261
+ *
2262
+ * @param onlySessionId - 给 subscribe 帧用:只补推该会话的待处理项;缺省补全部。
2263
+ */
2264
+ private replayPendingInteractions;
2265
+ private sendHello;
2266
+ private stateOf;
2267
+ private principalToDeviceView;
2268
+ private handleMessage;
2269
+ private handlePing;
2270
+ private handleSubscribe;
2271
+ private handleUnsubscribe;
2272
+ /**
2273
+ * 处理 `message` 帧:若未带 sessionId,先在用户首个授权根目录下建一个空会话;
2274
+ * 再把内容送进 chat.prompt。响应 `sent`,客户端后续通过 WS 的 `event` 帧
2275
+ * (或 REST /api/v1/events)拿回流式增量。
2276
+ */
2277
+ private handleMessageFrame;
2278
+ private handleWorkspaces;
2279
+ private handleSessions;
2280
+ private handleHistory;
2281
+ private handleAttachment;
2282
+ /** 向一条连接发一条帧(带背压队列)。 */
2283
+ private sendFrame;
2284
+ private sendError;
2285
+ /** 把队列里的帧逐条发出去。 */
2286
+ private drain;
2287
+ /** 拒绝未鉴权连接(401 close code)。 */
2288
+ private closeUnauthorized;
2289
+ /** 用自定义 close code 关闭,并附简短理由。 */
2290
+ private closeWithCode;
2291
+ /** 关闭所有连接(server.stop 时调用)。 */
2292
+ stop(timeoutMs?: number): Promise<void>;
2293
+ /** 当前连接数(供 metrics / 观测)。 */
2294
+ connectionCount(): number;
2295
+ }
2296
+ //#endregion
2120
2297
  //#region src/host/host-service-probe.d.ts
2121
2298
  interface ServiceProbe {
2122
2299
  name: string;
@@ -2480,6 +2657,12 @@ declare class FileService {
2480
2657
  private notify;
2481
2658
  }
2482
2659
  //#endregion
2660
+ //#region src/host/bff-rest.d.ts
2661
+ /** 宿主 `ctx.schedule` 服务在本层只需要 `create`(远程面没有新建,见 `bff-schedule.ts` 文件头)。 */
2662
+ interface ScheduleCreatePort {
2663
+ create: (sessionId: string, request: Record<string, unknown>) => Promise<unknown>;
2664
+ }
2665
+ //#endregion
2483
2666
  //#region src/host/router.d.ts
2484
2667
  interface RemoteOperationView {
2485
2668
  id: string;
@@ -2541,10 +2724,33 @@ interface RouterOptions {
2541
2724
  * 可选:不注入则这些端点返回 404,测试不必每条都补一个字段。
2542
2725
  */
2543
2726
  jobs?: JobAdapter;
2727
+ /**
2728
+ * 内核 typert Gateway 句柄:REST 的语音与定时任务读面(`bff-*.ts`)。
2729
+ *
2730
+ * 可选,且**缺席不是错误**:老内核上它本来就不存在,此时语音回
2731
+ * `200 {available:false}`、定时任务读面回 503 —— 与 `/ws/mobile` 帧的
2732
+ * `speech/unavailable`、`schedule/unavailable` 是**同一个**判据。
2733
+ */
2734
+ hostRemote?: HostRemoteHandle;
2735
+ /**
2736
+ * 宿主 `ctx.schedule` 服务:只有它有 `create`(远程面没注册,见
2737
+ * `bff-schedule.ts` 文件头)。可选,缺席时 `POST /chat/schedules` 回 503。
2738
+ */
2739
+ schedule?: ScheduleCreatePort;
2544
2740
  }
2545
2741
  declare class ApiRouter {
2546
2742
  private readonly options;
2743
+ /**
2744
+ * REST 对等面(分块上传 / 定时任务 / 语音)的分发体。
2745
+ *
2746
+ * 构造在这里而不是 `index.ts`:它只依赖 `RouterOptions` 已有的东西
2747
+ * (`database.dataDir`、`chat`、`hostRemote`、`schedule`),多一个外部装配点
2748
+ * 只会多一条会漂的接线。
2749
+ */
2750
+ private readonly bff;
2547
2751
  constructor(options: RouterOptions);
2752
+ /** 停机清理(分块 spool)。由 `index.ts` 的生命周期钩子调用。 */
2753
+ dispose(): Promise<void>;
2548
2754
  handle(req: IncomingMessage, res: ServerResponse, stripPrefix?: string): Promise<void>;
2549
2755
  private dispatch;
2550
2756
  private dispatchManagement;
@@ -2650,9 +2856,7 @@ interface RemoteApiServerOptions {
2650
2856
  * 为什么在构造参数里而不是全局变量:能力位是**这台服务端此刻**能做什么,
2651
2857
  * 同一进程里可能有多个实例(测试、重绑),用实例参数才不会被互相污染。
2652
2858
  */
2653
- capabilities?: {
2654
- jobs?: boolean;
2655
- };
2859
+ capabilities?: CapabilityFlags;
2656
2860
  }
2657
2861
  declare class RemoteApiServer implements RemoteControl {
2658
2862
  private readonly options;
@@ -2665,6 +2869,8 @@ declare class RemoteApiServer implements RemoteControl {
2665
2869
  private readonly operations;
2666
2870
  private readonly sockets;
2667
2871
  private readonly webSockets;
2872
+ /** 移动端网关 WebSocket(/ws/mobile):与 /api/v1/events 并存,WS 优先、REST 兜底。 */
2873
+ private readonly wsMobile;
2668
2874
  private readonly eventsAbort;
2669
2875
  private unsubscribeEvents;
2670
2876
  /** 所有连接的 drain 完成信号;`Promise.all` 它们即可等排空。 */
@@ -2677,6 +2883,16 @@ declare class RemoteApiServer implements RemoteControl {
2677
2883
  private readonly highWaterMarkBytes;
2678
2884
  private readonly heartbeatIntervalMs;
2679
2885
  constructor(options: RemoteApiServerOptions);
2886
+ /**
2887
+ * 注入全局模型目录与文件浏览依赖(index.ts 在 router 装配期调用)。
2888
+ * 这两个域依赖晚于本类构造的 DshSettingsAdapter / FileService,故走 setter 而非构造参数。
2889
+ */
2890
+ attachMobileHostServices(services: Parameters<MobileGatewayWsHandler['attachHostServices']>[0]): void;
2891
+ /**
2892
+ * 注入内核可选远程服务(typertGateway + owner peer),用于定时任务与官方语音 WS 代理。
2893
+ * 传入 undefined(老内核无这两个服务)时相关帧回结构化 unavailable,引导 REST 兜底。
2894
+ */
2895
+ attachMobileRemoteServices(remote: Parameters<MobileGatewayWsHandler['attachRemoteServices']>[0]): void;
2680
2896
  start(): Promise<void>;
2681
2897
  stop(): Promise<void>;
2682
2898
  status(operationId?: string): {