@aipanel/provider-deepseek 1.2.7 → 1.2.8

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/api.d.ts CHANGED
@@ -7,30 +7,68 @@ import type { SessionSummary } from "./types";
7
7
  export declare class DeepSeekAPI {
8
8
  private hostname;
9
9
  private getWebPort;
10
+ /** dsh 启动打印的 launch token(browser-session 认证),由 provider 在捕获后写入 */
11
+ private launchToken?;
12
+ /** 启动早期等待 token 就绪的源(dsh 进程 stdout 的 LaunchToken.wait),未就绪时勿立即抛错 */
13
+ private launchTokenSource?;
14
+ /** browser-session 签名 Cookie(经 launch token 换取),dsh web 所有 /api 请求必须携带 */
15
+ private authCookie?;
16
+ /** 认证引导的幂等 Promise,避免并发多次换取 */
17
+ private authPromise?;
10
18
  constructor(hostname: string, getWebPort: () => number);
11
19
  /** 应用壳 URL(无 deepLink 能力,所有会话共用) */
12
20
  get shellUrl(): string;
21
+ /** 注入 dsh 启动时打印的 launch token(来自 deepseek-web 的 stdout 解析) */
22
+ setLaunchToken(token: string): void;
23
+ /**
24
+ * 绑定 launch token 等待源(dsh 进程 stdout 解析的 LaunchToken.wait)。
25
+ * 启动早期 widget 可能在 token 捕获前就发起 /api 请求,此时先等待 token 就绪而非抛错。
26
+ */
27
+ setLaunchTokenSource(source: () => Promise<string>): void;
28
+ /** 当前 browser-session Cookie(未认证时 undefined);供代理向转发请求注入同一 Cookie */
29
+ getAuthCookie(): string | undefined;
30
+ /**
31
+ * 用 launch token 首次访问索引页换取 browser-session Cookie(dsh 0.1.2+ 的 browser-auth 门禁)。
32
+ * 幂等:已认证时直接返回。失败时抛错(调用方按需降级,不阻塞 dsh 启动)。
33
+ */
34
+ authenticate(): Promise<void>;
35
+ /**
36
+ * GET /?token=<launchToken>,从 303 响应捕获 Set-Cookie(browser-auth 用签名 cookie 换首访权限)。
37
+ * node:http 不自动跟随 303,因此能拿到该次响应的 Set-Cookie。
38
+ */
39
+ private bootstrapAuthCookie;
40
+ /** 确保已认证(未认证时先等待 launch token 就绪;无 token 来源才抛错) */
41
+ private ensureAuthenticated;
13
42
  /** 发起一次 unary RPC,返回 result.value(ok=false 时抛错) */
14
43
  private call;
15
44
  /**
16
- * 列出当前项目目录下的会话。
17
- * dsh session.list 不提供按目录过滤,故结合 workspace.list(path→sessionIds)
18
- * 与各会话的 cwd 字段,在本端合并去重出属于 projectDir 的会话集。
45
+ * 列出当前项目目录下的会话(可见性口径与 dsh UI 对齐)。
46
+ * workspace/follow baseline(workspace.list dsh 0.1.2+ 已移除)按 path 匹配工作区,
47
+ * sessionIds 与全局 archivedSessionIds,再结合 session/list 的 cwd 过滤去重;
48
+ * 过滤规则同 dsh-client-ui-workspace 的 sessionVisible:
49
+ * 排除 origin=subagent 的子代理会话(UI 不单列)、排除归档;blank(未开过回合)仅当
50
+ * sessionId === activeSessionId(当前选中会话,对应 UI 的 New Session 占位行)时展示。
19
51
  */
20
- listSessions(projectDir: string, retries?: number): Promise<SessionSummary[]>;
52
+ listSessions(projectDir: string, activeSessionId?: string, retries?: number): Promise<SessionSummary[]>;
21
53
  /** 在当前目录下创建会话(dsh 仅返回 { sessionId, agentPreset? },非完整 SessionSummary)。
22
- * 关键:必须先确保 projectDir 对应的 workspace 存在(workspace.create 幂等 get-or-create),
23
- * 再用 workspaceId 调 session.create。若只传 cwd,dsh 侧不会把会话挂到任何 workspace,
24
- * 新会话会落到侧边栏"未分组"。 */
54
+ * 与旧逻辑一致:先确保 projectDir 对应的 workspace 存在(workspace/create 幂等 get-or-create),
55
+ * 再用 workspaceId 调 session/create,让新会话挂到该 workspace。 */
25
56
  createSession(projectDir: string, retries?: number): Promise<{
26
57
  sessionId: string;
27
58
  }>;
28
59
  /**
29
- * 通过 dsh settings.update 应用 providerOptions 指定的用户设置(逐命名空间幂等 patch)。
60
+ * 通过 dsh settings/mutate 应用 providerOptions 指定的用户设置(逐命名空间幂等 patch)。
30
61
  * dsh 启动初期 API 未就绪,整体带重试;单个命名空间不存在会导致该次调用失败重试。
31
62
  */
32
63
  applySettings(sections: Record<string, Record<string, unknown>>, retries?: number): Promise<void>;
33
64
  /** 归档会话(dsh 无硬删除,仅归档;幂等) */
34
65
  archiveSession(sessionId: string, retries?: number): Promise<void>;
66
+ /**
67
+ * 取 workspace/follow 流的 baseline(等价旧 workspace.list 的快照:{ items, archivedSessionIds })。
68
+ * dsh 0.1.2+ 无 workspace.list RPC,工作区发现走 /api/remote.mux 上的 workspace/follow 流。
69
+ * 直连 dsh web(webPort)并携带 browser-session Cookie(原生 WebSocket 支持自定义请求头),
70
+ * 打开流读到首个 baseline 帧即关闭;启动早期 webPort 已就绪,不存在代理时序竞态。
71
+ */
72
+ private fetchWorkspaceBaseline;
35
73
  private createHttpRequest;
36
74
  }
