@wrongstack/webui 0.5.7 → 0.6.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.
@@ -1,5 +1,6 @@
1
1
  // src/server/index.ts
2
2
  import * as fs2 from "fs/promises";
3
+ import * as http from "http";
3
4
  import * as path from "path";
4
5
  import {
5
6
  Agent,
@@ -24,6 +25,7 @@ import {
24
25
  repairToolUseAdjacency,
25
26
  resolveContextWindowPolicy
26
27
  } from "@wrongstack/core";
28
+ import { ToolExecutor } from "@wrongstack/core/execution";
27
29
  import { decryptConfigSecrets, encryptConfigSecrets } from "@wrongstack/core/security";
28
30
  import { buildProviderFactoriesFromRegistry, makeProviderFromConfig } from "@wrongstack/providers";
29
31
  import { builtinToolsPack, forgetTool, rememberTool } from "@wrongstack/tools";
@@ -83,7 +85,13 @@ async function startWebUI(opts = {}) {
83
85
  const { config: baseConfig, vault, globalConfigPath, projectRoot, wpaths, logger } = boot;
84
86
  let config = baseConfig;
85
87
  let configWriteLock = Promise.resolve();
86
- console.log("[WebUI] Config loaded:", config.provider, "/", config.model);
88
+ console.log("[WebUI] Config loaded:", config.provider ?? "(none)", "/", config.model ?? "(none)");
89
+ if (!config.provider && config.providers && Object.keys(config.providers).length > 0) {
90
+ const firstKey = Object.keys(config.providers)[0];
91
+ config = patchConfig(config, { provider: firstKey });
92
+ console.log("[WebUI] No active provider \u2014 auto-selected:", firstKey);
93
+ }
94
+ const needsProvider = !config.provider || !config.model;
87
95
  const modelsRegistry = new DefaultModelsRegistry({
88
96
  cacheFile: wpaths.modelsCache,
89
97
  ttlSeconds: 24 * 3600
@@ -151,22 +159,46 @@ async function startWebUI(opts = {}) {
151
159
  provider: config.provider,
152
160
  model: config.model
153
161
  });
154
- const providerConfig = config.providers?.[config.provider] ?? {
155
- type: config.provider,
156
- apiKey: config.apiKey,
157
- baseUrl: config.baseUrl
158
- };
159
162
  let provider;
160
- try {
161
- const cfgWithType = { ...providerConfig, type: config.provider };
162
- if (config.features.modelsRegistry && providerRegistry.has(config.provider)) {
163
- provider = providerRegistry.create(cfgWithType);
163
+ if (!needsProvider) {
164
+ const providerConfig = config.providers?.[config.provider] ?? {
165
+ type: config.provider,
166
+ apiKey: config.apiKey,
167
+ baseUrl: config.baseUrl
168
+ };
169
+ try {
170
+ const cfgWithType = { ...providerConfig, type: config.provider };
171
+ if (config.features.modelsRegistry && providerRegistry.has(config.provider)) {
172
+ provider = providerRegistry.create(cfgWithType);
173
+ } else {
174
+ provider = makeProviderFromConfig(config.provider, cfgWithType);
175
+ }
176
+ } catch (err) {
177
+ console.error("[WebUI] Failed to create provider:", err);
178
+ throw err;
179
+ }
180
+ } else {
181
+ const savedProviders = config.providers ?? {};
182
+ const firstKey = Object.keys(savedProviders)[0];
183
+ if (firstKey) {
184
+ const firstProvider = savedProviders[firstKey];
185
+ try {
186
+ provider = makeProviderFromConfig(firstKey, {
187
+ ...firstProvider,
188
+ type: firstKey,
189
+ family: firstProvider.family,
190
+ apiKey: firstProvider.apiKey
191
+ });
192
+ console.log("[WebUI] Using saved provider:", firstKey);
193
+ } catch (err) {
194
+ console.error("[WebUI] Could not create provider stub:", err);
195
+ throw err;
196
+ }
164
197
  } else {
165
- provider = makeProviderFromConfig(config.provider, cfgWithType);
198
+ throw new Error(
199
+ "No provider configured. Run `wrongstack init` first, or configure via the WebUI."
200
+ );
166
201
  }
167
- } catch (err) {
168
- console.error("[WebUI] Failed to create provider:", err);
169
- throw err;
170
202
  }
171
203
  const context = new Context({
172
204
  systemPrompt,
@@ -228,6 +260,18 @@ async function startWebUI(opts = {}) {
228
260
  }
229
261
  autoCompactor.setMaxContext(newMaxContext);
230
262
  }
263
+ const secretScrubber = container.resolve(TOKENS.SecretScrubber);
264
+ const renderer = container.has(TOKENS.Renderer) ? container.resolve(TOKENS.Renderer) : void 0;
265
+ const toolExecutor = new ToolExecutor(toolRegistry, {
266
+ permissionPolicy: container.resolve(TOKENS.PermissionPolicy),
267
+ secretScrubber,
268
+ renderer,
269
+ events,
270
+ confirmAwaiter: void 0,
271
+ iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? 12e4,
272
+ perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? 5e4,
273
+ tracer: void 0
274
+ });
231
275
  const agent = new Agent({
232
276
  container,
233
277
  tools: toolRegistry,
@@ -239,7 +283,8 @@ async function startWebUI(opts = {}) {
239
283
  iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? 12e4,
240
284
  executionStrategy: config.tools?.defaultExecutionStrategy ?? "sequential",
241
285
  perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? 5e4,
242
- confirmAwaiter: void 0
286
+ confirmAwaiter: void 0,
287
+ toolExecutor
243
288
  });
244
289
  console.log("[WebUI] Agent initialized");
245
290
  async function sessionStartPayload() {
@@ -458,8 +503,8 @@ async function startWebUI(opts = {}) {
458
503
  rateLimits.delete(ws);
459
504
  console.log("[WebUI] Client disconnected, total:", clients.size);
460
505
  if (pendingConfirms.size > 0) {
461
- for (const [id, resolve] of pendingConfirms) {
462
- resolve("no");
506
+ for (const [id, resolve2] of pendingConfirms) {
507
+ resolve2("no");
463
508
  pendingConfirms.delete(id);
464
509
  }
465
510
  }
@@ -539,10 +584,10 @@ async function startWebUI(opts = {}) {
539
584
  }
540
585
  case "tool.confirm_result": {
541
586
  const { id, decision } = msg.payload;
542
- const resolve = pendingConfirms.get(id);
543
- if (resolve) {
587
+ const resolve2 = pendingConfirms.get(id);
588
+ if (resolve2) {
544
589
  pendingConfirms.delete(id);
545
- resolve(decision);
590
+ resolve2(decision);
546
591
  }
547
592
  break;
548
593
  }
@@ -1404,6 +1449,58 @@ async function startWebUI(opts = {}) {
1404
1449
  sendResult(ws, false, err instanceof Error ? err.message : String(err));
1405
1450
  }
1406
1451
  }
1452
+ const httpPort = Number.parseInt(process.env["PORT"] ?? "3456", 10);
1453
+ const DIST_DIR = path.resolve(import.meta.dirname, "../../dist");
1454
+ const mimeTypes = {
1455
+ ".html": "text/html",
1456
+ ".js": "application/javascript",
1457
+ ".css": "text/css",
1458
+ ".json": "application/json",
1459
+ ".svg": "image/svg+xml",
1460
+ ".png": "image/png",
1461
+ ".ico": "image/x-icon"
1462
+ };
1463
+ const httpServer = http.createServer(async (req, res) => {
1464
+ try {
1465
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${httpPort}`);
1466
+ let filePath;
1467
+ if (url.pathname === "/" || url.pathname === "") {
1468
+ filePath = path.join(DIST_DIR, "index.html");
1469
+ } else if (url.pathname.startsWith("/assets/")) {
1470
+ filePath = path.join(DIST_DIR, url.pathname);
1471
+ } else if (url.pathname.startsWith("/")) {
1472
+ filePath = path.join(DIST_DIR, url.pathname);
1473
+ } else {
1474
+ filePath = path.join(DIST_DIR, "index.html");
1475
+ }
1476
+ const ext = path.extname(filePath);
1477
+ const contentType = mimeTypes[ext] ?? "application/octet-stream";
1478
+ res.setHeader("Content-Type", contentType);
1479
+ if (ext === ".html") {
1480
+ res.setHeader("Cache-Control", "no-cache");
1481
+ }
1482
+ const fileContent = await fs2.readFile(filePath);
1483
+ res.writeHead(200);
1484
+ res.end(fileContent);
1485
+ } catch (err) {
1486
+ if (err.code === "ENOENT") {
1487
+ try {
1488
+ const fileContent = await fs2.readFile(path.join(DIST_DIR, "index.html"));
1489
+ res.writeHead(200, { "Content-Type": "text/html" });
1490
+ res.end(fileContent);
1491
+ } catch {
1492
+ res.writeHead(404);
1493
+ res.end("Not found");
1494
+ }
1495
+ } else {
1496
+ res.writeHead(500);
1497
+ res.end("Server error");
1498
+ }
1499
+ }
1500
+ });
1501
+ httpServer.listen(httpPort, wsHost, () => {
1502
+ console.log(`[WebUI] HTTP server running on http://${wsHost}:${httpPort}`);
1503
+ });
1407
1504
  const shutdown = async () => {
1408
1505
  console.log("[WebUI] Shutting down...");
1409
1506
  try {
@@ -1417,6 +1514,7 @@ async function startWebUI(opts = {}) {
1417
1514
  console.warn("[WebUI] Error closing session:", e);
1418
1515
  }
1419
1516
  for (const [ws] of clients) ws.close();
1517
+ httpServer.close();
1420
1518
  wssPrimary.close();
1421
1519
  wssSecondary?.close();
1422
1520
  process.exit(0);