@wrongstack/webui 0.264.0 → 0.267.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/assets/index-UZtAQH-v.css +2 -0
- package/dist/assets/index-q6W9UOEF.js +121 -0
- package/dist/assets/{vendor-CEQg2uSG.css → vendor-B2D6LvU3.css} +1 -1
- package/dist/assets/vendor-HxGAEBey.js +1326 -0
- package/dist/index.html +4 -4
- package/dist/index.js +12048 -6641
- package/dist/index.js.map +1 -1
- package/dist/server/entry.js +1772 -371
- package/dist/server/entry.js.map +1 -1
- package/dist/server/handlers.d.ts +45 -0
- package/dist/server/handlers.js +179 -0
- package/dist/server/handlers.js.map +1 -0
- package/dist/server/index.d.ts +173 -6
- package/dist/server/index.js +2042 -372
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +420 -2
- package/package.json +8 -5
- package/dist/assets/index-BBPaC1tO.js +0 -170
- package/dist/assets/index-DJmqJ5Wo.css +0 -2
- package/dist/assets/vendor-pWpGJmMc.js +0 -1303
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
import { TodoItem } from '@wrongstack/core';
|
|
3
|
+
|
|
4
|
+
interface WorklistContext {
|
|
5
|
+
context: {
|
|
6
|
+
todos: TodoItem[];
|
|
7
|
+
meta: Record<string, unknown>;
|
|
8
|
+
session: {
|
|
9
|
+
id: string;
|
|
10
|
+
} | null;
|
|
11
|
+
state?: unknown;
|
|
12
|
+
};
|
|
13
|
+
send: (ws: WebSocket, msg: object) => void;
|
|
14
|
+
broadcast: (msg: object) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Optional mutator for in-memory todo state. Servers that manage live
|
|
17
|
+
* agent state (e.g. the CLI embedded server) provide this so handlers
|
|
18
|
+
* can update the agent's todo list directly. Standalone server may omit.
|
|
19
|
+
*/
|
|
20
|
+
replaceTodos?: (todos: TodoItem[]) => void;
|
|
21
|
+
}
|
|
22
|
+
declare function handleTodosGet(ctx: WorklistContext, ws: WebSocket): void;
|
|
23
|
+
declare function handleTodosClear(ctx: WorklistContext, ws: WebSocket): void;
|
|
24
|
+
declare function handleTodosRemove(ctx: WorklistContext, ws: WebSocket, payload: {
|
|
25
|
+
id?: string;
|
|
26
|
+
index?: number;
|
|
27
|
+
} | undefined): void;
|
|
28
|
+
declare function handleTodoUpdate(ctx: WorklistContext, ws: WebSocket, payload: {
|
|
29
|
+
id: string;
|
|
30
|
+
status?: TodoItem['status'];
|
|
31
|
+
activeForm?: string;
|
|
32
|
+
}): void;
|
|
33
|
+
declare function handleTasksGet(ctx: WorklistContext, ws: WebSocket): Promise<void>;
|
|
34
|
+
declare function handleTaskUpdate(ctx: WorklistContext, ws: WebSocket, payload: {
|
|
35
|
+
id: string;
|
|
36
|
+
status: 'pending' | 'in_progress' | 'blocked' | 'failed' | 'review' | 'completed';
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
declare function handlePlanGet(ctx: WorklistContext, ws: WebSocket): Promise<void>;
|
|
39
|
+
declare function handlePlanTemplateUse(ctx: WorklistContext, ws: WebSocket, template: string): Promise<void>;
|
|
40
|
+
declare function handlePlanItemUpdate(ctx: WorklistContext, ws: WebSocket, payload: {
|
|
41
|
+
target: string;
|
|
42
|
+
status: 'open' | 'in_progress' | 'done';
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
|
|
45
|
+
export { type WorklistContext, handlePlanGet, handlePlanItemUpdate, handlePlanTemplateUse, handleTaskUpdate, handleTasksGet, handleTodoUpdate, handleTodosClear, handleTodosGet, handleTodosRemove };
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// src/server/handlers/worklist-handlers.ts
|
|
2
|
+
function sendResult(ws, ctx, ok, message) {
|
|
3
|
+
ctx.send(ws, { type: ok ? "ok" : "error", message });
|
|
4
|
+
}
|
|
5
|
+
function handleTodosGet(ctx, ws) {
|
|
6
|
+
ctx.send(ws, { type: "todos.updated", payload: { todos: ctx.context.todos } });
|
|
7
|
+
}
|
|
8
|
+
function handleTodosClear(ctx, ws) {
|
|
9
|
+
ctx.replaceTodos?.([]);
|
|
10
|
+
ctx.broadcast({ type: "todos.cleared" });
|
|
11
|
+
sendResult(ws, ctx, true, "Todo board cleared.");
|
|
12
|
+
}
|
|
13
|
+
function handleTodosRemove(ctx, ws, payload) {
|
|
14
|
+
if (!payload || payload.id === void 0 && payload.index === void 0) {
|
|
15
|
+
sendResult(ws, ctx, false, "todos.remove requires id or index.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const next = payload.id !== void 0 ? ctx.context.todos.filter((t) => t.id !== payload.id) : ctx.context.todos.filter((_, i) => i !== payload.index);
|
|
19
|
+
ctx.replaceTodos?.(next);
|
|
20
|
+
ctx.broadcast({ type: "todos.updated", payload: { todos: next } });
|
|
21
|
+
sendResult(ws, ctx, true, "Todo item removed.");
|
|
22
|
+
}
|
|
23
|
+
function handleTodoUpdate(ctx, ws, payload) {
|
|
24
|
+
const todo = ctx.context.todos.find((t) => t.id === payload.id);
|
|
25
|
+
if (!todo) {
|
|
26
|
+
sendResult(ws, ctx, false, `No todo with id "${payload.id}".`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const next = ctx.context.todos.map(
|
|
30
|
+
(t) => t.id === payload.id ? { ...t, ...payload.status !== void 0 && { status: payload.status }, ...payload.activeForm !== void 0 && { activeForm: payload.activeForm } } : t
|
|
31
|
+
);
|
|
32
|
+
ctx.replaceTodos?.(next);
|
|
33
|
+
ctx.broadcast({ type: "todos.updated", payload: { todos: next } });
|
|
34
|
+
sendResult(ws, ctx, true, `Todo "${todo.content}" updated.`);
|
|
35
|
+
}
|
|
36
|
+
async function handleTasksGet(ctx, ws) {
|
|
37
|
+
const taskPath = ctx.context.meta["task.path"];
|
|
38
|
+
if (typeof taskPath === "string" && taskPath) {
|
|
39
|
+
try {
|
|
40
|
+
const { loadTasks } = await import("@wrongstack/core");
|
|
41
|
+
const file = await loadTasks(taskPath);
|
|
42
|
+
ctx.send(ws, { type: "tasks.updated", payload: { tasks: file?.tasks ?? [] } });
|
|
43
|
+
} catch {
|
|
44
|
+
ctx.send(ws, { type: "tasks.updated", payload: { tasks: [] } });
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
ctx.send(ws, {
|
|
48
|
+
type: "tasks.updated",
|
|
49
|
+
payload: { tasks: [], error: "Task storage not configured." }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function handleTaskUpdate(ctx, ws, payload) {
|
|
54
|
+
const taskPath = ctx.context.meta["task.path"];
|
|
55
|
+
if (typeof taskPath !== "string" || !taskPath) {
|
|
56
|
+
sendResult(ws, ctx, false, "Task storage is not configured for this session.");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const { loadTasks, saveTasks } = await import("@wrongstack/core");
|
|
61
|
+
const file = await loadTasks(taskPath);
|
|
62
|
+
if (!file) {
|
|
63
|
+
sendResult(ws, ctx, false, "No task file found.");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const idx = file.tasks.findIndex((t) => t.id === payload.id);
|
|
67
|
+
if (idx === -1) {
|
|
68
|
+
sendResult(ws, ctx, false, `Task "${payload.id}" not found.`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
file.tasks[idx] = { ...file.tasks[idx], status: payload.status };
|
|
72
|
+
await saveTasks(taskPath, file);
|
|
73
|
+
ctx.broadcast({ type: "tasks.updated", payload: { tasks: file.tasks } });
|
|
74
|
+
sendResult(ws, ctx, true, `Task "${payload.id}" marked ${payload.status}.`);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
sendResult(ws, ctx, false, String(err));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function handlePlanGet(ctx, ws) {
|
|
80
|
+
const planPath = ctx.context.meta["plan.path"];
|
|
81
|
+
const sessionId = ctx.context.session?.id ?? "";
|
|
82
|
+
if (typeof planPath === "string" && planPath) {
|
|
83
|
+
try {
|
|
84
|
+
const { loadPlan } = await import("@wrongstack/core");
|
|
85
|
+
const plan = await loadPlan(planPath);
|
|
86
|
+
ctx.send(ws, {
|
|
87
|
+
type: "plan.updated",
|
|
88
|
+
payload: {
|
|
89
|
+
plan: plan ?? {
|
|
90
|
+
version: 1,
|
|
91
|
+
sessionId,
|
|
92
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
93
|
+
items: []
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
} catch {
|
|
98
|
+
ctx.send(ws, {
|
|
99
|
+
type: "plan.updated",
|
|
100
|
+
payload: {
|
|
101
|
+
plan: {
|
|
102
|
+
version: 1,
|
|
103
|
+
sessionId,
|
|
104
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
105
|
+
items: []
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
ctx.send(ws, {
|
|
112
|
+
type: "plan.updated",
|
|
113
|
+
payload: { plan: null, error: "Plan storage is not configured for this session." }
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function handlePlanTemplateUse(ctx, ws, template) {
|
|
118
|
+
const planPath = ctx.context.meta["plan.path"];
|
|
119
|
+
const sessionId = ctx.context.session?.id ?? "";
|
|
120
|
+
if (typeof planPath !== "string" || !planPath) {
|
|
121
|
+
sendResult(ws, ctx, false, "Plan storage is not configured for this session.");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const { getPlanTemplate, loadPlan, savePlan, emptyPlan, addPlanItem } = await import("@wrongstack/core");
|
|
126
|
+
const tpl = getPlanTemplate(template);
|
|
127
|
+
if (!tpl) {
|
|
128
|
+
sendResult(ws, ctx, false, `Unknown template "${template}".`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
let plan = await loadPlan(planPath) ?? emptyPlan(sessionId);
|
|
132
|
+
for (const item of tpl.items) {
|
|
133
|
+
({ plan } = addPlanItem(plan, item.title, item.details));
|
|
134
|
+
}
|
|
135
|
+
await savePlan(planPath, plan);
|
|
136
|
+
sendResult(ws, ctx, true, `Applied template "${tpl.name}" \u2014 ${tpl.items.length} items added.`);
|
|
137
|
+
ctx.broadcast({ type: "plan.updated", payload: { plan } });
|
|
138
|
+
} catch (err) {
|
|
139
|
+
sendResult(ws, ctx, false, String(err));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function handlePlanItemUpdate(ctx, ws, payload) {
|
|
143
|
+
const planPath = ctx.context.meta["plan.path"];
|
|
144
|
+
const sessionId = ctx.context.session?.id ?? "";
|
|
145
|
+
if (typeof planPath !== "string" || !planPath) {
|
|
146
|
+
sendResult(ws, ctx, false, "Plan storage is not configured for this session.");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const { loadPlan, savePlan, mutatePlan, setPlanItemStatus } = await import("@wrongstack/core");
|
|
151
|
+
let changed = false;
|
|
152
|
+
const plan = await mutatePlan(planPath, sessionId, async (p) => {
|
|
153
|
+
const before = p.updatedAt;
|
|
154
|
+
const updated = setPlanItemStatus(p, payload.target, payload.status);
|
|
155
|
+
changed = updated.updatedAt !== before;
|
|
156
|
+
return updated;
|
|
157
|
+
});
|
|
158
|
+
if (!changed) {
|
|
159
|
+
sendResult(ws, ctx, false, `No plan item matched "${payload.target}".`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
sendResult(ws, ctx, true, `Plan item status updated to "${payload.status}".`);
|
|
163
|
+
ctx.broadcast({ type: "plan.updated", payload: { plan } });
|
|
164
|
+
} catch (err) {
|
|
165
|
+
sendResult(ws, ctx, false, String(err));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export {
|
|
169
|
+
handlePlanGet,
|
|
170
|
+
handlePlanItemUpdate,
|
|
171
|
+
handlePlanTemplateUse,
|
|
172
|
+
handleTaskUpdate,
|
|
173
|
+
handleTasksGet,
|
|
174
|
+
handleTodoUpdate,
|
|
175
|
+
handleTodosClear,
|
|
176
|
+
handleTodosGet,
|
|
177
|
+
handleTodosRemove
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/handlers/worklist-handlers.ts"],"sourcesContent":["// ── Shared Worklist Handlers ─────────────────────────────────────────────────\n// Extracted from standalone server (packages/webui/src/server/index.ts) and CLI\n// embedded server (packages/cli/src/webui-server/). Both servers use these\n// handlers for todos, tasks, and plan operations. Keep them in sync.\n//\n// Message types handled here:\n// todos.get | todos.clear | todos.remove | todo.update\n// tasks.get | task.update\n// plan.get | plan.template_use | plan.item.update\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport type { WebSocket } from 'ws';\nimport type { TodoItem } from '@wrongstack/core';\n\n// ── Shared result helper ───────────────────────────────────────────────────────\n\nfunction sendResult(\n ws: WebSocket,\n ctx: WorklistContext,\n ok: boolean,\n message: string,\n): void {\n ctx.send(ws, { type: ok ? 'ok' : 'error', message });\n}\n\n// ── Context interface ─────────────────────────────────────────────────────────\n// Both servers satisfy this with their own local state.\n\nexport interface WorklistContext {\n context: {\n todos: TodoItem[];\n meta: Record<string, unknown>;\n session: { id: string } | null;\n state?: unknown;\n };\n send: (ws: WebSocket, msg: object) => void;\n broadcast: (msg: object) => void;\n /**\n * Optional mutator for in-memory todo state. Servers that manage live\n * agent state (e.g. the CLI embedded server) provide this so handlers\n * can update the agent's todo list directly. Standalone server may omit.\n */\n replaceTodos?: (todos: TodoItem[]) => void;\n}\n\n// ── Todos ─────────────────────────────────────────────────────────────────────\n\nexport function handleTodosGet(ctx: WorklistContext, ws: WebSocket): void {\n ctx.send(ws, { type: 'todos.updated', payload: { todos: ctx.context.todos } });\n}\n\nexport function handleTodosClear(ctx: WorklistContext, ws: WebSocket): void {\n ctx.replaceTodos?.([]);\n ctx.broadcast({ type: 'todos.cleared' });\n sendResult(ws, ctx, true, 'Todo board cleared.');\n}\n\nexport function handleTodosRemove(\n ctx: WorklistContext,\n ws: WebSocket,\n payload: { id?: string; index?: number } | undefined,\n): void {\n if (!payload || (payload.id === undefined && payload.index === undefined)) {\n sendResult(ws, ctx, false, 'todos.remove requires id or index.');\n return;\n }\n const next =\n payload.id !== undefined\n ? ctx.context.todos.filter((t) => t.id !== payload.id)\n : ctx.context.todos.filter((_, i) => i !== (payload.index as number));\n ctx.replaceTodos?.(next);\n ctx.broadcast({ type: 'todos.updated', payload: { todos: next } });\n sendResult(ws, ctx, true, 'Todo item removed.');\n}\n\nexport function handleTodoUpdate(\n ctx: WorklistContext,\n ws: WebSocket,\n payload: { id: string; status?: TodoItem['status']; activeForm?: string },\n): void {\n const todo = ctx.context.todos.find((t) => t.id === payload.id);\n if (!todo) {\n sendResult(ws, ctx, false, `No todo with id \"${payload.id}\".`);\n return;\n }\n const next = ctx.context.todos.map((t) =>\n t.id === payload.id\n ? { ...t, ...(payload.status !== undefined && { status: payload.status }), ...(payload.activeForm !== undefined && { activeForm: payload.activeForm }) }\n : t,\n );\n ctx.replaceTodos?.(next);\n ctx.broadcast({ type: 'todos.updated', payload: { todos: next } });\n sendResult(ws, ctx, true, `Todo \"${todo.content}\" updated.`);\n}\n\n// ── Tasks ─────────────────────────────────────────────────────────────────────\n\nexport async function handleTasksGet(ctx: WorklistContext, ws: WebSocket): Promise<void> {\n const taskPath = ctx.context.meta['task.path'];\n if (typeof taskPath === 'string' && taskPath) {\n try {\n const { loadTasks } = await import('@wrongstack/core');\n const file = await loadTasks(taskPath);\n ctx.send(ws, { type: 'tasks.updated', payload: { tasks: file?.tasks ?? [] } });\n } catch {\n ctx.send(ws, { type: 'tasks.updated', payload: { tasks: [] } });\n }\n } else {\n ctx.send(ws, {\n type: 'tasks.updated',\n payload: { tasks: [], error: 'Task storage not configured.' },\n });\n }\n}\n\nexport async function handleTaskUpdate(\n ctx: WorklistContext,\n ws: WebSocket,\n payload: {\n id: string;\n status: 'pending' | 'in_progress' | 'blocked' | 'failed' | 'review' | 'completed';\n },\n): Promise<void> {\n const taskPath = ctx.context.meta['task.path'];\n if (typeof taskPath !== 'string' || !taskPath) {\n sendResult(ws, ctx, false, 'Task storage is not configured for this session.');\n return;\n }\n try {\n const { loadTasks, saveTasks } = await import('@wrongstack/core');\n const file = await loadTasks(taskPath);\n if (!file) {\n sendResult(ws, ctx, false, 'No task file found.');\n return;\n }\n const idx = file.tasks.findIndex((t) => t.id === payload.id);\n if (idx === -1) {\n sendResult(ws, ctx, false, `Task \"${payload.id}\" not found.`);\n return;\n }\n file.tasks[idx] = { ...file.tasks[idx], status: payload.status };\n await saveTasks(taskPath, file);\n ctx.broadcast({ type: 'tasks.updated', payload: { tasks: file.tasks } });\n sendResult(ws, ctx, true, `Task \"${payload.id}\" marked ${payload.status}.`);\n } catch (err) {\n sendResult(ws, ctx, false, String(err));\n }\n}\n\n// ── Plan ───────────────────────────────────────────────────────────────────────\n\nexport async function handlePlanGet(ctx: WorklistContext, ws: WebSocket): Promise<void> {\n const planPath = ctx.context.meta['plan.path'];\n const sessionId = ctx.context.session?.id ?? '';\n if (typeof planPath === 'string' && planPath) {\n try {\n const { loadPlan } = await import('@wrongstack/core');\n const plan = await loadPlan(planPath);\n ctx.send(ws, {\n type: 'plan.updated',\n payload: {\n plan: plan ?? {\n version: 1,\n sessionId,\n updatedAt: new Date().toISOString(),\n items: [],\n },\n },\n });\n } catch {\n ctx.send(ws, {\n type: 'plan.updated',\n payload: {\n plan: {\n version: 1,\n sessionId,\n updatedAt: new Date().toISOString(),\n items: [],\n },\n },\n });\n }\n } else {\n ctx.send(ws, {\n type: 'plan.updated',\n payload: { plan: null, error: 'Plan storage is not configured for this session.' },\n });\n }\n}\n\nexport async function handlePlanTemplateUse(ctx: WorklistContext, ws: WebSocket, template: string): Promise<void> {\n const planPath = ctx.context.meta['plan.path'];\n const sessionId = ctx.context.session?.id ?? '';\n if (typeof planPath !== 'string' || !planPath) {\n sendResult(ws, ctx, false, 'Plan storage is not configured for this session.');\n return;\n }\n try {\n const { getPlanTemplate, loadPlan, savePlan, emptyPlan, addPlanItem } = await import('@wrongstack/core');\n const tpl = getPlanTemplate(template);\n if (!tpl) {\n sendResult(ws, ctx, false, `Unknown template \"${template}\".`);\n return;\n }\n let plan = (await loadPlan(planPath)) ?? emptyPlan(sessionId);\n for (const item of tpl.items) {\n ({ plan } = addPlanItem(plan, item.title, item.details));\n }\n await savePlan(planPath, plan);\n sendResult(ws, ctx, true, `Applied template \"${tpl.name}\" — ${tpl.items.length} items added.`);\n ctx.broadcast({ type: 'plan.updated', payload: { plan } });\n } catch (err) {\n sendResult(ws, ctx, false, String(err));\n }\n}\n\nexport async function handlePlanItemUpdate(\n ctx: WorklistContext,\n ws: WebSocket,\n payload: { target: string; status: 'open' | 'in_progress' | 'done' },\n): Promise<void> {\n const planPath = ctx.context.meta['plan.path'];\n const sessionId = ctx.context.session?.id ?? '';\n if (typeof planPath !== 'string' || !planPath) {\n sendResult(ws, ctx, false, 'Plan storage is not configured for this session.');\n return;\n }\n try {\n const { loadPlan, savePlan, mutatePlan, setPlanItemStatus } = await import('@wrongstack/core');\n let changed = false;\n const plan = await mutatePlan(planPath, sessionId, async (p) => {\n const before = p.updatedAt;\n const updated = setPlanItemStatus(p, payload.target, payload.status);\n changed = updated.updatedAt !== before;\n return updated;\n });\n if (!changed) {\n sendResult(ws, ctx, false, `No plan item matched \"${payload.target}\".`);\n return;\n }\n sendResult(ws, ctx, true, `Plan item status updated to \"${payload.status}\".`);\n ctx.broadcast({ type: 'plan.updated', payload: { plan } });\n } catch (err) {\n sendResult(ws, ctx, false, String(err));\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { WebSocket } from 'ws';
|
|
2
|
-
import { Agent, EventBus, SessionStore, ToolRegistry, ModelsRegistry, ConfigStore, SecretVault, JournalEntry, Logger, MemoryStore, ProviderConfig, ProviderApiKey, Context } from '@wrongstack/core';
|
|
2
|
+
import { Agent, EventBus, SessionStore, ToolRegistry, ModelsRegistry, ConfigStore, SecretVault, JournalEntry, Logger, MemoryStore, ProviderConfig, ProviderApiKey, Context, SkillLoader } from '@wrongstack/core';
|
|
3
3
|
import * as http from 'node:http';
|
|
4
|
+
import { MCPRegistry } from '@wrongstack/mcp';
|
|
5
|
+
import { SkillInstaller } from '@wrongstack/core/skills';
|
|
4
6
|
|
|
5
7
|
interface WSServerMessage {
|
|
6
8
|
type: string;
|
|
@@ -68,6 +70,25 @@ interface ConnectedClient {
|
|
|
68
70
|
ws: WebSocket;
|
|
69
71
|
sessionId: string | null;
|
|
70
72
|
connectedAt: number;
|
|
73
|
+
/** Unique per-connection id — used to key per-connection state (e.g. the
|
|
74
|
+
* rate-limit bucket) so distinct browser tabs that share the same
|
|
75
|
+
* `sessionId` do not collide, and so the entry is reliably removable on
|
|
76
|
+
* close (`String(ws)` is `"[object Object]"` for every socket). */
|
|
77
|
+
connId: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Metrics for the file watcher that watches status.json files. */
|
|
81
|
+
interface FileWatcherMetrics {
|
|
82
|
+
fileChangesDetected: number;
|
|
83
|
+
filesProcessed: number;
|
|
84
|
+
broadcastsSent: number;
|
|
85
|
+
debounceResets: number;
|
|
86
|
+
totalDebounceDelayMs: number;
|
|
87
|
+
activeProjects: number;
|
|
88
|
+
/** Average debounce delay in ms across all broadcasts. */
|
|
89
|
+
averageDebounceDelayMs: number;
|
|
90
|
+
/** Whether the file watcher is currently active. */
|
|
91
|
+
watcherActive: boolean;
|
|
71
92
|
}
|
|
72
93
|
|
|
73
94
|
interface CreateHttpServerOptions {
|
|
@@ -102,6 +123,17 @@ interface CreateHttpServerOptions {
|
|
|
102
123
|
* URL-token-only flow (e.g. in tests that don't want cookie state).
|
|
103
124
|
*/
|
|
104
125
|
enableWsCookie?: boolean | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* Optional file watcher metrics object. When provided, the
|
|
128
|
+
* /debug/watcher-metrics endpoint will be enabled to expose these metrics.
|
|
129
|
+
*/
|
|
130
|
+
watcherMetrics?: FileWatcherMetrics | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* Push-on-write hook. `POST /api/fleet/ping` (loopback only) invokes this to
|
|
133
|
+
* trigger an immediate fleet re-broadcast, so a TUI/REPL's registry write
|
|
134
|
+
* reaches the map without waiting on the file-watch/poll. Best-effort.
|
|
135
|
+
*/
|
|
136
|
+
onFleetPing?: (() => void) | undefined;
|
|
105
137
|
}
|
|
106
138
|
/**
|
|
107
139
|
* Inject the live WS port into the served HTML so the frontend connects to
|
|
@@ -291,13 +323,13 @@ declare function handleShellOpen(req: ShellOpenRequest, logger: Logger): Promise
|
|
|
291
323
|
* Send a JSON message to a single WebSocket client.
|
|
292
324
|
* No-op when the socket is not in OPEN state (disconnected / closing).
|
|
293
325
|
*/
|
|
294
|
-
declare function send(ws: WebSocket, msg:
|
|
326
|
+
declare function send(ws: WebSocket, msg: object): void;
|
|
295
327
|
/**
|
|
296
328
|
* Broadcast a JSON message to every connected client.
|
|
297
329
|
* Swallows per-socket send errors — a client that disconnected between the
|
|
298
330
|
* readyState check and `ws.send()` is cleaned up by its own `close` handler.
|
|
299
331
|
*/
|
|
300
|
-
declare function broadcast(clients: Map<WebSocket, ConnectedClient>, msg:
|
|
332
|
+
declare function broadcast(clients: Map<WebSocket, ConnectedClient>, msg: object): void;
|
|
301
333
|
/**
|
|
302
334
|
* Send a success/failure result message (used by key.* and provider.* handlers).
|
|
303
335
|
* The frontend expects `key.operation_result` with `{ success, message }`.
|
|
@@ -355,6 +387,42 @@ declare function handleFilesWrite(ws: WebSocket, msg: unknown, projectRoot: stri
|
|
|
355
387
|
*/
|
|
356
388
|
declare function handleFilesList(ws: WebSocket, msg: unknown, projectRoot: string): Promise<void>;
|
|
357
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Shared `git.info` WebSocket handler for both the standalone WebUI server and
|
|
392
|
+
* the CLI's `--webui` embedded server. Extracted from the duplicated switch
|
|
393
|
+
* cases in `index.ts` and `cli/src/webui-server.ts`, which had drifted (the
|
|
394
|
+
* standalone copy transposed ahead/behind and never matched deletions). One
|
|
395
|
+
* implementation here keeps both surfaces in lockstep.
|
|
396
|
+
*
|
|
397
|
+
* case 'git.info': return handleGitInfo(ws, projectRoot);
|
|
398
|
+
*/
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Read git branch, change stats, and upstream sync status from `projectRoot`
|
|
402
|
+
* and broadcast a `git.info` message. Never throws — a non-repo / missing-git
|
|
403
|
+
* directory yields an empty-but-valid payload.
|
|
404
|
+
*/
|
|
405
|
+
declare function handleGitInfo(ws: WebSocket, projectRoot: string): Promise<void>;
|
|
406
|
+
/**
|
|
407
|
+
* Read the working-tree change set (everything that differs from HEAD:
|
|
408
|
+
* staged, unstaged, and untracked) and broadcast a `git.changes` message.
|
|
409
|
+
*
|
|
410
|
+
* The file list comes from `git status --porcelain -z` (NUL-delimited so
|
|
411
|
+
* paths with spaces/unicode survive intact, and renames are unambiguous).
|
|
412
|
+
* Per-file line counts come from `--numstat` of both the unstaged and the
|
|
413
|
+
* staged diff, summed; untracked files count their own lines as additions.
|
|
414
|
+
* Never throws — a non-repo yields an empty list.
|
|
415
|
+
*/
|
|
416
|
+
declare function handleGitChanges(ws: WebSocket, projectRoot: string): Promise<void>;
|
|
417
|
+
/**
|
|
418
|
+
* Resolve the before/after text for a single file and broadcast a `git.diff`
|
|
419
|
+
* message. `oldText` is the file at HEAD (`git show HEAD:<path>`), `newText`
|
|
420
|
+
* is the current working-tree content. New/untracked files have empty
|
|
421
|
+
* `oldText`; deleted files have empty `newText`. Binary or oversized files
|
|
422
|
+
* are reported with a flag instead of content so the client can show a notice.
|
|
423
|
+
*/
|
|
424
|
+
declare function handleGitDiff(ws: WebSocket, projectRoot: string, path: string): Promise<void>;
|
|
425
|
+
|
|
358
426
|
/**
|
|
359
427
|
* Shared memory-operation WebSocket handlers for both the standalone WebUI
|
|
360
428
|
* server and the CLI's `--webui` embedded server. Extracted from the
|
|
@@ -382,6 +450,38 @@ declare function handleMemoryRemember(ws: WebSocket, msg: unknown, memoryStore:
|
|
|
382
450
|
*/
|
|
383
451
|
declare function handleMemoryForget(ws: WebSocket, msg: unknown, memoryStore: MemoryStore): Promise<void>;
|
|
384
452
|
|
|
453
|
+
/**
|
|
454
|
+
* MCP management handlers for the WebUI server (both the standalone
|
|
455
|
+
* `wrongstack webui` server and the CLI's embedded `--webui` server).
|
|
456
|
+
*
|
|
457
|
+
* These are thin WebSocket translators over the shared, surface-agnostic
|
|
458
|
+
* management core in `@wrongstack/mcp` (`manage.ts`) — the SAME core the REPL
|
|
459
|
+
* `/mcp` command writes against (same config.json, same MCPRegistry). All the
|
|
460
|
+
* config IO, url/header persistence, and live registry start/stop logic lives
|
|
461
|
+
* there; here we only map structured results to WS events the browser expects.
|
|
462
|
+
*/
|
|
463
|
+
|
|
464
|
+
/** mcp.list — configured servers merged with live registry status + tools. */
|
|
465
|
+
declare function handleMcpList(ws: WebSocket, _msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
|
|
466
|
+
/** mcp.add — persist a new server (incl. url/headers) and start it if enabled. */
|
|
467
|
+
declare function handleMcpAdd(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
|
|
468
|
+
/** mcp.update — re-persist config (incl. url/headers) and re-apply to registry. */
|
|
469
|
+
declare function handleMcpUpdate(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
|
|
470
|
+
/** mcp.remove — stop the server and delete it from config. */
|
|
471
|
+
declare function handleMcpRemove(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
|
|
472
|
+
/** mcp.enable — flip enabled:true in config and start the server. */
|
|
473
|
+
declare function handleMcpEnable(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
|
|
474
|
+
/** mcp.disable — stop the server and flip enabled:false in config. */
|
|
475
|
+
declare function handleMcpDisable(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
|
|
476
|
+
/** mcp.sleep — stop a running server (config stays enabled). */
|
|
477
|
+
declare function handleMcpSleep(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
|
|
478
|
+
/** mcp.wake — restart a sleeping/stopped server from config. */
|
|
479
|
+
declare function handleMcpWake(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
|
|
480
|
+
/** mcp.restart — stop + start a server. */
|
|
481
|
+
declare function handleMcpRestart(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
|
|
482
|
+
/** mcp.discover — ensure the server is running and report its live tools. */
|
|
483
|
+
declare function handleMcpDiscover(ws: WebSocket, msg: WSClientMessage, globalConfigPath: string, mcpRegistry?: MCPRegistry): Promise<void>;
|
|
484
|
+
|
|
385
485
|
/**
|
|
386
486
|
* Custom context modes — user-defined presets that are loaded from disk,
|
|
387
487
|
* merged with the built-in modes, and managed via WebSocket CRUD handlers.
|
|
@@ -510,8 +610,10 @@ interface KeyOpResult {
|
|
|
510
610
|
declare function normalizeKeys(cfg: ProviderConfig): ProviderApiKey[];
|
|
511
611
|
/**
|
|
512
612
|
* Write a normalized key list back onto a provider config: drop all key fields
|
|
513
|
-
* when empty, otherwise sync `apiKeys
|
|
514
|
-
* key
|
|
613
|
+
* when empty, otherwise sync `apiKeys` and re-point `activeKey` if it no longer
|
|
614
|
+
* names a present key. Does NOT mirror the plaintext key to the legacy `apiKey`
|
|
615
|
+
* field — that would leak the secret on accidental serialization. Consumers
|
|
616
|
+
* that need the real key should read from `apiKeys[]` directly.
|
|
515
617
|
*/
|
|
516
618
|
declare function writeKeysBack(cfg: ProviderConfig, keys: ProviderApiKey[]): void;
|
|
517
619
|
/** Mask a secret for display: `••••` for short keys, `abcd…wxyz` otherwise. */
|
|
@@ -607,10 +709,75 @@ declare class AutoPhaseWebSocketHandler {
|
|
|
607
709
|
private send;
|
|
608
710
|
}
|
|
609
711
|
|
|
712
|
+
/**
|
|
713
|
+
* Shared skills WebSocket handlers for both the standalone WebUI server
|
|
714
|
+
* (`packages/webui/src/server/index.ts`) and the CLI's `--webui` embedded
|
|
715
|
+
* server (`packages/cli/src/webui-server.ts`).
|
|
716
|
+
*
|
|
717
|
+
* These were previously inlined in BOTH servers, and the CLI copy had
|
|
718
|
+
* drifted — it only wired `skills.list`, so `skills.content` /
|
|
719
|
+
* `skills.export` / `skills.update` (and install/uninstall/create/edit)
|
|
720
|
+
* fell through to the "Unhandled message type" warning even though the
|
|
721
|
+
* SkillsPanel sends them. Extracting the full set here gives both servers
|
|
722
|
+
* one source of truth. Each function handles the full request→response
|
|
723
|
+
* cycle for one message type; callers drop them into their switch:
|
|
724
|
+
*
|
|
725
|
+
* case 'skills.content': return handleSkillsContent(ws, skillsCtx, msg);
|
|
726
|
+
*
|
|
727
|
+
* The logic is a verbatim lift of the standalone's inline cases — only the
|
|
728
|
+
* dependency references changed (`skillLoader`/`skillInstaller`/
|
|
729
|
+
* `projectRoot` → `ctx.*`, local `send`/`errMessage` → imported helpers).
|
|
730
|
+
*/
|
|
731
|
+
|
|
732
|
+
interface SkillsContext {
|
|
733
|
+
/** Backs skills.list/content/edit/export. Absent ⇒ feature disabled. */
|
|
734
|
+
skillLoader: SkillLoader | undefined;
|
|
735
|
+
/** Backs skills.install/uninstall/update. Absent ⇒ those ops disabled. */
|
|
736
|
+
skillInstaller: SkillInstaller | undefined;
|
|
737
|
+
/** Project root — used by skills.create to write `.wrongstack/skills/…`. */
|
|
738
|
+
projectRoot: string;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Read a single skill's body + its directory's related files + which other
|
|
742
|
+
* skills reference it by name. Powers the skill detail/preview view.
|
|
743
|
+
*/
|
|
744
|
+
declare function handleSkillsContent(ws: WebSocket, ctx: SkillsContext, msg: unknown): Promise<void>;
|
|
745
|
+
/**
|
|
746
|
+
* Install a skill from a git ref (`owner/repo` or URL). Optional `global`
|
|
747
|
+
* installs into the user-wide skills dir instead of the project's.
|
|
748
|
+
*/
|
|
749
|
+
declare function handleSkillsInstall(ws: WebSocket, ctx: SkillsContext, msg: unknown): Promise<void>;
|
|
750
|
+
/**
|
|
751
|
+
* Uninstall a skill by name. Optional `global` restricts/Targets the
|
|
752
|
+
* user-wide install.
|
|
753
|
+
*/
|
|
754
|
+
declare function handleSkillsUninstall(ws: WebSocket, ctx: SkillsContext, msg: unknown): Promise<void>;
|
|
755
|
+
/**
|
|
756
|
+
* Update one skill (`name`) or all installed skills (when `name` is
|
|
757
|
+
* omitted). Reports per-skill updated/unchanged/error tallies.
|
|
758
|
+
*/
|
|
759
|
+
declare function handleSkillsUpdate(ws: WebSocket, ctx: SkillsContext, msg: unknown): Promise<void>;
|
|
760
|
+
/**
|
|
761
|
+
* Scaffold a new project- or global-scoped skill from a name + description.
|
|
762
|
+
* Writes a templated `SKILL.md` under `.wrongstack/skills/<name>/` (project)
|
|
763
|
+
* or the user-wide skills dir (global).
|
|
764
|
+
*/
|
|
765
|
+
declare function handleSkillsCreate(ws: WebSocket, ctx: SkillsContext, msg: unknown): Promise<void>;
|
|
766
|
+
/**
|
|
767
|
+
* Overwrite a skill's body. Refuses bundled skills (read-only) and unknown
|
|
768
|
+
* names.
|
|
769
|
+
*/
|
|
770
|
+
declare function handleSkillsEdit(ws: WebSocket, ctx: SkillsContext, msg: unknown): Promise<void>;
|
|
771
|
+
/**
|
|
772
|
+
* Export every readable skill as a base64-encoded zip (one folder per skill,
|
|
773
|
+
* each with its `SKILL.md`). Powers the panel's "Export all" button.
|
|
774
|
+
*/
|
|
775
|
+
declare function handleSkillsExport(ws: WebSocket, ctx: SkillsContext): Promise<void>;
|
|
776
|
+
|
|
610
777
|
declare function startWebUI(opts?: WebUIOptions & {
|
|
611
778
|
wsPort?: number | undefined;
|
|
612
779
|
wsHost?: string | undefined;
|
|
613
780
|
open?: boolean | undefined;
|
|
614
781
|
}): Promise<void>;
|
|
615
782
|
|
|
616
|
-
export { AutoPhaseWebSocketHandler, type BackendServices, type ConnectedClient, type ContextBreakdown, type CreateHttpServerOptions, type CustomContextMode, type CustomModeStore, type EternalBroadcast, type EternalSubscribe, type EternalSubscription, type KeyOpResult, type MessageTokenEntry, type ProvidersRecord, type ShellOpenRequest, type ShellOpenResult, type ShellOpenTarget, type ToolTokenEntry, type VerifyClientInput, type WSClientMessage, type WSServerMessage, type WebUIInstanceRecord, type WebUIOptions, addProvider, broadcast, browserOpenCommand, buildCspHeader, createCustomModeStore, createEternalSubscription, createHttpServer, createProviderConfigIO, defaultBaseDir, deleteKey, errMessage, estimateTokens, extractToken, findFreePort, formatInstances, generateAuthToken, handleFilesList, handleFilesRead, handleFilesTree, handleFilesWrite, handleMemoryForget, handleMemoryList, handleMemoryRemember, handleShellOpen, hostHeaderOk, injectWsPort, isLoopbackBind, isLoopbackHostname, isPortFree, listInstances, loadSavedProviders, maskedKey, messagePreview, messageTokens, normalizeKeys, openBrowser, registerInstance, registryPath, removeProvider, saveProviders, send, sendResult, setActiveKey, startWebUI, stringifyContent, tokenMatches, unregisterInstance, upsertKey, verifyClient, writeKeysBack };
|
|
783
|
+
export { AutoPhaseWebSocketHandler, type BackendServices, type ConnectedClient, type ContextBreakdown, type CreateHttpServerOptions, type CustomContextMode, type CustomModeStore, type EternalBroadcast, type EternalSubscribe, type EternalSubscription, type KeyOpResult, type MessageTokenEntry, type ProvidersRecord, type ShellOpenRequest, type ShellOpenResult, type ShellOpenTarget, type SkillsContext, type ToolTokenEntry, type VerifyClientInput, type WSClientMessage, type WSServerMessage, type WebUIInstanceRecord, type WebUIOptions, addProvider, broadcast, browserOpenCommand, buildCspHeader, createCustomModeStore, createEternalSubscription, createHttpServer, createProviderConfigIO, defaultBaseDir, deleteKey, errMessage, estimateTokens, extractToken, findFreePort, formatInstances, generateAuthToken, handleFilesList, handleFilesRead, handleFilesTree, handleFilesWrite, handleGitChanges, handleGitDiff, handleGitInfo, handleMcpAdd, handleMcpDisable, handleMcpDiscover, handleMcpEnable, handleMcpList, handleMcpRemove, handleMcpRestart, handleMcpSleep, handleMcpUpdate, handleMcpWake, handleMemoryForget, handleMemoryList, handleMemoryRemember, handleShellOpen, handleSkillsContent, handleSkillsCreate, handleSkillsEdit, handleSkillsExport, handleSkillsInstall, handleSkillsUninstall, handleSkillsUpdate, hostHeaderOk, injectWsPort, isLoopbackBind, isLoopbackHostname, isPortFree, listInstances, loadSavedProviders, maskedKey, messagePreview, messageTokens, normalizeKeys, openBrowser, registerInstance, registryPath, removeProvider, saveProviders, send, sendResult, setActiveKey, startWebUI, stringifyContent, tokenMatches, unregisterInstance, upsertKey, verifyClient, writeKeysBack };
|