@wrongstack/webui-server 0.308.0 → 0.308.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +349 -43
- package/dist/protocol/client-integrations.d.ts +1 -1
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/server-integrations.d.ts +1 -1
- package/dist/server/embedded-lifecycle.d.ts +1 -0
- package/dist/server/entry.js +336 -40
- package/dist/server/frontend-static-serve.d.ts +6 -0
- package/dist/server/http-server/api-router.d.ts +7 -0
- package/dist/server/http-server/vector-memory-handlers.d.ts +53 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/memory-handlers.d.ts +19 -0
- package/dist/server/port-utils.d.ts +23 -3
- package/dist/server/start-webui-companion.d.ts +9 -1
- package/dist/server/start-webui-shutdown.d.ts +5 -0
- package/package.json +13 -12
|
@@ -73,6 +73,12 @@ export interface StaticServeOptions {
|
|
|
73
73
|
* listening and the WebSocketServer being attached.
|
|
74
74
|
*/
|
|
75
75
|
deferListen?: boolean | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Fail-fast port binding (no auto-advance on EADDRINUSE). Mirrors
|
|
78
|
+
* `WEBUI_STRICT_PORT` semantics for hosts that resolved the port without
|
|
79
|
+
* probing.
|
|
80
|
+
*/
|
|
81
|
+
strictPort?: boolean | undefined;
|
|
76
82
|
/** Package-resolution/build seams supplied by the owning host. */
|
|
77
83
|
ensureDistDeps?: EnsureDistDeps | undefined;
|
|
78
84
|
/**
|
|
@@ -24,6 +24,13 @@ export interface ApiRouterDeps {
|
|
|
24
24
|
getVectorMemoryStore?: (() => VectorMemoryStore | undefined) | undefined;
|
|
25
25
|
/** Model cache directory for the vector memory provider. */
|
|
26
26
|
vectorMemoryModelCacheDir?: string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Optional SAGE memory store. When provided, the
|
|
29
|
+
* `GET /api/memory/search?q=…&explain=1` route becomes active. Same
|
|
30
|
+
* strict opt-in contract as the vector memory store — non-CLI hosts
|
|
31
|
+
* that don't wire a memory store stay on their existing surface.
|
|
32
|
+
*/
|
|
33
|
+
getMemoryStore?: (() => import('@wrongstack/core/types').MemoryPort | undefined) | undefined;
|
|
27
34
|
}
|
|
28
35
|
export declare function handleApiRoutes(req: http.IncomingMessage, res: http.ServerResponse, url: URL, deps: ApiRouterDeps, requireAccessToken: boolean, accessTokenOk: boolean, getTechStackRuntime: () => Promise<{
|
|
29
36
|
store: import('@wrongstack/techstack').TechStackStore;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type * as http from 'node:http';
|
|
9
9
|
import type { VectorMemoryStore } from '@wrongstack/vector-memory';
|
|
10
|
+
import type { MemoryPort } from '@wrongstack/core/types';
|
|
10
11
|
export interface VectorMemoryStatusResponse {
|
|
11
12
|
enabled: boolean;
|
|
12
13
|
storePath?: string | undefined;
|
|
@@ -17,6 +18,12 @@ export interface VectorMemoryStatusResponse {
|
|
|
17
18
|
entries?: number | undefined;
|
|
18
19
|
vectors?: number | undefined;
|
|
19
20
|
providers?: string[] | undefined;
|
|
21
|
+
cache?: {
|
|
22
|
+
entries: number;
|
|
23
|
+
providers: number;
|
|
24
|
+
totalUseCount: number;
|
|
25
|
+
oldestLastUsedAt: string | null;
|
|
26
|
+
} | undefined;
|
|
20
27
|
}
|
|
21
28
|
export interface VectorMemorySearchHit {
|
|
22
29
|
id: string;
|
|
@@ -28,6 +35,14 @@ export interface VectorMemorySearchHit {
|
|
|
28
35
|
export interface VectorMemorySearchResponse {
|
|
29
36
|
hits: VectorMemorySearchHit[];
|
|
30
37
|
count: number;
|
|
38
|
+
/**
|
|
39
|
+
* Pairwise cosine similarity matrix between returned hits, in hit order.
|
|
40
|
+
* Cell [i][j] is the similarity between hits[i] and hits[j]. Optional —
|
|
41
|
+
* the route only computes it when `?similarity=1` is set, to keep the
|
|
42
|
+
* default response cheap. Used by the WebUI's heatmap view to surface
|
|
43
|
+
* whether the top-K results form coherent clusters.
|
|
44
|
+
*/
|
|
45
|
+
similarity?: number[][] | undefined;
|
|
31
46
|
}
|
|
32
47
|
/** Shape the store exposes. Kept narrow so we don't leak the full class. */
|
|
33
48
|
export interface VectorMemorySnapshot {
|
|
@@ -52,4 +67,42 @@ export declare function handleVectorMemorySearch(res: http.ServerResponse, url:
|
|
|
52
67
|
export declare function handleVectorMemoryStore(res: http.ServerResponse, req: http.IncomingMessage, getStore: () => VectorMemoryStore | undefined): Promise<void>;
|
|
53
68
|
/** Handle `DELETE /api/vector-memory/store/:id`. */
|
|
54
69
|
export declare function handleVectorMemoryForget(res: http.ServerResponse, url: URL, getStore: () => VectorMemoryStore | undefined): Promise<void>;
|
|
70
|
+
export interface MemorySearchHit {
|
|
71
|
+
id: string;
|
|
72
|
+
text: string;
|
|
73
|
+
kind: string;
|
|
74
|
+
status: string;
|
|
75
|
+
tags: string[];
|
|
76
|
+
/** Per-channel score breakdown — null on the side that didn't contribute. */
|
|
77
|
+
lexicalScore: number | null;
|
|
78
|
+
vectorScore: number | null;
|
|
79
|
+
/** RRF-style combined score, monotonically higher = better. */
|
|
80
|
+
finalScore: number;
|
|
81
|
+
/** Which channel(s) produced this hit. */
|
|
82
|
+
source: 'lexical' | 'vector' | 'both';
|
|
83
|
+
}
|
|
84
|
+
export interface MemorySearchResponse {
|
|
85
|
+
hits: MemorySearchHit[];
|
|
86
|
+
count: number;
|
|
87
|
+
/**
|
|
88
|
+
* Where the score breakdown came from. `breakdown` means the rich
|
|
89
|
+
* variant was used (vector channel was wired); `lexical` means the
|
|
90
|
+
* fallback synthesized a position-derived score because the surface
|
|
91
|
+
* doesn't ship `searchSageWithBreakdown`. The UI branches on this to
|
|
92
|
+
* decide whether to show a dual-column score card or a single column.
|
|
93
|
+
*/
|
|
94
|
+
channel: 'breakdown' | 'lexical';
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Handle `GET /api/memory/search?q=…&limit=…&explain=1`.
|
|
98
|
+
*
|
|
99
|
+
* Returns SAGE search hits. When `explain=1` is set and the underlying
|
|
100
|
+
* memory store exposes `searchSageWithBreakdown`, each hit carries a
|
|
101
|
+
* per-channel score breakdown (lexical, vector, RRF final, `source`
|
|
102
|
+
* attribution). Without the flag — or when the rich variant isn't
|
|
103
|
+
* available — the response degrades to lexical-only hits with
|
|
104
|
+
* `vectorScore: null` and `source: 'lexical'`, plus a top-level
|
|
105
|
+
* `channel: 'lexical'` so the caller can branch.
|
|
106
|
+
*/
|
|
107
|
+
export declare function handleMemorySearch(res: http.ServerResponse, url: URL, getStore: () => MemoryPort | undefined): Promise<void>;
|
|
55
108
|
//# sourceMappingURL=vector-memory-handlers.d.ts.map
|
package/dist/server/index.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ export { browserOpenCommand, openBrowser } from './open-browser.js';
|
|
|
69
69
|
export { isPathInside, resolveWorkingDirInsideProject } from './path-containment.js';
|
|
70
70
|
export type { ConfirmDecision, PendingConfirm } from './pending-confirms.js';
|
|
71
71
|
export { isDestructivePendingConfirm, resolveAllPendingConfirms, resolveYoloEligiblePendingConfirms, } from './pending-confirms.js';
|
|
72
|
-
export { findFreePort, getSurfaceDefaultPorts, isPortFree, SURFACE_DEFAULT_PORTS, type SurfaceKind, surfaceLabel, } from './port-utils.js';
|
|
72
|
+
export { findFreePort, getSurfaceDefaultPorts, isPortFree, isStrictPort, listenWithRetry, SURFACE_DEFAULT_PORTS, type SurfaceKind, surfaceLabel, } from './port-utils.js';
|
|
73
73
|
export { ensureDistDir, resolveDistDir, startStaticServe, type EnsureDistDeps, type ResolveDistOptions, type StaticServeDeps, type StaticServeHandle, type StaticServeOptions, } from './frontend-static-serve.js';
|
|
74
74
|
export { announceWebuiReady, createWebuiShutdown, registerWebuiInstance, registerWebuiSignalHandlers, type AnnounceWebuiReadyParams, type RegisterWebuiInstanceDeps, type RegisterWebuiInstanceParams, type WebuiShutdownResources, } from './embedded-lifecycle.js';
|
|
75
75
|
export { createStreamCoalescer, type StreamCoalescer, type StreamCoalescerDeps, type ToolProgressPayload, } from './stream-coalescer.js';
|
|
@@ -40,6 +40,25 @@ export declare function handleSageList(ws: WebSocket, memoryStore: MemoryPort):
|
|
|
40
40
|
* a full `listSage()` for stores that predate `listSagePage`.
|
|
41
41
|
*/
|
|
42
42
|
export declare function handleSageListPage(ws: WebSocket, msg: unknown, memoryStore: MemoryPort): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Run a search and return the per-channel score breakdown
|
|
45
|
+
* (lexical / vector / final + `source` attribution). Used by the
|
|
46
|
+
* WebUI's Memory panel so the operator can see WHY a memory is in
|
|
47
|
+
* the result list.
|
|
48
|
+
*
|
|
49
|
+
* Request: { type: 'memory.sage.searchBreakdown', payload: { query, limit?, includeStale? } }
|
|
50
|
+
* Response: { type: 'memory.sage.searchBreakdown', payload: { hits, source } }
|
|
51
|
+
*
|
|
52
|
+
* `source` is one of:
|
|
53
|
+
* - `breakdown`: the underlying surface returned a `VectorAugmentHit[]`
|
|
54
|
+
* with both lexical and vector scores populated per hit.
|
|
55
|
+
* - `lexical`: the surface only has the flat `searchSage` — we wrap
|
|
56
|
+
* the results with a position-derived score and `source: 'lexical'`.
|
|
57
|
+
*
|
|
58
|
+
* Clients branch on `source` to decide whether to render the dual
|
|
59
|
+
* score panel or a single lexical column.
|
|
60
|
+
*/
|
|
61
|
+
export declare function handleSageSearchBreakdown(ws: WebSocket, msg: unknown, memoryStore: MemoryPort): Promise<void>;
|
|
43
62
|
/**
|
|
44
63
|
* Get a single Sage entry by ID.
|
|
45
64
|
* Request: { type: 'memory.sage.get', payload: { id } }
|
|
@@ -9,14 +9,15 @@
|
|
|
9
9
|
* starts at 3456, SimpleUI at 3466. Set `WEBUI_STRICT_PORT=1` to disable.
|
|
10
10
|
* - **TOCTOU window.** The probe in `findFreePort` binds a throwaway
|
|
11
11
|
* `net.Server` then closes it, so there is a tiny race between "found free"
|
|
12
|
-
* and "real server binds".
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* and "real server binds". When that race is lost the real bind fails with
|
|
13
|
+
* EADDRINUSE; `listenWithRetry` is the safety net — it advances past the
|
|
14
|
+
* competitor (bounded) instead of crashing or hanging.
|
|
15
15
|
* - **Instance registry.** Every live process records itself in
|
|
16
16
|
* `~/.wrongstack/webui-instances.json` with its PID, surface, port, and
|
|
17
17
|
* project root. A crashed instance is pruned on the next registry read
|
|
18
18
|
* (`process.kill(pid, 0)` probe), so ghosts don't accumulate.
|
|
19
19
|
*/
|
|
20
|
+
import * as net from 'node:net';
|
|
20
21
|
/**
|
|
21
22
|
* Surface-specific default single port.
|
|
22
23
|
*
|
|
@@ -37,6 +38,12 @@ export declare const SURFACE_DEFAULT_PORTS: {
|
|
|
37
38
|
export type SurfaceKind = keyof typeof SURFACE_DEFAULT_PORTS;
|
|
38
39
|
/** Human-readable label for surfaces. */
|
|
39
40
|
export declare function surfaceLabel(surface: SurfaceKind): string;
|
|
41
|
+
/**
|
|
42
|
+
* True when `WEBUI_STRICT_PORT` requests fail-fast port binding (no
|
|
43
|
+
* auto-advance). Single source of truth for both `resolvePorts` (probe-time)
|
|
44
|
+
* and `startWebUI`'s bind-time retry — the two must not drift apart.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isStrictPort(): boolean;
|
|
40
47
|
/**
|
|
41
48
|
* Return the surface-specific default HTTP port.
|
|
42
49
|
*/
|
|
@@ -56,4 +63,17 @@ export interface FindFreePortOptions {
|
|
|
56
63
|
* `exclude`. Throws if nothing is free within `maxTries` steps.
|
|
57
64
|
*/
|
|
58
65
|
export declare function findFreePort(host: string, startPort: number, opts?: FindFreePortOptions): Promise<number>;
|
|
66
|
+
export interface ListenWithRetryOptions {
|
|
67
|
+
/** Bind attempts before giving up on EADDRINUSE. Default 10. */
|
|
68
|
+
maxTries?: number | undefined;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Listen `server` on `host` starting at `port`, advancing one port on each
|
|
72
|
+
* EADDRINUSE — the bind-time safety net for the `findFreePort` TOCTOU window
|
|
73
|
+
* (probe closes, competitor binds, real listen loses the race). Any other
|
|
74
|
+
* error (EACCES, EAFNOSUPPORT, …) rejects immediately; EADDRINUSE past the
|
|
75
|
+
* final attempt rejects with the last error. Resolves with the port the
|
|
76
|
+
* server actually bound, which exceeds the request when a retry advanced.
|
|
77
|
+
*/
|
|
78
|
+
export declare function listenWithRetry(server: net.Server, host: string, port: number, opts?: ListenWithRetryOptions): Promise<number>;
|
|
59
79
|
//# sourceMappingURL=port-utils.d.ts.map
|
|
@@ -12,5 +12,13 @@ import * as http from 'node:http';
|
|
|
12
12
|
* fallback for systems where `::` does not accept IPv4-mapped
|
|
13
13
|
* connections (net.ipv6.bindv6only=1, some Windows configs).
|
|
14
14
|
*/
|
|
15
|
-
export declare function setupCompanionServer(httpServer: http.Server, wsHost: string | undefined,
|
|
15
|
+
export declare function setupCompanionServer(httpServer: http.Server, wsHost: string | undefined,
|
|
16
|
+
/**
|
|
17
|
+
* The port the PRIMARY server actually bound — not the originally
|
|
18
|
+
* requested port. The companion must mirror it exactly: advertised URLs
|
|
19
|
+
* point at the primary's port, so a companion bound elsewhere would be
|
|
20
|
+
* unreachable dead weight. A single bind attempt; EADDRINUSE here means
|
|
21
|
+
* the mirror is impossible, so the companion degrades to null.
|
|
22
|
+
*/
|
|
23
|
+
httpPort: number): Promise<http.Server | null>;
|
|
16
24
|
//# sourceMappingURL=start-webui-companion.d.ts.map
|
|
@@ -38,6 +38,11 @@ export declare function setupWebuiShutdown(options: {
|
|
|
38
38
|
clearEternalSubscription: () => void;
|
|
39
39
|
codebaseIndexing: any;
|
|
40
40
|
memoryStore: any;
|
|
41
|
+
/** Vector memory store (mirrors CLI's teardown). `undefined` when the
|
|
42
|
+
* standalone WebUI host failed to construct one (read-only FS, etc.). */
|
|
43
|
+
vectorMemoryStore: {
|
|
44
|
+
close(): void;
|
|
45
|
+
} | undefined;
|
|
41
46
|
globalConfigPath: string;
|
|
42
47
|
}): () => void;
|
|
43
48
|
//# sourceMappingURL=start-webui-shutdown.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/webui-server",
|
|
3
|
-
"version": "0.308.
|
|
3
|
+
"version": "0.308.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "WrongStack WebUI HTTP/WebSocket server module — extracted from @wrongstack/webui in PR #243/244 to remove the CLI -> @wrongstack/webui/server cross-package edge (audit §3.1.1). Pure backend: HTTP routes, WebSocket handlers, MCP tool wrappers, HTML serving. The web frontend lives in @wrongstack/webui; this package is the standalone server it can run on.",
|
|
6
6
|
"keywords": [
|
|
@@ -40,17 +40,17 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"ws": "^8.21.3",
|
|
43
|
-
"@wrongstack/
|
|
44
|
-
"@wrongstack/
|
|
45
|
-
"@wrongstack/
|
|
46
|
-
"@wrongstack/sdd": "0.308.
|
|
47
|
-
"@wrongstack/
|
|
48
|
-
"@wrongstack/
|
|
49
|
-
"@wrongstack/
|
|
50
|
-
"@wrongstack/techstack": "0.308.
|
|
51
|
-
"@wrongstack/
|
|
52
|
-
"@wrongstack/
|
|
53
|
-
"@wrongstack/
|
|
43
|
+
"@wrongstack/core": "0.308.1",
|
|
44
|
+
"@wrongstack/requirement-intake": "0.308.1",
|
|
45
|
+
"@wrongstack/kanban": "0.308.1",
|
|
46
|
+
"@wrongstack/sdd": "0.308.1",
|
|
47
|
+
"@wrongstack/mcp": "0.308.1",
|
|
48
|
+
"@wrongstack/providers": "0.308.1",
|
|
49
|
+
"@wrongstack/runtime": "0.308.1",
|
|
50
|
+
"@wrongstack/techstack": "0.308.1",
|
|
51
|
+
"@wrongstack/sage": "0.308.1",
|
|
52
|
+
"@wrongstack/tools": "0.308.1",
|
|
53
|
+
"@wrongstack/vector-memory": "0.308.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/node": "^26.2.0",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"scripts": {
|
|
64
64
|
"build": "node ../../scripts/build-package.mjs",
|
|
65
65
|
"typecheck": "tsc --noEmit",
|
|
66
|
+
"test": "vitest run --config vitest.config.ts",
|
|
66
67
|
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
67
68
|
}
|
|
68
69
|
}
|