synthos 0.8.0 → 0.10.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.
Files changed (368) hide show
  1. package/README.md +1 -1
  2. package/default-pages/application/page.html +42 -0
  3. package/default-pages/application/page.json +10 -0
  4. package/default-pages/elevenlabs_effects_studio/page.html +1363 -0
  5. package/default-pages/elevenlabs_effects_studio/page.json +11 -0
  6. package/default-pages/elevenlabs_voice_studio/page.html +801 -0
  7. package/default-pages/elevenlabs_voice_studio/page.json +11 -0
  8. package/default-pages/{json_tools.html → json_tools/page.html} +13 -11
  9. package/default-pages/json_tools/page.json +10 -0
  10. package/default-pages/my_notes/notes/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json +5 -0
  11. package/default-pages/my_notes/page.html +132 -0
  12. package/default-pages/{my_notes.json → my_notes/page.json} +2 -2
  13. package/default-pages/neon_asteroids/files/Ambient_Space.mp3 +0 -0
  14. package/default-pages/neon_asteroids/files/Ambient_Space2.mp3 +0 -0
  15. package/default-pages/neon_asteroids/files/Ambient_Space3.mp3 +0 -0
  16. package/default-pages/neon_asteroids/files/Asteroid_Explosion.mp3 +0 -0
  17. package/default-pages/neon_asteroids/files/Hyperspace_Jump.mp3 +0 -0
  18. package/default-pages/neon_asteroids/files/Laser_Fire.mp3 +0 -0
  19. package/default-pages/neon_asteroids/files/Menu_Navigate.mp3 +0 -0
  20. package/default-pages/neon_asteroids/files/Power_Up_Collect.mp3 +0 -0
  21. package/default-pages/neon_asteroids/files/Saucer_Alert.mp3 +0 -0
  22. package/default-pages/neon_asteroids/files/Ship_Thrust.mp3 +0 -0
  23. package/default-pages/neon_asteroids/files/effects.json +74 -0
  24. package/default-pages/neon_asteroids/page.html +1803 -0
  25. package/default-pages/{neon_asteroids.json → neon_asteroids/page.json} +3 -3
  26. package/default-pages/{oregon_trail.html → oregon_trail/page.html} +16 -30
  27. package/default-pages/{oregon_trail.json → oregon_trail/page.json} +2 -2
  28. package/default-pages/retro_game_starter/page.html +1308 -0
  29. package/default-pages/retro_game_starter/page.json +12 -0
  30. package/default-pages/{sidebar_page.html → sidebar_page/page.html} +12 -10
  31. package/default-pages/sidebar_page/page.json +10 -0
  32. package/default-pages/{solar_explorer.html → solar_explorer/page.html} +15 -12
  33. package/default-pages/{solar_explorer.json → solar_explorer/page.json} +2 -2
  34. package/default-pages/{solar_tutorial.html → solar_tutorial/page.html} +12 -10
  35. package/default-pages/solar_tutorial/page.json +10 -0
  36. package/default-pages/{two-panel_page.html → two-panel_page/page.html} +13 -11
  37. package/default-pages/two-panel_page/page.json +10 -0
  38. package/default-pages/{us_map.html → us_map/page.html} +193 -192
  39. package/default-pages/{us_map.json → us_map/page.json} +12 -12
  40. package/default-pages/{us_map_1850.html → us_map_1850/page.html} +326 -325
  41. package/default-pages/{us_map_1850.json → us_map_1850/page.json} +12 -12
  42. package/default-pages/{western_cities_1850.html → western_cities_1850/page.html} +527 -526
  43. package/default-pages/{western_cities_1850.json → western_cities_1850/page.json} +12 -12
  44. package/default-themes/aurora-dawn.json +19 -0
  45. package/default-themes/aurora-dawn.v3.css +198 -0
  46. package/default-themes/aurora-dusk.json +19 -0
  47. package/default-themes/aurora-dusk.v3.css +200 -0
  48. package/default-themes/cosmos-dawn.json +19 -0
  49. package/default-themes/cosmos-dawn.v3.css +198 -0
  50. package/default-themes/cosmos-dusk.json +19 -0
  51. package/default-themes/cosmos-dusk.v3.css +200 -0
  52. package/default-themes/high-contrast-dark.json +19 -0
  53. package/default-themes/high-contrast-dark.v3.css +200 -0
  54. package/default-themes/high-contrast-light.json +19 -0
  55. package/default-themes/high-contrast-light.v3.css +198 -0
  56. package/default-themes/nebula-dawn.v2.css +110 -0
  57. package/default-themes/nebula-dawn.v3.css +199 -0
  58. package/default-themes/nebula-dusk.v2.css +104 -0
  59. package/default-themes/nebula-dusk.v3.css +201 -0
  60. package/default-themes/solar-flare-dawn.json +19 -0
  61. package/default-themes/solar-flare-dawn.v3.css +198 -0
  62. package/default-themes/solar-flare-dusk.json +19 -0
  63. package/default-themes/solar-flare-dusk.v3.css +200 -0
  64. package/dist/agents/index.d.ts +1 -1
  65. package/dist/agents/index.d.ts.map +1 -1
  66. package/dist/agents/index.js +2 -1
  67. package/dist/agents/index.js.map +1 -1
  68. package/dist/agents/openclaw/gatewayManager.d.ts +4 -0
  69. package/dist/agents/openclaw/gatewayManager.d.ts.map +1 -1
  70. package/dist/agents/openclaw/gatewayManager.js +27 -11
  71. package/dist/agents/openclaw/gatewayManager.js.map +1 -1
  72. package/dist/agents/openclaw/openclawProvider.d.ts.map +1 -1
  73. package/dist/agents/openclaw/openclawProvider.js +2 -4
  74. package/dist/agents/openclaw/openclawProvider.js.map +1 -1
  75. package/dist/agents/openclaw/sshTunnelManager.d.ts +2 -0
  76. package/dist/agents/openclaw/sshTunnelManager.d.ts.map +1 -1
  77. package/dist/agents/openclaw/sshTunnelManager.js +31 -12
  78. package/dist/agents/openclaw/sshTunnelManager.js.map +1 -1
  79. package/dist/builders/anthropic.d.ts +31 -0
  80. package/dist/builders/anthropic.d.ts.map +1 -0
  81. package/dist/builders/anthropic.js +227 -0
  82. package/dist/builders/anthropic.js.map +1 -0
  83. package/dist/builders/fireworksai.d.ts +9 -0
  84. package/dist/builders/fireworksai.d.ts.map +1 -0
  85. package/dist/builders/fireworksai.js +57 -0
  86. package/dist/builders/fireworksai.js.map +1 -0
  87. package/dist/builders/index.d.ts +13 -0
  88. package/dist/builders/index.d.ts.map +1 -0
  89. package/dist/builders/index.js +31 -0
  90. package/dist/builders/index.js.map +1 -0
  91. package/dist/builders/openai.d.ts +8 -0
  92. package/dist/builders/openai.d.ts.map +1 -0
  93. package/dist/builders/openai.js +87 -0
  94. package/dist/builders/openai.js.map +1 -0
  95. package/dist/builders/types.d.ts +54 -0
  96. package/dist/builders/types.d.ts.map +1 -0
  97. package/dist/builders/types.js +211 -0
  98. package/dist/builders/types.js.map +1 -0
  99. package/dist/connectors/index.d.ts +1 -1
  100. package/dist/connectors/index.d.ts.map +1 -1
  101. package/dist/connectors/index.js +3 -2
  102. package/dist/connectors/index.js.map +1 -1
  103. package/dist/connectors/registry.d.ts +2 -1
  104. package/dist/connectors/registry.d.ts.map +1 -1
  105. package/dist/connectors/registry.js +31 -8
  106. package/dist/connectors/registry.js.map +1 -1
  107. package/dist/customizer/Customizer.d.ts +62 -0
  108. package/dist/customizer/Customizer.d.ts.map +1 -0
  109. package/dist/customizer/Customizer.js +134 -0
  110. package/dist/customizer/Customizer.js.map +1 -0
  111. package/dist/customizer/index.d.ts +4 -0
  112. package/dist/customizer/index.d.ts.map +1 -0
  113. package/dist/customizer/index.js +9 -0
  114. package/dist/customizer/index.js.map +1 -0
  115. package/dist/files.d.ts +16 -0
  116. package/dist/files.d.ts.map +1 -1
  117. package/dist/files.js +60 -1
  118. package/dist/files.js.map +1 -1
  119. package/dist/index.d.ts +2 -0
  120. package/dist/index.d.ts.map +1 -1
  121. package/dist/index.js +2 -0
  122. package/dist/index.js.map +1 -1
  123. package/dist/init.d.ts +12 -6
  124. package/dist/init.d.ts.map +1 -1
  125. package/dist/init.js +150 -133
  126. package/dist/init.js.map +1 -1
  127. package/dist/migrations.d.ts.map +1 -1
  128. package/dist/migrations.js +23 -10
  129. package/dist/migrations.js.map +1 -1
  130. package/dist/models/anthropic.d.ts +4 -2
  131. package/dist/models/anthropic.d.ts.map +1 -1
  132. package/dist/models/anthropic.js +33 -6
  133. package/dist/models/anthropic.js.map +1 -1
  134. package/dist/models/fireworksai.d.ts.map +1 -1
  135. package/dist/models/fireworksai.js +9 -1
  136. package/dist/models/fireworksai.js.map +1 -1
  137. package/dist/models/index.d.ts +1 -1
  138. package/dist/models/index.d.ts.map +1 -1
  139. package/dist/models/index.js +2 -1
  140. package/dist/models/index.js.map +1 -1
  141. package/dist/models/openai.d.ts +1 -1
  142. package/dist/models/openai.d.ts.map +1 -1
  143. package/dist/models/openai.js +24 -3
  144. package/dist/models/openai.js.map +1 -1
  145. package/dist/models/types.d.ts +20 -1
  146. package/dist/models/types.d.ts.map +1 -1
  147. package/dist/models/types.js +6 -1
  148. package/dist/models/types.js.map +1 -1
  149. package/dist/pages.d.ts +34 -10
  150. package/dist/pages.d.ts.map +1 -1
  151. package/dist/pages.js +229 -79
  152. package/dist/pages.js.map +1 -1
  153. package/dist/service/createCompletePrompt.d.ts +2 -1
  154. package/dist/service/createCompletePrompt.d.ts.map +1 -1
  155. package/dist/service/createCompletePrompt.js +2 -2
  156. package/dist/service/createCompletePrompt.js.map +1 -1
  157. package/dist/service/requiresSettings.d.ts +2 -1
  158. package/dist/service/requiresSettings.d.ts.map +1 -1
  159. package/dist/service/requiresSettings.js +3 -3
  160. package/dist/service/requiresSettings.js.map +1 -1
  161. package/dist/service/server.d.ts +2 -1
  162. package/dist/service/server.d.ts.map +1 -1
  163. package/dist/service/server.js +37 -8
  164. package/dist/service/server.js.map +1 -1
  165. package/dist/service/transformPage.d.ts +47 -20
  166. package/dist/service/transformPage.d.ts.map +1 -1
  167. package/dist/service/transformPage.js +514 -293
  168. package/dist/service/transformPage.js.map +1 -1
  169. package/dist/service/useAgentRoutes.d.ts +2 -1
  170. package/dist/service/useAgentRoutes.d.ts.map +1 -1
  171. package/dist/service/useAgentRoutes.js +17 -14
  172. package/dist/service/useAgentRoutes.js.map +1 -1
  173. package/dist/service/useApiRoutes.d.ts +2 -1
  174. package/dist/service/useApiRoutes.d.ts.map +1 -1
  175. package/dist/service/useApiRoutes.js +287 -172
  176. package/dist/service/useApiRoutes.js.map +1 -1
  177. package/dist/service/useConnectorRoutes.js +17 -17
  178. package/dist/service/useConnectorRoutes.js.map +1 -1
  179. package/dist/service/useDataRoutes.d.ts.map +1 -1
  180. package/dist/service/useDataRoutes.js +13 -10
  181. package/dist/service/useDataRoutes.js.map +1 -1
  182. package/dist/service/useFileRoutes.d.ts +4 -0
  183. package/dist/service/useFileRoutes.d.ts.map +1 -0
  184. package/dist/service/useFileRoutes.js +122 -0
  185. package/dist/service/useFileRoutes.js.map +1 -0
  186. package/dist/service/usePageRoutes.d.ts +2 -1
  187. package/dist/service/usePageRoutes.d.ts.map +1 -1
  188. package/dist/service/usePageRoutes.js +671 -74
  189. package/dist/service/usePageRoutes.js.map +1 -1
  190. package/dist/service/useSharedDataRoutes.d.ts +4 -0
  191. package/dist/service/useSharedDataRoutes.d.ts.map +1 -0
  192. package/dist/service/useSharedDataRoutes.js +107 -0
  193. package/dist/service/useSharedDataRoutes.js.map +1 -0
  194. package/dist/service/useSharedFileRoutes.d.ts +4 -0
  195. package/dist/service/useSharedFileRoutes.d.ts.map +1 -0
  196. package/dist/service/useSharedFileRoutes.js +121 -0
  197. package/dist/service/useSharedFileRoutes.js.map +1 -0
  198. package/dist/settings.d.ts +5 -3
  199. package/dist/settings.d.ts.map +1 -1
  200. package/dist/settings.js +12 -10
  201. package/dist/settings.js.map +1 -1
  202. package/dist/storage/FsStorageProvider.d.ts +25 -0
  203. package/dist/storage/FsStorageProvider.d.ts.map +1 -0
  204. package/dist/storage/FsStorageProvider.js +103 -0
  205. package/dist/storage/FsStorageProvider.js.map +1 -0
  206. package/dist/storage/StorageProvider.d.ts +31 -0
  207. package/dist/storage/StorageProvider.d.ts.map +1 -0
  208. package/dist/storage/StorageProvider.js +3 -0
  209. package/dist/storage/StorageProvider.js.map +1 -0
  210. package/dist/storage/index.d.ts +3 -0
  211. package/dist/storage/index.d.ts.map +1 -0
  212. package/dist/storage/index.js +6 -0
  213. package/dist/storage/index.js.map +1 -0
  214. package/dist/synthos-cli.d.ts.map +1 -1
  215. package/dist/synthos-cli.js +4 -3
  216. package/dist/synthos-cli.js.map +1 -1
  217. package/dist/themes.d.ts +1 -0
  218. package/dist/themes.d.ts.map +1 -1
  219. package/dist/themes.js +65 -28
  220. package/dist/themes.js.map +1 -1
  221. package/migration-rules/v1-to-v2.md +193 -0
  222. package/migration-rules/v2-to-v3.md +481 -0
  223. package/package.json +11 -10
  224. package/required-pages/builder/page.html +43 -0
  225. package/required-pages/builder/page.json +10 -0
  226. package/required-pages/{pages.html → pages/page.html} +238 -233
  227. package/required-pages/pages/page.json +10 -0
  228. package/required-pages/{settings.html → settings/page.html} +389 -275
  229. package/required-pages/settings/page.json +10 -0
  230. package/required-pages/synthos_apis/page.html +846 -0
  231. package/required-pages/synthos_apis/page.json +10 -0
  232. package/required-pages/{synthos_scripts.html → synthos_scripts/page.html} +13 -11
  233. package/required-pages/synthos_scripts/page.json +10 -0
  234. package/src/agents/index.ts +1 -1
  235. package/src/agents/openclaw/gatewayManager.ts +22 -11
  236. package/src/agents/openclaw/openclawProvider.ts +2 -4
  237. package/src/agents/openclaw/sshTunnelManager.ts +19 -11
  238. package/src/builders/anthropic.ts +283 -0
  239. package/src/builders/fireworksai.ts +59 -0
  240. package/src/builders/index.ts +33 -0
  241. package/src/builders/openai.ts +89 -0
  242. package/src/builders/types.ts +261 -0
  243. package/src/connectors/index.ts +1 -1
  244. package/src/connectors/registry.ts +28 -8
  245. package/src/customizer/Customizer.ts +163 -0
  246. package/src/customizer/index.ts +5 -0
  247. package/src/files.ts +57 -0
  248. package/src/index.ts +3 -1
  249. package/src/init.ts +195 -145
  250. package/src/migrations.ts +30 -10
  251. package/src/models/anthropic.ts +40 -10
  252. package/src/models/fireworksai.ts +9 -2
  253. package/src/models/index.ts +1 -1
  254. package/src/models/openai.ts +26 -6
  255. package/src/models/types.ts +31 -1
  256. package/src/pages.ts +230 -77
  257. package/src/service/createCompletePrompt.ts +3 -2
  258. package/src/service/requiresSettings.ts +4 -3
  259. package/src/service/server.ts +36 -9
  260. package/src/service/transformPage.ts +557 -326
  261. package/src/service/useAgentRoutes.ts +19 -14
  262. package/src/service/useApiRoutes.ts +208 -84
  263. package/src/service/useConnectorRoutes.ts +18 -18
  264. package/src/service/useDataRoutes.ts +13 -10
  265. package/src/service/useFileRoutes.ts +128 -0
  266. package/src/service/usePageRoutes.ts +730 -81
  267. package/src/service/useSharedDataRoutes.ts +109 -0
  268. package/src/service/useSharedFileRoutes.ts +127 -0
  269. package/src/settings.ts +14 -10
  270. package/src/storage/FsStorageProvider.ts +87 -0
  271. package/src/storage/StorageProvider.ts +34 -0
  272. package/src/storage/index.ts +2 -0
  273. package/src/synthos-cli.ts +4 -3
  274. package/src/themes.ts +64 -27
  275. package/static-files/favicon.svg +12 -0
  276. package/static-files/fluentlm-instructions.llmd +868 -0
  277. package/static-files/fluentlm-instructions.md +1595 -0
  278. package/static-files/fluentlm.css +4844 -0
  279. package/static-files/fluentlm.js +3602 -0
  280. package/static-files/fluentlm.min.css +1 -0
  281. package/static-files/fluentlm.min.js +1 -0
  282. package/{page-scripts/helpers-v2.js → static-files/helpers.v3.js} +82 -0
  283. package/static-files/page.v3.js +1290 -0
  284. package/static-files/recommended-frameworks.llmd +81 -0
  285. package/static-files/recommended-frameworks.md +137 -0
  286. package/static-files/retro-game.js +877 -0
  287. package/static-files/shell.css +797 -0
  288. package/static-files/theme-dark.css +169 -0
  289. package/static-files/theme-light.css +169 -0
  290. package/tests/builders.spec.ts +139 -0
  291. package/tests/pages.spec.ts +54 -84
  292. package/tests/transformPage.spec.ts +299 -360
  293. package/default-pages/application.html +0 -40
  294. package/default-pages/application.json +0 -1
  295. package/default-pages/json_tools.json +0 -1
  296. package/default-pages/my_notes.html +0 -33
  297. package/default-pages/neon_asteroids.html +0 -77
  298. package/default-pages/sidebar_page.json +0 -1
  299. package/default-pages/solar_tutorial.json +0 -1
  300. package/default-pages/two-panel_page.json +0 -1
  301. package/dist/service/useGatewayRoutes.d.ts +0 -4
  302. package/dist/service/useGatewayRoutes.d.ts.map +0 -1
  303. package/dist/service/useGatewayRoutes.js +0 -168
  304. package/dist/service/useGatewayRoutes.js.map +0 -1
  305. package/page-scripts/page-v2.js +0 -656
  306. package/required-pages/builder.html +0 -48
  307. package/required-pages/builder.json +0 -1
  308. package/required-pages/pages.json +0 -1
  309. package/required-pages/settings.json +0 -1
  310. package/required-pages/synthos_apis.html +0 -327
  311. package/required-pages/synthos_apis.json +0 -1
  312. package/required-pages/synthos_scripts.json +0 -1
  313. package/src/connectors/airtable/connector.json +0 -27
  314. package/src/connectors/alpha-vantage/connector.json +0 -26
  315. package/src/connectors/brave-search/connector.json +0 -26
  316. package/src/connectors/cloudinary/connector.json +0 -27
  317. package/src/connectors/deepl/connector.json +0 -28
  318. package/src/connectors/elevenlabs/connector.json +0 -30
  319. package/src/connectors/giphy/connector.json +0 -27
  320. package/src/connectors/github/connector.json +0 -29
  321. package/src/connectors/huggingface/connector.json +0 -27
  322. package/src/connectors/imgur/connector.json +0 -29
  323. package/src/connectors/instagram/connector.json +0 -43
  324. package/src/connectors/jira/connector.json +0 -28
  325. package/src/connectors/mapbox/connector.json +0 -26
  326. package/src/connectors/nasa/connector.json +0 -27
  327. package/src/connectors/newsapi/connector.json +0 -27
  328. package/src/connectors/notion/connector.json +0 -28
  329. package/src/connectors/open-exchange-rates/connector.json +0 -27
  330. package/src/connectors/openweathermap/connector.json +0 -26
  331. package/src/connectors/pexels/connector.json +0 -27
  332. package/src/connectors/resend/connector.json +0 -29
  333. package/src/connectors/rss2json/connector.json +0 -27
  334. package/src/connectors/sendgrid/connector.json +0 -27
  335. package/src/connectors/spoonacular/connector.json +0 -28
  336. package/src/connectors/stability-ai/connector.json +0 -27
  337. package/src/connectors/twilio/connector.json +0 -28
  338. package/src/connectors/unsplash/connector.json +0 -27
  339. package/src/connectors/wolfram-alpha/connector.json +0 -26
  340. package/src/connectors/youtube-data/connector.json +0 -30
  341. /package/{dist/connectors → service-connectors}/airtable/connector.json +0 -0
  342. /package/{dist/connectors → service-connectors}/alpha-vantage/connector.json +0 -0
  343. /package/{dist/connectors → service-connectors}/brave-search/connector.json +0 -0
  344. /package/{dist/connectors → service-connectors}/cloudinary/connector.json +0 -0
  345. /package/{dist/connectors → service-connectors}/deepl/connector.json +0 -0
  346. /package/{dist/connectors → service-connectors}/elevenlabs/connector.json +0 -0
  347. /package/{dist/connectors → service-connectors}/giphy/connector.json +0 -0
  348. /package/{dist/connectors → service-connectors}/github/connector.json +0 -0
  349. /package/{dist/connectors → service-connectors}/huggingface/connector.json +0 -0
  350. /package/{dist/connectors → service-connectors}/imgur/connector.json +0 -0
  351. /package/{dist/connectors → service-connectors}/instagram/connector.json +0 -0
  352. /package/{dist/connectors → service-connectors}/jira/connector.json +0 -0
  353. /package/{dist/connectors → service-connectors}/mapbox/connector.json +0 -0
  354. /package/{dist/connectors → service-connectors}/nasa/connector.json +0 -0
  355. /package/{dist/connectors → service-connectors}/newsapi/connector.json +0 -0
  356. /package/{dist/connectors → service-connectors}/notion/connector.json +0 -0
  357. /package/{dist/connectors → service-connectors}/open-exchange-rates/connector.json +0 -0
  358. /package/{dist/connectors → service-connectors}/openweathermap/connector.json +0 -0
  359. /package/{dist/connectors → service-connectors}/pexels/connector.json +0 -0
  360. /package/{dist/connectors → service-connectors}/resend/connector.json +0 -0
  361. /package/{dist/connectors → service-connectors}/rss2json/connector.json +0 -0
  362. /package/{dist/connectors → service-connectors}/sendgrid/connector.json +0 -0
  363. /package/{dist/connectors → service-connectors}/spoonacular/connector.json +0 -0
  364. /package/{dist/connectors → service-connectors}/stability-ai/connector.json +0 -0
  365. /package/{dist/connectors → service-connectors}/twilio/connector.json +0 -0
  366. /package/{dist/connectors → service-connectors}/unsplash/connector.json +0 -0
  367. /package/{dist/connectors → service-connectors}/wolfram-alpha/connector.json +0 -0
  368. /package/{dist/connectors → service-connectors}/youtube-data/connector.json +0 -0