@@ -110,6 +110,11 @@ function generateBridgeScript(options = {}) {
110
110
  } catch (e) { /* ignore */ }
111
111
  }
112
112
 
113
+ // === \u9009\u62E9\u6A21\u5F0F\u72B6\u6001 ===
114
+ // \u8BB0\u5F55\u5BBF\u4E3B\u662F\u5426\u5904\u4E8E"\u9009\u62E9\u5143\u7D20"\u6A21\u5F0F\uFF1A\u9009\u62E9\u6A21\u5F0F\u5F00\u542F\u65F6\uFF0Ciframe \u5185\u6355\u83B7\u7684 Esc \u5E94\u4F18\u5148
115
+ // \u8F6C\u53D1\u7ED9\u5BBF\u4E3B\u9000\u51FA\u9009\u62E9\u6A21\u5F0F\uFF0C\u5E76\u541E\u6389\u672C\u9875\u81EA\u8EAB\u7684\u6309\u952E\u5904\u7406\uFF08\u5982\u505C\u6B62\u751F\u6210/\u5173\u95ED\u5F39\u5C42\uFF09\u3002
116
+ let isInSelectMode = false;
117
+
113
118
  // === \u6D88\u606F\u76D1\u542C ===
114
119
  window.addEventListener("message", function(event) {
115
120
  if (!event.data) return;
@@ -137,8 +142,40 @@ function generateBridgeScript(options = {}) {
137
142
  if (event.data.type === ${JSON.stringify(import_core.WIDGET_MSG.INSERT_FILE_PART)}) {
138
143
  pushSelection(event.data.element);
139
144
  }
145
+
146
+ // \u6838\u5FC3\u5C42\u901A\u77E5\uFF1A\u9009\u62E9\u5143\u7D20\u6A21\u5F0F\u53D8\u5316\uFF08\u66F4\u65B0\u672C\u5730\u72B6\u6001\uFF0C\u4F9B\u952E\u76D8\u8F6C\u53D1\u5224\u65AD\u662F\u5426\u541E\u6389\u672C\u9875 Esc \u5904\u7406\uFF09
147
+ if (event.data.type === ${JSON.stringify(import_core.WIDGET_MSG.SELECT_MODE_CHANGE)}) {
148
+ isInSelectMode = event.data.selectMode === true;
149
+ }
140
150
  });
141
151
 
152
+ // === \u952E\u76D8\u4E8B\u4EF6\u8F6C\u53D1\uFF08\u9000\u51FA/\u5207\u6362\u9009\u62E9\u6A21\u5F0F\uFF09 ===
153
+ // \u4E0E opencode bridge \u4E00\u81F4\uFF1Aiframe \u5185\u7684 keydown \u4E0D\u4F1A\u5192\u6CE1\u5230\u5BBF\u4E3B\u6587\u6863\uFF08\u8DE8\u6587\u6863\u4E8B\u4EF6\u9694\u79BB\uFF09\uFF0C
154
+ // \u56E0\u6B64\u5F53\u7126\u70B9\u5728\u804A\u5929 iframe \u5185\u65F6\uFF0C\u5BBF\u4E3B App \u6536\u4E0D\u5230 Esc / Ctrl+P\uFF0C\u5BFC\u81F4\uFF1A
155
+ // - \u9009\u62E9\u5143\u7D20\u6A21\u5F0F\u5F00\u542F\u65F6\u6309 Esc \u65E0\u6CD5\u9000\u51FA\uFF08\u88AB dsh \u81EA\u8EAB\u6309\u952E\u5904\u7406\u541E\u6389\u6216\u76F4\u63A5\u4E22\u5931\uFF09\uFF1B
156
+ // - \u5149\u6807\u5728\u804A\u5929\u8F93\u5165\u6846\u65F6\u65E0\u6CD5\u7528 Ctrl+P \u8FDB\u5165\u9009\u62E9\u5143\u7D20\u6A21\u5F0F\u3002
157
+ // \u6B64\u5904\u7528\u6355\u83B7\u9636\u6BB5\u76D1\u542C Esc / Ctrl+P \u5E76\u8F6C\u53D1\u4E3A KEYDOWN \u6D88\u606F\u7ED9\u5BBF\u4E3B App\uFF08App.vue \u636E\u6B64
158
+ // \u9000\u51FA/\u5207\u6362\u9009\u62E9\u6A21\u5F0F\uFF09\u3002\u9009\u62E9\u6A21\u5F0F\u5F00\u542F\u65F6\u4F18\u5148\u9000\u51FA\uFF1ApreventDefault + stopPropagation\uFF0C
159
+ // \u907F\u514D dsh \u81EA\u8EAB\u5BF9 Esc \u7684\u5904\u7406\u62A2\u8D70\u6309\u952E\u3002
160
+ window.addEventListener("keydown", function(event) {
161
+ if (event.key === "Escape" || (event.ctrlKey && event.key.toLowerCase() === "p")) {
162
+ if (isInSelectMode) {
163
+ event.preventDefault();
164
+ event.stopPropagation();
165
+ }
166
+ if (window.parent !== window) {
167
+ window.parent.postMessage({
168
+ type: ${JSON.stringify(import_core.WIDGET_MSG.KEYDOWN)},
169
+ key: event.key,
170
+ ctrlKey: event.ctrlKey,
171
+ metaKey: event.metaKey,
172
+ shiftKey: event.shiftKey,
173
+ altKey: event.altKey
174
+ }, "*");
175
+ }
176
+ }
177
+ }, true);
178
+
142
179
  // === \u5C31\u7EEA\u901A\u77E5 ===
143
180
  // \u4E0D\u518D\u7528 MutationObserver \u6BCF\u5E27\u53D1 READY\uFF08\u90A3\u4F1A\u5728 dsh \u521A\u6E32\u67D3\u3001\u76EE\u6807\u4F1A\u8BDD\u5C1A\u672A\u6FC0\u6D3B\u65F6\u5C31
144
181
  // \u63D0\u524D\u653E\u884C loading\uFF09\u3002\u6539\u4E3A\u76D1\u542C dsh-client \u63D2\u4EF6\u5728"\u76EE\u6807\u4F1A\u8BDD\u6FC0\u6D3B\u4E14\u6E32\u67D3\u7A33\u5B9A"\u540E\u6D3E\u53D1\u7684
package/lib/constants.cjs CHANGED
@@ -20,15 +20,13 @@ __export(constants_exports, {
20
20
  DEFAULT_DEEPSEEK_PROVIDER_OPTIONS: () => DEFAULT_DEEPSEEK_PROVIDER_OPTIONS,
21
21
  DSH_API_BASE: () => DSH_API_BASE,
22
22
  DSH_DEFAULT_PORT: () => DSH_DEFAULT_PORT,
23
- DSH_HOST_EVENTS_PATH: () => DSH_HOST_EVENTS_PATH,
24
23
  DSH_LOOPBACK_HOST: () => DSH_LOOPBACK_HOST,
25
- DSH_MUX_EVENTS_PATH: () => DSH_MUX_EVENTS_PATH,
24
+ DSH_REMOTE_MUX_PATH: () => DSH_REMOTE_MUX_PATH,
26
25
  DSH_STORAGE_KEYS: () => DSH_STORAGE_KEYS
27
26
  });
28
27
  module.exports = __toCommonJS(constants_exports);
29
28
  const DSH_API_BASE = "/api";
30
- const DSH_MUX_EVENTS_PATH = "/api/events.mux";
31
- const DSH_HOST_EVENTS_PATH = "/api/events.host";
29
+ const DSH_REMOTE_MUX_PATH = "/api/remote.mux";
32
30
  const DSH_LOOPBACK_HOST = "127.0.0.1";
33
31
  const DSH_DEFAULT_PORT = 3080;
34
32
  const DSH_STORAGE_KEYS = {
@@ -40,7 +38,6 @@ const DSH_STORAGE_KEYS = {
40
38
  DIAGNOSTICS_ENABLED: "dsh.bridge.diagnostics.enabled"
41
39
  };
42
40
  const DEFAULT_DEEPSEEK_PROVIDER_OPTIONS = {
43
- agentPreset: "code",
44
41
  // 对齐 opencode 的 enableLsp(默认 true):诊断功能默认开启
45
42
  enableDiagnostics: true,
46
43
  // 对齐 opencode:自动诊断默认开启
@@ -51,8 +48,7 @@ const DEFAULT_DEEPSEEK_PROVIDER_OPTIONS = {
51
48
  DEFAULT_DEEPSEEK_PROVIDER_OPTIONS,
52
49
  DSH_API_BASE,
53
50
  DSH_DEFAULT_PORT,
54
- DSH_HOST_EVENTS_PATH,
55
51
  DSH_LOOPBACK_HOST,
56
- DSH_MUX_EVENTS_PATH,
52
+ DSH_REMOTE_MUX_PATH,
57
53
  DSH_STORAGE_KEYS
58
54
  });
@@ -4,12 +4,15 @@
4
4
  */
5
5
  import type { DeepSeekProviderOptions } from "./types";
6
6
  /** ==================== dsh API ==================== */
7
- /** dsh 所有 RPC 路径前缀(POST /api/<method>、GET /api/events.mux) */
7
+ /** dsh 所有 RPC 路径前缀(POST /api/<endpoint>) */
8
8
  export declare const DSH_API_BASE = "/api";
9
- /** mux 事件流端点(会话级聚合流,含 session/event 可推导 thinking/streaming) */
10
- export declare const DSH_MUX_EVENTS_PATH = "/api/events.mux";
11
- /** host 事件流端点(host 级,含 host/session-status.running 运行态开关) */
12
- export declare const DSH_HOST_EVENTS_PATH = "/api/events.host";
9
+ /**
10
+ * Remote mux WebSocket 端点(dsh 0.1.2+)。
11
+ * 旧版 /api/events.mux、/api/events.host workspace.list RPC 已移除:
12
+ * 会话/工作区能力改经此 mux 以流方式订阅(workspace/follow baseline、session/follow、
13
+ * 转发事件 $events 等),unary RPC 则保持 POST /api/<endpoint>。
14
+ */
15
+ export declare const DSH_REMOTE_MUX_PATH = "/api/remote.mux";
13
16
  /** dsh 唯一允许的绑定主机字面量(服务 schema 只接受 127.0.0.1 / 0.0.0.0) */
14
17
  export declare const DSH_LOOPBACK_HOST = "127.0.0.1";
15
18
  /** dsh web 默认端口(未显式指定时) */
@@ -2,6 +2,7 @@ var __defProp = Object.defineProperty;
2
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
4
  var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
6
  var __export = (target, all) => {
6
7
  for (var name in all)
7
8
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -15,16 +16,61 @@ var __copyProps = (to, from, except, desc) => {
15
16
  return to;
16
17
  };
17
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
18
20
  var deepseek_web_exports = {};
19
21
  __export(deepseek_web_exports, {
22
+ LaunchToken: () => LaunchToken,
20
23
  startDeepSeekWeb: () => startDeepSeekWeb
21
24
  });
22
25
  module.exports = __toCommonJS(deepseek_web_exports);
23
26
  var import_execa = require("execa");
24
27
  var import_node = require("@aipanel/core/node");
25
28
  const log = (0, import_node.createLogger)("DeepSeekWeb");
29
+ class LaunchToken {
30
+ constructor() {
31
+ __publicField(this, "token");
32
+ /** 首次超时后缓存失败:本启动已不可能再打印 token,后续 wait 直接快速失败 */
33
+ __publicField(this, "failure");
34
+ __publicField(this, "waiters", []);
35
+ }
36
+ /** 从子进程输出写入已解析的 token(幂等:只接受第一个) */
37
+ set(token) {
38
+ if (this.token !== void 0) return;
39
+ this.token = token;
40
+ this.failure = void 0;
41
+ for (const w of this.waiters) {
42
+ clearTimeout(w.timer);
43
+ w.resolve(token);
44
+ }
45
+ this.waiters = [];
46
+ }
47
+ /** 已解析的 token(未就绪时 undefined) */
48
+ get() {
49
+ return this.token;
50
+ }
51
+ /** 等待 token 就绪(默认 20s 超时;未打印则抛错并快速失败,调用方降级处理) */
52
+ wait(timeoutMs = 2e4) {
53
+ if (this.token !== void 0) return Promise.resolve(this.token);
54
+ if (this.failure) return Promise.reject(this.failure);
55
+ return new Promise((resolve, reject) => {
56
+ const timer = setTimeout(() => {
57
+ const err = new Error(
58
+ `dsh launch token was not captured from stdout within ${timeoutMs}ms (dsh >= 0.1.2 should print the "dsh web: http://127.0.0.1:<port>/?token=..." URL)`
59
+ );
60
+ this.failure = err;
61
+ for (const w of this.waiters) {
62
+ clearTimeout(w.timer);
63
+ w.reject(err);
64
+ }
65
+ this.waiters = [];
66
+ reject(err);
67
+ }, timeoutMs);
68
+ this.waiters.push({ resolve, reject, timer });
69
+ });
70
+ }
71
+ }
26
72
  function startDeepSeekWeb(options) {
27
- const { port, hostname, cwd, patchPath, home, verbose } = options;
73
+ const { port, hostname, cwd, patchPath, home, verbose, launchToken } = options;
28
74
  const args = ["--profile", "web"];
29
75
  if (patchPath) {
30
76
  args.push("--patch", patchPath);
@@ -63,8 +109,19 @@ function startDeepSeekWeb(options) {
63
109
  }).catch((e) => {
64
110
  log.error("[dsh spawn failed]", { error: e instanceof Error ? e.message : String(e) });
65
111
  });
112
+ let stdoutBuffer = "";
66
113
  proc.stdout?.on("data", (data) => {
67
- const output = data.toString().trim();
114
+ const chunk = data.toString();
115
+ stdoutBuffer += chunk;
116
+ if (stdoutBuffer.length > 4096) stdoutBuffer = stdoutBuffer.slice(-4096);
117
+ if (launchToken && !launchToken.get()) {
118
+ const match = stdoutBuffer.match(/[?&]token=([A-Za-z0-9_-]+)/);
119
+ if (match) {
120
+ launchToken.set(match[1]);
121
+ log.debug("[dsh] captured launch token");
122
+ }
123
+ }
124
+ const output = chunk.trim();
68
125
  if (output) {
69
126
  log.debug("[dsh stdout]", { output });
70
127
  (0, import_node.getProcessLogBuffer)().addProviderStdout(output);
@@ -81,5 +138,6 @@ function startDeepSeekWeb(options) {
81
138
  }
82
139
  // Annotate the CommonJS export names for ESM import in node:
83
140
  0 && (module.exports = {
141
+ LaunchToken,
84
142
  startDeepSeekWeb
85
143
  });
@@ -1,4 +1,22 @@
1
1
  import { type ResultPromise } from "execa";
2
+ /**
3
+ * dsh 启动时打印的 launch token 捕获器。
4
+ * dsh web 升级后在索引页与 /api 走 browser-session 认证:启动成功会在 stdout 打印
5
+ * `dsh web: http://127.0.0.1:3080/?token=<launchToken>`,首次访问需携带该 token 换取签名 cookie。
6
+ * 本类在子进程 stdout 就该 URL 打洞解析 token,供 DeepSeekAPI 与代理做认证。
7
+ */
8
+ export declare class LaunchToken {
9
+ private token?;
10
+ /** 首次超时后缓存失败:本启动已不可能再打印 token,后续 wait 直接快速失败 */
11
+ private failure?;
12
+ private waiters;
13
+ /** 从子进程输出写入已解析的 token(幂等:只接受第一个) */
14
+ set(token: string): void;
15
+ /** 已解析的 token(未就绪时 undefined) */
16
+ get(): string | undefined;
17
+ /** 等待 token 就绪(默认 20s 超时;未打印则抛错并快速失败,调用方降级处理) */
18
+ wait(timeoutMs?: number): Promise<string>;
19
+ }
2
20
  export interface DeepSeekWebOptions {
3
21
  /** 服务端口 */
4
22
  port: number;
@@ -12,6 +30,8 @@ export interface DeepSeekWebOptions {
12
30
  home?: string;
13
31
  /** 启用 verbose 模式 */
14
32
  verbose?: boolean;
33
+ /** launch token 捕获器:进程 stdout 解析出 ?token= 后写入,供认证使用 */
34
+ launchToken?: LaunchToken;
15
35
  }
16
36
  /**
17
37
  * 启动 dsh web 服务。
package/lib/profile.cjs CHANGED
@@ -42,7 +42,8 @@ function buildDshOverlay(options) {
42
42
  pluginAvailable = true,
43
43
  clientAvailable = true,
44
44
  autoDiagnose,
45
- enableDiagnostics = true
45
+ enableDiagnostics = true,
46
+ eventsToken
46
47
  } = options;
47
48
  const mcpUrl = `http://127.0.0.1:${vitePort}${import_node.MCP_API_PATH}`;
48
49
  const rows = [];
@@ -68,7 +69,11 @@ function buildDshOverlay(options) {
68
69
  ` vitePort: ${vitePort}`,
69
70
  ` contextApiPath: ${JSON.stringify(import_node.CONTEXT_API_PATH)}`,
70
71
  ` enableDiagnostics: ${enableDiagnostics ? "true" : "false"}`,
71
- ...autoDiagnose !== void 0 ? [` autoDiagnose: ${autoDiagnose ? "true" : "false"}`] : []
72
+ ...autoDiagnose !== void 0 ? [` autoDiagnose: ${autoDiagnose ? "true" : "false"}`] : [],
73
+ ...eventsToken ? [
74
+ ` eventsToken: ${JSON.stringify(eventsToken)}`,
75
+ ` eventsPath: ${JSON.stringify(import_node.HOST_EVENTS_API_PATH)}`
76
+ ] : []
72
77
  ].join("\n")
73
78
  );
74
79
  rows.push(
package/lib/profile.d.ts CHANGED
@@ -11,6 +11,12 @@ export declare function buildDshOverlay(options: {
11
11
  * undefined 时不写入 overlay,由 dsh-plugin 回退到 OPENCODE_ENABLE_LINT=1(与 opencode 一致)。
12
12
  */
13
13
  autoDiagnose?: boolean;
14
+ /**
15
+ * 宿主事件推送令牌(core 每轮启动随机):随 plugin config 注入 dsh-plugin,
16
+ * 使其把归一化 ProviderEvent POST 到 core 的 HOST_EVENTS_API_PATH。
17
+ * 缺省(undefined)时不写该配置行,事件中继不启用(兼容未升级的 dsh-plugin)。
18
+ */
19
+ eventsToken?: string;
14
20
  /**
15
21
  * 诊断功能总开关(provider option enableDiagnostics)。
16
22
  * true(默认,对齐 opencode enableLsp)时 host 插件注册 run_diagnostics 工具与自动诊断逻辑。
package/lib/provider.cjs CHANGED
@@ -23,6 +23,7 @@ __export(provider_exports, {
23
23
  });
24
24
  module.exports = __toCommonJS(provider_exports);
25
25
  const import_meta = {};
26
+ var import_core = require("@aipanel/core");
26
27
  var import_node = require("@aipanel/core/node");
27
28
  var import_constants = require("./constants.cjs");
28
29
  var import_constants2 = require("./constants.cjs");
@@ -76,6 +77,18 @@ or run without installing:
76
77
  };
77
78
  }
78
79
  const version = await (0, import_system.getDeepSeekVersion)();
80
+ const compatible = version === null ? true : (0, import_system.isDeepSeekVersionAtLeast)(version);
81
+ if (compatible === false) {
82
+ return {
83
+ ok: false,
84
+ message: `DeepSeek Harness (dsh) ${version} is too old: this provider requires dsh >= ${import_system.MIN_DSH_VERSION} (browser-session auth / Remote RPC protocol).
85
+
86
+ Please upgrade:
87
+
88
+ npm install -g @deepseek-ai/dsh@latest
89
+ `
90
+ };
91
+ }
79
92
  return { ok: true, version: version ?? void 0 };
80
93
  }
81
94
  async start(options) {
@@ -116,18 +129,50 @@ or run without installing:
116
129
  pluginAvailable,
117
130
  clientAvailable,
118
131
  autoDiagnose: this.opts.autoDiagnose,
119
- enableDiagnostics: this.opts.enableDiagnostics
132
+ enableDiagnostics: this.opts.enableDiagnostics,
133
+ // 宿主事件推送令牌(core 每轮启动随机):随 plugin config 注入 dsh-plugin 用于回推鉴权
134
+ eventsToken: options.eventsToken
120
135
  });
121
136
  const patchPath = (0, import_profile.writeDshOverlay)(options.cwd, overlay);
137
+ const launchToken = new import_deepseek_web.LaunchToken();
138
+ this.api.setLaunchTokenSource(() => launchToken.wait());
122
139
  const proc = (0, import_deepseek_web.startDeepSeekWeb)({
123
140
  port: options.port,
124
141
  hostname: import_constants2.DSH_LOOPBACK_HOST,
125
142
  cwd: options.cwd,
126
143
  patchPath,
127
144
  home: this.opts.home,
128
- verbose: options.verbose
145
+ verbose: options.verbose,
146
+ launchToken
129
147
  });
130
148
  this.process = proc;
149
+ let webAuthCookie;
150
+ try {
151
+ const token = await launchToken.wait();
152
+ this.api.setLaunchToken(token);
153
+ const maxAuthAttempts = 3;
154
+ for (let attempt = 1; ; attempt++) {
155
+ try {
156
+ await this.api.authenticate();
157
+ break;
158
+ } catch (e) {
159
+ if (attempt >= maxAuthAttempts) throw e;
160
+ log.debug("dsh browser-session auth bootstrap attempt failed, retrying", {
161
+ attempt,
162
+ error: e instanceof Error ? e.message : String(e)
163
+ });
164
+ await (0, import_core.sleep)(250 * attempt);
165
+ }
166
+ }
167
+ webAuthCookie = this.api.getAuthCookie();
168
+ } catch (e) {
169
+ log.warn(
170
+ "failed to establish dsh web browser-session auth; UI may show authentication required",
171
+ {
172
+ error: e instanceof Error ? e.message : String(e)
173
+ }
174
+ );
175
+ }
131
176
  const settings = this.buildSettingsToApply();
132
177
  if (Object.keys(settings).length > 0) {
133
178
  void this.api.applySettings(settings).catch((e) => {
@@ -137,7 +182,7 @@ or run without installing:
137
182
  });
138
183
  });
139
184
  }
140
- return { url: this.api.shellUrl, processHandle: proc };
185
+ return { url: this.api.shellUrl, processHandle: proc, webAuthCookie };
141
186
  }
142
187
  /** 把 providerOptions 配置映射为 dsh settings 命名空间 patch(仅含用户显式配置项) */
143
188
  buildSettingsToApply() {
@@ -169,8 +214,8 @@ or run without installing:
169
214
  async killOrphans() {
170
215
  return (0, import_system.killOrphanDeepSeekProcesses)();
171
216
  }
172
- async listSessions(projectDir) {
173
- const sessions = await this.api.listSessions(projectDir);
217
+ async listSessions(projectDir, activeSessionId) {
218
+ const sessions = await this.api.listSessions(projectDir, activeSessionId);
174
219
  const url = this.buildSessionUrl(projectDir, "");
175
220
  return sessions.map((s) => toChatSession(s, url));
176
221
  }
@@ -195,104 +240,12 @@ or run without installing:
195
240
  return `http://${import_constants2.DSH_LOOPBACK_HOST}:${this.deps.getProxyPort()}/`;
196
241
  }
197
242
  subscribeEvents(handler) {
198
- const port = this.deps.getWebPort();
199
- const base = `ws://${import_constants2.DSH_LOOPBACK_HOST}:${port}`;
200
- const endpoints = [import_constants2.DSH_MUX_EVENTS_PATH, import_constants2.DSH_HOST_EVENTS_PATH];
201
- log.debug("Subscribing to dsh event streams (WebSocket)", {
202
- endpoints: endpoints.map((p) => base + p)
203
- });
204
- let aborted = false;
205
- const sockets = /* @__PURE__ */ new Set();
206
- const retryTimers = /* @__PURE__ */ new Set();
207
- const cleanupAll = () => {
208
- for (const timer of retryTimers) clearTimeout(timer);
209
- retryTimers.clear();
210
- for (const ws of sockets) {
211
- try {
212
- ws.close();
213
- } catch {
214
- }
215
- }
216
- sockets.clear();
217
- };
218
- const connect = (path, attempt = 0) => {
219
- if (aborted) return;
220
- let socket;
221
- try {
222
- socket = new WebSocket(base + path);
223
- } catch (e) {
224
- log.warn("dsh event WebSocket create failed", { path, error: String(e) });
225
- scheduleReconnect(path, attempt + 1);
226
- return;
227
- }
228
- sockets.add(socket);
229
- socket.onmessage = (ev) => {
230
- try {
231
- const frame = JSON.parse(String(ev.data));
232
- const event = mapEvent(frame);
233
- if (event) handler(event);
234
- } catch {
235
- }
236
- };
237
- socket.onclose = () => {
238
- sockets.delete(socket);
239
- scheduleReconnect(path, attempt + 1);
240
- };
241
- socket.onerror = () => {
242
- try {
243
- socket.close();
244
- } catch {
245
- }
246
- };
247
- };
248
- const scheduleReconnect = (path, attempt) => {
249
- if (aborted) return;
250
- const delays = [250, 500, 1e3, 2e3, 5e3];
251
- const after = delays[Math.min(attempt, delays.length - 1)];
252
- const timer = setTimeout(() => {
253
- retryTimers.delete(timer);
254
- connect(path);
255
- }, after);
256
- retryTimers.add(timer);
257
- };
258
- endpoints.forEach((p) => connect(p));
243
+ void handler;
244
+ log.debug("dsh \u4E8B\u4EF6\u7ECF\u5BBF\u4E3B\u63D2\u4EF6(dsh-plugin)\u4E2D\u7EE7\u63A8\u9001\uFF0Cprovider \u76F4\u8FDE\u4E8B\u4EF6\u901A\u9053\u4E3A\u7A7A");
259
245
  return () => {
260
- aborted = true;
261
- cleanupAll();
262
246
  };
263
247
  }
264
248
  }
265
- function mapEvent(frame) {
266
- if (!frame || typeof frame.method !== "string" || !frame.payload) return null;
267
- const payload = frame.payload;
268
- const sessionId = payload.sessionId ?? "";
269
- switch (frame.method) {
270
- case "host/session-status": {
271
- if (!sessionId) return null;
272
- const running = payload.running === true;
273
- return { type: "session.status", sessionId, status: running ? "running" : "idle" };
274
- }
275
- case "session/event": {
276
- const event = payload.event;
277
- const type = event?.type ?? "";
278
- if (!sessionId || !type) return null;
279
- switch (type) {
280
- case "assistant/message":
281
- case "turn/end":
282
- return { type: "thinking", sessionId, thinking: false };
283
- case "assistant/chunk":
284
- case "thinking/delta":
285
- case "turn/start":
286
- case "step/start":
287
- return { type: "thinking", sessionId, thinking: true };
288
- default:
289
- return null;
290
- }
291
- }
292
- default:
293
- return null;
294
- }
295
- }
296
249
  function toChatSession(s, url) {
297
250
  return {
298
251
  id: s.sessionId,
@@ -309,7 +262,7 @@ function resolveDeepSeekOptions(options) {
309
262
  return {
310
263
  ...import_constants.DEFAULT_DEEPSEEK_PROVIDER_OPTIONS,
311
264
  home: po.home ?? options.home,
312
- agentPreset: po.agentPreset ?? options.agentPreset ?? import_constants.DEFAULT_DEEPSEEK_PROVIDER_OPTIONS.agentPreset,
265
+ agentPreset: po.agentPreset ?? options.agentPreset,
313
266
  permissionPreset: po.permissionPreset ?? options.permissionPreset,
314
267
  busyEnter: po.busyEnter ?? options.busyEnter,
315
268
  autoDiagnose: po.autoDiagnose ?? options.autoDiagnose ?? import_constants.DEFAULT_DEEPSEEK_PROVIDER_OPTIONS.autoDiagnose,
package/lib/provider.d.ts CHANGED
@@ -38,7 +38,7 @@ export declare class DeepSeekWebProvider implements WebProvider {
38
38
  private buildSettingsToApply;
39
39
  stop(): Promise<void>;
40
40
  killOrphans(): Promise<number>;
41
- listSessions(projectDir: string): Promise<ChatSession[]>;
41
+ listSessions(projectDir: string, activeSessionId?: string): Promise<ChatSession[]>;
42
42
  createSession(projectDir: string, title?: string): Promise<ChatSession>;
43
43
  deleteSession(sessionId: string): Promise<void>;
44
44
  buildSessionUrl(projectDir: string, sessionId: string): string;
package/lib/system.cjs CHANGED
@@ -17,8 +17,10 @@ var __copyProps = (to, from, except, desc) => {
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
  var system_exports = {};
19
19
  __export(system_exports, {
20
+ MIN_DSH_VERSION: () => MIN_DSH_VERSION,
20
21
  checkDeepSeekInstalled: () => checkDeepSeekInstalled,
21
22
  getDeepSeekVersion: () => getDeepSeekVersion,
23
+ isDeepSeekVersionAtLeast: () => isDeepSeekVersionAtLeast,
22
24
  killOrphanDeepSeekProcesses: () => killOrphanDeepSeekProcesses
23
25
  });
24
26
  module.exports = __toCommonJS(system_exports);
@@ -42,6 +44,45 @@ async function checkDeepSeekInstalled() {
42
44
  });
43
45
  });
44
46
  }
47
+ const MIN_DSH_VERSION = "0.1.2-rc.1";
48
+ function parseDshVersion(version) {
49
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(version.trim());
50
+ if (!match) return null;
51
+ return {
52
+ major: Number(match[1]),
53
+ minor: Number(match[2]),
54
+ patch: Number(match[3]),
55
+ pre: match[4]
56
+ };
57
+ }
58
+ function isDeepSeekVersionAtLeast(version, minimum = MIN_DSH_VERSION) {
59
+ const a = parseDshVersion(version);
60
+ const b = parseDshVersion(minimum);
61
+ if (!a || !b) return null;
62
+ for (const key of ["major", "minor", "patch"]) {
63
+ if (a[key] !== b[key]) return a[key] > b[key];
64
+ }
65
+ if (a.pre === void 0 && b.pre === void 0) return true;
66
+ if (a.pre === void 0) return true;
67
+ if (b.pre === void 0) return false;
68
+ const pa = a.pre.split(".");
69
+ const pb = b.pre.split(".");
70
+ const len = Math.max(pa.length, pb.length);
71
+ for (let i = 0; i < len; i++) {
72
+ const x = pa[i];
73
+ const y = pb[i];
74
+ if (x === void 0) return true;
75
+ if (y === void 0) return false;
76
+ const xn = /^\d+$/.test(x) ? Number(x) : NaN;
77
+ const yn = /^\d+$/.test(y) ? Number(y) : NaN;
78
+ if (!Number.isNaN(xn) && !Number.isNaN(yn)) {
79
+ if (xn !== yn) return xn > yn;
80
+ } else if (x !== y) {
81
+ return x > y;
82
+ }
83
+ }
84
+ return true;
85
+ }
45
86
  function getDeepSeekVersion() {
46
87
  return new Promise((resolve) => {
47
88
  const proc = (0, import_child_process.spawn)("dsh", ["--version"], { stdio: "pipe", shell: true });
@@ -189,7 +230,9 @@ function killOrphanProcessesOnUnix(resolve, timer) {
189
230
  }
190
231
  // Annotate the CommonJS export names for ESM import in node:
191
232
  0 && (module.exports = {
233
+ MIN_DSH_VERSION,
192
234
  checkDeepSeekInstalled,
193
235
  getDeepSeekVersion,
236
+ isDeepSeekVersionAtLeast,
194
237
  killOrphanDeepSeekProcesses
195
238
  });
package/lib/system.d.ts CHANGED
@@ -1,3 +1,10 @@
1
1
  export declare function checkDeepSeekInstalled(): Promise<boolean>;
2
+ /** 本 provider 要求的 dsh 最低版本(0.1.2 起:browser-session 认证 + {args} Remote RPC + remote.mux,协议不向下兼容) */
3
+ export declare const MIN_DSH_VERSION = "0.1.2-rc.1";
4
+ /**
5
+ * 判定 version 是否 >= minimum(semver 风格,含 pre-release 比较:0.1.2 > 0.1.2-rc.1)。
6
+ * @returns true/false;任一版本无法解析时返回 null(调用方按"无法确认"放行)。
7
+ */
8
+ export declare function isDeepSeekVersionAtLeast(version: string, minimum?: string): boolean | null;
2
9
  export declare function getDeepSeekVersion(): Promise<string | null>;
3
10
  export declare function killOrphanDeepSeekProcesses(): Promise<number>;