@wrongstack/webui-server 0.302.0 → 0.303.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/index.js +630 -308
- package/dist/server/agent-roster-handlers.d.ts +7 -6
- package/dist/server/codemap-cache.d.ts +13 -1
- package/dist/server/connections-health-route.d.ts +2 -2
- package/dist/server/conversation-operations.d.ts +9 -4
- package/dist/server/embedded-host-adapters.d.ts +7 -5
- package/dist/server/entry.js +901 -289
- package/dist/server/handlers/worklist-handlers.d.ts +23 -4
- package/dist/server/handlers.js +126 -23
- package/dist/server/kanban-route-protocol.d.ts +1 -1
- package/dist/server/kanban-routes.d.ts +10 -2
- package/dist/server/kanban-supervisor.d.ts +1 -1
- package/dist/server/lifecycle.d.ts +7 -0
- package/dist/server/message-dispatcher.d.ts +10 -0
- package/dist/server/server-runtime.d.ts +22 -3
- package/dist/server/session-handlers.d.ts +3 -2
- package/dist/server/standalone-session-identity.d.ts +10 -2
- package/package.json +11 -11
|
@@ -12,22 +12,41 @@ export interface WorklistContext {
|
|
|
12
12
|
send: (ws: WebSocket, msg: WSServerMessage) => void;
|
|
13
13
|
broadcast: (msg: WSServerMessage) => void;
|
|
14
14
|
replaceTodos?: ((todos: TodoItem[]) => void) | undefined;
|
|
15
|
+
mutateTodos?: ((todos: TodoItem[]) => Promise<{
|
|
16
|
+
todos: TodoItem[];
|
|
17
|
+
warnings?: string[] | undefined;
|
|
18
|
+
}>) | undefined;
|
|
19
|
+
mutateTaskStatus?: ((id: string, status: 'pending' | 'in_progress' | 'blocked' | 'failed' | 'review' | 'completed') => Promise<{
|
|
20
|
+
ok: boolean;
|
|
21
|
+
message: string;
|
|
22
|
+
}>) | undefined;
|
|
23
|
+
mutatePlan?: ((operation: {
|
|
24
|
+
action: 'template_use';
|
|
25
|
+
template: string;
|
|
26
|
+
} | {
|
|
27
|
+
action: 'status';
|
|
28
|
+
target: string;
|
|
29
|
+
status: 'open' | 'in_progress' | 'done';
|
|
30
|
+
}) => Promise<{
|
|
31
|
+
ok: boolean;
|
|
32
|
+
message: string;
|
|
33
|
+
}>) | undefined;
|
|
15
34
|
}
|
|
16
35
|
export interface WorklistMessage {
|
|
17
36
|
type: string;
|
|
18
37
|
payload?: unknown;
|
|
19
38
|
}
|
|
20
39
|
export declare function handleTodosGet(ctx: WorklistContext, ws: WebSocket): void;
|
|
21
|
-
export declare function handleTodosClear(ctx: WorklistContext, ws: WebSocket): void
|
|
40
|
+
export declare function handleTodosClear(ctx: WorklistContext, ws: WebSocket): Promise<void>;
|
|
22
41
|
export declare function handleTodosRemove(ctx: WorklistContext, ws: WebSocket, payload: {
|
|
23
42
|
id?: string | undefined;
|
|
24
43
|
index?: number | undefined;
|
|
25
|
-
} | undefined): void
|
|
44
|
+
} | undefined): Promise<void>;
|
|
26
45
|
export declare function handleTodoUpdate(ctx: WorklistContext, ws: WebSocket, payload: {
|
|
27
|
-
id
|
|
46
|
+
id?: string | undefined;
|
|
28
47
|
status?: TodoItem['status'] | undefined;
|
|
29
48
|
activeForm?: string | undefined;
|
|
30
|
-
}): void
|
|
49
|
+
} | undefined): Promise<void>;
|
|
31
50
|
export declare function handleTasksGet(ctx: WorklistContext, ws: WebSocket): Promise<void>;
|
|
32
51
|
export declare function handleTaskUpdate(ctx: WorklistContext, ws: WebSocket, payload: {
|
|
33
52
|
id: string;
|
package/dist/server/handlers.js
CHANGED
|
@@ -55,12 +55,34 @@ function handleTodosGet(ctx, ws) {
|
|
|
55
55
|
payload: sessionPayload(ctx, { todos: [...ctx.context.todos] })
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
-
function
|
|
59
|
-
ctx.
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
async function commitTodos(ctx, todos) {
|
|
59
|
+
if (ctx.mutateTodos) {
|
|
60
|
+
const result = await ctx.mutateTodos(todos);
|
|
61
|
+
return { todos: result.todos, warnings: result.warnings ?? [] };
|
|
62
|
+
}
|
|
63
|
+
ctx.replaceTodos?.(todos);
|
|
64
|
+
return { todos: [...todos], warnings: [] };
|
|
65
|
+
}
|
|
66
|
+
function managedProjectionMessage() {
|
|
67
|
+
return "Kanban-bound todos are task projections. Change or remove the task from Kanban.";
|
|
62
68
|
}
|
|
63
|
-
function
|
|
69
|
+
async function handleTodosClear(ctx, ws) {
|
|
70
|
+
if (ctx.context.todos.some((todo) => todo.kanbanBoardId && todo.kanbanTaskId)) {
|
|
71
|
+
sendResult(ctx, ws, false, managedProjectionMessage());
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const result = await commitTodos(ctx, []);
|
|
76
|
+
sendResult(ctx, ws, true, "Todos cleared");
|
|
77
|
+
ctx.broadcast({
|
|
78
|
+
type: "todos.updated",
|
|
79
|
+
payload: sessionPayload(ctx, { todos: result.todos })
|
|
80
|
+
});
|
|
81
|
+
} catch (error) {
|
|
82
|
+
sendResult(ctx, ws, false, error instanceof Error ? error.message : String(error));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function handleTodosRemove(ctx, ws, payload) {
|
|
64
86
|
if (!payload) {
|
|
65
87
|
sendResult(ctx, ws, false, "Missing id or index");
|
|
66
88
|
return;
|
|
@@ -77,12 +99,27 @@ function handleTodosRemove(ctx, ws, payload) {
|
|
|
77
99
|
sendResult(ctx, ws, false, "Todo not found");
|
|
78
100
|
return;
|
|
79
101
|
}
|
|
102
|
+
if (removed.kanbanBoardId && removed.kanbanTaskId) {
|
|
103
|
+
sendResult(ctx, ws, false, managedProjectionMessage());
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
80
106
|
const next = [...todos.slice(0, targetIndex), ...todos.slice(targetIndex + 1)];
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
107
|
+
try {
|
|
108
|
+
const result = await commitTodos(ctx, next);
|
|
109
|
+
sendResult(ctx, ws, true, `Removed: ${removed.content}`);
|
|
110
|
+
ctx.broadcast({
|
|
111
|
+
type: "todos.updated",
|
|
112
|
+
payload: sessionPayload(ctx, { todos: result.todos })
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
sendResult(ctx, ws, false, error instanceof Error ? error.message : String(error));
|
|
116
|
+
}
|
|
84
117
|
}
|
|
85
|
-
function handleTodoUpdate(ctx, ws, payload) {
|
|
118
|
+
async function handleTodoUpdate(ctx, ws, payload) {
|
|
119
|
+
if (!payload || typeof payload.id !== "string" || payload.status !== void 0 && payload.status !== "pending" && payload.status !== "in_progress" && payload.status !== "completed" || payload.activeForm !== void 0 && typeof payload.activeForm !== "string") {
|
|
120
|
+
sendResult(ctx, ws, false, "Invalid todo update payload");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
86
123
|
const index = ctx.context.todos.findIndex((todo) => todo.id === payload.id);
|
|
87
124
|
const existing = ctx.context.todos[index];
|
|
88
125
|
if (index === -1 || !existing) {
|
|
@@ -95,9 +132,25 @@ function handleTodoUpdate(ctx, ws, payload) {
|
|
|
95
132
|
status: payload.status ?? existing.status,
|
|
96
133
|
activeForm: payload.activeForm !== void 0 ? payload.activeForm : existing.activeForm
|
|
97
134
|
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
135
|
+
try {
|
|
136
|
+
const result = await commitTodos(ctx, next);
|
|
137
|
+
const projected = result.todos.find((todo) => todo.id === existing.id);
|
|
138
|
+
const requestedStatus = payload.status ?? existing.status;
|
|
139
|
+
const projectionRejected = Boolean(existing.kanbanBoardId && existing.kanbanTaskId) && projected?.status !== requestedStatus;
|
|
140
|
+
const warning = result.warnings[0];
|
|
141
|
+
sendResult(
|
|
142
|
+
ctx,
|
|
143
|
+
ws,
|
|
144
|
+
!projectionRejected,
|
|
145
|
+
projectionRejected ? warning ?? `Kanban kept "${existing.content}" at ${projected?.status ?? "its current state"}.` : warning ? `Todo "${existing.content}" updated. ${warning}` : `Todo "${existing.content}" updated`
|
|
146
|
+
);
|
|
147
|
+
ctx.broadcast({
|
|
148
|
+
type: "todos.updated",
|
|
149
|
+
payload: sessionPayload(ctx, { todos: result.todos })
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
sendResult(ctx, ws, false, error instanceof Error ? error.message : String(error));
|
|
153
|
+
}
|
|
101
154
|
}
|
|
102
155
|
async function handleTasksGet(ctx, ws) {
|
|
103
156
|
const taskPath = taskPathOf(ctx);
|
|
@@ -125,14 +178,32 @@ async function handleTaskUpdate(ctx, ws, payload) {
|
|
|
125
178
|
return;
|
|
126
179
|
}
|
|
127
180
|
try {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
181
|
+
let file;
|
|
182
|
+
if (ctx.mutateTaskStatus) {
|
|
183
|
+
const result = await ctx.mutateTaskStatus(payload.id, payload.status);
|
|
184
|
+
if (!result.ok) {
|
|
185
|
+
sendResult(ctx, ws, false, result.message);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
file = await loadTasks(taskPath);
|
|
189
|
+
if (!file) throw new Error("Task mutation succeeded but its persisted snapshot is missing.");
|
|
190
|
+
sendResult(ctx, ws, true, result.message);
|
|
191
|
+
} else {
|
|
192
|
+
let matched = false;
|
|
193
|
+
file = await mutateTasks(taskPath, currentSessionId(ctx), async (tasks) => {
|
|
194
|
+
const task = tasks.tasks.find((candidate) => candidate.id === payload.id);
|
|
195
|
+
if (!task) return tasks;
|
|
196
|
+
matched = true;
|
|
197
|
+
task.status = payload.status;
|
|
198
|
+
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
199
|
+
return tasks;
|
|
200
|
+
});
|
|
201
|
+
if (!matched) {
|
|
202
|
+
sendResult(ctx, ws, false, `Task "${payload.id}" not found.`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
sendResult(ctx, ws, true, `Task status updated to "${payload.status}".`);
|
|
206
|
+
}
|
|
136
207
|
ctx.broadcast({
|
|
137
208
|
type: "tasks.updated",
|
|
138
209
|
payload: sessionPayload(ctx, { tasks: file.tasks })
|
|
@@ -179,6 +250,18 @@ async function handlePlanTemplateUse(ctx, ws, template) {
|
|
|
179
250
|
return;
|
|
180
251
|
}
|
|
181
252
|
try {
|
|
253
|
+
if (ctx.mutatePlan) {
|
|
254
|
+
const result = await ctx.mutatePlan({ action: "template_use", template });
|
|
255
|
+
if (!result.ok) {
|
|
256
|
+
sendResult(ctx, ws, false, result.message);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const plan2 = await loadPlan(planPath);
|
|
260
|
+
if (!plan2) throw new Error("Plan mutation succeeded but its persisted snapshot is missing.");
|
|
261
|
+
sendResult(ctx, ws, true, result.message);
|
|
262
|
+
ctx.broadcast({ type: "plan.updated", payload: sessionPayload(ctx, { plan: plan2 }) });
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
182
265
|
const templateDefinition = getPlanTemplate(template);
|
|
183
266
|
if (!templateDefinition) {
|
|
184
267
|
sendResult(ctx, ws, false, `Unknown template "${template}".`);
|
|
@@ -207,6 +290,22 @@ async function handlePlanItemUpdate(ctx, ws, payload) {
|
|
|
207
290
|
return;
|
|
208
291
|
}
|
|
209
292
|
try {
|
|
293
|
+
if (ctx.mutatePlan) {
|
|
294
|
+
const result = await ctx.mutatePlan({
|
|
295
|
+
action: "status",
|
|
296
|
+
target: payload.target,
|
|
297
|
+
status: payload.status
|
|
298
|
+
});
|
|
299
|
+
if (!result.ok) {
|
|
300
|
+
sendResult(ctx, ws, false, result.message);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const plan2 = await loadPlan(planPath);
|
|
304
|
+
if (!plan2) throw new Error("Plan mutation succeeded but its persisted snapshot is missing.");
|
|
305
|
+
sendResult(ctx, ws, true, result.message);
|
|
306
|
+
ctx.broadcast({ type: "plan.updated", payload: sessionPayload(ctx, { plan: plan2 }) });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
210
309
|
let changed = false;
|
|
211
310
|
const plan = await mutatePlan(planPath, currentSessionId(ctx), async (currentPlan) => {
|
|
212
311
|
const before = currentPlan.updatedAt;
|
|
@@ -230,13 +329,17 @@ async function handleWorklistMessage(ctx, ws, message) {
|
|
|
230
329
|
handleTodosGet(ctx, ws);
|
|
231
330
|
return;
|
|
232
331
|
case "todos.clear":
|
|
233
|
-
handleTodosClear(ctx, ws);
|
|
332
|
+
await handleTodosClear(ctx, ws);
|
|
234
333
|
return;
|
|
235
334
|
case "todos.remove":
|
|
236
|
-
handleTodosRemove(
|
|
335
|
+
await handleTodosRemove(
|
|
336
|
+
ctx,
|
|
337
|
+
ws,
|
|
338
|
+
message.payload
|
|
339
|
+
);
|
|
237
340
|
return;
|
|
238
341
|
case "todo.update":
|
|
239
|
-
handleTodoUpdate(
|
|
342
|
+
await handleTodoUpdate(
|
|
240
343
|
ctx,
|
|
241
344
|
ws,
|
|
242
345
|
message.payload
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const KANBAN_CLIENT_MESSAGE_TYPES: readonly ['kanban.capabilities', 'kanban.column.add', 'kanban.column.remove', 'kanban.create', 'kanban.decomposition.approve', 'kanban.decomposition.reject', 'kanban.delete', 'kanban.duplicate', 'kanban.generate', 'kanban.get', 'kanban.health', 'kanban.list', 'kanban.snapshot', 'kanban.supervisor.audit', 'kanban.supervisor.status', 'kanban.task.activity', 'kanban.task.activity.add', 'kanban.task.add', 'kanban.task.assign', 'kanban.task.chain', 'kanban.task.chain.get', 'kanban.task.check.add', 'kanban.task.check.update', 'kanban.task.claim', 'kanban.task.copy', 'kanban.task.dispatch', 'kanban.task.get', 'kanban.task.merge', 'kanban.task.metric.add', 'kanban.task.metric.update', 'kanban.task.move', 'kanban.task.note.add', 'kanban.task.ready', 'kanban.task.release', 'kanban.task.remove', 'kanban.task.split', 'kanban.task.transfer', 'kanban.task.transition', 'kanban.task.update', 'kanban.task.verify', 'kanban.taskgraph.export', 'kanban.taskgraph.sync', 'kanban.update'];
|
|
1
|
+
export declare const KANBAN_CLIENT_MESSAGE_TYPES: readonly ['kanban.capabilities', 'kanban.column.add', 'kanban.column.remove', 'kanban.create', 'kanban.decomposition.approve', 'kanban.decomposition.reject', 'kanban.delete', 'kanban.duplicate', 'kanban.generate', 'kanban.get', 'kanban.health', 'kanban.list', 'kanban.snapshot', 'kanban.supervisor.audit', 'kanban.supervisor.status', 'kanban.task.activity', 'kanban.task.activity.add', 'kanban.task.add', 'kanban.task.assign', 'kanban.task.chain', 'kanban.task.chain.get', 'kanban.task.check.add', 'kanban.task.check.update', 'kanban.task.claim', 'kanban.task.copy', 'kanban.task.dispatch', 'kanban.task.get', 'kanban.task.merge', 'kanban.task.metric.add', 'kanban.task.metric.update', 'kanban.task.move', 'kanban.task.note.add', 'kanban.task.ready', 'kanban.task.release', 'kanban.task.remove', 'kanban.task.split', 'kanban.task.transfer', 'kanban.task.transition', 'kanban.task.update', 'kanban.task.verify', 'kanban.taskgraph.export', 'kanban.taskgraph.sync', 'kanban.update', 'kanban.workbench'];
|
|
2
2
|
//# sourceMappingURL=kanban-route-protocol.d.ts.map
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import type { Context } from '@wrongstack/core/agent';
|
|
2
2
|
import type { WebSocket } from 'ws';
|
|
3
|
-
import type { WSClientMessage, WSServerMessage } from './types.js';
|
|
4
3
|
import { type KanbanTaskDispatcher } from './kanban-dispatch.js';
|
|
4
|
+
import type { KanbanSupervisor } from './kanban-supervisor.js';
|
|
5
|
+
import type { WSClientMessage, WSServerMessage } from './types.js';
|
|
6
|
+
export { type KanbanBoardPage, paginateKanbanBoards } from './kanban-route-pagination.js';
|
|
5
7
|
export { KANBAN_CLIENT_MESSAGE_TYPES } from './kanban-route-protocol.js';
|
|
6
|
-
export { paginateKanbanBoards, type KanbanBoardPage } from './kanban-route-pagination.js';
|
|
7
8
|
export interface KanbanRouteContext {
|
|
8
9
|
projectRoot: string;
|
|
9
10
|
context?: Context | undefined;
|
|
10
11
|
broadcast?: ((msg: WSServerMessage) => void) | undefined;
|
|
11
12
|
dispatchTask?: KanbanTaskDispatcher | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Background supervisor (if enabled). When present, the `kanban.supervisor.audit`
|
|
15
|
+
* and `kanban.supervisor.status` routes delegate to its in-process audit cycle
|
|
16
|
+
* instead of running a second reconcile/health/recover chain — preventing the
|
|
17
|
+
* double-audit that happens when both paths refresh the same board concurrently.
|
|
18
|
+
*/
|
|
19
|
+
supervisor?: KanbanSupervisor | undefined;
|
|
12
20
|
}
|
|
13
21
|
export declare function handleKanbanRoute(ws: WebSocket, msg: WSClientMessage, ctx: KanbanRouteContext): Promise<boolean>;
|
|
14
22
|
//# sourceMappingURL=kanban-routes.d.ts.map
|
|
@@ -25,7 +25,7 @@ export interface KanbanSupervisorDispatchOptions {
|
|
|
25
25
|
}) => void | Promise<void>) | undefined;
|
|
26
26
|
}
|
|
27
27
|
export interface KanbanSupervisorDeps {
|
|
28
|
-
projectRoot: string;
|
|
28
|
+
projectRoot: string | (() => string);
|
|
29
29
|
broadcast: (message: {
|
|
30
30
|
type: string;
|
|
31
31
|
payload: unknown;
|
|
@@ -38,6 +38,13 @@ export interface LifecycleResources {
|
|
|
38
38
|
* logged, never thrown — cleanup must not block a clean shutdown.
|
|
39
39
|
*/
|
|
40
40
|
onShutdown?: (() => Promise<void> | void) | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Fires **before** any HTTP/WS servers close. Use this for cleanup that must
|
|
43
|
+
* finish while the network is still up — e.g. telling a Kanban supervisor
|
|
44
|
+
* to flush its periodic tick so no in-flight `kanban.*` broadcast races a
|
|
45
|
+
* `WebSocketServer.close()`.
|
|
46
|
+
*/
|
|
47
|
+
onPreShutdown?: (() => Promise<void> | void) | undefined;
|
|
41
48
|
/** Output sink. Defaults to `console.log`. */
|
|
42
49
|
log?: ((msg: string) => void) | undefined;
|
|
43
50
|
/** Process exit. Defaults to `process.exit`. Injectable for tests. */
|
|
@@ -29,6 +29,9 @@ import type { ConnectedClient, WSClientMessage } from './types.js';
|
|
|
29
29
|
interface RunLockControl {
|
|
30
30
|
get(): AbortController | null;
|
|
31
31
|
set(ctrl: AbortController | null): void;
|
|
32
|
+
/** Session ID that owns the active run, or null when idle. */
|
|
33
|
+
getSession(): string | null;
|
|
34
|
+
setSession(id: string | null): void;
|
|
32
35
|
}
|
|
33
36
|
interface MessageDispatcherOptions {
|
|
34
37
|
state: WebuiMutableState;
|
|
@@ -47,6 +50,13 @@ interface MessageDispatcherOptions {
|
|
|
47
50
|
runLock: RunLockControl;
|
|
48
51
|
/** Pending permission confirmations — tool.confirm_result resolves one. */
|
|
49
52
|
pendingConfirms: Map<string, PendingConfirm>;
|
|
53
|
+
/**
|
|
54
|
+
* Caller-supplied register hook. The dispatcher calls this **once** during
|
|
55
|
+
* construction to hand its disposer to the caller. The caller is
|
|
56
|
+
* responsible for invoking the registered disposer during its own shutdown
|
|
57
|
+
* — the dispatcher itself never invokes the disposer.
|
|
58
|
+
*/
|
|
59
|
+
onDispose?: ((disposer: () => void) => void) | undefined;
|
|
50
60
|
}
|
|
51
61
|
/**
|
|
52
62
|
* Build the inbound message dispatcher. Mirrors the `handleMessage` closure
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server lifecycle helpers for the standalone WebUI server.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1e of the god-module split: the port resolution, WS auth/server
|
|
5
|
+
* creation, event arming, session-start payload builder, HTTP server
|
|
6
|
+
* startup, and graceful shutdown registration all moved here from
|
|
7
|
+
* `start-webui.ts` so the orchestrator reads as connect-the-dots.
|
|
8
|
+
*
|
|
9
|
+
* Each function is a pure construction step — no behaviour change. The
|
|
10
|
+
* WS/HTTP/shutdown wiring that used to be inline (~370 lines) now lives
|
|
11
|
+
* behind four focused entry points.
|
|
12
|
+
*/
|
|
1
13
|
import type { Config, ModelsRegistry } from '@wrongstack/core/types';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
14
|
+
import { type WebSocket, WebSocketServer } from 'ws';
|
|
15
|
+
import { type FileWatcherMetrics, setupEvents } from './setup-events.js';
|
|
4
16
|
import type { ConnectedClient } from './types.js';
|
|
5
17
|
interface ResolvedPorts {
|
|
6
18
|
wsHost: string;
|
|
@@ -110,8 +122,15 @@ interface ShutdownDeps {
|
|
|
110
122
|
flushSession: () => Promise<void>;
|
|
111
123
|
clients: () => IterableIterator<WebSocket>;
|
|
112
124
|
servers: Array<import('node:http').Server | WebSocketServer>;
|
|
125
|
+
/**
|
|
126
|
+
* Fires **before** any HTTP/WS servers close. Use this for cleanup that must
|
|
127
|
+
* finish while the network is still up — e.g. telling a Kanban supervisor
|
|
128
|
+
* to flush its periodic tick so no in-flight `kanban.*` broadcast races a
|
|
129
|
+
* `WebSocketServer.close()`.
|
|
130
|
+
*/
|
|
131
|
+
onPreShutdown?: () => Promise<void> | void;
|
|
113
132
|
onShutdown: () => Promise<void> | void;
|
|
114
133
|
}
|
|
115
|
-
export declare function registerShutdown(deps: ShutdownDeps): void;
|
|
134
|
+
export declare function registerShutdown(deps: ShutdownDeps): () => void;
|
|
116
135
|
export {};
|
|
117
136
|
//# sourceMappingURL=server-runtime.d.ts.map
|
|
@@ -80,8 +80,9 @@ export interface SessionHandlersContext {
|
|
|
80
80
|
* swapped. Without this, a run started in the previous session keeps
|
|
81
81
|
* streaming/tool-calling in the background after session.new/resume.
|
|
82
82
|
*/
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
/** When sessionId is provided, abort only that session's run; otherwise abort all. */
|
|
84
|
+
abortActiveRun?: ((sessionId?: string) => void) | undefined;
|
|
85
|
+
isRunActive?: ((sessionId?: string) => boolean) | undefined;
|
|
85
86
|
sessionStartPayload: (overrides?: Record<string, unknown>) => Promise<SessionStartPayload>;
|
|
86
87
|
}
|
|
87
88
|
export declare function createSessionHandlers(ctx: SessionHandlersContext): SessionRouteHandlers;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EventBus } from '@wrongstack/core/kernel';
|
|
2
|
-
import { AgentStatusTracker,
|
|
2
|
+
import { AgentStatusTracker, getSessionRegistry } from '@wrongstack/core/storage';
|
|
3
3
|
import type { Config, Logger } from '@wrongstack/core/types';
|
|
4
4
|
export interface StandaloneSessionIdentityPaths {
|
|
5
5
|
globalRoot: string;
|
|
@@ -13,10 +13,17 @@ export interface StandaloneSessionIdentityOptions {
|
|
|
13
13
|
paths: StandaloneSessionIdentityPaths;
|
|
14
14
|
workingDir: string;
|
|
15
15
|
initialSessionId: string;
|
|
16
|
-
sessionRegistry?:
|
|
16
|
+
sessionRegistry?: SessionIdentityRegistry | undefined;
|
|
17
17
|
/** Test/embedding escape hatch; production defaults to enabled. */
|
|
18
18
|
enableHqTelemetry?: boolean | undefined;
|
|
19
19
|
}
|
|
20
|
+
interface SessionIdentityRegistry {
|
|
21
|
+
register(entry: Parameters<ReturnType<typeof getSessionRegistry>['register']>[0]): Promise<void>;
|
|
22
|
+
updateAgents(agents: Parameters<ReturnType<typeof getSessionRegistry>['updateAgents']>[0]): Promise<void>;
|
|
23
|
+
markClosing(): Promise<void>;
|
|
24
|
+
unregister(): Promise<void>;
|
|
25
|
+
reserveResume?: ReturnType<typeof getSessionRegistry>['reserveResume'] | undefined;
|
|
26
|
+
}
|
|
20
27
|
export interface SessionIdentityTarget {
|
|
21
28
|
projectSlug: string;
|
|
22
29
|
projectRoot: string;
|
|
@@ -37,4 +44,5 @@ export interface StandaloneSessionIdentityLifecycle {
|
|
|
37
44
|
* external surface is fail-soft, while identity selection itself stays live.
|
|
38
45
|
*/
|
|
39
46
|
export declare function createStandaloneSessionIdentityLifecycle(opts: StandaloneSessionIdentityOptions): Promise<StandaloneSessionIdentityLifecycle>;
|
|
47
|
+
export {};
|
|
40
48
|
//# sourceMappingURL=standalone-session-identity.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/webui-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.303.0",
|
|
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,16 +40,16 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"ws": "^8.21.1",
|
|
43
|
-
"@wrongstack/core": "0.
|
|
44
|
-
"@wrongstack/
|
|
45
|
-
"@wrongstack/
|
|
46
|
-
"@wrongstack/
|
|
47
|
-
"@wrongstack/
|
|
48
|
-
"@wrongstack/sage": "0.
|
|
49
|
-
"@wrongstack/providers": "0.
|
|
50
|
-
"@wrongstack/
|
|
51
|
-
"@wrongstack/
|
|
52
|
-
"@wrongstack/
|
|
43
|
+
"@wrongstack/core": "0.303.0",
|
|
44
|
+
"@wrongstack/kanban": "0.303.0",
|
|
45
|
+
"@wrongstack/requirement-intake": "0.303.0",
|
|
46
|
+
"@wrongstack/runtime": "0.303.0",
|
|
47
|
+
"@wrongstack/mcp": "0.303.0",
|
|
48
|
+
"@wrongstack/sage": "0.303.0",
|
|
49
|
+
"@wrongstack/providers": "0.303.0",
|
|
50
|
+
"@wrongstack/sdd": "0.303.0",
|
|
51
|
+
"@wrongstack/techstack": "0.303.0",
|
|
52
|
+
"@wrongstack/tools": "0.303.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^26.1.2",
|