@wrongstack/webui-server 0.310.0 → 0.310.1

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/index.js CHANGED
@@ -97,7 +97,12 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
97
97
  // `prefs.update` exactly so the panel does not error with
98
98
  // "unknown preference key".
99
99
  "readSymbols",
100
- "showSageMemoryInject"
100
+ "showSageMemoryInject",
101
+ // WrongProxy / WrongTrace master switch. When on, the CLI rewrites every
102
+ // provider's base URL through the configured proxy URL (default
103
+ // http://localhost:8000 → http://localhost:8000/proxy/<host><path>).
104
+ // Excluded providers (openai-codex) flow through unchanged.
105
+ "wrongProxyEnabled"
101
106
  ]);
102
107
  var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set([
103
108
  "fallbackModels",
@@ -164,7 +169,11 @@ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
164
169
  "chimeraModel",
165
170
  "autoReviewProvider",
166
171
  "autoReviewModel",
167
- "autoReviewFallbackProfile"
172
+ "autoReviewFallbackProfile",
173
+ // WrongProxy / WrongTrace URL. Empty = unset. Default lives in
174
+ // `LocalPrefs.DEFAULTS.wrongProxyUrl` ('http://localhost:8000'); users
175
+ // can override here for non-default daemon ports / paths.
176
+ "wrongProxyUrl"
168
177
  ]);