@@ -0,0 +1,10 @@
1
+ {
2
+ "title": "SynthOS APIs",
3
+ "categories": [
4
+ "System"
5
+ ],
6
+ "pinned": false,
7
+ "showInAll": false,
8
+ "pageVersion": 3,
9
+ "mode": "locked"
10
+ }
@@ -8,22 +8,24 @@
8
8
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
9
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.1/marked.min.js"></script>
10
10
  <script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.1.0/mermaid.min.js"></script>
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
11
12
  <script id="page-info" src="/api/page-info.js?page=scripts"></script>
12
13
  </head>
13
14
  <body>
15
+ <div class="shell-toolbar" data-locked="true">
16
+ <button class="shell-toolbar-btn" id="builderToggle" aria-label="Page Builder" data-locked="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><path d="M7 18.5H6.2c-1.77 0-3.2-1.43-3.2-3.2V7.7C3 5.93 4.43 4.5 6.2 4.5h11.6c1.77 0 3.2 1.43 3.2 3.2v7.6c0 1.77-1.43 3.2-3.2 3.2H12l-4.2 3.2c-.5.38-1.2.02-1.2-.6V18.5Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><circle cx="8.5" cy="11.5" r="1" fill="currentColor"/><circle cx="12" cy="11.5" r="1" fill="currentColor"/><circle cx="15.5" cy="11.5" r="1" fill="currentColor"/></svg></button>
17
+ <button class="shell-toolbar-btn" id="pagesBtn" aria-label="View All Pages" data-locked="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none"><rect x="3" y="3" width="11" height="12" rx="1.5" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M6 7.5h5M6 10h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><rect x="18" y="3" width="11" height="12" rx="1.5" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M21 7.5h5M21 10h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><rect x="3" y="18" width="11" height="12" rx="1.5" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M6 22.5h5M6 25h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><rect x="18" y="18" width="11" height="12" rx="1.5" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M21 22.5h5M21 25h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>
18
+ <button class="shell-toolbar-btn" id="saveBtn" aria-label="Save Page" data-locked="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M17 21v-8H7v8" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M7 3v5h8" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg></button>
19
+ <div class="shell-toolbar-spacer" data-locked="true"></div>
20
+ <button class="shell-toolbar-btn" id="settingsBtn" aria-label="Settings" data-locked="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" stroke="currentColor" stroke-width="1.8"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
21
+ </div>
14
22
  <div class="chat-panel" data-locked="true">
