@cnstra/devtools-server 1.2.0 → 3.0.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.cjs CHANGED
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var devtoolsDto = require('@cnstra/devtools-dto');
4
+ var persist = require('@cnstra/persist');
4
5
 
5
6
  // src/index.ts
6
7
  var CNSDevToolsServer = class {
7
- constructor(repository) {
8
+ constructor(repository, stimulationRepository = new persist.CNSInMemoryStimulationRepository()) {
8
9
  this.repository = repository;
9
10
  /** WebSocket → appId (for app clients) */
10
11
  this.appSockets = /* @__PURE__ */ new Map();
@@ -14,6 +15,7 @@ var CNSDevToolsServer = class {
14
15
  this.uiClients = /* @__PURE__ */ new Set();
15
16
  this.lastCpuUsage = process.cpuUsage();
16
17
  this.lastCpuTime = Date.now();
18
+ this.stimulationRepository = stimulationRepository;
17
19
  }
18
20
  // ─── Message entry point ─────────────────────────────────────────────────
19
21
  async handleMessage(ws, raw) {
@@ -27,14 +29,14 @@ var CNSDevToolsServer = class {
27
29
  if (!data || typeof data !== "object" || !("type" in data)) return;
28
30
  const type = data.type;
29
31
  if (type === "batch") {
30
- const result = devtoolsDto.CNSDTOAppBatchMessageSchema.safeParse(data);
32
+ const result = devtoolsDto.CNSAppBatchMessageDTOSchema.safeParse(data);
31
33
  if (!result.success) return;
32
34
  for (const item of result.data.items) {
33
35
  await this.handleAppBatchItem(ws, item);
34
36
  }
35
37
  return;
36
38
  }
37
- const clientResult = devtoolsDto.CNSDTOClientMessageSchema.safeParse(data);
39
+ const clientResult = devtoolsDto.CNSClientMessageDTOSchema.safeParse(data);
38
40
  if (!clientResult.success) return;
39
41
  const msg = clientResult.data;
40
42
  switch (msg.type) {
@@ -47,17 +49,119 @@ var CNSDevToolsServer = class {
47
49
  case "topology.query":
48
50
  await this.handleTopologyQuery(ws, msg.requestId, msg.cnsId, msg.appId);
49
51
  break;
50
- case "stimulations.query":
51
- await this.handleStimulationsQuery(ws, msg);
52
- break;
53
- case "hops.query":
54
- await this.handleHopsQuery(ws, msg);
55
- break;
56
52
  case "replay.start":
57
53
  await this.handleReplayStart(ws, msg);
58
54
  break;
55
+ case "stimulation.retry":
56
+ await this.handleStimulationRetry(ws, msg);
57
+ break;
58
+ case "stimulation.clone":
59
+ await this.handleStimulationClone(ws, msg);
60
+ break;
61
+ case "cns.stimulations.list":
62
+ await this.handleStimulationsList(ws, msg);
63
+ break;
64
+ case "cns.stimulations.facets":
65
+ await this.handleStimulationsFacets(ws, msg);
66
+ break;
67
+ case "cns.stimulations.snapshot":
68
+ await this.handleStimulationsSnapshot(ws, msg);
69
+ break;
70
+ case "cns.stimulation.detail":
71
+ await this.handleStimulationDetail(ws, msg);
72
+ break;
59
73
  }
60
74
  }
75
+ /**
76
+ * Explorer protocol (bucket-shaped). The roster row = resumable state minus
77
+ * `progress`, plus envelope summaries derived from the run's attempts.
78
+ *
79
+ * Pushdown: if the store implements `ICNSStimulationRosterQuery` (e.g. the
80
+ * Postgres store), the filter + keyset + GROUP BY run INSIDE it (SQL). Else we
81
+ * fall back to the shared reference derivation over the reader — identical
82
+ * results, only the work moves. Roster shapes are structurally the wire DTOs,
83
+ * so no mapping is needed at the boundary.
84
+ */
85
+ toRosterFilter(scopeName, filter) {
86
+ return { ...filter ?? {}, ...scopeName ? { scopeName } : {} };
87
+ }
88
+ async handleStimulationsList(ws, msg) {
89
+ const filter = this.toRosterFilter(msg.scopeName, msg.filter);
90
+ const limit = typeof msg.limit === "number" ? msg.limit : 50;
91
+ const opts = { cursor: msg.cursor, limit };
92
+ const store = this.stimulationRepository;
93
+ const page = persist.supportsRosterQuery(store) ? await store.queryRoster(filter, opts) : persist.keysetPage(
94
+ (await persist.loadRosterEntries(store, filter)).map((e) => e.row),
95
+ opts
96
+ );
97
+ this.send(ws, {
98
+ type: "cns.stimulations.list.result",
99
+ requestId: msg.requestId,
100
+ stimulations: page.rows,
101
+ nextCursor: page.nextCursor,
102
+ total: page.total
103
+ });
104
+ }
105
+ async handleStimulationsFacets(ws, msg) {
106
+ const filter = this.toRosterFilter(msg.scopeName, msg.filter);
107
+ const store = this.stimulationRepository;
108
+ const facets = persist.supportsRosterQuery(store) ? await store.queryFacets(filter, { metricPath: msg.metricPath }) : persist.computeRosterFacets(await persist.loadRosterEntries(store, filter), {
109
+ metricPath: msg.metricPath
110
+ });
111
+ this.send(ws, {
112
+ type: "cns.stimulations.facets.result",
113
+ requestId: msg.requestId,
114
+ facets
115
+ });
116
+ }
117
+ async handleStimulationDetail(ws, msg) {
118
+ const attempts = await this.stimulationRepository.getAttempts(
119
+ msg.stimulationId
120
+ );
121
+ const tasks = [];
122
+ for (const a of attempts) {
123
+ const ts = await this.stimulationRepository.getTasks(
124
+ a.stimulationAttemptId
125
+ );
126
+ tasks.push(...ts);
127
+ }
128
+ this.send(ws, {
129
+ type: "cns.stimulation.detail.result",
130
+ requestId: msg.requestId,
131
+ attempts,
132
+ tasks
133
+ });
134
+ }
135
+ /**
136
+ * The topology/signal views' bulk feed: a bounded, newest-first set of runs as
137
+ * FLAT buckets (stimulations + attempts + tasks) the panel joins client-side by
138
+ * id. Replaces the retired nested `cns.stimulations.query` tree — same data, no
139
+ * server-side join, no client-side flatten.
140
+ */
141
+ async handleStimulationsSnapshot(ws, msg) {
142
+ const filter = this.toRosterFilter(msg.scopeName, msg.filter);
143
+ const store = this.stimulationRepository;
144
+ const limit = typeof msg.limit === "number" ? msg.limit : 200;
145
+ const page = persist.supportsRosterQuery(store) ? await store.queryRoster(filter, { limit }) : persist.keysetPage(
146
+ (await persist.loadRosterEntries(store, filter)).map((e) => e.row),
147
+ { limit }
148
+ );
149
+ const attempts = [];
150
+ const tasks = [];
151
+ for (const row of page.rows) {
152
+ const as = await store.getAttempts(row.stimulationId);
153
+ attempts.push(...as);
154
+ for (const a of as)
155
+ tasks.push(...await store.getTasks(a.stimulationAttemptId));
156
+ }
157
+ this.send(ws, {
158
+ type: "cns.stimulations.snapshot.result",
159
+ requestId: msg.requestId,
160
+ stimulations: page.rows,
161
+ attempts,
162
+ tasks
163
+ });
164
+ }
61
165
  addClient(ws) {
62
166
  this.uiClients.add(ws);
63
167
  if (this.uiClients.size === 1) this.startMetrics();
@@ -79,17 +183,22 @@ var CNSDevToolsServer = class {
79
183
  case "topology":
80
184
  await this.handleTopology(ws, item);
81
185
  break;
82
- case "stimulation.started":
83
- await this.handleStimulationStarted(ws, item.stimulation);
186
+ // ── Name-based resumable model ──
187
+ case "cns.stimulation":
188
+ await this.stimulationRepository.saveStimulation(item.data);
84
189
  break;
85
- case "stimulation.hop":
86
- await this.handleStimulationHop(item.hop);
190
+ case "cns.stimulation.attempt":
191
+ await this.stimulationRepository.saveAttempt(item.data);
87
192
  break;
88
- case "stimulation.completed":
89
- await this.handleStimulationCompleted(item);
193
+ case "cns.stimulation.task":
194
+ await this.stimulationRepository.appendTask(item.data);
90
195
  break;
91
196
  }
92
197
  }
198
+ /** The name-based resumable store, for tests/integrations that want to read it back. */
199
+ getStimulationRepository() {
200
+ return this.stimulationRepository;
201
+ }
93
202
  async handleTopology(ws, msg) {
94
203
  const { appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites } = msg;
95
204
  const now = Date.now();
@@ -114,35 +223,6 @@ var CNSDevToolsServer = class {
114
223
  this.broadcast({ type: "app.connected", app, topology: { cnsId, neurons, collaterals, dendrites } });
115
224
  this.broadcastRaw(JSON.stringify(topologyBroadcast));
116
225
  }
117
- async handleStimulationStarted(ws, stimulation) {
118
- const appId = this.appSockets.get(ws);
119
- if (appId) {
120
- const apps = await this.repository.listApps();
121
- const app = apps.find((a) => a.id === appId);
122
- if (app) await this.repository.upsertApp({ ...app, lastSeenAt: Date.now() });
123
- }
124
- await this.repository.saveStimulation(stimulation);
125
- this.broadcast({ type: "stimulation.started", stimulation });
126
- }
127
- async handleStimulationHop(hop) {
128
- await this.repository.saveHop(hop);
129
- this.broadcast({ type: "stimulation.hop", hop });
130
- }
131
- async handleStimulationCompleted(item) {
132
- await this.repository.completeStimulation(
133
- item.stimulationId,
134
- item.completedAt,
135
- item.hopCount,
136
- item.hasError
137
- );
138
- this.broadcast({
139
- type: "stimulation.completed",
140
- stimulationId: item.stimulationId,
141
- completedAt: item.completedAt,
142
- hopCount: item.hopCount,
143
- hasError: item.hasError
144
- });
145
- }
146
226
  // ─── UI client query handlers ─────────────────────────────────────────────
147
227
  async handleClientConnect(ws) {
148
228
  this.addClient(ws);
@@ -162,25 +242,6 @@ var CNSDevToolsServer = class {
162
242
  if (appId) snapshots = snapshots.filter((s) => s.appId === appId);
163
243
  this.send(ws, { type: "topology.result", requestId, snapshots });
164
244
  }
165
- async handleStimulationsQuery(ws, msg) {
166
- const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : void 0);
167
- if (!appId) {
168
- this.send(ws, { type: "stimulations.result", requestId: msg.requestId, items: [], total: 0, offset: 0 });
169
- return;
170
- }
171
- const { items, total } = await this.repository.getStimulations(appId, msg.filter ?? {});
172
- this.send(ws, {
173
- type: "stimulations.result",
174
- requestId: msg.requestId,
175
- items,
176
- total,
177
- offset: msg.filter?.offset ?? 0
178
- });
179
- }
180
- async handleHopsQuery(ws, msg) {
181
- const items = await this.repository.getHops(msg.stimulationId);
182
- this.send(ws, { type: "hops.result", requestId: msg.requestId, items });
183
- }
184
245
  async handleReplayStart(ws, msg) {
185
246
  const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : void 0);
186
247
  const appSockets = appId ? this.appWsByAppId.get(appId) : void 0;
@@ -198,6 +259,94 @@ var CNSDevToolsServer = class {
198
259
  }
199
260
  }
200
261
  }
262
+ // ─── Resumable actions (retry / clone), Phase 2b-2 ──────────────────────────────
263
+ //
264
+ // The UI's request is thin (a stimulationId). The CNS + name→ref registry live in
265
+ // the APP, so the server can't execute it: it ENRICHES from the resumable store (the
266
+ // stored entry + progress + a server-assigned attempt identity) and forwards a
267
+ // resume/launch command to the owning app, routed by scopeName (= the cns id).
268
+ /** Resolve the app that owns a stimulation's scope, or reject if unreachable. */
269
+ async resolveScopeApp(scopeName) {
270
+ const appId = scopeName ? await this.repository.findAppByCns(scopeName) : void 0;
271
+ const sockets = appId ? this.appWsByAppId.get(appId) : void 0;
272
+ return sockets && sockets.size > 0 ? sockets : void 0;
273
+ }
274
+ async handleStimulationRetry(ws, msg) {
275
+ const reject = (reason) => this.send(ws, {
276
+ type: "stimulation.retry.rejected",
277
+ requestId: msg.requestId,
278
+ stimulationId: msg.stimulationId,
279
+ reason
280
+ });
281
+ const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);
282
+ if (!stim) return reject("unknown stimulation");
283
+ if (stim.progress.tasks.length === 0)
284
+ return reject("nothing to resume (empty frontier)");
285
+ const appSockets = await this.resolveScopeApp(stim.scopeName);
286
+ if (!appSockets) return reject("app not connected");
287
+ const attempts = await this.stimulationRepository.getAttempts(msg.stimulationId);
288
+ const nextAttempt = attempts.length + 1;
289
+ const stimulationAttemptId = devtoolsDto.buildAttemptId(msg.stimulationId, nextAttempt);
290
+ this.send(ws, {
291
+ type: "stimulation.retry.accepted",
292
+ requestId: msg.requestId,
293
+ stimulationId: msg.stimulationId,
294
+ newStimulationAttemptId: stimulationAttemptId
295
+ });
296
+ const payload = JSON.stringify({
297
+ type: "cns.stimulation.resume",
298
+ requestId: msg.requestId,
299
+ scopeName: stim.scopeName,
300
+ stimulationId: msg.stimulationId,
301
+ stimulationAttemptId,
302
+ attemptNumber: nextAttempt,
303
+ entry: stim.entry,
304
+ progress: stim.progress,
305
+ options: msg.options
306
+ });
307
+ for (const appWs of appSockets) {
308
+ try {
309
+ appWs.send(payload);
310
+ } catch {
311
+ }
312
+ }
313
+ }
314
+ async handleStimulationClone(ws, msg) {
315
+ const reject = (reason) => this.send(ws, {
316
+ type: "stimulation.clone.rejected",
317
+ requestId: msg.requestId,
318
+ stimulationId: msg.stimulationId,
319
+ reason
320
+ });
321
+ const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);
322
+ if (!stim) return reject("unknown stimulation");
323
+ const appSockets = await this.resolveScopeApp(stim.scopeName);
324
+ if (!appSockets) return reject("app not connected");
325
+ const newStimulationId = devtoolsDto.buildCloneId(msg.stimulationId);
326
+ const stimulationAttemptId = devtoolsDto.buildAttemptId(newStimulationId, 1);
327
+ this.send(ws, {
328
+ type: "stimulation.clone.accepted",
329
+ requestId: msg.requestId,
330
+ stimulationId: msg.stimulationId,
331
+ newStimulationId,
332
+ newStimulationAttemptId: stimulationAttemptId
333
+ });
334
+ const payload = JSON.stringify({
335
+ type: "cns.stimulation.launch",
336
+ requestId: msg.requestId,
337
+ scopeName: stim.scopeName,
338
+ stimulationId: newStimulationId,
339
+ stimulationAttemptId,
340
+ entry: stim.entry,
341
+ options: msg.options
342
+ });
343
+ for (const appWs of appSockets) {
344
+ try {
345
+ appWs.send(payload);
346
+ } catch {
347
+ }
348
+ }
349
+ }
201
350
  // ─── Broadcast helpers ────────────────────────────────────────────────────
202
351
  broadcast(message) {
203
352
  this.broadcastRaw(JSON.stringify(message));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["CNSDTOAppBatchMessageSchema","CNSDTOClientMessageSchema"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n CNSDTOApp,\n CNSDTOAppBatchMessageSchema,\n CNSDTOClientMessageSchema,\n CNSDTOStimulation,\n CNSDTOStimulationFilter,\n CNSDTOHop,\n CNSDTOTopologyMessage,\n} from '@cnstra/devtools-dto';\nimport { WebSocket } from 'ws';\n\n// ─── Repository interface ─────────────────────────────────────────────────────\n\nexport type CNSDTOTopologySnapshot = Omit<CNSDTOTopologyMessage, 'type'>;\n\nexport interface ICNSDevToolsServerRepository {\n // Apps\n upsertApp(app: CNSDTOApp): void | Promise<void>;\n listApps(): CNSDTOApp[] | Promise<CNSDTOApp[]>;\n\n // Topology\n saveTopology(snapshot: CNSDTOTopologySnapshot): void | Promise<void>;\n getTopology(cnsId?: string): CNSDTOTopologySnapshot[] | Promise<CNSDTOTopologySnapshot[]>;\n\n // Stimulations\n saveStimulation(stimulation: CNSDTOStimulation): void | Promise<void>;\n completeStimulation(\n stimulationId: string,\n completedAt: number,\n hopCount: number,\n hasError: boolean\n ): void | Promise<void>;\n getStimulations(\n appId: string,\n filter: CNSDTOStimulationFilter\n ): { items: CNSDTOStimulation[]; total: number } | Promise<{ items: CNSDTOStimulation[]; total: number }>;\n\n // Hops\n saveHop(hop: CNSDTOHop): void | Promise<void>;\n getHops(stimulationId: string): CNSDTOHop[] | Promise<CNSDTOHop[]>;\n\n // CNS ↔ App mapping\n addCnsToApp(appId: string, cnsId: string): void | Promise<void>;\n getCnsByApp(appId: string): string[] | Promise<string[]>;\n findAppByCns(cnsId: string): string | undefined | Promise<string | undefined>;\n}\n\n// ─── Server ───────────────────────────────────────────────────────────────────\n\nexport class CNSDevToolsServer {\n /** WebSocket → appId (for app clients) */\n private appSockets = new Map<WebSocket, string>();\n /** appId → WebSocket[] (one app may have multiple CNS instances) */\n private appWsByAppId = new Map<string, Set<WebSocket>>();\n /** WebSocket clients that registered as UI clients */\n private uiClients = new Set<WebSocket>();\n\n private metricsTimer?: NodeJS.Timeout;\n private lastCpuUsage = process.cpuUsage();\n private lastCpuTime = Date.now();\n\n constructor(private readonly repository: ICNSDevToolsServerRepository) {}\n\n // ─── Message entry point ─────────────────────────────────────────────────\n\n async handleMessage(\n ws: WebSocket,\n raw: unknown\n ): Promise<void> {\n if (typeof raw !== 'string' && typeof raw !== 'object') return;\n\n let data: unknown;\n try {\n data = typeof raw === 'string' ? JSON.parse(raw) : raw;\n } catch {\n return;\n }\n\n if (!data || typeof data !== 'object' || !('type' in data)) return;\n const type = (data as any).type as string;\n\n // App batch (topology + stimulation events)\n if (type === 'batch') {\n const result = CNSDTOAppBatchMessageSchema.safeParse(data);\n if (!result.success) return;\n for (const item of result.data.items) {\n await this.handleAppBatchItem(ws, item);\n }\n return;\n }\n\n // UI client message\n const clientResult = CNSDTOClientMessageSchema.safeParse(data);\n if (!clientResult.success) return;\n const msg = clientResult.data;\n\n switch (msg.type) {\n case 'client.connect':\n await this.handleClientConnect(ws);\n break;\n case 'apps.query':\n await this.handleAppsQuery(ws, msg.requestId);\n break;\n case 'topology.query':\n await this.handleTopologyQuery(ws, msg.requestId, msg.cnsId, msg.appId);\n break;\n case 'stimulations.query':\n await this.handleStimulationsQuery(ws, msg);\n break;\n case 'hops.query':\n await this.handleHopsQuery(ws, msg);\n break;\n case 'replay.start':\n await this.handleReplayStart(ws, msg);\n break;\n }\n }\n\n addClient(ws: WebSocket): void {\n this.uiClients.add(ws);\n if (this.uiClients.size === 1) this.startMetrics();\n }\n\n removeClient(ws: WebSocket): void {\n this.uiClients.delete(ws);\n this.appSockets.delete(ws);\n if (this.uiClients.size === 0) this.stopMetrics();\n }\n\n async getActiveApps(): Promise<CNSDTOApp[]> {\n return this.repository.listApps();\n }\n\n stop(): void {\n this.stopMetrics();\n }\n\n // ─── App batch item handlers ──────────────────────────────────────────────\n\n private async handleAppBatchItem(ws: WebSocket, item: any): Promise<void> {\n switch (item.type) {\n case 'topology':\n await this.handleTopology(ws, item);\n break;\n case 'stimulation.started':\n await this.handleStimulationStarted(ws, item.stimulation);\n break;\n case 'stimulation.hop':\n await this.handleStimulationHop(item.hop);\n break;\n case 'stimulation.completed':\n await this.handleStimulationCompleted(item);\n break;\n }\n }\n\n private async handleTopology(ws: WebSocket, msg: any): Promise<void> {\n const { appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites } = msg;\n\n const now = Date.now();\n const app: CNSDTOApp = {\n id: appId,\n name: appName,\n version,\n connectedAt: now,\n lastSeenAt: now,\n };\n\n await this.repository.upsertApp(app);\n await this.repository.addCnsToApp(appId, cnsId);\n await this.repository.saveTopology({ appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites });\n\n // Track app socket\n this.appSockets.set(ws, appId);\n if (!this.appWsByAppId.has(appId)) this.appWsByAppId.set(appId, new Set());\n this.appWsByAppId.get(appId)!.add(ws);\n\n // Broadcast topology + app connected to UI clients\n const topologyBroadcast = {\n type: 'topology.result' as const,\n requestId: '__broadcast__',\n snapshots: await this.repository.getTopology(cnsId),\n };\n\n this.broadcast({ type: 'app.connected', app, topology: { cnsId, neurons, collaterals, dendrites } });\n // Also send topology snapshot so UI can refresh graph\n this.broadcastRaw(JSON.stringify(topologyBroadcast));\n }\n\n private async handleStimulationStarted(ws: WebSocket, stimulation: CNSDTOStimulation): Promise<void> {\n // Update lastSeenAt for the app\n const appId = this.appSockets.get(ws);\n if (appId) {\n const apps = await this.repository.listApps();\n const app = apps.find(a => a.id === appId);\n if (app) await this.repository.upsertApp({ ...app, lastSeenAt: Date.now() });\n }\n\n await this.repository.saveStimulation(stimulation);\n this.broadcast({ type: 'stimulation.started', stimulation });\n }\n\n private async handleStimulationHop(hop: CNSDTOHop): Promise<void> {\n await this.repository.saveHop(hop);\n this.broadcast({ type: 'stimulation.hop', hop });\n }\n\n private async handleStimulationCompleted(item: any): Promise<void> {\n await this.repository.completeStimulation(\n item.stimulationId,\n item.completedAt,\n item.hopCount,\n item.hasError\n );\n this.broadcast({\n type: 'stimulation.completed',\n stimulationId: item.stimulationId,\n completedAt: item.completedAt,\n hopCount: item.hopCount,\n hasError: item.hasError,\n });\n }\n\n // ─── UI client query handlers ─────────────────────────────────────────────\n\n private async handleClientConnect(ws: WebSocket): Promise<void> {\n this.addClient(ws);\n this.send(ws, { type: 'apps.result', requestId: '__init__', items: await this.repository.listApps() });\n this.send(ws, {\n type: 'topology.result',\n requestId: '__init__',\n snapshots: await this.repository.getTopology(),\n });\n }\n\n private async handleAppsQuery(ws: WebSocket, requestId: string): Promise<void> {\n const items = await this.repository.listApps();\n this.send(ws, { type: 'apps.result', requestId, items });\n }\n\n private async handleTopologyQuery(\n ws: WebSocket,\n requestId: string,\n cnsId?: string,\n appId?: string\n ): Promise<void> {\n let snapshots = await this.repository.getTopology(cnsId);\n if (appId) snapshots = snapshots.filter(s => s.appId === appId);\n this.send(ws, { type: 'topology.result', requestId, snapshots });\n }\n\n private async handleStimulationsQuery(ws: WebSocket, msg: any): Promise<void> {\n const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : undefined);\n\n if (!appId) {\n this.send(ws, { type: 'stimulations.result', requestId: msg.requestId, items: [], total: 0, offset: 0 });\n return;\n }\n\n const { items, total } = await this.repository.getStimulations(appId, msg.filter ?? {});\n this.send(ws, {\n type: 'stimulations.result',\n requestId: msg.requestId,\n items,\n total,\n offset: msg.filter?.offset ?? 0,\n });\n }\n\n private async handleHopsQuery(ws: WebSocket, msg: any): Promise<void> {\n const items = await this.repository.getHops(msg.stimulationId);\n this.send(ws, { type: 'hops.result', requestId: msg.requestId, items });\n }\n\n private async handleReplayStart(ws: WebSocket, msg: any): Promise<void> {\n const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : undefined);\n const appSockets = appId ? this.appWsByAppId.get(appId) : undefined;\n\n if (!appSockets || appSockets.size === 0) {\n this.send(ws, { type: 'replay.rejected', replayId: msg.replayId, reason: 'App not connected' });\n return;\n }\n\n const newStimulationId = `${msg.stimulationId}-replay-${Date.now()}`;\n this.send(ws, { type: 'replay.accepted', replayId: msg.replayId, newStimulationId });\n\n const payload = JSON.stringify(msg);\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n // ─── Broadcast helpers ────────────────────────────────────────────────────\n\n private broadcast(message: object): void {\n this.broadcastRaw(JSON.stringify(message));\n }\n\n private broadcastRaw(payload: string): void {\n for (const client of this.uiClients) {\n try {\n if ((client as any).readyState === 1) client.send(payload);\n } catch {}\n }\n }\n\n private send(ws: WebSocket, message: object): void {\n try {\n if ((ws as any).readyState === 1) ws.send(JSON.stringify(message));\n } catch {}\n }\n\n // ─── Metrics ──────────────────────────────────────────────────────────────\n\n private startMetrics(): void {\n if (this.metricsTimer) return;\n this.metricsTimer = setInterval(() => {\n const mem = process.memoryUsage();\n const now = Date.now();\n const cpu = process.cpuUsage(this.lastCpuUsage);\n const elapsed = (now - this.lastCpuTime) * 1000;\n const cpuPercent = elapsed > 0 ? ((cpu.user + cpu.system) / elapsed) * 100 : 0;\n this.lastCpuUsage = process.cpuUsage();\n this.lastCpuTime = now;\n\n this.broadcast({\n type: 'server.metrics',\n timestamp: now,\n rssMB: mem.rss / 1024 / 1024,\n heapUsedMB: mem.heapUsed / 1024 / 1024,\n heapTotalMB: mem.heapTotal / 1024 / 1024,\n cpuPercent,\n });\n }, 1000);\n }\n\n private stopMetrics(): void {\n if (this.metricsTimer) {\n clearInterval(this.metricsTimer);\n this.metricsTimer = undefined;\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["CNSInMemoryStimulationRepository","CNSAppBatchMessageDTOSchema","CNSClientMessageDTOSchema","supportsRosterQuery","keysetPage","loadRosterEntries","computeRosterFacets","buildAttemptId","buildCloneId"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n TCNSAppDTO,\n CNSAppBatchMessageDTOSchema,\n CNSClientMessageDTOSchema,\n TCNSTopologyMessageDTO,\n} from '@cnstra/devtools-dto';\nimport type {\n TCNSStimulationFilter,\n TCNSStimulationTask,\n TCNSStimulationAttempt,\n} from '@cnstra/devtools-dto';\nimport { buildAttemptId, buildCloneId } from '@cnstra/devtools-dto';\nimport type {\n ICNSStimulationRepository,\n TCNSStimulationRosterFilter,\n} from '@cnstra/persist';\nimport {\n CNSInMemoryStimulationRepository,\n supportsRosterQuery,\n loadRosterEntries,\n keysetPage,\n computeRosterFacets,\n} from '@cnstra/persist';\nimport { WebSocket } from 'ws';\n\n// ─── Repository interface ─────────────────────────────────────────────────────\n\nexport type TCNSTopologySnapshotDTO = Omit<TCNSTopologyMessageDTO, 'type'>;\n\nexport interface ICNSDevToolsServerRepository {\n // Apps\n upsertApp(app: TCNSAppDTO): void | Promise<void>;\n listApps(): TCNSAppDTO[] | Promise<TCNSAppDTO[]>;\n\n // Topology\n saveTopology(snapshot: TCNSTopologySnapshotDTO): void | Promise<void>;\n getTopology(cnsId?: string): TCNSTopologySnapshotDTO[] | Promise<TCNSTopologySnapshotDTO[]>;\n\n // CNS ↔ App mapping\n addCnsToApp(appId: string, cnsId: string): void | Promise<void>;\n getCnsByApp(appId: string): string[] | Promise<string[]>;\n findAppByCns(cnsId: string): string | undefined | Promise<string | undefined>;\n}\n\n// ─── Server ───────────────────────────────────────────────────────────────────\n\nexport class CNSDevToolsServer {\n /** WebSocket → appId (for app clients) */\n private appSockets = new Map<WebSocket, string>();\n /** appId → WebSocket[] (one app may have multiple CNS instances) */\n private appWsByAppId = new Map<string, Set<WebSocket>>();\n /** WebSocket clients that registered as UI clients */\n private uiClients = new Set<WebSocket>();\n\n private metricsTimer?: NodeJS.Timeout;\n private lastCpuUsage = process.cpuUsage();\n private lastCpuTime = Date.now();\n\n /**\n * Name-based resumable store (Stimulation/Attempt/Task). Written from the three\n * `cns.stimulation*` batch items the producer emits when `trackStimulations` is on.\n * Defaults to in-memory; the example-app can inject a Postgres store.\n */\n private readonly stimulationRepository: ICNSStimulationRepository;\n\n constructor(\n private readonly repository: ICNSDevToolsServerRepository,\n stimulationRepository: ICNSStimulationRepository = new CNSInMemoryStimulationRepository()\n ) {\n this.stimulationRepository = stimulationRepository;\n }\n\n // ─── Message entry point ─────────────────────────────────────────────────\n\n async handleMessage(\n ws: WebSocket,\n raw: unknown\n ): Promise<void> {\n if (typeof raw !== 'string' && typeof raw !== 'object') return;\n\n let data: unknown;\n try {\n data = typeof raw === 'string' ? JSON.parse(raw) : raw;\n } catch {\n return;\n }\n\n if (!data || typeof data !== 'object' || !('type' in data)) return;\n const type = (data as any).type as string;\n\n // App batch (topology + stimulation events)\n if (type === 'batch') {\n const result = CNSAppBatchMessageDTOSchema.safeParse(data);\n if (!result.success) return;\n for (const item of result.data.items) {\n await this.handleAppBatchItem(ws, item);\n }\n return;\n }\n\n // UI client message\n const clientResult = CNSClientMessageDTOSchema.safeParse(data);\n if (!clientResult.success) return;\n const msg = clientResult.data;\n\n switch (msg.type) {\n case 'client.connect':\n await this.handleClientConnect(ws);\n break;\n case 'apps.query':\n await this.handleAppsQuery(ws, msg.requestId);\n break;\n case 'topology.query':\n await this.handleTopologyQuery(ws, msg.requestId, msg.cnsId, msg.appId);\n break;\n case 'replay.start':\n await this.handleReplayStart(ws, msg);\n break;\n case 'stimulation.retry':\n await this.handleStimulationRetry(ws, msg);\n break;\n case 'stimulation.clone':\n await this.handleStimulationClone(ws, msg);\n break;\n case 'cns.stimulations.list':\n await this.handleStimulationsList(ws, msg);\n break;\n case 'cns.stimulations.facets':\n await this.handleStimulationsFacets(ws, msg);\n break;\n case 'cns.stimulations.snapshot':\n await this.handleStimulationsSnapshot(ws, msg);\n break;\n case 'cns.stimulation.detail':\n await this.handleStimulationDetail(ws, msg);\n break;\n }\n }\n\n /**\n * Explorer protocol (bucket-shaped). The roster row = resumable state minus\n * `progress`, plus envelope summaries derived from the run's attempts.\n *\n * Pushdown: if the store implements `ICNSStimulationRosterQuery` (e.g. the\n * Postgres store), the filter + keyset + GROUP BY run INSIDE it (SQL). Else we\n * fall back to the shared reference derivation over the reader — identical\n * results, only the work moves. Roster shapes are structurally the wire DTOs,\n * so no mapping is needed at the boundary.\n */\n private toRosterFilter(\n scopeName: string | undefined,\n filter: TCNSStimulationFilter | undefined\n ): TCNSStimulationRosterFilter {\n return { ...(filter ?? {}), ...(scopeName ? { scopeName } : {}) };\n }\n\n private async handleStimulationsList(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const filter = this.toRosterFilter(msg.scopeName, msg.filter);\n const limit = typeof msg.limit === 'number' ? msg.limit : 50;\n const opts = { cursor: msg.cursor, limit };\n const store = this.stimulationRepository;\n const page = supportsRosterQuery(store)\n ? await store.queryRoster(filter, opts)\n : keysetPage(\n (await loadRosterEntries(store, filter)).map(e => e.row),\n opts\n );\n this.send(ws, {\n type: 'cns.stimulations.list.result',\n requestId: msg.requestId,\n stimulations: page.rows,\n nextCursor: page.nextCursor,\n total: page.total,\n });\n }\n\n private async handleStimulationsFacets(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const filter = this.toRosterFilter(msg.scopeName, msg.filter);\n const store = this.stimulationRepository;\n const facets = supportsRosterQuery(store)\n ? await store.queryFacets(filter, { metricPath: msg.metricPath })\n : computeRosterFacets(await loadRosterEntries(store, filter), {\n metricPath: msg.metricPath,\n });\n this.send(ws, {\n type: 'cns.stimulations.facets.result',\n requestId: msg.requestId,\n facets,\n });\n }\n\n private async handleStimulationDetail(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const attempts = await this.stimulationRepository.getAttempts(\n msg.stimulationId\n );\n const tasks: TCNSStimulationTask[] = [];\n for (const a of attempts) {\n const ts = await this.stimulationRepository.getTasks(\n a.stimulationAttemptId\n );\n tasks.push(...ts);\n }\n this.send(ws, {\n type: 'cns.stimulation.detail.result',\n requestId: msg.requestId,\n attempts,\n tasks,\n });\n }\n\n /**\n * The topology/signal views' bulk feed: a bounded, newest-first set of runs as\n * FLAT buckets (stimulations + attempts + tasks) the panel joins client-side by\n * id. Replaces the retired nested `cns.stimulations.query` tree — same data, no\n * server-side join, no client-side flatten.\n */\n private async handleStimulationsSnapshot(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const filter = this.toRosterFilter(msg.scopeName, msg.filter);\n const store = this.stimulationRepository;\n const limit = typeof msg.limit === 'number' ? msg.limit : 200;\n const page = supportsRosterQuery(store)\n ? await store.queryRoster(filter, { limit })\n : keysetPage(\n (await loadRosterEntries(store, filter)).map(e => e.row),\n { limit }\n );\n const attempts: TCNSStimulationAttempt[] = [];\n const tasks: TCNSStimulationTask[] = [];\n for (const row of page.rows) {\n const as = await store.getAttempts(row.stimulationId);\n attempts.push(...as);\n for (const a of as)\n tasks.push(...(await store.getTasks(a.stimulationAttemptId)));\n }\n this.send(ws, {\n type: 'cns.stimulations.snapshot.result',\n requestId: msg.requestId,\n stimulations: page.rows,\n attempts,\n tasks,\n });\n }\n\n addClient(ws: WebSocket): void {\n this.uiClients.add(ws);\n if (this.uiClients.size === 1) this.startMetrics();\n }\n\n removeClient(ws: WebSocket): void {\n this.uiClients.delete(ws);\n this.appSockets.delete(ws);\n if (this.uiClients.size === 0) this.stopMetrics();\n }\n\n async getActiveApps(): Promise<TCNSAppDTO[]> {\n return this.repository.listApps();\n }\n\n stop(): void {\n this.stopMetrics();\n }\n\n // ─── App batch item handlers ──────────────────────────────────────────────\n\n private async handleAppBatchItem(ws: WebSocket, item: any): Promise<void> {\n switch (item.type) {\n case 'topology':\n await this.handleTopology(ws, item);\n break;\n // ── Name-based resumable model ──\n case 'cns.stimulation':\n await this.stimulationRepository.saveStimulation(item.data);\n break;\n case 'cns.stimulation.attempt':\n await this.stimulationRepository.saveAttempt(item.data);\n break;\n case 'cns.stimulation.task':\n await this.stimulationRepository.appendTask(item.data);\n break;\n }\n }\n\n /** The name-based resumable store, for tests/integrations that want to read it back. */\n getStimulationRepository(): ICNSStimulationRepository {\n return this.stimulationRepository;\n }\n\n private async handleTopology(ws: WebSocket, msg: any): Promise<void> {\n const { appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites } = msg;\n\n const now = Date.now();\n const app: TCNSAppDTO = {\n id: appId,\n name: appName,\n version,\n connectedAt: now,\n lastSeenAt: now,\n };\n\n await this.repository.upsertApp(app);\n await this.repository.addCnsToApp(appId, cnsId);\n await this.repository.saveTopology({ appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites });\n\n // Track app socket\n this.appSockets.set(ws, appId);\n if (!this.appWsByAppId.has(appId)) this.appWsByAppId.set(appId, new Set());\n this.appWsByAppId.get(appId)!.add(ws);\n\n // Broadcast topology + app connected to UI clients\n const topologyBroadcast = {\n type: 'topology.result' as const,\n requestId: '__broadcast__',\n snapshots: await this.repository.getTopology(cnsId),\n };\n\n this.broadcast({ type: 'app.connected', app, topology: { cnsId, neurons, collaterals, dendrites } });\n // Also send topology snapshot so UI can refresh graph\n this.broadcastRaw(JSON.stringify(topologyBroadcast));\n }\n\n // ─── UI client query handlers ─────────────────────────────────────────────\n\n private async handleClientConnect(ws: WebSocket): Promise<void> {\n this.addClient(ws);\n this.send(ws, { type: 'apps.result', requestId: '__init__', items: await this.repository.listApps() });\n this.send(ws, {\n type: 'topology.result',\n requestId: '__init__',\n snapshots: await this.repository.getTopology(),\n });\n }\n\n private async handleAppsQuery(ws: WebSocket, requestId: string): Promise<void> {\n const items = await this.repository.listApps();\n this.send(ws, { type: 'apps.result', requestId, items });\n }\n\n private async handleTopologyQuery(\n ws: WebSocket,\n requestId: string,\n cnsId?: string,\n appId?: string\n ): Promise<void> {\n let snapshots = await this.repository.getTopology(cnsId);\n if (appId) snapshots = snapshots.filter(s => s.appId === appId);\n this.send(ws, { type: 'topology.result', requestId, snapshots });\n }\n\n private async handleReplayStart(ws: WebSocket, msg: any): Promise<void> {\n const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : undefined);\n const appSockets = appId ? this.appWsByAppId.get(appId) : undefined;\n\n if (!appSockets || appSockets.size === 0) {\n this.send(ws, { type: 'replay.rejected', replayId: msg.replayId, reason: 'App not connected' });\n return;\n }\n\n const newStimulationId = `${msg.stimulationId}-replay-${Date.now()}`;\n this.send(ws, { type: 'replay.accepted', replayId: msg.replayId, newStimulationId });\n\n const payload = JSON.stringify(msg);\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n // ─── Resumable actions (retry / clone), Phase 2b-2 ──────────────────────────────\n //\n // The UI's request is thin (a stimulationId). The CNS + name→ref registry live in\n // the APP, so the server can't execute it: it ENRICHES from the resumable store (the\n // stored entry + progress + a server-assigned attempt identity) and forwards a\n // resume/launch command to the owning app, routed by scopeName (= the cns id).\n\n /** Resolve the app that owns a stimulation's scope, or reject if unreachable. */\n private async resolveScopeApp(\n scopeName: string | undefined\n ): Promise<Set<WebSocket> | undefined> {\n const appId = scopeName\n ? await this.repository.findAppByCns(scopeName)\n : undefined;\n const sockets = appId ? this.appWsByAppId.get(appId) : undefined;\n return sockets && sockets.size > 0 ? sockets : undefined;\n }\n\n private async handleStimulationRetry(ws: WebSocket, msg: any): Promise<void> {\n const reject = (reason: string) =>\n this.send(ws, {\n type: 'stimulation.retry.rejected',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n reason,\n });\n\n const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);\n if (!stim) return reject('unknown stimulation');\n if (stim.progress.tasks.length === 0)\n return reject('nothing to resume (empty frontier)');\n\n const appSockets = await this.resolveScopeApp(stim.scopeName);\n if (!appSockets) return reject('app not connected');\n\n const attempts = await this.stimulationRepository.getAttempts(msg.stimulationId);\n const nextAttempt = attempts.length + 1;\n const stimulationAttemptId = buildAttemptId(msg.stimulationId, nextAttempt);\n\n this.send(ws, {\n type: 'stimulation.retry.accepted',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n newStimulationAttemptId: stimulationAttemptId,\n });\n\n const payload = JSON.stringify({\n type: 'cns.stimulation.resume',\n requestId: msg.requestId,\n scopeName: stim.scopeName,\n stimulationId: msg.stimulationId,\n stimulationAttemptId,\n attemptNumber: nextAttempt,\n entry: stim.entry,\n progress: stim.progress,\n options: msg.options,\n });\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n private async handleStimulationClone(ws: WebSocket, msg: any): Promise<void> {\n const reject = (reason: string) =>\n this.send(ws, {\n type: 'stimulation.clone.rejected',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n reason,\n });\n\n const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);\n if (!stim) return reject('unknown stimulation');\n\n const appSockets = await this.resolveScopeApp(stim.scopeName);\n if (!appSockets) return reject('app not connected');\n\n const newStimulationId = buildCloneId(msg.stimulationId);\n const stimulationAttemptId = buildAttemptId(newStimulationId, 1);\n\n this.send(ws, {\n type: 'stimulation.clone.accepted',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n newStimulationId,\n newStimulationAttemptId: stimulationAttemptId,\n });\n\n const payload = JSON.stringify({\n type: 'cns.stimulation.launch',\n requestId: msg.requestId,\n scopeName: stim.scopeName,\n stimulationId: newStimulationId,\n stimulationAttemptId,\n entry: stim.entry,\n options: msg.options,\n });\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n // ─── Broadcast helpers ────────────────────────────────────────────────────\n\n private broadcast(message: object): void {\n this.broadcastRaw(JSON.stringify(message));\n }\n\n private broadcastRaw(payload: string): void {\n for (const client of this.uiClients) {\n try {\n if ((client as any).readyState === 1) client.send(payload);\n } catch {}\n }\n }\n\n private send(ws: WebSocket, message: object): void {\n try {\n if ((ws as any).readyState === 1) ws.send(JSON.stringify(message));\n } catch {}\n }\n\n // ─── Metrics ──────────────────────────────────────────────────────────────\n\n private startMetrics(): void {\n if (this.metricsTimer) return;\n this.metricsTimer = setInterval(() => {\n const mem = process.memoryUsage();\n const now = Date.now();\n const cpu = process.cpuUsage(this.lastCpuUsage);\n const elapsed = (now - this.lastCpuTime) * 1000;\n const cpuPercent = elapsed > 0 ? ((cpu.user + cpu.system) / elapsed) * 100 : 0;\n this.lastCpuUsage = process.cpuUsage();\n this.lastCpuTime = now;\n\n this.broadcast({\n type: 'server.metrics',\n timestamp: now,\n rssMB: mem.rss / 1024 / 1024,\n heapUsedMB: mem.heapUsed / 1024 / 1024,\n heapTotalMB: mem.heapTotal / 1024 / 1024,\n cpuPercent,\n });\n }, 1000);\n }\n\n private stopMetrics(): void {\n if (this.metricsTimer) {\n clearInterval(this.metricsTimer);\n this.metricsTimer = undefined;\n }\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,22 +1,12 @@
1
- import { CNSDTOApp, CNSDTOStimulation, CNSDTOStimulationFilter, CNSDTOHop, CNSDTOTopologyMessage } from '@cnstra/devtools-dto';
1
+ import { TCNSAppDTO, TCNSTopologyMessageDTO } from '@cnstra/devtools-dto';
2
+ import type { ICNSStimulationRepository } from '@cnstra/persist';
2
3
  import { WebSocket } from 'ws';
3
- export type CNSDTOTopologySnapshot = Omit<CNSDTOTopologyMessage, 'type'>;
4
+ export type TCNSTopologySnapshotDTO = Omit<TCNSTopologyMessageDTO, 'type'>;
4
5
  export interface ICNSDevToolsServerRepository {
5
- upsertApp(app: CNSDTOApp): void | Promise<void>;
6
- listApps(): CNSDTOApp[] | Promise<CNSDTOApp[]>;
7
- saveTopology(snapshot: CNSDTOTopologySnapshot): void | Promise<void>;
8
- getTopology(cnsId?: string): CNSDTOTopologySnapshot[] | Promise<CNSDTOTopologySnapshot[]>;
9
- saveStimulation(stimulation: CNSDTOStimulation): void | Promise<void>;
10
- completeStimulation(stimulationId: string, completedAt: number, hopCount: number, hasError: boolean): void | Promise<void>;
11
- getStimulations(appId: string, filter: CNSDTOStimulationFilter): {
12
- items: CNSDTOStimulation[];
13
- total: number;
14
- } | Promise<{
15
- items: CNSDTOStimulation[];
16
- total: number;
17
- }>;
18
- saveHop(hop: CNSDTOHop): void | Promise<void>;
19
- getHops(stimulationId: string): CNSDTOHop[] | Promise<CNSDTOHop[]>;
6
+ upsertApp(app: TCNSAppDTO): void | Promise<void>;
7
+ listApps(): TCNSAppDTO[] | Promise<TCNSAppDTO[]>;
8
+ saveTopology(snapshot: TCNSTopologySnapshotDTO): void | Promise<void>;
9
+ getTopology(cnsId?: string): TCNSTopologySnapshotDTO[] | Promise<TCNSTopologySnapshotDTO[]>;
20
10
  addCnsToApp(appId: string, cnsId: string): void | Promise<void>;
21
11
  getCnsByApp(appId: string): string[] | Promise<string[]>;
22
12
  findAppByCns(cnsId: string): string | undefined | Promise<string | undefined>;
@@ -32,23 +22,51 @@ export declare class CNSDevToolsServer {
32
22
  private metricsTimer?;
33
23
  private lastCpuUsage;
34
24
  private lastCpuTime;
35
- constructor(repository: ICNSDevToolsServerRepository);
25
+ /**
26
+ * Name-based resumable store (Stimulation/Attempt/Task). Written from the three
27
+ * `cns.stimulation*` batch items the producer emits when `trackStimulations` is on.
28
+ * Defaults to in-memory; the example-app can inject a Postgres store.
29
+ */
30
+ private readonly stimulationRepository;
31
+ constructor(repository: ICNSDevToolsServerRepository, stimulationRepository?: ICNSStimulationRepository);
36
32
  handleMessage(ws: WebSocket, raw: unknown): Promise<void>;
33
+ /**
34
+ * Explorer protocol (bucket-shaped). The roster row = resumable state minus
35
+ * `progress`, plus envelope summaries derived from the run's attempts.
36
+ *
37
+ * Pushdown: if the store implements `ICNSStimulationRosterQuery` (e.g. the
38
+ * Postgres store), the filter + keyset + GROUP BY run INSIDE it (SQL). Else we
39
+ * fall back to the shared reference derivation over the reader — identical
40
+ * results, only the work moves. Roster shapes are structurally the wire DTOs,
41
+ * so no mapping is needed at the boundary.
42
+ */
43
+ private toRosterFilter;
44
+ private handleStimulationsList;
45
+ private handleStimulationsFacets;
46
+ private handleStimulationDetail;
47
+ /**
48
+ * The topology/signal views' bulk feed: a bounded, newest-first set of runs as
49
+ * FLAT buckets (stimulations + attempts + tasks) the panel joins client-side by
50
+ * id. Replaces the retired nested `cns.stimulations.query` tree — same data, no
51
+ * server-side join, no client-side flatten.
52
+ */
53
+ private handleStimulationsSnapshot;
37
54
  addClient(ws: WebSocket): void;
38
55
  removeClient(ws: WebSocket): void;
39
- getActiveApps(): Promise<CNSDTOApp[]>;
56
+ getActiveApps(): Promise<TCNSAppDTO[]>;
40
57
  stop(): void;
41
58
  private handleAppBatchItem;
59
+ /** The name-based resumable store, for tests/integrations that want to read it back. */
60
+ getStimulationRepository(): ICNSStimulationRepository;
42
61
  private handleTopology;
43
- private handleStimulationStarted;
44
- private handleStimulationHop;
45
- private handleStimulationCompleted;
46
62
  private handleClientConnect;
47
63
  private handleAppsQuery;
48
64
  private handleTopologyQuery;
49
- private handleStimulationsQuery;
50
- private handleHopsQuery;
51
65
  private handleReplayStart;
66
+ /** Resolve the app that owns a stimulation's scope, or reject if unreachable. */
67
+ private resolveScopeApp;
68
+ private handleStimulationRetry;
69
+ private handleStimulationClone;
52
70
  private broadcast;
53
71
  private broadcastRaw;
54
72
  private send;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,UAAU,EAGV,sBAAsB,EACzB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,KAAK,EACR,yBAAyB,EAE5B,MAAM,iBAAiB,CAAC;AAQzB,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAI/B,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAC;AAE3E,MAAM,WAAW,4BAA4B;IAEzC,SAAS,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,QAAQ,IAAI,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAGjD,YAAY,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,uBAAuB,EAAE,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAAC;IAG5F,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACjF;AAID,qBAAa,iBAAiB;IAoBtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAnB/B,0CAA0C;IAC1C,OAAO,CAAC,UAAU,CAAgC;IAClD,oEAAoE;IACpE,OAAO,CAAC,YAAY,CAAqC;IACzD,sDAAsD;IACtD,OAAO,CAAC,SAAS,CAAwB;IAEzC,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,WAAW,CAAc;IAEjC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA4B;gBAG7C,UAAU,EAAE,4BAA4B,EACzD,qBAAqB,GAAE,yBAAkE;IAOvF,aAAa,CACf,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC;IA8DhB;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;YAOR,sBAAsB;YAuBtB,wBAAwB;YAkBxB,uBAAuB;IAsBrC;;;;;OAKG;YACW,0BAA0B;IA8BxC,SAAS,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAK9B,YAAY,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAM3B,aAAa,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAI5C,IAAI,IAAI,IAAI;YAME,kBAAkB;IAkBhC,wFAAwF;IACxF,wBAAwB,IAAI,yBAAyB;YAIvC,cAAc;YAmCd,mBAAmB;YAUnB,eAAe;YAKf,mBAAmB;YAWnB,iBAAiB;IAyB/B,iFAAiF;YACnE,eAAe;YAUf,sBAAsB;YA4CtB,sBAAsB;IA0CpC,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,YAAY;IAsBpB,OAAO,CAAC,WAAW;CAMtB"}
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
- import { CNSDTOAppBatchMessageSchema, CNSDTOClientMessageSchema } from '@cnstra/devtools-dto';
1
+ import { CNSAppBatchMessageDTOSchema, CNSClientMessageDTOSchema, buildAttemptId, buildCloneId } from '@cnstra/devtools-dto';
2
+ import { CNSInMemoryStimulationRepository, supportsRosterQuery, keysetPage, loadRosterEntries, computeRosterFacets } from '@cnstra/persist';
2
3
 
3
4
  // src/index.ts
4
5
  var CNSDevToolsServer = class {
5
- constructor(repository) {
6
+ constructor(repository, stimulationRepository = new CNSInMemoryStimulationRepository()) {
6
7
  this.repository = repository;
7
8
  /** WebSocket → appId (for app clients) */
8
9
  this.appSockets = /* @__PURE__ */ new Map();
@@ -12,6 +13,7 @@ var CNSDevToolsServer = class {
12
13
  this.uiClients = /* @__PURE__ */ new Set();
13
14
  this.lastCpuUsage = process.cpuUsage();
14
15
  this.lastCpuTime = Date.now();
16
+ this.stimulationRepository = stimulationRepository;
15
17
  }
16
18
  // ─── Message entry point ─────────────────────────────────────────────────
17
19
  async handleMessage(ws, raw) {
@@ -25,14 +27,14 @@ var CNSDevToolsServer = class {
25
27
  if (!data || typeof data !== "object" || !("type" in data)) return;
26
28
  const type = data.type;
27
29
  if (type === "batch") {
28
- const result = CNSDTOAppBatchMessageSchema.safeParse(data);
30
+ const result = CNSAppBatchMessageDTOSchema.safeParse(data);
29
31
  if (!result.success) return;
30
32
  for (const item of result.data.items) {
31
33
  await this.handleAppBatchItem(ws, item);
32
34
  }
33
35
  return;
34
36
  }
35
- const clientResult = CNSDTOClientMessageSchema.safeParse(data);
37
+ const clientResult = CNSClientMessageDTOSchema.safeParse(data);
36
38
  if (!clientResult.success) return;
37
39
  const msg = clientResult.data;
38
40
  switch (msg.type) {
@@ -45,17 +47,119 @@ var CNSDevToolsServer = class {
45
47
  case "topology.query":
46
48
  await this.handleTopologyQuery(ws, msg.requestId, msg.cnsId, msg.appId);
47
49
  break;
48
- case "stimulations.query":
49
- await this.handleStimulationsQuery(ws, msg);
50
- break;
51
- case "hops.query":
52
- await this.handleHopsQuery(ws, msg);
53
- break;
54
50
  case "replay.start":
55
51
  await this.handleReplayStart(ws, msg);
56
52
  break;
53
+ case "stimulation.retry":
54
+ await this.handleStimulationRetry(ws, msg);
55
+ break;
56
+ case "stimulation.clone":
57
+ await this.handleStimulationClone(ws, msg);
58
+ break;
59
+ case "cns.stimulations.list":
60
+ await this.handleStimulationsList(ws, msg);
61
+ break;
62
+ case "cns.stimulations.facets":
63
+ await this.handleStimulationsFacets(ws, msg);
64
+ break;
65
+ case "cns.stimulations.snapshot":
66
+ await this.handleStimulationsSnapshot(ws, msg);
67
+ break;
68
+ case "cns.stimulation.detail":
69
+ await this.handleStimulationDetail(ws, msg);
70
+ break;
57
71
  }
58
72
  }
73
+ /**
74
+ * Explorer protocol (bucket-shaped). The roster row = resumable state minus
75
+ * `progress`, plus envelope summaries derived from the run's attempts.
76
+ *
77
+ * Pushdown: if the store implements `ICNSStimulationRosterQuery` (e.g. the
78
+ * Postgres store), the filter + keyset + GROUP BY run INSIDE it (SQL). Else we
79
+ * fall back to the shared reference derivation over the reader — identical
80
+ * results, only the work moves. Roster shapes are structurally the wire DTOs,
81
+ * so no mapping is needed at the boundary.
82
+ */
83
+ toRosterFilter(scopeName, filter) {
84
+ return { ...filter ?? {}, ...scopeName ? { scopeName } : {} };
85
+ }
86
+ async handleStimulationsList(ws, msg) {
87
+ const filter = this.toRosterFilter(msg.scopeName, msg.filter);
88
+ const limit = typeof msg.limit === "number" ? msg.limit : 50;
89
+ const opts = { cursor: msg.cursor, limit };
90
+ const store = this.stimulationRepository;
91
+ const page = supportsRosterQuery(store) ? await store.queryRoster(filter, opts) : keysetPage(
92
+ (await loadRosterEntries(store, filter)).map((e) => e.row),
93
+ opts
94
+ );
95
+ this.send(ws, {
96
+ type: "cns.stimulations.list.result",
97
+ requestId: msg.requestId,
98
+ stimulations: page.rows,
99
+ nextCursor: page.nextCursor,
100
+ total: page.total
101
+ });
102
+ }
103
+ async handleStimulationsFacets(ws, msg) {
104
+ const filter = this.toRosterFilter(msg.scopeName, msg.filter);
105
+ const store = this.stimulationRepository;
106
+ const facets = supportsRosterQuery(store) ? await store.queryFacets(filter, { metricPath: msg.metricPath }) : computeRosterFacets(await loadRosterEntries(store, filter), {
107
+ metricPath: msg.metricPath
108
+ });
109
+ this.send(ws, {
110
+ type: "cns.stimulations.facets.result",
111
+ requestId: msg.requestId,
112
+ facets
113
+ });
114
+ }
115
+ async handleStimulationDetail(ws, msg) {
116
+ const attempts = await this.stimulationRepository.getAttempts(
117
+ msg.stimulationId
118
+ );
119
+ const tasks = [];
120
+ for (const a of attempts) {
121
+ const ts = await this.stimulationRepository.getTasks(
122
+ a.stimulationAttemptId
123
+ );
124
+ tasks.push(...ts);
125
+ }
126
+ this.send(ws, {
127
+ type: "cns.stimulation.detail.result",
128
+ requestId: msg.requestId,
129
+ attempts,
130
+ tasks
131
+ });
132
+ }
133
+ /**
134
+ * The topology/signal views' bulk feed: a bounded, newest-first set of runs as
135
+ * FLAT buckets (stimulations + attempts + tasks) the panel joins client-side by
136
+ * id. Replaces the retired nested `cns.stimulations.query` tree — same data, no
137
+ * server-side join, no client-side flatten.
138
+ */
139
+ async handleStimulationsSnapshot(ws, msg) {
140
+ const filter = this.toRosterFilter(msg.scopeName, msg.filter);
141
+ const store = this.stimulationRepository;
142
+ const limit = typeof msg.limit === "number" ? msg.limit : 200;
143
+ const page = supportsRosterQuery(store) ? await store.queryRoster(filter, { limit }) : keysetPage(
144
+ (await loadRosterEntries(store, filter)).map((e) => e.row),
145
+ { limit }
146
+ );
147
+ const attempts = [];
148
+ const tasks = [];
149
+ for (const row of page.rows) {
150
+ const as = await store.getAttempts(row.stimulationId);
151
+ attempts.push(...as);
152
+ for (const a of as)
153
+ tasks.push(...await store.getTasks(a.stimulationAttemptId));
154
+ }
155
+ this.send(ws, {
156
+ type: "cns.stimulations.snapshot.result",
157
+ requestId: msg.requestId,
158
+ stimulations: page.rows,
159
+ attempts,
160
+ tasks
161
+ });
162
+ }
59
163
  addClient(ws) {
60
164
  this.uiClients.add(ws);
61
165
  if (this.uiClients.size === 1) this.startMetrics();
@@ -77,17 +181,22 @@ var CNSDevToolsServer = class {
77
181
  case "topology":
78
182
  await this.handleTopology(ws, item);
79
183
  break;
80
- case "stimulation.started":
81
- await this.handleStimulationStarted(ws, item.stimulation);
184
+ // ── Name-based resumable model ──
185
+ case "cns.stimulation":
186
+ await this.stimulationRepository.saveStimulation(item.data);
82
187
  break;
83
- case "stimulation.hop":
84
- await this.handleStimulationHop(item.hop);
188
+ case "cns.stimulation.attempt":
189
+ await this.stimulationRepository.saveAttempt(item.data);
85
190
  break;
86
- case "stimulation.completed":
87
- await this.handleStimulationCompleted(item);
191
+ case "cns.stimulation.task":
192
+ await this.stimulationRepository.appendTask(item.data);
88
193
  break;
89
194
  }
90
195
  }
196
+ /** The name-based resumable store, for tests/integrations that want to read it back. */
197
+ getStimulationRepository() {
198
+ return this.stimulationRepository;
199
+ }
91
200
  async handleTopology(ws, msg) {
92
201
  const { appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites } = msg;
93
202
  const now = Date.now();
@@ -112,35 +221,6 @@ var CNSDevToolsServer = class {
112
221
  this.broadcast({ type: "app.connected", app, topology: { cnsId, neurons, collaterals, dendrites } });
113
222
  this.broadcastRaw(JSON.stringify(topologyBroadcast));
114
223
  }
115
- async handleStimulationStarted(ws, stimulation) {
116
- const appId = this.appSockets.get(ws);
117
- if (appId) {
118
- const apps = await this.repository.listApps();
119
- const app = apps.find((a) => a.id === appId);
120
- if (app) await this.repository.upsertApp({ ...app, lastSeenAt: Date.now() });
121
- }
122
- await this.repository.saveStimulation(stimulation);
123
- this.broadcast({ type: "stimulation.started", stimulation });
124
- }
125
- async handleStimulationHop(hop) {
126
- await this.repository.saveHop(hop);
127
- this.broadcast({ type: "stimulation.hop", hop });
128
- }
129
- async handleStimulationCompleted(item) {
130
- await this.repository.completeStimulation(
131
- item.stimulationId,
132
- item.completedAt,
133
- item.hopCount,
134
- item.hasError
135
- );
136
- this.broadcast({
137
- type: "stimulation.completed",
138
- stimulationId: item.stimulationId,
139
- completedAt: item.completedAt,
140
- hopCount: item.hopCount,
141
- hasError: item.hasError
142
- });
143
- }
144
224
  // ─── UI client query handlers ─────────────────────────────────────────────
145
225
  async handleClientConnect(ws) {
146
226
  this.addClient(ws);
@@ -160,25 +240,6 @@ var CNSDevToolsServer = class {
160
240
  if (appId) snapshots = snapshots.filter((s) => s.appId === appId);
161
241
  this.send(ws, { type: "topology.result", requestId, snapshots });
162
242
  }
163
- async handleStimulationsQuery(ws, msg) {
164
- const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : void 0);
165
- if (!appId) {
166
- this.send(ws, { type: "stimulations.result", requestId: msg.requestId, items: [], total: 0, offset: 0 });
167
- return;
168
- }
169
- const { items, total } = await this.repository.getStimulations(appId, msg.filter ?? {});
170
- this.send(ws, {
171
- type: "stimulations.result",
172
- requestId: msg.requestId,
173
- items,
174
- total,
175
- offset: msg.filter?.offset ?? 0
176
- });
177
- }
178
- async handleHopsQuery(ws, msg) {
179
- const items = await this.repository.getHops(msg.stimulationId);
180
- this.send(ws, { type: "hops.result", requestId: msg.requestId, items });
181
- }
182
243
  async handleReplayStart(ws, msg) {
183
244
  const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : void 0);
184
245
  const appSockets = appId ? this.appWsByAppId.get(appId) : void 0;
@@ -196,6 +257,94 @@ var CNSDevToolsServer = class {
196
257
  }
197
258
  }
198
259
  }
260
+ // ─── Resumable actions (retry / clone), Phase 2b-2 ──────────────────────────────
261
+ //
262
+ // The UI's request is thin (a stimulationId). The CNS + name→ref registry live in
263
+ // the APP, so the server can't execute it: it ENRICHES from the resumable store (the
264
+ // stored entry + progress + a server-assigned attempt identity) and forwards a
265
+ // resume/launch command to the owning app, routed by scopeName (= the cns id).
266
+ /** Resolve the app that owns a stimulation's scope, or reject if unreachable. */
267
+ async resolveScopeApp(scopeName) {
268
+ const appId = scopeName ? await this.repository.findAppByCns(scopeName) : void 0;
269
+ const sockets = appId ? this.appWsByAppId.get(appId) : void 0;
270
+ return sockets && sockets.size > 0 ? sockets : void 0;
271
+ }
272
+ async handleStimulationRetry(ws, msg) {
273
+ const reject = (reason) => this.send(ws, {
274
+ type: "stimulation.retry.rejected",
275
+ requestId: msg.requestId,
276
+ stimulationId: msg.stimulationId,
277
+ reason
278
+ });
279
+ const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);
280
+ if (!stim) return reject("unknown stimulation");
281
+ if (stim.progress.tasks.length === 0)
282
+ return reject("nothing to resume (empty frontier)");
283
+ const appSockets = await this.resolveScopeApp(stim.scopeName);
284
+ if (!appSockets) return reject("app not connected");
285
+ const attempts = await this.stimulationRepository.getAttempts(msg.stimulationId);
286
+ const nextAttempt = attempts.length + 1;
287
+ const stimulationAttemptId = buildAttemptId(msg.stimulationId, nextAttempt);
288
+ this.send(ws, {
289
+ type: "stimulation.retry.accepted",
290
+ requestId: msg.requestId,
291
+ stimulationId: msg.stimulationId,
292
+ newStimulationAttemptId: stimulationAttemptId
293
+ });
294
+ const payload = JSON.stringify({
295
+ type: "cns.stimulation.resume",
296
+ requestId: msg.requestId,
297
+ scopeName: stim.scopeName,
298
+ stimulationId: msg.stimulationId,
299
+ stimulationAttemptId,
300
+ attemptNumber: nextAttempt,
301
+ entry: stim.entry,
302
+ progress: stim.progress,
303
+ options: msg.options
304
+ });
305
+ for (const appWs of appSockets) {
306
+ try {
307
+ appWs.send(payload);
308
+ } catch {
309
+ }
310
+ }
311
+ }
312
+ async handleStimulationClone(ws, msg) {
313
+ const reject = (reason) => this.send(ws, {
314
+ type: "stimulation.clone.rejected",
315
+ requestId: msg.requestId,
316
+ stimulationId: msg.stimulationId,
317
+ reason
318
+ });
319
+ const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);
320
+ if (!stim) return reject("unknown stimulation");
321
+ const appSockets = await this.resolveScopeApp(stim.scopeName);
322
+ if (!appSockets) return reject("app not connected");
323
+ const newStimulationId = buildCloneId(msg.stimulationId);
324
+ const stimulationAttemptId = buildAttemptId(newStimulationId, 1);
325
+ this.send(ws, {
326
+ type: "stimulation.clone.accepted",
327
+ requestId: msg.requestId,
328
+ stimulationId: msg.stimulationId,
329
+ newStimulationId,
330
+ newStimulationAttemptId: stimulationAttemptId
331
+ });
332
+ const payload = JSON.stringify({
333
+ type: "cns.stimulation.launch",
334
+ requestId: msg.requestId,
335
+ scopeName: stim.scopeName,
336
+ stimulationId: newStimulationId,
337
+ stimulationAttemptId,
338
+ entry: stim.entry,
339
+ options: msg.options
340
+ });
341
+ for (const appWs of appSockets) {
342
+ try {
343
+ appWs.send(payload);
344
+ } catch {
345
+ }
346
+ }
347
+ }
199
348
  // ─── Broadcast helpers ────────────────────────────────────────────────────
200
349
  broadcast(message) {
201
350
  this.broadcastRaw(JSON.stringify(message));
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n CNSDTOApp,\n CNSDTOAppBatchMessageSchema,\n CNSDTOClientMessageSchema,\n CNSDTOStimulation,\n CNSDTOStimulationFilter,\n CNSDTOHop,\n CNSDTOTopologyMessage,\n} from '@cnstra/devtools-dto';\nimport { WebSocket } from 'ws';\n\n// ─── Repository interface ─────────────────────────────────────────────────────\n\nexport type CNSDTOTopologySnapshot = Omit<CNSDTOTopologyMessage, 'type'>;\n\nexport interface ICNSDevToolsServerRepository {\n // Apps\n upsertApp(app: CNSDTOApp): void | Promise<void>;\n listApps(): CNSDTOApp[] | Promise<CNSDTOApp[]>;\n\n // Topology\n saveTopology(snapshot: CNSDTOTopologySnapshot): void | Promise<void>;\n getTopology(cnsId?: string): CNSDTOTopologySnapshot[] | Promise<CNSDTOTopologySnapshot[]>;\n\n // Stimulations\n saveStimulation(stimulation: CNSDTOStimulation): void | Promise<void>;\n completeStimulation(\n stimulationId: string,\n completedAt: number,\n hopCount: number,\n hasError: boolean\n ): void | Promise<void>;\n getStimulations(\n appId: string,\n filter: CNSDTOStimulationFilter\n ): { items: CNSDTOStimulation[]; total: number } | Promise<{ items: CNSDTOStimulation[]; total: number }>;\n\n // Hops\n saveHop(hop: CNSDTOHop): void | Promise<void>;\n getHops(stimulationId: string): CNSDTOHop[] | Promise<CNSDTOHop[]>;\n\n // CNS ↔ App mapping\n addCnsToApp(appId: string, cnsId: string): void | Promise<void>;\n getCnsByApp(appId: string): string[] | Promise<string[]>;\n findAppByCns(cnsId: string): string | undefined | Promise<string | undefined>;\n}\n\n// ─── Server ───────────────────────────────────────────────────────────────────\n\nexport class CNSDevToolsServer {\n /** WebSocket → appId (for app clients) */\n private appSockets = new Map<WebSocket, string>();\n /** appId → WebSocket[] (one app may have multiple CNS instances) */\n private appWsByAppId = new Map<string, Set<WebSocket>>();\n /** WebSocket clients that registered as UI clients */\n private uiClients = new Set<WebSocket>();\n\n private metricsTimer?: NodeJS.Timeout;\n private lastCpuUsage = process.cpuUsage();\n private lastCpuTime = Date.now();\n\n constructor(private readonly repository: ICNSDevToolsServerRepository) {}\n\n // ─── Message entry point ─────────────────────────────────────────────────\n\n async handleMessage(\n ws: WebSocket,\n raw: unknown\n ): Promise<void> {\n if (typeof raw !== 'string' && typeof raw !== 'object') return;\n\n let data: unknown;\n try {\n data = typeof raw === 'string' ? JSON.parse(raw) : raw;\n } catch {\n return;\n }\n\n if (!data || typeof data !== 'object' || !('type' in data)) return;\n const type = (data as any).type as string;\n\n // App batch (topology + stimulation events)\n if (type === 'batch') {\n const result = CNSDTOAppBatchMessageSchema.safeParse(data);\n if (!result.success) return;\n for (const item of result.data.items) {\n await this.handleAppBatchItem(ws, item);\n }\n return;\n }\n\n // UI client message\n const clientResult = CNSDTOClientMessageSchema.safeParse(data);\n if (!clientResult.success) return;\n const msg = clientResult.data;\n\n switch (msg.type) {\n case 'client.connect':\n await this.handleClientConnect(ws);\n break;\n case 'apps.query':\n await this.handleAppsQuery(ws, msg.requestId);\n break;\n case 'topology.query':\n await this.handleTopologyQuery(ws, msg.requestId, msg.cnsId, msg.appId);\n break;\n case 'stimulations.query':\n await this.handleStimulationsQuery(ws, msg);\n break;\n case 'hops.query':\n await this.handleHopsQuery(ws, msg);\n break;\n case 'replay.start':\n await this.handleReplayStart(ws, msg);\n break;\n }\n }\n\n addClient(ws: WebSocket): void {\n this.uiClients.add(ws);\n if (this.uiClients.size === 1) this.startMetrics();\n }\n\n removeClient(ws: WebSocket): void {\n this.uiClients.delete(ws);\n this.appSockets.delete(ws);\n if (this.uiClients.size === 0) this.stopMetrics();\n }\n\n async getActiveApps(): Promise<CNSDTOApp[]> {\n return this.repository.listApps();\n }\n\n stop(): void {\n this.stopMetrics();\n }\n\n // ─── App batch item handlers ──────────────────────────────────────────────\n\n private async handleAppBatchItem(ws: WebSocket, item: any): Promise<void> {\n switch (item.type) {\n case 'topology':\n await this.handleTopology(ws, item);\n break;\n case 'stimulation.started':\n await this.handleStimulationStarted(ws, item.stimulation);\n break;\n case 'stimulation.hop':\n await this.handleStimulationHop(item.hop);\n break;\n case 'stimulation.completed':\n await this.handleStimulationCompleted(item);\n break;\n }\n }\n\n private async handleTopology(ws: WebSocket, msg: any): Promise<void> {\n const { appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites } = msg;\n\n const now = Date.now();\n const app: CNSDTOApp = {\n id: appId,\n name: appName,\n version,\n connectedAt: now,\n lastSeenAt: now,\n };\n\n await this.repository.upsertApp(app);\n await this.repository.addCnsToApp(appId, cnsId);\n await this.repository.saveTopology({ appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites });\n\n // Track app socket\n this.appSockets.set(ws, appId);\n if (!this.appWsByAppId.has(appId)) this.appWsByAppId.set(appId, new Set());\n this.appWsByAppId.get(appId)!.add(ws);\n\n // Broadcast topology + app connected to UI clients\n const topologyBroadcast = {\n type: 'topology.result' as const,\n requestId: '__broadcast__',\n snapshots: await this.repository.getTopology(cnsId),\n };\n\n this.broadcast({ type: 'app.connected', app, topology: { cnsId, neurons, collaterals, dendrites } });\n // Also send topology snapshot so UI can refresh graph\n this.broadcastRaw(JSON.stringify(topologyBroadcast));\n }\n\n private async handleStimulationStarted(ws: WebSocket, stimulation: CNSDTOStimulation): Promise<void> {\n // Update lastSeenAt for the app\n const appId = this.appSockets.get(ws);\n if (appId) {\n const apps = await this.repository.listApps();\n const app = apps.find(a => a.id === appId);\n if (app) await this.repository.upsertApp({ ...app, lastSeenAt: Date.now() });\n }\n\n await this.repository.saveStimulation(stimulation);\n this.broadcast({ type: 'stimulation.started', stimulation });\n }\n\n private async handleStimulationHop(hop: CNSDTOHop): Promise<void> {\n await this.repository.saveHop(hop);\n this.broadcast({ type: 'stimulation.hop', hop });\n }\n\n private async handleStimulationCompleted(item: any): Promise<void> {\n await this.repository.completeStimulation(\n item.stimulationId,\n item.completedAt,\n item.hopCount,\n item.hasError\n );\n this.broadcast({\n type: 'stimulation.completed',\n stimulationId: item.stimulationId,\n completedAt: item.completedAt,\n hopCount: item.hopCount,\n hasError: item.hasError,\n });\n }\n\n // ─── UI client query handlers ─────────────────────────────────────────────\n\n private async handleClientConnect(ws: WebSocket): Promise<void> {\n this.addClient(ws);\n this.send(ws, { type: 'apps.result', requestId: '__init__', items: await this.repository.listApps() });\n this.send(ws, {\n type: 'topology.result',\n requestId: '__init__',\n snapshots: await this.repository.getTopology(),\n });\n }\n\n private async handleAppsQuery(ws: WebSocket, requestId: string): Promise<void> {\n const items = await this.repository.listApps();\n this.send(ws, { type: 'apps.result', requestId, items });\n }\n\n private async handleTopologyQuery(\n ws: WebSocket,\n requestId: string,\n cnsId?: string,\n appId?: string\n ): Promise<void> {\n let snapshots = await this.repository.getTopology(cnsId);\n if (appId) snapshots = snapshots.filter(s => s.appId === appId);\n this.send(ws, { type: 'topology.result', requestId, snapshots });\n }\n\n private async handleStimulationsQuery(ws: WebSocket, msg: any): Promise<void> {\n const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : undefined);\n\n if (!appId) {\n this.send(ws, { type: 'stimulations.result', requestId: msg.requestId, items: [], total: 0, offset: 0 });\n return;\n }\n\n const { items, total } = await this.repository.getStimulations(appId, msg.filter ?? {});\n this.send(ws, {\n type: 'stimulations.result',\n requestId: msg.requestId,\n items,\n total,\n offset: msg.filter?.offset ?? 0,\n });\n }\n\n private async handleHopsQuery(ws: WebSocket, msg: any): Promise<void> {\n const items = await this.repository.getHops(msg.stimulationId);\n this.send(ws, { type: 'hops.result', requestId: msg.requestId, items });\n }\n\n private async handleReplayStart(ws: WebSocket, msg: any): Promise<void> {\n const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : undefined);\n const appSockets = appId ? this.appWsByAppId.get(appId) : undefined;\n\n if (!appSockets || appSockets.size === 0) {\n this.send(ws, { type: 'replay.rejected', replayId: msg.replayId, reason: 'App not connected' });\n return;\n }\n\n const newStimulationId = `${msg.stimulationId}-replay-${Date.now()}`;\n this.send(ws, { type: 'replay.accepted', replayId: msg.replayId, newStimulationId });\n\n const payload = JSON.stringify(msg);\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n // ─── Broadcast helpers ────────────────────────────────────────────────────\n\n private broadcast(message: object): void {\n this.broadcastRaw(JSON.stringify(message));\n }\n\n private broadcastRaw(payload: string): void {\n for (const client of this.uiClients) {\n try {\n if ((client as any).readyState === 1) client.send(payload);\n } catch {}\n }\n }\n\n private send(ws: WebSocket, message: object): void {\n try {\n if ((ws as any).readyState === 1) ws.send(JSON.stringify(message));\n } catch {}\n }\n\n // ─── Metrics ──────────────────────────────────────────────────────────────\n\n private startMetrics(): void {\n if (this.metricsTimer) return;\n this.metricsTimer = setInterval(() => {\n const mem = process.memoryUsage();\n const now = Date.now();\n const cpu = process.cpuUsage(this.lastCpuUsage);\n const elapsed = (now - this.lastCpuTime) * 1000;\n const cpuPercent = elapsed > 0 ? ((cpu.user + cpu.system) / elapsed) * 100 : 0;\n this.lastCpuUsage = process.cpuUsage();\n this.lastCpuTime = now;\n\n this.broadcast({\n type: 'server.metrics',\n timestamp: now,\n rssMB: mem.rss / 1024 / 1024,\n heapUsedMB: mem.heapUsed / 1024 / 1024,\n heapTotalMB: mem.heapTotal / 1024 / 1024,\n cpuPercent,\n });\n }, 1000);\n }\n\n private stopMetrics(): void {\n if (this.metricsTimer) {\n clearInterval(this.metricsTimer);\n this.metricsTimer = undefined;\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n TCNSAppDTO,\n CNSAppBatchMessageDTOSchema,\n CNSClientMessageDTOSchema,\n TCNSTopologyMessageDTO,\n} from '@cnstra/devtools-dto';\nimport type {\n TCNSStimulationFilter,\n TCNSStimulationTask,\n TCNSStimulationAttempt,\n} from '@cnstra/devtools-dto';\nimport { buildAttemptId, buildCloneId } from '@cnstra/devtools-dto';\nimport type {\n ICNSStimulationRepository,\n TCNSStimulationRosterFilter,\n} from '@cnstra/persist';\nimport {\n CNSInMemoryStimulationRepository,\n supportsRosterQuery,\n loadRosterEntries,\n keysetPage,\n computeRosterFacets,\n} from '@cnstra/persist';\nimport { WebSocket } from 'ws';\n\n// ─── Repository interface ─────────────────────────────────────────────────────\n\nexport type TCNSTopologySnapshotDTO = Omit<TCNSTopologyMessageDTO, 'type'>;\n\nexport interface ICNSDevToolsServerRepository {\n // Apps\n upsertApp(app: TCNSAppDTO): void | Promise<void>;\n listApps(): TCNSAppDTO[] | Promise<TCNSAppDTO[]>;\n\n // Topology\n saveTopology(snapshot: TCNSTopologySnapshotDTO): void | Promise<void>;\n getTopology(cnsId?: string): TCNSTopologySnapshotDTO[] | Promise<TCNSTopologySnapshotDTO[]>;\n\n // CNS ↔ App mapping\n addCnsToApp(appId: string, cnsId: string): void | Promise<void>;\n getCnsByApp(appId: string): string[] | Promise<string[]>;\n findAppByCns(cnsId: string): string | undefined | Promise<string | undefined>;\n}\n\n// ─── Server ───────────────────────────────────────────────────────────────────\n\nexport class CNSDevToolsServer {\n /** WebSocket → appId (for app clients) */\n private appSockets = new Map<WebSocket, string>();\n /** appId → WebSocket[] (one app may have multiple CNS instances) */\n private appWsByAppId = new Map<string, Set<WebSocket>>();\n /** WebSocket clients that registered as UI clients */\n private uiClients = new Set<WebSocket>();\n\n private metricsTimer?: NodeJS.Timeout;\n private lastCpuUsage = process.cpuUsage();\n private lastCpuTime = Date.now();\n\n /**\n * Name-based resumable store (Stimulation/Attempt/Task). Written from the three\n * `cns.stimulation*` batch items the producer emits when `trackStimulations` is on.\n * Defaults to in-memory; the example-app can inject a Postgres store.\n */\n private readonly stimulationRepository: ICNSStimulationRepository;\n\n constructor(\n private readonly repository: ICNSDevToolsServerRepository,\n stimulationRepository: ICNSStimulationRepository = new CNSInMemoryStimulationRepository()\n ) {\n this.stimulationRepository = stimulationRepository;\n }\n\n // ─── Message entry point ─────────────────────────────────────────────────\n\n async handleMessage(\n ws: WebSocket,\n raw: unknown\n ): Promise<void> {\n if (typeof raw !== 'string' && typeof raw !== 'object') return;\n\n let data: unknown;\n try {\n data = typeof raw === 'string' ? JSON.parse(raw) : raw;\n } catch {\n return;\n }\n\n if (!data || typeof data !== 'object' || !('type' in data)) return;\n const type = (data as any).type as string;\n\n // App batch (topology + stimulation events)\n if (type === 'batch') {\n const result = CNSAppBatchMessageDTOSchema.safeParse(data);\n if (!result.success) return;\n for (const item of result.data.items) {\n await this.handleAppBatchItem(ws, item);\n }\n return;\n }\n\n // UI client message\n const clientResult = CNSClientMessageDTOSchema.safeParse(data);\n if (!clientResult.success) return;\n const msg = clientResult.data;\n\n switch (msg.type) {\n case 'client.connect':\n await this.handleClientConnect(ws);\n break;\n case 'apps.query':\n await this.handleAppsQuery(ws, msg.requestId);\n break;\n case 'topology.query':\n await this.handleTopologyQuery(ws, msg.requestId, msg.cnsId, msg.appId);\n break;\n case 'replay.start':\n await this.handleReplayStart(ws, msg);\n break;\n case 'stimulation.retry':\n await this.handleStimulationRetry(ws, msg);\n break;\n case 'stimulation.clone':\n await this.handleStimulationClone(ws, msg);\n break;\n case 'cns.stimulations.list':\n await this.handleStimulationsList(ws, msg);\n break;\n case 'cns.stimulations.facets':\n await this.handleStimulationsFacets(ws, msg);\n break;\n case 'cns.stimulations.snapshot':\n await this.handleStimulationsSnapshot(ws, msg);\n break;\n case 'cns.stimulation.detail':\n await this.handleStimulationDetail(ws, msg);\n break;\n }\n }\n\n /**\n * Explorer protocol (bucket-shaped). The roster row = resumable state minus\n * `progress`, plus envelope summaries derived from the run's attempts.\n *\n * Pushdown: if the store implements `ICNSStimulationRosterQuery` (e.g. the\n * Postgres store), the filter + keyset + GROUP BY run INSIDE it (SQL). Else we\n * fall back to the shared reference derivation over the reader — identical\n * results, only the work moves. Roster shapes are structurally the wire DTOs,\n * so no mapping is needed at the boundary.\n */\n private toRosterFilter(\n scopeName: string | undefined,\n filter: TCNSStimulationFilter | undefined\n ): TCNSStimulationRosterFilter {\n return { ...(filter ?? {}), ...(scopeName ? { scopeName } : {}) };\n }\n\n private async handleStimulationsList(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const filter = this.toRosterFilter(msg.scopeName, msg.filter);\n const limit = typeof msg.limit === 'number' ? msg.limit : 50;\n const opts = { cursor: msg.cursor, limit };\n const store = this.stimulationRepository;\n const page = supportsRosterQuery(store)\n ? await store.queryRoster(filter, opts)\n : keysetPage(\n (await loadRosterEntries(store, filter)).map(e => e.row),\n opts\n );\n this.send(ws, {\n type: 'cns.stimulations.list.result',\n requestId: msg.requestId,\n stimulations: page.rows,\n nextCursor: page.nextCursor,\n total: page.total,\n });\n }\n\n private async handleStimulationsFacets(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const filter = this.toRosterFilter(msg.scopeName, msg.filter);\n const store = this.stimulationRepository;\n const facets = supportsRosterQuery(store)\n ? await store.queryFacets(filter, { metricPath: msg.metricPath })\n : computeRosterFacets(await loadRosterEntries(store, filter), {\n metricPath: msg.metricPath,\n });\n this.send(ws, {\n type: 'cns.stimulations.facets.result',\n requestId: msg.requestId,\n facets,\n });\n }\n\n private async handleStimulationDetail(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const attempts = await this.stimulationRepository.getAttempts(\n msg.stimulationId\n );\n const tasks: TCNSStimulationTask[] = [];\n for (const a of attempts) {\n const ts = await this.stimulationRepository.getTasks(\n a.stimulationAttemptId\n );\n tasks.push(...ts);\n }\n this.send(ws, {\n type: 'cns.stimulation.detail.result',\n requestId: msg.requestId,\n attempts,\n tasks,\n });\n }\n\n /**\n * The topology/signal views' bulk feed: a bounded, newest-first set of runs as\n * FLAT buckets (stimulations + attempts + tasks) the panel joins client-side by\n * id. Replaces the retired nested `cns.stimulations.query` tree — same data, no\n * server-side join, no client-side flatten.\n */\n private async handleStimulationsSnapshot(\n ws: WebSocket,\n msg: any\n ): Promise<void> {\n const filter = this.toRosterFilter(msg.scopeName, msg.filter);\n const store = this.stimulationRepository;\n const limit = typeof msg.limit === 'number' ? msg.limit : 200;\n const page = supportsRosterQuery(store)\n ? await store.queryRoster(filter, { limit })\n : keysetPage(\n (await loadRosterEntries(store, filter)).map(e => e.row),\n { limit }\n );\n const attempts: TCNSStimulationAttempt[] = [];\n const tasks: TCNSStimulationTask[] = [];\n for (const row of page.rows) {\n const as = await store.getAttempts(row.stimulationId);\n attempts.push(...as);\n for (const a of as)\n tasks.push(...(await store.getTasks(a.stimulationAttemptId)));\n }\n this.send(ws, {\n type: 'cns.stimulations.snapshot.result',\n requestId: msg.requestId,\n stimulations: page.rows,\n attempts,\n tasks,\n });\n }\n\n addClient(ws: WebSocket): void {\n this.uiClients.add(ws);\n if (this.uiClients.size === 1) this.startMetrics();\n }\n\n removeClient(ws: WebSocket): void {\n this.uiClients.delete(ws);\n this.appSockets.delete(ws);\n if (this.uiClients.size === 0) this.stopMetrics();\n }\n\n async getActiveApps(): Promise<TCNSAppDTO[]> {\n return this.repository.listApps();\n }\n\n stop(): void {\n this.stopMetrics();\n }\n\n // ─── App batch item handlers ──────────────────────────────────────────────\n\n private async handleAppBatchItem(ws: WebSocket, item: any): Promise<void> {\n switch (item.type) {\n case 'topology':\n await this.handleTopology(ws, item);\n break;\n // ── Name-based resumable model ──\n case 'cns.stimulation':\n await this.stimulationRepository.saveStimulation(item.data);\n break;\n case 'cns.stimulation.attempt':\n await this.stimulationRepository.saveAttempt(item.data);\n break;\n case 'cns.stimulation.task':\n await this.stimulationRepository.appendTask(item.data);\n break;\n }\n }\n\n /** The name-based resumable store, for tests/integrations that want to read it back. */\n getStimulationRepository(): ICNSStimulationRepository {\n return this.stimulationRepository;\n }\n\n private async handleTopology(ws: WebSocket, msg: any): Promise<void> {\n const { appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites } = msg;\n\n const now = Date.now();\n const app: TCNSAppDTO = {\n id: appId,\n name: appName,\n version,\n connectedAt: now,\n lastSeenAt: now,\n };\n\n await this.repository.upsertApp(app);\n await this.repository.addCnsToApp(appId, cnsId);\n await this.repository.saveTopology({ appId, cnsId, appName, version, timestamp, neurons, collaterals, dendrites });\n\n // Track app socket\n this.appSockets.set(ws, appId);\n if (!this.appWsByAppId.has(appId)) this.appWsByAppId.set(appId, new Set());\n this.appWsByAppId.get(appId)!.add(ws);\n\n // Broadcast topology + app connected to UI clients\n const topologyBroadcast = {\n type: 'topology.result' as const,\n requestId: '__broadcast__',\n snapshots: await this.repository.getTopology(cnsId),\n };\n\n this.broadcast({ type: 'app.connected', app, topology: { cnsId, neurons, collaterals, dendrites } });\n // Also send topology snapshot so UI can refresh graph\n this.broadcastRaw(JSON.stringify(topologyBroadcast));\n }\n\n // ─── UI client query handlers ─────────────────────────────────────────────\n\n private async handleClientConnect(ws: WebSocket): Promise<void> {\n this.addClient(ws);\n this.send(ws, { type: 'apps.result', requestId: '__init__', items: await this.repository.listApps() });\n this.send(ws, {\n type: 'topology.result',\n requestId: '__init__',\n snapshots: await this.repository.getTopology(),\n });\n }\n\n private async handleAppsQuery(ws: WebSocket, requestId: string): Promise<void> {\n const items = await this.repository.listApps();\n this.send(ws, { type: 'apps.result', requestId, items });\n }\n\n private async handleTopologyQuery(\n ws: WebSocket,\n requestId: string,\n cnsId?: string,\n appId?: string\n ): Promise<void> {\n let snapshots = await this.repository.getTopology(cnsId);\n if (appId) snapshots = snapshots.filter(s => s.appId === appId);\n this.send(ws, { type: 'topology.result', requestId, snapshots });\n }\n\n private async handleReplayStart(ws: WebSocket, msg: any): Promise<void> {\n const appId = msg.appId ?? (msg.cnsId ? await this.repository.findAppByCns(msg.cnsId) : undefined);\n const appSockets = appId ? this.appWsByAppId.get(appId) : undefined;\n\n if (!appSockets || appSockets.size === 0) {\n this.send(ws, { type: 'replay.rejected', replayId: msg.replayId, reason: 'App not connected' });\n return;\n }\n\n const newStimulationId = `${msg.stimulationId}-replay-${Date.now()}`;\n this.send(ws, { type: 'replay.accepted', replayId: msg.replayId, newStimulationId });\n\n const payload = JSON.stringify(msg);\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n // ─── Resumable actions (retry / clone), Phase 2b-2 ──────────────────────────────\n //\n // The UI's request is thin (a stimulationId). The CNS + name→ref registry live in\n // the APP, so the server can't execute it: it ENRICHES from the resumable store (the\n // stored entry + progress + a server-assigned attempt identity) and forwards a\n // resume/launch command to the owning app, routed by scopeName (= the cns id).\n\n /** Resolve the app that owns a stimulation's scope, or reject if unreachable. */\n private async resolveScopeApp(\n scopeName: string | undefined\n ): Promise<Set<WebSocket> | undefined> {\n const appId = scopeName\n ? await this.repository.findAppByCns(scopeName)\n : undefined;\n const sockets = appId ? this.appWsByAppId.get(appId) : undefined;\n return sockets && sockets.size > 0 ? sockets : undefined;\n }\n\n private async handleStimulationRetry(ws: WebSocket, msg: any): Promise<void> {\n const reject = (reason: string) =>\n this.send(ws, {\n type: 'stimulation.retry.rejected',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n reason,\n });\n\n const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);\n if (!stim) return reject('unknown stimulation');\n if (stim.progress.tasks.length === 0)\n return reject('nothing to resume (empty frontier)');\n\n const appSockets = await this.resolveScopeApp(stim.scopeName);\n if (!appSockets) return reject('app not connected');\n\n const attempts = await this.stimulationRepository.getAttempts(msg.stimulationId);\n const nextAttempt = attempts.length + 1;\n const stimulationAttemptId = buildAttemptId(msg.stimulationId, nextAttempt);\n\n this.send(ws, {\n type: 'stimulation.retry.accepted',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n newStimulationAttemptId: stimulationAttemptId,\n });\n\n const payload = JSON.stringify({\n type: 'cns.stimulation.resume',\n requestId: msg.requestId,\n scopeName: stim.scopeName,\n stimulationId: msg.stimulationId,\n stimulationAttemptId,\n attemptNumber: nextAttempt,\n entry: stim.entry,\n progress: stim.progress,\n options: msg.options,\n });\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n private async handleStimulationClone(ws: WebSocket, msg: any): Promise<void> {\n const reject = (reason: string) =>\n this.send(ws, {\n type: 'stimulation.clone.rejected',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n reason,\n });\n\n const stim = await this.stimulationRepository.getStimulation(msg.stimulationId);\n if (!stim) return reject('unknown stimulation');\n\n const appSockets = await this.resolveScopeApp(stim.scopeName);\n if (!appSockets) return reject('app not connected');\n\n const newStimulationId = buildCloneId(msg.stimulationId);\n const stimulationAttemptId = buildAttemptId(newStimulationId, 1);\n\n this.send(ws, {\n type: 'stimulation.clone.accepted',\n requestId: msg.requestId,\n stimulationId: msg.stimulationId,\n newStimulationId,\n newStimulationAttemptId: stimulationAttemptId,\n });\n\n const payload = JSON.stringify({\n type: 'cns.stimulation.launch',\n requestId: msg.requestId,\n scopeName: stim.scopeName,\n stimulationId: newStimulationId,\n stimulationAttemptId,\n entry: stim.entry,\n options: msg.options,\n });\n for (const appWs of appSockets) {\n try { appWs.send(payload); } catch {}\n }\n }\n\n // ─── Broadcast helpers ────────────────────────────────────────────────────\n\n private broadcast(message: object): void {\n this.broadcastRaw(JSON.stringify(message));\n }\n\n private broadcastRaw(payload: string): void {\n for (const client of this.uiClients) {\n try {\n if ((client as any).readyState === 1) client.send(payload);\n } catch {}\n }\n }\n\n private send(ws: WebSocket, message: object): void {\n try {\n if ((ws as any).readyState === 1) ws.send(JSON.stringify(message));\n } catch {}\n }\n\n // ─── Metrics ──────────────────────────────────────────────────────────────\n\n private startMetrics(): void {\n if (this.metricsTimer) return;\n this.metricsTimer = setInterval(() => {\n const mem = process.memoryUsage();\n const now = Date.now();\n const cpu = process.cpuUsage(this.lastCpuUsage);\n const elapsed = (now - this.lastCpuTime) * 1000;\n const cpuPercent = elapsed > 0 ? ((cpu.user + cpu.system) / elapsed) * 100 : 0;\n this.lastCpuUsage = process.cpuUsage();\n this.lastCpuTime = now;\n\n this.broadcast({\n type: 'server.metrics',\n timestamp: now,\n rssMB: mem.rss / 1024 / 1024,\n heapUsedMB: mem.heapUsed / 1024 / 1024,\n heapTotalMB: mem.heapTotal / 1024 / 1024,\n cpuPercent,\n });\n }, 1000);\n }\n\n private stopMetrics(): void {\n if (this.metricsTimer) {\n clearInterval(this.metricsTimer);\n this.metricsTimer = undefined;\n }\n }\n}\n"]}
package/dist/start.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { ICNS, TCNSNeuron, CNSPersistOptionsRegistry } from '@cnstra/core';
1
+ import type { CNSPersistOptionsRegistry, ICNSStimulationRepository } from '@cnstra/persist';
2
+ import type { ICNS, TCNSNeuron } from '@cnstra/types';
2
3
  export type StartDevToolsOptions = {
3
4
  /** Port for HTTP (UI) and WebSocket. Default: 3141 */
4
5
  port?: number;
@@ -7,6 +8,15 @@ export type StartDevToolsOptions = {
7
8
  /** App ID shown in DevTools. Default: 'app' */
8
9
  appId?: string;
9
10
  consoleLogEnabled?: boolean;
11
+ /**
12
+ * The canonical name-based Stimulation → Attempt → Task store the Explorer
13
+ * reads its runs / facets / waterfall from. Pass the SAME instance you give a
14
+ * pg-boss / BullMQ worker as its `observe` store, and every job run shows up
15
+ * in the Explorer live (one process, shared memory). Use a
16
+ * `CNSPostgresStimulationRepository` to keep history across restarts, or omit
17
+ * for a fresh ephemeral in-memory store.
18
+ */
19
+ stimulationRepository?: ICNSStimulationRepository;
10
20
  };
11
21
  export type DevToolsHandle = {
12
22
  url: string;
@@ -23,5 +33,5 @@ export type DevToolsHandle = {
23
33
  * await startDevTools(cns, registry);
24
34
  * // → 🧠 CNStra DevTools: http://localhost:3141
25
35
  */
26
- export declare function startDevTools(cns: ICNS<TCNSNeuron<any, any>>, registry: CNSPersistOptionsRegistry, options?: StartDevToolsOptions): Promise<DevToolsHandle>;
36
+ export declare function startDevTools(cns: ICNS<TCNSNeuron<any>>, registry: CNSPersistOptionsRegistry, options?: StartDevToolsOptions): Promise<DevToolsHandle>;
27
37
  //# sourceMappingURL=start.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../src/start.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AA6ChF,MAAM,MAAM,oBAAoB,GAAG;IAC/B,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAC/B,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAC/B,QAAQ,EAAE,yBAAyB,EACnC,OAAO,GAAE,oBAAyB,GACnC,OAAO,CAAC,cAAc,CAAC,CAiEzB"}
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../src/start.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACR,yBAAyB,EACzB,yBAAyB,EAC5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AA6CtD,MAAM,MAAM,oBAAoB,GAAG;IAC/B,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;;OAOG;IACH,qBAAqB,CAAC,EAAE,yBAAyB,CAAC;CACrD,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAC/B,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAC1B,QAAQ,EAAE,yBAAyB,EACnC,OAAO,GAAE,oBAAyB,GACnC,OAAO,CAAC,cAAc,CAAC,CAoEzB"}
package/package.json CHANGED
@@ -1,71 +1,79 @@
1
1
  {
2
- "name": "@cnstra/devtools-server",
3
- "version": "1.2.0",
4
- "description": "CNStra DevTools server (WebSocket only for now)",
5
- "keywords": [
6
- "cnstra",
7
- "devtools",
8
- "server",
9
- "websocket"
10
- ],
11
- "type": "module",
12
- "main": "./dist/index.cjs",
13
- "module": "./dist/index.js",
14
- "types": "./dist/index.d.ts",
15
- "sideEffects": false,
16
- "exports": {
17
- ".": {
18
- "types": "./dist/index.d.ts",
19
- "import": "./dist/index.js",
20
- "require": "./dist/index.cjs"
21
- }
22
- },
23
- "bin": {
24
- "cnstra-devtools": "./dist/cli.js"
25
- },
26
- "scripts": {
27
- "build": "tsup && tsc --emitDeclarationOnly",
28
- "build:types": "tsc --emitDeclarationOnly",
29
- "start": "node dist/index.js",
30
- "prepublishOnly": "npm run test && npm run build",
31
- "test": "jest"
32
- },
33
- "files": [
34
- "dist"
35
- ],
36
- "dependencies": {
37
- "@cnstra/devtools-dto": "^1.0.0",
38
- "@cnstra/devtools-server-repository-in-memory": "^1.1.0",
39
- "ws": "^8.18.0"
40
- },
41
- "peerDependencies": {
42
- "@cnstra/core": "^2.1.0",
43
- "@cnstra/devtools": "^1.0.1",
44
- "@cnstra/devtools-panel-ui": "^1.0.0",
45
- "@cnstra/devtools-transport-ws": "^1.0.0"
46
- },
47
- "peerDependenciesMeta": {
48
- "@cnstra/devtools-panel-ui": {
49
- "optional": true
2
+ "name": "@cnstra/devtools-server",
3
+ "version": "3.0.0",
4
+ "description": "CNStra DevTools server (WebSocket only for now)",
5
+ "keywords": [
6
+ "cnstra",
7
+ "devtools",
8
+ "server",
9
+ "websocket"
10
+ ],
11
+ "type": "module",
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "sideEffects": false,
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js",
20
+ "require": "./dist/index.cjs"
21
+ }
22
+ },
23
+ "bin": {
24
+ "cnstra-devtools": "./dist/cli.js"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup && tsc --emitDeclarationOnly",
28
+ "build:types": "tsc --emitDeclarationOnly",
29
+ "start": "node dist/index.js",
30
+ "prepublishOnly": "node ../../scripts/check-drift.mjs --gate && npm run test && npm run build",
31
+ "test": "jest"
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "dependencies": {
37
+ "@cnstra/devtools-dto": "^3.0.0",
38
+ "@cnstra/devtools-server-repository-in-memory": "^3.0.0",
39
+ "@cnstra/types": "^0.2.0",
40
+ "ws": "^8.18.0"
41
+ },
42
+ "peerDependencies": {
43
+ "@cnstra/core": "^3.0.0",
44
+ "@cnstra/devtools": "^3.0.0",
45
+ "@cnstra/devtools-panel-ui": "^3.0.0",
46
+ "@cnstra/devtools-transport-ws": "^2.0.0",
47
+ "@cnstra/persist": "^0.2.0"
48
+ },
49
+ "devDependencies": {
50
+ "@cnstra/broker": "^0.2.0",
51
+ "@cnstra/core": "^3.0.0",
52
+ "@cnstra/devtools": "^3.0.0",
53
+ "@cnstra/devtools-panel-ui": "^3.0.0",
54
+ "@cnstra/devtools-transport-ws": "^2.0.0",
55
+ "@cnstra/persist": "^0.2.0",
56
+ "@types/node": "^22.7.4",
57
+ "@types/ws": "^8.5.10",
58
+ "jest": "^29.7.0",
59
+ "ts-jest": "^29.1.2",
60
+ "tsup": "8.5.1",
61
+ "typescript": "^5.8.3"
50
62
  },
51
- "@cnstra/devtools": {
52
- "optional": true
63
+ "peerDependenciesMeta": {
64
+ "@cnstra/devtools-panel-ui": {
65
+ "optional": true
66
+ },
67
+ "@cnstra/devtools": {
68
+ "optional": true
69
+ },
70
+ "@cnstra/devtools-transport-ws": {
71
+ "optional": true
72
+ }
53
73
  },
54
- "@cnstra/devtools-transport-ws": {
55
- "optional": true
74
+ "author": "Andrei Baikov <abaikov23@gmail.com>",
75
+ "license": "MIT",
76
+ "publishConfig": {
77
+ "access": "public"
56
78
  }
57
- },
58
- "devDependencies": {
59
- "@types/node": "^22.7.4",
60
- "@types/ws": "^8.5.10",
61
- "jest": "^29.7.0",
62
- "ts-jest": "^29.1.2",
63
- "tsup": "8.5.1",
64
- "typescript": "^5.8.3"
65
- },
66
- "author": "Andrei Baikov <abaikov23@gmail.com>",
67
- "license": "MIT",
68
- "publishConfig": {
69
- "access": "public"
70
- }
71
79
  }