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