15
- <div class="chat-header" data-locked="true">SynthOS</div>
23
+ <div class="chat-header" data-locked="true"><span>Page Builder</span><button class="chat-header-close" id="builderClose" aria-label="Close builder" data-locked="true">&times;</button></div>
16
24
  <div class="chat-messages" id="chatMessages" data-locked="true">
17
25
  <div class="chat-message"><p><strong>SynthOS:</strong> Add or modify scripts that can be executed using the <code>/api/scripts/:id</code> API.</p></div>
18
- </div>
19
- <div class="link-group" data-locked="true">
20
- <a href="#" id="saveLink" data-locked="true">Save</a>
21
- <a href="/pages" id="pagesLink" data-locked="true">Pages</a>
22
- <a href="#" id="resetLink" data-locked="true">Reset</a>
23
- </div>
26
+ </div>
24
27
  <form action="/" method="POST" id="chatForm" data-locked="true">
25
- <input type="text" class="chat-input" id="chatInput" name="message" placeholder="Type a message..." data-locked="true">
26
- <button type="submit" class="chat-submit" data-locked="true">Send</button>
28
+ <textarea class="chat-input" id="chatInput" name="message" rows="2" placeholder="Type a message..." data-locked="true"></textarea>
27
29
  </form>
28
30
  </div>
