@wrongstack/webui 0.3.1 → 0.3.2
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/assets/index-CJmc6zwr.js +94 -0
- package/dist/assets/{index-CRAjL4S_.css → index-TCwASaz8.css} +1 -1
- package/dist/assets/vendor-Dff2jyfM.js +521 -0
- package/dist/index.css +14 -0
- package/dist/index.css.map +1 -1
- package/dist/index.html +3 -2
- package/dist/index.js +1054 -899
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +135 -49
- package/dist/server/entry.js.map +1 -1
- package/dist/server/index.js +135 -49
- package/dist/server/index.js.map +1 -1
- package/package.json +21 -14
- package/dist/assets/index-C1jTV4WR.js +0 -609
package/dist/server/entry.js
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// src/server/index.ts
|
|
3
|
-
import * as
|
|
4
|
-
import * as os from "os";
|
|
3
|
+
import * as fs2 from "fs/promises";
|
|
5
4
|
import * as path from "path";
|
|
6
5
|
import {
|
|
7
6
|
Agent,
|
|
8
7
|
AutoCompactionMiddleware,
|
|
9
|
-
Container,
|
|
10
8
|
Context,
|
|
11
|
-
DefaultConfigLoader,
|
|
12
|
-
DefaultConfigStore,
|
|
13
|
-
DefaultErrorHandler,
|
|
14
|
-
DefaultLogger,
|
|
15
9
|
DefaultMemoryStore,
|
|
16
10
|
DefaultModeStore,
|
|
17
11
|
DefaultModelsRegistry,
|
|
18
|
-
DefaultPathResolver,
|
|
19
|
-
DefaultPermissionPolicy,
|
|
20
|
-
DefaultRetryPolicy,
|
|
21
|
-
DefaultSecretScrubber,
|
|
22
|
-
DefaultSecretVault,
|
|
23
12
|
DefaultSessionStore,
|
|
24
13
|
DefaultSkillLoader,
|
|
25
14
|
DefaultSystemPromptBuilder,
|
|
@@ -31,13 +20,29 @@ import {
|
|
|
31
20
|
ToolRegistry,
|
|
32
21
|
atomicWrite,
|
|
33
22
|
createDefaultPipelines,
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
24
|
+
listContextWindowModes,
|
|
25
|
+
repairToolUseAdjacency,
|
|
26
|
+
resolveContextWindowPolicy
|
|
36
27
|
} from "@wrongstack/core";
|
|
37
28
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig } from "@wrongstack/providers";
|
|
38
29
|
import { forgetTool, rememberTool } from "@wrongstack/tools";
|
|
39
30
|
import { builtinToolsPack } from "@wrongstack/tools/pack";
|
|
40
31
|
import { WebSocket, WebSocketServer } from "ws";
|
|
32
|
+
import { randomBytes } from "crypto";
|
|
33
|
+
import { createDefaultContainer } from "@wrongstack/runtime";
|
|
34
|
+
|
|
35
|
+
// src/server/boot.ts
|
|
36
|
+
import * as fs from "fs/promises";
|
|
37
|
+
import * as os from "os";
|
|
38
|
+
import {
|
|
39
|
+
DefaultConfigLoader,
|
|
40
|
+
DefaultLogger,
|
|
41
|
+
DefaultPathResolver,
|
|
42
|
+
DefaultSecretVault,
|
|
43
|
+
migratePlaintextSecrets,
|
|
44
|
+
resolveWstackPaths
|
|
45
|
+
} from "@wrongstack/core";
|
|
41
46
|
async function bootConfig() {
|
|
42
47
|
const cwd = process.cwd();
|
|
43
48
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
@@ -64,18 +69,13 @@ async function bootConfig() {
|
|
|
64
69
|
level: config.log?.level ?? "info",
|
|
65
70
|
file: wpaths.logFile
|
|
66
71
|
});
|
|
67
|
-
return {
|
|
68
|
-
config,
|
|
69
|
-
vault,
|
|
70
|
-
globalConfigPath: wpaths.globalConfig,
|
|
71
|
-
projectRoot,
|
|
72
|
-
wpaths,
|
|
73
|
-
logger
|
|
74
|
-
};
|
|
72
|
+
return { config, vault, globalConfigPath: wpaths.globalConfig, projectRoot, wpaths, logger };
|
|
75
73
|
}
|
|
76
74
|
function patchConfig(config, updates) {
|
|
77
75
|
return Object.freeze({ ...config, ...updates });
|
|
78
76
|
}
|
|
77
|
+
|
|
78
|
+
// src/server/index.ts
|
|
79
79
|
async function startWebUI(opts = {}) {
|
|
80
80
|
const wsPort2 = opts.wsPort ?? 3457;
|
|
81
81
|
const wsHost2 = opts.wsHost ?? "127.0.0.1";
|
|
@@ -88,22 +88,8 @@ async function startWebUI(opts = {}) {
|
|
|
88
88
|
cacheFile: wpaths.modelsCache,
|
|
89
89
|
ttlSeconds: 24 * 3600
|
|
90
90
|
});
|
|
91
|
-
const container =
|
|
92
|
-
const configStore =
|
|
93
|
-
container.bind(TOKENS.ConfigStore, () => configStore);
|
|
94
|
-
container.bind(TOKENS.Logger, () => logger);
|
|
95
|
-
container.bind(TOKENS.SecretScrubber, () => new DefaultSecretScrubber());
|
|
96
|
-
container.bind(TOKENS.RetryPolicy, () => new DefaultRetryPolicy());
|
|
97
|
-
container.bind(TOKENS.ErrorHandler, () => new DefaultErrorHandler());
|
|
98
|
-
container.bind(TOKENS.ModelsRegistry, () => modelsRegistry);
|
|
99
|
-
container.bind(
|
|
100
|
-
TOKENS.PermissionPolicy,
|
|
101
|
-
() => new DefaultPermissionPolicy({
|
|
102
|
-
trustFile: wpaths.projectTrust,
|
|
103
|
-
yolo: false,
|
|
104
|
-
promptDelegate: void 0
|
|
105
|
-
})
|
|
106
|
-
);
|
|
91
|
+
const container = createDefaultContainer({ config, wpaths, logger, modelsRegistry });
|
|
92
|
+
const configStore = container.resolve(TOKENS.ConfigStore);
|
|
107
93
|
const providerRegistry = new ProviderRegistry();
|
|
108
94
|
try {
|
|
109
95
|
const factories = await buildProviderFactoriesFromRegistry({
|
|
@@ -192,24 +178,42 @@ async function startWebUI(opts = {}) {
|
|
|
192
178
|
projectRoot,
|
|
193
179
|
model: config.model
|
|
194
180
|
});
|
|
181
|
+
const initialContextPolicy = resolveContextWindowPolicy(config.context);
|
|
182
|
+
context.meta["contextWindowMode"] = initialContextPolicy.id;
|
|
183
|
+
context.meta["contextWindowPolicy"] = initialContextPolicy;
|
|
195
184
|
const pipelines = createDefaultPipelines();
|
|
196
185
|
const compactor = new HybridCompactor({
|
|
197
186
|
preserveK: config.context?.preserveK ?? 20,
|
|
198
187
|
eliseThreshold: config.context?.eliseThreshold ?? 0.7
|
|
199
188
|
});
|
|
200
189
|
if (config.context?.autoCompact !== false) {
|
|
190
|
+
const effectiveMaxContext = config.context?.effectiveMaxContext ?? provider.capabilities.maxContext;
|
|
201
191
|
const autoCompactor = new AutoCompactionMiddleware(
|
|
202
192
|
compactor,
|
|
203
|
-
|
|
193
|
+
effectiveMaxContext,
|
|
204
194
|
(ctx) => {
|
|
205
195
|
let total = 0;
|
|
206
196
|
for (const m of ctx.messages) {
|
|
207
197
|
if (typeof m.content === "string") total += Math.ceil(m.content.length / 4);
|
|
198
|
+
else if (Array.isArray(m.content)) {
|
|
199
|
+
for (const b of m.content) total += Math.ceil(JSON.stringify(b).length / 4);
|
|
200
|
+
}
|
|
208
201
|
}
|
|
209
202
|
return total;
|
|
210
203
|
},
|
|
211
|
-
{
|
|
212
|
-
|
|
204
|
+
{
|
|
205
|
+
warn: initialContextPolicy.thresholds.warn,
|
|
206
|
+
soft: initialContextPolicy.thresholds.soft,
|
|
207
|
+
hard: initialContextPolicy.thresholds.hard
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
events,
|
|
211
|
+
aggressiveOn: initialContextPolicy.aggressiveOn,
|
|
212
|
+
policyProvider: (ctx) => {
|
|
213
|
+
const policy = ctx.meta["contextWindowPolicy"];
|
|
214
|
+
return policy && typeof policy === "object" ? policy : initialContextPolicy;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
213
217
|
);
|
|
214
218
|
pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
|
|
215
219
|
}
|
|
@@ -251,15 +255,27 @@ async function startWebUI(opts = {}) {
|
|
|
251
255
|
cacheReadCost,
|
|
252
256
|
projectName: path.basename(projectRoot) || projectRoot,
|
|
253
257
|
cwd: projectRoot,
|
|
254
|
-
mode: modeId
|
|
258
|
+
mode: modeId,
|
|
259
|
+
contextMode: String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID),
|
|
260
|
+
wsToken
|
|
255
261
|
};
|
|
256
262
|
}
|
|
263
|
+
const wsToken = randomBytes(16).toString("hex");
|
|
264
|
+
console.log(`[WebUI] WS auth token: ${wsToken}`);
|
|
265
|
+
const isLoopback = (hostname) => hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
257
266
|
const verifyClient = (info) => {
|
|
258
267
|
const origin = info.origin;
|
|
259
|
-
|
|
268
|
+
const url = info.req.url ?? "";
|
|
269
|
+
const tokenMatch = url.match(/[?&]token=([^&]+)/);
|
|
270
|
+
const providedToken = tokenMatch ? tokenMatch[1] : void 0;
|
|
271
|
+
const tokenOk = providedToken === wsToken;
|
|
272
|
+
if (!origin) {
|
|
273
|
+
return tokenOk || wsHost2 === "127.0.0.1" || wsHost2 === "::1" || wsHost2 === "localhost";
|
|
274
|
+
}
|
|
260
275
|
try {
|
|
261
276
|
const { hostname } = new URL(origin);
|
|
262
|
-
|
|
277
|
+
if (isLoopback(hostname)) return true;
|
|
278
|
+
return tokenOk;
|
|
263
279
|
} catch {
|
|
264
280
|
return false;
|
|
265
281
|
}
|
|
@@ -336,6 +352,16 @@ async function startWebUI(opts = {}) {
|
|
|
336
352
|
}
|
|
337
353
|
});
|
|
338
354
|
});
|
|
355
|
+
events.on("context.repaired", (e) => {
|
|
356
|
+
broadcast({
|
|
357
|
+
type: "context.repaired",
|
|
358
|
+
payload: {
|
|
359
|
+
removedToolUses: e.removedToolUses,
|
|
360
|
+
removedToolResults: e.removedToolResults,
|
|
361
|
+
removedMessages: e.removedMessages
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
});
|
|
339
365
|
events.on("error", (e) => {
|
|
340
366
|
broadcast({
|
|
341
367
|
type: "error",
|
|
@@ -536,6 +562,8 @@ async function startWebUI(opts = {}) {
|
|
|
536
562
|
type: "context.debug",
|
|
537
563
|
payload: {
|
|
538
564
|
total,
|
|
565
|
+
mode: context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
566
|
+
policy: context.meta["contextWindowPolicy"],
|
|
539
567
|
systemPrompt: sysTokens,
|
|
540
568
|
tools: { total: toolTokens, count: tools.length, breakdown: toolBreakdown },
|
|
541
569
|
messages: {
|
|
@@ -557,7 +585,8 @@ async function startWebUI(opts = {}) {
|
|
|
557
585
|
before: report.before,
|
|
558
586
|
after: report.after,
|
|
559
587
|
saved: Math.max(0, report.before - report.after),
|
|
560
|
-
reductions: report.reductions
|
|
588
|
+
reductions: report.reductions,
|
|
589
|
+
repaired: report.repaired
|
|
561
590
|
}
|
|
562
591
|
});
|
|
563
592
|
sendResult(
|
|
@@ -570,6 +599,63 @@ async function startWebUI(opts = {}) {
|
|
|
570
599
|
}
|
|
571
600
|
break;
|
|
572
601
|
}
|
|
602
|
+
case "context.repair": {
|
|
603
|
+
const beforeMessages = context.messages.length;
|
|
604
|
+
const repaired = repairToolUseAdjacency(context.messages);
|
|
605
|
+
if (repaired.report.changed) {
|
|
606
|
+
context.state.replaceMessages(repaired.messages);
|
|
607
|
+
}
|
|
608
|
+
const payload = {
|
|
609
|
+
removedToolUses: repaired.report.removedToolUses,
|
|
610
|
+
removedToolResults: repaired.report.removedToolResults,
|
|
611
|
+
removedMessages: repaired.report.removedMessages,
|
|
612
|
+
beforeMessages,
|
|
613
|
+
afterMessages: context.messages.length
|
|
614
|
+
};
|
|
615
|
+
broadcast({ type: "context.repaired", payload });
|
|
616
|
+
const removed = payload.removedToolUses.length + payload.removedToolResults.length + payload.removedMessages;
|
|
617
|
+
sendResult(
|
|
618
|
+
ws,
|
|
619
|
+
true,
|
|
620
|
+
removed > 0 ? `Context repaired: removed ${removed} orphan protocol item(s)` : "Context repair found no orphan protocol blocks"
|
|
621
|
+
);
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
case "context.modes.list": {
|
|
625
|
+
const active = String(context.meta["contextWindowMode"] ?? DEFAULT_CONTEXT_WINDOW_MODE_ID);
|
|
626
|
+
send(ws, {
|
|
627
|
+
type: "context.modes.list",
|
|
628
|
+
payload: {
|
|
629
|
+
activeId: active,
|
|
630
|
+
modes: listContextWindowModes().map((m) => ({
|
|
631
|
+
id: m.id,
|
|
632
|
+
name: m.name,
|
|
633
|
+
description: m.description,
|
|
634
|
+
isActive: m.id === active,
|
|
635
|
+
thresholds: m.thresholds,
|
|
636
|
+
preserveK: m.preserveK,
|
|
637
|
+
eliseThreshold: m.eliseThreshold
|
|
638
|
+
}))
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
case "context.mode.switch": {
|
|
644
|
+
const { id } = msg.payload;
|
|
645
|
+
const policy = resolveContextWindowPolicy({}, id);
|
|
646
|
+
if (policy.id !== id) {
|
|
647
|
+
sendResult(ws, false, `Unknown context mode "${id}"`);
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
context.meta["contextWindowMode"] = policy.id;
|
|
651
|
+
context.meta["contextWindowPolicy"] = policy;
|
|
652
|
+
sendResult(ws, true, `Context mode switched to ${policy.id}`);
|
|
653
|
+
broadcast({
|
|
654
|
+
type: "context.mode.changed",
|
|
655
|
+
payload: { id: policy.id, name: policy.name, policy }
|
|
656
|
+
});
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
573
659
|
case "providers.list": {
|
|
574
660
|
const providers = await modelsRegistry.listProviders();
|
|
575
661
|
const savedIds = new Set(Object.keys(config.providers ?? {}));
|
|
@@ -624,7 +710,7 @@ async function startWebUI(opts = {}) {
|
|
|
624
710
|
const newProv = providerRegistry.has(newProvider) ? providerRegistry.create({ ...providerCfg, type: newProvider }) : makeProviderFromConfig(newProvider, providerCfg);
|
|
625
711
|
context.provider = newProv;
|
|
626
712
|
try {
|
|
627
|
-
const raw = await
|
|
713
|
+
const raw = await fs2.readFile(globalConfigPath, "utf8");
|
|
628
714
|
const parsed = JSON.parse(raw);
|
|
629
715
|
parsed.provider = newProvider;
|
|
630
716
|
parsed.model = newModel;
|
|
@@ -926,7 +1012,7 @@ async function startWebUI(opts = {}) {
|
|
|
926
1012
|
if (depth > 8 || results.length >= 600) return;
|
|
927
1013
|
let entries = [];
|
|
928
1014
|
try {
|
|
929
|
-
entries = await
|
|
1015
|
+
entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
930
1016
|
} catch {
|
|
931
1017
|
return;
|
|
932
1018
|
}
|
|
@@ -1063,7 +1149,7 @@ async function startWebUI(opts = {}) {
|
|
|
1063
1149
|
}
|
|
1064
1150
|
async function loadSavedProviders() {
|
|
1065
1151
|
try {
|
|
1066
|
-
const raw = await
|
|
1152
|
+
const raw = await fs2.readFile(globalConfigPath, "utf8");
|
|
1067
1153
|
const parsed = JSON.parse(raw);
|
|
1068
1154
|
return parsed.providers ?? {};
|
|
1069
1155
|
} catch {
|
|
@@ -1073,7 +1159,7 @@ async function startWebUI(opts = {}) {
|
|
|
1073
1159
|
async function saveProviders(providers) {
|
|
1074
1160
|
let parsed;
|
|
1075
1161
|
try {
|
|
1076
|
-
const raw = await
|
|
1162
|
+
const raw = await fs2.readFile(globalConfigPath, "utf8");
|
|
1077
1163
|
parsed = JSON.parse(raw);
|
|
1078
1164
|
} catch {
|
|
1079
1165
|
parsed = {};
|