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.
- package/README.md +1 -1
- package/default-pages/application/page.html +42 -0
- package/default-pages/application/page.json +10 -0
- package/default-pages/elevenlabs_effects_studio/page.html +1363 -0
- package/default-pages/elevenlabs_effects_studio/page.json +11 -0
- package/default-pages/elevenlabs_voice_studio/page.html +801 -0
- package/default-pages/elevenlabs_voice_studio/page.json +11 -0
- package/default-pages/{json_tools.html → json_tools/page.html} +13 -11
- package/default-pages/json_tools/page.json +10 -0
- package/default-pages/my_notes/notes/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json +5 -0
- package/default-pages/my_notes/page.html +132 -0
- package/default-pages/{my_notes.json → my_notes/page.json} +2 -2
- package/default-pages/neon_asteroids/files/Ambient_Space.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ambient_Space2.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ambient_Space3.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Asteroid_Explosion.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Hyperspace_Jump.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Laser_Fire.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Menu_Navigate.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Power_Up_Collect.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Saucer_Alert.mp3 +0 -0
- package/default-pages/neon_asteroids/files/Ship_Thrust.mp3 +0 -0
- package/default-pages/neon_asteroids/files/effects.json +74 -0
- package/default-pages/neon_asteroids/page.html +1803 -0
- package/default-pages/{neon_asteroids.json → neon_asteroids/page.json} +3 -3
- package/default-pages/{oregon_trail.html → oregon_trail/page.html} +16 -30
- package/default-pages/{oregon_trail.json → oregon_trail/page.json} +2 -2
- package/default-pages/retro_game_starter/page.html +1308 -0
- package/default-pages/retro_game_starter/page.json +12 -0
- package/default-pages/{sidebar_page.html → sidebar_page/page.html} +12 -10
- package/default-pages/sidebar_page/page.json +10 -0
- package/default-pages/{solar_explorer.html → solar_explorer/page.html} +15 -12
- package/default-pages/{solar_explorer.json → solar_explorer/page.json} +2 -2
- package/default-pages/{solar_tutorial.html → solar_tutorial/page.html} +12 -10
- package/default-pages/solar_tutorial/page.json +10 -0
- package/default-pages/{two-panel_page.html → two-panel_page/page.html} +13 -11
- package/default-pages/two-panel_page/page.json +10 -0
- package/default-pages/{us_map.html → us_map/page.html} +193 -192
- package/default-pages/{us_map.json → us_map/page.json} +12 -12
- package/default-pages/{us_map_1850.html → us_map_1850/page.html} +326 -325
- package/default-pages/{us_map_1850.json → us_map_1850/page.json} +12 -12
- package/default-pages/{western_cities_1850.html → western_cities_1850/page.html} +527 -526
- package/default-pages/{western_cities_1850.json → western_cities_1850/page.json} +12 -12
- package/default-themes/aurora-dawn.json +19 -0
- package/default-themes/aurora-dawn.v3.css +198 -0
- package/default-themes/aurora-dusk.json +19 -0
- package/default-themes/aurora-dusk.v3.css +200 -0
- package/default-themes/cosmos-dawn.json +19 -0
- package/default-themes/cosmos-dawn.v3.css +198 -0
- package/default-themes/cosmos-dusk.json +19 -0
- package/default-themes/cosmos-dusk.v3.css +200 -0
- package/default-themes/high-contrast-dark.json +19 -0
- package/default-themes/high-contrast-dark.v3.css +200 -0
- package/default-themes/high-contrast-light.json +19 -0
- package/default-themes/high-contrast-light.v3.css +198 -0
- package/default-themes/nebula-dawn.v2.css +110 -0
- package/default-themes/nebula-dawn.v3.css +199 -0
- package/default-themes/nebula-dusk.v2.css +104 -0
- package/default-themes/nebula-dusk.v3.css +201 -0
- package/default-themes/solar-flare-dawn.json +19 -0
- package/default-themes/solar-flare-dawn.v3.css +198 -0
- package/default-themes/solar-flare-dusk.json +19 -0
- package/default-themes/solar-flare-dusk.v3.css +200 -0
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +2 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/openclaw/gatewayManager.d.ts +4 -0
- package/dist/agents/openclaw/gatewayManager.d.ts.map +1 -1
- package/dist/agents/openclaw/gatewayManager.js +27 -11
- package/dist/agents/openclaw/gatewayManager.js.map +1 -1
- package/dist/agents/openclaw/openclawProvider.d.ts.map +1 -1
- package/dist/agents/openclaw/openclawProvider.js +2 -4
- package/dist/agents/openclaw/openclawProvider.js.map +1 -1
- package/dist/agents/openclaw/sshTunnelManager.d.ts +2 -0
- package/dist/agents/openclaw/sshTunnelManager.d.ts.map +1 -1
- package/dist/agents/openclaw/sshTunnelManager.js +31 -12
- package/dist/agents/openclaw/sshTunnelManager.js.map +1 -1
- package/dist/builders/anthropic.d.ts +31 -0
- package/dist/builders/anthropic.d.ts.map +1 -0
- package/dist/builders/anthropic.js +227 -0
- package/dist/builders/anthropic.js.map +1 -0
- package/dist/builders/fireworksai.d.ts +9 -0
- package/dist/builders/fireworksai.d.ts.map +1 -0
- package/dist/builders/fireworksai.js +57 -0
- package/dist/builders/fireworksai.js.map +1 -0
- package/dist/builders/index.d.ts +13 -0
- package/dist/builders/index.d.ts.map +1 -0
- package/dist/builders/index.js +31 -0
- package/dist/builders/index.js.map +1 -0
- package/dist/builders/openai.d.ts +8 -0
- package/dist/builders/openai.d.ts.map +1 -0
- package/dist/builders/openai.js +87 -0
- package/dist/builders/openai.js.map +1 -0
- package/dist/builders/types.d.ts +54 -0
- package/dist/builders/types.d.ts.map +1 -0
- package/dist/builders/types.js +211 -0
- package/dist/builders/types.js.map +1 -0
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +3 -2
- package/dist/connectors/index.js.map +1 -1
- package/dist/connectors/registry.d.ts +2 -1
- package/dist/connectors/registry.d.ts.map +1 -1
- package/dist/connectors/registry.js +31 -8
- package/dist/connectors/registry.js.map +1 -1
- package/dist/customizer/Customizer.d.ts +62 -0
- package/dist/customizer/Customizer.d.ts.map +1 -0
- package/dist/customizer/Customizer.js +134 -0
- package/dist/customizer/Customizer.js.map +1 -0
- package/dist/customizer/index.d.ts +4 -0
- package/dist/customizer/index.d.ts.map +1 -0
- package/dist/customizer/index.js +9 -0
- package/dist/customizer/index.js.map +1 -0
- package/dist/files.d.ts +16 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +60 -1
- package/dist/files.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +12 -6
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +150 -133
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +23 -10
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +4 -2
- package/dist/models/anthropic.d.ts.map +1 -1
- package/dist/models/anthropic.js +33 -6
- package/dist/models/anthropic.js.map +1 -1
- package/dist/models/fireworksai.d.ts.map +1 -1
- package/dist/models/fireworksai.js +9 -1
- package/dist/models/fireworksai.js.map +1 -1
- package/dist/models/index.d.ts +1 -1
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +2 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/openai.d.ts +1 -1
- package/dist/models/openai.d.ts.map +1 -1
- package/dist/models/openai.js +24 -3
- package/dist/models/openai.js.map +1 -1
- package/dist/models/types.d.ts +20 -1
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js +6 -1
- package/dist/models/types.js.map +1 -1
- package/dist/pages.d.ts +34 -10
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +229 -79
- package/dist/pages.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts +2 -1
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +2 -2
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/requiresSettings.d.ts +2 -1
- package/dist/service/requiresSettings.d.ts.map +1 -1
- package/dist/service/requiresSettings.js +3 -3
- package/dist/service/requiresSettings.js.map +1 -1
- package/dist/service/server.d.ts +2 -1
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +37 -8
- package/dist/service/server.js.map +1 -1
- package/dist/service/transformPage.d.ts +47 -20
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +514 -293
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useAgentRoutes.d.ts +2 -1
- package/dist/service/useAgentRoutes.d.ts.map +1 -1
- package/dist/service/useAgentRoutes.js +17 -14
- package/dist/service/useAgentRoutes.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts +2 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +287 -172
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.js +17 -17
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useDataRoutes.d.ts.map +1 -1
- package/dist/service/useDataRoutes.js +13 -10
- package/dist/service/useDataRoutes.js.map +1 -1
- package/dist/service/useFileRoutes.d.ts +4 -0
- package/dist/service/useFileRoutes.d.ts.map +1 -0
- package/dist/service/useFileRoutes.js +122 -0
- package/dist/service/useFileRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts +2 -1
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +671 -74
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/service/useSharedDataRoutes.d.ts +4 -0
- package/dist/service/useSharedDataRoutes.d.ts.map +1 -0
- package/dist/service/useSharedDataRoutes.js +107 -0
- package/dist/service/useSharedDataRoutes.js.map +1 -0
- package/dist/service/useSharedFileRoutes.d.ts +4 -0
- package/dist/service/useSharedFileRoutes.d.ts.map +1 -0
- package/dist/service/useSharedFileRoutes.js +121 -0
- package/dist/service/useSharedFileRoutes.js.map +1 -0
- package/dist/settings.d.ts +5 -3
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +12 -10
- package/dist/settings.js.map +1 -1
- package/dist/storage/FsStorageProvider.d.ts +25 -0
- package/dist/storage/FsStorageProvider.d.ts.map +1 -0
- package/dist/storage/FsStorageProvider.js +103 -0
- package/dist/storage/FsStorageProvider.js.map +1 -0
- package/dist/storage/StorageProvider.d.ts +31 -0
- package/dist/storage/StorageProvider.d.ts.map +1 -0
- package/dist/storage/StorageProvider.js +3 -0
- package/dist/storage/StorageProvider.js.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/synthos-cli.d.ts.map +1 -1
- package/dist/synthos-cli.js +4 -3
- package/dist/synthos-cli.js.map +1 -1
- package/dist/themes.d.ts +1 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +65 -28
- package/dist/themes.js.map +1 -1
- package/migration-rules/v1-to-v2.md +193 -0
- package/migration-rules/v2-to-v3.md +481 -0
- package/package.json +11 -10
- package/required-pages/builder/page.html +43 -0
- package/required-pages/builder/page.json +10 -0
- package/required-pages/{pages.html → pages/page.html} +238 -233
- package/required-pages/pages/page.json +10 -0
- package/required-pages/{settings.html → settings/page.html} +389 -275
- package/required-pages/settings/page.json +10 -0
- package/required-pages/synthos_apis/page.html +846 -0
- package/required-pages/synthos_apis/page.json +10 -0
- package/required-pages/{synthos_scripts.html → synthos_scripts/page.html} +13 -11
- package/required-pages/synthos_scripts/page.json +10 -0
- package/src/agents/index.ts +1 -1
- package/src/agents/openclaw/gatewayManager.ts +22 -11
- package/src/agents/openclaw/openclawProvider.ts +2 -4
- package/src/agents/openclaw/sshTunnelManager.ts +19 -11
- package/src/builders/anthropic.ts +283 -0
- package/src/builders/fireworksai.ts +59 -0
- package/src/builders/index.ts +33 -0
- package/src/builders/openai.ts +89 -0
- package/src/builders/types.ts +261 -0
- package/src/connectors/index.ts +1 -1
- package/src/connectors/registry.ts +28 -8
- package/src/customizer/Customizer.ts +163 -0
- package/src/customizer/index.ts +5 -0
- package/src/files.ts +57 -0
- package/src/index.ts +3 -1
- package/src/init.ts +195 -145
- package/src/migrations.ts +30 -10
- package/src/models/anthropic.ts +40 -10
- package/src/models/fireworksai.ts +9 -2
- package/src/models/index.ts +1 -1
- package/src/models/openai.ts +26 -6
- package/src/models/types.ts +31 -1
- package/src/pages.ts +230 -77
- package/src/service/createCompletePrompt.ts +3 -2
- package/src/service/requiresSettings.ts +4 -3
- package/src/service/server.ts +36 -9
- package/src/service/transformPage.ts +557 -326
- package/src/service/useAgentRoutes.ts +19 -14
- package/src/service/useApiRoutes.ts +208 -84
- package/src/service/useConnectorRoutes.ts +18 -18
- package/src/service/useDataRoutes.ts +13 -10
- package/src/service/useFileRoutes.ts +128 -0
- package/src/service/usePageRoutes.ts +730 -81
- package/src/service/useSharedDataRoutes.ts +109 -0
- package/src/service/useSharedFileRoutes.ts +127 -0
- package/src/settings.ts +14 -10
- package/src/storage/FsStorageProvider.ts +87 -0
- package/src/storage/StorageProvider.ts +34 -0
- package/src/storage/index.ts +2 -0
- package/src/synthos-cli.ts +4 -3
- package/src/themes.ts +64 -27
- package/static-files/favicon.svg +12 -0
- package/static-files/fluentlm-instructions.llmd +868 -0
- package/static-files/fluentlm-instructions.md +1595 -0
- package/static-files/fluentlm.css +4844 -0
- package/static-files/fluentlm.js +3602 -0
- package/static-files/fluentlm.min.css +1 -0
- package/static-files/fluentlm.min.js +1 -0
- package/{page-scripts/helpers-v2.js → static-files/helpers.v3.js} +82 -0
- package/static-files/page.v3.js +1290 -0
- package/static-files/recommended-frameworks.llmd +81 -0
- package/static-files/recommended-frameworks.md +137 -0
- package/static-files/retro-game.js +877 -0
- package/static-files/shell.css +797 -0
- package/static-files/theme-dark.css +169 -0
- package/static-files/theme-light.css +169 -0
- package/tests/builders.spec.ts +139 -0
- package/tests/pages.spec.ts +54 -84
- package/tests/transformPage.spec.ts +299 -360
- package/default-pages/application.html +0 -40
- package/default-pages/application.json +0 -1
- package/default-pages/json_tools.json +0 -1
- package/default-pages/my_notes.html +0 -33
- package/default-pages/neon_asteroids.html +0 -77
- package/default-pages/sidebar_page.json +0 -1
- package/default-pages/solar_tutorial.json +0 -1
- package/default-pages/two-panel_page.json +0 -1
- package/dist/service/useGatewayRoutes.d.ts +0 -4
- package/dist/service/useGatewayRoutes.d.ts.map +0 -1
- package/dist/service/useGatewayRoutes.js +0 -168
- package/dist/service/useGatewayRoutes.js.map +0 -1
- package/page-scripts/page-v2.js +0 -656
- package/required-pages/builder.html +0 -48
- package/required-pages/builder.json +0 -1
- package/required-pages/pages.json +0 -1
- package/required-pages/settings.json +0 -1
- package/required-pages/synthos_apis.html +0 -327
- package/required-pages/synthos_apis.json +0 -1
- package/required-pages/synthos_scripts.json +0 -1
- package/src/connectors/airtable/connector.json +0 -27
- package/src/connectors/alpha-vantage/connector.json +0 -26
- package/src/connectors/brave-search/connector.json +0 -26
- package/src/connectors/cloudinary/connector.json +0 -27
- package/src/connectors/deepl/connector.json +0 -28
- package/src/connectors/elevenlabs/connector.json +0 -30
- package/src/connectors/giphy/connector.json +0 -27
- package/src/connectors/github/connector.json +0 -29
- package/src/connectors/huggingface/connector.json +0 -27
- package/src/connectors/imgur/connector.json +0 -29
- package/src/connectors/instagram/connector.json +0 -43
- package/src/connectors/jira/connector.json +0 -28
- package/src/connectors/mapbox/connector.json +0 -26
- package/src/connectors/nasa/connector.json +0 -27
- package/src/connectors/newsapi/connector.json +0 -27
- package/src/connectors/notion/connector.json +0 -28
- package/src/connectors/open-exchange-rates/connector.json +0 -27
- package/src/connectors/openweathermap/connector.json +0 -26
- package/src/connectors/pexels/connector.json +0 -27
- package/src/connectors/resend/connector.json +0 -29
- package/src/connectors/rss2json/connector.json +0 -27
- package/src/connectors/sendgrid/connector.json +0 -27
- package/src/connectors/spoonacular/connector.json +0 -28
- package/src/connectors/stability-ai/connector.json +0 -27
- package/src/connectors/twilio/connector.json +0 -28
- package/src/connectors/unsplash/connector.json +0 -27
- package/src/connectors/wolfram-alpha/connector.json +0 -26
- package/src/connectors/youtube-data/connector.json +0 -30
- /package/{dist/connectors → service-connectors}/airtable/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/alpha-vantage/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/brave-search/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/cloudinary/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/deepl/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/elevenlabs/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/giphy/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/github/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/huggingface/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/imgur/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/instagram/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/jira/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/mapbox/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/nasa/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/newsapi/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/notion/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/open-exchange-rates/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/openweathermap/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/pexels/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/resend/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/rss2json/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/sendgrid/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/spoonacular/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/stability-ai/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/twilio/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/unsplash/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/wolfram-alpha/connector.json +0 -0
- /package/{dist/connectors → service-connectors}/youtube-data/connector.json +0 -0
|
@@ -1,29 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { listScripts } from "../scripts";
|
|
1
|
+
import { AgentCompletion } from "../models";
|
|
3
2
|
import * as cheerio from "cheerio";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { AgentConfig } from "../agents";
|
|
3
|
+
import { Customizer } from "../customizer";
|
|
4
|
+
import { Attachment, Builder, ContextSection } from "../builders/types";
|
|
7
5
|
|
|
8
6
|
// ---------------------------------------------------------------------------
|
|
9
7
|
// Types
|
|
10
8
|
// ---------------------------------------------------------------------------
|
|
11
9
|
|
|
12
|
-
export interface TransformPageArgs
|
|
13
|
-
pagesFolder: string;
|
|
10
|
+
export interface TransformPageArgs {
|
|
14
11
|
pageState: string;
|
|
15
12
|
message: string;
|
|
16
13
|
instructions?: string;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
/** User's configured A2A agents (from settings). */
|
|
26
|
-
configuredAgents?: AgentConfig[];
|
|
14
|
+
builder: Builder;
|
|
15
|
+
additionalSections: ContextSection[];
|
|
16
|
+
/** True when this is the builder page (has chat panel). */
|
|
17
|
+
isBuilder?: boolean;
|
|
18
|
+
/** Product name for branding in prompts (defaults to 'SynthOS'). */
|
|
19
|
+
productName?: string;
|
|
20
|
+
/** Optional image attachments sent alongside the user message. */
|
|
21
|
+
attachments?: Attachment[];
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
export type ChangeOp =
|
|
@@ -31,20 +26,12 @@ export type ChangeOp =
|
|
|
31
26
|
| { op: "replace"; nodeId: string; html: string }
|
|
32
27
|
| { op: "delete"; nodeId: string }
|
|
33
28
|
| { op: "insert"; parentId: string; position: "prepend" | "append" | "before" | "after"; html: string }
|
|
34
|
-
| { op: "style-element"; nodeId: string; style: string }
|
|
29
|
+
| { op: "style-element"; nodeId: string; style: string }
|
|
30
|
+
| { op: "search-replace"; nodeId: string; search: string; replace: string }
|
|
31
|
+
| { op: "search-insert"; nodeId: string; after: string; content: string };
|
|
35
32
|
|
|
36
33
|
export type ChangeList = ChangeOp[];
|
|
37
34
|
|
|
38
|
-
interface FailedOp {
|
|
39
|
-
op: ChangeOp;
|
|
40
|
-
reason: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
interface ApplyResult {
|
|
44
|
-
html: string;
|
|
45
|
-
failedOps: FailedOp[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
35
|
// ---------------------------------------------------------------------------
|
|
49
36
|
// Public entry point
|
|
50
37
|
// ---------------------------------------------------------------------------
|
|
@@ -55,167 +42,179 @@ export interface TransformPageResult {
|
|
|
55
42
|
}
|
|
56
43
|
|
|
57
44
|
export async function transformPage(args: TransformPageArgs): Promise<AgentCompletion<TransformPageResult>> {
|
|
58
|
-
const {
|
|
45
|
+
const { message, builder, additionalSections } = args;
|
|
46
|
+
|
|
47
|
+
// 0. Strip the early error-capture script so the LLM never sees it
|
|
48
|
+
const pageState = stripErrorCapture(args.pageState);
|
|
59
49
|
|
|
60
50
|
// 1. Assign data-node-id to every element
|
|
61
51
|
const { html: annotatedHtml } = assignNodeIds(pageState);
|
|
62
52
|
|
|
63
53
|
try {
|
|
64
|
-
// 2. Build
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
54
|
+
// 2. Build CURRENT_PAGE section
|
|
55
|
+
const currentPage: ContextSection = {
|
|
56
|
+
title: '<CURRENT_PAGE>',
|
|
57
|
+
content: annotatedHtml,
|
|
58
|
+
instructions: '',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// 3. Determine newBuild: if isBuilder, count .chat-message in annotated HTML
|
|
62
|
+
let newBuild = false;
|
|
63
|
+
if (args.isBuilder) {
|
|
64
|
+
const $ = cheerio.load(annotatedHtml, { decodeEntities: false });
|
|
65
|
+
const messageCount = $('#chatMessages .chat-message').length;
|
|
66
|
+
newBuild = messageCount <= 1;
|
|
77
67
|
}
|
|
78
68
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
connectorsBlock = `<CONFIGURED_CONNECTORS>\nThe user has configured and enabled these connectors:\n${blocks.join('\n\n')}\n\nYou may use synthos.connectors.call(connector, method, path, opts) to call them.\nIMPORTANT: Before making any connector call, ALWAYS check that the connector is configured first using synthos.connectors.list(). If the connector is not configured, show the user a friendly message with a link to the Settings > Connectors page (/settings?tab=connectors) so they can set it up.\nDo NOT hardcode API keys. The connector proxy attaches authentication automatically.`;
|
|
69
|
+
// 4. Call builder
|
|
70
|
+
const result = await builder.run(currentPage, additionalSections, message, newBuild, args.attachments);
|
|
71
|
+
|
|
72
|
+
// 5. Switch on result kind
|
|
73
|
+
switch (result.kind) {
|
|
74
|
+
case 'transforms': {
|
|
75
|
+
const applied = applyChangeList(annotatedHtml, result.changes);
|
|
76
|
+
const clean = stripNodeIds(applied);
|
|
77
|
+
const deduped = deduplicateInlineScripts(clean);
|
|
78
|
+
const safe = ensureScriptsBeforeBodyClose(deduped);
|
|
79
|
+
return { completed: true, value: { html: safe, changeCount: result.changes.length } };
|
|
80
|
+
}
|
|
81
|
+
case 'reply': {
|
|
82
|
+
const productName = args.productName ?? 'SynthOS';
|
|
83
|
+
const withReply = appendChatReply(annotatedHtml, message, result.text, productName);
|
|
84
|
+
const clean = stripNodeIds(withReply);
|
|
85
|
+
const deduped = deduplicateInlineScripts(clean);
|
|
86
|
+
const safe = ensureScriptsBeforeBodyClose(deduped);
|
|
87
|
+
return { completed: true, value: { html: safe, changeCount: -1 } };
|
|
88
|
+
}
|
|
89
|
+
case 'error': {
|
|
90
|
+
const productName = args.productName ?? 'SynthOS';
|
|
91
|
+
const errorHtml = appendChatError(annotatedHtml, message, result.error.message, productName);
|
|
92
|
+
const clean = stripNodeIds(errorHtml);
|
|
93
|
+
return { completed: true, value: { html: clean, changeCount: -1 } };
|
|
105
94
|
}
|
|
106
95
|
}
|
|
96
|
+
} catch (err: unknown) {
|
|
97
|
+
// On any error: append error message to chat
|
|
98
|
+
const productName = args.productName ?? 'SynthOS';
|
|
99
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
100
|
+
const errorHtml = appendChatError(annotatedHtml, message, errorMessage, productName);
|
|
101
|
+
const clean = stripNodeIds(errorHtml);
|
|
102
|
+
return { completed: true, value: { html: clean, changeCount: -1 } };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
107
105
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (enabledAgents.length > 0) {
|
|
112
|
-
const agentBlocks = enabledAgents.map(a => {
|
|
113
|
-
let block = `- ${a.name} (id: "${a.id}", provider: ${a.provider})`;
|
|
114
|
-
block += `\n Description: ${a.description}`;
|
|
115
|
-
if (a.capabilities?.streaming) {
|
|
116
|
-
block += `\n Supports streaming: yes`;
|
|
117
|
-
}
|
|
118
|
-
if (a.skills && a.skills.length > 0) {
|
|
119
|
-
const skillList = a.skills.map(s => ` - ${s.name}: ${s.description}`).join('\n');
|
|
120
|
-
block += `\n Skills:\n${skillList}`;
|
|
121
|
-
}
|
|
122
|
-
return block;
|
|
123
|
-
});
|
|
124
|
-
agentsBlock = `<CONFIGURED_AGENTS>\nThe user has configured these agents:\n\n${agentBlocks.join('\n\n')}\n\n${AGENT_API_REFERENCE}`;
|
|
125
|
-
}
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Chat reply helper
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
126
109
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Append a user message and a reply to #chatMessages using cheerio.
|
|
112
|
+
*/
|
|
113
|
+
function appendChatReply(annotatedHtml: string, userMessage: string, replyText: string, productName: string): string {
|
|
114
|
+
const $ = cheerio.load(annotatedHtml, { decodeEntities: false });
|
|
115
|
+
const chatMessages = $('#chatMessages');
|
|
116
|
+
if (chatMessages.length > 0) {
|
|
117
|
+
chatMessages.append(`<div class="chat-message"><p><strong>User:</strong> ${escapeHtml(userMessage)}</p></div>`);
|
|
118
|
+
const replyHtml = simpleMarkdown(replyText);
|
|
119
|
+
chatMessages.append(`<div class="chat-message"><p><strong>${escapeHtml(productName)}:</strong> ${replyHtml}</p></div>`);
|
|
120
|
+
}
|
|
121
|
+
return $.html();
|
|
122
|
+
}
|
|
132
123
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
124
|
+
function escapeHtml(text: string): string {
|
|
125
|
+
return text
|
|
126
|
+
.replace(/&/g, '&')
|
|
127
|
+
.replace(/</g, '<')
|
|
128
|
+
.replace(/>/g, '>')
|
|
129
|
+
.replace(/"/g, '"');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Lightweight markdown-to-HTML converter for chat reply text.
|
|
134
|
+
* Handles: code blocks, inline code, bold, italic, links, unordered/ordered lists, paragraphs.
|
|
135
|
+
*/
|
|
136
|
+
export function simpleMarkdown(text: string): string {
|
|
137
|
+
// Extract fenced code blocks first to protect their contents
|
|
138
|
+
const codeBlocks: string[] = [];
|
|
139
|
+
let processed = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
|
|
140
|
+
const idx = codeBlocks.length;
|
|
141
|
+
const escaped = escapeHtml(code.replace(/\n$/, ''));
|
|
142
|
+
const langAttr = lang ? ` class="language-${escapeHtml(lang)}"` : '';
|
|
143
|
+
codeBlocks.push(`<pre><code${langAttr}>${escaped}</code></pre>`);
|
|
144
|
+
return `\x00CODEBLOCK${idx}\x00`;
|
|
145
|
+
});
|
|
140
146
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
// Split into paragraphs by blank lines
|
|
148
|
+
const blocks = processed.split(/\n{2,}/);
|
|
149
|
+
const htmlBlocks: string[] = [];
|
|
150
|
+
|
|
151
|
+
for (const block of blocks) {
|
|
152
|
+
const trimmed = block.trim();
|
|
153
|
+
if (!trimmed) continue;
|
|
154
|
+
|
|
155
|
+
// Code block placeholder
|
|
156
|
+
if (/^\x00CODEBLOCK\d+\x00$/.test(trimmed)) {
|
|
157
|
+
htmlBlocks.push(trimmed);
|
|
158
|
+
continue;
|
|
145
159
|
}
|
|
146
160
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (firstPass.failedOps.length > 0) {
|
|
157
|
-
console.warn(`transformPage: ${firstPass.failedOps.length} op(s) failed — attempting repair pass`);
|
|
158
|
-
try {
|
|
159
|
-
// Re-assign fresh node IDs on the partially-updated HTML
|
|
160
|
-
const { html: reAnnotatedHtml } = assignNodeIds(stripNodeIds(firstPass.html));
|
|
161
|
-
|
|
162
|
-
// Build compact repair prompt
|
|
163
|
-
const failedSummary = firstPass.failedOps
|
|
164
|
-
.map((f, i) => `${i + 1}. op="${f.op.op}" — ${f.reason}\n original: ${JSON.stringify(f.op)}`)
|
|
165
|
-
.join('\n');
|
|
166
|
-
|
|
167
|
-
const repairSystem: SystemMessage = {
|
|
168
|
-
role: 'system',
|
|
169
|
-
content: `<CURRENT_PAGE>\n${reAnnotatedHtml}\n\n<FAILED_OPERATIONS>\n${failedSummary}`
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const repairPrompt: UserMessage = {
|
|
173
|
-
role: 'user',
|
|
174
|
-
content: repairUSER_MESSAGE
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const repairResult = await completePrompt({ prompt: repairPrompt, system: repairSystem });
|
|
178
|
-
|
|
179
|
-
if (repairResult.completed) {
|
|
180
|
-
const repairChanges = parseChangeList(repairResult.value);
|
|
181
|
-
if (repairChanges.length > 0) {
|
|
182
|
-
const repairPass = applyChangeListWithReport(reAnnotatedHtml, repairChanges);
|
|
183
|
-
const repairSuccessCount = repairChanges.length - repairPass.failedOps.length;
|
|
184
|
-
if (repairPass.failedOps.length > 0) {
|
|
185
|
-
console.warn(`transformPage: repair pass had ${repairPass.failedOps.length} remaining failure(s) — keeping partial result`);
|
|
186
|
-
}
|
|
187
|
-
finalHtml = repairPass.html;
|
|
188
|
-
successCount += repairSuccessCount;
|
|
189
|
-
console.log(`transformPage: repair pass applied ${repairSuccessCount} fix(es)`);
|
|
190
|
-
} else {
|
|
191
|
-
console.log('transformPage: repair pass returned no changes (model deemed repairs unnecessary)');
|
|
192
|
-
}
|
|
193
|
-
} else {
|
|
194
|
-
console.warn('transformPage: repair LLM call failed — keeping partial result from first pass');
|
|
195
|
-
}
|
|
196
|
-
} catch (repairErr: unknown) {
|
|
197
|
-
const msg = repairErr instanceof Error ? repairErr.message : String(repairErr);
|
|
198
|
-
console.warn(`transformPage: repair pass error — ${msg} — keeping partial result from first pass`);
|
|
199
|
-
}
|
|
161
|
+
// Unordered list (lines starting with - or *)
|
|
162
|
+
if (/^[\-\*] /m.test(trimmed) && trimmed.split('\n').every(l => /^[\-\*] /.test(l.trim()) || l.trim() === '')) {
|
|
163
|
+
const items = trimmed.split('\n')
|
|
164
|
+
.map(l => l.trim())
|
|
165
|
+
.filter(l => l)
|
|
166
|
+
.map(l => `<li>${inlineMarkdown(l.replace(/^[\-\*] /, ''))}</li>`)
|
|
167
|
+
.join('');
|
|
168
|
+
htmlBlocks.push(`<ul>${items}</ul>`);
|
|
169
|
+
continue;
|
|
200
170
|
}
|
|
201
171
|
|
|
202
|
-
//
|
|
203
|
-
|
|
172
|
+
// Ordered list (lines starting with 1. 2. etc.)
|
|
173
|
+
if (/^\d+\. /m.test(trimmed) && trimmed.split('\n').every(l => /^\d+\. /.test(l.trim()) || l.trim() === '')) {
|
|
174
|
+
const items = trimmed.split('\n')
|
|
175
|
+
.map(l => l.trim())
|
|
176
|
+
.filter(l => l)
|
|
177
|
+
.map(l => `<li>${inlineMarkdown(l.replace(/^\d+\. /, ''))}</li>`)
|
|
178
|
+
.join('');
|
|
179
|
+
htmlBlocks.push(`<ol>${items}</ol>`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
204
182
|
|
|
205
|
-
//
|
|
206
|
-
|
|
183
|
+
// Regular paragraph
|
|
184
|
+
htmlBlocks.push(`<p>${inlineMarkdown(trimmed.replace(/\n/g, '<br>'))}</p>`);
|
|
185
|
+
}
|
|
207
186
|
|
|
208
|
-
|
|
209
|
-
|
|
187
|
+
// Restore code blocks
|
|
188
|
+
let result = htmlBlocks.join('');
|
|
189
|
+
result = result.replace(/\x00CODEBLOCK(\d+)\x00/g, (_m, idx) => codeBlocks[parseInt(idx)]);
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
210
192
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
193
|
+
/** Apply inline markdown formatting: bold, italic, inline code, links. */
|
|
194
|
+
function inlineMarkdown(text: string): string {
|
|
195
|
+
// Inline code (protect from further processing)
|
|
196
|
+
const codes: string[] = [];
|
|
197
|
+
let result = text.replace(/`([^`]+)`/g, (_m, code) => {
|
|
198
|
+
const idx = codes.length;
|
|
199
|
+
codes.push(`<code>${escapeHtml(code)}</code>`);
|
|
200
|
+
return `\x00CODE${idx}\x00`;
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Bold (**text** or __text__)
|
|
204
|
+
result = result.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
205
|
+
result = result.replace(/__(.+?)__/g, '<strong>$1</strong>');
|
|
206
|
+
|
|
207
|
+
// Italic (*text* or _text_)
|
|
208
|
+
result = result.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
209
|
+
result = result.replace(/(?<!\w)_(.+?)_(?!\w)/g, '<em>$1</em>');
|
|
210
|
+
|
|
211
|
+
// Links [text](url)
|
|
212
|
+
result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
213
|
+
|
|
214
|
+
// Restore inline code
|
|
215
|
+
result = result.replace(/\x00CODE(\d+)\x00/g, (_m, idx) => codes[parseInt(idx)]);
|
|
216
|
+
|
|
217
|
+
return result;
|
|
219
218
|
}
|
|
220
219
|
|
|
221
220
|
// ---------------------------------------------------------------------------
|
|
@@ -247,6 +246,69 @@ export function stripNodeIds(html: string): string {
|
|
|
247
246
|
return $.html();
|
|
248
247
|
}
|
|
249
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Remove the early error-capture script injected into <head> so the LLM
|
|
251
|
+
* never sees it during page transformation.
|
|
252
|
+
*/
|
|
253
|
+
function stripErrorCapture(html: string): string {
|
|
254
|
+
const id = 'synthos-error-capture';
|
|
255
|
+
if (!html.includes(id)) return html;
|
|
256
|
+
const $ = cheerio.load(html, { decodeEntities: false });
|
|
257
|
+
$(`#${id}`).remove();
|
|
258
|
+
return $.html();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Find `needle` in `haystack` using whitespace-normalized comparison.
|
|
263
|
+
* Collapses runs of whitespace (spaces, tabs, newlines) into a single space
|
|
264
|
+
* for comparison purposes but returns the position in the original string.
|
|
265
|
+
* Returns -1 if no match found.
|
|
266
|
+
*/
|
|
267
|
+
export function normalizedIndexOf(haystack: string, needle: string): { start: number; end: number } | null {
|
|
268
|
+
// Build a mapping from normalized-string positions to original-string positions
|
|
269
|
+
const normChars: string[] = [];
|
|
270
|
+
const origPositions: number[] = []; // origPositions[i] = original index of normChars[i]
|
|
271
|
+
let inWhitespace = false;
|
|
272
|
+
|
|
273
|
+
for (let i = 0; i < haystack.length; i++) {
|
|
274
|
+
const ch = haystack[i];
|
|
275
|
+
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
276
|
+
if (!inWhitespace) {
|
|
277
|
+
normChars.push(' ');
|
|
278
|
+
origPositions.push(i);
|
|
279
|
+
inWhitespace = true;
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
normChars.push(ch);
|
|
283
|
+
origPositions.push(i);
|
|
284
|
+
inWhitespace = false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const normHaystack = normChars.join('');
|
|
289
|
+
|
|
290
|
+
// Normalize the needle the same way
|
|
291
|
+
const normNeedle = needle.replace(/\s+/g, ' ');
|
|
292
|
+
|
|
293
|
+
const idx = normHaystack.indexOf(normNeedle);
|
|
294
|
+
if (idx === -1) return null;
|
|
295
|
+
|
|
296
|
+
const start = origPositions[idx];
|
|
297
|
+
// The end position: find the original position of the last matched char, then go one past
|
|
298
|
+
const lastNormIdx = idx + normNeedle.length - 1;
|
|
299
|
+
const lastOrigPos = origPositions[lastNormIdx];
|
|
300
|
+
// Advance past any trailing whitespace that was collapsed in the original
|
|
301
|
+
let end = lastOrigPos + 1;
|
|
302
|
+
if (haystack[lastOrigPos] === ' ' || haystack[lastOrigPos] === '\t' || haystack[lastOrigPos] === '\n' || haystack[lastOrigPos] === '\r') {
|
|
303
|
+
// The last matched normalized char was a whitespace collapse — extend to include all original whitespace
|
|
304
|
+
while (end < haystack.length && (haystack[end] === ' ' || haystack[end] === '\t' || haystack[end] === '\n' || haystack[end] === '\r')) {
|
|
305
|
+
end++;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return { start, end };
|
|
310
|
+
}
|
|
311
|
+
|
|
250
312
|
/**
|
|
251
313
|
* Remove duplicate inline `<script>` blocks using a two-pass approach.
|
|
252
314
|
*
|
|
@@ -385,6 +447,29 @@ function isElementLocked(el: cheerio.Cheerio, $: cheerio.Root): boolean {
|
|
|
385
447
|
return el.attr('data-locked') !== undefined;
|
|
386
448
|
}
|
|
387
449
|
|
|
450
|
+
/**
|
|
451
|
+
* If the target element is a <script> or <style> and the html is wrapped in a
|
|
452
|
+
* redundant matching tag, strip the outer tag to avoid nesting (e.g.
|
|
453
|
+
* `<script>` inside `<script>`). Returns the inner content when unwrapped.
|
|
454
|
+
*/
|
|
455
|
+
function unwrapRedundantTag(tagName: string | undefined, html: string): string {
|
|
456
|
+
if (tagName !== 'script' && tagName !== 'style') return html;
|
|
457
|
+
const re = new RegExp(`^\\s*<${tagName}[^>]*>([\\s\\S]*)</${tagName}>\\s*$`, 'i');
|
|
458
|
+
const match = html.match(re);
|
|
459
|
+
return match ? match[1] : html;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Strip any `<script>` or `<style>` tags from text that will be injected
|
|
464
|
+
* inside an existing script/style block (search-replace, search-insert).
|
|
465
|
+
*/
|
|
466
|
+
function stripNestedBlockTags(tagName: string | undefined, text: string): string {
|
|
467
|
+
if (tagName !== 'script' && tagName !== 'style') return text;
|
|
468
|
+
return text
|
|
469
|
+
.replace(new RegExp(`<${tagName}[^>]*>`, 'gi'), '')
|
|
470
|
+
.replace(new RegExp(`</${tagName}>`, 'gi'), '');
|
|
471
|
+
}
|
|
472
|
+
|
|
388
473
|
/**
|
|
389
474
|
* Apply a list of CRUD operations to annotated HTML (elements must have `data-node-id`).
|
|
390
475
|
*/
|
|
@@ -399,7 +484,8 @@ export function applyChangeList(html: string, changes: ChangeList): string {
|
|
|
399
484
|
console.warn(`applyChangeList: skipping update — node ${change.nodeId} not found (already removed?)`);
|
|
400
485
|
break;
|
|
401
486
|
}
|
|
402
|
-
el.
|
|
487
|
+
const tag = el.prop('tagName')?.toLowerCase();
|
|
488
|
+
el.html(unwrapRedundantTag(tag, change.html));
|
|
403
489
|
break;
|
|
404
490
|
}
|
|
405
491
|
case 'replace': {
|
|
@@ -412,7 +498,16 @@ export function applyChangeList(html: string, changes: ChangeList): string {
|
|
|
412
498
|
console.warn(`applyChangeList: skipping replace — node ${change.nodeId} is data-locked`);
|
|
413
499
|
break;
|
|
414
500
|
}
|
|
415
|
-
|
|
501
|
+
// If the target is a <script> or <style> and the html doesn't
|
|
502
|
+
// include the outer tag (or wraps it redundantly), treat as an
|
|
503
|
+
// update (set inner content) instead of replacing the element.
|
|
504
|
+
const tagName = el.prop('tagName')?.toLowerCase();
|
|
505
|
+
const cleaned = unwrapRedundantTag(tagName, change.html);
|
|
506
|
+
if ((tagName === 'script' || tagName === 'style') && !cleaned.trimStart().startsWith('<')) {
|
|
507
|
+
el.html(cleaned);
|
|
508
|
+
} else {
|
|
509
|
+
el.replaceWith(change.html);
|
|
510
|
+
}
|
|
416
511
|
break;
|
|
417
512
|
}
|
|
418
513
|
case 'delete': {
|
|
@@ -430,13 +525,20 @@ export function applyChangeList(html: string, changes: ChangeList): string {
|
|
|
430
525
|
}
|
|
431
526
|
case 'insert': {
|
|
432
527
|
const parent = $(`[data-node-id="${change.parentId}"]`);
|
|
433
|
-
if (parent.length === 0)
|
|
528
|
+
if (parent.length === 0) {
|
|
529
|
+
console.warn(`applyChangeList: skipping insert — parent node ${change.parentId} not found`);
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
// Unwrap redundant tags when inserting into script/style
|
|
533
|
+
const parentTag = parent.prop('tagName')?.toLowerCase();
|
|
534
|
+
const insertHtml = unwrapRedundantTag(parentTag, change.html);
|
|
434
535
|
switch (change.position) {
|
|
435
|
-
case 'prepend': parent.prepend(
|
|
436
|
-
case 'append': parent.append(
|
|
437
|
-
case 'before': parent.before(
|
|
438
|
-
case 'after': parent.after(
|
|
439
|
-
default:
|
|
536
|
+
case 'prepend': parent.prepend(insertHtml); break;
|
|
537
|
+
case 'append': parent.append(insertHtml); break;
|
|
538
|
+
case 'before': parent.before(insertHtml); break;
|
|
539
|
+
case 'after': parent.after(insertHtml); break;
|
|
540
|
+
default:
|
|
541
|
+
console.warn(`applyChangeList: skipping insert — unknown position "${(change as any).position}"`);
|
|
440
542
|
}
|
|
441
543
|
break;
|
|
442
544
|
}
|
|
@@ -453,101 +555,49 @@ export function applyChangeList(html: string, changes: ChangeList): string {
|
|
|
453
555
|
el.attr('style', change.style);
|
|
454
556
|
break;
|
|
455
557
|
}
|
|
456
|
-
|
|
457
|
-
throw new Error(`Unknown change op: "${(change as any).op}"`);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
return $.html();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* Apply a list of CRUD operations and report any ops that failed due to
|
|
466
|
-
* missing nodes (instead of throwing). Unknown op types still throw.
|
|
467
|
-
*/
|
|
468
|
-
function applyChangeListWithReport(html: string, changes: ChangeList): ApplyResult {
|
|
469
|
-
const $ = cheerio.load(html, { decodeEntities: false });
|
|
470
|
-
const failedOps: FailedOp[] = [];
|
|
471
|
-
|
|
472
|
-
for (const change of changes) {
|
|
473
|
-
switch (change.op) {
|
|
474
|
-
case 'update': {
|
|
475
|
-
const el = $(`[data-node-id="${change.nodeId}"]`);
|
|
476
|
-
if (el.length === 0) {
|
|
477
|
-
const reason = `node ${change.nodeId} not found (already removed?)`;
|
|
478
|
-
console.warn(`applyChangeListWithReport: skipping update — ${reason}`);
|
|
479
|
-
failedOps.push({ op: change, reason });
|
|
480
|
-
break;
|
|
481
|
-
}
|
|
482
|
-
el.html(change.html);
|
|
483
|
-
break;
|
|
484
|
-
}
|
|
485
|
-
case 'replace': {
|
|
486
|
-
const el = $(`[data-node-id="${change.nodeId}"]`);
|
|
487
|
-
if (el.length === 0) {
|
|
488
|
-
const reason = `node ${change.nodeId} not found (already removed?)`;
|
|
489
|
-
console.warn(`applyChangeListWithReport: skipping replace — ${reason}`);
|
|
490
|
-
failedOps.push({ op: change, reason });
|
|
491
|
-
break;
|
|
492
|
-
}
|
|
493
|
-
if (isElementLocked(el, $)) {
|
|
494
|
-
const reason = `node ${change.nodeId} is data-locked`;
|
|
495
|
-
console.warn(`applyChangeListWithReport: skipping replace — ${reason}`);
|
|
496
|
-
failedOps.push({ op: change, reason });
|
|
497
|
-
break;
|
|
498
|
-
}
|
|
499
|
-
el.replaceWith(change.html);
|
|
500
|
-
break;
|
|
501
|
-
}
|
|
502
|
-
case 'delete': {
|
|
558
|
+
case 'search-replace': {
|
|
503
559
|
const el = $(`[data-node-id="${change.nodeId}"]`);
|
|
504
560
|
if (el.length === 0) {
|
|
505
|
-
|
|
506
|
-
console.warn(`applyChangeListWithReport: skipping delete — ${reason}`);
|
|
507
|
-
failedOps.push({ op: change, reason });
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
510
|
-
if (isElementLocked(el, $)) {
|
|
511
|
-
const reason = `node ${change.nodeId} is data-locked`;
|
|
512
|
-
console.warn(`applyChangeListWithReport: skipping delete — ${reason}`);
|
|
513
|
-
failedOps.push({ op: change, reason });
|
|
514
|
-
break;
|
|
515
|
-
}
|
|
516
|
-
el.remove();
|
|
517
|
-
break;
|
|
518
|
-
}
|
|
519
|
-
case 'insert': {
|
|
520
|
-
const parent = $(`[data-node-id="${change.parentId}"]`);
|
|
521
|
-
if (parent.length === 0) {
|
|
522
|
-
const reason = `parent node ${change.parentId} not found`;
|
|
523
|
-
console.warn(`applyChangeListWithReport: skipping insert — ${reason}`);
|
|
524
|
-
failedOps.push({ op: change, reason });
|
|
561
|
+
console.warn(`applyChangeList: skipping search-replace — node ${change.nodeId} not found (already removed?)`);
|
|
525
562
|
break;
|
|
526
563
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
564
|
+
const srTag = el.prop('tagName')?.toLowerCase();
|
|
565
|
+
const replaceText = stripNestedBlockTags(srTag, change.replace);
|
|
566
|
+
const content = el.html() ?? '';
|
|
567
|
+
const exactIdx = content.indexOf(change.search);
|
|
568
|
+
if (exactIdx !== -1) {
|
|
569
|
+
el.html(content.slice(0, exactIdx) + replaceText + content.slice(exactIdx + change.search.length));
|
|
570
|
+
} else {
|
|
571
|
+
const norm = normalizedIndexOf(content, change.search);
|
|
572
|
+
if (norm) {
|
|
573
|
+
el.html(content.slice(0, norm.start) + replaceText + content.slice(norm.end));
|
|
574
|
+
} else {
|
|
575
|
+
console.warn(`applyChangeList: skipping search-replace — search text not found in node ${change.nodeId}`);
|
|
576
|
+
}
|
|
533
577
|
}
|
|
534
578
|
break;
|
|
535
579
|
}
|
|
536
|
-
case '
|
|
580
|
+
case 'search-insert': {
|
|
537
581
|
const el = $(`[data-node-id="${change.nodeId}"]`);
|
|
538
582
|
if (el.length === 0) {
|
|
539
|
-
|
|
540
|
-
console.warn(`applyChangeListWithReport: skipping style-element — ${reason}`);
|
|
541
|
-
failedOps.push({ op: change, reason });
|
|
583
|
+
console.warn(`applyChangeList: skipping search-insert — node ${change.nodeId} not found (already removed?)`);
|
|
542
584
|
break;
|
|
543
585
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
586
|
+
const siTag = el.prop('tagName')?.toLowerCase();
|
|
587
|
+
const insertContent = stripNestedBlockTags(siTag, change.content);
|
|
588
|
+
const content = el.html() ?? '';
|
|
589
|
+
const exactIdx = content.indexOf(change.after);
|
|
590
|
+
if (exactIdx !== -1) {
|
|
591
|
+
const insertPos = exactIdx + change.after.length;
|
|
592
|
+
el.html(content.slice(0, insertPos) + insertContent + content.slice(insertPos));
|
|
593
|
+
} else {
|
|
594
|
+
const norm = normalizedIndexOf(content, change.after);
|
|
595
|
+
if (norm) {
|
|
596
|
+
el.html(content.slice(0, norm.end) + insertContent + content.slice(norm.end));
|
|
597
|
+
} else {
|
|
598
|
+
console.warn(`applyChangeList: skipping search-insert — after text not found in node ${change.nodeId}`);
|
|
599
|
+
}
|
|
549
600
|
}
|
|
550
|
-
el.attr('style', change.style);
|
|
551
601
|
break;
|
|
552
602
|
}
|
|
553
603
|
default:
|
|
@@ -555,27 +605,24 @@ function applyChangeListWithReport(html: string, changes: ChangeList): ApplyResu
|
|
|
555
605
|
}
|
|
556
606
|
}
|
|
557
607
|
|
|
558
|
-
return
|
|
608
|
+
return $.html();
|
|
559
609
|
}
|
|
560
610
|
|
|
561
611
|
/**
|
|
562
|
-
*
|
|
612
|
+
* Append a user message and a styled error message to #chatMessages.
|
|
563
613
|
*/
|
|
564
|
-
|
|
614
|
+
function appendChatError(html: string, userMessage: string, errorDetails: string, productName: string): string {
|
|
565
615
|
const $ = cheerio.load(html, { decodeEntities: false });
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
} else {
|
|
576
|
-
return html + scriptTag;
|
|
616
|
+
const chatMessages = $('#chatMessages');
|
|
617
|
+
if (chatMessages.length > 0) {
|
|
618
|
+
chatMessages.append(
|
|
619
|
+
`<div class="chat-message"><p><strong>User:</strong> ${escapeHtml(userMessage)}</p></div>`
|
|
620
|
+
);
|
|
621
|
+
chatMessages.append(
|
|
622
|
+
`<div class="chat-message chat-message-error"><p><strong>${escapeHtml(productName)}:</strong> Something went wrong \u2014 please try again.</p>`
|
|
623
|
+
+ `<p class="chat-error-details">${escapeHtml(errorDetails)}</p></div>`
|
|
624
|
+
);
|
|
577
625
|
}
|
|
578
|
-
|
|
579
626
|
return $.html();
|
|
580
627
|
}
|
|
581
628
|
|
|
@@ -610,19 +657,20 @@ export function parseChangeList(response: string): ChangeList {
|
|
|
610
657
|
// Prompt constants
|
|
611
658
|
// ---------------------------------------------------------------------------
|
|
612
659
|
|
|
613
|
-
|
|
614
|
-
`<MESSAGE_FORMAT>
|
|
615
|
-
<div class="chat-message"><p><strong>{
|
|
616
|
-
|
|
660
|
+
export function getMessageFormat(productName: string): string {
|
|
661
|
+
return `<MESSAGE_FORMAT>
|
|
662
|
+
<div class="chat-message"><p><strong>{${productName}: | User:}</strong> {message contents}</p></div>
|
|
663
|
+
`;
|
|
664
|
+
}
|
|
617
665
|
|
|
618
|
-
|
|
619
|
-
`Apply the users <USER_MESSAGE> to the .viewerPanel of the <CURRENT_PAGE> by generating a list of changes in JSON format.
|
|
666
|
+
export function getTransformInstr(productName: string): string {
|
|
667
|
+
return `Apply the users <USER_MESSAGE> to the .viewerPanel of the <CURRENT_PAGE> by generating a list of changes in JSON format.
|
|
620
668
|
Never remove any element that has a data-locked attribute. You may modfiy the inner text of a data-locked element or any of its unlocked child elements.
|
|
621
669
|
|
|
622
|
-
If the <USER_MESSAGE> involves clearning the chat history, remove all .chat-message elements inside the #chatMessages container except for the first
|
|
623
|
-
If there's no <USER_MESSAGE> add a
|
|
624
|
-
If there is a <USER_MESSAGE> but the intent is unclear, add a User: message with the <USER_MESSAGE> to the chat and add a
|
|
625
|
-
If there is a <USER_MESSAGE> with clear intent, add a User: message with the <USER_MESSAGE> to the chat and add a
|
|
670
|
+
If the <USER_MESSAGE> involves clearning the chat history, remove all .chat-message elements inside the #chatMessages container except for the first ${productName}: message. You may modify that message contents if requested.
|
|
671
|
+
If there's no <USER_MESSAGE> add a ${productName}: message to the chat with aasking the user what they would like to do.
|
|
672
|
+
If there is a <USER_MESSAGE> but the intent is unclear, add a User: message with the <USER_MESSAGE> to the chat and add a ${productName}: message asking the user for clarification on their intent.
|
|
673
|
+
If there is a <USER_MESSAGE> with clear intent, add a User: message with the <USER_MESSAGE> to the chat and add a ${productName}: message explaining your change or answering their question.
|
|
626
674
|
If a <USER_MESSAGE> is overly long, summarize the User: message.
|
|
627
675
|
|
|
628
676
|
When updating the .viewerPanel you may alse add/remove/update style blocks to the header unless they're data-locked. Use inline styles if you need to modify the .viewerPanel itself.
|
|
@@ -646,31 +694,11 @@ Do not add duplicate script blocks with the same logic! Consolidate inline scrip
|
|
|
646
694
|
Each element in the CURRENT_PAGE has a data-node-id attribute. Don't use the id attribute for targeting nodes (reserve it for scripts and styles) — use data-node-id.
|
|
647
695
|
If you're trying to assign an id to script or style block, use "replace" not "update".
|
|
648
696
|
Your first operation should always be an update to your thoughts block, where you can reason through the user's request and plan your changes before applying them to the page.
|
|
649
|
-
Return a JSON array of change operations to apply to the page. Do NOT return the full HTML page.
|
|
650
|
-
|
|
651
|
-
Each operation must be one of:
|
|
652
|
-
{ "op": "update", "nodeId": "<data-node-id>", "html": "<new innerHTML>" }
|
|
653
|
-
— replaces the innerHTML of the target element
|
|
654
697
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
{ "op": "delete", "nodeId": "<data-node-id>" }
|
|
659
|
-
— removes the element from the page
|
|
660
|
-
|
|
661
|
-
{ "op": "insert", "parentId": "<data-node-id>", "position": "prepend"|"append"|"before"|"after", "html": "<new element HTML>" }
|
|
662
|
-
— inserts new HTML relative to the parent element
|
|
663
|
-
|
|
664
|
-
{ "op": "style-element", "nodeId": "<data-node-id>", "style": "<css style string>" }
|
|
665
|
-
— sets the style attribute of the target element (must be unlocked)
|
|
666
|
-
|
|
667
|
-
Return ONLY the JSON array. Example:
|
|
668
|
-
[
|
|
669
|
-
{ "op": "update", "nodeId": "5", "html": "<p>Hello world</p>" },
|
|
670
|
-
{ "op": "insert", "parentId": "3", "position": "append", "html": "<div class=\\"msg\\">New message</div>" }
|
|
671
|
-
]`;
|
|
698
|
+
CRITICAL — FluentLM components: If a <FLUENTLM_COMPONENTS> section is present, you MUST use those components for all standard UI elements (buttons, inputs, selects, dialogs, tabs, cards, toggles, etc.). Never create custom CSS classes for UI controls that have a FluentLM equivalent. Refer to <FLUENTLM_COMPONENTS> for the exact class names and markup patterns.`;
|
|
699
|
+
}
|
|
672
700
|
|
|
673
|
-
const AGENT_API_REFERENCE =
|
|
701
|
+
export const AGENT_API_REFERENCE =
|
|
674
702
|
`## Agent API
|
|
675
703
|
|
|
676
704
|
Check availability first (required):
|
|
@@ -704,7 +732,221 @@ Stream with attachments:
|
|
|
704
732
|
IMPORTANT: Always check synthos.agents.list({ enabled: true }) before calling an agent.
|
|
705
733
|
If no agents are configured, show the user a link to Settings > Agents (/settings?tab=agents).`;
|
|
706
734
|
|
|
707
|
-
|
|
735
|
+
// ---------------------------------------------------------------------------
|
|
736
|
+
// Route hint blocks — keyed by feature group so they can be filtered
|
|
737
|
+
// ---------------------------------------------------------------------------
|
|
738
|
+
|
|
739
|
+
export const DEFAULT_ROUTE_HINTS = new Map<string, string>([
|
|
740
|
+
['data', `GET /api/data/:page/:table
|
|
741
|
+
description: Retrieve all rows from a page-scoped table (tables are stored per-page). Supports pagination via query params.
|
|
742
|
+
query params: limit (number, optional) — max rows to return; offset (number, optional, default 0) — rows to skip
|
|
743
|
+
response (without limit): Array of JSON rows [{ id: string, ... }]
|
|
744
|
+
response (with limit): { items: [{ id: string, ... }], total: number, offset: number, limit: number, hasMore: boolean }
|
|
745
|
+
|
|
746
|
+
GET /api/data/:page/:table/:id
|
|
747
|
+
description: Retrieve a single row from a page-scoped table
|
|
748
|
+
response: JSON row { id: string, ... }
|
|
749
|
+
|
|
750
|
+
POST /api/data/:page/:table
|
|
751
|
+
description: Replaces or adds a single row to a page-scoped table and returns the row
|
|
752
|
+
request: JSON row { id?: string, ... }
|
|
753
|
+
response: { id: string, ... }
|
|
754
|
+
|
|
755
|
+
DELETE /api/data/:page/:table/:id
|
|
756
|
+
description: Delete a single row from a page-scoped table
|
|
757
|
+
response: { success: true }
|
|
758
|
+
|
|
759
|
+
synthos.data.list(table, opts?) — GET /api/data/:page/:table (auto-scoped to current page; opts: { limit?, offset? } — when limit is set, returns { items, total, offset, limit, hasMore })
|
|
760
|
+
synthos.data.get(table, id) — GET /api/data/:page/:table/:id (auto-scoped to current page)
|
|
761
|
+
synthos.data.save(table, row) — POST /api/data/:page/:table (auto-scoped to current page)
|
|
762
|
+
synthos.data.remove(table, id) — DELETE /api/data/:page/:table/:id (auto-scoped to current page)`],
|
|
763
|
+
|
|
764
|
+
['api', `POST /api/generate/image
|
|
765
|
+
description: Generate an image based on a prompt
|
|
766
|
+
request: { prompt: string, shape: 'square' | 'portrait' | 'landscape', style: 'vivid' | 'natural' }
|
|
767
|
+
response: { url: string }
|
|
768
|
+
|
|
769
|
+
POST /api/generate/completion
|
|
770
|
+
description: Generates a text completion based on a prompt
|
|
771
|
+
request: { prompt: string, temperature?: number }
|
|
772
|
+
response: { answer: string, explanation: string }
|
|
773
|
+
|
|
774
|
+
synthos.generate.image({ prompt, shape, style }) — POST /api/generate/image
|
|
775
|
+
synthos.generate.completion({ prompt, temperature? }) — POST /api/generate/completion`],
|
|
776
|
+
|
|
777
|
+
['pages', `GET /api/pages
|
|
778
|
+
description: Retrieve a list of all pages with metadata
|
|
779
|
+
response: Array of { name: string, title: string, categories: string[], pinned: boolean, createdDate: string, lastModified: string, pageVersion: number, mode: 'unlocked' | 'locked' }
|
|
780
|
+
|
|
781
|
+
GET /api/pages/:name
|
|
782
|
+
description: Retrieve metadata for a single page
|
|
783
|
+
response: { title: string, categories: string[], pinned: boolean, createdDate: string, lastModified: string, pageVersion: number, mode: 'unlocked' | 'locked' }
|
|
784
|
+
|
|
785
|
+
POST /api/pages/:name
|
|
786
|
+
description: Update page metadata (merge semantics — send only fields to change; lastModified is auto-set)
|
|
787
|
+
request: { title?: string, categories?: string[], pinned?: boolean, mode?: 'unlocked' | 'locked' }
|
|
788
|
+
response: Full metadata object
|
|
789
|
+
|
|
790
|
+
DELETE /api/pages/:name
|
|
791
|
+
description: Delete a user page (cannot delete required/system pages)
|
|
792
|
+
response: { deleted: true }
|
|
793
|
+
|
|
794
|
+
POST /api/pages/:name/ask
|
|
795
|
+
description: Ask a question about a page with full HTML context
|
|
796
|
+
request: { question: string }
|
|
797
|
+
response: { answer: string }
|
|
798
|
+
|
|
799
|
+
synthos.pages.list() — GET /api/pages
|
|
800
|
+
synthos.pages.get(name) — GET /api/pages/:name
|
|
801
|
+
synthos.pages.update(name, metadata) — POST /api/pages/:name
|
|
802
|
+
synthos.pages.remove(name) — DELETE /api/pages/:name
|
|
803
|
+
synthos.pages.ask(name, question) — POST /api/pages/:name/ask`],
|
|
804
|
+
|
|
805
|
+
['scripts', `POST /api/scripts/:id
|
|
806
|
+
description: Execute a script with the passed in variables
|
|
807
|
+
request: { [key: string]: string }
|
|
808
|
+
response: string
|
|
809
|
+
|
|
810
|
+
synthos.scripts.run(id, variables) — POST /api/scripts/:id`],
|
|
811
|
+
|
|
812
|
+
['search', `POST /api/search/web
|
|
813
|
+
description: Search the web using Brave Search (must be enabled in Settings > Connectors)
|
|
814
|
+
request: { query: string, count?: number, country?: string, freshness?: string }
|
|
815
|
+
response: { results: [{ title: string, url: string, description: string }] }
|
|
816
|
+
|
|
817
|
+
synthos.search.web(query, opts?) — POST /api/search/web (opts: { count?, country?, freshness? })`],
|
|
818
|
+
|
|
819
|
+
['agents', `GET /api/agents
|
|
820
|
+
description: List configured agents (A2A and OpenClaw). Supports ?enabled=true and ?provider=a2a|openclaw filters.
|
|
821
|
+
response: [{ id: string, name: string, description: string, url: string, enabled: boolean, provider: 'a2a'|'openclaw', capabilities?: object }]
|
|
822
|
+
|
|
823
|
+
POST /api/agents/:id/send
|
|
824
|
+
description: Send a text message to an agent (works for both A2A and OpenClaw protocols) and receive a normalized response
|
|
825
|
+
request: { message: string, attachments?: [{ fileName: string, mimeType: string, content: string }] }
|
|
826
|
+
response: { kind: 'message'|'task', text?: string, raw: object }
|
|
827
|
+
|
|
828
|
+
POST /api/agents/:id/stream
|
|
829
|
+
description: Send a message and receive a streaming SSE response (text/event-stream). Each event is JSON: { kind: 'text'|'status'|'artifact'|'done'|'error', data: any }
|
|
830
|
+
request: { message: string, attachments?: [{ fileName: string, mimeType: string, content: string }] }
|
|
831
|
+
response: SSE stream
|
|
832
|
+
|
|
833
|
+
synthos.agents.list(opts?) — GET /api/agents (returns configured agents; opts: { enabled?, provider? }; returns [{ id, name, description, url, enabled, provider, capabilities }])
|
|
834
|
+
synthos.agents.send(agentId, message, attachments?) — POST /api/agents/:id/send (sends a text message to any agent, returns normalized { kind, text, raw }; attachments: [{ fileName, mimeType, content }])
|
|
835
|
+
synthos.agents.sendStream(agentId, message, onEvent, attachments?) — POST /api/agents/:id/stream (SSE streaming; onEvent receives { kind, data }; returns { close() } handle; attachments: [{ fileName, mimeType, content }])
|
|
836
|
+
synthos.agents.isEnabled(agentId) — checks if an agent is enabled (returns Promise<boolean>)
|
|
837
|
+
synthos.agents.getCapabilities(agentId) — returns agent capabilities object (streaming, skills, etc.)`],
|
|
838
|
+
|
|
839
|
+
['connectors', `GET /api/connectors
|
|
840
|
+
description: List available connectors (REST API proxies). Supports ?category=X and ?id=X filters.
|
|
841
|
+
response: [{ id: string, name: string, category: string, configured: boolean }]
|
|
842
|
+
|
|
843
|
+
GET /api/connectors/:id
|
|
844
|
+
description: Get full detail for a connector including its definition and configuration status
|
|
845
|
+
response: { id, name, category, description, baseUrl, authStrategy, authKey, fields, configured, enabled, hasKey }
|
|
846
|
+
|
|
847
|
+
POST /api/connectors (proxy call)
|
|
848
|
+
description: Proxy a request through a configured connector. The connector attaches auth automatically.
|
|
849
|
+
request: { connector: string, method: string, path: string, headers?: object, body?: any, query?: object }
|
|
850
|
+
response: Upstream API response (JSON or text)
|
|
851
|
+
|
|
852
|
+
synthos.connectors.call(connector, method, path, opts?) — POST /api/connectors (proxy call; opts: { headers?, body?, query? })
|
|
853
|
+
synthos.connectors.list(opts?) — GET /api/connectors (opts: { category?, id? })`],
|
|
854
|
+
|
|
855
|
+
['files', `GET /api/files/:page
|
|
856
|
+
description: List files stored for a page (with sizes)
|
|
857
|
+
response: { files: [{ name: string, size: number }] }
|
|
858
|
+
|
|
859
|
+
POST /api/files/:page
|
|
860
|
+
description: Upload a file to a page's file storage (raw body + x-filename header)
|
|
861
|
+
request: Raw binary body with x-filename header
|
|
862
|
+
response: { name: string, size: number }
|
|
863
|
+
|
|
864
|
+
GET /api/files/:page/:filename
|
|
865
|
+
description: Download/serve a specific file from a page's file storage
|
|
866
|
+
response: File content (served with appropriate content-type)
|
|
867
|
+
|
|
868
|
+
DELETE /api/files/:page/:filename
|
|
869
|
+
description: Delete a file from a page's file storage
|
|
870
|
+
response: { deleted: true }
|
|
871
|
+
|
|
872
|
+
synthos.files.list() — GET /api/files/:page (auto-scoped to current page)
|
|
873
|
+
synthos.files.upload(filename, blob) — POST /api/files/:page (auto-scoped to current page; sends raw body with x-filename header)
|
|
874
|
+
synthos.files.url(filename) — returns URL string /api/files/:page/:filename (for <img src>, <a href>, etc.)
|
|
875
|
+
synthos.files.remove(filename) — DELETE /api/files/:page/:filename (auto-scoped to current page)`],
|
|
876
|
+
|
|
877
|
+
['shared-data', `GET /api/shared/data/:table
|
|
878
|
+
description: Retrieve all rows from a shared (cross-page) table. Supports pagination via query params.
|
|
879
|
+
query params: limit (number, optional) — max rows to return; offset (number, optional, default 0) — rows to skip
|
|
880
|
+
response (without limit): Array of JSON rows [{ id: string, ... }]
|
|
881
|
+
response (with limit): { items: [{ id: string, ... }], total: number, offset: number, limit: number, hasMore: boolean }
|
|
882
|
+
|
|
883
|
+
GET /api/shared/data/:table/:id
|
|
884
|
+
description: Retrieve a single row from a shared table
|
|
885
|
+
response: JSON row { id: string, ... }
|
|
886
|
+
|
|
887
|
+
POST /api/shared/data/:table
|
|
888
|
+
description: Replaces or adds a single row to a shared table and returns the row
|
|
889
|
+
request: JSON row { id?: string, ... }
|
|
890
|
+
response: { id: string, ... }
|
|
891
|
+
|
|
892
|
+
DELETE /api/shared/data/:table/:id
|
|
893
|
+
description: Delete a single row from a shared table
|
|
894
|
+
response: { success: true }
|
|
895
|
+
|
|
896
|
+
synthos.shared.data.list(table, opts?) — GET /api/shared/data/:table (opts: { limit?, offset? } — when limit is set, returns { items, total, offset, limit, hasMore })
|
|
897
|
+
synthos.shared.data.get(table, id) — GET /api/shared/data/:table/:id
|
|
898
|
+
synthos.shared.data.save(table, row) — POST /api/shared/data/:table
|
|
899
|
+
synthos.shared.data.remove(table, id) — DELETE /api/shared/data/:table/:id`],
|
|
900
|
+
|
|
901
|
+
['shared-files', `GET /api/shared/files
|
|
902
|
+
description: List files in shared (cross-page) file storage (with sizes)
|
|
903
|
+
response: { files: [{ name: string, size: number }] }
|
|
904
|
+
|
|
905
|
+
POST /api/shared/files
|
|
906
|
+
description: Upload a file to shared file storage (raw body + x-filename header)
|
|
907
|
+
request: Raw binary body with x-filename header
|
|
908
|
+
response: { name: string, size: number }
|
|
909
|
+
|
|
910
|
+
GET /api/shared/files/:filename
|
|
911
|
+
description: Download/serve a specific file from shared file storage
|
|
912
|
+
response: File content (served with appropriate content-type)
|
|
913
|
+
|
|
914
|
+
DELETE /api/shared/files/:filename
|
|
915
|
+
description: Delete a file from shared file storage
|
|
916
|
+
response: { deleted: true }
|
|
917
|
+
|
|
918
|
+
synthos.shared.files.list() — GET /api/shared/files
|
|
919
|
+
synthos.shared.files.upload(filename, blob) — POST /api/shared/files (sends raw body with x-filename header)
|
|
920
|
+
synthos.shared.files.url(filename) — returns URL string /api/shared/files/:filename (for <img src>, <a href>, etc.)
|
|
921
|
+
synthos.shared.files.remove(filename) — DELETE /api/shared/files/:filename`],
|
|
922
|
+
]);
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Assemble the <SERVER_APIS> prompt block, including only hints for enabled
|
|
926
|
+
* feature groups and any custom route hints from the Customizer.
|
|
927
|
+
*/
|
|
928
|
+
export function buildRouteHints(customizer: Customizer): string {
|
|
929
|
+
const blocks: string[] = ['<SERVER_APIS>'];
|
|
930
|
+
|
|
931
|
+
// Built-in hints — only include enabled groups
|
|
932
|
+
for (const [group, hints] of DEFAULT_ROUTE_HINTS) {
|
|
933
|
+
if (customizer.isEnabled(group)) {
|
|
934
|
+
blocks.push(hints);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Custom route hints from fork
|
|
939
|
+
for (const hint of customizer.getRouteHints()) {
|
|
940
|
+
blocks.push(hint);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
blocks.push('PAGE HELPERS (available globally as window.synthos):');
|
|
944
|
+
blocks.push('All methods return Promises. Prefer these helpers over raw fetch().');
|
|
945
|
+
return blocks.join('\n\n');
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Backward-compatible full serverAPIs string (used when no Customizer is passed)
|
|
949
|
+
export const serverAPIs =
|
|
708
950
|
`<SERVER_APIS>
|
|
709
951
|
GET /api/data/:page/:table
|
|
710
952
|
description: Retrieve all rows from a page-scoped table (tables are stored per-page). Supports pagination via query params.
|
|
@@ -752,6 +994,11 @@ DELETE /api/pages/:name
|
|
|
752
994
|
description: Delete a user page (cannot delete required/system pages)
|
|
753
995
|
response: { deleted: true }
|
|
754
996
|
|
|
997
|
+
POST /api/pages/:name/ask
|
|
998
|
+
description: Ask a question about a page with full HTML context
|
|
999
|
+
request: { question: string }
|
|
1000
|
+
response: { answer: string }
|
|
1001
|
+
|
|
755
1002
|
POST /api/scripts/:id
|
|
756
1003
|
description: Execute a script with the passed in variables
|
|
757
1004
|
request: { [key: string]: string }
|
|
@@ -801,6 +1048,7 @@ PAGE HELPERS (available globally as window.synthos):
|
|
|
801
1048
|
synthos.pages.get(name) — GET /api/pages/:name
|
|
802
1049
|
synthos.pages.update(name, metadata) — POST /api/pages/:name
|
|
803
1050
|
synthos.pages.remove(name) — DELETE /api/pages/:name
|
|
1051
|
+
synthos.pages.ask(name, question) — POST /api/pages/:name/ask
|
|
804
1052
|
synthos.search.web(query, opts?) — POST /api/search/web (opts: { count?, country?, freshness? })
|
|
805
1053
|
synthos.connectors.call(connector, method, path, opts?) — POST /api/connectors (proxy call; opts: { headers?, body?, query? })
|
|
806
1054
|
synthos.connectors.list(opts?) — GET /api/connectors (opts: { category?, id? })
|
|
@@ -810,20 +1058,3 @@ PAGE HELPERS (available globally as window.synthos):
|
|
|
810
1058
|
synthos.agents.isEnabled(agentId) — checks if an agent is enabled (returns Promise<boolean>)
|
|
811
1059
|
synthos.agents.getCapabilities(agentId) — returns agent capabilities object (streaming, skills, etc.)
|
|
812
1060
|
All methods return Promises. Prefer these helpers over raw fetch().`;
|
|
813
|
-
|
|
814
|
-
const repairUSER_MESSAGE =
|
|
815
|
-
`Some change operations from the previous response failed because the target nodes no longer exist in the page (they were removed or replaced by earlier operations in the same batch).
|
|
816
|
-
|
|
817
|
-
Below is the CURRENT state of the page after the successful operations were applied, followed by the list of operations that failed and why.
|
|
818
|
-
|
|
819
|
-
Re-generate corrected versions of ONLY the failed operations, targeting nodes that actually exist in the current page. Each element has a data-node-id attribute you can reference.
|
|
820
|
-
If a failed operation is no longer needed (e.g. the intended change was already accomplished by another op), omit it.
|
|
821
|
-
Return an empty JSON array [] if no repairs are needed.
|
|
822
|
-
|
|
823
|
-
Return ONLY a JSON array of change operations using the same format:
|
|
824
|
-
{ "op": "update", "nodeId": "<data-node-id>", "html": "<new innerHTML>" }
|
|
825
|
-
{ "op": "replace", "nodeId": "<data-node-id>", "html": "<new outerHTML>" }
|
|
826
|
-
{ "op": "delete", "nodeId": "<data-node-id>" }
|
|
827
|
-
{ "op": "insert", "parentId": "<data-node-id>", "position": "prepend"|"append"|"before"|"after", "html": "<new element HTML>" }
|
|
828
|
-
{ "op": "style-element", "nodeId": "<data-node-id>", "style": "<css style string>" }`;
|
|
829
|
-
|