@wrongstack/webui-server 0.310.0 → 0.313.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/index.js +243 -14
- package/dist/server/entry.js +226 -12
- package/dist/server/pref-helpers.d.ts +1 -1
- package/dist/server/prefs-handlers.d.ts +11 -1
- package/dist/server/proxy-runtime.d.ts +82 -0
- package/dist/server/start-webui-proxy-apply.d.ts +38 -0
- package/dist/server/start-webui-shutdown.d.ts +1 -0
- package/package.json +14 -13
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:3444 → http://localhost:3444/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:3444'); 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,
|
|
@@ -178,7 +187,7 @@ var ENUM_PREF_KEYS = {
|
|
|
178
187
|
reasoningEffort: REASONING_EFFORT_VALUES,
|
|
179
188
|
cacheTtl: CACHE_TTL_VALUES,
|
|
180
189
|
statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
|
|
181
|
-
animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
|
|
190
|
+
animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "static", "cycle"]),
|
|
182
191
|
fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"]),
|
|
183
192
|
// Chimera autoFix + auto-review cascade threshold
|
|
184
193
|
chimeraAutoFix: /* @__PURE__ */ new Set(["off", "ask", "auto"]),
|
|
@@ -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
|
}
|
|
@@ -6364,7 +6376,8 @@ function estimateTokens(s) {
|
|
|
6364
6376
|
function stringifyContent(c) {
|
|
6365
6377
|
if (typeof c === "string") return c;
|
|
6366
6378
|
try {
|
|
6367
|
-
|
|
6379
|
+
const serialized = JSON.stringify(c);
|
|
6380
|
+
return serialized === void 0 ? String(c) : serialized;
|
|
6368
6381
|
} catch {
|
|
6369
6382
|
return String(c);
|
|
6370
6383
|
}
|
|
@@ -7290,6 +7303,8 @@ function seedContextMeta(config, context) {
|
|
|
7290
7303
|
meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
|
|
7291
7304
|
const tgMs = tgExt?.["longToolThresholdMs"];
|
|
7292
7305
|
meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
|
|
7306
|
+
meta["wrongProxyEnabled"] = config.tools?.wrongProxy?.enabled === true;
|
|
7307
|
+
meta["wrongProxyUrl"] = config.tools?.wrongProxy?.url ?? "";
|
|
7293
7308
|
{
|
|
7294
7309
|
const pluginsEnabled = {};
|
|
7295
7310
|
const record = (name2) => {
|
|
@@ -7662,6 +7677,87 @@ import * as path13 from "node:path";
|
|
|
7662
7677
|
import { TOKENS } from "@wrongstack/core/kernel";
|
|
7663
7678
|
import { DefaultSessionStore as DefaultSessionStore2 } from "@wrongstack/core/storage";
|
|
7664
7679
|
import { toErrorMessage as toErrorMessage7, wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core/utils";
|
|
7680
|
+
|
|
7681
|
+
// src/server/proxy-runtime.ts
|
|
7682
|
+
import {
|
|
7683
|
+
applyProxyConfig,
|
|
7684
|
+
getProxyConfig,
|
|
7685
|
+
rewriteBaseUrl,
|
|
7686
|
+
shouldRewriteFor
|
|
7687
|
+
} from "@wrongstack/core/wiring/proxy-rewrite";
|
|
7688
|
+
var HEALTH_PATH = "/api/health";
|
|
7689
|
+
var PROBE_TIMEOUT_MS = 2e3;
|
|
7690
|
+
function seedWrongProxyFromConfig(config) {
|
|
7691
|
+
const wp = config.tools?.wrongProxy;
|
|
7692
|
+
if (!wp) {
|
|
7693
|
+
return;
|
|
7694
|
+
}
|
|
7695
|
+
applyProxyConfig({
|
|
7696
|
+
enabled: wp.enabled === true,
|
|
7697
|
+
url: typeof wp.url === "string" ? wp.url : ""
|
|
7698
|
+
});
|
|
7699
|
+
}
|
|
7700
|
+
async function probeWrongProxyActive() {
|
|
7701
|
+
const cfg = getProxyConfig();
|
|
7702
|
+
if (!cfg.enabled || !cfg.url) {
|
|
7703
|
+
applyProxyConfig({ active: false });
|
|
7704
|
+
return false;
|
|
7705
|
+
}
|
|
7706
|
+
const abort = new AbortController();
|
|
7707
|
+
const timeout = setTimeout(() => abort.abort(), PROBE_TIMEOUT_MS);
|
|
7708
|
+
if (typeof timeout.unref === "function") {
|
|
7709
|
+
timeout.unref();
|
|
7710
|
+
}
|
|
7711
|
+
try {
|
|
7712
|
+
const healthUrl = `${cfg.url.replace(/\/+$/, "")}${HEALTH_PATH}`;
|
|
7713
|
+
const res = await fetch(healthUrl, {
|
|
7714
|
+
method: "GET",
|
|
7715
|
+
signal: abort.signal,
|
|
7716
|
+
headers: { accept: "application/json" }
|
|
7717
|
+
});
|
|
7718
|
+
const ok2 = res.ok && res.status >= 200 && res.status < 300;
|
|
7719
|
+
applyProxyConfig({ active: ok2 });
|
|
7720
|
+
return ok2;
|
|
7721
|
+
} catch {
|
|
7722
|
+
applyProxyConfig({ active: false });
|
|
7723
|
+
return false;
|
|
7724
|
+
} finally {
|
|
7725
|
+
clearTimeout(timeout);
|
|
7726
|
+
}
|
|
7727
|
+
}
|
|
7728
|
+
async function applyWrongProxyPrefs(payload) {
|
|
7729
|
+
const patch = {};
|
|
7730
|
+
if (typeof payload["wrongProxyEnabled"] === "boolean") {
|
|
7731
|
+
patch.enabled = payload["wrongProxyEnabled"];
|
|
7732
|
+
}
|
|
7733
|
+
if (typeof payload["wrongProxyUrl"] === "string") {
|
|
7734
|
+
patch.url = payload["wrongProxyUrl"];
|
|
7735
|
+
}
|
|
7736
|
+
if (Object.keys(patch).length === 0) return;
|
|
7737
|
+
applyProxyConfig(patch);
|
|
7738
|
+
await probeWrongProxyActive();
|
|
7739
|
+
}
|
|
7740
|
+
async function bootstrapWrongProxyFromConfig(config) {
|
|
7741
|
+
seedWrongProxyFromConfig(config);
|
|
7742
|
+
await probeWrongProxyActive();
|
|
7743
|
+
}
|
|
7744
|
+
function routeProviderCfgThroughProxy(cfg, fallbackBaseUrl, providerId) {
|
|
7745
|
+
const raw = cfg;
|
|
7746
|
+
const cfgType = typeof raw.type === "string" ? raw.type : void 0;
|
|
7747
|
+
const cfgBaseUrl = typeof raw.baseUrl === "string" ? raw.baseUrl : void 0;
|
|
7748
|
+
const factoryType = cfgType ?? providerId;
|
|
7749
|
+
const rawBaseUrl = cfgBaseUrl ?? fallbackBaseUrl;
|
|
7750
|
+
if (!rawBaseUrl || !shouldRewriteFor(factoryType)) {
|
|
7751
|
+
return { ...cfg };
|
|
7752
|
+
}
|
|
7753
|
+
const baseUrl = rewriteBaseUrl(rawBaseUrl, getProxyConfig().url);
|
|
7754
|
+
return {
|
|
7755
|
+
...cfg,
|
|
7756
|
+
...baseUrl !== cfgBaseUrl ? { baseUrl } : {}
|
|
7757
|
+
};
|
|
7758
|
+
}
|
|
7759
|
+
|
|
7760
|
+
// src/server/embedded-host-adapters.ts
|
|
7665
7761
|
import { makeProviderFromConfig } from "@wrongstack/providers";
|
|
7666
7762
|
|
|
7667
7763
|
// src/server/project-handlers.ts
|
|
@@ -9884,7 +9980,12 @@ async function applyEmbeddedModelSwitch(ctx, providerId, modelId) {
|
|
|
9884
9980
|
await agentContext.runModelTransition(async () => {
|
|
9885
9981
|
const saved = await ctx.loadSavedProviders();
|
|
9886
9982
|
const providerConfig = saved[providerId] ?? { type: providerId };
|
|
9887
|
-
const
|
|
9983
|
+
const routedConfig = routeProviderCfgThroughProxy(
|
|
9984
|
+
providerConfig,
|
|
9985
|
+
ctx.getConfig?.()?.baseUrl,
|
|
9986
|
+
providerId
|
|
9987
|
+
);
|
|
9988
|
+
const nextProvider = makeProviderFromConfig(providerId, routedConfig);
|
|
9888
9989
|
await ctx.modelsRegistry?.refresh().catch((error2) => {
|
|
9889
9990
|
ctx.log(
|
|
9890
9991
|
JSON.stringify({
|
|
@@ -16267,7 +16368,15 @@ function createEmbeddedMessageRouter(deps2) {
|
|
|
16267
16368
|
getLiveProviderId: () => deps2.agentConfigCtx.agent.ctx.provider.id,
|
|
16268
16369
|
buildProvider: async (providerId) => {
|
|
16269
16370
|
const saved = await deps2.agentConfigCtx.loadSavedProviders();
|
|
16270
|
-
|
|
16371
|
+
const providerCfg = saved[providerId] ?? { type: providerId };
|
|
16372
|
+
return makeProviderFromConfig2(
|
|
16373
|
+
providerId,
|
|
16374
|
+
routeProviderCfgThroughProxy(
|
|
16375
|
+
providerCfg,
|
|
16376
|
+
deps2.agentConfigCtx.getConfig?.()?.baseUrl,
|
|
16377
|
+
providerId
|
|
16378
|
+
)
|
|
16379
|
+
);
|
|
16271
16380
|
},
|
|
16272
16381
|
applyModelSwitch: (providerId, modelId) => applyEmbeddedModelSwitch(deps2.agentConfigCtx, providerId, modelId),
|
|
16273
16382
|
isRunActive: () => deps2.conversationCtx.abortControllers.size > 0,
|
|
@@ -21101,7 +21210,13 @@ var PREF_KEYS = [
|
|
|
21101
21210
|
// Per-plugin enable/disable map (parity with the embedded server).
|
|
21102
21211
|
"pluginsEnabled",
|
|
21103
21212
|
// Fleet chat verbosity: off | full (migrated from streamFleet boolean).
|
|
21104
|
-
"fleetChatVerbosity"
|
|
21213
|
+
"fleetChatVerbosity",
|
|
21214
|
+
// WrongProxy / WrongTrace: master switch + configurable URL (default
|
|
21215
|
+
// http://localhost:3444). When `wrongProxyEnabled` is true and the daemon
|
|
21216
|
+
// is reachable, every provider's base URL flows through
|
|
21217
|
+
// `${wrongProxyUrl}/proxy/<host><path>`. openai-codex is excluded by spec.
|
|
21218
|
+
"wrongProxyEnabled",
|
|
21219
|
+
"wrongProxyUrl"
|
|
21105
21220
|
];
|
|
21106
21221
|
function prefSnapshot(contextMeta) {
|
|
21107
21222
|
const snapshot = {};
|
|
@@ -21367,6 +21482,15 @@ async function persistPrefsToConfig(deps2, holder, payload) {
|
|
|
21367
21482
|
}
|
|
21368
21483
|
if (typeof payload["debugStream"] === "boolean")
|
|
21369
21484
|
decrypted.debugStream = payload["debugStream"];
|
|
21485
|
+
if (typeof payload["wrongProxyEnabled"] === "boolean" || typeof payload["wrongProxyUrl"] === "string") {
|
|
21486
|
+
const toolsCfg = decrypted.tools ?? {};
|
|
21487
|
+
const wp = toolsCfg["wrongProxy"] ?? {};
|
|
21488
|
+
if (typeof payload["wrongProxyEnabled"] === "boolean")
|
|
21489
|
+
wp["enabled"] = payload["wrongProxyEnabled"];
|
|
21490
|
+
if (typeof payload["wrongProxyUrl"] === "string") wp["url"] = payload["wrongProxyUrl"];
|
|
21491
|
+
toolsCfg["wrongProxy"] = wp;
|
|
21492
|
+
decrypted.tools = toolsCfg;
|
|
21493
|
+
}
|
|
21370
21494
|
if (typeof payload["pluginsEnabled"] === "object" && payload["pluginsEnabled"] !== null) {
|
|
21371
21495
|
const ext = decrypted.extensions ?? {};
|
|
21372
21496
|
const toggled = [];
|
|
@@ -24134,6 +24258,8 @@ import {
|
|
|
24134
24258
|
// src/server/backend-services.ts
|
|
24135
24259
|
import { join as join15 } from "node:path";
|
|
24136
24260
|
import { Agent } from "@wrongstack/core/agent";
|
|
24261
|
+
import { HookRegistry, HookRunner } from "@wrongstack/core/hooks";
|
|
24262
|
+
import { createWrongTraceHookPair, recordGateDecision, snapshotGateDecisions, persistWrongTraceGateCounters } from "@wrongstack/wrongtrace";
|
|
24137
24263
|
import {
|
|
24138
24264
|
BrainDecisionLedger,
|
|
24139
24265
|
BrainMonitor,
|
|
@@ -25175,6 +25301,33 @@ async function createAgentServices(input) {
|
|
|
25175
25301
|
const secretScrubber = container.resolve(TOKENS2.SecretScrubber);
|
|
25176
25302
|
const renderer = container.has(TOKENS2.Renderer) ? container.resolve(TOKENS2.Renderer) : void 0;
|
|
25177
25303
|
const permissionPolicy = container.resolve(TOKENS2.PermissionPolicy);
|
|
25304
|
+
const wrongTraceHookRegistry = new HookRegistry();
|
|
25305
|
+
const wrongTraceHooks = createWrongTraceHookPair(() => context.session.id, {
|
|
25306
|
+
emit: (event) => {
|
|
25307
|
+
events.emit("wrongtrace.gate.decision", event);
|
|
25308
|
+
recordGateDecision(event);
|
|
25309
|
+
void persistWrongTraceGateCounters(projectRoot, snapshotGateDecisions());
|
|
25310
|
+
}
|
|
25311
|
+
});
|
|
25312
|
+
wrongTraceHookRegistry.registerInProcess(
|
|
25313
|
+
"PreToolUse",
|
|
25314
|
+
"edit|write|replace|patch|codebase-ast-replace",
|
|
25315
|
+
wrongTraceHooks.preToolUse,
|
|
25316
|
+
"wrongtrace-gate"
|
|
25317
|
+
);
|
|
25318
|
+
wrongTraceHookRegistry.registerInProcess(
|
|
25319
|
+
"PostToolUse",
|
|
25320
|
+
"edit|write|replace|patch|codebase-ast-replace",
|
|
25321
|
+
wrongTraceHooks.postToolUse,
|
|
25322
|
+
"wrongtrace-gate"
|
|
25323
|
+
);
|
|
25324
|
+
const wrongTraceHookRunner = new HookRunner({
|
|
25325
|
+
registry: wrongTraceHookRegistry,
|
|
25326
|
+
sessionId: () => context.session.id,
|
|
25327
|
+
// Coordination, not enforcement: these hooks are fail-open by
|
|
25328
|
+
// construction and must run regardless of shell-hook gating.
|
|
25329
|
+
allowNonPolicy: true
|
|
25330
|
+
});
|
|
25178
25331
|
const toolExecutor = new ToolExecutor(toolRegistry, {
|
|
25179
25332
|
permissionPolicy,
|
|
25180
25333
|
secretScrubber,
|
|
@@ -25184,6 +25337,7 @@ async function createAgentServices(input) {
|
|
|
25184
25337
|
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
25185
25338
|
perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
25186
25339
|
tracer: void 0,
|
|
25340
|
+
hookRunner: wrongTraceHookRunner,
|
|
25187
25341
|
// Off unless the operator opts in. The WebUI drives the same agent as the
|
|
25188
25342
|
// CLI, so it must resolve this identically — a surface-dependent gate
|
|
25189
25343
|
// would mean the same repo governs under `wstack` and not in the browser.
|
|
@@ -25405,6 +25559,12 @@ async function createAgentServices(input) {
|
|
|
25405
25559
|
// round-robin keeps reassigning the doomed model. Mirrors the CLI
|
|
25406
25560
|
// factory wiring at host-subagent-factory.ts:337.
|
|
25407
25561
|
statusTracker: container.safeResolve(TOKENS2.ProviderModelStatusTracker),
|
|
25562
|
+
// WrongTrace lock gate for SDD-wizard workers: the standalone server
|
|
25563
|
+
// already built a dedicated WrongTrace-only HookRunner for its own
|
|
25564
|
+
// executor above; handing the same runner to the runtime factory
|
|
25565
|
+
// makes every worker edit honor peer locks with one process-wide
|
|
25566
|
+
// owner identity (context.session.id).
|
|
25567
|
+
hookRunner: wrongTraceHookRunner,
|
|
25408
25568
|
...input.installToolBoundary ? { installToolBoundary: input.installToolBoundary } : {}
|
|
25409
25569
|
}),
|
|
25410
25570
|
paths: {
|
|
@@ -26133,7 +26293,12 @@ function resolveSetupProvider(opts) {
|
|
|
26133
26293
|
baseUrl: config.baseUrl
|
|
26134
26294
|
};
|
|
26135
26295
|
try {
|
|
26136
|
-
const
|
|
26296
|
+
const routedConfig = routeProviderCfgThroughProxy(
|
|
26297
|
+
providerConfig,
|
|
26298
|
+
config.baseUrl,
|
|
26299
|
+
config.provider
|
|
26300
|
+
);
|
|
26301
|
+
const cfgWithType = { ...routedConfig, type: config.provider };
|
|
26137
26302
|
const provider2 = config.features.modelsRegistry && providerRegistry.has(config.provider) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig3(config.provider, cfgWithType);
|
|
26138
26303
|
return { provider: provider2, needsSetup: false };
|
|
26139
26304
|
} catch (err) {
|
|
@@ -26146,8 +26311,13 @@ function resolveSetupProvider(opts) {
|
|
|
26146
26311
|
if (firstKey) {
|
|
26147
26312
|
const firstProvider = expectDefined3(savedProviders[firstKey]);
|
|
26148
26313
|
try {
|
|
26314
|
+
const routedConfig = routeProviderCfgThroughProxy(
|
|
26315
|
+
firstProvider,
|
|
26316
|
+
config.baseUrl,
|
|
26317
|
+
firstKey
|
|
26318
|
+
);
|
|
26149
26319
|
const provider2 = makeProviderFromConfig3(firstKey, {
|
|
26150
|
-
...
|
|
26320
|
+
...routedConfig,
|
|
26151
26321
|
type: firstKey,
|
|
26152
26322
|
family: firstProvider.family,
|
|
26153
26323
|
apiKey: firstProvider.apiKey
|
|
@@ -26793,9 +26963,14 @@ function buildRoutes(state, deps2, cb) {
|
|
|
26793
26963
|
const cur = state.getConfig();
|
|
26794
26964
|
const newCfg = patchConfig(cur, { provider: newProvider, model: newModel });
|
|
26795
26965
|
const providerCfg = newCfg.providers?.[newProvider] ?? { type: newProvider };
|
|
26796
|
-
const
|
|
26966
|
+
const routedCfg = routeProviderCfgThroughProxy(
|
|
26967
|
+
providerCfg,
|
|
26968
|
+
newCfg.baseUrl,
|
|
26969
|
+
newProvider
|
|
26970
|
+
);
|
|
26971
|
+
const built = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...routedCfg, type: newProvider }) : makeProviderFromConfig4(newProvider, routedCfg);
|
|
26797
26972
|
const newProv = deps2.modelsRegistry ? await withCatalogCapabilities(deps2.modelsRegistry, newProvider, built, {
|
|
26798
|
-
...
|
|
26973
|
+
...routedCfg,
|
|
26799
26974
|
type: newProvider,
|
|
26800
26975
|
model: newModel
|
|
26801
26976
|
}) : built;
|
|
@@ -26807,7 +26982,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
26807
26982
|
deps2.configStore.update({ provider: newProvider, model: newModel });
|
|
26808
26983
|
deps2.context.model = newModel;
|
|
26809
26984
|
deps2.context.provider = newProv;
|
|
26810
|
-
await cb.updateAutoCompactionMaxContext(newProv, newProvider,
|
|
26985
|
+
await cb.updateAutoCompactionMaxContext(newProv, newProvider, routedCfg).catch((error2) => {
|
|
26811
26986
|
deps2.logger.warn(`model.switch capability refresh failed: ${String(error2)}`);
|
|
26812
26987
|
});
|
|
26813
26988
|
broadcast(state.getClients(), {
|
|
@@ -26948,6 +27123,13 @@ function buildRoutes(state, deps2, cb) {
|
|
|
26948
27123
|
if (typeof payload["fallbackAuto"] === "boolean")
|
|
26949
27124
|
config.fallbackAuto = payload["fallbackAuto"];
|
|
26950
27125
|
},
|
|
27126
|
+
// WrongProxy / WrongTrace: reflect the standalone toggle/URL into the
|
|
27127
|
+
// shared `ProxyConfig` singleton immediately and await the re-probe so
|
|
27128
|
+
// `active` is fresh before a subsequent model.switch reads it. In the
|
|
27129
|
+
// CLI-hosted path this same key is the CLI's `applyWrongProxyPrefs`; when
|
|
27130
|
+
// running as its own process there is no CLI to inject it, so route it to
|
|
27131
|
+
// the server-local runtime module.
|
|
27132
|
+
applyWrongProxyPrefs: (payload) => applyWrongProxyPrefs(payload),
|
|
26951
27133
|
setAutoCompact: (enabled) => {
|
|
26952
27134
|
deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
|
|
26953
27135
|
if (enabled && deps2.autoCompactor) {
|
|
@@ -27432,7 +27614,12 @@ function setupWebuiCredentialWatcher(options) {
|
|
|
27432
27614
|
...snapshot.apiKey !== void 0 ? { apiKey: snapshot.apiKey } : {},
|
|
27433
27615
|
...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {}
|
|
27434
27616
|
};
|
|
27435
|
-
const
|
|
27617
|
+
const routedCfg = routeProviderCfgThroughProxy(
|
|
27618
|
+
providerCfg,
|
|
27619
|
+
state.getConfig().baseUrl,
|
|
27620
|
+
activeId
|
|
27621
|
+
);
|
|
27622
|
+
const newProv = deps2.providerRegistry.has(activeId) ? deps2.providerRegistry.create({ ...routedCfg, type: activeId }) : makeProviderFromConfig5(activeId, { ...routedCfg, type: activeId });
|
|
27436
27623
|
deps2.context.provider = newProv;
|
|
27437
27624
|
void updateAutoCompactionMaxContext(newProv).catch(() => void 0);
|
|
27438
27625
|
console.log(`[WebUI] Provider credentials reloaded from config.json (${activeId})`);
|
|
@@ -27447,6 +27634,40 @@ function setupWebuiCredentialWatcher(options) {
|
|
|
27447
27634
|
return credentialWatcher.close;
|
|
27448
27635
|
}
|
|
27449
27636
|
|
|
27637
|
+
// src/server/start-webui-proxy-apply.ts
|
|
27638
|
+
import { createProxyInstantApply } from "@wrongstack/core/wiring/proxy-rewrite";
|
|
27639
|
+
import { makeProviderFromConfig as makeProviderFromConfig6, withCatalogCapabilities as withCatalogCapabilities2 } from "@wrongstack/providers";
|
|
27640
|
+
function setupWebuiProxyInstantApply(options) {
|
|
27641
|
+
const { state, deps: deps2, updateAutoCompactionMaxContext } = options;
|
|
27642
|
+
const instantApply = createProxyInstantApply({
|
|
27643
|
+
// Prefer the LIVE provider id over the config's — mirrors the CLI
|
|
27644
|
+
// wiring; the context is the source of truth once the session boots.
|
|
27645
|
+
getActiveProviderId: () => deps2.context.provider?.id ?? state.getConfig().provider ?? "",
|
|
27646
|
+
// Same raw-read rule as every other WebUI provider-build site:
|
|
27647
|
+
// savedCfg.baseUrl ?? top-level config.baseUrl.
|
|
27648
|
+
getRawBaseUrl: (providerId) => state.getConfig().providers?.[providerId]?.baseUrl ?? state.getConfig().baseUrl,
|
|
27649
|
+
rebuildProvider: async (providerId) => {
|
|
27650
|
+
await deps2.context.runModelTransition(async () => {
|
|
27651
|
+
if (deps2.context.provider?.id !== providerId) return;
|
|
27652
|
+
const cur = state.getConfig();
|
|
27653
|
+
const providerCfg = cur.providers?.[providerId] ?? { type: providerId };
|
|
27654
|
+
const routedCfg = routeProviderCfgThroughProxy(providerCfg, cur.baseUrl, providerId);
|
|
27655
|
+
const built = deps2.providerRegistry.has(providerId) ? deps2.providerRegistry.create({ ...routedCfg, type: providerId }) : makeProviderFromConfig6(providerId, routedCfg);
|
|
27656
|
+
const model = deps2.context.model ?? cur.model ?? "";
|
|
27657
|
+
const newProv = deps2.modelsRegistry ? await withCatalogCapabilities2(deps2.modelsRegistry, providerId, built, {
|
|
27658
|
+
...routedCfg,
|
|
27659
|
+
type: providerId,
|
|
27660
|
+
model
|
|
27661
|
+
}) : built;
|
|
27662
|
+
deps2.context.provider = newProv;
|
|
27663
|
+
void updateAutoCompactionMaxContext(newProv, providerId).catch(() => void 0);
|
|
27664
|
+
});
|
|
27665
|
+
},
|
|
27666
|
+
logger: deps2.logger
|
|
27667
|
+
});
|
|
27668
|
+
return () => instantApply.dispose();
|
|
27669
|
+
}
|
|
27670
|
+
|
|
27450
27671
|
// src/server/start-webui-remediation.ts
|
|
27451
27672
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
27452
27673
|
import { toLanguagePackageInput } from "@wrongstack/techstack";
|
|
@@ -27536,6 +27757,7 @@ function setupWebuiShutdown(options) {
|
|
|
27536
27757
|
await options.todosCheckpoint.detach();
|
|
27537
27758
|
await options.stopHeapWatchdog();
|
|
27538
27759
|
options.getCredentialWatcherClose()?.();
|
|
27760
|
+
options.getProxyInstantApplyDispose()();
|
|
27539
27761
|
options.disposeRealtimeHandlers();
|
|
27540
27762
|
const governanceCleanup = await options.governanceHandle?.close();
|
|
27541
27763
|
if (governanceCleanup && !governanceCleanup.ok) {
|
|
@@ -27634,6 +27856,7 @@ async function startWebUI(opts = {}) {
|
|
|
27634
27856
|
console.log("[WebUI] Starting backend services...");
|
|
27635
27857
|
const boot = await bootConfig();
|
|
27636
27858
|
const { config: baseConfig, globalConfigPath, wpaths, logger } = boot;
|
|
27859
|
+
await bootstrapWrongProxyFromConfig(baseConfig);
|
|
27637
27860
|
const vault = opts.services?.vault ?? boot.vault;
|
|
27638
27861
|
let config = baseConfig;
|
|
27639
27862
|
let projectRoot = boot.projectRoot;
|
|
@@ -28081,6 +28304,11 @@ async function startWebUI(opts = {}) {
|
|
|
28081
28304
|
clients,
|
|
28082
28305
|
updateAutoCompactionMaxContext
|
|
28083
28306
|
});
|
|
28307
|
+
const proxyInstantApplyDispose = setupWebuiProxyInstantApply({
|
|
28308
|
+
state,
|
|
28309
|
+
deps: deps2,
|
|
28310
|
+
updateAutoCompactionMaxContext
|
|
28311
|
+
});
|
|
28084
28312
|
const stopHeapWatchdog = startSharedHeapWatchdog({
|
|
28085
28313
|
collectStats: () => ({
|
|
28086
28314
|
surface: opts.surface ?? "webui",
|
|
@@ -28188,6 +28416,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
28188
28416
|
todosCheckpoint,
|
|
28189
28417
|
stopHeapWatchdog,
|
|
28190
28418
|
getCredentialWatcherClose: () => credentialWatcherClose,
|
|
28419
|
+
getProxyInstantApplyDispose: () => proxyInstantApplyDispose,
|
|
28191
28420
|
disposeRealtimeHandlers,
|
|
28192
28421
|
governanceHandle,
|
|
28193
28422
|
logger,
|
package/dist/server/entry.js
CHANGED
|
@@ -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:3444 → http://localhost:3444/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:3444'); 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,
|
|
@@ -179,7 +188,7 @@ var ENUM_PREF_KEYS = {
|
|
|
179
188
|
reasoningEffort: REASONING_EFFORT_VALUES,
|
|
180
189
|
cacheTtl: CACHE_TTL_VALUES,
|
|
181
190
|
statuslineMode: /* @__PURE__ */ new Set(["minimum", "detailed", "no-color"]),
|
|
182
|
-
animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "cycle"]),
|
|
191
|
+
animationStyle: /* @__PURE__ */ new Set(["rainbow", "wave", "pulse", "dots", "breathe", "static", "cycle"]),
|
|
183
192
|
fsAccess: /* @__PURE__ */ new Set(["unrestricted", "project"]),
|
|
184
193
|
// Chimera autoFix + auto-review cascade threshold
|
|
185
194
|
chimeraAutoFix: /* @__PURE__ */ new Set(["off", "ask", "auto"]),
|
|
@@ -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
|
}
|
|
@@ -6179,7 +6191,8 @@ function estimateTokens(s) {
|
|
|
6179
6191
|
function stringifyContent(c) {
|
|
6180
6192
|
if (typeof c === "string") return c;
|
|
6181
6193
|
try {
|
|
6182
|
-
|
|
6194
|
+
const serialized = JSON.stringify(c);
|
|
6195
|
+
return serialized === void 0 ? String(c) : serialized;
|
|
6183
6196
|
} catch {
|
|
6184
6197
|
return String(c);
|
|
6185
6198
|
}
|
|
@@ -7105,6 +7118,8 @@ function seedContextMeta(config, context) {
|
|
|
7105
7118
|
meta["tgDelegate"] = tgExt?.["notifyOnDelegate"] !== false;
|
|
7106
7119
|
const tgMs = tgExt?.["longToolThresholdMs"];
|
|
7107
7120
|
meta["tgLongToolMs"] = typeof tgMs === "number" ? tgMs : 3e4;
|
|
7121
|
+
meta["wrongProxyEnabled"] = config.tools?.wrongProxy?.enabled === true;
|
|
7122
|
+
meta["wrongProxyUrl"] = config.tools?.wrongProxy?.url ?? "";
|
|
7108
7123
|
{
|
|
7109
7124
|
const pluginsEnabled = {};
|
|
7110
7125
|
const record = (name2) => {
|
|
@@ -7471,6 +7486,85 @@ function createCustomModeStore(wrongstackDir) {
|
|
|
7471
7486
|
return { modes, load: load2, save: save2, create, update, remove, list };
|
|
7472
7487
|
}
|
|
7473
7488
|
|
|
7489
|
+
// src/server/proxy-runtime.ts
|
|
7490
|
+
import {
|
|
7491
|
+
applyProxyConfig,
|
|
7492
|
+
getProxyConfig,
|
|
7493
|
+
rewriteBaseUrl,
|
|
7494
|
+
shouldRewriteFor
|
|
7495
|
+
} from "@wrongstack/core/wiring/proxy-rewrite";
|
|
7496
|
+
var HEALTH_PATH = "/api/health";
|
|
7497
|
+
var PROBE_TIMEOUT_MS = 2e3;
|
|
7498
|
+
function seedWrongProxyFromConfig(config) {
|
|
7499
|
+
const wp = config.tools?.wrongProxy;
|
|
7500
|
+
if (!wp) {
|
|
7501
|
+
return;
|
|
7502
|
+
}
|
|
7503
|
+
applyProxyConfig({
|
|
7504
|
+
enabled: wp.enabled === true,
|
|
7505
|
+
url: typeof wp.url === "string" ? wp.url : ""
|
|
7506
|
+
});
|
|
7507
|
+
}
|
|
7508
|
+
async function probeWrongProxyActive() {
|
|
7509
|
+
const cfg = getProxyConfig();
|
|
7510
|
+
if (!cfg.enabled || !cfg.url) {
|
|
7511
|
+
applyProxyConfig({ active: false });
|
|
7512
|
+
return false;
|
|
7513
|
+
}
|
|
7514
|
+
const abort = new AbortController();
|
|
7515
|
+
const timeout = setTimeout(() => abort.abort(), PROBE_TIMEOUT_MS);
|
|
7516
|
+
if (typeof timeout.unref === "function") {
|
|
7517
|
+
timeout.unref();
|
|
7518
|
+
}
|
|
7519
|
+
try {
|
|
7520
|
+
const healthUrl = `${cfg.url.replace(/\/+$/, "")}${HEALTH_PATH}`;
|
|
7521
|
+
const res = await fetch(healthUrl, {
|
|
7522
|
+
method: "GET",
|
|
7523
|
+
signal: abort.signal,
|
|
7524
|
+
headers: { accept: "application/json" }
|
|
7525
|
+
});
|
|
7526
|
+
const ok2 = res.ok && res.status >= 200 && res.status < 300;
|
|
7527
|
+
applyProxyConfig({ active: ok2 });
|
|
7528
|
+
return ok2;
|
|
7529
|
+
} catch {
|
|
7530
|
+
applyProxyConfig({ active: false });
|
|
7531
|
+
return false;
|
|
7532
|
+
} finally {
|
|
7533
|
+
clearTimeout(timeout);
|
|
7534
|
+
}
|
|
7535
|
+
}
|
|
7536
|
+
async function applyWrongProxyPrefs(payload) {
|
|
7537
|
+
const patch = {};
|
|
7538
|
+
if (typeof payload["wrongProxyEnabled"] === "boolean") {
|
|
7539
|
+
patch.enabled = payload["wrongProxyEnabled"];
|
|
7540
|
+
}
|
|
7541
|
+
if (typeof payload["wrongProxyUrl"] === "string") {
|
|
7542
|
+
patch.url = payload["wrongProxyUrl"];
|
|
7543
|
+
}
|
|
7544
|
+
if (Object.keys(patch).length === 0) return;
|
|
7545
|
+
applyProxyConfig(patch);
|
|
7546
|
+
await probeWrongProxyActive();
|
|
7547
|
+
}
|
|
7548
|
+
async function bootstrapWrongProxyFromConfig(config) {
|
|
7549
|
+
seedWrongProxyFromConfig(config);
|
|
7550
|
+
await probeWrongProxyActive();
|
|
7551
|
+
}
|
|
7552
|
+
function routeProviderCfgThroughProxy(cfg, fallbackBaseUrl, providerId) {
|
|
7553
|
+
const raw = cfg;
|
|
7554
|
+
const cfgType = typeof raw.type === "string" ? raw.type : void 0;
|
|
7555
|
+
const cfgBaseUrl = typeof raw.baseUrl === "string" ? raw.baseUrl : void 0;
|
|
7556
|
+
const factoryType = cfgType ?? providerId;
|
|
7557
|
+
const rawBaseUrl = cfgBaseUrl ?? fallbackBaseUrl;
|
|
7558
|
+
if (!rawBaseUrl || !shouldRewriteFor(factoryType)) {
|
|
7559
|
+
return { ...cfg };
|
|
7560
|
+
}
|
|
7561
|
+
const baseUrl = rewriteBaseUrl(rawBaseUrl, getProxyConfig().url);
|
|
7562
|
+
return {
|
|
7563
|
+
...cfg,
|
|
7564
|
+
...baseUrl !== cfgBaseUrl ? { baseUrl } : {}
|
|
7565
|
+
};
|
|
7566
|
+
}
|
|
7567
|
+
|
|
7474
7568
|
// src/server/project-handlers.ts
|
|
7475
7569
|
import * as fs10 from "node:fs/promises";
|
|
7476
7570
|
import * as path11 from "node:path";
|
|
@@ -19402,7 +19496,13 @@ var PREF_KEYS = [
|
|
|
19402
19496
|
// Per-plugin enable/disable map (parity with the embedded server).
|
|
19403
19497
|
"pluginsEnabled",
|
|
19404
19498
|
// Fleet chat verbosity: off | full (migrated from streamFleet boolean).
|
|
19405
|
-
"fleetChatVerbosity"
|
|
19499
|
+
"fleetChatVerbosity",
|
|
19500
|
+
// WrongProxy / WrongTrace: master switch + configurable URL (default
|
|
19501
|
+
// http://localhost:3444). When `wrongProxyEnabled` is true and the daemon
|
|
19502
|
+
// is reachable, every provider's base URL flows through
|
|
19503
|
+
// `${wrongProxyUrl}/proxy/<host><path>`. openai-codex is excluded by spec.
|
|
19504
|
+
"wrongProxyEnabled",
|
|
19505
|
+
"wrongProxyUrl"
|
|
19406
19506
|
];
|
|
19407
19507
|
function prefSnapshot(contextMeta) {
|
|
19408
19508
|
const snapshot = {};
|
|
@@ -19668,6 +19768,15 @@ async function persistPrefsToConfig(deps2, holder, payload) {
|
|
|
19668
19768
|
}
|
|
19669
19769
|
if (typeof payload["debugStream"] === "boolean")
|
|
19670
19770
|
decrypted.debugStream = payload["debugStream"];
|
|
19771
|
+
if (typeof payload["wrongProxyEnabled"] === "boolean" || typeof payload["wrongProxyUrl"] === "string") {
|
|
19772
|
+
const toolsCfg = decrypted.tools ?? {};
|
|
19773
|
+
const wp = toolsCfg["wrongProxy"] ?? {};
|
|
19774
|
+
if (typeof payload["wrongProxyEnabled"] === "boolean")
|
|
19775
|
+
wp["enabled"] = payload["wrongProxyEnabled"];
|
|
19776
|
+
if (typeof payload["wrongProxyUrl"] === "string") wp["url"] = payload["wrongProxyUrl"];
|
|
19777
|
+
toolsCfg["wrongProxy"] = wp;
|
|
19778
|
+
decrypted.tools = toolsCfg;
|
|
19779
|
+
}
|
|
19671
19780
|
if (typeof payload["pluginsEnabled"] === "object" && payload["pluginsEnabled"] !== null) {
|
|
19672
19781
|
const ext = decrypted.extensions ?? {};
|
|
19673
19782
|
const toggled = [];
|
|
@@ -22333,6 +22442,8 @@ import {
|
|
|
22333
22442
|
// src/server/backend-services.ts
|
|
22334
22443
|
import { join as join12 } from "node:path";
|
|
22335
22444
|
import { Agent } from "@wrongstack/core/agent";
|
|
22445
|
+
import { HookRegistry, HookRunner } from "@wrongstack/core/hooks";
|
|
22446
|
+
import { createWrongTraceHookPair, recordGateDecision, snapshotGateDecisions, persistWrongTraceGateCounters } from "@wrongstack/wrongtrace";
|
|
22336
22447
|
import {
|
|
22337
22448
|
BrainDecisionLedger,
|
|
22338
22449
|
BrainMonitor,
|
|
@@ -23374,6 +23485,33 @@ async function createAgentServices(input) {
|
|
|
23374
23485
|
const secretScrubber = container.resolve(TOKENS.SecretScrubber);
|
|
23375
23486
|
const renderer = container.has(TOKENS.Renderer) ? container.resolve(TOKENS.Renderer) : void 0;
|
|
23376
23487
|
const permissionPolicy = container.resolve(TOKENS.PermissionPolicy);
|
|
23488
|
+
const wrongTraceHookRegistry = new HookRegistry();
|
|
23489
|
+
const wrongTraceHooks = createWrongTraceHookPair(() => context.session.id, {
|
|
23490
|
+
emit: (event) => {
|
|
23491
|
+
events.emit("wrongtrace.gate.decision", event);
|
|
23492
|
+
recordGateDecision(event);
|
|
23493
|
+
void persistWrongTraceGateCounters(projectRoot, snapshotGateDecisions());
|
|
23494
|
+
}
|
|
23495
|
+
});
|
|
23496
|
+
wrongTraceHookRegistry.registerInProcess(
|
|
23497
|
+
"PreToolUse",
|
|
23498
|
+
"edit|write|replace|patch|codebase-ast-replace",
|
|
23499
|
+
wrongTraceHooks.preToolUse,
|
|
23500
|
+
"wrongtrace-gate"
|
|
23501
|
+
);
|
|
23502
|
+
wrongTraceHookRegistry.registerInProcess(
|
|
23503
|
+
"PostToolUse",
|
|
23504
|
+
"edit|write|replace|patch|codebase-ast-replace",
|
|
23505
|
+
wrongTraceHooks.postToolUse,
|
|
23506
|
+
"wrongtrace-gate"
|
|
23507
|
+
);
|
|
23508
|
+
const wrongTraceHookRunner = new HookRunner({
|
|
23509
|
+
registry: wrongTraceHookRegistry,
|
|
23510
|
+
sessionId: () => context.session.id,
|
|
23511
|
+
// Coordination, not enforcement: these hooks are fail-open by
|
|
23512
|
+
// construction and must run regardless of shell-hook gating.
|
|
23513
|
+
allowNonPolicy: true
|
|
23514
|
+
});
|
|
23377
23515
|
const toolExecutor = new ToolExecutor(toolRegistry, {
|
|
23378
23516
|
permissionPolicy,
|
|
23379
23517
|
secretScrubber,
|
|
@@ -23383,6 +23521,7 @@ async function createAgentServices(input) {
|
|
|
23383
23521
|
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
23384
23522
|
perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
23385
23523
|
tracer: void 0,
|
|
23524
|
+
hookRunner: wrongTraceHookRunner,
|
|
23386
23525
|
// Off unless the operator opts in. The WebUI drives the same agent as the
|
|
23387
23526
|
// CLI, so it must resolve this identically — a surface-dependent gate
|
|
23388
23527
|
// would mean the same repo governs under `wstack` and not in the browser.
|
|
@@ -23604,6 +23743,12 @@ async function createAgentServices(input) {
|
|
|
23604
23743
|
// round-robin keeps reassigning the doomed model. Mirrors the CLI
|
|
23605
23744
|
// factory wiring at host-subagent-factory.ts:337.
|
|
23606
23745
|
statusTracker: container.safeResolve(TOKENS.ProviderModelStatusTracker),
|
|
23746
|
+
// WrongTrace lock gate for SDD-wizard workers: the standalone server
|
|
23747
|
+
// already built a dedicated WrongTrace-only HookRunner for its own
|
|
23748
|
+
// executor above; handing the same runner to the runtime factory
|
|
23749
|
+
// makes every worker edit honor peer locks with one process-wide
|
|
23750
|
+
// owner identity (context.session.id).
|
|
23751
|
+
hookRunner: wrongTraceHookRunner,
|
|
23607
23752
|
...input.installToolBoundary ? { installToolBoundary: input.installToolBoundary } : {}
|
|
23608
23753
|
}),
|
|
23609
23754
|
paths: {
|
|
@@ -24332,7 +24477,12 @@ function resolveSetupProvider(opts) {
|
|
|
24332
24477
|
baseUrl: config.baseUrl
|
|
24333
24478
|
};
|
|
24334
24479
|
try {
|
|
24335
|
-
const
|
|
24480
|
+
const routedConfig = routeProviderCfgThroughProxy(
|
|
24481
|
+
providerConfig,
|
|
24482
|
+
config.baseUrl,
|
|
24483
|
+
config.provider
|
|
24484
|
+
);
|
|
24485
|
+
const cfgWithType = { ...routedConfig, type: config.provider };
|
|
24336
24486
|
const provider2 = config.features.modelsRegistry && providerRegistry.has(config.provider) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig(config.provider, cfgWithType);
|
|
24337
24487
|
return { provider: provider2, needsSetup: false };
|
|
24338
24488
|
} catch (err) {
|
|
@@ -24345,8 +24495,13 @@ function resolveSetupProvider(opts) {
|
|
|
24345
24495
|
if (firstKey) {
|
|
24346
24496
|
const firstProvider = expectDefined2(savedProviders[firstKey]);
|
|
24347
24497
|
try {
|
|
24498
|
+
const routedConfig = routeProviderCfgThroughProxy(
|
|
24499
|
+
firstProvider,
|
|
24500
|
+
config.baseUrl,
|
|
24501
|
+
firstKey
|
|
24502
|
+
);
|
|
24348
24503
|
const provider2 = makeProviderFromConfig(firstKey, {
|
|
24349
|
-
...
|
|
24504
|
+
...routedConfig,
|
|
24350
24505
|
type: firstKey,
|
|
24351
24506
|
family: firstProvider.family,
|
|
24352
24507
|
apiKey: firstProvider.apiKey
|
|
@@ -24992,9 +25147,14 @@ function buildRoutes(state, deps2, cb) {
|
|
|
24992
25147
|
const cur = state.getConfig();
|
|
24993
25148
|
const newCfg = patchConfig(cur, { provider: newProvider, model: newModel });
|
|
24994
25149
|
const providerCfg = newCfg.providers?.[newProvider] ?? { type: newProvider };
|
|
24995
|
-
const
|
|
25150
|
+
const routedCfg = routeProviderCfgThroughProxy(
|
|
25151
|
+
providerCfg,
|
|
25152
|
+
newCfg.baseUrl,
|
|
25153
|
+
newProvider
|
|
25154
|
+
);
|
|
25155
|
+
const built = deps2.providerRegistry.has(newProvider) ? deps2.providerRegistry.create({ ...routedCfg, type: newProvider }) : makeProviderFromConfig2(newProvider, routedCfg);
|
|
24996
25156
|
const newProv = deps2.modelsRegistry ? await withCatalogCapabilities(deps2.modelsRegistry, newProvider, built, {
|
|
24997
|
-
...
|
|
25157
|
+
...routedCfg,
|
|
24998
25158
|
type: newProvider,
|
|
24999
25159
|
model: newModel
|
|
25000
25160
|
}) : built;
|
|
@@ -25006,7 +25166,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
25006
25166
|
deps2.configStore.update({ provider: newProvider, model: newModel });
|
|
25007
25167
|
deps2.context.model = newModel;
|
|
25008
25168
|
deps2.context.provider = newProv;
|
|
25009
|
-
await cb.updateAutoCompactionMaxContext(newProv, newProvider,
|
|
25169
|
+
await cb.updateAutoCompactionMaxContext(newProv, newProvider, routedCfg).catch((error2) => {
|
|
25010
25170
|
deps2.logger.warn(`model.switch capability refresh failed: ${String(error2)}`);
|
|
25011
25171
|
});
|
|
25012
25172
|
broadcast(state.getClients(), {
|
|
@@ -25147,6 +25307,13 @@ function buildRoutes(state, deps2, cb) {
|
|
|
25147
25307
|
if (typeof payload["fallbackAuto"] === "boolean")
|
|
25148
25308
|
config.fallbackAuto = payload["fallbackAuto"];
|
|
25149
25309
|
},
|
|
25310
|
+
// WrongProxy / WrongTrace: reflect the standalone toggle/URL into the
|
|
25311
|
+
// shared `ProxyConfig` singleton immediately and await the re-probe so
|
|
25312
|
+
// `active` is fresh before a subsequent model.switch reads it. In the
|
|
25313
|
+
// CLI-hosted path this same key is the CLI's `applyWrongProxyPrefs`; when
|
|
25314
|
+
// running as its own process there is no CLI to inject it, so route it to
|
|
25315
|
+
// the server-local runtime module.
|
|
25316
|
+
applyWrongProxyPrefs: (payload) => applyWrongProxyPrefs(payload),
|
|
25150
25317
|
setAutoCompact: (enabled) => {
|
|
25151
25318
|
deps2.pipelines.contextWindow.remove("AutoCompaction", { optional: true });
|
|
25152
25319
|
if (enabled && deps2.autoCompactor) {
|
|
@@ -25631,7 +25798,12 @@ function setupWebuiCredentialWatcher(options) {
|
|
|
25631
25798
|
...snapshot.apiKey !== void 0 ? { apiKey: snapshot.apiKey } : {},
|
|
25632
25799
|
...snapshot.baseUrl !== void 0 ? { baseUrl: snapshot.baseUrl } : {}
|
|
25633
25800
|
};
|
|
25634
|
-
const
|
|
25801
|
+
const routedCfg = routeProviderCfgThroughProxy(
|
|
25802
|
+
providerCfg,
|
|
25803
|
+
state.getConfig().baseUrl,
|
|
25804
|
+
activeId
|
|
25805
|
+
);
|
|
25806
|
+
const newProv = deps2.providerRegistry.has(activeId) ? deps2.providerRegistry.create({ ...routedCfg, type: activeId }) : makeProviderFromConfig3(activeId, { ...routedCfg, type: activeId });
|
|
25635
25807
|
deps2.context.provider = newProv;
|
|
25636
25808
|
void updateAutoCompactionMaxContext(newProv).catch(() => void 0);
|
|
25637
25809
|
console.log(`[WebUI] Provider credentials reloaded from config.json (${activeId})`);
|
|
@@ -25646,6 +25818,40 @@ function setupWebuiCredentialWatcher(options) {
|
|
|
25646
25818
|
return credentialWatcher.close;
|
|
25647
25819
|
}
|
|
25648
25820
|
|
|
25821
|
+
// src/server/start-webui-proxy-apply.ts
|
|
25822
|
+
import { createProxyInstantApply } from "@wrongstack/core/wiring/proxy-rewrite";
|
|
25823
|
+
import { makeProviderFromConfig as makeProviderFromConfig4, withCatalogCapabilities as withCatalogCapabilities2 } from "@wrongstack/providers";
|
|
25824
|
+
function setupWebuiProxyInstantApply(options) {
|
|
25825
|
+
const { state, deps: deps2, updateAutoCompactionMaxContext } = options;
|
|
25826
|
+
const instantApply = createProxyInstantApply({
|
|
25827
|
+
// Prefer the LIVE provider id over the config's — mirrors the CLI
|
|
25828
|
+
// wiring; the context is the source of truth once the session boots.
|
|
25829
|
+
getActiveProviderId: () => deps2.context.provider?.id ?? state.getConfig().provider ?? "",
|
|
25830
|
+
// Same raw-read rule as every other WebUI provider-build site:
|
|
25831
|
+
// savedCfg.baseUrl ?? top-level config.baseUrl.
|
|
25832
|
+
getRawBaseUrl: (providerId) => state.getConfig().providers?.[providerId]?.baseUrl ?? state.getConfig().baseUrl,
|
|
25833
|
+
rebuildProvider: async (providerId) => {
|
|
25834
|
+
await deps2.context.runModelTransition(async () => {
|
|
25835
|
+
if (deps2.context.provider?.id !== providerId) return;
|
|
25836
|
+
const cur = state.getConfig();
|
|
25837
|
+
const providerCfg = cur.providers?.[providerId] ?? { type: providerId };
|
|
25838
|
+
const routedCfg = routeProviderCfgThroughProxy(providerCfg, cur.baseUrl, providerId);
|
|
25839
|
+
const built = deps2.providerRegistry.has(providerId) ? deps2.providerRegistry.create({ ...routedCfg, type: providerId }) : makeProviderFromConfig4(providerId, routedCfg);
|
|
25840
|
+
const model = deps2.context.model ?? cur.model ?? "";
|
|
25841
|
+
const newProv = deps2.modelsRegistry ? await withCatalogCapabilities2(deps2.modelsRegistry, providerId, built, {
|
|
25842
|
+
...routedCfg,
|
|
25843
|
+
type: providerId,
|
|
25844
|
+
model
|
|
25845
|
+
}) : built;
|
|
25846
|
+
deps2.context.provider = newProv;
|
|
25847
|
+
void updateAutoCompactionMaxContext(newProv, providerId).catch(() => void 0);
|
|
25848
|
+
});
|
|
25849
|
+
},
|
|
25850
|
+
logger: deps2.logger
|
|
25851
|
+
});
|
|
25852
|
+
return () => instantApply.dispose();
|
|
25853
|
+
}
|
|
25854
|
+
|
|
25649
25855
|
// src/server/start-webui-remediation.ts
|
|
25650
25856
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
25651
25857
|
import { toLanguagePackageInput } from "@wrongstack/techstack";
|
|
@@ -25735,6 +25941,7 @@ function setupWebuiShutdown(options) {
|
|
|
25735
25941
|
await options.todosCheckpoint.detach();
|
|
25736
25942
|
await options.stopHeapWatchdog();
|
|
25737
25943
|
options.getCredentialWatcherClose()?.();
|
|
25944
|
+
options.getProxyInstantApplyDispose()();
|
|
25738
25945
|
options.disposeRealtimeHandlers();
|
|
25739
25946
|
const governanceCleanup = await options.governanceHandle?.close();
|
|
25740
25947
|
if (governanceCleanup && !governanceCleanup.ok) {
|
|
@@ -25833,6 +26040,7 @@ async function startWebUI(opts = {}) {
|
|
|
25833
26040
|
console.log("[WebUI] Starting backend services...");
|
|
25834
26041
|
const boot = await bootConfig();
|
|
25835
26042
|
const { config: baseConfig, globalConfigPath, wpaths, logger } = boot;
|
|
26043
|
+
await bootstrapWrongProxyFromConfig(baseConfig);
|
|
25836
26044
|
const vault = opts.services?.vault ?? boot.vault;
|
|
25837
26045
|
let config = baseConfig;
|
|
25838
26046
|
let projectRoot = boot.projectRoot;
|
|
@@ -26280,6 +26488,11 @@ async function startWebUI(opts = {}) {
|
|
|
26280
26488
|
clients,
|
|
26281
26489
|
updateAutoCompactionMaxContext
|
|
26282
26490
|
});
|
|
26491
|
+
const proxyInstantApplyDispose = setupWebuiProxyInstantApply({
|
|
26492
|
+
state,
|
|
26493
|
+
deps: deps2,
|
|
26494
|
+
updateAutoCompactionMaxContext
|
|
26495
|
+
});
|
|
26283
26496
|
const stopHeapWatchdog = startSharedHeapWatchdog({
|
|
26284
26497
|
collectStats: () => ({
|
|
26285
26498
|
surface: opts.surface ?? "webui",
|
|
@@ -26387,6 +26600,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
26387
26600
|
todosCheckpoint,
|
|
26388
26601
|
stopHeapWatchdog,
|
|
26389
26602
|
getCredentialWatcherClose: () => credentialWatcherClose,
|
|
26603
|
+
getProxyInstantApplyDispose: () => proxyInstantApplyDispose,
|
|
26390
26604
|
disposeRealtimeHandlers,
|
|
26391
26605
|
governanceHandle,
|
|
26392
26606
|
logger,
|
|
@@ -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
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WrongProxy instant-apply for the standalone WebUI server.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the CLI-side wiring in `setupProviderRuntime` (packages/cli/src/
|
|
5
|
+
* wiring/provider-runtime-setup.ts): subscribe to material proxy-config
|
|
6
|
+
* changes via `createProxyInstantApply` and rebuild the LIVE provider when
|
|
7
|
+
* the routing verdict for the active provider flips, so a Settings toggle
|
|
8
|
+
* or probe deactivation takes effect immediately instead of on the next
|
|
9
|
+
* incidental provider build.
|
|
10
|
+
*
|
|
11
|
+
* The rebuild sequence mirrors `applyModelSwitchCore` (routes.ts) — route
|
|
12
|
+
* the cfg through `routeProviderCfgThroughProxy`, build via registry or
|
|
13
|
+
* `makeProviderFromConfig`, overlay the live model's catalog capabilities
|
|
14
|
+
* — minus the parts that don't apply when provider+model are unchanged
|
|
15
|
+
* (no config persist, no session.start spam). The swap itself runs inside
|
|
16
|
+
* `runModelTransition` with a superseded-check so a queued rebuild can
|
|
17
|
+
* never overwrite a newer /model switch.
|
|
18
|
+
*
|
|
19
|
+
* In the CLI-hosted path this server module is NOT wired (the CLI's own
|
|
20
|
+
* `setupProviderRuntime` instant-apply covers the shared agent context),
|
|
21
|
+
* so there is exactly one rebuilder per process.
|
|
22
|
+
*/
|
|
23
|
+
import type { Provider, ProviderConfig } from '@wrongstack/core/types';
|
|
24
|
+
import type { WebuiDeps, WebuiMutableState } from './routes.js';
|
|
25
|
+
export interface WebuiProxyApplyOptions {
|
|
26
|
+
state: WebuiMutableState;
|
|
27
|
+
deps: WebuiDeps;
|
|
28
|
+
/** Mirrors `cb.updateAutoCompactionMaxContext` in start-webui.ts. */
|
|
29
|
+
updateAutoCompactionMaxContext: (provider: Provider, providerId?: string, providerCfg?: ProviderConfig | undefined) => Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Wire the standalone server's WrongProxy instant-apply. Returns the
|
|
33
|
+
* dispose function for shutdown wiring. The helper itself lives in
|
|
34
|
+
* `@wrongstack/core` so both hosts share the identical change-detection
|
|
35
|
+
* semantics (effective-URL comparison, serialized rebuilds).
|
|
36
|
+
*/
|
|
37
|
+
export declare function setupWebuiProxyInstantApply(options: WebuiProxyApplyOptions): () => void;
|
|
38
|
+
//# sourceMappingURL=start-webui-proxy-apply.d.ts.map
|
|
@@ -18,6 +18,7 @@ export declare function setupWebuiShutdown(options: {
|
|
|
18
18
|
};
|
|
19
19
|
stopHeapWatchdog: () => Promise<void>;
|
|
20
20
|
getCredentialWatcherClose: () => (() => void) | undefined;
|
|
21
|
+
getProxyInstantApplyDispose: () => () => void;
|
|
21
22
|
disposeRealtimeHandlers: () => void;
|
|
22
23
|
governanceHandle?: {
|
|
23
24
|
close: () => Promise<{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/webui-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.313.0",
|
|
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,19 @@
|
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"ws": "^8.21.3",
|
|
39
|
-
"@wrongstack/
|
|
40
|
-
"@wrongstack/
|
|
41
|
-
"@wrongstack/
|
|
42
|
-
"@wrongstack/
|
|
43
|
-
"@wrongstack/
|
|
44
|
-
"@wrongstack/
|
|
45
|
-
"@wrongstack/
|
|
46
|
-
"@wrongstack/
|
|
47
|
-
"@wrongstack/
|
|
48
|
-
"@wrongstack/
|
|
49
|
-
"@wrongstack/
|
|
50
|
-
"@wrongstack/
|
|
39
|
+
"@wrongstack/core": "0.313.0",
|
|
40
|
+
"@wrongstack/mcp": "0.313.0",
|
|
41
|
+
"@wrongstack/kanban": "0.313.0",
|
|
42
|
+
"@wrongstack/sage": "0.313.0",
|
|
43
|
+
"@wrongstack/runtime": "0.313.0",
|
|
44
|
+
"@wrongstack/providers": "0.313.0",
|
|
45
|
+
"@wrongstack/sdd": "0.313.0",
|
|
46
|
+
"@wrongstack/requirement-intake": "0.313.0",
|
|
47
|
+
"@wrongstack/techstack": "0.313.0",
|
|
48
|
+
"@wrongstack/tools": "0.313.0",
|
|
49
|
+
"@wrongstack/vector-memory": "0.313.0",
|
|
50
|
+
"@wrongstack/wrongtrace": "0.313.0",
|
|
51
|
+
"@wrongstack/webui-protocol": "0.313.0"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@types/node": "^26.2.0",
|