@simonyea/holysheep-cli 2.1.76 → 2.1.78

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.
@@ -115,6 +115,34 @@ var require_claude_process_proxy = __commonJS({
115
115
  var ENABLE_TIMING_LOG = process.env.HS_CLAUDE_TIMING_LOG === "1";
116
116
  var SLOW_PATH_LOG_MS = Number(process.env.HS_CLAUDE_SLOW_PATH_LOG_MS) || 5e3;
117
117
  var ENABLE_LEASE_LOG = process.env.HS_CLAUDE_LEASE_LOG === "1";
118
+ function positiveInt(value, fallback) {
119
+ const n = Number(value);
120
+ return Number.isFinite(n) && n > 0 ? n : fallback;
121
+ }
122
+ __name(positiveInt, "positiveInt");
123
+ var LEASE_OPEN_TIMEOUT_MS = positiveInt(process.env.HS_CLAUDE_LEASE_OPEN_TIMEOUT_MS, 15e3);
124
+ var LEASE_CLOSE_TIMEOUT_MS = positiveInt(process.env.HS_CLAUDE_LEASE_CLOSE_TIMEOUT_MS, 5e3);
125
+ async function fetchWithTimeout(url, options, timeoutMs, label, consume = (response) => response) {
126
+ const controller = typeof AbortController === "function" ? new AbortController() : null;
127
+ const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
128
+ if (timer && typeof timer.unref === "function") timer.unref();
129
+ try {
130
+ const requestOptions = controller ? { ...options, signal: controller.signal } : { ...options, timeout: timeoutMs };
131
+ const response = await fetch(url, requestOptions);
132
+ return await consume(response);
133
+ } catch (err) {
134
+ const aborted = err && (err.name === "AbortError" || err.type === "aborted");
135
+ if (aborted) {
136
+ const timeoutErr = new Error(`${label} timed out after ${timeoutMs}ms`);
137
+ timeoutErr.code = "ETIMEDOUT";
138
+ throw timeoutErr;
139
+ }
140
+ throw err;
141
+ } finally {
142
+ if (timer) clearTimeout(timer);
143
+ }
144
+ }
145
+ __name(fetchWithTimeout, "fetchWithTimeout");
118
146
  function sanitizeUrl(value) {
119
147
  if (!value) return "";
120
148
  try {
@@ -201,19 +229,27 @@ var require_claude_process_proxy = __commonJS({
201
229
  const controlPlaneUrl = getControlPlaneUrl(config);
202
230
  if (!controlPlaneUrl) throw new Error("Claude relay control plane is not configured");
203
231
  const startedAt = Date.now();
204
- const response = await fetch(`${controlPlaneUrl}/session/open`, {
205
- method: "POST",
206
- headers: { "content-type": "application/json" },
207
- body: JSON.stringify({
208
- sessionId,
209
- bridgeId: config.bridgeId || "local-bridge",
210
- deviceId: config.deviceId || "",
211
- installSource: config.installSource || "holysheep-cli",
212
- proxyMode: "claude-process",
213
- forceReassign: options.forceReassign === true
232
+ const { response, payload } = await fetchWithTimeout(
233
+ `${controlPlaneUrl}/session/open`,
234
+ {
235
+ method: "POST",
236
+ headers: { "content-type": "application/json" },
237
+ body: JSON.stringify({
238
+ sessionId,
239
+ bridgeId: config.bridgeId || "local-bridge",
240
+ deviceId: config.deviceId || "",
241
+ installSource: config.installSource || "holysheep-cli",
242
+ proxyMode: "claude-process",
243
+ forceReassign: options.forceReassign === true
244
+ })
245
+ },
246
+ LEASE_OPEN_TIMEOUT_MS,
247
+ "Claude relay session open",
248
+ async (response2) => ({
249
+ response: response2,
250
+ payload: await response2.json().catch(() => null)
214
251
  })
215
- });
216
- const payload = await response.json().catch(() => null);
252
+ );
217
253
  if (!response.ok || !(payload == null ? void 0 : payload.success) || !((_a = payload == null ? void 0 : payload.data) == null ? void 0 : _a.ticket)) {
218
254
  throw new Error(((_b = payload == null ? void 0 : payload.error) == null ? void 0 : _b.message) || `Failed to open Claude session (HTTP ${response.status})`);
219
255
  }
@@ -302,7 +338,53 @@ var require_claude_process_proxy = __commonJS({
302
338
  })}`
303
339
  );
304
340
  }
305
- const upReq = https2.request({
341
+ let settled = false;
342
+ let responseTimer = null;
343
+ let stallTimer = null;
344
+ let upReq = null;
345
+ let upResRef = null;
346
+ const clearResponseTimer = /* @__PURE__ */ __name(() => {
347
+ if (responseTimer) {
348
+ clearTimeout(responseTimer);
349
+ responseTimer = null;
350
+ }
351
+ }, "clearResponseTimer");
352
+ const clearStallTimer = /* @__PURE__ */ __name(() => {
353
+ if (stallTimer) {
354
+ clearTimeout(stallTimer);
355
+ stallTimer = null;
356
+ }
357
+ }, "clearStallTimer");
358
+ const clearTimers = /* @__PURE__ */ __name(() => {
359
+ clearResponseTimer();
360
+ clearStallTimer();
361
+ }, "clearTimers");
362
+ const fail = /* @__PURE__ */ __name((error) => {
363
+ if (settled) return;
364
+ settled = true;
365
+ clearTimers();
366
+ const err = error instanceof Error ? error : new Error(String(error || "direct-https upstream error"));
367
+ if (upReq && !upReq.destroyed) upReq.destroy(err);
368
+ if (upResRef && !upResRef.destroyed) upResRef.destroy(err);
369
+ if (clientRes.headersSent && !clientRes.destroyed) clientRes.destroy(err);
370
+ reject(err);
371
+ }, "fail");
372
+ const finish = /* @__PURE__ */ __name(() => {
373
+ if (settled) return;
374
+ settled = true;
375
+ clearTimers();
376
+ resolve();
377
+ }, "finish");
378
+ const armResponseTimer = /* @__PURE__ */ __name(() => {
379
+ if (responseTimer || settled) return;
380
+ responseTimer = setTimeout(() => fail(new Error("direct-https upstream response timeout")), RESPONSE_TIMEOUT_MS);
381
+ }, "armResponseTimer");
382
+ const armStallTimer = /* @__PURE__ */ __name(() => {
383
+ if (settled) return;
384
+ clearStallTimer();
385
+ stallTimer = setTimeout(() => fail(new Error("direct-https upstream stream stalled")), STALL_TIMEOUT_MS);
386
+ }, "armStallTimer");
387
+ upReq = https2.request({
306
388
  hostname: target.hostname,
307
389
  port: target.port || 443,
308
390
  path: target.pathname + target.search,
@@ -310,15 +392,21 @@ var require_claude_process_proxy = __commonJS({
310
392
  headers,
311
393
  timeout: RESPONSE_TIMEOUT_MS + 5e3
312
394
  }, (upRes) => {
395
+ upResRef = upRes;
396
+ clearResponseTimer();
397
+ armStallTimer();
313
398
  clientRes.writeHead(upRes.statusCode, upRes.headers);
399
+ upRes.on("data", armStallTimer);
314
400
  upRes.pipe(clientRes);
315
- upRes.on("end", resolve);
316
- upRes.on("error", reject);
401
+ upRes.on("end", finish);
402
+ upRes.on("close", finish);
403
+ upRes.on("error", fail);
317
404
  });
318
- upReq.on("error", reject);
405
+ upReq.on("error", fail);
319
406
  upReq.on("timeout", () => {
320
- upReq.destroy(new Error("direct-https upstream timeout"));
407
+ fail(new Error("direct-https upstream timeout"));
321
408
  });
409
+ armResponseTimer();
322
410
  clientReq.pipe(upReq);
323
411
  });
324
412
  }
@@ -406,9 +494,9 @@ var require_claude_process_proxy = __commonJS({
406
494
  if (settled || sawUpstreamResponse || responseTimer) return;
407
495
  responseTimer = setTimeout(() => failWithAnthropicError("upstream response timeout"), RESPONSE_TIMEOUT_MS);
408
496
  }, "armResponseTimer");
409
- const armStallTimer = /* @__PURE__ */ __name(() => {
497
+ const armStallTimer = /* @__PURE__ */ __name((markData = true) => {
410
498
  if (settled) return;
411
- sawUpstreamData = true;
499
+ if (markData) sawUpstreamData = true;
412
500
  clearStallTimer();
413
501
  stallTimer = setTimeout(() => failWithAnthropicError("upstream stream stalled"), STALL_TIMEOUT_MS);
414
502
  }, "armStallTimer");
@@ -460,9 +548,15 @@ var require_claude_process_proxy = __commonJS({
460
548
  const status = forwardRes.statusCode || 502;
461
549
  if (status === 403 || status === 502 || status === 503) {
462
550
  const chunks = [];
463
- forwardRes.on("data", (c) => chunks.push(c));
551
+ let ended = false;
552
+ armStallTimer(false);
553
+ forwardRes.on("data", (c) => {
554
+ chunks.push(c);
555
+ armStallTimer(true);
556
+ });
464
557
  forwardRes.on("end", () => {
465
558
  if (settled) return;
559
+ ended = true;
466
560
  settled = true;
467
561
  clearTimers();
468
562
  const body = Buffer.concat(chunks).toString("utf8");
@@ -475,10 +569,14 @@ var require_claude_process_proxy = __commonJS({
475
569
  });
476
570
  reject(error);
477
571
  });
572
+ forwardRes.on("close", () => {
573
+ if (!ended && !settled) failWithAnthropicError("upstream error response closed before end");
574
+ });
478
575
  forwardRes.on("error", fail);
479
576
  return;
480
577
  }
481
578
  upstreamAssignedAt = upstreamAssignedAt || Date.now();
579
+ armStallTimer(false);
482
580
  clientRes.writeHead(status, forwardRes.headers);
483
581
  forwardRes.on("data", () => {
484
582
  if (!firstByteAt) {
@@ -777,7 +875,7 @@ var require_claude_process_proxy = __commonJS({
777
875
  forceReassign: shouldRefreshLeaseAfterError(err)
778
876
  });
779
877
  if (clientRes.headersSent) return;
780
- if (!isRetryableNodeLeaseError(err) && attempt > 0) break;
878
+ if (!isRetryableNodeLeaseError(err)) break;
781
879
  }
782
880
  }
783
881
  if (lastError && !clientRes.headersSent) {
@@ -786,7 +884,12 @@ var require_claude_process_proxy = __commonJS({
786
884
  if (isConnectionLevel) {
787
885
  try {
788
886
  const config = readConfig(configPath);
789
- const lease = getCachedLease(sessionId) || await fetchFreshLease(config, sessionId, {});
887
+ let lease;
888
+ try {
889
+ lease = getCachedLease(sessionId);
890
+ } catch {
891
+ lease = await fetchFreshLease(config, sessionId, {});
892
+ }
790
893
  logProxyTiming("request.direct-fallback", {
791
894
  sessionId,
792
895
  originalError: msg.slice(0, 160)
@@ -905,7 +1008,7 @@ var require_claude_process_proxy = __commonJS({
905
1008
  break;
906
1009
  } catch (err) {
907
1010
  lastError = err;
908
- if (!isRetryableNodeLeaseError(err) && attempt > 0) break;
1011
+ if (!isRetryableNodeLeaseError(err)) break;
909
1012
  }
910
1013
  }
911
1014
  if (lastError) {
@@ -968,14 +1071,19 @@ ${body}`);
968
1071
  const startedAt = Date.now();
969
1072
  let status = "unknown";
970
1073
  try {
971
- const response = await fetch(`${controlPlaneUrl}/session/close`, {
972
- method: "POST",
973
- headers: { "content-type": "application/json" },
974
- body: JSON.stringify({ sessionId })
975
- });
1074
+ const response = await fetchWithTimeout(
1075
+ `${controlPlaneUrl}/session/close`,
1076
+ {
1077
+ method: "POST",
1078
+ headers: { "content-type": "application/json" },
1079
+ body: JSON.stringify({ sessionId })
1080
+ },
1081
+ LEASE_CLOSE_TIMEOUT_MS,
1082
+ "Claude relay session close"
1083
+ );
976
1084
  status = response.ok ? "ok" : `http_${response.status}`;
977
1085
  } catch (err) {
978
- status = `err_${err.name || "unknown"}`;
1086
+ status = err && err.code === "ETIMEDOUT" ? "timeout" : `err_${err.name || "unknown"}`;
979
1087
  }
980
1088
  console.error(
981
1089
  `[hs-claude-proxy] lease.close ${JSON.stringify({
@@ -4382,7 +4490,7 @@ ${block}
4382
4490
  getConfigPath() {
4383
4491
  return CONFIG_FILE;
4384
4492
  },
4385
- hint: "CodeBuddy \u901A\u8FC7 ~/.codebuddy/.env \u8BFB\u53D6 CODEBUDDY_API_KEY / CODEBUDDY_BASE_URL\uFF08\u7B2C\u4E09\u65B9\u6A21\u5F0F\uFF09\uFF1B\u4E2D\u56FD\u7248/iOA \u7248\u7528\u6237\u8BF7\u81EA\u884C\u8BBE\u7F6E CODEBUDDY_INTERNET_ENVIRONMENT",
4493
+ hint: "CodeBuddy \u9ED8\u8BA4\u8D70 Tencent SSO\uFF08\u8981\u6C42\u4EA4\u4E92\u5F0F /login\uFF09\u3002HolySheep \u5DF2\u628A API Key/Base URL \u5199\u5230 ~/.codebuddy/.env \u4F5C\u4E3A\u7B2C\u4E09\u65B9\u6A21\u5F0F\u5907\u7528\uFF1B\u8981\u542F\u7528\u7B2C\u4E09\u65B9\u8DEF\u7531\uFF0C\u9700\u8981 export \u8FD9\u4E24\u4E2A\u53D8\u91CF\u8FDB\u5F53\u524D shell\uFF08CodeBuddy \u4E0D\u81EA\u52A8\u8BFB .env\uFF09\uFF1Aset -a; source ~/.codebuddy/.env; set +a; export CODEBUDDY_INTERNET_ENVIRONMENT=third_party\u3002CODEBUDDY_INTERNET_ENVIRONMENT \u6211\u4EEC\u6545\u610F\u4E0D\u5199\u5165 .env\uFF0C\u4EE5\u514D\u8986\u76D6\u4E2D\u56FD\u7248/iOA \u7248\u7528\u6237\u9009\u62E9\u3002",
4386
4494
  launchCmd: "codebuddy",
4387
4495
  installCmd: "npm install -g @tencent-ai/codebuddy-code",
4388
4496
  docsUrl: "https://www.codebuddy.ai/docs/cli/env-vars",
@@ -4404,11 +4512,11 @@ var require_package = __commonJS({
4404
4512
  "package.json"(exports2, module2) {
4405
4513
  module2.exports = {
4406
4514
  name: "@simonyea/holysheep-cli",
4407
- version: "2.1.76",
4515
+ version: "2.1.78",
4408
4516
  description: "Claude Code/Cursor/Cline API relay for China \u2014 \xA51=$1, WeChat/Alipay payment, no credit card, no VPN. One command setup for all AI coding tools.",
4409
4517
  scripts: {
4410
4518
  build: "node scripts/build.mjs",
4411
- test: "node tests/droid.test.js && node tests/workspace-store.test.js && node tests/runtime-stale-upgrade.test.js && node tests/hermes.test.js && node tests/preflight.test.js && node tests/opencode-auth-purge.test.js && node tests/shell-winpath.test.js && node tests/openclaw-atomic-write.test.js && node tests/openclaw-disable-auth-direct.test.js && node tests/opencode-default-model.test.js && node tests/paths-bundled.test.js && node tests/aionui-runtime-resources.test.js && node tests/aionui-wrapper-claude-proxy.test.js && node tests/aionui-wrapper-probe.test.js && node tests/aionui-wrapper-proxy-integration.test.js && node tests/aionui-wrapper-all-clis-autoconf.test.js && node tests/aionui-wrapper-env-signal.test.js && node tests/aionui-wrapper-csp-rewrite.test.js && node tests/aionui-wrapper-version-status.test.js && node tests/claude-proxy-daemon.test.js && node tests/claude-proxy-vscode-settings.test.js && node tests/claude-proxy-vscode-env.test.js && node tests/acptypes-patch.test.js && node tests/codex-approval-policy.test.js && node tests/version-check.test.js && node tests/runclaude-missing-binary.test.js && node tests/claude-code-configure-preserve.test.js && node tests/webui-claude-proxy-virtual-tool.test.js && node tests/webui-claude-proxy-ui.test.js && node tests/webui-response-consumer.test.js && node tests/webui-claude-proxy-install-gating.test.js && node tests/claude-code-windows-local-bin.test.js && node tests/qwen-code-config.test.js && node tests/codebuddy-config.test.js && node tests/hs-reset-routing.test.js",
4519
+ test: "node tests/droid.test.js && node tests/workspace-store.test.js && node tests/runtime-stale-upgrade.test.js && node tests/hermes.test.js && node tests/preflight.test.js && node tests/opencode-auth-purge.test.js && node tests/shell-winpath.test.js && node tests/openclaw-atomic-write.test.js && node tests/openclaw-disable-auth-direct.test.js && node tests/opencode-default-model.test.js && node tests/paths-bundled.test.js && node tests/aionui-runtime-resources.test.js && node tests/aionui-wrapper-claude-proxy.test.js && node tests/aionui-wrapper-probe.test.js && node tests/aionui-wrapper-proxy-integration.test.js && node tests/aionui-wrapper-all-clis-autoconf.test.js && node tests/aionui-wrapper-env-signal.test.js && node tests/aionui-wrapper-csp-rewrite.test.js && node tests/aionui-wrapper-version-status.test.js && node tests/claude-proxy-daemon.test.js && node tests/claude-proxy-vscode-settings.test.js && node tests/claude-proxy-vscode-env.test.js && node tests/acptypes-patch.test.js && node tests/codex-approval-policy.test.js && node tests/version-check.test.js && node tests/runclaude-missing-binary.test.js && node tests/claude-timeout-hardening.test.js && node tests/claude-code-configure-preserve.test.js && node tests/webui-claude-proxy-virtual-tool.test.js && node tests/webui-claude-proxy-ui.test.js && node tests/webui-response-consumer.test.js && node tests/webui-claude-proxy-install-gating.test.js && node tests/claude-code-windows-local-bin.test.js && node tests/qwen-code-config.test.js && node tests/codebuddy-config.test.js && node tests/hs-reset-routing.test.js && node tests/setup-auto-install-routing.test.js && node tests/upgrade-routing.test.js",
4412
4520
  prepublishOnly: "npm run build && npm test && node scripts/check-tarball-size.js"
4413
4521
  },
4414
4522
  keywords: [
package/dist/index.js CHANGED
@@ -12,11 +12,11 @@ var require_package = __commonJS({
12
12
  "package.json"(exports2, module2) {
13
13
  module2.exports = {
14
14
  name: "@simonyea/holysheep-cli",
15
- version: "2.1.76",
15
+ version: "2.1.78",
16
16
  description: "Claude Code/Cursor/Cline API relay for China \u2014 \xA51=$1, WeChat/Alipay payment, no credit card, no VPN. One command setup for all AI coding tools.",
17
17
  scripts: {
18
18
  build: "node scripts/build.mjs",
19
- test: "node tests/droid.test.js && node tests/workspace-store.test.js && node tests/runtime-stale-upgrade.test.js && node tests/hermes.test.js && node tests/preflight.test.js && node tests/opencode-auth-purge.test.js && node tests/shell-winpath.test.js && node tests/openclaw-atomic-write.test.js && node tests/openclaw-disable-auth-direct.test.js && node tests/opencode-default-model.test.js && node tests/paths-bundled.test.js && node tests/aionui-runtime-resources.test.js && node tests/aionui-wrapper-claude-proxy.test.js && node tests/aionui-wrapper-probe.test.js && node tests/aionui-wrapper-proxy-integration.test.js && node tests/aionui-wrapper-all-clis-autoconf.test.js && node tests/aionui-wrapper-env-signal.test.js && node tests/aionui-wrapper-csp-rewrite.test.js && node tests/aionui-wrapper-version-status.test.js && node tests/claude-proxy-daemon.test.js && node tests/claude-proxy-vscode-settings.test.js && node tests/claude-proxy-vscode-env.test.js && node tests/acptypes-patch.test.js && node tests/codex-approval-policy.test.js && node tests/version-check.test.js && node tests/runclaude-missing-binary.test.js && node tests/claude-code-configure-preserve.test.js && node tests/webui-claude-proxy-virtual-tool.test.js && node tests/webui-claude-proxy-ui.test.js && node tests/webui-response-consumer.test.js && node tests/webui-claude-proxy-install-gating.test.js && node tests/claude-code-windows-local-bin.test.js && node tests/qwen-code-config.test.js && node tests/codebuddy-config.test.js && node tests/hs-reset-routing.test.js",
19
+ test: "node tests/droid.test.js && node tests/workspace-store.test.js && node tests/runtime-stale-upgrade.test.js && node tests/hermes.test.js && node tests/preflight.test.js && node tests/opencode-auth-purge.test.js && node tests/shell-winpath.test.js && node tests/openclaw-atomic-write.test.js && node tests/openclaw-disable-auth-direct.test.js && node tests/opencode-default-model.test.js && node tests/paths-bundled.test.js && node tests/aionui-runtime-resources.test.js && node tests/aionui-wrapper-claude-proxy.test.js && node tests/aionui-wrapper-probe.test.js && node tests/aionui-wrapper-proxy-integration.test.js && node tests/aionui-wrapper-all-clis-autoconf.test.js && node tests/aionui-wrapper-env-signal.test.js && node tests/aionui-wrapper-csp-rewrite.test.js && node tests/aionui-wrapper-version-status.test.js && node tests/claude-proxy-daemon.test.js && node tests/claude-proxy-vscode-settings.test.js && node tests/claude-proxy-vscode-env.test.js && node tests/acptypes-patch.test.js && node tests/codex-approval-policy.test.js && node tests/version-check.test.js && node tests/runclaude-missing-binary.test.js && node tests/claude-timeout-hardening.test.js && node tests/claude-code-configure-preserve.test.js && node tests/webui-claude-proxy-virtual-tool.test.js && node tests/webui-claude-proxy-ui.test.js && node tests/webui-response-consumer.test.js && node tests/webui-claude-proxy-install-gating.test.js && node tests/claude-code-windows-local-bin.test.js && node tests/qwen-code-config.test.js && node tests/codebuddy-config.test.js && node tests/hs-reset-routing.test.js && node tests/setup-auto-install-routing.test.js && node tests/upgrade-routing.test.js",
20
20
  prepublishOnly: "npm run build && npm test && node scripts/check-tarball-size.js"
21
21
  },
22
22
  keywords: [
@@ -834,6 +834,34 @@ var require_claude_process_proxy = __commonJS({
834
834
  var ENABLE_TIMING_LOG = process.env.HS_CLAUDE_TIMING_LOG === "1";
835
835
  var SLOW_PATH_LOG_MS = Number(process.env.HS_CLAUDE_SLOW_PATH_LOG_MS) || 5e3;
836
836
  var ENABLE_LEASE_LOG = process.env.HS_CLAUDE_LEASE_LOG === "1";
837
+ function positiveInt(value, fallback) {
838
+ const n = Number(value);
839
+ return Number.isFinite(n) && n > 0 ? n : fallback;
840
+ }
841
+ __name(positiveInt, "positiveInt");
842
+ var LEASE_OPEN_TIMEOUT_MS = positiveInt(process.env.HS_CLAUDE_LEASE_OPEN_TIMEOUT_MS, 15e3);
843
+ var LEASE_CLOSE_TIMEOUT_MS = positiveInt(process.env.HS_CLAUDE_LEASE_CLOSE_TIMEOUT_MS, 5e3);
844
+ async function fetchWithTimeout(url, options, timeoutMs, label, consume = (response) => response) {
845
+ const controller = typeof AbortController === "function" ? new AbortController() : null;
846
+ const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
847
+ if (timer && typeof timer.unref === "function") timer.unref();
848
+ try {
849
+ const requestOptions = controller ? { ...options, signal: controller.signal } : { ...options, timeout: timeoutMs };
850
+ const response = await fetch(url, requestOptions);
851
+ return await consume(response);
852
+ } catch (err) {
853
+ const aborted = err && (err.name === "AbortError" || err.type === "aborted");
854
+ if (aborted) {
855
+ const timeoutErr = new Error(`${label} timed out after ${timeoutMs}ms`);
856
+ timeoutErr.code = "ETIMEDOUT";
857
+ throw timeoutErr;
858
+ }
859
+ throw err;
860
+ } finally {
861
+ if (timer) clearTimeout(timer);
862
+ }
863
+ }
864
+ __name(fetchWithTimeout, "fetchWithTimeout");
837
865
  function sanitizeUrl(value) {
838
866
  if (!value) return "";
839
867
  try {
@@ -920,19 +948,27 @@ var require_claude_process_proxy = __commonJS({
920
948
  const controlPlaneUrl = getControlPlaneUrl(config);
921
949
  if (!controlPlaneUrl) throw new Error("Claude relay control plane is not configured");
922
950
  const startedAt = Date.now();
923
- const response = await fetch(`${controlPlaneUrl}/session/open`, {
924
- method: "POST",
925
- headers: { "content-type": "application/json" },
926
- body: JSON.stringify({
927
- sessionId,
928
- bridgeId: config.bridgeId || "local-bridge",
929
- deviceId: config.deviceId || "",
930
- installSource: config.installSource || "holysheep-cli",
931
- proxyMode: "claude-process",
932
- forceReassign: options.forceReassign === true
951
+ const { response, payload } = await fetchWithTimeout(
952
+ `${controlPlaneUrl}/session/open`,
953
+ {
954
+ method: "POST",
955
+ headers: { "content-type": "application/json" },
956
+ body: JSON.stringify({
957
+ sessionId,
958
+ bridgeId: config.bridgeId || "local-bridge",
959
+ deviceId: config.deviceId || "",
960
+ installSource: config.installSource || "holysheep-cli",
961
+ proxyMode: "claude-process",
962
+ forceReassign: options.forceReassign === true
963
+ })
964
+ },
965
+ LEASE_OPEN_TIMEOUT_MS,
966
+ "Claude relay session open",
967
+ async (response2) => ({
968
+ response: response2,
969
+ payload: await response2.json().catch(() => null)
933
970
  })
934
- });
935
- const payload = await response.json().catch(() => null);
971
+ );
936
972
  if (!response.ok || !(payload == null ? void 0 : payload.success) || !((_a = payload == null ? void 0 : payload.data) == null ? void 0 : _a.ticket)) {
937
973
  throw new Error(((_b = payload == null ? void 0 : payload.error) == null ? void 0 : _b.message) || `Failed to open Claude session (HTTP ${response.status})`);
938
974
  }
@@ -1021,7 +1057,53 @@ var require_claude_process_proxy = __commonJS({
1021
1057
  })}`
1022
1058
  );
1023
1059
  }
1024
- const upReq = https2.request({
1060
+ let settled = false;
1061
+ let responseTimer = null;
1062
+ let stallTimer = null;
1063
+ let upReq = null;
1064
+ let upResRef = null;
1065
+ const clearResponseTimer = /* @__PURE__ */ __name(() => {
1066
+ if (responseTimer) {
1067
+ clearTimeout(responseTimer);
1068
+ responseTimer = null;
1069
+ }
1070
+ }, "clearResponseTimer");
1071
+ const clearStallTimer = /* @__PURE__ */ __name(() => {
1072
+ if (stallTimer) {
1073
+ clearTimeout(stallTimer);
1074
+ stallTimer = null;
1075
+ }
1076
+ }, "clearStallTimer");
1077
+ const clearTimers = /* @__PURE__ */ __name(() => {
1078
+ clearResponseTimer();
1079
+ clearStallTimer();
1080
+ }, "clearTimers");
1081
+ const fail = /* @__PURE__ */ __name((error) => {
1082
+ if (settled) return;
1083
+ settled = true;
1084
+ clearTimers();
1085
+ const err = error instanceof Error ? error : new Error(String(error || "direct-https upstream error"));
1086
+ if (upReq && !upReq.destroyed) upReq.destroy(err);
1087
+ if (upResRef && !upResRef.destroyed) upResRef.destroy(err);
1088
+ if (clientRes.headersSent && !clientRes.destroyed) clientRes.destroy(err);
1089
+ reject(err);
1090
+ }, "fail");
1091
+ const finish = /* @__PURE__ */ __name(() => {
1092
+ if (settled) return;
1093
+ settled = true;
1094
+ clearTimers();
1095
+ resolve();
1096
+ }, "finish");
1097
+ const armResponseTimer = /* @__PURE__ */ __name(() => {
1098
+ if (responseTimer || settled) return;
1099
+ responseTimer = setTimeout(() => fail(new Error("direct-https upstream response timeout")), RESPONSE_TIMEOUT_MS);
1100
+ }, "armResponseTimer");
1101
+ const armStallTimer = /* @__PURE__ */ __name(() => {
1102
+ if (settled) return;
1103
+ clearStallTimer();
1104
+ stallTimer = setTimeout(() => fail(new Error("direct-https upstream stream stalled")), STALL_TIMEOUT_MS);
1105
+ }, "armStallTimer");
1106
+ upReq = https2.request({
1025
1107
  hostname: target.hostname,
1026
1108
  port: target.port || 443,
1027
1109
  path: target.pathname + target.search,
@@ -1029,15 +1111,21 @@ var require_claude_process_proxy = __commonJS({
1029
1111
  headers,
1030
1112
  timeout: RESPONSE_TIMEOUT_MS + 5e3
1031
1113
  }, (upRes) => {
1114
+ upResRef = upRes;
1115
+ clearResponseTimer();
1116
+ armStallTimer();
1032
1117
  clientRes.writeHead(upRes.statusCode, upRes.headers);
1118
+ upRes.on("data", armStallTimer);
1033
1119
  upRes.pipe(clientRes);
1034
- upRes.on("end", resolve);
1035
- upRes.on("error", reject);
1120
+ upRes.on("end", finish);
1121
+ upRes.on("close", finish);
1122
+ upRes.on("error", fail);
1036
1123
  });
1037
- upReq.on("error", reject);
1124
+ upReq.on("error", fail);
1038
1125
  upReq.on("timeout", () => {
1039
- upReq.destroy(new Error("direct-https upstream timeout"));
1126
+ fail(new Error("direct-https upstream timeout"));
1040
1127
  });
1128
+ armResponseTimer();
1041
1129
  clientReq.pipe(upReq);
1042
1130
  });
1043
1131
  }
@@ -1125,9 +1213,9 @@ var require_claude_process_proxy = __commonJS({
1125
1213
  if (settled || sawUpstreamResponse || responseTimer) return;
1126
1214
  responseTimer = setTimeout(() => failWithAnthropicError("upstream response timeout"), RESPONSE_TIMEOUT_MS);
1127
1215
  }, "armResponseTimer");
1128
- const armStallTimer = /* @__PURE__ */ __name(() => {
1216
+ const armStallTimer = /* @__PURE__ */ __name((markData = true) => {
1129
1217
  if (settled) return;
1130
- sawUpstreamData = true;
1218
+ if (markData) sawUpstreamData = true;
1131
1219
  clearStallTimer();
1132
1220
  stallTimer = setTimeout(() => failWithAnthropicError("upstream stream stalled"), STALL_TIMEOUT_MS);
1133
1221
  }, "armStallTimer");
@@ -1179,9 +1267,15 @@ var require_claude_process_proxy = __commonJS({
1179
1267
  const status = forwardRes.statusCode || 502;
1180
1268
  if (status === 403 || status === 502 || status === 503) {
1181
1269
  const chunks = [];
1182
- forwardRes.on("data", (c) => chunks.push(c));
1270
+ let ended = false;
1271
+ armStallTimer(false);
1272
+ forwardRes.on("data", (c) => {
1273
+ chunks.push(c);
1274
+ armStallTimer(true);
1275
+ });
1183
1276
  forwardRes.on("end", () => {
1184
1277
  if (settled) return;
1278
+ ended = true;
1185
1279
  settled = true;
1186
1280
  clearTimers();
1187
1281
  const body = Buffer.concat(chunks).toString("utf8");
@@ -1194,10 +1288,14 @@ var require_claude_process_proxy = __commonJS({
1194
1288
  });
1195
1289
  reject(error);
1196
1290
  });
1291
+ forwardRes.on("close", () => {
1292
+ if (!ended && !settled) failWithAnthropicError("upstream error response closed before end");
1293
+ });
1197
1294
  forwardRes.on("error", fail);
1198
1295
  return;
1199
1296
  }
1200
1297
  upstreamAssignedAt = upstreamAssignedAt || Date.now();
1298
+ armStallTimer(false);
1201
1299
  clientRes.writeHead(status, forwardRes.headers);
1202
1300
  forwardRes.on("data", () => {
1203
1301
  if (!firstByteAt) {
@@ -1496,7 +1594,7 @@ var require_claude_process_proxy = __commonJS({
1496
1594
  forceReassign: shouldRefreshLeaseAfterError(err)
1497
1595
  });
1498
1596
  if (clientRes.headersSent) return;
1499
- if (!isRetryableNodeLeaseError(err) && attempt > 0) break;
1597
+ if (!isRetryableNodeLeaseError(err)) break;
1500
1598
  }
1501
1599
  }
1502
1600
  if (lastError && !clientRes.headersSent) {
@@ -1505,7 +1603,12 @@ var require_claude_process_proxy = __commonJS({
1505
1603
  if (isConnectionLevel) {
1506
1604
  try {
1507
1605
  const config = readConfig(configPath);
1508
- const lease = getCachedLease(sessionId) || await fetchFreshLease(config, sessionId, {});
1606
+ let lease;
1607
+ try {
1608
+ lease = getCachedLease(sessionId);
1609
+ } catch {
1610
+ lease = await fetchFreshLease(config, sessionId, {});
1611
+ }
1509
1612
  logProxyTiming("request.direct-fallback", {
1510
1613
  sessionId,
1511
1614
  originalError: msg.slice(0, 160)
@@ -1624,7 +1727,7 @@ var require_claude_process_proxy = __commonJS({
1624
1727
  break;
1625
1728
  } catch (err) {
1626
1729
  lastError = err;
1627
- if (!isRetryableNodeLeaseError(err) && attempt > 0) break;
1730
+ if (!isRetryableNodeLeaseError(err)) break;
1628
1731
  }
1629
1732
  }
1630
1733
  if (lastError) {
@@ -1687,14 +1790,19 @@ ${body}`);
1687
1790
  const startedAt = Date.now();
1688
1791
  let status = "unknown";
1689
1792
  try {
1690
- const response = await fetch(`${controlPlaneUrl}/session/close`, {
1691
- method: "POST",
1692
- headers: { "content-type": "application/json" },
1693
- body: JSON.stringify({ sessionId })
1694
- });
1793
+ const response = await fetchWithTimeout(
1794
+ `${controlPlaneUrl}/session/close`,
1795
+ {
1796
+ method: "POST",
1797
+ headers: { "content-type": "application/json" },
1798
+ body: JSON.stringify({ sessionId })
1799
+ },
1800
+ LEASE_CLOSE_TIMEOUT_MS,
1801
+ "Claude relay session close"
1802
+ );
1695
1803
  status = response.ok ? "ok" : `http_${response.status}`;
1696
1804
  } catch (err) {
1697
- status = `err_${err.name || "unknown"}`;
1805
+ status = err && err.code === "ETIMEDOUT" ? "timeout" : `err_${err.name || "unknown"}`;
1698
1806
  }
1699
1807
  console.error(
1700
1808
  `[hs-claude-proxy] lease.close ${JSON.stringify({
@@ -4950,7 +5058,7 @@ ${block}
4950
5058
  getConfigPath() {
4951
5059
  return CONFIG_FILE;
4952
5060
  },
4953
- hint: "CodeBuddy \u901A\u8FC7 ~/.codebuddy/.env \u8BFB\u53D6 CODEBUDDY_API_KEY / CODEBUDDY_BASE_URL\uFF08\u7B2C\u4E09\u65B9\u6A21\u5F0F\uFF09\uFF1B\u4E2D\u56FD\u7248/iOA \u7248\u7528\u6237\u8BF7\u81EA\u884C\u8BBE\u7F6E CODEBUDDY_INTERNET_ENVIRONMENT",
5061
+ hint: "CodeBuddy \u9ED8\u8BA4\u8D70 Tencent SSO\uFF08\u8981\u6C42\u4EA4\u4E92\u5F0F /login\uFF09\u3002HolySheep \u5DF2\u628A API Key/Base URL \u5199\u5230 ~/.codebuddy/.env \u4F5C\u4E3A\u7B2C\u4E09\u65B9\u6A21\u5F0F\u5907\u7528\uFF1B\u8981\u542F\u7528\u7B2C\u4E09\u65B9\u8DEF\u7531\uFF0C\u9700\u8981 export \u8FD9\u4E24\u4E2A\u53D8\u91CF\u8FDB\u5F53\u524D shell\uFF08CodeBuddy \u4E0D\u81EA\u52A8\u8BFB .env\uFF09\uFF1Aset -a; source ~/.codebuddy/.env; set +a; export CODEBUDDY_INTERNET_ENVIRONMENT=third_party\u3002CODEBUDDY_INTERNET_ENVIRONMENT \u6211\u4EEC\u6545\u610F\u4E0D\u5199\u5165 .env\uFF0C\u4EE5\u514D\u8986\u76D6\u4E2D\u56FD\u7248/iOA \u7248\u7528\u6237\u9009\u62E9\u3002",
4954
5062
  launchCmd: "codebuddy",
4955
5063
  installCmd: "npm install -g @tencent-ai/codebuddy-code",
4956
5064
  docsUrl: "https://www.codebuddy.ai/docs/cli/env-vars",
@@ -5231,7 +5339,13 @@ var require_setup = __commonJS({
5231
5339
  "gemini-cli": { cmd: "npm install -g @google/gemini-cli", mgr: "npm" },
5232
5340
  "opencode": { cmd: "npm install -g opencode-ai", mgr: "npm" },
5233
5341
  "openclaw": { cmd: "npm install -g openclaw@latest", mgr: "npm" },
5234
- "aider": { cmd: "pip install aider-chat", mgr: "pip" }
5342
+ "aider": { cmd: "pip install aider-chat", mgr: "pip" },
5343
+ // [v2.1.77] Qwen Code + CodeBuddy: hs setup install entry. Adapter
5344
+ // checkInstalled() is hardcoded true (npx fallback for AionUI), so the
5345
+ // setup flow needs to detect real PATH separately. canAutoInstall(t.id)
5346
+ // already keys off this dict — adding here is enough.
5347
+ "qwen-code": { cmd: "npm install -g @qwen-code/qwen-code", mgr: "npm" },
5348
+ "codebuddy": { cmd: "npm install -g @tencent-ai/codebuddy-code", mgr: "npm" }
5235
5349
  };
5236
5350
  function getWindowsImmediateLaunchCmd(tool) {
5237
5351
  if (process.platform !== "win32" || !(tool == null ? void 0 : tool.launchCmd)) return null;
@@ -5954,6 +6068,26 @@ var require_upgrade = __commonJS({
5954
6068
  versionCmd: "gemini --version",
5955
6069
  npmPkg: "@google/gemini-cli",
5956
6070
  installCmd: "npm install -g @google/gemini-cli@latest"
6071
+ },
6072
+ // [v2.1.77] Qwen Code: adapter checkInstalled() is hardcoded true (npx
6073
+ // fallback for AionUI), so we must NOT use that here — upgrade uses
6074
+ // commandExists(tool.command) below, which checks real PATH. Same for
6075
+ // CodeBuddy.
6076
+ {
6077
+ name: "Qwen Code",
6078
+ id: "qwen-code",
6079
+ command: "qwen",
6080
+ versionCmd: "qwen --version",
6081
+ npmPkg: "@qwen-code/qwen-code",
6082
+ installCmd: "npm install -g @qwen-code/qwen-code@latest"
6083
+ },
6084
+ {
6085
+ name: "CodeBuddy",
6086
+ id: "codebuddy",
6087
+ command: "codebuddy",
6088
+ versionCmd: "codebuddy --version",
6089
+ npmPkg: "@tencent-ai/codebuddy-code",
6090
+ installCmd: "npm install -g @tencent-ai/codebuddy-code@latest"
5957
6091
  }
5958
6092
  ];
5959
6093
  function getLocalVersion(tool) {
@@ -15,6 +15,15 @@ try {
15
15
  }
16
16
  var PROXY_HOST = parsed.hostname;
17
17
  var PROXY_PORT = Number(parsed.port) || 80;
18
+ function positiveInt(value, fallback) {
19
+ const n = Number(value);
20
+ return Number.isFinite(n) && n > 0 ? n : fallback;
21
+ }
22
+ __name(positiveInt, "positiveInt");
23
+ var CONNECT_TIMEOUT_MS = positiveInt(
24
+ process.env.HS_PROXY_CONNECT_TIMEOUT_MS || process.env.HS_CLAUDE_CONNECT_TIMEOUT_MS,
25
+ 15e3
26
+ );
18
27
  var SKIP = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "localhost", "0.0.0.0", PROXY_HOST]);
19
28
  var BLOCK = /* @__PURE__ */ new Set(["api.anthropic.com"]);
20
29
  function shouldProxy(host, port) {
@@ -30,16 +39,39 @@ function rewriteHost(host) {
30
39
  __name(rewriteHost, "rewriteHost");
31
40
  function setupTunnel(sock, host, port, origEmit) {
32
41
  const targetHost = rewriteHost(host);
33
- sock.write(`CONNECT ${targetHost}:${port} HTTP/1.1\r
34
- Host: ${targetHost}:${port}\r
35
- \r
36
- `);
37
- let buf = Buffer.alloc(0);
38
- sock.on("data", /* @__PURE__ */ __name(function onData(chunk) {
42
+ let settled = false;
43
+ let timer = null;
44
+ const cleanup = /* @__PURE__ */ __name(() => {
45
+ if (timer) {
46
+ clearTimeout(timer);
47
+ timer = null;
48
+ }
49
+ sock.removeListener("data", onData);
50
+ sock.removeListener("close", onClose);
51
+ sock.removeListener("error", onError);
52
+ }, "cleanup");
53
+ const fail = /* @__PURE__ */ __name((message) => {
54
+ if (settled) return;
55
+ settled = true;
56
+ cleanup();
57
+ sock.emit = origEmit;
58
+ sock.destroy(new Error(message));
59
+ }, "fail");
60
+ function onClose() {
61
+ cleanup();
62
+ }
63
+ __name(onClose, "onClose");
64
+ function onError() {
65
+ cleanup();
66
+ }
67
+ __name(onError, "onError");
68
+ function onData(chunk) {
39
69
  buf = Buffer.concat([buf, chunk]);
40
70
  const i = buf.indexOf("\r\n\r\n");
41
71
  if (i === -1) return;
42
- sock.removeListener("data", onData);
72
+ if (settled) return;
73
+ settled = true;
74
+ cleanup();
43
75
  if (!buf.slice(0, i).toString().includes(" 200 ")) {
44
76
  sock.emit = origEmit;
45
77
  sock.destroy(new Error(`CONNECT ${host}:${port} failed: ${buf.slice(0, buf.indexOf("\r\n")).toString()}`));
@@ -49,7 +81,22 @@ Host: ${targetHost}:${port}\r
49
81
  if (rest.length) sock.unshift(rest);
50
82
  sock.emit = origEmit;
51
83
  origEmit("connect");
52
- }, "onData"));
84
+ }
85
+ __name(onData, "onData");
86
+ if (CONNECT_TIMEOUT_MS > 0) {
87
+ timer = setTimeout(() => {
88
+ fail(`CONNECT ${host}:${port} timed out after ${CONNECT_TIMEOUT_MS}ms`);
89
+ }, CONNECT_TIMEOUT_MS);
90
+ if (typeof timer.unref === "function") timer.unref();
91
+ }
92
+ sock.once("close", onClose);
93
+ sock.once("error", onError);
94
+ sock.write(`CONNECT ${targetHost}:${port} HTTP/1.1\r
95
+ Host: ${targetHost}:${port}\r
96
+ \r
97
+ `);
98
+ let buf = Buffer.alloc(0);
99
+ sock.on("data", onData);
53
100
  }
54
101
  __name(setupTunnel, "setupTunnel");
55
102
  function patchEmitAndConnect(sock, host, port, connectFn) {
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@simonyea/holysheep-cli",
3
- "version": "2.1.76",
3
+ "version": "2.1.78",
4
4
  "description": "Claude Code/Cursor/Cline API relay for China — ¥1=$1, WeChat/Alipay payment, no credit card, no VPN. One command setup for all AI coding tools.",
5
5
  "scripts": {
6
6
  "build": "node scripts/build.mjs",
7
- "test": "node tests/droid.test.js && node tests/workspace-store.test.js && node tests/runtime-stale-upgrade.test.js && node tests/hermes.test.js && node tests/preflight.test.js && node tests/opencode-auth-purge.test.js && node tests/shell-winpath.test.js && node tests/openclaw-atomic-write.test.js && node tests/openclaw-disable-auth-direct.test.js && node tests/opencode-default-model.test.js && node tests/paths-bundled.test.js && node tests/aionui-runtime-resources.test.js && node tests/aionui-wrapper-claude-proxy.test.js && node tests/aionui-wrapper-probe.test.js && node tests/aionui-wrapper-proxy-integration.test.js && node tests/aionui-wrapper-all-clis-autoconf.test.js && node tests/aionui-wrapper-env-signal.test.js && node tests/aionui-wrapper-csp-rewrite.test.js && node tests/aionui-wrapper-version-status.test.js && node tests/claude-proxy-daemon.test.js && node tests/claude-proxy-vscode-settings.test.js && node tests/claude-proxy-vscode-env.test.js && node tests/acptypes-patch.test.js && node tests/codex-approval-policy.test.js && node tests/version-check.test.js && node tests/runclaude-missing-binary.test.js && node tests/claude-code-configure-preserve.test.js && node tests/webui-claude-proxy-virtual-tool.test.js && node tests/webui-claude-proxy-ui.test.js && node tests/webui-response-consumer.test.js && node tests/webui-claude-proxy-install-gating.test.js && node tests/claude-code-windows-local-bin.test.js && node tests/qwen-code-config.test.js && node tests/codebuddy-config.test.js && node tests/hs-reset-routing.test.js",
7
+ "test": "node tests/droid.test.js && node tests/workspace-store.test.js && node tests/runtime-stale-upgrade.test.js && node tests/hermes.test.js && node tests/preflight.test.js && node tests/opencode-auth-purge.test.js && node tests/shell-winpath.test.js && node tests/openclaw-atomic-write.test.js && node tests/openclaw-disable-auth-direct.test.js && node tests/opencode-default-model.test.js && node tests/paths-bundled.test.js && node tests/aionui-runtime-resources.test.js && node tests/aionui-wrapper-claude-proxy.test.js && node tests/aionui-wrapper-probe.test.js && node tests/aionui-wrapper-proxy-integration.test.js && node tests/aionui-wrapper-all-clis-autoconf.test.js && node tests/aionui-wrapper-env-signal.test.js && node tests/aionui-wrapper-csp-rewrite.test.js && node tests/aionui-wrapper-version-status.test.js && node tests/claude-proxy-daemon.test.js && node tests/claude-proxy-vscode-settings.test.js && node tests/claude-proxy-vscode-env.test.js && node tests/acptypes-patch.test.js && node tests/codex-approval-policy.test.js && node tests/version-check.test.js && node tests/runclaude-missing-binary.test.js && node tests/claude-timeout-hardening.test.js && node tests/claude-code-configure-preserve.test.js && node tests/webui-claude-proxy-virtual-tool.test.js && node tests/webui-claude-proxy-ui.test.js && node tests/webui-response-consumer.test.js && node tests/webui-claude-proxy-install-gating.test.js && node tests/claude-code-windows-local-bin.test.js && node tests/qwen-code-config.test.js && node tests/codebuddy-config.test.js && node tests/hs-reset-routing.test.js && node tests/setup-auto-install-routing.test.js && node tests/upgrade-routing.test.js",
8
8
  "prepublishOnly": "npm run build && npm test && node scripts/check-tarball-size.js"
9
9
  },
10
10
  "keywords": [