ai-fly 0.5.0-alpha.1 → 0.6.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/dist/ai-fly.js CHANGED
@@ -66,7 +66,7 @@ async function dispatch(command, rest) {
66
66
  const lazy = (load) => {
67
67
  return (argv, ctx) => load().then((m) => m.run([...argv], ctx));
68
68
  };
69
- return match(command).with("serve", () => lazy(() => import("./serve-B7CRdGoy.mjs"))(rest)).with("app", async () => {
69
+ return match(command).with("serve", () => lazy(() => import("./serve-DJ8id2Qo.mjs"))(rest)).with("app", async () => {
70
70
  const { join, dirname } = await import("node:path");
71
71
  const { fileURLToPath } = await import("node:url");
72
72
  const { spawn } = await import("node:child_process");
@@ -81,7 +81,7 @@ async function dispatch(command, rest) {
81
81
  return await new Promise((resolve) => {
82
82
  child.once("exit", (c) => resolve(c ?? 0));
83
83
  });
84
- }).with("daemon", () => lazy(() => import("./daemon-Oh-SQUeo.mjs"))(rest)).with("presets", () => lazy(() => import("./presets-BwkVV7E2.mjs"))(rest)).with("hooks", () => lazy(() => import("./hooks-BztmB61b.mjs"))(rest)).with("secret", () => lazy(() => import("./secret-BrfkuRkP.mjs"))(rest)).with("settings", async () => (await import("./settings-15j9BB6j.mjs")).runAsSettings(rest, {})).with("relay", async () => (await import("./settings-15j9BB6j.mjs")).runAsRelay(rest, {})).with("test", () => lazy(() => import("./test-ChqaPwHA.mjs"))(rest)).with("service", () => lazy(() => import("./service-P-vz61tx.mjs"))(rest)).with("group", () => lazy(() => import("./group-Dh64zhKn.mjs"))(rest)).with("share", () => lazy(() => import("./share-BogH4NQJ.mjs"))(rest)).with("revoke", () => lazy(() => import("./revoke-CQdhajpu.mjs"))(rest)).with("join", () => lazy(() => import("./join-CLD4fdCf.mjs"))(rest)).with("import", () => lazy(() => import("./import-DpW4ignw.mjs"))(rest)).with("run", () => lazy(() => import("./run-nJ_AEsMr.mjs"))(rest)).with("ports", () => lazy(() => import("./ports-DmBKgLSi.mjs"))(rest)).with("forget", () => lazy(() => import("./forget-DGJwF3Gy.mjs"))(rest)).with("services", () => lazy(() => import("./services-Deh7Gxwa.mjs"))(rest)).with("key", () => {
84
+ }).with("daemon", () => lazy(() => import("./daemon-D5J50r7U.mjs"))(rest)).with("presets", () => lazy(() => import("./presets-BwkVV7E2.mjs"))(rest)).with("hooks", () => lazy(() => import("./hooks-BztmB61b.mjs"))(rest)).with("secret", () => lazy(() => import("./secret-BrfkuRkP.mjs"))(rest)).with("settings", async () => (await import("./settings-15j9BB6j.mjs")).runAsSettings(rest, {})).with("relay", async () => (await import("./settings-15j9BB6j.mjs")).runAsRelay(rest, {})).with("test", () => lazy(() => import("./test-ChqaPwHA.mjs"))(rest)).with("service", () => lazy(() => import("./service-P-vz61tx.mjs"))(rest)).with("group", () => lazy(() => import("./group-Dh64zhKn.mjs"))(rest)).with("share", () => lazy(() => import("./share-mozydAfj.mjs"))(rest)).with("revoke", () => lazy(() => import("./revoke-DMx9hZUH.mjs"))(rest)).with("join", () => lazy(() => import("./join-CLD4fdCf.mjs"))(rest)).with("import", () => lazy(() => import("./import-DN8bNBJU.mjs"))(rest)).with("run", () => lazy(() => import("./run-C-CxjlLu.mjs"))(rest)).with("ports", () => lazy(() => import("./ports-DmBKgLSi.mjs"))(rest)).with("forget", () => lazy(() => import("./forget-DGJwF3Gy.mjs"))(rest)).with("services", () => lazy(() => import("./services-Deh7Gxwa.mjs"))(rest)).with("key", () => {
85
85
  if (rest[0] === "add") return lazy(() => import("./key-JLJewbw9.mjs"))(rest);
86
86
  return lazy(() => import("./key-Cpw-HzT4.mjs"))(rest);
87
87
  }).with("status", async () => {
@@ -90,8 +90,8 @@ async function dispatch(command, rest) {
90
90
  const wantConsumer = scope === "consumer" || scope !== "provider" && consumersRootExists();
91
91
  const argv = scope === "provider" || scope === "consumer" ? tail : rest;
92
92
  let code = 0;
93
- if (wantProvider) code = Math.max(code, await lazy(() => import("./status-VCGexKPW.mjs"))(argv));
94
- if (wantConsumer) code = Math.max(code, await lazy(() => import("./status-COzqwi-v.mjs"))(argv));
93
+ if (wantProvider) code = Math.max(code, await lazy(() => import("./status-DsFpGrzC.mjs"))(argv));
94
+ if (wantConsumer) code = Math.max(code, await lazy(() => import("./status-BlvQvxGW.mjs"))(argv));
95
95
  if (!wantProvider && !wantConsumer) process.stdout.write("nothing to report: no provider storage and no imported consumers\n");
96
96
  return code;
97
97
  }).otherwise(() => {
package/dist/app/main.js CHANGED
@@ -3775,6 +3775,7 @@ async function forwardHttp(ctx, plan, t) {
3775
3775
  /** js-backend-fetch 路径:连接期探测 + fetch(fetchImpl 测试注入缝保留于此)。 */
3776
3776
  const nativeResponse = async () => {
3777
3777
  const probe = ctx.probeConnect ?? defaultProbeConnect;
3778
+ if (ctrl.signal.aborted || ctx.signal.aborted) throw new ProbeFailedError();
3778
3779
  try {
3779
3780
  await probe(plan.url, t.connectMs);
3780
3781
  } catch {
@@ -3921,6 +3922,8 @@ async function forwardHttp(ctx, plan, t) {
3921
3922
  }
3922
3923
  //#endregion
3923
3924
  //#region src/provider/engine.ts
3925
+ /** per-peer 并发授权会话容量(LRU 逐出——内核暂无会话终结通知的防泄漏上限)。 */
3926
+ const SESSION_AUTH_CAP = 32;
3924
3927
  /** 错误码 → 载体响应(shared 映射表;null status(本地中止族)→ 504 语义)。 */
3925
3928
  function errorCarrierResponse(code, message) {
3926
3929
  const m = ERROR_HTTP_MAPPING[code] ?? {
@@ -4041,23 +4044,34 @@ var ProviderEngine = class {
4041
4044
  async refreshSession(ps) {
4042
4045
  if (!ps.authed()) return;
4043
4046
  const dir = this.directory();
4044
- const { valid } = evaluateKeyring(ps.keyring, dir);
4045
- if (valid.length === 0) {
4046
- ps.keyring = [];
4047
- ps.grants = [];
4047
+ let anyAlive = false;
4048
+ for (const sessionId of [...ps.sessionAuthIds()]) {
4049
+ const sa = ps.sessionAuthOf(sessionId);
4050
+ if (sa === void 0) continue;
4051
+ const { valid } = evaluateKeyring(sa.keyring, dir);
4052
+ if (valid.length === 0) {
4053
+ ps.dropSessionAuth(sessionId);
4054
+ continue;
4055
+ }
4056
+ anyAlive = true;
4057
+ const header = buildAuthOk(valid, dir, { refresh: true });
4058
+ const json = JSON.stringify(header);
4059
+ if (json === sa.lastView) {
4060
+ sa.grants = valid;
4061
+ continue;
4062
+ }
4063
+ this.catalogSeq++;
4064
+ sa.grants = valid;
4065
+ sa.lastView = json;
4066
+ ps.notifyWatch(sessionId, header);
4067
+ }
4068
+ if (!anyAlive) {
4048
4069
  this.keyIndex.untrack(ps);
4049
4070
  ps.notifyWatchInvalidated();
4050
4071
  this.fabric.disconnect(ps.peerId).catch(() => void 0);
4051
4072
  return;
4052
4073
  }
4053
- const header = buildAuthOk(valid, dir, { refresh: true });
4054
- const json = JSON.stringify(header);
4055
- if (json === ps.lastView) return;
4056
- this.catalogSeq++;
4057
- ps.grants = valid;
4058
- ps.lastView = json;
4059
4074
  this.keyIndex.track(ps);
4060
- ps.notifyWatch(header);
4061
4075
  }
4062
4076
  async addPeer(peerId) {
4063
4077
  if (this.stopped) return;
@@ -4104,12 +4118,14 @@ var ProviderPeerServer = class {
4104
4118
  disposed = false;
4105
4119
  active = /* @__PURE__ */ new Map();
4106
4120
  acquired = /* @__PURE__ */ new Map();
4107
- /** 最近一次 AUTH 呈交的钥环原文(仅内存;撤钥后重算用;MUST NOT 进日志)。 */
4108
- keyring = [];
4109
- grants = [];
4110
- lastView = "";
4111
- /** watch 长轮询等待者(目录变更时唤醒;授权失效以 "invalid" 唤醒 → 401)。 */
4112
- watchWaiters = /* @__PURE__ */ new Set();
4121
+ /**
4122
+ * session_id → 授权态(LRU;上限防泄漏——内核暂无「会话终结」通知,旧
4123
+ * session 条目由容量逐出。恢复轮 sessionId 稳定,逐出只发生在远超活跃
4124
+ * 会话数的场景)。
4125
+ */
4126
+ sessionAuths = /* @__PURE__ */ new Map();
4127
+ /** watch 长轮询等待者(目录变更按各自 session 视图唤醒;失效 → 401)。 */
4128
+ watchWaiters = /* @__PURE__ */ new Map();
4113
4129
  constructor(engine, peerId, fabric) {
4114
4130
  this.engine = engine;
4115
4131
  this.peerId = peerId;
@@ -4118,31 +4134,85 @@ var ProviderPeerServer = class {
4118
4134
  async start(serveHttpImpl) {
4119
4135
  this.server = await serveHttpImpl(this.fabric, this.peerId, (req) => this.handle(req));
4120
4136
  }
4137
+ /** 任一会话持有有效授权(peer 级观测)。 */
4121
4138
  authed() {
4122
- return this.grants.length > 0;
4139
+ for (const sa of this.sessionAuths.values()) if (sa.grants.length > 0) return true;
4140
+ return false;
4141
+ }
4142
+ /** 该会话的授权态(无 → undefined:未 AUTH/已失效/被逐出)。访问即提升
4143
+ * LRU 位次(Map 重插——访问序逐出,非仅插入序)。 */
4144
+ sessionAuthOf(sessionId) {
4145
+ const sa = this.sessionAuths.get(sessionId);
4146
+ if (sa !== void 0) {
4147
+ this.sessionAuths.delete(sessionId);
4148
+ this.sessionAuths.set(sessionId, sa);
4149
+ }
4150
+ return sa;
4151
+ }
4152
+ /** 会话 id 快照(engine.refreshSession 迭代用)。 */
4153
+ sessionAuthIds() {
4154
+ return [...this.sessionAuths.keys()];
4155
+ }
4156
+ /** 摘除会话授权态(refresh 裁剪路径——全钥失效的会话;watch 随之唤醒 401)。 */
4157
+ dropSessionAuth(sessionId) {
4158
+ this.sessionAuths.delete(sessionId);
4159
+ this.invalidateWatch(sessionId);
4160
+ }
4161
+ /** 写入该会话授权态(LRU 容量逐出最旧)。 */
4162
+ putSessionAuth(sessionId, keyring, grants) {
4163
+ this.sessionAuths.delete(sessionId);
4164
+ this.sessionAuths.set(sessionId, {
4165
+ keyring,
4166
+ grants,
4167
+ lastView: ""
4168
+ });
4169
+ while (this.sessionAuths.size > SESSION_AUTH_CAP) {
4170
+ const oldest = this.sessionAuths.keys().next().value;
4171
+ if (oldest === void 0) break;
4172
+ this.sessionAuths.delete(oldest);
4173
+ this.invalidateWatch(oldest);
4174
+ }
4123
4175
  }
4124
4176
  get keys() {
4125
- return this.keyring;
4177
+ const out = /* @__PURE__ */ new Set();
4178
+ for (const sa of this.sessionAuths.values()) for (const k of sa.keyring) out.add(k);
4179
+ return [...out];
4126
4180
  }
4127
4181
  get keyIds() {
4128
- return new Set(this.grants.map((g) => g.keyId));
4182
+ const out = /* @__PURE__ */ new Set();
4183
+ for (const sa of this.sessionAuths.values()) for (const g of sa.grants) out.add(g.keyId);
4184
+ return out;
4129
4185
  }
4130
4186
  /** 撤键/目录变更后的刷新:唤醒 watch 长轮询(全量视图;refresh 语义不变)。 */
4131
4187
  async pushRefresh(header) {
4132
- this.lastView = JSON.stringify(header);
4133
- this.notifyWatch(header);
4188
+ this.notifyWatchAll(header);
4134
4189
  }
4135
4190
  disconnect(reason) {
4136
4191
  this.disposeLocal(reason);
4137
4192
  }
4138
- /** 唤醒全部 watch 等待者(视图变更)。 */
4139
- notifyWatch(view) {
4140
- for (const wake of [...this.watchWaiters]) wake(view);
4193
+ /** 会话授权失效(drop/无效 AUTH/逐出):唤醒该会话全部 watch 等待者以
4194
+ * 401 收敛(5.2-P2——此前删集合不唤醒,在途长轮询干等到 20s 超时)。 */
4195
+ invalidateWatch(sessionId) {
4196
+ const waiters = this.watchWaiters.get(sessionId);
4197
+ if (waiters === void 0) return;
4198
+ for (const wake of [...waiters]) wake("invalid");
4199
+ this.watchWaiters.delete(sessionId);
4200
+ }
4201
+ /** 唤醒指定会话的 watch 等待者(该会话自己的刷新视图)。 */
4202
+ notifyWatch(sessionId, view) {
4203
+ const waiters = this.watchWaiters.get(sessionId);
4204
+ if (waiters === void 0) return;
4205
+ for (const wake of [...waiters]) wake(view);
4206
+ this.watchWaiters.delete(sessionId);
4207
+ }
4208
+ /** 唤醒全部 watch 等待者(键重加等 peer 级推送:并集视图,各会话自行重同步)。 */
4209
+ notifyWatchAll(view) {
4210
+ for (const waiters of [...this.watchWaiters.values()]) for (const wake of [...waiters]) wake(view);
4141
4211
  this.watchWaiters.clear();
4142
4212
  }
4143
4213
  /** 授权失效:唤醒等待者以 401 收敛(watch 循环退出,由 active 转换重启)。 */
4144
4214
  notifyWatchInvalidated() {
4145
- for (const wake of [...this.watchWaiters]) wake("invalid");
4215
+ for (const waiters of [...this.watchWaiters.values()]) for (const wake of [...waiters]) wake("invalid");
4146
4216
  this.watchWaiters.clear();
4147
4217
  }
4148
4218
  /** 本地善后(撤钥/引擎关闭):中止全部在途、释放限额、停 HTTP 引擎、断 fabric 会话。 */
@@ -4163,6 +4233,8 @@ var ProviderPeerServer = class {
4163
4233
  let result;
4164
4234
  if (barePath === "/_aifly/auth" && req.method === "POST") result = await this.handleAuth(req);
4165
4235
  else if (barePath === "/_aifly/catalog-watch" && req.method === "GET") result = await this.handleWatch(req);
4236
+ else if (barePath === "/_aifly/auth" || barePath === "/_aifly/catalog-watch") result = errorCarrierResponse(ERROR_CODE.forbidden_method, `method not allowed on control endpoint: ${req.method} ${barePath}`);
4237
+ else if (barePath.startsWith("/_aifly/")) result = errorCarrierResponse(ERROR_CODE.unauthorized, "unauthorized");
4166
4238
  else result = await this.handleForward(req);
4167
4239
  return result;
4168
4240
  }
@@ -4177,7 +4249,7 @@ var ProviderPeerServer = class {
4177
4249
  });
4178
4250
  return out;
4179
4251
  }
4180
- /** AUTH(重复 AUTH 以最后一次为准:钥环变更 = 重授权)。 */
4252
+ /** AUTH(重复 AUTH 以最后一次为准:钥环变更 = 重授权;授权按 session_id 隔离)。 */
4181
4253
  async handleAuth(req) {
4182
4254
  let parsedBody;
4183
4255
  try {
@@ -4190,18 +4262,16 @@ var ProviderPeerServer = class {
4190
4262
  if (!parsed.success) return errorCarrierResponse(ERROR_CODE.protocol_error, "auth body schema rejected");
4191
4263
  const decision = handleAuthFrame(parsed.data, this.engine.directory());
4192
4264
  if (decision.kind === "err") {
4193
- this.keyring = [];
4194
- this.grants = [];
4195
- this.engine.keySessionIndex().untrack(this);
4265
+ this.sessionAuths.delete(req.sessionId);
4266
+ this.invalidateWatch(req.sessionId);
4267
+ if (!this.authed()) this.engine.keySessionIndex().untrack(this);
4196
4268
  return {
4197
4269
  status: 403,
4198
4270
  headers: this.jsonHeaders(),
4199
4271
  bodyChunks: [Buffer.from(JSON.stringify(decision.header))]
4200
4272
  };
4201
4273
  }
4202
- this.keyring = decision.keys;
4203
- this.grants = decision.valid;
4204
- this.lastView = JSON.stringify(decision.header);
4274
+ this.putSessionAuth(req.sessionId, decision.keys, decision.valid);
4205
4275
  this.engine.keySessionIndex().track(this);
4206
4276
  return {
4207
4277
  status: 200,
@@ -4211,17 +4281,18 @@ var ProviderPeerServer = class {
4211
4281
  }
4212
4282
  /** 目录刷新长轮询:since 落后即时回;未变更挂起至视图变更(200)或超时(204)。 */
4213
4283
  async handleWatch(req) {
4214
- if (!this.authed()) return errorCarrierResponse(ERROR_CODE.unauthorized, "session is not authenticated");
4284
+ const sa = this.sessionAuthOf(req.sessionId);
4285
+ if (sa === void 0 || sa.grants.length === 0) return errorCarrierResponse(ERROR_CODE.unauthorized, "session is not authenticated");
4215
4286
  const query = req.path.split("?", 2)[1] ?? "";
4216
4287
  const sinceRaw = Number(new URLSearchParams(query).get("since") ?? "0");
4217
4288
  const since = Number.isFinite(sinceRaw) && sinceRaw >= 0 ? Math.floor(sinceRaw) : 0;
4218
4289
  const current = this.engine.currentCatalogSeq();
4219
4290
  if (since < current) {
4220
4291
  const dir = this.engine.directory();
4221
- const { valid } = evaluateKeyring(this.keyring, dir);
4292
+ const { valid } = evaluateKeyring(sa.keyring, dir);
4222
4293
  if (valid.length === 0) return errorCarrierResponse(ERROR_CODE.unauthorized, "no valid key remains");
4223
4294
  const header = buildAuthOk(valid, dir, { refresh: true });
4224
- this.grants = valid;
4295
+ sa.grants = valid;
4225
4296
  return {
4226
4297
  status: 200,
4227
4298
  headers: this.jsonHeaders({ "x-aifly-catalog-seq": String(current) }),
@@ -4230,16 +4301,27 @@ var ProviderPeerServer = class {
4230
4301
  }
4231
4302
  const changed = await new Promise((resolve) => {
4232
4303
  const timer = setTimeout(() => {
4233
- this.watchWaiters.delete(wake);
4304
+ let waiters = this.watchWaiters.get(req.sessionId);
4305
+ waiters?.delete(wake);
4306
+ if (waiters !== void 0 && waiters.size === 0) this.watchWaiters.delete(req.sessionId);
4234
4307
  resolve(null);
4235
4308
  }, CATALOG_WATCH_TIMEOUT_MS);
4236
4309
  const wake = (view) => {
4237
4310
  clearTimeout(timer);
4238
4311
  resolve(view);
4239
4312
  };
4240
- this.watchWaiters.add(wake);
4313
+ let waiters = this.watchWaiters.get(req.sessionId);
4314
+ if (waiters === void 0) {
4315
+ waiters = /* @__PURE__ */ new Set();
4316
+ this.watchWaiters.set(req.sessionId, waiters);
4317
+ }
4318
+ waiters.add(wake);
4241
4319
  });
4242
4320
  if (changed === "invalid") return errorCarrierResponse(ERROR_CODE.unauthorized, "authorization invalidated");
4321
+ if (changed !== null) {
4322
+ const saAfter = this.sessionAuthOf(req.sessionId);
4323
+ if (saAfter === void 0 || saAfter.grants.length === 0) return errorCarrierResponse(ERROR_CODE.unauthorized, "session is not authenticated");
4324
+ }
4243
4325
  if (changed === null) return {
4244
4326
  status: 204,
4245
4327
  headers: [{
@@ -4254,13 +4336,15 @@ var ProviderPeerServer = class {
4254
4336
  };
4255
4337
  }
4256
4338
  async handleForward(req) {
4257
- if (!this.authed()) return errorCarrierResponse(ERROR_CODE.unauthorized, "session is not authenticated");
4339
+ const sa = this.sessionAuthOf(req.sessionId);
4340
+ if (sa === void 0 || sa.grants.length === 0) return errorCarrierResponse(ERROR_CODE.unauthorized, "session is not authenticated");
4341
+ if (req.signal.aborted) return errorCarrierResponse(ERROR_CODE.aborted, "request cancelled before dispatch");
4258
4342
  const serviceHeader = req.headers.find((h) => h.name === AIFLY_SERVICE_HEADER)?.value;
4259
4343
  if (serviceHeader === void 0) return errorCarrierResponse(ERROR_CODE.protocol_error, `missing ${AIFLY_SERVICE_HEADER} header`);
4260
4344
  const method = req.method.toUpperCase();
4261
4345
  if (!HTTP_METHODS.includes(method)) return errorCarrierResponse(ERROR_CODE.forbidden_method, `http method not allowed: ${method}`);
4262
4346
  const service = this.engine.store.getService(serviceHeader);
4263
- const grant = service === void 0 ? void 0 : this.grants.find((g) => this.engine.groupHasService(g.group, serviceHeader));
4347
+ const grant = service === void 0 ? void 0 : sa.grants.find((g) => this.engine.groupHasService(g.group, serviceHeader));
4264
4348
  if (service === void 0 || service.enabled === false || grant === void 0) return errorCarrierResponse(ERROR_CODE.unknown_service, "service not available");
4265
4349
  const acquired = this.engine.limits.acquire(grant.keyId, grant.group);
4266
4350
  if (!acquired.ok) return errorCarrierResponse(acquired.code, acquired.code === "rate_limited" ? "group concurrency limit reached" : "daily request quota reached");
@@ -4299,6 +4383,11 @@ var ProviderPeerServer = class {
4299
4383
  };
4300
4384
  const outcome = new CarrierOutcome((status, headers) => req.respondStreaming(status, headers));
4301
4385
  const ctrl = new AbortController();
4386
+ const abortUpstream = () => {
4387
+ ctrl.abort(new UpstreamAbortError(ERROR_CODE.aborted, true));
4388
+ };
4389
+ req.signal.addEventListener("abort", abortUpstream, { once: true });
4390
+ if (req.signal.aborted) abortUpstream();
4302
4391
  const act = {
4303
4392
  ctrl,
4304
4393
  keyId: grant.keyId,
@@ -5144,6 +5233,10 @@ function removeKeyring(root, ref) {
5144
5233
  }
5145
5234
  };
5146
5235
  }
5236
+ //#endregion
5237
+ //#region src/consumer/providers.ts
5238
+ /** fetchHttp head 等待期取消键序号(per 进程单调;abortKey 注册表关联)。 */
5239
+ let fetchAbortKeySeq = 0;
5147
5240
  /**
5148
5241
  * full jitter:delay = random() * min(cap, base * 2^attempt)。
5149
5242
  * attempt 从 0 起(首次重连窗口 [0, base]);封顶后窗口恒为 [0, cap]。
@@ -5314,6 +5407,7 @@ var ProviderConnection = class {
5314
5407
  });
5315
5408
  let aborted = false;
5316
5409
  let tunnel = null;
5410
+ const abortKey = ++fetchAbortKeySeq;
5317
5411
  (async () => {
5318
5412
  try {
5319
5413
  const resp = await session.fetchHttp({
@@ -5321,7 +5415,8 @@ var ProviderConnection = class {
5321
5415
  path: input.path,
5322
5416
  headers,
5323
5417
  ...input.body.length > 0 ? { body: [input.body] } : {},
5324
- ...input.upgrade ? { keepOpen: true } : {}
5418
+ ...input.upgrade ? { keepOpen: true } : {},
5419
+ abortKey
5325
5420
  });
5326
5421
  if (aborted) {
5327
5422
  try {
@@ -5398,6 +5493,9 @@ var ProviderConnection = class {
5398
5493
  id,
5399
5494
  abort: () => {
5400
5495
  aborted = true;
5496
+ try {
5497
+ session.abortFetch?.(abortKey);
5498
+ } catch {}
5401
5499
  try {
5402
5500
  tunnel?.abort?.();
5403
5501
  } catch {}
@@ -99,7 +99,7 @@ async function start(startArgv, home) {
99
99
  process.on("exit", () => {
100
100
  clearPid(home);
101
101
  });
102
- const { run: serveRun } = await import("./serve-B7CRdGoy.mjs");
102
+ const { run: serveRun } = await import("./serve-DJ8id2Qo.mjs");
103
103
  return serveRun([...startArgv], { homedir: home });
104
104
  }
105
105
  /** 后台 pid(活性核验;陈旧 pidfile 就地清理返回 null)。 */
@@ -3,7 +3,7 @@ import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
3
  import { n as consumersRoot, s as listKeyrings } from "./store-Bu4I1rNj.mjs";
4
4
  import { a as ringsForRun, i as printListeners, n as ctxHomedir, o as waitForSignals, r as ctxOut, t as createSdkFabricFactory } from "./common-9KRvrs5f.mjs";
5
5
  import { a as importLink, i as formatLinkPreview, n as decodeShareLink, r as formatKeyringSummary } from "./join-D5QUz8mh.mjs";
6
- import { n as createFabricSessionFactory, t as startEngine } from "./runtime-D9rSByOO.mjs";
6
+ import { n as createFabricSessionFactory, t as startEngine } from "./runtime-DKmrkfNT.mjs";
7
7
  import { homedir } from "node:os";
8
8
  //#region src/cli/commands/consumer/import.ts
9
9
  async function run(argv, ctx = {}) {
@@ -2,7 +2,7 @@ import { n as UsageError, r as reportCliError } from "./errors-BGtxPkM7.mjs";
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
3
  import { t as resolveHttpProxy } from "./proxy-D7VMUVEg.mjs";
4
4
  import { d as resolvedRelayUrls, f as str, u as resolveDataDir } from "./common-DqSyhR6F.mjs";
5
- import { t as openExistingFabric } from "./serve-BlWkrYrl.mjs";
5
+ import { t as openExistingFabric } from "./serve-BB6-4Ixd.mjs";
6
6
  import { homedir } from "node:os";
7
7
  //#region src/cli/commands/provider/revoke.ts
8
8
  const SPEC = {
@@ -2,7 +2,7 @@ import { n as UsageError, r as reportCliError, t as CliError } from "./errors-BG
2
2
  import { n as parseArgv } from "./args-CSxXW9eD.mjs";
3
3
  import { c as loadKeyring, n as consumersRoot, o as keyringDir, s as listKeyrings } from "./store-Bu4I1rNj.mjs";
4
4
  import { a as ringsForRun, i as printListeners, n as ctxHomedir, o as waitForSignals, r as ctxOut, t as createSdkFabricFactory } from "./common-9KRvrs5f.mjs";
5
- import { n as createFabricSessionFactory, t as startEngine } from "./runtime-D9rSByOO.mjs";
5
+ import { n as createFabricSessionFactory, t as startEngine } from "./runtime-DKmrkfNT.mjs";
6
6
  import { a as killHard, c as terminateGracefully, i as isAlive, l as writeLastStart, n as daemonDir, o as readLastStart, r as ensureDaemonDir, s as readPid, t as clearPid, u as writePid } from "./daemon-state-CYkiW3SA.mjs";
7
7
  import { openSync, readFileSync, watch } from "node:fs";
8
8
  import { resolve } from "node:path";
@@ -5,6 +5,9 @@ import { n as desiredPortFor, r as listenWithFallback, t as LOOPBACK_HOST } from
5
5
  import { Hono } from "hono";
6
6
  import { createAdaptorServer } from "@hono/node-server";
7
7
  import { WebSocketServer } from "ws";
8
+ //#region src/consumer/providers.ts
9
+ /** fetchHttp head 等待期取消键序号(per 进程单调;abortKey 注册表关联)。 */
10
+ let fetchAbortKeySeq = 0;
8
11
  /**
9
12
  * full jitter:delay = random() * min(cap, base * 2^attempt)。
10
13
  * attempt 从 0 起(首次重连窗口 [0, base]);封顶后窗口恒为 [0, cap]。
@@ -175,6 +178,7 @@ var ProviderConnection = class {
175
178
  });
176
179
  let aborted = false;
177
180
  let tunnel = null;
181
+ const abortKey = ++fetchAbortKeySeq;
178
182
  (async () => {
179
183
  try {
180
184
  const resp = await session.fetchHttp({
@@ -182,7 +186,8 @@ var ProviderConnection = class {
182
186
  path: input.path,
183
187
  headers,
184
188
  ...input.body.length > 0 ? { body: [input.body] } : {},
185
- ...input.upgrade ? { keepOpen: true } : {}
189
+ ...input.upgrade ? { keepOpen: true } : {},
190
+ abortKey
186
191
  });
187
192
  if (aborted) {
188
193
  try {
@@ -259,6 +264,9 @@ var ProviderConnection = class {
259
264
  id,
260
265
  abort: () => {
261
266
  aborted = true;
267
+ try {
268
+ session.abortFetch?.(abortKey);
269
+ } catch {}
262
270
  try {
263
271
  tunnel?.abort?.();
264
272
  } catch {}