dsh-mobile 0.4.4 → 0.4.5

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.mts CHANGED
@@ -1422,6 +1422,15 @@ declare class TaskEventHub {
1422
1422
  }
1423
1423
  //#endregion
1424
1424
  //#region src/plugin.d.ts
1425
+ declare module '@deepseek-ai/dsh-llm' {
1426
+ interface MessageSourceMap {
1427
+ 'plugin:dsh-mobile': {
1428
+ kind: 'plugin:dsh-mobile';
1429
+ form: 'notice';
1430
+ summary: string;
1431
+ };
1432
+ }
1433
+ }
1425
1434
  /** Stable Cordis plugin name. */
1426
1435
  declare const name = "dsh-mobile";
1427
1436
  /** The stock WebServer serves the control card; Connection authenticates the loopback DSH origin. */
package/lib/index.mjs CHANGED
@@ -2451,6 +2451,16 @@ const MOBILE_BOOT_SIZE_PROBE_HEADER = "x-dsh-mobile-size-probe";
2451
2451
  const MAX_MOBILE_BOOT_BATCHES = 8;
2452
2452
  const MOBILE_BOOT_UPSTREAM_ATTEMPTS = 4;
2453
2453
  const MOBILE_BOOT_RETRY_DELAY_MS = 150;
2454
+ function upstreamPluginBundleUrl(source, upstreamOrigin) {
2455
+ if (!source.startsWith("/plugins/") && !source.startsWith("plugins/") || source.includes("#")) return void 0;
2456
+ try {
2457
+ const target = new URL(source, new URL("/", upstreamOrigin));
2458
+ if (target.origin !== upstreamOrigin.origin || !target.pathname.startsWith("/plugins/")) return void 0;
2459
+ return target;
2460
+ } catch {
2461
+ return;
2462
+ }
2463
+ }
2454
2464
  const TRANSIENT_UPSTREAM_ERROR_CODES = /* @__PURE__ */ new Set([
2455
2465
  "EAI_AGAIN",
2456
2466
  "ECONNABORTED",
@@ -2870,8 +2880,9 @@ function shouldCompressResponse(request, response) {
2870
2880
  const pathname = request.url?.split("?", 1)[0] ?? "";
2871
2881
  return (request.method === "GET" && (pathname.startsWith("/plugins/") || pathname.startsWith("/assets/")) || request.method === "POST" && pathname === SESSION_HISTORY_PATH) && response.statusCode === 200 && request.headers.range === void 0 && response.headers["content-range"] === void 0 && response.headers["content-encoding"] === void 0 && acceptsGzip(request.headers["accept-encoding"]) && isCompressibleContentType(response.headers["content-type"]);
2872
2882
  }
2873
- function revisionedStaticCacheControl(request) {
2883
+ function revisionedStaticCacheControl(request, statusCode) {
2874
2884
  if (request.method !== "GET" && request.method !== "HEAD") return void 0;
2885
+ if (statusCode !== 200) return void 0;
2875
2886
  let target;
2876
2887
  try {
2877
2888
  target = new URL(request.url ?? "/", "https://dsh-mobile.invalid");
@@ -2991,6 +3002,12 @@ function discoveryBroadcastTargets(cidrs) {
2991
3002
  }
2992
3003
  return [...targets];
2993
3004
  }
3005
+ /** Keep Node's upgrade-owned socket safe after the HTTP parser removes its listener. */
3006
+ function guardUpgradeSocket(socket) {
3007
+ socket.on("error", () => {
3008
+ if (!socket.destroyed) socket.destroy();
3009
+ });
3010
+ }
2994
3011
  function discoveryFailure(error) {
2995
3012
  const code = error instanceof Error && typeof error.code === "string" ? error.code : "unknown";
2996
3013
  return Object.freeze({
@@ -3225,9 +3242,11 @@ var MobileAccessGateway = class {
3225
3242
  socket.destroy();
3226
3243
  });
3227
3244
  server.on("upgrade", (request, socket, head) => {
3228
- this.handleUpgrade(request, socket, head).catch((error) => {
3245
+ const client = socket;
3246
+ guardUpgradeSocket(client);
3247
+ this.handleUpgrade(request, client, head).catch((error) => {
3229
3248
  const mapped = mapError(error);
3230
- rejectUpgrade(socket, mapped.status, mapped.code);
3249
+ rejectUpgrade(client, mapped.status, mapped.code);
3231
3250
  });
3232
3251
  });
3233
3252
  server.on("clientError", (_error, socket) => {
@@ -4118,7 +4137,7 @@ var MobileAccessGateway = class {
4118
4137
  async resolveMobileBootSizes(planEntries) {
4119
4138
  const sizes = /* @__PURE__ */ new Map();
4120
4139
  const passThrough = /* @__PURE__ */ new Set();
4121
- const candidates = planEntries.filter((entry) => entry.id !== MOBILE_LAYOUT_MODULE && entry.url.startsWith("/plugins/"));
4140
+ const candidates = planEntries.filter((entry) => entry.id !== MOBILE_LAYOUT_MODULE && upstreamPluginBundleUrl(entry.url, this.config.upstreamOrigin) !== void 0);
4122
4141
  let cursor = 0;
4123
4142
  const worker = async () => {
4124
4143
  while (cursor < candidates.length) {
@@ -4142,11 +4161,10 @@ var MobileAccessGateway = class {
4142
4161
  * cached for a short window.
4143
4162
  */
4144
4163
  async upstreamBundleSize(source) {
4145
- if (!source.startsWith("/plugins/") || source.includes("#")) return void 0;
4164
+ const target = upstreamPluginBundleUrl(source, this.config.upstreamOrigin);
4165
+ if (target === void 0) return void 0;
4146
4166
  const cached = this.bootEntrySizeCache.get(source);
4147
4167
  if (cached !== void 0 && Date.now() - cached.at < MOBILE_BOOT_SIZE_CACHE_TTL_MS) return cached.size;
4148
- const target = new URL(source, this.config.upstreamOrigin);
4149
- if (target.origin !== this.config.upstreamOrigin.origin) return void 0;
4150
4168
  const upstreamCookie = await this.upstreamCookieHeader();
4151
4169
  let upstreamRequest;
4152
4170
  try {
@@ -4312,9 +4330,8 @@ var MobileAccessGateway = class {
4312
4330
  }
4313
4331
  async readUpstreamClientBundle(source, signal) {
4314
4332
  signal.throwIfAborted();
4315
- if (!source.startsWith("/plugins/") || source.includes("#")) throw new HttpError(502, "upstream_unavailable");
4316
- const target = new URL(source, this.config.upstreamOrigin);
4317
- if (target.origin !== this.config.upstreamOrigin.origin) throw new HttpError(502, "upstream_unavailable");
4333
+ const target = upstreamPluginBundleUrl(source, this.config.upstreamOrigin);
4334
+ if (target === void 0) throw new HttpError(502, "upstream_unavailable");
4318
4335
  let upstreamRequest;
4319
4336
  const aborted = () => {
4320
4337
  upstreamRequest?.destroy(/* @__PURE__ */ new Error("request aborted"));
@@ -4460,7 +4477,8 @@ var MobileAccessGateway = class {
4460
4477
  });
4461
4478
  setSecurityHeaders(response, this.tlsEnabled, "proxied");
4462
4479
  const headers = sanitizeResponseHeaders(proxied.headers, this.config.upstreamOrigin);
4463
- const cacheControl = revisionedStaticCacheControl(request$2);
4480
+ const statusCode = proxied.statusCode ?? 502;
4481
+ const cacheControl = revisionedStaticCacheControl(request$2, statusCode);
4464
4482
  if (cacheControl !== void 0) headers["cache-control"] = cacheControl;
4465
4483
  const compressed = shouldCompressResponse(request$2, proxied);
4466
4484
  if (compressed) {
@@ -4470,7 +4488,7 @@ var MobileAccessGateway = class {
4470
4488
  headers["content-encoding"] = "gzip";
4471
4489
  addVaryAcceptEncoding(headers);
4472
4490
  }
4473
- response.writeHead(proxied.statusCode ?? 502, headers);
4491
+ response.writeHead(statusCode, headers);
4474
4492
  await Promise.all([bodyDone, compressed ? pipeline(proxied, createGzip(), response) : pipeline(proxied, response)]);
4475
4493
  } catch (error) {
4476
4494
  holder.request?.destroy();
@@ -4680,6 +4698,7 @@ var MobileAccessGateway = class {
4680
4698
  const authorization = this.authorize(request);
4681
4699
  if (this.activeWebSockets.size >= this.config.maxWebSockets) throw new HttpError(429, "busy");
4682
4700
  const upstreamCookie = await this.upstreamCookieHeader();
4701
+ if (client.destroyed) return;
4683
4702
  const upstream = connect({
4684
4703
  host: stripIpv6Brackets(this.config.upstreamOrigin.hostname),
4685
4704
  port: Number(this.config.upstreamOrigin.port)
@@ -11926,8 +11945,7 @@ async function apply(ctx, config) {
11926
11945
  text: `${guide}\n\n用户需求:${task}`
11927
11946
  }],
11928
11947
  source: {
11929
- kind: "plugin",
11930
- plugin: "dsh-mobile",
11948
+ kind: "plugin:dsh-mobile",
11931
11949
  form: "notice",
11932
11950
  summary: boundContextSummary(`/mobile ${task}`)
11933
11951
  }