@nodetool-ai/websocket 0.7.0-rc.11

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.
Files changed (261) hide show
  1. package/dist/agent/agent-runtime.d.ts +47 -0
  2. package/dist/agent/agent-runtime.d.ts.map +1 -0
  3. package/dist/agent/agent-runtime.js +577 -0
  4. package/dist/agent/agent-runtime.js.map +1 -0
  5. package/dist/agent/codex-agent.d.ts +41 -0
  6. package/dist/agent/codex-agent.d.ts.map +1 -0
  7. package/dist/agent/codex-agent.js +334 -0
  8. package/dist/agent/codex-agent.js.map +1 -0
  9. package/dist/agent/index.d.ts +6 -0
  10. package/dist/agent/index.d.ts.map +1 -0
  11. package/dist/agent/index.js +4 -0
  12. package/dist/agent/index.js.map +1 -0
  13. package/dist/agent/llm-agent.d.ts +51 -0
  14. package/dist/agent/llm-agent.d.ts.map +1 -0
  15. package/dist/agent/llm-agent.js +526 -0
  16. package/dist/agent/llm-agent.js.map +1 -0
  17. package/dist/agent/mcp-tool-server.d.ts +33 -0
  18. package/dist/agent/mcp-tool-server.d.ts.map +1 -0
  19. package/dist/agent/mcp-tool-server.js +292 -0
  20. package/dist/agent/mcp-tool-server.js.map +1 -0
  21. package/dist/agent/opencode-agent.d.ts +32 -0
  22. package/dist/agent/opencode-agent.d.ts.map +1 -0
  23. package/dist/agent/opencode-agent.js +332 -0
  24. package/dist/agent/opencode-agent.js.map +1 -0
  25. package/dist/agent/pi-agent.d.ts +42 -0
  26. package/dist/agent/pi-agent.d.ts.map +1 -0
  27. package/dist/agent/pi-agent.js +429 -0
  28. package/dist/agent/pi-agent.js.map +1 -0
  29. package/dist/agent/sdk-provider.d.ts +46 -0
  30. package/dist/agent/sdk-provider.d.ts.map +1 -0
  31. package/dist/agent/sdk-provider.js +50 -0
  32. package/dist/agent/sdk-provider.js.map +1 -0
  33. package/dist/agent/socket-route.d.ts +11 -0
  34. package/dist/agent/socket-route.d.ts.map +1 -0
  35. package/dist/agent/socket-route.js +262 -0
  36. package/dist/agent/socket-route.js.map +1 -0
  37. package/dist/agent/transport.d.ts +39 -0
  38. package/dist/agent/transport.d.ts.map +1 -0
  39. package/dist/agent/transport.js +17 -0
  40. package/dist/agent/transport.js.map +1 -0
  41. package/dist/agent/types.d.ts +11 -0
  42. package/dist/agent/types.d.ts.map +1 -0
  43. package/dist/agent/types.js +10 -0
  44. package/dist/agent/types.js.map +1 -0
  45. package/dist/collection-api.d.ts +14 -0
  46. package/dist/collection-api.d.ts.map +1 -0
  47. package/dist/collection-api.js +75 -0
  48. package/dist/collection-api.js.map +1 -0
  49. package/dist/error-codes.d.ts +3 -0
  50. package/dist/error-codes.d.ts.map +1 -0
  51. package/dist/error-codes.js +2 -0
  52. package/dist/error-codes.js.map +1 -0
  53. package/dist/file-api.d.ts +12 -0
  54. package/dist/file-api.d.ts.map +1 -0
  55. package/dist/file-api.js +76 -0
  56. package/dist/file-api.js.map +1 -0
  57. package/dist/http-api.d.ts +94 -0
  58. package/dist/http-api.d.ts.map +1 -0
  59. package/dist/http-api.js +1472 -0
  60. package/dist/http-api.js.map +1 -0
  61. package/dist/index.d.ts +6 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +6 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/lib/asset-paths.d.ts +10 -0
  66. package/dist/lib/asset-paths.d.ts.map +1 -0
  67. package/dist/lib/asset-paths.js +45 -0
  68. package/dist/lib/asset-paths.js.map +1 -0
  69. package/dist/lib/bridge.d.ts +8 -0
  70. package/dist/lib/bridge.d.ts.map +1 -0
  71. package/dist/lib/bridge.js +70 -0
  72. package/dist/lib/bridge.js.map +1 -0
  73. package/dist/mcp-server.d.ts +32 -0
  74. package/dist/mcp-server.d.ts.map +1 -0
  75. package/dist/mcp-server.js +842 -0
  76. package/dist/mcp-server.js.map +1 -0
  77. package/dist/models-api.d.ts +12 -0
  78. package/dist/models-api.d.ts.map +1 -0
  79. package/dist/models-api.js +730 -0
  80. package/dist/models-api.js.map +1 -0
  81. package/dist/oauth-api.d.ts +28 -0
  82. package/dist/oauth-api.d.ts.map +1 -0
  83. package/dist/oauth-api.js +681 -0
  84. package/dist/oauth-api.js.map +1 -0
  85. package/dist/openai-api.d.ts +63 -0
  86. package/dist/openai-api.d.ts.map +1 -0
  87. package/dist/openai-api.js +359 -0
  88. package/dist/openai-api.js.map +1 -0
  89. package/dist/plugins/websocket.d.ts +14 -0
  90. package/dist/plugins/websocket.d.ts.map +1 -0
  91. package/dist/plugins/websocket.js +431 -0
  92. package/dist/plugins/websocket.js.map +1 -0
  93. package/dist/resolve-media-urls.d.ts +19 -0
  94. package/dist/resolve-media-urls.d.ts.map +1 -0
  95. package/dist/resolve-media-urls.js +109 -0
  96. package/dist/resolve-media-urls.js.map +1 -0
  97. package/dist/routes/assets.d.ts +22 -0
  98. package/dist/routes/assets.d.ts.map +1 -0
  99. package/dist/routes/assets.js +168 -0
  100. package/dist/routes/assets.js.map +1 -0
  101. package/dist/routes/collections.d.ts +16 -0
  102. package/dist/routes/collections.d.ts.map +1 -0
  103. package/dist/routes/collections.js +32 -0
  104. package/dist/routes/collections.js.map +1 -0
  105. package/dist/routes/files.d.ts +12 -0
  106. package/dist/routes/files.d.ts.map +1 -0
  107. package/dist/routes/files.js +11 -0
  108. package/dist/routes/files.js.map +1 -0
  109. package/dist/routes/health.d.ts +4 -0
  110. package/dist/routes/health.d.ts.map +1 -0
  111. package/dist/routes/health.js +41 -0
  112. package/dist/routes/health.js.map +1 -0
  113. package/dist/routes/jobs.d.ts +16 -0
  114. package/dist/routes/jobs.d.ts.map +1 -0
  115. package/dist/routes/jobs.js +25 -0
  116. package/dist/routes/jobs.js.map +1 -0
  117. package/dist/routes/nodes.d.ts +8 -0
  118. package/dist/routes/nodes.d.ts.map +1 -0
  119. package/dist/routes/nodes.js +124 -0
  120. package/dist/routes/nodes.js.map +1 -0
  121. package/dist/routes/oauth.d.ts +8 -0
  122. package/dist/routes/oauth.d.ts.map +1 -0
  123. package/dist/routes/oauth.js +19 -0
  124. package/dist/routes/oauth.js.map +1 -0
  125. package/dist/routes/openai.d.ts +8 -0
  126. package/dist/routes/openai.d.ts.map +1 -0
  127. package/dist/routes/openai.js +20 -0
  128. package/dist/routes/openai.js.map +1 -0
  129. package/dist/routes/storage.d.ts +12 -0
  130. package/dist/routes/storage.d.ts.map +1 -0
  131. package/dist/routes/storage.js +13 -0
  132. package/dist/routes/storage.js.map +1 -0
  133. package/dist/routes/workflows.d.ts +18 -0
  134. package/dist/routes/workflows.d.ts.map +1 -0
  135. package/dist/routes/workflows.js +25 -0
  136. package/dist/routes/workflows.js.map +1 -0
  137. package/dist/routes/workspace.d.ts +15 -0
  138. package/dist/routes/workspace.d.ts.map +1 -0
  139. package/dist/routes/workspace.js +24 -0
  140. package/dist/routes/workspace.js.map +1 -0
  141. package/dist/screenshot-server.d.ts +21 -0
  142. package/dist/screenshot-server.d.ts.map +1 -0
  143. package/dist/screenshot-server.js +222 -0
  144. package/dist/screenshot-server.js.map +1 -0
  145. package/dist/server.d.ts +10 -0
  146. package/dist/server.d.ts.map +1 -0
  147. package/dist/server.js +783 -0
  148. package/dist/server.js.map +1 -0
  149. package/dist/settings-registry.d.ts +29 -0
  150. package/dist/settings-registry.d.ts.map +1 -0
  151. package/dist/settings-registry.js +117 -0
  152. package/dist/settings-registry.js.map +1 -0
  153. package/dist/storage-api.d.ts +5 -0
  154. package/dist/storage-api.d.ts.map +1 -0
  155. package/dist/storage-api.js +221 -0
  156. package/dist/storage-api.js.map +1 -0
  157. package/dist/test-ui-server.d.ts +19 -0
  158. package/dist/test-ui-server.d.ts.map +1 -0
  159. package/dist/test-ui-server.js +1256 -0
  160. package/dist/test-ui-server.js.map +1 -0
  161. package/dist/trpc/context.d.ts +21 -0
  162. package/dist/trpc/context.d.ts.map +1 -0
  163. package/dist/trpc/context.js +10 -0
  164. package/dist/trpc/context.js.map +1 -0
  165. package/dist/trpc/error-formatter.d.ts +18 -0
  166. package/dist/trpc/error-formatter.d.ts.map +1 -0
  167. package/dist/trpc/error-formatter.js +41 -0
  168. package/dist/trpc/error-formatter.js.map +1 -0
  169. package/dist/trpc/index.d.ts +18 -0
  170. package/dist/trpc/index.d.ts.map +1 -0
  171. package/dist/trpc/index.js +13 -0
  172. package/dist/trpc/index.js.map +1 -0
  173. package/dist/trpc/middleware.d.ts +8 -0
  174. package/dist/trpc/middleware.d.ts.map +1 -0
  175. package/dist/trpc/middleware.js +12 -0
  176. package/dist/trpc/middleware.js.map +1 -0
  177. package/dist/trpc/router.d.ts +2543 -0
  178. package/dist/trpc/router.d.ts.map +1 -0
  179. package/dist/trpc/router.js +43 -0
  180. package/dist/trpc/router.js.map +1 -0
  181. package/dist/trpc/routers/assets.d.ts +229 -0
  182. package/dist/trpc/routers/assets.d.ts.map +1 -0
  183. package/dist/trpc/routers/assets.js +263 -0
  184. package/dist/trpc/routers/assets.js.map +1 -0
  185. package/dist/trpc/routers/collections.d.ts +88 -0
  186. package/dist/trpc/routers/collections.d.ts.map +1 -0
  187. package/dist/trpc/routers/collections.js +166 -0
  188. package/dist/trpc/routers/collections.js.map +1 -0
  189. package/dist/trpc/routers/costs.d.ts +129 -0
  190. package/dist/trpc/routers/costs.d.ts.map +1 -0
  191. package/dist/trpc/routers/costs.js +95 -0
  192. package/dist/trpc/routers/costs.js.map +1 -0
  193. package/dist/trpc/routers/files.d.ts +34 -0
  194. package/dist/trpc/routers/files.d.ts.map +1 -0
  195. package/dist/trpc/routers/files.js +92 -0
  196. package/dist/trpc/routers/files.js.map +1 -0
  197. package/dist/trpc/routers/jobs.d.ts +92 -0
  198. package/dist/trpc/routers/jobs.d.ts.map +1 -0
  199. package/dist/trpc/routers/jobs.js +100 -0
  200. package/dist/trpc/routers/jobs.js.map +1 -0
  201. package/dist/trpc/routers/mcp-config.d.ts +69 -0
  202. package/dist/trpc/routers/mcp-config.d.ts.map +1 -0
  203. package/dist/trpc/routers/mcp-config.js +290 -0
  204. package/dist/trpc/routers/mcp-config.js.map +1 -0
  205. package/dist/trpc/routers/messages.d.ts +121 -0
  206. package/dist/trpc/routers/messages.d.ts.map +1 -0
  207. package/dist/trpc/routers/messages.js +113 -0
  208. package/dist/trpc/routers/messages.js.map +1 -0
  209. package/dist/trpc/routers/models.d.ts +748 -0
  210. package/dist/trpc/routers/models.d.ts.map +1 -0
  211. package/dist/trpc/routers/models.js +1107 -0
  212. package/dist/trpc/routers/models.js.map +1 -0
  213. package/dist/trpc/routers/nodes.d.ts +48 -0
  214. package/dist/trpc/routers/nodes.d.ts.map +1 -0
  215. package/dist/trpc/routers/nodes.js +57 -0
  216. package/dist/trpc/routers/nodes.js.map +1 -0
  217. package/dist/trpc/routers/sandboxes.d.ts +64 -0
  218. package/dist/trpc/routers/sandboxes.d.ts.map +1 -0
  219. package/dist/trpc/routers/sandboxes.js +327 -0
  220. package/dist/trpc/routers/sandboxes.js.map +1 -0
  221. package/dist/trpc/routers/settings.d.ts +113 -0
  222. package/dist/trpc/routers/settings.d.ts.map +1 -0
  223. package/dist/trpc/routers/settings.js +193 -0
  224. package/dist/trpc/routers/settings.js.map +1 -0
  225. package/dist/trpc/routers/skills.d.ts +41 -0
  226. package/dist/trpc/routers/skills.d.ts.map +1 -0
  227. package/dist/trpc/routers/skills.js +194 -0
  228. package/dist/trpc/routers/skills.js.map +1 -0
  229. package/dist/trpc/routers/storage.d.ts +44 -0
  230. package/dist/trpc/routers/storage.d.ts.map +1 -0
  231. package/dist/trpc/routers/storage.js +153 -0
  232. package/dist/trpc/routers/storage.js.map +1 -0
  233. package/dist/trpc/routers/threads.d.ts +95 -0
  234. package/dist/trpc/routers/threads.d.ts.map +1 -0
  235. package/dist/trpc/routers/threads.js +150 -0
  236. package/dist/trpc/routers/threads.js.map +1 -0
  237. package/dist/trpc/routers/users.d.ts +79 -0
  238. package/dist/trpc/routers/users.d.ts.map +1 -0
  239. package/dist/trpc/routers/users.js +125 -0
  240. package/dist/trpc/routers/users.js.map +1 -0
  241. package/dist/trpc/routers/workflows.d.ts +681 -0
  242. package/dist/trpc/routers/workflows.d.ts.map +1 -0
  243. package/dist/trpc/routers/workflows.js +725 -0
  244. package/dist/trpc/routers/workflows.js.map +1 -0
  245. package/dist/trpc/routers/workspace.d.ts +127 -0
  246. package/dist/trpc/routers/workspace.d.ts.map +1 -0
  247. package/dist/trpc/routers/workspace.js +203 -0
  248. package/dist/trpc/routers/workspace.js.map +1 -0
  249. package/dist/unified-websocket-runner.d.ts +280 -0
  250. package/dist/unified-websocket-runner.d.ts.map +1 -0
  251. package/dist/unified-websocket-runner.js +3840 -0
  252. package/dist/unified-websocket-runner.js.map +1 -0
  253. package/dist/workspace-api.d.ts +14 -0
  254. package/dist/workspace-api.d.ts.map +1 -0
  255. package/dist/workspace-api.js +104 -0
  256. package/dist/workspace-api.js.map +1 -0
  257. package/dist/ws-adapter.d.ts +25 -0
  258. package/dist/ws-adapter.d.ts.map +1 -0
  259. package/dist/ws-adapter.js +63 -0
  260. package/dist/ws-adapter.js.map +1 -0
  261. package/package.json +92 -0