169
178
  var ENUM_PREF_KEYS = {
170
179
  autonomy: AUTONOMY_VALUES,
@@ -1054,7 +1063,7 @@ function routingPatch(payload) {
1054
1063
  function handlePrefsGet(ctx, ws) {
1055
1064
  ctx.send(ws, { type: "prefs.updated", payload: ctx.snapshot() });
1056
1065
  }
1057
- function handlePrefsUpdate(ctx, ws, input) {
1066
+ async function handlePrefsUpdate(ctx, ws, input) {
1058
1067
  const parsed = validatePrefsUpdatePayload(input);
1059
1068
  if (!parsed.ok) {
1060
1069
  sendResult(ctx, ws, false, parsed.message);
@@ -1087,6 +1096,9 @@ function handlePrefsUpdate(ctx, ws, input) {
1087
1096
  ({ setDebugStreamEnabled }) => setDebugStreamEnabled(payload["debugStream"])
1088
1097
  );
1089
1098
  }
1099
+ if (typeof payload["wrongProxyEnabled"] === "boolean" || typeof payload["wrongProxyUrl"] === "string") {
1100
+ await ctx.applyWrongProxyPrefs?.(payload);
1101
+ }
1090
1102
  if (typeof payload["logLevel"] === "string" && ["debug", "info", "warn", "error"].includes(payload["logLevel"])) {
1091
1103
  ctx.setLogLevel?.(payload["logLevel"]);
1092
1104
  }
@@ -7290,6 +7302,8 @@ function seedContextMeta(config, context) {
7290
7302
  meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
7291
7303
  const tgMs = tgExt?.["longToolThresholdMs"];
7292
7304
  meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
7305
+ meta["wrongProxyEnabled"] = config.tools?.wrongProxy?.enabled === true;
7306
+ meta["wrongProxyUrl"] = config.tools?.wrongProxy?.url ?? "";
7293
7307
  {
7294
7308
  const pluginsEnabled = {};
7295
7309
  const record = (name2) => {
@@ -7662,6 +7676,87 @@ import * as path13 from "node:path";
7662
7676
  import { TOKENS } from "@wrongstack/core/kernel";
7663
7677
  import { DefaultSessionStore as DefaultSessionStore2 } from "@wrongstack/core/storage";
7664
7678
  import { toErrorMessage as toErrorMessage7, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core/utils";
7679
+
7680
+ // src/server/proxy-runtime.ts
7681
+ import {
7682
+ applyProxyConfig,
7683
+ getProxyConfig,
7684
+ rewriteBaseUrl,
7685
+ shouldRewriteFor
7686
+ } from "@wrongstack/core/wiring/proxy-rewrite";
7687
+ var HEALTH_PATH = "/api/health";
7688
+ var PROBE_TIMEOUT_MS = 2e3;
7689
+ function seedWrongProxyFromConfig(config) {
7690
+ const wp = config.tools?.wrongProxy;
7691
+ if (!wp) {
7692
+ return;
7693
+ }
7694
+ applyProxyConfig({
7695
+ enabled: wp.enabled === true,
7696
+ url: typeof wp.url === "string" ? wp.url : ""
7697
+ });
7698
+ }
7699
+ async function probeWrongProxyActive() {
7700
+ const cfg = getProxyConfig();
7701
+ if (!cfg.enabled || !cfg.url) {
7702
+ applyProxyConfig({ active: false });
7703
+ return false;
7704
+ }
7705
+ const abort = new AbortController();
7706
+ const timeout = setTimeout(() => abort.abort(), PROBE_TIMEOUT_MS);
7707
+ if (typeof timeout.unref === "function") {
7708
+ timeout.unref();
7709
+ }
7710
+ try {
7711
+ const healthUrl = `${cfg.url.replace(/\/+$/, "")}${HEALTH_PATH}`;
7712
+ const res = await fetch(healthUrl, {
7713
+ method: "GET",
7714
+ signal: abort.signal,
7715
+ headers: { accept: "application/json" }
7716
+ });
7717
+ const ok2 = res.ok && res.status >= 200 && res.status < 300;
7718
+ applyProxyConfig({ active: ok2 });
7719
+ return ok2;
7720
+ } catch {
7721
+ applyProxyConfig({ active: false });
7722
+ return false;
7723
+ } finally {
7724
+ clearTimeout(timeout);
7725
+ }
7726
+ }
7727
+ async function applyWrongProxyPrefs(payload) {
7728
+ const patch = {};
7729
+ if (typeof payload["wrongProxyEnabled"] === "boolean") {
7730
+ patch.enabled = payload["wrongProxyEnabled"];
7731
+ }
7732
+ if (typeof payload["wrongProxyUrl"] === "string") {
7733
+ patch.url = payload["wrongProxyUrl"];
7734
+ }
7735
+ if (Object.keys(patch).length === 0) return;
7736
+ applyProxyConfig(patch);
7737
+ await probeWrongProxyActive();
7738
+ }
7739
+ async function bootstrapWrongProxyFromConfig(config) {
7740
+ seedWrongProxyFromConfig(config);
7741
+ await probeWrongProxyActive();
7742
+ }
7743
+ function routeProviderCfgThroughProxy(cfg, fallbackBaseUrl, providerId) {
7744
+ const raw = cfg;
7745
+ const cfgType = typeof raw.type === "string" ? raw.type : void 0;
7746
+ const cfgBaseUrl = typeof raw.baseUrl === "string" ? raw.baseUrl : void 0;
7747
+ const factoryType = cfgType ?? providerId;
7748
+ const rawBaseUrl = cfgBaseUrl ?? fallbackBaseUrl;
7749
+ if (!rawBaseUrl || !shouldRewriteFor(factoryType)) {
7750
+ return { ...cfg };
7751
+ }
7752
+ const baseUrl = rewriteBaseUrl(rawBaseUrl, getProxyConfig().url);
7753
+ return {
7754
+ ...cfg,
7755
+ ...baseUrl !== cfgBaseUrl ? { baseUrl } : {}
7756
+ };
7757
+ }
7758
+
7759
+ // src/server/embedded-host-adapters.ts
7665
7760
  import { makeProviderFromConfig } from "@wrongstack/providers";
7666
7761
 
7667
7762
  // src/server/project-handlers.ts
@@ -9884,7 +9979,12 @@ async function applyEmbeddedModelSwitch(ctx, providerId, modelId) {
9884
9979
  await agentContext.runModelTransition(async () => {
9885
9980
  const saved = await ctx.loadSavedProviders();
9886
9981
  const providerConfig = saved[providerId] ?? { type: providerId };
9887
- const nextProvider = makeProviderFromConfig(providerId, providerConfig);
9982
+ const routedConfig = routeProviderCfgThroughProxy(
9983
+ providerConfig,
9984
+ ctx.getConfig?.()?.baseUrl,
9985
+ providerId
9986
+ );
9987
+ const nextProvider = makeProviderFromConfig(providerId, routedConfig);
9888
9988
  await ctx.modelsRegistry?.refresh().catch((error2) => {
9889
9989
  ctx.log(
9890
9990
  JSON.stringify({
@@ -16267,7 +16367,15 @@ function createEmbeddedMessageRouter(deps2) {
16267
16367
  getLiveProviderId: () => deps2.agentConfigCtx.agent.ctx.provider.id,
16268
16368
  buildProvider: async (providerId) => {
16269
16369
  const saved = await deps2.agentConfigCtx.loadSavedProviders();
16270
- return makeProviderFromConfig2(providerId, saved[providerId] ?? { type: providerId });
16370
+ const providerCfg = saved[providerId] ?? { type: providerId };
16371
+ return makeProviderFromConfig2(
16372
+ providerId,
16373
+ routeProviderCfgThroughProxy(
16374
+ providerCfg,
16375
+ deps2.agentConfigCtx.getConfig?.()?.baseUrl,
16376
+ providerId
16377
+ )
16378
+ );
16271
16379
  },
16272
16380
  applyModelSwitch: (providerId, modelId) => applyEmbeddedModelSwitch(deps2.agentConfigCtx, providerId, modelId),
16273
16381
  isRunActive: () => deps2.conversationCtx.abortControllers.size > 0,
@@ -21101,7 +21209,13 @@ var PREF_KEYS = [
21101
21209
  // Per-plugin enable/disable map (parity with the embedded server).
21102
21210
  "pluginsEnabled",
21103
21211
  // Fleet chat verbosity: off | full (migrated from streamFleet boolean).
21104
- "fleetChatVerbosity"
21212
+ "fleetChatVerbosity",
21213
+ // WrongProxy / WrongTrace: master switch + configurable URL (default
21214
+ // http://localhost:8000). When `wrongProxyEnabled` is true and the daemon
21215
+ // is reachable, every provider's base URL flows through
21216
+ // `${wrongProxyUrl}/proxy/<host><path>`. openai-codex is excluded by spec.
21217
+ "wrongProxyEnabled",
21218
+ "wrongProxyUrl"
21105
21219
  ];
21106
21220
  function prefSnapshot(contextMeta) {
21107
21221
  const snapshot = {};
@@ -21367,6 +21481,15 @@ async function persistPrefsToConfig(deps2, holder, payload) {
21367
21481
  }
21368
21482
  if (typeof payload["debugStream"] === "boolean")
21369
21483
  decrypted.debugStream = payload["debugStream"];
21484
+ if (typeof payload["wrongProxyEnabled"] === "boolean" || typeof payload["wrongProxyUrl"] === "string") {
21485
+ const toolsCfg = decrypted.tools ?? {};
21486
+ const wp = toolsCfg["wrongProxy"] ?? {};
21487
+ if (typeof payload["wrongProxyEnabled"] === "boolean")
21488
+ wp["enabled"] = payload["wrongProxyEnabled"];
21489
+ if (typeof payload["wrongProxyUrl"] === "string") wp["url"] = payload["wrongProxyUrl"];
21490
+ toolsCfg["wrongProxy"] = wp;
21491
+ decrypted.tools = toolsCfg;
21492
+ }
21370
21493
  if (typeof payload["pluginsEnabled"] === "object" && payload["pluginsEnabled"] !== null) {
21371
21494
  const ext = decrypted.extensions ?? {};
21372
21495
  const toggled = [];
@@ -26133,7 +26256,12 @@ function resolveSetupProvider(opts) {
26133
26256
  baseUrl: config.baseUrl
26134
26257
  };
26135
26258
  try {
26136
- const cfgWithType = { ...providerConfig, type: config.provider };
26259
+ const routedConfig = routeProviderCfgThroughProxy(
26260
+ providerConfig,
26261
+ config.baseUrl,
26262
+ config.provider
26263
+ );
26264
+ const cfgWithType = { ...routedConfig, type: config.provider };
26137
26265
  const provider2 = config.features.modelsRegistry && providerRegistry.has(config.provider) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig3(config.provider, cfgWithType);
26138
26266
  return { provider: provider2, needsSetup: false };
26139
26267
  } catch (err) {
@@ -26146,8 +26274,13 @@ function resolveSetupProvider(opts) {
26146
26274
  if (firstKey) {
26147
26275
  const firstProvider = expectDefined3(savedProviders[firstKey]);
26148
26276
  try {
26277
+ const routedConfig = routeProviderCfgThroughProxy(
26278
+ firstProvider,
26279
+ config.baseUrl,
26280
+ firstKey
26281
+ );
26149
26282
  const provider2 = makeProviderFromConfig3(firstKey, {
26150
- ...firstProvider,
26283
+ ...routedConfig,
26151
26284
  type: firstKey,
26152
26285
  family: firstProvider.family,
26153
26286
  apiKey: firstProvider.apiKey
@@ -26793,9 +26926,14 @@ function buildRoutes(state, deps2, cb) {
26793
26926
  const cur = state.getConfig();
26794
26927
  const newCfg = patchConfig(cur, { provider: newProvider, model: newModel });
26795
26928
  const providerCfg = newCfg.providers?.[newProvider] ?? { type: newProvider };
26796
- const built = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig4(newProvider, providerCfg);
26929
+ const routedCfg = routeProviderCfgThroughProxy(
26930
+ providerCfg,
26931
+ newCfg.baseUrl,
26932
+ newProvider
26933
+ );
26934
+ const built = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...routedCfg, type: newProvider }) : makeProviderFromConfig4(newProvider, routedCfg);
26797
26935
  const newProv = deps2.modelsRegistry ? await withCatalogCapabilities(deps2.modelsRegistry, newProvider, built, {
26798
- ...providerCfg,
26936
+ ...routedCfg,
26799
26937
  type: newProvider,
26800
26938
  model: newModel
26801
26939
  }) : built;
@@ -26807,7 +26945,7 @@ function buildRoutes(state, deps2, cb) {
26807
26945
  deps2.configStore.update({ provider: newProvider, model: newModel });
26808
26946
  deps2.context.model = newModel;
26809
26947
  deps2.context.provider = newProv;
26810
- await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg).catch((error2) => {
26948
+ await cb.updateAutoCompactionMaxContext(newProv, newProvider, routedCfg).catch((error2) => {
26811
26949
  deps2.logger.warn(`model.switch capability refresh failed: ${String(error2)}`);
26812
26950
  });
26813
26951
  broadcast(state.getClients(), {
@@ -26948,6 +27086,13 @@ function buildRoutes(state, deps2, cb) {
26948
27086
  if (typeof payload["fallbackAuto"] === "boolean")
26949
27087
  config.fallbackAuto = payload["fallbackAuto"];
26950
27088
  },
27089
+ // WrongProxy / WrongTrace: reflect the standalone toggle/URL into the
27090
+ // shared `ProxyConfig` singleton immediately and await the re-probe so
27091
+ // `active` is fresh before a subsequent model.switch reads it. In the
27092
+ // CLI-hosted path this same key is the CLI's `applyWrongProxyPrefs`; when
27093
+ // running as its own process there is no CLI to inject it, so route it to
27094
+ // the server-local runtime module.
27095
+ applyWrongProxyPrefs: (payload) => applyWrongProxyPrefs(payload),
26951
27096
  setAutoCompact: (enabled) => {
26952
27097
  deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
26953
27098
  if (enabled && deps2.autoCompactor) {
@@ -27432,7 +27577,12 @@ function setupWebuiCredentialWatcher(options) {
27432
27577
  ...snapshot.apiKey !== void 0 ? { apiKey: snapshot.apiKey } : {},
27433
27578
  ...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {}
27434
27579
  };
27435
- const newProv = deps2.providerRegistry.has(activeId) ? deps2.providerRegistry.create({ ...providerCfg, type: activeId }) : makeProviderFromConfig5(activeId, { ...providerCfg, type: activeId });
27580
+ const routedCfg = routeProviderCfgThroughProxy(
27581
+ providerCfg,
27582
+ state.getConfig().baseUrl,
27583
+ activeId
27584
+ );
27585
+ const newProv = deps2.providerRegistry.has(activeId) ? deps2.providerRegistry.create({ ...routedCfg, type: activeId }) : makeProviderFromConfig5(activeId, { ...routedCfg, type: activeId });
27436
27586
  deps2.context.provider = newProv;
27437
27587
  void updateAutoCompactionMaxContext(newProv).catch(() => void 0);
27438
27588
  console.log(`[WebUI] Provider credentials reloaded from config.json (${activeId})`);
@@ -27634,6 +27784,7 @@ async function startWebUI(opts = {}) {
27634
27784
  console.log("[WebUI] Starting backend services...");
27635
27785
  const boot = await bootConfig();
27636
27786
  const { config: baseConfig, globalConfigPath, wpaths, logger } = boot;
27787
+ await bootstrapWrongProxyFromConfig(baseConfig);
27637
27788
  const vault = opts.services?.vault ?? boot.vault;
27638
27789
  let config = baseConfig;
27639
27790
  let projectRoot = boot.projectRoot;
@@ -98,7 +98,12 @@ var BOOLEAN_PREF_KEYS = /* @__PURE__ */ new Set([
98
98
  // `prefs.update` exactly so the panel does not error with
99
99
  // "unknown preference key".
100
100
  "readSymbols",
101
- "showSageMemoryInject"
101
+ "showSageMemoryInject",
102
+ // WrongProxy / WrongTrace master switch. When on, the CLI rewrites every
103
+ // provider's base URL through the configured proxy URL (default
104
+ // http://localhost:8000 → http://localhost:8000/proxy/<host><path>).
105
+ // Excluded providers (openai-codex) flow through unchanged.
106
+ "wrongProxyEnabled"
102
107
  ]);
103
108
  var STRING_ARRAY_PREF_KEYS = /* @__PURE__ */ new Set([
104
109
  "fallbackModels",
@@ -165,7 +170,11 @@ var STRING_PREF_KEYS = /* @__PURE__ */ new Set([
165
170
  "chimeraModel",
166
171
  "autoReviewProvider",
167
172
  "autoReviewModel",
168
- "autoReviewFallbackProfile"
173
+ "autoReviewFallbackProfile",
174
+ // WrongProxy / WrongTrace URL. Empty = unset. Default lives in
175
+ // `LocalPrefs.DEFAULTS.wrongProxyUrl` ('http://localhost:8000'); users
176
+ // can override here for non-default daemon ports / paths.
177
+ "wrongProxyUrl"
169
178
  ]);
170
179
  var ENUM_PREF_KEYS = {
171
180
  autonomy: AUTONOMY_VALUES,
@@ -1031,7 +1040,7 @@ function routingPatch(payload) {
1031
1040
  function handlePrefsGet(ctx, ws) {
1032
1041
  ctx.send(ws, { type: "prefs.updated", payload: ctx.snapshot() });
1033
1042
  }
1034
- function handlePrefsUpdate(ctx, ws, input) {
1043
+ async function handlePrefsUpdate(ctx, ws, input) {
1035
1044
  const parsed = validatePrefsUpdatePayload(input);
1036
1045
  if (!parsed.ok) {
1037
1046
  sendResult(ctx, ws, false, parsed.message);
@@ -1064,6 +1073,9 @@ function handlePrefsUpdate(ctx, ws, input) {
1064
1073
  ({ setDebugStreamEnabled }) => setDebugStreamEnabled(payload["debugStream"])
1065
1074
  );
1066
1075
  }
1076
+ if (typeof payload["wrongProxyEnabled"] === "boolean" || typeof payload["wrongProxyUrl"] === "string") {
1077
+ await ctx.applyWrongProxyPrefs?.(payload);
1078
+ }
1067
1079
  if (typeof payload["logLevel"] === "string" && ["debug", "info", "warn", "error"].includes(payload["logLevel"])) {
1068
1080
  ctx.setLogLevel?.(payload["logLevel"]);
1069
1081
  }
@@ -7105,6 +7117,8 @@ function seedContextMeta(config, context) {
7105
7117
  meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
7106
7118
  const tgMs = tgExt?.["longToolThresholdMs"];
7107
7119
  meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
7120
+ meta["wrongProxyEnabled"] = config.tools?.wrongProxy?.enabled === true;
7121
+ meta["wrongProxyUrl"] = config.tools?.wrongProxy?.url ?? "";
7108
7122
  {
7109
7123
  const pluginsEnabled = {};
7110
7124
  const record = (name2) => {
@@ -7471,6 +7485,85 @@ function createCustomModeStore(wrongstackDir) {
7471
7485
  return { modes, load: load2, save: save2, create, update, remove, list };
7472
7486
  }
7473
7487
 
7488
+ // src/server/proxy-runtime.ts
7489
+ import {
7490
+ applyProxyConfig,
7491
+ getProxyConfig,
7492
+ rewriteBaseUrl,
7493
+ shouldRewriteFor
7494
+ } from "@wrongstack/core/wiring/proxy-rewrite";
7495
+ var HEALTH_PATH = "/api/health";
7496
+ var PROBE_TIMEOUT_MS = 2e3;
7497
+ function seedWrongProxyFromConfig(config) {
7498
+ const wp = config.tools?.wrongProxy;
7499
+ if (!wp) {
7500
+ return;
7501
+ }
7502
+ applyProxyConfig({
7503
+ enabled: wp.enabled === true,
7504
+ url: typeof wp.url === "string" ? wp.url : ""
7505
+ });
7506
+ }
7507
+ async function probeWrongProxyActive() {
7508
+ const cfg = getProxyConfig();
7509
+ if (!cfg.enabled || !cfg.url) {
7510
+ applyProxyConfig({ active: false });
7511
+ return false;
7512
+ }
7513
+ const abort = new AbortController();
7514
+ const timeout = setTimeout(() => abort.abort(), PROBE_TIMEOUT_MS);
7515
+ if (typeof timeout.unref === "function") {
7516
+ timeout.unref();
7517
+ }
7518
+ try {
7519
+ const healthUrl = `${cfg.url.replace(/\/+$/, "")}${HEALTH_PATH}`;
7520
+ const res = await fetch(healthUrl, {
7521
+ method: "GET",
7522
+ signal: abort.signal,
7523
+ headers: { accept: "application/json" }
7524
+ });
7525
+ const ok2 = res.ok && res.status >= 200 && res.status < 300;
7526
+ applyProxyConfig({ active: ok2 });
7527
+ return ok2;
7528
+ } catch {
7529
+ applyProxyConfig({ active: false });
7530
+ return false;
7531
+ } finally {
7532
+ clearTimeout(timeout);
7533
+ }
7534
+ }
7535
+ async function applyWrongProxyPrefs(payload) {
7536
+ const patch = {};
7537
+ if (typeof payload["wrongProxyEnabled"] === "boolean") {
7538
+ patch.enabled = payload["wrongProxyEnabled"];
7539
+ }
7540
+ if (typeof payload["wrongProxyUrl"] === "string") {
7541
+ patch.url = payload["wrongProxyUrl"];
7542
+ }
7543
+ if (Object.keys(patch).length === 0) return;
7544
+ applyProxyConfig(patch);
7545
+ await probeWrongProxyActive();
7546
+ }
7547
+ async function bootstrapWrongProxyFromConfig(config) {
7548
+ seedWrongProxyFromConfig(config);
7549
+ await probeWrongProxyActive();
7550
+ }
7551
+ function routeProviderCfgThroughProxy(cfg, fallbackBaseUrl, providerId) {
7552
+ const raw = cfg;
7553
+ const cfgType = typeof raw.type === "string" ? raw.type : void 0;
7554
+ const cfgBaseUrl = typeof raw.baseUrl === "string" ? raw.baseUrl : void 0;
7555
+ const factoryType = cfgType ?? providerId;
7556
+ const rawBaseUrl = cfgBaseUrl ?? fallbackBaseUrl;
7557
+ if (!rawBaseUrl || !shouldRewriteFor(factoryType)) {
7558
+ return { ...cfg };
7559
+ }
7560
+ const baseUrl = rewriteBaseUrl(rawBaseUrl, getProxyConfig().url);
7561
+ return {
7562
+ ...cfg,
7563
+ ...baseUrl !== cfgBaseUrl ? { baseUrl } : {}
7564
+ };
7565
+ }
7566
+
7474
7567
  // src/server/project-handlers.ts
7475
7568
  import * as fs10 from "node:fs/promises";
7476
7569
  import * as path11 from "node:path";
@@ -19402,7 +19495,13 @@ var PREF_KEYS = [
19402
19495
  // Per-plugin enable/disable map (parity with the embedded server).
19403
19496
  "pluginsEnabled",
19404
19497
  // Fleet chat verbosity: off | full (migrated from streamFleet boolean).
19405
- "fleetChatVerbosity"
19498
+ "fleetChatVerbosity",
19499
+ // WrongProxy / WrongTrace: master switch + configurable URL (default
19500
+ // http://localhost:8000). When `wrongProxyEnabled` is true and the daemon
19501
+ // is reachable, every provider's base URL flows through
19502
+ // `${wrongProxyUrl}/proxy/<host><path>`. openai-codex is excluded by spec.
19503
+ "wrongProxyEnabled",
19504
+ "wrongProxyUrl"
19406
19505
  ];
19407
19506
  function prefSnapshot(contextMeta) {
19408
19507
  const snapshot = {};
@@ -19668,6 +19767,15 @@ async function persistPrefsToConfig(deps2, holder, payload) {
19668
19767
  }
19669
19768
  if (typeof payload["debugStream"] === "boolean")
19670
19769
  decrypted.debugStream = payload["debugStream"];
19770
+ if (typeof payload["wrongProxyEnabled"] === "boolean" || typeof payload["wrongProxyUrl"] === "string") {
19771
+ const toolsCfg = decrypted.tools ?? {};
19772
+ const wp = toolsCfg["wrongProxy"] ?? {};
19773
+ if (typeof payload["wrongProxyEnabled"] === "boolean")
19774
+ wp["enabled"] = payload["wrongProxyEnabled"];
19775
+ if (typeof payload["wrongProxyUrl"] === "string") wp["url"] = payload["wrongProxyUrl"];
19776
+ toolsCfg["wrongProxy"] = wp;
19777
+ decrypted.tools = toolsCfg;
19778
+ }
19671
19779
  if (typeof payload["pluginsEnabled"] === "object" && payload["pluginsEnabled"] !== null) {
19672
19780
  const ext = decrypted.extensions ?? {};
19673
19781
  const toggled = [];
@@ -24332,7 +24440,12 @@ function resolveSetupProvider(opts) {
24332
24440
  baseUrl: config.baseUrl
24333
24441
  };
24334
24442
  try {
24335
- const cfgWithType = { ...providerConfig, type: config.provider };
24443
+ const routedConfig = routeProviderCfgThroughProxy(
24444
+ providerConfig,
24445
+ config.baseUrl,
24446
+ config.provider
24447
+ );
24448
+ const cfgWithType = { ...routedConfig, type: config.provider };
24336
24449
  const provider2 = config.features.modelsRegistry && providerRegistry.has(config.provider) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig(config.provider, cfgWithType);
24337
24450
  return { provider: provider2, needsSetup: false };
24338
24451
  } catch (err) {
@@ -24345,8 +24458,13 @@ function resolveSetupProvider(opts) {
24345
24458
  if (firstKey) {
24346
24459
  const firstProvider = expectDefined2(savedProviders[firstKey]);
24347
24460
  try {
24461
+ const routedConfig = routeProviderCfgThroughProxy(
24462
+ firstProvider,
24463
+ config.baseUrl,
24464
+ firstKey
24465
+ );
24348
24466
  const provider2 = makeProviderFromConfig(firstKey, {
24349
- ...firstProvider,
24467
+ ...routedConfig,
24350
24468
  type: firstKey,
24351
24469
  family: firstProvider.family,
24352
24470
  apiKey: firstProvider.apiKey
@@ -24992,9 +25110,14 @@ function buildRoutes(state, deps2, cb) {
24992
25110
  const cur = state.getConfig();
24993
25111
  const newCfg = patchConfig(cur, { provider: newProvider, model: newModel });
24994
25112
  const providerCfg = newCfg.providers?.[newProvider] ?? { type: newProvider };
24995
- const built = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, providerCfg);
25113
+ const routedCfg = routeProviderCfgThroughProxy(
25114
+ providerCfg,
25115
+ newCfg.baseUrl,
25116
+ newProvider
25117
+ );
25118
+ const built = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...routedCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, routedCfg);
24996
25119
  const newProv = deps2.modelsRegistry ? await withCatalogCapabilities(deps2.modelsRegistry, newProvider, built, {
24997
- ...providerCfg,
25120
+ ...routedCfg,
24998
25121
  type: newProvider,
24999
25122
  model: newModel
25000
25123
  }) : built;
@@ -25006,7 +25129,7 @@ function buildRoutes(state, deps2, cb) {
25006
25129
  deps2.configStore.update({ provider: newProvider, model: newModel });
25007
25130
  deps2.context.model = newModel;
25008
25131
  deps2.context.provider = newProv;
25009
- await cb.updateAutoCompactionMaxContext(newProv, newProvider, providerCfg).catch((error2) => {
25132
+ await cb.updateAutoCompactionMaxContext(newProv, newProvider, routedCfg).catch((error2) => {
25010
25133
  deps2.logger.warn(`model.switch capability refresh failed: ${String(error2)}`);
25011
25134
  });
25012
25135
  broadcast(state.getClients(), {
@@ -25147,6 +25270,13 @@ function buildRoutes(state, deps2, cb) {
25147
25270
  if (typeof payload["fallbackAuto"] === "boolean")
25148
25271
  config.fallbackAuto = payload["fallbackAuto"];
25149
25272
  },
25273
+ // WrongProxy / WrongTrace: reflect the standalone toggle/URL into the
25274
+ // shared `ProxyConfig` singleton immediately and await the re-probe so
25275
+ // `active` is fresh before a subsequent model.switch reads it. In the
25276
+ // CLI-hosted path this same key is the CLI's `applyWrongProxyPrefs`; when
25277
+ // running as its own process there is no CLI to inject it, so route it to
25278
+ // the server-local runtime module.
25279
+ applyWrongProxyPrefs: (payload) => applyWrongProxyPrefs(payload),
25150
25280
  setAutoCompact: (enabled) => {
25151
25281
  deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
25152
25282
  if (enabled && deps2.autoCompactor) {
@@ -25631,7 +25761,12 @@ function setupWebuiCredentialWatcher(options) {
25631
25761
  ...snapshot.apiKey !== void 0 ? { apiKey: snapshot.apiKey } : {},
25632
25762
  ...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {}
25633
25763
  };
25634
- const newProv = deps2.providerRegistry.has(activeId) ? deps2.providerRegistry.create({ ...providerCfg, type: activeId }) : makeProviderFromConfig3(activeId, { ...providerCfg, type: activeId });
25764
+ const routedCfg = routeProviderCfgThroughProxy(
25765
+ providerCfg,
25766
+ state.getConfig().baseUrl,
25767
+ activeId
25768
+ );
25769
+ const newProv = deps2.providerRegistry.has(activeId) ? deps2.providerRegistry.create({ ...routedCfg, type: activeId }) : makeProviderFromConfig3(activeId, { ...routedCfg, type: activeId });
25635
25770
  deps2.context.provider = newProv;
25636
25771
  void updateAutoCompactionMaxContext(newProv).catch(() => void 0);
25637
25772
  console.log(`[WebUI] Provider credentials reloaded from config.json (${activeId})`);
@@ -25833,6 +25968,7 @@ async function startWebUI(opts = {}) {
25833
25968
  console.log("[WebUI] Starting backend services...");
25834
25969
  const boot = await bootConfig();
25835
25970
  const { config: baseConfig, globalConfigPath, wpaths, logger } = boot;
25971
+ await bootstrapWrongProxyFromConfig(baseConfig);
25836
25972
  const vault = opts.services?.vault ?? boot.vault;
25837
25973
  let config = baseConfig;
25838
25974
  let projectRoot = boot.projectRoot;
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import type { SecretVault } from '@wrongstack/core/types';
19
19
  /** Pref keys exposed to the settings panel via prefs.get / prefs.updated. */
20
- export declare const PREF_KEYS: readonly ['autonomy', 'autonomyDelayMs', 'autoProceedMaxIterations', 'yolo', 'maxIterations', 'chime', 'confirmExit', 'nextPrediction', 'nextStepsTool', 'enhanceEnabled', 'enhanceDelayMs', 'enhanceLanguage', 'featureMcp', 'featurePlugins', 'featureMemory', 'featureSkills', 'featureModelsRegistry', 'indexOnStart', 'contextAutoCompact', 'contextStrategy', 'contextMode', 'tokenSavingTier', 'maxConcurrent', 'titleAnimation', 'uiLocale', 'logLevel', 'auditLevel', 'hqEnabled', 'hqUrl', 'hqRawContent', 'tgConfigured', 'tgSessionEnd', 'tgDelegate', 'tgLongToolMs', 'reasoningMode', 'reasoningEffort', 'reasoningPreserve', 'cacheTtl', 'fallbackModels', 'fallbackProfiles', 'favoriteModels', 'favoriteModelsOnly', 'modelAvailabilitySchedule', 'modelMatrix', 'fallbackAuto', 'refinerProvider', 'refinerModel', 'refinerFallbackProfile', 'thinkingWord', 'statuslineMode', 'animationStyle', 'showModelReasoning', 'breakerEnabled', 'breakerAutoKillResetMs', 'fsAccess', 'debugStream', 'chimeraEnabled', 'chimeraProvider', 'chimeraModel', 'chimeraMaxFiles', 'chimeraAutoFix', 'autoReviewEnabled', 'autoReviewProvider', 'autoReviewModel', 'autoReviewFallbackProfile', 'autoReviewModelSelection', 'autoReviewFallbackModels', 'autoReviewDebounceMs', 'autoReviewMaxFilesPerBatch', 'autoReviewMaxConcurrentReviews', 'autoReviewCascadeOn', 'groupToolCalls', 'showThinkingLogs', 'autoCollapseInput', 'pluginsEnabled', 'fleetChatVerbosity'];
20
+ export declare const PREF_KEYS: readonly ['autonomy', 'autonomyDelayMs', 'autoProceedMaxIterations', 'yolo', 'maxIterations', 'chime', 'confirmExit', 'nextPrediction', 'nextStepsTool', 'enhanceEnabled', 'enhanceDelayMs', 'enhanceLanguage', 'featureMcp', 'featurePlugins', 'featureMemory', 'featureSkills', 'featureModelsRegistry', 'indexOnStart', 'contextAutoCompact', 'contextStrategy', 'contextMode', 'tokenSavingTier', 'maxConcurrent', 'titleAnimation', 'uiLocale', 'logLevel', 'auditLevel', 'hqEnabled', 'hqUrl', 'hqRawContent', 'tgConfigured', 'tgSessionEnd', 'tgDelegate', 'tgLongToolMs', 'reasoningMode', 'reasoningEffort', 'reasoningPreserve', 'cacheTtl', 'fallbackModels', 'fallbackProfiles', 'favoriteModels', 'favoriteModelsOnly', 'modelAvailabilitySchedule', 'modelMatrix', 'fallbackAuto', 'refinerProvider', 'refinerModel', 'refinerFallbackProfile', 'thinkingWord', 'statuslineMode', 'animationStyle', 'showModelReasoning', 'breakerEnabled', 'breakerAutoKillResetMs', 'fsAccess', 'debugStream', 'chimeraEnabled', 'chimeraProvider', 'chimeraModel', 'chimeraMaxFiles', 'chimeraAutoFix', 'autoReviewEnabled', 'autoReviewProvider', 'autoReviewModel', 'autoReviewFallbackProfile', 'autoReviewModelSelection', 'autoReviewFallbackModels', 'autoReviewDebounceMs', 'autoReviewMaxFilesPerBatch', 'autoReviewMaxConcurrentReviews', 'autoReviewCascadeOn', 'groupToolCalls', 'showThinkingLogs', 'autoCollapseInput', 'pluginsEnabled', 'fleetChatVerbosity', 'wrongProxyEnabled', 'wrongProxyUrl'];
21
21
  export interface PrefHelperDeps {
22
22
  /** Path to the active profile config; the sole settings mutation target. */
23
23
  profileConfigPath: string;
@@ -13,10 +13,20 @@ export interface PrefsHandlerContext {
13
13
  applyConfigPrefs?: ((payload: Record<string, unknown>) => void) | undefined;
14
14
  setAutoCompact?: ((enabled: boolean) => void) | undefined;
15
15
  setLogLevel?: ((level: 'debug' | 'info' | 'warn' | 'error') => void) | undefined;
16
+ /**
17
+ * WrongProxy / WrongTrace: applies the toggle + URL to the runtime
18
+ * config and kicks the periodic probe. Injected by the CLI's
19
+ * `createPrefsSeeding` so the WS server stays package-agnostic — the
20
+ * server doesn't pull `@wrongstack/cli`'s import graph or its
21
+ * `setInterval`-owning probe module. May return a promise so the
22
+ * standalone server's re-probe can be awaited before the next
23
+ * prefs-dependent request (e.g. an immediate model.switch).
24
+ */
25
+ applyWrongProxyPrefs?: ((payload: Record<string, unknown>) => void | Promise<void>) | undefined;
16
26
  send: (ws: WebSocket, message: WSServerMessage) => void;
17
27
  broadcast: (message: WSServerMessage) => void;
18
28
  }
19
29
  export declare function handlePrefsGet(ctx: PrefsHandlerContext, ws: WebSocket): void;
20
- export declare function handlePrefsUpdate(ctx: PrefsHandlerContext, ws: WebSocket, input: Record<string, unknown>): void;
30
+ export declare function handlePrefsUpdate(ctx: PrefsHandlerContext, ws: WebSocket, input: Record<string, unknown>): Promise<void>;
21
31
  export declare function handleAutonomySwitch(ctx: PrefsHandlerContext, ws: WebSocket, mode: string): void;
22
32
  //# sourceMappingURL=prefs-handlers.d.ts.map
@@ -0,0 +1,82 @@
1
+ /**
2
+ * WrongProxy / WrongTrace runtime seeding for the standalone WebUI server.
3
+ *
4
+ * The CLI hosts WebUI in-process via `dispatch-webui.ts`, which injects
5
+ * `applyWrongProxyPrefs` (from `@wrongstack/cli/wiring/proxy-wiring`) into
6
+ * the WS prefs handler and shares the CLI's proxy singleton. But when the
7
+ * WebUI server runs as its OWN process (`startWebUI`), no CLI is present to
8
+ * seed the `ProxyConfig` singleton in `@wrongstack/core/wiring/proxy-rewrite`
9
+ * — so all of the server's provider-build paths would construct providers
10
+ * with the raw base URL and never traverse the proxy.
11
+ *
12
+ * webui ⇏ cli, so this module cannot import `@wrongstack/cli`'s probe. It
13
+ * owns the same two concerns the CLI's `proxy-wiring` does, but dependency-
14
+ * light and self-contained:
15
+ *
16
+ * 1. Seeding the shared `ProxyConfig` singleton (`enabled` / `url`) from
17
+ * `config.tools.wrongProxy` at boot.
18
+ * 2. A one-shot `/api/health` reachability probe that flips `active` to
19
+ * true so `shouldRewriteFor()` returns true for subsequent provider
20
+ * builds. Unlike the CLI's periodic probe this runs once at seed time
21
+ * (the standalone server re-probes when the operator toggles the field
22
+ * via `applyWrongProxyPrefs`); a long-lived daemon is assumed stable.
23
+ *
24
+ * The rewrite itself (`shouldRewriteFor` / `rewriteBaseUrl`) is pure logic
25
+ * in `@wrongstack/core` and is applied at each provider-build site in this
26
+ * server (setup-screen, routes applyModelSwitchCore, embedded-message-router,
27
+ * embedded-host-adapters, start-webui-credential-watcher).
28
+ */
29
+ import type { Config } from '@wrongstack/core/types';
30
+ /**
31
+ * Seed the shared `ProxyConfig` singleton from the persisted
32
+ * `config.tools.wrongProxy.{enabled,url}` block. Idempotent — safe to call
33
+ * at every boot. A provider that is eligible and reachable with a
34
+ * configured URL will then have its base URL rewritten through the proxy.
35
+ */
36
+ export declare function seedWrongProxyFromConfig(config: Config): void;
37
+ /**
38
+ * Probe the configured proxy daemon once and flip `active` to true when it
39
+ * answers a 2xx on `/api/health`. Mirrors the CLI's probe contract but
40
+ * without owning a `setInterval` loop. Resolves with the new `active` value.
41
+ * A non-2xx / timeout / ECONNREFUSED leaves `active` false so the rewriter
42
+ * keeps base URLs unchanged.
43
+ */
44
+ export declare function probeWrongProxyActive(): Promise<boolean>;
45
+ /**
46
+ * Apply the `wrongProxyEnabled` + `wrongProxyUrl` portion of a WS prefs
47
+ * payload, then re-probe so the rewrite reflects the new state immediately
48
+ * (the next provider build reads the freshly-probed `active`). Skips when the
49
+ * payload carries neither key. Mirrors the CLI's `applyWrongProxyPrefs`.
50
+ */
51
+ export declare function applyWrongProxyPrefs(payload: Record<string, unknown>): Promise<void>;
52
+ /**
53
+ * Seed the singleton from the persisted config and await the first probe so
54
+ * `active` is correct BEFORE the server's provider is constructed. Without
55
+ * the await, `setup-screen`'s `resolveSetupProvider` reads the singleton
56
+ * (synchronously) while `active` is still false and bakes the raw base URL
57
+ * into the initial provider. Mirrors the CLI's
58
+ * `bootstrapWrongProxy` + `awaitFirstWrongProxyProbe` pair.
59
+ */
60
+ export declare function bootstrapWrongProxyFromConfig(config: Config): Promise<void>;
61
+ /**
62
+ * Apply the WrongProxy / WrongTrace base-URL rewrite to ONE provider config
63
+ * before it is handed to a provider factory. This is the single shared seam
64
+ * for every WebUI-server provider-build path so the eligibility + compose
65
+ * rules cannot drift between call sites:
66
+ *
67
+ * - `routes.ts` `applyModelSwitchCore` (model.switch rebuild)
68
+ * - `setup-screen.ts` `resolveSetupProvider` (boot branches 1 + 2)
69
+ * - `embedded-message-router.ts` `buildProvider`
70
+ * - `embedded-host-adapters.ts` `applyEmbeddedModelSwitch`
71
+ * - `start-webui-credential-watcher.ts` (credential hot-reload)
72
+ *
73
+ * Returns a copy of `cfg` with `baseUrl` overridden to
74
+ * `${proxyUrl}/proxy/<host><path>` when the toggle is on, the daemon is
75
+ * reachable (`active`) and the provider is eligible (openai-codex excluded
76
+ * by the rewriter). `fallbackBaseUrl` is used when `cfg` carries no explicit
77
+ * baseUrl (the call site's top-level config baseUrl). When the proxy is off
78
+ * or unreachable, the cfg is returned unchanged so factory construction
79
+ * behaves exactly as before the proxy feature existed.
80
+ */
81
+ export declare function routeProviderCfgThroughProxy<T extends object>(cfg: Readonly<T>, fallbackBaseUrl: string | undefined, providerId: string): T;
82
+ //# sourceMappingURL=proxy-runtime.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/webui-server",
3
- "version": "0.310.0",
3
+ "version": "0.310.1",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack WebUI HTTP/WebSocket server module — extracted from @wrongstack/webui in PR #243/244 to remove the CLI -> @wrongstack/webui/server cross-package edge (audit §3.1.1). Pure backend: HTTP routes, WebSocket handlers, MCP tool wrappers, HTML serving. The web frontend lives in @wrongstack/webui; this package is the standalone server it can run on.",
6
6
  "keywords": [
@@ -36,18 +36,18 @@
36
36
  ],
37
37
  "dependencies": {
38
38
  "ws": "^8.21.3",
39
- "@wrongstack/kanban": "0.310.0",
40
- "@wrongstack/requirement-intake": "0.310.0",
41
- "@wrongstack/providers": "0.310.0",
42
- "@wrongstack/runtime": "0.310.0",
43
- "@wrongstack/sdd": "0.310.0",
44
- "@wrongstack/core": "0.310.0",
45
- "@wrongstack/sage": "0.310.0",
46
- "@wrongstack/techstack": "0.310.0",
47
- "@wrongstack/mcp": "0.310.0",
48
- "@wrongstack/vector-memory": "0.310.0",
49
- "@wrongstack/tools": "0.310.0",
50
- "@wrongstack/webui-protocol": "0.310.0"
39
+ "@wrongstack/core": "0.310.1",
40
+ "@wrongstack/kanban": "0.310.1",
41
+ "@wrongstack/mcp": "0.310.1",
42
+ "@wrongstack/sage": "0.310.1",
43
+ "@wrongstack/runtime": "0.310.1",
44
+ "@wrongstack/providers": "0.310.1",
45
+ "@wrongstack/sdd": "0.310.1",
46
+ "@wrongstack/tools": "0.310.1",
47
+ "@wrongstack/requirement-intake": "0.310.1",
48
+ "@wrongstack/vector-memory": "0.310.1",
49
+ "@wrongstack/techstack": "0.310.1",
50
+ "@wrongstack/webui-protocol": "0.310.1"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "^26.2.0",