29
31
  <div class="viewer-panel" id="viewerPanel">
@@ -82,6 +84,6 @@
82
84
  <div id="instructions" style="display: none;" data-locked="true"></div>
83
85
  <div id="thoughts" style="display: none;" data-locked="true"></div>
84
86
  <script id="script-editor">const scriptList=document.getElementById("scriptList"),scriptId=document.getElementById("scriptId"),scriptType=document.getElementById("scriptType"),scriptCommand=document.getElementById("scriptCommand"),scriptDescription=document.getElementById("scriptDescription"),scriptVariables=document.getElementById("scriptVariables"),addScriptBtn=document.querySelector(".add-script-btn"),saveScriptBtn=document.querySelector(".save-script-btn"),deleteScriptBtn=document.querySelector(".delete-script-btn"),placeholderMessage=document.getElementById("placeholderMessage"),commandError=document.getElementById("commandError");let isNewScript=!1;function showScriptDetails(){placeholderMessage.style.display="none",scriptId.style.display="block",scriptType.style.display="block",scriptCommand.style.display="block",scriptDescription.style.display="block",scriptVariables.style.display="block",saveScriptBtn.style.display="block",deleteScriptBtn.style.display=isNewScript?"none":"block"}function hideScriptDetails(){placeholderMessage.style.display="block",scriptId.style.display="none",scriptType.style.display="none",scriptCommand.style.display="none",scriptDescription.style.display="none",scriptVariables.style.display="none",saveScriptBtn.style.display="none",deleteScriptBtn.style.display="none",commandError.style.display="none"}function formatId(id){return id.toLowerCase().replace(/\s+/g,"-")}function loadScripts(){fetch("/api/data/scripts/scripts").then(response=>response.json()).then(scripts=>{scriptList.innerHTML="",scripts.forEach(script=>{const li=document.createElement("li");li.textContent=script.id,li.dataset.id=script.id,li.dataset.type=script.type,li.dataset.command=script.command,li.dataset.description=script.description||"",li.dataset.variables=script.variables||"",scriptList.appendChild(li)})}).catch(error=>console.error("Error loading scripts:",error))}function saveScript(script){fetch("/api/data/scripts/scripts").then(response=>response.json()).then(scripts=>{scripts.find(s=>s.id===script.id)&&isNewScript?confirm("A script with this ID already exists. Do you want to overwrite it?")&&performSave(script):performSave(script)}).catch(error=>console.error("Error checking existing scripts:",error))}function performSave(script){fetch("/api/data/scripts/scripts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(script)}).then(response=>response.json()).then(savedScript=>{loadScripts(),isNewScript=!1,setTimeout(()=>{const newScriptElement=document.querySelector(`[data-id="${savedScript.id}"]`);newScriptElement&&newScriptElement.click()},100)}).catch(error=>console.error("Error saving script:",error))}function deleteScript(id){confirm("Are you sure you want to delete this script?")&&fetch(`/api/data/scripts/scripts/${id}`,{method:"DELETE"}).then(response=>response.json()).then(result=>{result.success?(loadScripts(),hideScriptDetails()):console.error("Failed to delete script.")}).catch(error=>console.error("Error deleting script:",error))}scriptList.addEventListener("click",e=>{"LI"===e.target.tagName&&(document.querySelectorAll(".script-list li").forEach(li=>li.classList.remove("active")),e.target.classList.add("active"),showScriptDetails(),scriptId.value=e.target.dataset.id,scriptType.value=e.target.dataset.type||"command",scriptCommand.value=e.target.dataset.command||"",scriptDescription.value=e.target.dataset.description||"",scriptVariables.value=e.target.dataset.variables||"",commandError.style.display="none",isNewScript=!1,deleteScriptBtn.style.display="block")}),addScriptBtn.addEventListener("click",()=>{const newId=formatId(`script-${Date.now()}`);showScriptDetails(),scriptId.value=newId,scriptType.value="command",scriptCommand.value="",scriptDescription.value="",scriptVariables.value="",commandError.style.display="none",document.querySelectorAll(".script-list li").forEach(li=>li.classList.remove("active")),isNewScript=!0,deleteScriptBtn.style.display="none"}),saveScriptBtn.addEventListener("click",()=>{scriptCommand.value.trim()?saveScript({id:formatId(scriptId.value),type:scriptType.value,command:scriptCommand.value,description:scriptDescription.value.trim()||void 0,variables:scriptVariables.value.trim()||void 0}):commandError.style.display="block"}),deleteScriptBtn.addEventListener("click",()=>{const id=scriptId.value;id&&deleteScript(id)}),scriptId.addEventListener("input",e=>{e.target.value=formatId(e.target.value)}),scriptCommand.addEventListener("input",()=>{commandError.style.display="none"}),loadScripts(),hideScriptDetails();</script>
85
- <script id="page-helpers" src="/api/page-helpers.js?v=2" data-locked="true"></script>
86
- <script id="page-script" src="/api/page-script.js?v=2" data-locked="true"></script>
87
+ <script id="page-helpers" src="/api/page-helpers.js?v=3" data-locked="true"></script>
88
+ <script id="page-script" src="/api/page-script.js?v=3" data-locked="true"></script>
87
89
  </body></html>
@@ -0,0 +1,10 @@
1
+ {
2
+ "title": "SynthOS Scripts",
3
+ "categories": [
4
+ "System"
5
+ ],
6
+ "pinned": false,
7
+ "showInAll": false,
8
+ "pageVersion": 3,
9
+ "mode": "locked"
10
+ }
@@ -1,6 +1,6 @@
1
1
  export { AgentConfig, AgentResponse, AgentEvent, AgentProvider, Attachment, ChatMessage } from './types';
2
2
  export { a2aProvider } from './a2a/a2aProvider';
3
3
  export { openclawProvider } from './openclaw/openclawProvider';
4
- export { connectAgent, disconnectAgent, getAgentStatus } from './openclaw/gatewayManager';
4
+ export { connectAgent, disconnectAgent, getAgentStatus, setOpenClawDebug } from './openclaw/gatewayManager';
5
5
  export { startTunnel, stopTunnel, getTunnelStatus } from './openclaw/sshTunnelManager';
6
6
  export { discoverA2AAgent, discoverOpenClawAgent } from './discovery';
@@ -1,4 +1,4 @@
1
- import { startTunnel, stopTunnel, SshTunnelConfig } from './sshTunnelManager';
1
+ import { startTunnel, stopTunnel, setSshTunnelDebug, SshTunnelConfig } from './sshTunnelManager';
2
2
 
3
3
  // ---------------------------------------------------------------------------
4
4
  // Types
@@ -13,6 +13,7 @@ export interface GatewayConfig {
13
13
  enabled: boolean;
14
14
  role: 'operator';
15
15
  scopes: string[];
16
+ productName?: string;
16
17
  }
17
18
 
18
19
  interface PendingRequest {
@@ -51,6 +52,14 @@ const BASE_RECONNECT_DELAY_MS = 1_000;
51
52
  // Module-level connection pool
52
53
  // ---------------------------------------------------------------------------
53
54
 
55
+ let _debug = false;
56
+
57
+ /** Enable or disable OpenClaw + SSH tunnel debug logging. */
58
+ export function setOpenClawDebug(enabled: boolean): void {
59
+ _debug = enabled;
60
+ setSshTunnelDebug(enabled);
61
+ }
62
+
54
63
  const connections = new Map<string, GatewayConnection>();
55
64
 
56
65
  let _nextId = 1;
@@ -161,7 +170,7 @@ export async function connect(config: GatewayConfig): Promise<GatewayConnection>
161
170
  ws.addEventListener('error', () => {
162
171
  // The close event will fire after this, so we don't settle here.
163
172
  // Just log for diagnostics.
164
- console.error(`[OpenClaw] WebSocket error on gateway ${config.name}`);
173
+ if (_debug) console.error(`[OpenClaw] WebSocket error on gateway ${config.name}`);
165
174
  });
166
175
  } catch (err) {
167
176
  settle(err instanceof Error ? err : new Error(String(err)));
@@ -241,7 +250,7 @@ export function request(conn: GatewayConnection, method: string, params?: unknow
241
250
  msg.params = params;
242
251
  }
243
252
 
244
- console.log(`[OpenClaw] → req: ${id} ${method}`);
253
+ if (_debug) console.log(`[OpenClaw] → req: ${id} ${method}`);
245
254
  conn.ws.send(JSON.stringify(msg));
246
255
  });
247
256
  }
