@cnstra/devtools-server 2.0.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 +101 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +28 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +103 -62
- package/dist/index.js.map +1 -1
- package/dist/start.d.ts +11 -2
- package/dist/start.d.ts.map +1 -1
- package/package.json +74 -73
package/dist/index.cjs
CHANGED
|
@@ -29,14 +29,14 @@ var CNSDevToolsServer = class {
|
|
|
29
29
|
if (!data || typeof data !== "object" || !("type" in data)) return;
|
|
30
30
|
const type = data.type;
|
|
31
31
|
if (type === "batch") {
|
|
32
|
-
const result = devtoolsDto.
|
|
32
|
+
const result = devtoolsDto.CNSAppBatchMessageDTOSchema.safeParse(data);
|
|
33
33
|
if (!result.success) return;
|
|
34
34
|
for (const item of result.data.items) {
|
|
35
35
|
await this.handleAppBatchItem(ws, item);
|
|
36
36
|
}
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
const clientResult = devtoolsDto.
|
|
39
|
+
const clientResult = devtoolsDto.CNSClientMessageDTOSchema.safeParse(data);
|
|
40
40
|
if (!clientResult.success) return;
|
|
41
41
|
const msg = clientResult.data;
|
|
42
42
|
switch (msg.type) {
|
|
@@ -58,67 +58,108 @@ var CNSDevToolsServer = class {
|
|
|
58
58
|
case "stimulation.clone":
|
|
59
59
|
await this.handleStimulationClone(ws, msg);
|
|
60
60
|
break;
|
|
61
|
-
case "cns.stimulations.
|
|
62
|
-
await this.
|
|
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);
|
|
63
72
|
break;
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
/**
|
|
67
|
-
*
|
|
68
|
-
* `
|
|
69
|
-
*
|
|
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.
|
|
70
84
|
*/
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
74
120
|
);
|
|
75
|
-
const
|
|
76
|
-
for (const
|
|
77
|
-
const
|
|
78
|
-
|
|
121
|
+
const tasks = [];
|
|
122
|
+
for (const a of attempts) {
|
|
123
|
+
const ts = await this.stimulationRepository.getTasks(
|
|
124
|
+
a.stimulationAttemptId
|
|
79
125
|
);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
entry: {
|
|
111
|
-
collateralName: stim.entry.collateralName,
|
|
112
|
-
payload: stim.entry.payload
|
|
113
|
-
},
|
|
114
|
-
frontier: stim.progress.tasks.map((t) => t.neuronName),
|
|
115
|
-
attempts: attemptViews
|
|
116
|
-
});
|
|
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));
|
|
117
156
|
}
|
|
118
157
|
this.send(ws, {
|
|
119
|
-
type: "cns.stimulations.result",
|
|
158
|
+
type: "cns.stimulations.snapshot.result",
|
|
120
159
|
requestId: msg.requestId,
|
|
121
|
-
|
|
160
|
+
stimulations: page.rows,
|
|
161
|
+
attempts,
|
|
162
|
+
tasks
|
|
122
163
|
});
|
|
123
164
|
}
|
|
124
165
|
addClient(ws) {
|
|
@@ -142,7 +183,7 @@ var CNSDevToolsServer = class {
|
|
|
142
183
|
case "topology":
|
|
143
184
|
await this.handleTopology(ws, item);
|
|
144
185
|
break;
|
|
145
|
-
// ── Name-based
|
|
186
|
+
// ── Name-based resumable model ──
|
|
146
187
|
case "cns.stimulation":
|
|
147
188
|
await this.stimulationRepository.saveStimulation(item.data);
|
|
148
189
|
break;
|
|
@@ -154,7 +195,7 @@ var CNSDevToolsServer = class {
|
|
|
154
195
|
break;
|
|
155
196
|
}
|
|
156
197
|
}
|
|
157
|
-
/** The name-based
|
|
198
|
+
/** The name-based resumable store, for tests/integrations that want to read it back. */
|
|
158
199
|
getStimulationRepository() {
|
|
159
200
|
return this.stimulationRepository;
|
|
160
201
|
}
|
|
@@ -218,10 +259,10 @@ var CNSDevToolsServer = class {
|
|
|
218
259
|
}
|
|
219
260
|
}
|
|
220
261
|
}
|
|
221
|
-
// ───
|
|
262
|
+
// ─── Resumable actions (retry / clone), Phase 2b-2 ──────────────────────────────
|
|
222
263
|
//
|
|
223
264
|
// The UI's request is thin (a stimulationId). The CNS + name→ref registry live in
|
|
224
|
-
// the APP, so the server can't execute it: it ENRICHES from the
|
|
265
|
+
// the APP, so the server can't execute it: it ENRICHES from the resumable store (the
|
|
225
266
|
// stored entry + progress + a server-assigned attempt identity) and forwards a
|
|
226
267
|
// resume/launch command to the owning app, routed by scopeName (= the cns id).
|
|
227
268
|
/** Resolve the app that owns a stimulation's scope, or reject if unreachable. */
|
|
@@ -245,7 +286,7 @@ var CNSDevToolsServer = class {
|
|
|
245
286
|
if (!appSockets) return reject("app not connected");
|
|
246
287
|
const attempts = await this.stimulationRepository.getAttempts(msg.stimulationId);
|
|
247
288
|
const nextAttempt = attempts.length + 1;
|
|
248
|
-
const stimulationAttemptId =
|
|
289
|
+
const stimulationAttemptId = devtoolsDto.buildAttemptId(msg.stimulationId, nextAttempt);
|
|
249
290
|
this.send(ws, {
|
|
250
291
|
type: "stimulation.retry.accepted",
|
|
251
292
|
requestId: msg.requestId,
|
|
@@ -281,8 +322,8 @@ var CNSDevToolsServer = class {
|
|
|
281
322
|
if (!stim) return reject("unknown stimulation");
|
|
282
323
|
const appSockets = await this.resolveScopeApp(stim.scopeName);
|
|
283
324
|
if (!appSockets) return reject("app not connected");
|
|
284
|
-
const newStimulationId =
|
|
285
|
-
const stimulationAttemptId =
|
|
325
|
+
const newStimulationId = devtoolsDto.buildCloneId(msg.stimulationId);
|
|
326
|
+
const stimulationAttemptId = devtoolsDto.buildAttemptId(newStimulationId, 1);
|
|
286
327
|
this.send(ws, {
|
|
287
328
|
type: "stimulation.clone.accepted",
|
|
288
329
|
requestId: msg.requestId,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["CNSInMemoryStimulationRepository","CNSDTOAppBatchMessageSchema","CNSDTOClientMessageSchema"],"mappings":"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","file":"index.cjs","sourcesContent":["import {\n CNSDTOApp,\n CNSDTOAppBatchMessageSchema,\n CNSDTOClientMessageSchema,\n CNSDTOTopologyMessage,\n} from '@cnstra/devtools-dto';\nimport type { ICNSStimulationRepository } from '@cnstra/persist';\nimport { CNSInMemoryStimulationRepository } from '@cnstra/persist';\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 // 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 durable 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 = 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 '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.query':\n await this.handleRunsQuery(ws, msg);\n break;\n }\n }\n\n /**\n * Name-based observability (Phase 2b-3): project the durable\n * `ICNSStimulationRepository` into the run/attempt/task roster the panel\n * renders. This replaces the legacy id-based `stimulations.query`/`hops.query`.\n */\n private async handleRunsQuery(ws: WebSocket, msg: any): Promise<void> {\n const stims = await this.stimulationRepository.listStimulations(\n msg.scopeName ? { scopeName: msg.scopeName } : undefined\n );\n const runs = [];\n for (const stim of stims) {\n const attempts = await this.stimulationRepository.getAttempts(\n stim.stimulationId\n );\n const attemptViews = [];\n for (const a of attempts) {\n const tasks = await this.stimulationRepository.getTasks(\n a.stimulationAttemptId\n );\n attemptViews.push({\n attemptNumber: a.attemptNumber,\n status: a.status,\n hopCount: a.hopCount,\n startedAt: a.startedAt,\n completedAt: a.completedAt ?? null,\n tasks: tasks.map(t => ({\n index: t.index,\n neuronName: t.neuronName,\n dendriteCollateralName: t.dendriteCollateralName,\n status: t.status,\n output: t.output\n ? {\n collateralName: t.output.collateralName,\n payload: t.output.payload,\n }\n : null,\n error: t.error ?? null,\n startedAt: t.startedAt,\n duration: t.duration ?? null,\n })),\n });\n }\n runs.push({\n runId: stim.stimulationId,\n status: stim.status,\n scopeName: stim.scopeName,\n entry: {\n collateralName: stim.entry.collateralName,\n payload: stim.entry.payload,\n },\n frontier: stim.progress.tasks.map(t => t.neuronName),\n attempts: attemptViews,\n });\n }\n this.send(ws, {\n type: 'cns.stimulations.result',\n requestId: msg.requestId,\n runs,\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 // ── Name-based durable 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 durable 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: 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 // ─── 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 // ─── Durable 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 durable 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 = `${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 = `${msg.stimulationId}-clone-${Date.now()}`;\n const stimulationAttemptId = `${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"]}
|
|
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,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TCNSAppDTO, TCNSTopologyMessageDTO } from '@cnstra/devtools-dto';
|
|
2
2
|
import type { ICNSStimulationRepository } from '@cnstra/persist';
|
|
3
3
|
import { WebSocket } from 'ws';
|
|
4
|
-
export type
|
|
4
|
+
export type TCNSTopologySnapshotDTO = Omit<TCNSTopologyMessageDTO, 'type'>;
|
|
5
5
|
export interface ICNSDevToolsServerRepository {
|
|
6
|
-
upsertApp(app:
|
|
7
|
-
listApps():
|
|
8
|
-
saveTopology(snapshot:
|
|
9
|
-
getTopology(cnsId?: string):
|
|
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[]>;
|
|
10
10
|
addCnsToApp(appId: string, cnsId: string): void | Promise<void>;
|
|
11
11
|
getCnsByApp(appId: string): string[] | Promise<string[]>;
|
|
12
12
|
findAppByCns(cnsId: string): string | undefined | Promise<string | undefined>;
|
|
@@ -23,7 +23,7 @@ export declare class CNSDevToolsServer {
|
|
|
23
23
|
private lastCpuUsage;
|
|
24
24
|
private lastCpuTime;
|
|
25
25
|
/**
|
|
26
|
-
* Name-based
|
|
26
|
+
* Name-based resumable store (Stimulation/Attempt/Task). Written from the three
|
|
27
27
|
* `cns.stimulation*` batch items the producer emits when `trackStimulations` is on.
|
|
28
28
|
* Defaults to in-memory; the example-app can inject a Postgres store.
|
|
29
29
|
*/
|
|
@@ -31,17 +31,32 @@ export declare class CNSDevToolsServer {
|
|
|
31
31
|
constructor(repository: ICNSDevToolsServerRepository, stimulationRepository?: ICNSStimulationRepository);
|
|
32
32
|
handleMessage(ws: WebSocket, raw: unknown): Promise<void>;
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
* `
|
|
36
|
-
*
|
|
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.
|
|
37
42
|
*/
|
|
38
|
-
private
|
|
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;
|
|
39
54
|
addClient(ws: WebSocket): void;
|
|
40
55
|
removeClient(ws: WebSocket): void;
|
|
41
|
-
getActiveApps(): Promise<
|
|
56
|
+
getActiveApps(): Promise<TCNSAppDTO[]>;
|
|
42
57
|
stop(): void;
|
|
43
58
|
private handleAppBatchItem;
|
|
44
|
-
/** The name-based
|
|
59
|
+
/** The name-based resumable store, for tests/integrations that want to read it back. */
|
|
45
60
|
getStimulationRepository(): ICNSStimulationRepository;
|
|
46
61
|
private handleTopology;
|
|
47
62
|
private handleClientConnect;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,
|
|
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,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CNSInMemoryStimulationRepository } from '@cnstra/persist';
|
|
1
|
+
import { CNSAppBatchMessageDTOSchema, CNSClientMessageDTOSchema, buildAttemptId, buildCloneId } from '@cnstra/devtools-dto';
|
|
2
|
+
import { CNSInMemoryStimulationRepository, supportsRosterQuery, keysetPage, loadRosterEntries, computeRosterFacets } from '@cnstra/persist';
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
var CNSDevToolsServer = class {
|
|
@@ -27,14 +27,14 @@ var CNSDevToolsServer = class {
|
|
|
27
27
|
if (!data || typeof data !== "object" || !("type" in data)) return;
|
|
28
28
|
const type = data.type;
|
|
29
29
|
if (type === "batch") {
|
|
30
|
-
const result =
|
|
30
|
+
const result = CNSAppBatchMessageDTOSchema.safeParse(data);
|
|
31
31
|
if (!result.success) return;
|
|
32
32
|
for (const item of result.data.items) {
|
|
33
33
|
await this.handleAppBatchItem(ws, item);
|
|
34
34
|
}
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
|
-
const clientResult =
|
|
37
|
+
const clientResult = CNSClientMessageDTOSchema.safeParse(data);
|
|
38
38
|
if (!clientResult.success) return;
|
|
39
39
|
const msg = clientResult.data;
|
|
40
40
|
switch (msg.type) {
|
|
@@ -56,67 +56,108 @@ var CNSDevToolsServer = class {
|
|
|
56
56
|
case "stimulation.clone":
|
|
57
57
|
await this.handleStimulationClone(ws, msg);
|
|
58
58
|
break;
|
|
59
|
-
case "cns.stimulations.
|
|
60
|
-
await this.
|
|
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);
|
|
61
70
|
break;
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
/**
|
|
65
|
-
*
|
|
66
|
-
* `
|
|
67
|
-
*
|
|
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.
|
|
68
82
|
*/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
72
118
|
);
|
|
73
|
-
const
|
|
74
|
-
for (const
|
|
75
|
-
const
|
|
76
|
-
|
|
119
|
+
const tasks = [];
|
|
120
|
+
for (const a of attempts) {
|
|
121
|
+
const ts = await this.stimulationRepository.getTasks(
|
|
122
|
+
a.stimulationAttemptId
|
|
77
123
|
);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
entry: {
|
|
109
|
-
collateralName: stim.entry.collateralName,
|
|
110
|
-
payload: stim.entry.payload
|
|
111
|
-
},
|
|
112
|
-
frontier: stim.progress.tasks.map((t) => t.neuronName),
|
|
113
|
-
attempts: attemptViews
|
|
114
|
-
});
|
|
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));
|
|
115
154
|
}
|
|
116
155
|
this.send(ws, {
|
|
117
|
-
type: "cns.stimulations.result",
|
|
156
|
+
type: "cns.stimulations.snapshot.result",
|
|
118
157
|
requestId: msg.requestId,
|
|
119
|
-
|
|
158
|
+
stimulations: page.rows,
|
|
159
|
+
attempts,
|
|
160
|
+
tasks
|
|
120
161
|
});
|
|
121
162
|
}
|
|
122
163
|
addClient(ws) {
|
|
@@ -140,7 +181,7 @@ var CNSDevToolsServer = class {
|
|
|
140
181
|
case "topology":
|
|
141
182
|
await this.handleTopology(ws, item);
|
|
142
183
|
break;
|
|
143
|
-
// ── Name-based
|
|
184
|
+
// ── Name-based resumable model ──
|
|
144
185
|
case "cns.stimulation":
|
|
145
186
|
await this.stimulationRepository.saveStimulation(item.data);
|
|
146
187
|
break;
|
|
@@ -152,7 +193,7 @@ var CNSDevToolsServer = class {
|
|
|
152
193
|
break;
|
|
153
194
|
}
|
|
154
195
|
}
|
|
155
|
-
/** The name-based
|
|
196
|
+
/** The name-based resumable store, for tests/integrations that want to read it back. */
|
|
156
197
|
getStimulationRepository() {
|
|
157
198
|
return this.stimulationRepository;
|
|
158
199
|
}
|
|
@@ -216,10 +257,10 @@ var CNSDevToolsServer = class {
|
|
|
216
257
|
}
|
|
217
258
|
}
|
|
218
259
|
}
|
|
219
|
-
// ───
|
|
260
|
+
// ─── Resumable actions (retry / clone), Phase 2b-2 ──────────────────────────────
|
|
220
261
|
//
|
|
221
262
|
// The UI's request is thin (a stimulationId). The CNS + name→ref registry live in
|
|
222
|
-
// the APP, so the server can't execute it: it ENRICHES from the
|
|
263
|
+
// the APP, so the server can't execute it: it ENRICHES from the resumable store (the
|
|
223
264
|
// stored entry + progress + a server-assigned attempt identity) and forwards a
|
|
224
265
|
// resume/launch command to the owning app, routed by scopeName (= the cns id).
|
|
225
266
|
/** Resolve the app that owns a stimulation's scope, or reject if unreachable. */
|
|
@@ -243,7 +284,7 @@ var CNSDevToolsServer = class {
|
|
|
243
284
|
if (!appSockets) return reject("app not connected");
|
|
244
285
|
const attempts = await this.stimulationRepository.getAttempts(msg.stimulationId);
|
|
245
286
|
const nextAttempt = attempts.length + 1;
|
|
246
|
-
const stimulationAttemptId =
|
|
287
|
+
const stimulationAttemptId = buildAttemptId(msg.stimulationId, nextAttempt);
|
|
247
288
|
this.send(ws, {
|
|
248
289
|
type: "stimulation.retry.accepted",
|
|
249
290
|
requestId: msg.requestId,
|
|
@@ -279,8 +320,8 @@ var CNSDevToolsServer = class {
|
|
|
279
320
|
if (!stim) return reject("unknown stimulation");
|
|
280
321
|
const appSockets = await this.resolveScopeApp(stim.scopeName);
|
|
281
322
|
if (!appSockets) return reject("app not connected");
|
|
282
|
-
const newStimulationId =
|
|
283
|
-
const stimulationAttemptId =
|
|
323
|
+
const newStimulationId = buildCloneId(msg.stimulationId);
|
|
324
|
+
const stimulationAttemptId = buildAttemptId(newStimulationId, 1);
|
|
284
325
|
this.send(ws, {
|
|
285
326
|
type: "stimulation.clone.accepted",
|
|
286
327
|
requestId: msg.requestId,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;AA+BO,IAAM,oBAAN,MAAwB;AAAA,EAmB3B,WAAA,CACqB,UAAA,EACjB,qBAAA,GAAmD,IAAI,kCAAiC,EAC1F;AAFmB,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AAlBrB;AAAA,IAAA,IAAA,CAAQ,UAAA,uBAAiB,GAAA,EAAuB;AAEhD;AAAA,IAAA,IAAA,CAAQ,YAAA,uBAAmB,GAAA,EAA4B;AAEvD;AAAA,IAAA,IAAA,CAAQ,SAAA,uBAAgB,GAAA,EAAe;AAGvC,IAAA,IAAA,CAAQ,YAAA,GAAe,QAAQ,QAAA,EAAS;AACxC,IAAA,IAAA,CAAQ,WAAA,GAAc,KAAK,GAAA,EAAI;AAa3B,IAAA,IAAA,CAAK,qBAAA,GAAwB,qBAAA;AAAA,EACjC;AAAA;AAAA,EAIA,MAAM,aAAA,CACF,EAAA,EACA,GAAA,EACa;AACb,IAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,OAAO,QAAQ,QAAA,EAAU;AAExD,IAAA,IAAI,IAAA;AACJ,IAAA,IAAI;AACA,MAAA,IAAA,GAAO,OAAO,GAAA,KAAQ,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,GAAI,GAAA;AAAA,IACvD,CAAA,CAAA,MAAQ;AACJ,MAAA;AAAA,IACJ;AAEA,IAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,SAAS,QAAA,IAAY,EAAE,UAAU,IAAA,CAAA,EAAO;AAC5D,IAAA,MAAM,OAAQ,IAAA,CAAa,IAAA;AAG3B,IAAA,IAAI,SAAS,OAAA,EAAS;AAClB,MAAA,MAAM,MAAA,GAAS,2BAAA,CAA4B,SAAA,CAAU,IAAI,CAAA;AACzD,MAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACrB,MAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,IAAA,CAAK,KAAA,EAAO;AAClC,QAAA,MAAM,IAAA,CAAK,kBAAA,CAAmB,EAAA,EAAI,IAAI,CAAA;AAAA,MAC1C;AACA,MAAA;AAAA,IACJ;AAGA,IAAA,MAAM,YAAA,GAAe,yBAAA,CAA0B,SAAA,CAAU,IAAI,CAAA;AAC7D,IAAA,IAAI,CAAC,aAAa,OAAA,EAAS;AAC3B,IAAA,MAAM,MAAM,YAAA,CAAa,IAAA;AAEzB,IAAA,QAAQ,IAAI,IAAA;AAAM,MACd,KAAK,gBAAA;AACD,QAAA,MAAM,IAAA,CAAK,oBAAoB,EAAE,CAAA;AACjC,QAAA;AAAA,MACJ,KAAK,YAAA;AACD,QAAA,MAAM,IAAA,CAAK,eAAA,CAAgB,EAAA,EAAI,GAAA,CAAI,SAAS,CAAA;AAC5C,QAAA;AAAA,MACJ,KAAK,gBAAA;AACD,QAAA,MAAM,IAAA,CAAK,oBAAoB,EAAA,EAAI,GAAA,CAAI,WAAW,GAAA,CAAI,KAAA,EAAO,IAAI,KAAK,CAAA;AACtE,QAAA;AAAA,MACJ,KAAK,cAAA;AACD,QAAA,MAAM,IAAA,CAAK,iBAAA,CAAkB,EAAA,EAAI,GAAG,CAAA;AACpC,QAAA;AAAA,MACJ,KAAK,mBAAA;AACD,QAAA,MAAM,IAAA,CAAK,sBAAA,CAAuB,EAAA,EAAI,GAAG,CAAA;AACzC,QAAA;AAAA,MACJ,KAAK,mBAAA;AACD,QAAA,MAAM,IAAA,CAAK,sBAAA,CAAuB,EAAA,EAAI,GAAG,CAAA;AACzC,QAAA;AAAA,MACJ,KAAK,wBAAA;AACD,QAAA,MAAM,IAAA,CAAK,eAAA,CAAgB,EAAA,EAAI,GAAG,CAAA;AAClC,QAAA;AAAA;AACR,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,eAAA,CAAgB,EAAA,EAAe,GAAA,EAAyB;AAClE,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,qBAAA,CAAsB,gBAAA;AAAA,MAC3C,IAAI,SAAA,GAAY,EAAE,SAAA,EAAW,GAAA,CAAI,WAAU,GAAI;AAAA,KACnD;AACA,IAAA,MAAM,OAAO,EAAC;AACd,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACtB,MAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,qBAAA,CAAsB,WAAA;AAAA,QAC9C,IAAA,CAAK;AAAA,OACT;AACA,MAAA,MAAM,eAAe,EAAC;AACtB,MAAA,KAAA,MAAW,KAAK,QAAA,EAAU;AACtB,QAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,qBAAA,CAAsB,QAAA;AAAA,UAC3C,CAAA,CAAE;AAAA,SACN;AACA,QAAA,YAAA,CAAa,IAAA,CAAK;AAAA,UACd,eAAe,CAAA,CAAE,aAAA;AAAA,UACjB,QAAQ,CAAA,CAAE,MAAA;AAAA,UACV,UAAU,CAAA,CAAE,QAAA;AAAA,UACZ,WAAW,CAAA,CAAE,SAAA;AAAA,UACb,WAAA,EAAa,EAAE,WAAA,IAAe,IAAA;AAAA,UAC9B,KAAA,EAAO,KAAA,CAAM,GAAA,CAAI,CAAA,CAAA,MAAM;AAAA,YACnB,OAAO,CAAA,CAAE,KAAA;AAAA,YACT,YAAY,CAAA,CAAE,UAAA;AAAA,YACd,wBAAwB,CAAA,CAAE,sBAAA;AAAA,YAC1B,QAAQ,CAAA,CAAE,MAAA;AAAA,YACV,MAAA,EAAQ,EAAE,MAAA,GACJ;AAAA,cACI,cAAA,EAAgB,EAAE,MAAA,CAAO,cAAA;AAAA,cACzB,OAAA,EAAS,EAAE,MAAA,CAAO;AAAA,aACtB,GACA,IAAA;AAAA,YACN,KAAA,EAAO,EAAE,KAAA,IAAS,IAAA;AAAA,YAClB,WAAW,CAAA,CAAE,SAAA;AAAA,YACb,QAAA,EAAU,EAAE,QAAA,IAAY;AAAA,WAC5B,CAAE;AAAA,SACL,CAAA;AAAA,MACL;AACA,MAAA,IAAA,CAAK,IAAA,CAAK;AAAA,QACN,OAAO,IAAA,CAAK,aAAA;AAAA,QACZ,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,WAAW,IAAA,CAAK,SAAA;AAAA,QAChB,KAAA,EAAO;AAAA,UACH,cAAA,EAAgB,KAAK,KAAA,CAAM,cAAA;AAAA,UAC3B,OAAA,EAAS,KAAK,KAAA,CAAM;AAAA,SACxB;AAAA,QACA,UAAU,IAAA,CAAK,QAAA,CAAS,MAAM,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,UAAU,CAAA;AAAA,QACnD,QAAA,EAAU;AAAA,OACb,CAAA;AAAA,IACL;AACA,IAAA,IAAA,CAAK,KAAK,EAAA,EAAI;AAAA,MACV,IAAA,EAAM,yBAAA;AAAA,MACN,WAAW,GAAA,CAAI,SAAA;AAAA,MACf;AAAA,KACH,CAAA;AAAA,EACL;AAAA,EAEA,UAAU,EAAA,EAAqB;AAC3B,IAAA,IAAA,CAAK,SAAA,CAAU,IAAI,EAAE,CAAA;AACrB,IAAA,IAAI,IAAA,CAAK,SAAA,CAAU,IAAA,KAAS,CAAA,OAAQ,YAAA,EAAa;AAAA,EACrD;AAAA,EAEA,aAAa,EAAA,EAAqB;AAC9B,IAAA,IAAA,CAAK,SAAA,CAAU,OAAO,EAAE,CAAA;AACxB,IAAA,IAAA,CAAK,UAAA,CAAW,OAAO,EAAE,CAAA;AACzB,IAAA,IAAI,IAAA,CAAK,SAAA,CAAU,IAAA,KAAS,CAAA,OAAQ,WAAA,EAAY;AAAA,EACpD;AAAA,EAEA,MAAM,aAAA,GAAsC;AACxC,IAAA,OAAO,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,EACpC;AAAA,EAEA,IAAA,GAAa;AACT,IAAA,IAAA,CAAK,WAAA,EAAY;AAAA,EACrB;AAAA;AAAA,EAIA,MAAc,kBAAA,CAAmB,EAAA,EAAe,IAAA,EAA0B;AACtE,IAAA,QAAQ,KAAK,IAAA;AAAM,MACf,KAAK,UAAA;AACD,QAAA,MAAM,IAAA,CAAK,cAAA,CAAe,EAAA,EAAI,IAAI,CAAA;AAClC,QAAA;AAAA;AAAA,MAEJ,KAAK,iBAAA;AACD,QAAA,MAAM,IAAA,CAAK,qBAAA,CAAsB,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA;AAC1D,QAAA;AAAA,MACJ,KAAK,yBAAA;AACD,QAAA,MAAM,IAAA,CAAK,qBAAA,CAAsB,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA;AACtD,QAAA;AAAA,MACJ,KAAK,sBAAA;AACD,QAAA,MAAM,IAAA,CAAK,qBAAA,CAAsB,UAAA,CAAW,IAAA,CAAK,IAAI,CAAA;AACrD,QAAA;AAAA;AACR,EACJ;AAAA;AAAA,EAGA,wBAAA,GAAsD;AAClD,IAAA,OAAO,IAAA,CAAK,qBAAA;AAAA,EAChB;AAAA,EAEA,MAAc,cAAA,CAAe,EAAA,EAAe,GAAA,EAAyB;AACjE,IAAA,MAAM,EAAE,OAAO,KAAA,EAAO,OAAA,EAAS,SAAS,SAAA,EAAW,OAAA,EAAS,WAAA,EAAa,SAAA,EAAU,GAAI,GAAA;AAEvF,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,MAAM,GAAA,GAAiB;AAAA,MACnB,EAAA,EAAI,KAAA;AAAA,MACJ,IAAA,EAAM,OAAA;AAAA,MACN,OAAA;AAAA,MACA,WAAA,EAAa,GAAA;AAAA,MACb,UAAA,EAAY;AAAA,KAChB;AAEA,IAAA,MAAM,IAAA,CAAK,UAAA,CAAW,SAAA,CAAU,GAAG,CAAA;AACnC,IAAA,MAAM,IAAA,CAAK,UAAA,CAAW,WAAA,CAAY,KAAA,EAAO,KAAK,CAAA;AAC9C,IAAA,MAAM,IAAA,CAAK,UAAA,CAAW,YAAA,CAAa,EAAE,KAAA,EAAO,KAAA,EAAO,OAAA,EAAS,OAAA,EAAS,SAAA,EAAW,OAAA,EAAS,WAAA,EAAa,SAAA,EAAW,CAAA;AAGjH,IAAA,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,EAAA,EAAI,KAAK,CAAA;AAC7B,IAAA,IAAI,CAAC,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,KAAK,CAAA,EAAG,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,KAAA,kBAAO,IAAI,GAAA,EAAK,CAAA;AACzE,IAAA,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,KAAK,CAAA,CAAG,IAAI,EAAE,CAAA;AAGpC,IAAA,MAAM,iBAAA,GAAoB;AAAA,MACtB,IAAA,EAAM,iBAAA;AAAA,MACN,SAAA,EAAW,eAAA;AAAA,MACX,SAAA,EAAW,MAAM,IAAA,CAAK,UAAA,CAAW,YAAY,KAAK;AAAA,KACtD;AAEA,IAAA,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAM,eAAA,EAAiB,GAAA,EAAK,QAAA,EAAU,EAAE,KAAA,EAAO,OAAA,EAAS,WAAA,EAAa,SAAA,EAAU,EAAG,CAAA;AAEnG,IAAA,IAAA,CAAK,YAAA,CAAa,IAAA,CAAK,SAAA,CAAU,iBAAiB,CAAC,CAAA;AAAA,EACvD;AAAA;AAAA,EAIA,MAAc,oBAAoB,EAAA,EAA8B;AAC5D,IAAA,IAAA,CAAK,UAAU,EAAE,CAAA;AACjB,IAAA,IAAA,CAAK,IAAA,CAAK,EAAA,EAAI,EAAE,IAAA,EAAM,aAAA,EAAe,SAAA,EAAW,UAAA,EAAY,KAAA,EAAO,MAAM,IAAA,CAAK,UAAA,CAAW,QAAA,IAAY,CAAA;AACrG,IAAA,IAAA,CAAK,KAAK,EAAA,EAAI;AAAA,MACV,IAAA,EAAM,iBAAA;AAAA,MACN,SAAA,EAAW,UAAA;AAAA,MACX,SAAA,EAAW,MAAM,IAAA,CAAK,UAAA,CAAW,WAAA;AAAY,KAChD,CAAA;AAAA,EACL;AAAA,EAEA,MAAc,eAAA,CAAgB,EAAA,EAAe,SAAA,EAAkC;AAC3E,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,UAAA,CAAW,QAAA,EAAS;AAC7C,IAAA,IAAA,CAAK,KAAK,EAAA,EAAI,EAAE,MAAM,aAAA,EAAe,SAAA,EAAW,OAAO,CAAA;AAAA,EAC3D;AAAA,EAEA,MAAc,mBAAA,CACV,EAAA,EACA,SAAA,EACA,OACA,KAAA,EACa;AACb,IAAA,IAAI,SAAA,GAAY,MAAM,IAAA,CAAK,UAAA,CAAW,YAAY,KAAK,CAAA;AACvD,IAAA,IAAI,OAAO,SAAA,GAAY,SAAA,CAAU,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,UAAU,KAAK,CAAA;AAC9D,IAAA,IAAA,CAAK,KAAK,EAAA,EAAI,EAAE,MAAM,iBAAA,EAAmB,SAAA,EAAW,WAAW,CAAA;AAAA,EACnE;AAAA,EAEA,MAAc,iBAAA,CAAkB,EAAA,EAAe,GAAA,EAAyB;AACpE,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,KAAU,GAAA,CAAI,KAAA,GAAQ,MAAM,IAAA,CAAK,UAAA,CAAW,YAAA,CAAa,GAAA,CAAI,KAAK,CAAA,GAAI,MAAA,CAAA;AACxF,IAAA,MAAM,aAAa,KAAA,GAAQ,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,KAAK,CAAA,GAAI,MAAA;AAE1D,IAAA,IAAI,CAAC,UAAA,IAAc,UAAA,CAAW,IAAA,KAAS,CAAA,EAAG;AACtC,MAAA,IAAA,CAAK,IAAA,CAAK,EAAA,EAAI,EAAE,IAAA,EAAM,iBAAA,EAAmB,UAAU,GAAA,CAAI,QAAA,EAAU,MAAA,EAAQ,mBAAA,EAAqB,CAAA;AAC9F,MAAA;AAAA,IACJ;AAEA,IAAA,MAAM,mBAAmB,CAAA,EAAG,GAAA,CAAI,aAAa,CAAA,QAAA,EAAW,IAAA,CAAK,KAAK,CAAA,CAAA;AAClE,IAAA,IAAA,CAAK,IAAA,CAAK,IAAI,EAAE,IAAA,EAAM,mBAAmB,QAAA,EAAU,GAAA,CAAI,QAAA,EAAU,gBAAA,EAAkB,CAAA;AAEnF,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,SAAA,CAAU,GAAG,CAAA;AAClC,IAAA,KAAA,MAAW,SAAS,UAAA,EAAY;AAC5B,MAAA,IAAI;AAAE,QAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAAA,MAAG,CAAA,CAAA,MAAQ;AAAA,MAAC;AAAA,IACxC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,gBACV,SAAA,EACmC;AACnC,IAAA,MAAM,QAAQ,SAAA,GACR,MAAM,KAAK,UAAA,CAAW,YAAA,CAAa,SAAS,CAAA,GAC5C,MAAA;AACN,IAAA,MAAM,UAAU,KAAA,GAAQ,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,KAAK,CAAA,GAAI,MAAA;AACvD,IAAA,OAAO,OAAA,IAAW,OAAA,CAAQ,IAAA,GAAO,CAAA,GAAI,OAAA,GAAU,MAAA;AAAA,EACnD;AAAA,EAEA,MAAc,sBAAA,CAAuB,EAAA,EAAe,GAAA,EAAyB;AACzE,IAAA,MAAM,MAAA,GAAS,CAAC,MAAA,KACZ,IAAA,CAAK,KAAK,EAAA,EAAI;AAAA,MACV,IAAA,EAAM,4BAAA;AAAA,MACN,WAAW,GAAA,CAAI,SAAA;AAAA,MACf,eAAe,GAAA,CAAI,aAAA;AAAA,MACnB;AAAA,KACH,CAAA;AAEL,IAAA,MAAM,OAAO,MAAM,IAAA,CAAK,qBAAA,CAAsB,cAAA,CAAe,IAAI,aAAa,CAAA;AAC9E,IAAA,IAAI,CAAC,IAAA,EAAM,OAAO,MAAA,CAAO,qBAAqB,CAAA;AAC9C,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,KAAA,CAAM,MAAA,KAAW,CAAA;AAC/B,MAAA,OAAO,OAAO,oCAAoC,CAAA;AAEtD,IAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,eAAA,CAAgB,KAAK,SAAS,CAAA;AAC5D,IAAA,IAAI,CAAC,UAAA,EAAY,OAAO,MAAA,CAAO,mBAAmB,CAAA;AAElD,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,qBAAA,CAAsB,WAAA,CAAY,IAAI,aAAa,CAAA;AAC/E,IAAA,MAAM,WAAA,GAAc,SAAS,MAAA,GAAS,CAAA;AACtC,IAAA,MAAM,oBAAA,GAAuB,CAAA,EAAG,GAAA,CAAI,aAAa,IAAI,WAAW,CAAA,CAAA;AAEhE,IAAA,IAAA,CAAK,KAAK,EAAA,EAAI;AAAA,MACV,IAAA,EAAM,4BAAA;AAAA,MACN,WAAW,GAAA,CAAI,SAAA;AAAA,MACf,eAAe,GAAA,CAAI,aAAA;AAAA,MACnB,uBAAA,EAAyB;AAAA,KAC5B,CAAA;AAED,IAAA,MAAM,OAAA,GAAU,KAAK,SAAA,CAAU;AAAA,MAC3B,IAAA,EAAM,wBAAA;AAAA,MACN,WAAW,GAAA,CAAI,SAAA;AAAA,MACf,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,eAAe,GAAA,CAAI,aAAA;AAAA,MACnB,oBAAA;AAAA,MACA,aAAA,EAAe,WAAA;AAAA,MACf,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,SAAS,GAAA,CAAI;AAAA,KAChB,CAAA;AACD,IAAA,KAAA,MAAW,SAAS,UAAA,EAAY;AAC5B,MAAA,IAAI;AAAE,QAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAAA,MAAG,CAAA,CAAA,MAAQ;AAAA,MAAC;AAAA,IACxC;AAAA,EACJ;AAAA,EAEA,MAAc,sBAAA,CAAuB,EAAA,EAAe,GAAA,EAAyB;AACzE,IAAA,MAAM,MAAA,GAAS,CAAC,MAAA,KACZ,IAAA,CAAK,KAAK,EAAA,EAAI;AAAA,MACV,IAAA,EAAM,4BAAA;AAAA,MACN,WAAW,GAAA,CAAI,SAAA;AAAA,MACf,eAAe,GAAA,CAAI,aAAA;AAAA,MACnB;AAAA,KACH,CAAA;AAEL,IAAA,MAAM,OAAO,MAAM,IAAA,CAAK,qBAAA,CAAsB,cAAA,CAAe,IAAI,aAAa,CAAA;AAC9E,IAAA,IAAI,CAAC,IAAA,EAAM,OAAO,MAAA,CAAO,qBAAqB,CAAA;AAE9C,IAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,eAAA,CAAgB,KAAK,SAAS,CAAA;AAC5D,IAAA,IAAI,CAAC,UAAA,EAAY,OAAO,MAAA,CAAO,mBAAmB,CAAA;AAElD,IAAA,MAAM,mBAAmB,CAAA,EAAG,GAAA,CAAI,aAAa,CAAA,OAAA,EAAU,IAAA,CAAK,KAAK,CAAA,CAAA;AACjE,IAAA,MAAM,oBAAA,GAAuB,GAAG,gBAAgB,CAAA,EAAA,CAAA;AAEhD,IAAA,IAAA,CAAK,KAAK,EAAA,EAAI;AAAA,MACV,IAAA,EAAM,4BAAA;AAAA,MACN,WAAW,GAAA,CAAI,SAAA;AAAA,MACf,eAAe,GAAA,CAAI,aAAA;AAAA,MACnB,gBAAA;AAAA,MACA,uBAAA,EAAyB;AAAA,KAC5B,CAAA;AAED,IAAA,MAAM,OAAA,GAAU,KAAK,SAAA,CAAU;AAAA,MAC3B,IAAA,EAAM,wBAAA;AAAA,MACN,WAAW,GAAA,CAAI,SAAA;AAAA,MACf,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,aAAA,EAAe,gBAAA;AAAA,MACf,oBAAA;AAAA,MACA,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,SAAS,GAAA,CAAI;AAAA,KAChB,CAAA;AACD,IAAA,KAAA,MAAW,SAAS,UAAA,EAAY;AAC5B,MAAA,IAAI;AAAE,QAAA,KAAA,CAAM,KAAK,OAAO,CAAA;AAAA,MAAG,CAAA,CAAA,MAAQ;AAAA,MAAC;AAAA,IACxC;AAAA,EACJ;AAAA;AAAA,EAIQ,UAAU,OAAA,EAAuB;AACrC,IAAA,IAAA,CAAK,YAAA,CAAa,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AAAA,EAC7C;AAAA,EAEQ,aAAa,OAAA,EAAuB;AACxC,IAAA,KAAA,MAAW,MAAA,IAAU,KAAK,SAAA,EAAW;AACjC,MAAA,IAAI;AACA,QAAA,IAAK,MAAA,CAAe,UAAA,KAAe,CAAA,EAAG,MAAA,CAAO,KAAK,OAAO,CAAA;AAAA,MAC7D,CAAA,CAAA,MAAQ;AAAA,MAAC;AAAA,IACb;AAAA,EACJ;AAAA,EAEQ,IAAA,CAAK,IAAe,OAAA,EAAuB;AAC/C,IAAA,IAAI;AACA,MAAA,IAAK,EAAA,CAAW,eAAe,CAAA,EAAG,EAAA,CAAG,KAAK,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AAAA,IACrE,CAAA,CAAA,MAAQ;AAAA,IAAC;AAAA,EACb;AAAA;AAAA,EAIQ,YAAA,GAAqB;AACzB,IAAA,IAAI,KAAK,YAAA,EAAc;AACvB,IAAA,IAAA,CAAK,YAAA,GAAe,YAAY,MAAM;AAClC,MAAA,MAAM,GAAA,GAAM,QAAQ,WAAA,EAAY;AAChC,MAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,MAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,QAAA,CAAS,IAAA,CAAK,YAAY,CAAA;AAC9C,MAAA,MAAM,OAAA,GAAA,CAAW,GAAA,GAAM,IAAA,CAAK,WAAA,IAAe,GAAA;AAC3C,MAAA,MAAM,UAAA,GAAa,UAAU,CAAA,GAAA,CAAM,GAAA,CAAI,OAAO,GAAA,CAAI,MAAA,IAAU,UAAW,GAAA,GAAM,CAAA;AAC7E,MAAA,IAAA,CAAK,YAAA,GAAe,QAAQ,QAAA,EAAS;AACrC,MAAA,IAAA,CAAK,WAAA,GAAc,GAAA;AAEnB,MAAA,IAAA,CAAK,SAAA,CAAU;AAAA,QACX,IAAA,EAAM,gBAAA;AAAA,QACN,SAAA,EAAW,GAAA;AAAA,QACX,KAAA,EAAO,GAAA,CAAI,GAAA,GAAM,IAAA,GAAO,IAAA;AAAA,QACxB,UAAA,EAAY,GAAA,CAAI,QAAA,GAAW,IAAA,GAAO,IAAA;AAAA,QAClC,WAAA,EAAa,GAAA,CAAI,SAAA,GAAY,IAAA,GAAO,IAAA;AAAA,QACpC;AAAA,OACH,CAAA;AAAA,IACL,GAAG,GAAI,CAAA;AAAA,EACX;AAAA,EAEQ,WAAA,GAAoB;AACxB,IAAA,IAAI,KAAK,YAAA,EAAc;AACnB,MAAA,aAAA,CAAc,KAAK,YAAY,CAAA;AAC/B,MAAA,IAAA,CAAK,YAAA,GAAe,MAAA;AAAA,IACxB;AAAA,EACJ;AACJ","file":"index.js","sourcesContent":["import {\n CNSDTOApp,\n CNSDTOAppBatchMessageSchema,\n CNSDTOClientMessageSchema,\n CNSDTOTopologyMessage,\n} from '@cnstra/devtools-dto';\nimport type { ICNSStimulationRepository } from '@cnstra/persist';\nimport { CNSInMemoryStimulationRepository } from '@cnstra/persist';\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 // 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 durable 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 = 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 '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.query':\n await this.handleRunsQuery(ws, msg);\n break;\n }\n }\n\n /**\n * Name-based observability (Phase 2b-3): project the durable\n * `ICNSStimulationRepository` into the run/attempt/task roster the panel\n * renders. This replaces the legacy id-based `stimulations.query`/`hops.query`.\n */\n private async handleRunsQuery(ws: WebSocket, msg: any): Promise<void> {\n const stims = await this.stimulationRepository.listStimulations(\n msg.scopeName ? { scopeName: msg.scopeName } : undefined\n );\n const runs = [];\n for (const stim of stims) {\n const attempts = await this.stimulationRepository.getAttempts(\n stim.stimulationId\n );\n const attemptViews = [];\n for (const a of attempts) {\n const tasks = await this.stimulationRepository.getTasks(\n a.stimulationAttemptId\n );\n attemptViews.push({\n attemptNumber: a.attemptNumber,\n status: a.status,\n hopCount: a.hopCount,\n startedAt: a.startedAt,\n completedAt: a.completedAt ?? null,\n tasks: tasks.map(t => ({\n index: t.index,\n neuronName: t.neuronName,\n dendriteCollateralName: t.dendriteCollateralName,\n status: t.status,\n output: t.output\n ? {\n collateralName: t.output.collateralName,\n payload: t.output.payload,\n }\n : null,\n error: t.error ?? null,\n startedAt: t.startedAt,\n duration: t.duration ?? null,\n })),\n });\n }\n runs.push({\n runId: stim.stimulationId,\n status: stim.status,\n scopeName: stim.scopeName,\n entry: {\n collateralName: stim.entry.collateralName,\n payload: stim.entry.payload,\n },\n frontier: stim.progress.tasks.map(t => t.neuronName),\n attempts: attemptViews,\n });\n }\n this.send(ws, {\n type: 'cns.stimulations.result',\n requestId: msg.requestId,\n runs,\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 // ── Name-based durable 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 durable 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: 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 // ─── 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 // ─── Durable 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 durable 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 = `${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 = `${msg.stimulationId}-clone-${Date.now()}`;\n const stimulationAttemptId = `${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"]}
|
|
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,4 @@
|
|
|
1
|
-
import type { CNSPersistOptionsRegistry } from '@cnstra/persist';
|
|
1
|
+
import type { CNSPersistOptionsRegistry, ICNSStimulationRepository } from '@cnstra/persist';
|
|
2
2
|
import type { ICNS, TCNSNeuron } from '@cnstra/types';
|
|
3
3
|
export type StartDevToolsOptions = {
|
|
4
4
|
/** Port for HTTP (UI) and WebSocket. Default: 3141 */
|
|
@@ -8,6 +8,15 @@ export type StartDevToolsOptions = {
|
|
|
8
8
|
/** App ID shown in DevTools. Default: 'app' */
|
|
9
9
|
appId?: string;
|
|
10
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;
|
|
11
20
|
};
|
|
12
21
|
export type DevToolsHandle = {
|
|
13
22
|
url: string;
|
|
@@ -24,5 +33,5 @@ export type DevToolsHandle = {
|
|
|
24
33
|
* await startDevTools(cns, registry);
|
|
25
34
|
* // → 🧠 CNStra DevTools: http://localhost:3141
|
|
26
35
|
*/
|
|
27
|
-
export declare function startDevTools(cns: ICNS<TCNSNeuron<any
|
|
36
|
+
export declare function startDevTools(cns: ICNS<TCNSNeuron<any>>, registry: CNSPersistOptionsRegistry, options?: StartDevToolsOptions): Promise<DevToolsHandle>;
|
|
28
37
|
//# sourceMappingURL=start.d.ts.map
|
package/dist/start.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../src/start.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,
|
|
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,78 +1,79 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"peerDependenciesMeta": {
|
|
63
|
-
"@cnstra/devtools-panel-ui": {
|
|
64
|
-
"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"
|
|
65
62
|
},
|
|
66
|
-
"
|
|
67
|
-
|
|
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
|
+
}
|
|
68
73
|
},
|
|
69
|
-
"
|
|
70
|
-
|
|
74
|
+
"author": "Andrei Baikov <abaikov23@gmail.com>",
|
|
75
|
+
"license": "MIT",
|
|
76
|
+
"publishConfig": {
|
|
77
|
+
"access": "public"
|
|
71
78
|
}
|
|
72
|
-
},
|
|
73
|
-
"author": "Andrei Baikov <abaikov23@gmail.com>",
|
|
74
|
-
"license": "MIT",
|
|
75
|
-
"publishConfig": {
|
|
76
|
-
"access": "public"
|
|
77
|
-
}
|
|
78
79
|
}
|