package/dist/server.js ADDED
@@ -0,0 +1,783 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * NodeTool WebSocket + HTTP server entry point — Fastify edition.
4
+ *
5
+ * TLS: set TLS_CERT and TLS_KEY to paths of cert.pem / key.pem.
6
+ * An HTTP→HTTPS redirect server starts on port 80 when TLS is active.
7
+ * Connect the CLI with: npm run chat -- --url ws://localhost:7777/ws
8
+ */
9
+ import { join, resolve, dirname } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
12
+ import { spawnSync } from "node:child_process";
13
+ import { createServer as createHttpServer } from "node:http";
14
+ import crypto from "node:crypto";
15
+ import { createLogger, getDefaultDbPath } from "@nodetool-ai/config";
16
+ import { NodeRegistry } from "@nodetool-ai/node-sdk";
17
+ import { registerBaseNodes } from "@nodetool-ai/base-nodes";
18
+ import { registerElevenLabsNodes } from "@nodetool-ai/elevenlabs-nodes";
19
+ import { registerTransformersJsNodes } from "@nodetool-ai/transformers-js-nodes";
20
+ import { registerTransformersJsProvider } from "@nodetool-ai/transformers-js-provider";
21
+ import { registerFalNodes } from "@nodetool-ai/fal-nodes";
22
+ import { registerKieNodes } from "@nodetool-ai/kie-nodes";
23
+ import { registerReplicateNodes } from "@nodetool-ai/replicate-nodes";
24
+ import { setSecretResolver, PythonStdioBridge } from "@nodetool-ai/runtime";
25
+ import { initMasterKey } from "@nodetool-ai/security";
26
+ import { initDb, getSecret } from "@nodetool-ai/models";
27
+ import { GoogleSearchTool, GoogleNewsTool, GoogleImagesTool, GoogleGroundedSearchTool, GoogleImageGenerationTool, OpenAIWebSearchTool, OpenAIImageGenerationTool, OpenAITextToSpeechTool, BrowserTool, ScreenshotTool, HttpRequestTool, CalculatorTool, RunCodeTool, StatisticsTool, GeometryTool, ConversionTool, SearchEmailTool, ArchiveEmailTool, AddLabelToEmailTool, DataForSEOSearchTool, DataForSEONewsTool, DataForSEOImagesTool } from "@nodetool-ai/agents";
28
+ import { registerPythonProviders } from "./models-api.js";
29
+ import { handleMcpHttpRequest } from "./mcp-server.js";
30
+ import Fastify from "fastify";
31
+ import fastifyWebSocket from "@fastify/websocket";
32
+ import fastifyCors from "@fastify/cors";
33
+ import fastifyStatic from "@fastify/static";
34
+ import { encode } from "@msgpack/msgpack";
35
+ import { SupabaseAuthProvider, LocalAuthProvider } from "@nodetool-ai/auth";
36
+ import { fastifyTRPCPlugin } from "@trpc/server/adapters/fastify";
37
+ import { appRouter } from "./trpc/router.js";
38
+ import { createContextFactory } from "./trpc/context.js";
39
+ import websocketPlugin from "./plugins/websocket.js";
40
+ import healthRoute from "./routes/health.js";
41
+ import assetsRoutes from "./routes/assets.js";
42
+ import workflowsRoutes from "./routes/workflows.js";
43
+ import jobsRoutes from "./routes/jobs.js";
44
+ import nodesRoutes from "./routes/nodes.js";
45
+ import storageRoutes from "./routes/storage.js";
46
+ import openaiRoutes from "./routes/openai.js";
47
+ import oauthRoutes from "./routes/oauth.js";
48
+ import workspaceRoutes from "./routes/workspace.js";
49
+ import filesRoutes from "./routes/files.js";
50
+ import collectionsRoutes from "./routes/collections.js";
51
+ import { agentSocketRoute, getAgentRuntime } from "./agent/index.js";
52
+ // @llamaindex/liteparse bundles a webpack pdf.js whose `isNodeJS` heuristic
53
+ // resolves to false inside Electron utilityProcess (process.type === "utility"),
54
+ // causing it to take browser-only code paths that reference the global `document`
55
+ // and throw `ReferenceError: document is not defined` on parse(). Reporting
56
+ // process.type as "browser" — the value Electron's main process uses — flips
57
+ // the heuristic back to Node mode so NodeBinaryDataFactory / fs-based asset
58
+ // loading is selected. No-op outside Electron.
59
+ {
60
+ const proc = process;
61
+ if (proc.versions?.electron && proc.type && proc.type !== "browser") {
62
+ try {
63
+ Object.defineProperty(process, "type", {
64
+ value: "browser",
65
+ writable: true,
66
+ configurable: true
67
+ });
68
+ }
69
+ catch {
70
+ // Property is non-configurable in some Electron builds; ignore.
71
+ }
72
+ }
73
+ }
74
+ const log = createLogger("nodetool.websocket.server");
75
+ const startupT0 = performance.now();
76
+ function startupMs() {
77
+ return `${(performance.now() - startupT0).toFixed(0)}ms`;
78
+ }
79
+ async function broadcastResourceChange(app, change) {
80
+ const message = encode({
81
+ type: "resource_change",
82
+ ...change
83
+ });
84
+ for (const client of app.websocketServer.clients) {
85
+ if (client.readyState === 1) {
86
+ client.send(message);
87
+ }
88
+ }
89
+ }
90
+ async function notifyPythonBridgeResourceChanges(app, pythonBridge) {
91
+ await broadcastResourceChange(app, {
92
+ event: "updated",
93
+ resource_type: "metadata",
94
+ resource: { id: "nodes", etag: String(Date.now()) }
95
+ });
96
+ const registered = await registerPythonProviders(pythonBridge);
97
+ if (registered.length > 0) {
98
+ log.info(`Registered Python providers: ${registered.join(", ")}`);
99
+ }
100
+ const discoveredProviders = await pythonBridge.listProviders();
101
+ const registeredProviders = new Set(registered);
102
+ for (const provider of discoveredProviders) {
103
+ await broadcastResourceChange(app, {
104
+ event: registeredProviders.has(provider.id) ? "created" : "updated",
105
+ resource_type: "provider",
106
+ resource: {
107
+ id: provider.id,
108
+ provider: provider.id,
109
+ capabilities: provider.capabilities,
110
+ etag: String(Date.now())
111
+ }
112
+ });
113
+ await broadcastResourceChange(app, {
114
+ event: "updated",
115
+ resource_type: "model",
116
+ resource: {
117
+ id: provider.id,
118
+ provider: provider.id,
119
+ etag: String(Date.now())
120
+ }
121
+ });
122
+ }
123
+ }
124
+ // ---------------------------------------------------------------------------
125
+ // Database setup
126
+ // ---------------------------------------------------------------------------
127
+ const dbPath = getDefaultDbPath();
128
+ try {
129
+ mkdirSync(dirname(dbPath), { recursive: true });
130
+ initDb(dbPath);
131
+ log.info(`Database ready [${startupMs()}]`, { path: dbPath });
132
+ // Initialize master key from keychain before any secret access.
133
+ // This must happen before setSecretResolver so that getMasterKey() (sync)
134
+ // returns the keychain key rather than auto-generating a new one.
135
+ await initMasterKey();
136
+ setSecretResolver((key, userId) => getSecret(key, userId).then((v) => v ?? undefined));
137
+ }
138
+ catch (err) {
139
+ log.error("Database setup failed", err instanceof Error ? err : new Error(String(err)));
140
+ process.exit(1);
141
+ }
142
+ // ---------------------------------------------------------------------------
143
+ // Python pip metadata root detection
144
+ // ---------------------------------------------------------------------------
145
+ function detectPipMetadataRoots() {
146
+ const script = `
147
+ import json, pathlib, subprocess, sys
148
+ roots = set()
149
+ try:
150
+ # Discover all nodetool-* packages
151
+ list_proc = subprocess.run(
152
+ [sys.executable, "-m", "pip", "list", "--format=json"],
153
+ capture_output=True, text=True, check=False,
154
+ )
155
+ pkg_names = [
156
+ p["name"] for p in json.loads(list_proc.stdout or "[]")
157
+ if p["name"].startswith("nodetool-")
158
+ ] or ["nodetool-core", "nodetool-base"]
159
+ proc = subprocess.run(
160
+ [sys.executable, "-m", "pip", "show", "-f"] + pkg_names,
161
+ capture_output=True, text=True, check=False,
162
+ )
163
+ output = proc.stdout or ""
164
+ except Exception:
165
+ output = ""
166
+ location = None
167
+ in_files = False
168
+ for raw in output.splitlines():
169
+ line = raw.rstrip("\\n")
170
+ if line.startswith("Name: "):
171
+ location = None; in_files = False; continue
172
+ if line.startswith("Location: "):
173
+ location = line.split(":", 1)[1].strip(); continue
174
+ if line.startswith("Editable project location: "):
175
+ editable = line.split(":", 1)[1].strip()
176
+ if editable: roots.add(editable)
177
+ continue
178
+ if line.startswith("Files:"): in_files = True; continue
179
+ if line.startswith("---"):
180
+ location = None; in_files = False; continue
181
+ if not in_files or not location or not line.startswith(" "): continue
182
+ rel = line.strip().replace("\\\\", "/")
183
+ if "package_metadata" not in rel: continue
184
+ abs_path = (pathlib.Path(location) / rel).resolve()
185
+ metadata_dir = abs_path if abs_path.is_dir() else abs_path.parent
186
+ roots.add(str(metadata_dir))
187
+ print(json.dumps(sorted(roots)))
188
+ `;
189
+ for (const python of ["python3", "python"]) {
190
+ const proc = spawnSync(python, ["-c", script], {
191
+ encoding: "utf8",
192
+ stdio: ["ignore", "pipe", "ignore"]
193
+ });
194
+ if (proc.status !== 0 || !proc.stdout)
195
+ continue;
196
+ try {
197
+ const roots = JSON.parse(proc.stdout.trim());
198
+ if (Array.isArray(roots)) {
199
+ return roots.filter((p) => typeof p === "string" && p.length > 0 && existsSync(p));
200
+ }
201
+ }
202
+ catch {
203
+ // try next python executable
204
+ }
205
+ }
206
+ return [];
207
+ }
208
+ const metadataRoots = detectPipMetadataRoots();
209
+ // Also scan local TS node packages that have nodetool/package_metadata
210
+ const localPackagesDir = resolve(dirname(fileURLToPath(import.meta.url)), "../../");
211
+ if (existsSync(localPackagesDir)) {
212
+ try {
213
+ for (const entry of readdirSync(localPackagesDir, {
214
+ withFileTypes: true
215
+ })) {
216
+ if (!entry.isDirectory())
217
+ continue;
218
+ const metaDir = join(localPackagesDir, entry.name, "nodetool", "package_metadata");
219
+ if (existsSync(metaDir)) {
220
+ const resolved = resolve(localPackagesDir, entry.name);
221
+ if (!metadataRoots.includes(resolved)) {
222
+ metadataRoots.push(resolved);
223
+ }
224
+ }
225
+ }
226
+ }
227
+ catch {
228
+ // ignore scan errors
229
+ }
230
+ }
231
+ log.info(`Metadata roots detected [${startupMs()}]`, {
232
+ roots: metadataRoots
233
+ });
234
+ // ---------------------------------------------------------------------------
235
+ // Node registry
236
+ // ---------------------------------------------------------------------------
237
+ const registry = new NodeRegistry();
238
+ registry.loadPythonMetadata({ roots: metadataRoots, maxDepth: 8 });
239
+ log.info(`Python metadata loaded [${startupMs()}]`);
240
+ registerBaseNodes(registry);
241
+ registerElevenLabsNodes(registry);
242
+ registerTransformersJsNodes(registry);
243
+ registerFalNodes(registry);
244
+ registerKieNodes(registry);
245
+ registerReplicateNodes(registry);
246
+ registerTransformersJsProvider();
247
+ log.info(`Node registry ready [${startupMs()}]`);
248
+ // ---------------------------------------------------------------------------
249
+ // Python bridge
250
+ // ---------------------------------------------------------------------------
251
+ const pythonBridge = new PythonStdioBridge({
252
+ workerArgs: process.env["NODETOOL_WORKER_NAMESPACES"]
253
+ ? ["--namespaces", process.env["NODETOOL_WORKER_NAMESPACES"]]
254
+ : []
255
+ });
256
+ let pythonBridgeReady = false;
257
+ function logPythonBridgeDiagnostics(context) {
258
+ const loadErrors = pythonBridge.getLoadErrors?.() ?? [];
259
+ if (loadErrors.length === 0)
260
+ return;
261
+ log.warn(`Python bridge ${context} with ${loadErrors.length} load error(s)`);
262
+ for (const entry of loadErrors.slice(0, 10)) {
263
+ log.warn(`[python-worker][load-error] ${entry.module} (${entry.phase}): ${entry.error}`);
264
+ }
265
+ if (loadErrors.length > 10) {
266
+ log.warn(`[python-worker][load-error] ... ${loadErrors.length - 10} additional load error(s) omitted`);
267
+ }
268
+ }
269
+ pythonBridge.on("stderr", (msg) => {
270
+ for (const line of msg.split("\n")) {
271
+ if (line.trim())
272
+ log.debug(`[python-worker] ${line}`);
273
+ }
274
+ });
275
+ pythonBridge.on("exit", (code) => {
276
+ log.warn(`Python worker exited with code ${code}`);
277
+ pythonBridgeReady = false;
278
+ });
279
+ // ---------------------------------------------------------------------------
280
+ // Tool registry
281
+ // ---------------------------------------------------------------------------
282
+ const builtinToolClasses = [
283
+ GoogleSearchTool,
284
+ GoogleNewsTool,
285
+ GoogleImagesTool,
286
+ GoogleGroundedSearchTool,
287
+ GoogleImageGenerationTool,
288
+ OpenAIWebSearchTool,
289
+ OpenAIImageGenerationTool,
290
+ OpenAITextToSpeechTool,
291
+ BrowserTool,
292
+ ScreenshotTool,
293
+ HttpRequestTool,
294
+ CalculatorTool,
295
+ RunCodeTool,
296
+ StatisticsTool,
297
+ GeometryTool,
298
+ ConversionTool,
299
+ SearchEmailTool,
300
+ ArchiveEmailTool,
301
+ AddLabelToEmailTool,
302
+ DataForSEOSearchTool,
303
+ DataForSEONewsTool,
304
+ DataForSEOImagesTool
305
+ ];
306
+ // Lazy tool class map — defers instantiation until first access.
307
+ let _toolClassMap = null;
308
+ function getToolClassMap() {
309
+ if (!_toolClassMap) {
310
+ _toolClassMap = new Map();
311
+ for (const cls of builtinToolClasses) {
312
+ const instance = new cls();
313
+ _toolClassMap.set(instance.name, cls);
314
+ }
315
+ log.info(`Tool class map built (${_toolClassMap.size} tools) [${startupMs()}]`);
316
+ }
317
+ return _toolClassMap;
318
+ }
319
+ // Expose as a getter-backed object so existing code using toolClassMap works unchanged.
320
+ const toolClassMap = new Proxy(new Map(), {
321
+ get(_target, prop, _receiver) {
322
+ const map = getToolClassMap();
323
+ const value = map[prop];
324
+ if (typeof value === "function") {
325
+ return value.bind(map);
326
+ }
327
+ return value;
328
+ }
329
+ });
330
+ // ---------------------------------------------------------------------------
331
+ // TLS configuration
332
+ // ---------------------------------------------------------------------------
333
+ function findCert(envVar, filename) {
334
+ const fromEnv = process.env[envVar];
335
+ if (fromEnv && existsSync(fromEnv))
336
+ return fromEnv;
337
+ // Walk up from cwd looking for the cert file
338
+ let dir = resolve(process.cwd());
339
+ for (let i = 0; i < 5; i++) {
340
+ const candidate = join(dir, filename);
341
+ if (existsSync(candidate))
342
+ return candidate;
343
+ const parent = dirname(dir);
344
+ if (parent === dir)
345
+ break;
346
+ dir = parent;
347
+ }
348
+ return undefined;
349
+ }
350
+ const tlsCertPath = findCert("TLS_CERT", "cert.pem");
351
+ const tlsKeyPath = findCert("TLS_KEY", "key.pem");
352
+ const tlsEnabled = Boolean(tlsCertPath && tlsKeyPath);
353
+ let httpsOptions;
354
+ if (tlsEnabled && tlsCertPath && tlsKeyPath) {
355
+ httpsOptions = {
356
+ cert: readFileSync(tlsCertPath),
357
+ key: readFileSync(tlsKeyPath)
358
+ };
359
+ log.info("TLS enabled", { cert: tlsCertPath, key: tlsKeyPath });
360
+ }
361
+ // ---------------------------------------------------------------------------
362
+ // Host / port
363
+ // ---------------------------------------------------------------------------
364
+ const port = Number(process.env["PORT"] ?? 7777);
365
+ const isProduction = process.env["NODETOOL_ENV"] === "production";
366
+ // In production, bind to all interfaces unless HOST is explicitly set
367
+ const host = process.env["HOST"] ?? (isProduction ? "0.0.0.0" : "127.0.0.1");
368
+ // ---------------------------------------------------------------------------
369
+ // Fastify app
370
+ // ---------------------------------------------------------------------------
371
+ const app = Fastify({
372
+ ...(httpsOptions ? { https: httpsOptions } : {}),
373
+ trustProxy: true,
374
+ bodyLimit: 100 * 1024 * 1024, // 100 MB
375
+ logger: false,
376
+ // tRPC's httpBatchLink encodes all batched procedure names into a single
377
+ // URL path segment joined with commas (e.g. `/trpc/foo,bar,baz`). Fastify's
378
+ // default `maxParamLength` of 100 rejects larger batches with a 404 — bump
379
+ // it so batches up to ~50 procedures route correctly.
380
+ routerOptions: {
381
+ ignoreTrailingSlash: true,
382
+ maxParamLength: 2000
383
+ },
384
+ genReqId: (req) => {
385
+ return req.headers["x-request-id"] || crypto.randomUUID();
386
+ }
387
+ });
388
+ // ---------------------------------------------------------------------------
389
+ // Request ID correlation
390
+ // ---------------------------------------------------------------------------
391
+ app.addHook("onRequest", async (request) => {
392
+ request.log.info({ reqId: request.id, method: request.method, url: request.url }, "incoming request");
393
+ });
394
+ app.addHook("onSend", async (request, reply) => {
395
+ reply.header("X-Request-Id", request.id);
396
+ });
397
+ // ---------------------------------------------------------------------------
398
+ // Auth
399
+ // ---------------------------------------------------------------------------
400
+ const supabaseUrl = process.env["SUPABASE_URL"];
401
+ const supabaseKey = process.env["SUPABASE_KEY"];
402
+ const supabaseMode = Boolean(supabaseUrl && supabaseKey);
403
+ const supabaseProvider = supabaseMode
404
+ ? new SupabaseAuthProvider({
405
+ supabaseUrl: supabaseUrl,
406
+ supabaseKey: supabaseKey
407
+ })
408
+ : null;
409
+ const localProvider = supabaseProvider ? null : new LocalAuthProvider();
410
+ app.decorateRequest("userId", null);
411
+ app.addHook("onRequest", async (req, reply) => {
412
+ // Let CORS preflight through — the @fastify/cors plugin handles OPTIONS responses
413
+ if (req.method === "OPTIONS")
414
+ return;
415
+ // Public routes — no auth required
416
+ const pathname = req.url.split("?")[0];
417
+ if (pathname === "/health" ||
418
+ pathname === "/ready" ||
419
+ pathname.startsWith("/api/oauth/") ||
420
+ pathname === "/api/assets/packages" ||
421
+ pathname.startsWith("/api/assets/packages/") ||
422
+ pathname === "/api/nodes/metadata") {
423
+ return;
424
+ }
425
+ // Static frontend assets don't require auth (served by fastifyStatic)
426
+ if (hasStaticApp &&
427
+ req.method === "GET" &&
428
+ !pathname.startsWith("/api") &&
429
+ !pathname.startsWith("/ws") &&
430
+ !pathname.startsWith("/v1") &&
431
+ !pathname.startsWith("/trpc")) {
432
+ return;
433
+ }
434
+ // Extract token from the appropriate source
435
+ const isWs = req.headers["upgrade"]?.toLowerCase() === "websocket";
436
+ const searchParams = new URLSearchParams(req.url.split("?")[1] ?? "");
437
+ const provider = supabaseProvider ?? localProvider;
438
+ const token = isWs
439
+ ? provider.extractTokenFromWs(req.headers, searchParams)
440
+ : provider.extractTokenFromHeaders(req.headers);
441
+ if (supabaseMode) {
442
+ if (!token) {
443
+ reply.status(401).send({ error: "Unauthorized" });
444
+ return;
445
+ }
446
+ const result = await supabaseProvider.verifyToken(token);
447
+ if (!result.ok) {
448
+ reply.status(401).send({ error: result.error ?? "Unauthorized" });
449
+ return;
450
+ }
451
+ req.userId = result.userId ?? null;
452
+ return;
453
+ }
454
+ // Dev mode: localhost only
455
+ // Use req.socket.remoteAddress rather than req.ip because trustProxy: true
456
+ // makes req.ip reflect x-forwarded-for (spoofable).
457
+ const remoteAddr = req.socket.remoteAddress ?? "";
458
+ const isLocalhost = remoteAddr === "127.0.0.1" || remoteAddr === "::1";
459
+ if (!isLocalhost) {
460
+ reply.status(401).send({ error: "Remote access requires authentication" });
461
+ return;
462
+ }
463
+ req.userId = "1";
464
+ });
465
+ // CORS
466
+ await app.register(fastifyCors, { origin: true });
467
+ // WebSocket support
468
+ await app.register(fastifyWebSocket);
469
+ // Parse all request bodies as raw Buffer — the Web API handlers do their own parsing
470
+ app.removeAllContentTypeParsers();
471
+ app.addContentTypeParser("*", { parseAs: "buffer" }, (_req, body, done) => {
472
+ done(null, body);
473
+ });
474
+ // ---------------------------------------------------------------------------
475
+ // API options for HTTP route handlers
476
+ // ---------------------------------------------------------------------------
477
+ const apiOptions = { metadataRoots, registry };
478
+ const staticFolder = process.env["STATIC_FOLDER"];
479
+ const hasStaticApp = Boolean(staticFolder && existsSync(staticFolder));
480
+ // ---------------------------------------------------------------------------
481
+ // Register route plugins
482
+ // ---------------------------------------------------------------------------
483
+ const createContext = createContextFactory({
484
+ registry,
485
+ apiOptions,
486
+ pythonBridge,
487
+ getPythonBridgeReady: () => pythonBridgeReady
488
+ });
489
+ await app.register(fastifyTRPCPlugin, {
490
+ prefix: "/trpc",
491
+ trpcOptions: {
492
+ router: appRouter,
493
+ createContext,
494
+ onError({ path, error }) {
495
+ log.error(`tRPC error on ${path}`, error instanceof Error ? error : new Error(String(error)));
496
+ // Surface inner ZodError details (output validation failures) so callers
497
+ // can diagnose the offending fields instead of seeing only "Output
498
+ // validation failed".
499
+ const cause = error.cause;
500
+ if (cause && typeof cause === "object" && "issues" in cause) {
501
+ log.error(`tRPC error cause on ${path}: ${JSON.stringify(cause.issues)}`);
502
+ }
503
+ }
504
+ }
505
+ });
506
+ await app.register(websocketPlugin, {
507
+ registry,
508
+ pythonBridge,
509
+ getPythonBridgeReady: () => pythonBridgeReady,
510
+ ensurePythonBridge: async () => {
511
+ if (pythonBridgeReady)
512
+ return;
513
+ log.info(`Lazily starting Python bridge [${startupMs()}]`);
514
+ try {
515
+ await pythonBridge.ensureConnected();
516
+ }
517
+ catch (err) {
518
+ log.warn(`Python bridge lazy start failed [${startupMs()}]`, err instanceof Error ? err : new Error(String(err)));
519
+ throw err;
520
+ }
521
+ pythonBridgeReady = true;
522
+ log.info(`Python bridge lazy start completed [${startupMs()}]`);
523
+ const meta = pythonBridge.getNodeMetadata();
524
+ // Register Python bridge nodes — skip those already loaded from JSON metadata
525
+ let bridgeOnly = 0;
526
+ for (const nodeMeta of meta) {
527
+ if (!nodeMeta.node_type)
528
+ continue;
529
+ if (registry.getMetadata(nodeMeta.node_type))
530
+ continue;
531
+ bridgeOnly++;
532
+ registry.loadMetadata(nodeMeta.node_type, {
533
+ ...nodeMeta,
534
+ namespace: nodeMeta.node_type.split(".").slice(0, -1).join("."),
535
+ layout: "default",
536
+ recommended_models: [],
537
+ basic_fields: [],
538
+ required_settings: nodeMeta.required_settings ?? [],
539
+ is_dynamic: nodeMeta.is_dynamic ?? false,
540
+ is_streaming_output: nodeMeta.is_streaming_output ?? false,
541
+ expose_as_tool: false,
542
+ supports_dynamic_outputs: false
543
+ });
544
+ }
545
+ log.info(`Python bridge connected [${startupMs()}] — ${meta.length} Python nodes (${bridgeOnly} bridge-only, ${meta.length - bridgeOnly} from JSON)`);
546
+ pythonBridge
547
+ .getWorkerStatus?.()
548
+ ?.then((status) => {
549
+ log.info(`Python bridge status [${startupMs()}]`, {
550
+ protocol_version: status.protocol_version,
551
+ node_count: status.node_count,
552
+ provider_count: status.provider_count,
553
+ namespaces: status.namespaces,
554
+ transport: status.transport,
555
+ max_frame_size: status.max_frame_size,
556
+ load_error_count: status.load_errors.length
557
+ });
558
+ })
559
+ .catch((err) => {
560
+ log.warn(`Failed to fetch Python bridge status [${startupMs()}]`, err instanceof Error ? err : new Error(String(err)));
561
+ });
562
+ logPythonBridgeDiagnostics("connected");
563
+ notifyPythonBridgeResourceChanges(app, pythonBridge).catch((err) => {
564
+ log.warn("Failed to notify Python bridge resource changes", err instanceof Error ? err : new Error(String(err)));
565
+ });
566
+ },
567
+ toolClassMap
568
+ });
569
+ await app.register(healthRoute);
570
+ // All HTTP API routes receive apiOptions
571
+ const routeOpts = { apiOptions };
572
+ await app.register(assetsRoutes, routeOpts);
573
+ await app.register(workflowsRoutes, routeOpts);
574
+ await app.register(jobsRoutes, routeOpts);
575
+ await app.register(nodesRoutes, routeOpts);
576
+ await app.register(storageRoutes, routeOpts);
577
+ await app.register(openaiRoutes, routeOpts);
578
+ await app.register(oauthRoutes, routeOpts);
579
+ await app.register(workspaceRoutes, routeOpts);
580
+ await app.register(filesRoutes, routeOpts);
581
+ await app.register(collectionsRoutes, routeOpts);
582
+ // MCP endpoints are only available in local/dev mode — not in production.
583
+ // The configuration endpoints moved to the tRPC `mcpConfig` router; the
584
+ // `/mcp` proxy below is a bare MCP over-HTTP transport and stays on REST.
585
+ if (!isProduction) {
586
+ app.all("/mcp", async (req, reply) => {
587
+ const protocol = httpsOptions ? "https" : "http";
588
+ const url = `${protocol}://${req.headers.host ?? `127.0.0.1:${port}`}${req.url}`;
589
+ const headers = new Headers();
590
+ for (const [key, value] of Object.entries(req.headers)) {
591
+ if (Array.isArray(value)) {
592
+ for (const item of value) {
593
+ headers.append(key, item);
594
+ }
595
+ }
596
+ else if (typeof value === "string") {
597
+ headers.set(key, value);
598
+ }
599
+ }
600
+ const requestBody = req.method === "GET" || req.method === "DELETE"
601
+ ? undefined
602
+ : Buffer.isBuffer(req.body)
603
+ ? req.body
604
+ : typeof req.body === "string"
605
+ ? req.body
606
+ : req.body == null
607
+ ? Buffer.alloc(0)
608
+ : JSON.stringify(req.body);
609
+ const request = new Request(url, {
610
+ method: req.method,
611
+ headers,
612
+ body: requestBody,
613
+ duplex: "half"
614
+ });
615
+ const response = await handleMcpHttpRequest(request, { metadataRoots });
616
+ if (!response) {
617
+ reply.status(404).send({ error: "Not found" });
618
+ return;
619
+ }
620
+ reply.status(response.status);
621
+ response.headers.forEach((value, key) => {
622
+ reply.header(key, value);
623
+ });
624
+ const payload = Buffer.from(await response.arrayBuffer());
625
+ reply.send(payload);
626
+ });
627
+ }
628
+ await app.register(agentSocketRoute);
629
+ log.info(`Routes registered [${startupMs()}]`);
630
+ if (hasStaticApp && staticFolder) {
631
+ log.info("Serving static app", { root: staticFolder });
632
+ await app.register(fastifyStatic, {
633
+ root: staticFolder,
634
+ prefix: "/"
635
+ });
636
+ app.get("/", async (_req, reply) => {
637
+ return reply.sendFile("index.html");
638
+ });
639
+ app.get("/apps/index.html", async (_req, reply) => {
640
+ return reply.sendFile("index.html");
641
+ });
642
+ }
643
+ // Log all 404s so we can identify missing routes
644
+ app.setNotFoundHandler((req, reply) => {
645
+ const pathname = req.url.split("?")[0] ?? "/";
646
+ if (hasStaticApp &&
647
+ req.method === "GET" &&
648
+ !pathname.startsWith("/api") &&
649
+ !pathname.startsWith("/health") &&
650
+ !pathname.startsWith("/ws") &&
651
+ !pathname.startsWith("/v1") &&
652
+ !pathname.startsWith("/oauth") &&
653
+ !pathname.startsWith("/trpc") &&
654
+ !pathname.includes(".")) {
655
+ return reply.sendFile("index.html");
656
+ }
657
+ log.warn(`404 Not Found: ${req.method} ${req.url}`);
658
+ return reply.status(404).send({ detail: "Not found" });
659
+ });
660
+ // ---------------------------------------------------------------------------
661
+ // HTTP → HTTPS redirect server (when TLS is active)
662
+ // ---------------------------------------------------------------------------
663
+ if (tlsEnabled) {
664
+ const redirectPort = Number(process.env["REDIRECT_PORT"] ?? 80);
665
+ const redirectServer = createHttpServer((req, res) => {
666
+ const reqHost = req.headers.host?.split(":")[0] ?? "localhost";
667
+ const location = `https://${reqHost}:${port}${req.url ?? "/"}`;
668
+ res.statusCode = 301;
669
+ res.setHeader("Location", location);
670
+ res.end();
671
+ });
672
+ redirectServer.on("error", (err) => {
673
+ if (err.code === "EACCES") {
674
+ log.warn(`HTTP redirect server skipped — port ${redirectPort} requires elevated privileges`);
675
+ }
676
+ else {
677
+ log.error("HTTP redirect server error", err);
678
+ }
679
+ });
680
+ redirectServer.listen(redirectPort, "0.0.0.0", () => {
681
+ log.info(`HTTP → HTTPS redirect server listening on :${redirectPort}`);
682
+ });
683
+ }
684
+ // ---------------------------------------------------------------------------
685
+ // Start Fastify server immediately, Python bridge connects in background
686
+ // ---------------------------------------------------------------------------
687
+ const proto = tlsEnabled ? "https" : "http";
688
+ app.listen({ port, host }, (err) => {
689
+ if (err) {
690
+ log.error("Failed to start server", err);
691
+ process.exit(1);
692
+ }
693
+ log.info(`Server listening on ${proto}://${host}:${port} [${startupMs()}]`);
694
+ log.info(`WebSocket endpoint: ${tlsEnabled ? "wss" : "ws"}://${host}:${port}/ws`);
695
+ });
696
+ // ---------------------------------------------------------------------------
697
+ // Graceful shutdown — ensure child processes (Python worker, etc.) are killed
698
+ // ---------------------------------------------------------------------------
699
+ async function shutdown(signal) {
700
+ log.info(`${signal} received — shutting down`);
701
+ log.info("Closing Python bridge");
702
+ try {
703
+ getAgentRuntime().closeAllSessions();
704
+ }
705
+ catch {
706
+ // best-effort cleanup
707
+ }
708
+ pythonBridge.close();
709
+ try {
710
+ await app.close();
711
+ }
712
+ catch {
713
+ // ignore close errors
714
+ }
715
+ process.exit(0);
716
+ }
717
+ process.on("SIGINT", () => void shutdown("SIGINT"));
718
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
719
+ // On Windows, Ctrl+C in some terminals fires SIGBREAK instead of SIGINT
720
+ if (process.platform === "win32") {
721
+ process.on("SIGBREAK", () => void shutdown("SIGBREAK"));
722
+ }
723
+ // Start Python bridge eagerly if Python is installed.
724
+ if (pythonBridge.hasPython()) {
725
+ log.info(`Starting Python bridge eagerly [${startupMs()}]`);
726
+ pythonBridge
727
+ .ensureConnected()
728
+ .then(() => {
729
+ pythonBridgeReady = true;
730
+ log.info(`Python bridge eager start completed [${startupMs()}]`);
731
+ const meta = pythonBridge.getNodeMetadata();
732
+ let bridgeOnly = 0;
733
+ for (const nodeMeta of meta) {
734
+ if (!nodeMeta.node_type)
735
+ continue;
736
+ if (registry.getMetadata(nodeMeta.node_type))
737
+ continue;
738
+ bridgeOnly++;
739
+ registry.loadMetadata(nodeMeta.node_type, {
740
+ ...nodeMeta,
741
+ namespace: nodeMeta.node_type.split(".").slice(0, -1).join("."),
742
+ layout: "default",
743
+ recommended_models: [],
744
+ basic_fields: [],
745
+ required_settings: nodeMeta.required_settings ?? [],
746
+ is_dynamic: nodeMeta.is_dynamic ?? false,
747
+ is_streaming_output: nodeMeta.is_streaming_output ?? false,
748
+ expose_as_tool: false,
749
+ supports_dynamic_outputs: false
750
+ });
751
+ }
752
+ log.info(`Python bridge connected [${startupMs()}] — ${meta.length} Python nodes (${bridgeOnly} bridge-only, ${meta.length - bridgeOnly} from JSON)`);
753
+ pythonBridge
754
+ .getWorkerStatus?.()
755
+ ?.then((status) => {
756
+ log.info(`Python bridge status [${startupMs()}]`, {
757
+ protocol_version: status.protocol_version,
758
+ node_count: status.node_count,
759
+ provider_count: status.provider_count,
760
+ namespaces: status.namespaces,
761
+ transport: status.transport,
762
+ max_frame_size: status.max_frame_size,
763
+ load_error_count: status.load_errors.length
764
+ });
765
+ })
766
+ .catch((err) => {
767
+ log.warn(`Failed to fetch Python bridge status [${startupMs()}]`, err instanceof Error ? err : new Error(String(err)));
768
+ });
769
+ logPythonBridgeDiagnostics("connected");
770
+ notifyPythonBridgeResourceChanges(app, pythonBridge).catch((err) => {
771
+ log.warn("Failed to notify Python bridge resource changes", err instanceof Error ? err : new Error(String(err)));
772
+ });
773
+ })
774
+ .catch((err) => {
775
+ log.warn("Python bridge failed to start (Python nodes will not be available)", err instanceof Error ? err : new Error(String(err)));
776
+ });
777
+ }
778
+ else {
779
+ log.info(isProduction
780
+ ? "Python bridge disabled in production — Python nodes will not be available"
781
+ : "Python not found — Python nodes will not be available");
782
+ }
783
+ //# sourceMappingURL=server.js.map