@@ -290,6 +299,7 @@ export async function connectAgent(agent: {
290
299
  url: string;
291
300
  token: string;
292
301
  sshTunnel?: { enabled: boolean; command: string; password: string };
302
+ productName?: string;
293
303
  }): Promise<GatewayConnection> {
294
304
  // Start SSH tunnel first if configured
295
305
  if (agent.sshTunnel?.enabled && agent.sshTunnel.command) {
@@ -308,6 +318,7 @@ export async function connectAgent(agent: {
308
318
  enabled: true,
309
319
  role: 'operator',
310
320
  scopes: ['operator.read', 'operator.write', 'operator.approvals'],
321
+ productName: agent.productName,
311
322
  };
312
323
  return connect(gwConfig);
313
324
  }
@@ -366,7 +377,7 @@ function handleMessage(
366
377
  // Skip noisy per-token agent events
367
378
  if (eventName === 'agent') {
368
379
  // Still dispatch to listeners below, just don't log
369
- } else {
380
+ } else if (_debug) {
370
381
  console.log(`[OpenClaw] ← event: ${eventName}`);
371
382
  if (eventName === 'chat') {
372
383
  console.log(`[OpenClaw] ← ${eventName} payload:\n${JSON.stringify(msg.payload, null, 2)}`);
@@ -383,7 +394,7 @@ function handleMessage(
383
394
  if (listeners) {
384
395
  for (const listener of listeners) {
385
396
  try { listener(msg.payload); } catch (err) {
386
- console.error(`[OpenClaw] Event listener error (${eventName}):`, err);
397
+ if (_debug) console.error(`[OpenClaw] Event listener error (${eventName}):`, err);
387
398
  }
388
399
  }
389
400
  }
@@ -392,7 +403,7 @@ function handleMessage(
392
403
  if (wildcardListeners) {
393
404
  for (const listener of wildcardListeners) {
394
405
  try { listener(msg); } catch (err) {
395
- console.error(`[OpenClaw] Wildcard listener error:`, err);
406
+ if (_debug) console.error(`[OpenClaw] Wildcard listener error:`, err);
396
407
  }
397
408
  }
398
409
  }
@@ -407,10 +418,10 @@ function handleMessage(
407
418
  const error = msg.error as Record<string, unknown> | string | undefined;
408
419
 
409
420
  const payloadType = (payload as Record<string, unknown>).type as string | undefined;
410
- console.log(`[OpenClaw] ← res: ${id} ${ok ? (payloadType ?? 'ok') : 'ERROR'}`);
421
+ if (_debug) console.log(`[OpenClaw] ← res: ${id} ${ok ? (payloadType ?? 'ok') : 'ERROR'}`);
411
422
 
412
423
  if (!ok || error) {
413
- console.error(`[OpenClaw] ← error (id: ${id}):\n${JSON.stringify(msg, null, 2)}`);
424
+ if (_debug) console.error(`[OpenClaw] ← error (id: ${id}):\n${JSON.stringify(msg, null, 2)}`);
414
425
  }
415
426
 
416
427
  // Check if this is the hello-ok response to our connect request
@@ -502,7 +513,7 @@ function sendConnectRequest(conn: GatewayConnection): void {
502
513
  permissions: {},
503
514
  auth: { token: conn.config.token },
504
515
  locale: 'en-US',
505
- userAgent: 'SynthOS/1.0',
516
+ userAgent: `${conn.config.productName ?? 'SynthOS'}/1.0`,
506
517
  },
507
518
  };
508
519
 
@@ -510,7 +521,7 @@ function sendConnectRequest(conn: GatewayConnection): void {
510
521
  conn.pendingRequests.set(id, {
511
522
  resolve: () => { /* handled in handleMessage via hello-ok check */ },
512
523
  reject: (err: Error) => {
513
- console.error(`[OpenClaw] Connect request failed for ${conn.config.name}: ${err.message}`);
524
+ if (_debug) console.error(`[OpenClaw] Connect request failed for ${conn.config.name}: ${err.message}`);
514
525
  },
515
526
  timer: setTimeout(() => {
516
527
  conn.pendingRequests.delete(id);
@@ -553,7 +564,7 @@ function scheduleReconnect(conn: GatewayConnection): void {
553
564
  try {
554
565
  await connect(conn.config);
555
566
  } catch (err) {
556
- console.error(`[OpenClaw] Reconnect failed for ${conn.config.name}:`, err instanceof Error ? err.message : err);
567
+ if (_debug) console.error(`[OpenClaw] Reconnect failed for ${conn.config.name}:`, err instanceof Error ? err.message : err);
557
568
  }
558
569
  }, delay);
559
570
  }
@@ -248,14 +248,12 @@ export const openclawProvider: AgentProvider = {
248
248
  async abortChat(agent: AgentConfig): Promise<void> {
249
249
  const conn = await getConnection(agent);
250
250
  const sessionKey = await resolveSessionKey(agent, conn);
251
- const result = await request(conn, 'chat.abort', { sessionKey });
252
- console.log('[OpenClaw] ← raw chat.abort:', JSON.stringify(result, null, 2));
251
+ await request(conn, 'chat.abort', { sessionKey });
253
252
  },
254
253
 
255
254
  async clearSession(agent: AgentConfig): Promise<void> {
256
255
  const conn = await getConnection(agent);
257
256
  const sessionKey = await resolveSessionKey(agent, conn);
258
- const result = await request(conn, 'sessions.reset', { sessionKey });
259
- console.log('[OpenClaw] ← raw sessions.reset:', JSON.stringify(result, null, 2));
257
+ await request(conn, 'sessions.reset', { sessionKey });
260
258
  },
261
259
  };
@@ -37,6 +37,13 @@ const MAX_RECONNECT_DELAY_MS = 120_000;
37
37
 
38
38
  const IS_WINDOWS = process.platform === 'win32';
39
39
 
40
+ let _debug = false;
41
+
42
+ /** Enable or disable SSH tunnel debug logging. */
43
+ export function setSshTunnelDebug(enabled: boolean): void {
44
+ _debug = enabled;
45
+ }
46
+
40
47
  // ---------------------------------------------------------------------------
41
48
  // Shared askpass script — reads password from an env var, never touches disk
42
49
  // with the actual secret. One script is created per process lifetime and
@@ -144,7 +151,7 @@ export function stopTunnel(agentId: string): void {
144
151
  entry.running = false;
145
152
  entry.reconnecting = false;
146
153
  tunnels.delete(agentId);
147
- console.log(`[SSH Tunnel] Stopped tunnel for agent "${agentId}"`);
154
+ if (_debug) console.log(`[SSH Tunnel] Stopped tunnel for agent "${agentId}"`);
148
155
  }
149
156
 
150
157
  /**
@@ -206,7 +213,7 @@ function probePort(port: number, timeoutMs: number = 1000): Promise<boolean> {
206
213
  */
207
214
  function spawnTunnel(entry: TunnelEntry): Promise<void> {
208
215
  return new Promise<void>((resolve, reject) => {
209
- console.log(`[SSH Tunnel] Starting tunnel for agent "${entry.agentId}": ${entry.config.command}`);
216
+ if (_debug) console.log(`[SSH Tunnel] Starting tunnel for agent "${entry.agentId}": ${entry.config.command}`);
210
217
 
211
218
  let settled = false;
212
219
 
@@ -247,7 +254,7 @@ function spawnTunnel(entry: TunnelEntry): Promise<void> {
247
254
  env.DISPLAY = env.DISPLAY || 'dummy:0';
248
255
  }
249
256
 
250
- console.log(`[SSH Tunnel] Spawning: ${cmd} ${args.join(' ')}`);
257
+ if (_debug) console.log(`[SSH Tunnel] Spawning: ${cmd} ${args.join(' ')}`);
251
258
 
252
259
  const child = spawn(cmd, args, {
253
260
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -260,6 +267,7 @@ function spawnTunnel(entry: TunnelEntry): Promise<void> {
260
267
 
261
268
  // Log all output for diagnostics
262
269
  const handleOutput = (source: string) => (data: Buffer) => {
270
+ if (!_debug) return;
263
271
  const text = data.toString().trim();
264
272
  if (text) {
265
273
  console.log(`[SSH Tunnel] [${entry.agentId}] ${source}: ${text}`);
@@ -270,7 +278,7 @@ function spawnTunnel(entry: TunnelEntry): Promise<void> {
270
278
  child.stderr?.on('data', handleOutput('stderr'));
271
279
 
272
280
  child.on('error', (err) => {
273
- console.error(`[SSH Tunnel] Spawn error for agent "${entry.agentId}":`, err.message);
281
+ if (_debug) console.error(`[SSH Tunnel] Spawn error for agent "${entry.agentId}":`, err.message);
274
282
  entry.running = false;
275
283
  entry.process = null;
276
284
  settle(err);
@@ -281,7 +289,7 @@ function spawnTunnel(entry: TunnelEntry): Promise<void> {
281
289
  entry.running = false;
282
290
  entry.process = null;
283
291
 
284
- console.log(`[SSH Tunnel] Process exited for agent "${entry.agentId}" (code=${code}, signal=${signal})`);
292
+ if (_debug) console.log(`[SSH Tunnel] Process exited for agent "${entry.agentId}" (code=${code}, signal=${signal})`);
285
293
 
286
294
  // If we never settled (tunnel never came up), reject
287
295
  settle(new Error(`SSH tunnel exited before becoming ready (code=${code})`));
@@ -303,7 +311,7 @@ function spawnTunnel(entry: TunnelEntry): Promise<void> {
303
311
  entry.running = true;
304
312
  entry.reconnecting = false;
305
313
  entry.reconnectAttempts = 0;
306
- console.log(`[SSH Tunnel] Tunnel ready (delay heuristic) for agent "${entry.agentId}"`);
314
+ if (_debug) console.log(`[SSH Tunnel] Tunnel ready (delay heuristic) for agent "${entry.agentId}"`);
307
315
  settle();
308
316
  }
309
317
  }, TUNNEL_READY_PROBE_DELAY_MS);
@@ -336,7 +344,7 @@ function waitForPort(entry: TunnelEntry, port: number, settle: (err?: Error) =>
336
344
  entry.running = true;
337
345
  entry.reconnecting = false;
338
346
  entry.reconnectAttempts = 0;
339
- console.log(`[SSH Tunnel] Tunnel ready (port ${port} open) for agent "${entry.agentId}"`);
347
+ if (_debug) console.log(`[SSH Tunnel] Tunnel ready (port ${port} open) for agent "${entry.agentId}"`);
340
348
  settle();
341
349
  } else if (attempts >= maxAttempts) {
342
350
  clearInterval(timer);
@@ -344,7 +352,7 @@ function waitForPort(entry: TunnelEntry, port: number, settle: (err?: Error) =>
344
352
  entry.running = true;
345
353
  entry.reconnecting = false;
346
354
  entry.reconnectAttempts = 0;
347
- console.log(`[SSH Tunnel] Tunnel assumed ready (port probe timed out) for agent "${entry.agentId}"`);
355
+ if (_debug) console.log(`[SSH Tunnel] Tunnel assumed ready (port probe timed out) for agent "${entry.agentId}"`);
348
356
  settle();
349
357
  }
350
358
  }, intervalMs);
@@ -364,7 +372,7 @@ function scheduleReconnect(entry: TunnelEntry): void {
364
372
  MAX_RECONNECT_DELAY_MS
365
373
  );
366
374
 
367
- console.log(`[SSH Tunnel] Scheduling reconnect for agent "${entry.agentId}" in ${delay}ms (attempt ${entry.reconnectAttempts})`);
375
+ if (_debug) console.log(`[SSH Tunnel] Scheduling reconnect for agent "${entry.agentId}" in ${delay}ms (attempt ${entry.reconnectAttempts})`);
368
376
 
369
377
  entry.reconnectTimer = setTimeout(async () => {
370
378
  entry.reconnectTimer = null;
@@ -372,9 +380,9 @@ function scheduleReconnect(entry: TunnelEntry): void {
372
380
 
373
381
  try {
374
382
  await spawnTunnel(entry);
375
- console.log(`[SSH Tunnel] Reconnected tunnel for agent "${entry.agentId}"`);
383
+ if (_debug) console.log(`[SSH Tunnel] Reconnected tunnel for agent "${entry.agentId}"`);
376
384
  } catch (err) {
377
- console.error(`[SSH Tunnel] Reconnect failed for agent "${entry.agentId}":`,
385
+ if (_debug) console.error(`[SSH Tunnel] Reconnect failed for agent "${entry.agentId}":`,
378
386
  err instanceof Error ? err.message : err);
379
387
  // Schedule another attempt
380
388
  if (!entry.intentionalStop) {
@@ -0,0 +1,283 @@
1
+ import { anthropic as createAnthropicModel, completePrompt, ContentBlock } from '../models';
2
+ import { parseChangeList, getTransformInstr } from '../service/transformPage';
3
+ import { Attachment, Builder, BuilderResult, CHANGE_OPS_SCHEMA, CHANGE_OPS_SCHEMA_NO_RANGED, ContextSection } from './types';
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Builder options — passed from the route handler
7
+ // ---------------------------------------------------------------------------
8
+
9
+ export interface AnthropicBuilderOptions {
10
+ apiKey?: string;
11
+ model?: string;
12
+ /** Optional wrapper applied to any internally-created model (e.g. for debug logging). */
13
+ wrapModel?: (model: completePrompt) => completePrompt;
14
+ /** When true, this is the first edit to a saved page (v0→v1) — forces Opus + no ranged writes. */
15
+ isFirstEdit?: boolean;
16
+ /** When true, retry the last edit — forces Opus + no ranged writes, skips classification. */
17
+ tryAgain?: boolean;
18
+ }
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Request classification
22
+ // ---------------------------------------------------------------------------
23
+
24
+ export type Classification = 'question' | 'easy-change' | 'medium-change' | 'hard-change' | 're-write';
25
+
26
+ export interface ClassifyResult {
27
+ classification: Classification;
28
+ /** When classification is "question", this contains the answer text. */
29
+ answer?: string;
30
+ }
31
+
32
+ const CLASSIFIER_SYSTEM_PROMPT = `You classify user messages for a web page builder. Default to a change request. Only classify as "question" when the user is purely asking for information with zero implication that anything should change.
33
+
34
+ <DECISION_RULES>
35
+ Step 1 — Is this a pure information question with zero change implication?
36
+ Examples: "What color is the header?", "How many sections are there?", "What font is the title using?"
37
+ Yes, and there is absolutely no suggestion that anything should change → "question"
38
+ No → go to step 2.
39
+
40
+ Step 2 — Classify complexity:
41
+ "easy-change": Text edits, color/style tweaks, toggling visibility, adding/removing a single element, minor CSS changes.
42
+ "medium-change": Modifying existing JS logic, updating event handlers, changing multiple related styles, adding a small feature to existing functionality, updating existing behavior.
43
+ "hard-change": Complex new features, significant new JS, games, animations, restructuring layout, forms with validation, multi-step work.
44
+ "re-write": Complete page overhaul, fundamental restructure, "start over", "rebuild", "redo the whole page".
45
+
46
+ <OUTPUT_FORMAT>
47
+ Return only JSON. No other text.
48
+ - Change: { "classification": "easy-change" } or { "classification": "medium-change" } or { "classification": "hard-change" } or { "classification": "re-write" }
49
+ - Question: { "classification": "question", "answer": "<brief answer>" }`;
50
+
51
+ export async function classifyRequest(
52
+ apiKey: string,
53
+ pageHtml: string,
54
+ userMessage: string
55
+ ): Promise<ClassifyResult> {
56
+ try {
57
+ const sonnet = createAnthropicModel({ apiKey, model: 'claude-sonnet-4-5' });
58
+ const result = await sonnet({
59
+ system: { role: 'system', content: CLASSIFIER_SYSTEM_PROMPT },
60
+ prompt: { role: 'user', content: `<PAGE_HTML>\n${pageHtml}\n\n<USER_MESSAGE>\n${userMessage}` },
61
+ jsonMode: true,
62
+ });
63
+
64
+ if (!result.completed || !result.value) {
65
+ return { classification: 'hard-change' };
66
+ }
67
+
68
+ const parsed = JSON.parse(result.value);
69
+ const c = parsed.classification;
70
+ if (c === 'question') {
71
+ return { classification: 'question', answer: typeof parsed.answer === 'string' ? parsed.answer : '' };
72
+ }
73
+ if (c === 'easy-change' || c === 'medium-change' || c === 'hard-change' || c === 're-write') {
74
+ return { classification: c };
75
+ }
76
+ return { classification: 'hard-change' };
77
+ } catch {
78
+ return { classification: 'hard-change' };
79
+ }
80
+ }
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // Anthropic builder factory
84
+ // ---------------------------------------------------------------------------
85
+
86
+ /**
87
+ * Create an Anthropic-tuned builder.
88
+ *
89
+ * @param complete The completePrompt function (already configured with API key / model).
90
+ * @param userInstructions Optional user-level instructions from settings.
91
+ * @param productName Product name for branding in prompts (defaults to 'SynthOS').
92
+ * @param options Optional API key and model name for classifier routing.
93
+ */
94
+ export function createAnthropicBuilder(
95
+ complete: completePrompt,
96
+ userInstructions?: string,
97
+ productName?: string,
98
+ options?: AnthropicBuilderOptions
99
+ ): Builder {
100
+ const name = productName ?? 'SynthOS';
101
+
102
+ return {
103
+ async run(currentPage, additionalSections, userMessage, newBuild, attachments?): Promise<BuilderResult> {
104
+ try {
105
+ const isOpus = options?.model?.startsWith('claude-opus-');
106
+ const noRanged = CHANGE_OPS_SCHEMA_NO_RANGED;
107
+
108
+ // Non-Opus models or missing apiKey: existing behavior
109
+ if (!isOpus || !options?.apiKey) {
110
+ return buildWithModel(complete, currentPage, additionalSections, userMessage, userInstructions, name, attachments);
111
+ }
112
+
113
+ // Console errors bypass classification — always route to Opus with no ranged writes
114
+ if (userMessage.includes('CONSOLE_ERRORS:')) {
115
+ console.log('classifyRequest: console errors detected → routing to ' + options.model! + ' (no ranged)');
116
+ return buildWithModel(complete, currentPage, additionalSections, userMessage, userInstructions, name, attachments, noRanged);
117
+ }
118
+
119
+ // New builds always use Opus with no ranged writes
120
+ if (newBuild) {
121
+ console.log('classifyRequest: new build → routing to ' + options.model! + ' (no ranged)');
122
+ return buildWithModel(complete, currentPage, additionalSections, userMessage, userInstructions, name, attachments, noRanged);
123
+ }
124
+
125
+ // First edit to a saved page (v0→v1) — force Opus with no ranged writes
126
+ if (options?.isFirstEdit) {
127
+ console.log('classifyRequest: first edit (v0→v1) → routing to ' + options.model! + ' (no ranged)');
128
+ return buildWithModel(complete, currentPage, additionalSections, userMessage, userInstructions, name, attachments, noRanged);
129
+ }
130
+
131
+ // Try again — force Opus with no ranged writes, skip classification
132
+ if (options?.tryAgain) {
133
+ console.log('classifyRequest: try again → routing to claude-opus-4-6 (no ranged)');
134
+ let opus: completePrompt = createAnthropicModel({ apiKey: options.apiKey!, model: 'claude-opus-4-6' });
135
+ if (options.wrapModel) opus = options.wrapModel(opus);
136
+ return buildWithModel(opus, currentPage, additionalSections, userMessage, userInstructions, name, attachments, noRanged);
137
+ }
138
+
139
+ // Classify the request using Sonnet
140
+ const classifyResult = await classifyRequest(options.apiKey, currentPage.content, userMessage);
141
+ console.log(`classifyRequest: "${classifyResult.classification}" → routing to ${routeLabel(classifyResult.classification, options.model!)}`);
142
+
143
+ // Questions — answer was already provided by the classifier
144
+ if (classifyResult.classification === 'question') {
145
+ return { kind: 'reply', text: classifyResult.answer ?? '' };
146
+ }
147
+
148
+ // Easy changes use Sonnet with default schema (ranged allowed)
149
+ if (classifyResult.classification === 'easy-change') {
150
+ let sonnet: completePrompt = createAnthropicModel({ apiKey: options.apiKey, model: 'claude-sonnet-4-5' });
151
+ if (options.wrapModel) sonnet = options.wrapModel(sonnet);
152
+ return buildWithModel(sonnet, currentPage, additionalSections, userMessage, userInstructions, name, attachments);
153
+ }
154
+
155
+ // Medium changes use Sonnet with no ranged writes
156
+ if (classifyResult.classification === 'medium-change') {
157
+ let sonnet: completePrompt = createAnthropicModel({ apiKey: options.apiKey, model: 'claude-sonnet-4-5' });
158
+ if (options.wrapModel) sonnet = options.wrapModel(sonnet);
159
+ return buildWithModel(sonnet, currentPage, additionalSections, userMessage, userInstructions, name, attachments, noRanged);
160
+ }
161
+
162
+ // Hard changes and re-writes use Opus with no ranged writes
163
+ return buildWithModel(complete, currentPage, additionalSections, userMessage, userInstructions, name, attachments, noRanged);
164
+ } catch (err: unknown) {
165
+ return { kind: 'error', error: err instanceof Error ? err : new Error(String(err)) };
166
+ }
167
+ }
168
+ };
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // Build flow — shared prompt construction + model call + parsing
173
+ // ---------------------------------------------------------------------------
174
+
175
+ export async function buildWithModel(
176
+ model: completePrompt,
177
+ currentPage: ContextSection,
178
+ additionalSections: ContextSection[],
179
+ userMessage: string,
180
+ userInstructions: string | undefined,
181
+ productName: string,
182
+ attachments?: Attachment[],
183
+ outputSchema?: Record<string, unknown>
184
+ ): Promise<BuilderResult> {
185
+ // -- System message: all static content (cacheable) --
186
+ const systemParts: string[] = [];
187
+ for (const section of additionalSections) {
188
+ if (section.content) {
189
+ systemParts.push(`${section.title}\n${section.content}`);
190
+ }
191
+ }
192
+
193
+ const instructionParts: string[] = [];
194
+ if (userInstructions?.trim()) {
195
+ instructionParts.push(userInstructions);
196
+ }
197
+ for (const section of additionalSections) {
198
+ if (section.instructions?.trim()) {
199
+ instructionParts.push(section.instructions);
200
+ }
201
+ }
202
+ instructionParts.push(getTransformInstr(productName));
203
+ const instructions = instructionParts.filter(s => s.trim() !== '').join('\n');
204
+ systemParts.push(`<INSTRUCTIONS>\n${instructions}`);
205
+
206
+ const systemContent = systemParts.join('\n\n');
207
+
208
+ // -- User message: dynamic content only (current page + user message) --
209
+ const promptText = `${currentPage.title}\n${currentPage.content}\n\n<USER_MESSAGE>\n${userMessage}`;
210
+
211
+ // Build prompt content — multimodal when image attachments are present
212
+ const imageAttachments = (attachments ?? []).filter(a => a.mediaType.startsWith('image/'));
213
+ let promptContent: string | ContentBlock[];
214
+ if (imageAttachments.length > 0) {
215
+ const blocks: ContentBlock[] = [{ type: 'text', text: promptText }];
216
+ for (const att of imageAttachments) {
217
+ blocks.push({ type: 'image', mediaType: att.mediaType, data: att.data });
218
+ }
219
+ promptContent = blocks;
220
+ } else {
221
+ promptContent = promptText;
222
+ }
223
+
224
+ // -- Call model --
225
+ const result = await model({
226
+ system: { role: 'system', content: systemContent },
227
+ prompt: { role: 'user', content: promptContent },
228
+ cacheSystem: true,
229
+ outputSchema: outputSchema ?? CHANGE_OPS_SCHEMA,
230
+ });
231
+
232
+ if (!result.completed) {
233
+ return { kind: 'error', error: result.error ?? new Error('Model call failed') };
234
+ }
235
+
236
+ // -- Parse response --
237
+ return parseBuilderResponse(result.value!);
238
+ }
239
+
240
+ // ---------------------------------------------------------------------------
241
+ // Route label for console logging
242
+ // ---------------------------------------------------------------------------
243
+
244
+ function routeLabel(classification: Classification, configuredModel: string): string {
245
+ if (classification === 'question') return 'classifier (answered inline)';
246
+ if (classification === 'easy-change') return 'claude-sonnet-4-5';
247
+ if (classification === 'medium-change') return 'claude-sonnet-4-5 (no ranged)';
248
+ if (classification === 're-write') return configuredModel + ' (re-write)';
249
+ return configuredModel + ' (no ranged)';
250
+ }
251
+
252
+ // ---------------------------------------------------------------------------
253
+ // Response parsing — shared across builders
254
+ // ---------------------------------------------------------------------------
255
+
256
+ export function parseBuilderResponse(raw: string): BuilderResult {
257
+ // Try parsing as a JSON object with a kind discriminator
258
+ try {
259
+ const parsed = JSON.parse(raw);
260
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
261
+ if (parsed.kind === 'transforms' && Array.isArray(parsed.changes)) {
262
+ return { kind: 'transforms', changes: parsed.changes };
263
+ }
264
+ if (parsed.kind === 'reply' && typeof parsed.text === 'string') {
265
+ return { kind: 'reply', text: parsed.text };
266
+ }
267
+ }
268
+ // Bare array — backward compat
269
+ if (Array.isArray(parsed)) {
270
+ return { kind: 'transforms', changes: parsed };
271
+ }
272
+ } catch {
273
+ // fall through to parseChangeList extraction
274
+ }
275
+
276
+ // Fall back to extracting a JSON array from the response text
277
+ try {
278
+ const changes = parseChangeList(raw);
279
+ return { kind: 'transforms', changes };
280
+ } catch {
281
+ return { kind: 'error', error: new Error('Failed to parse model response as JSON') };
282
+ }
283
+ }