synthos 0.8.0 → 0.9.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 +1822 -0
- package/default-pages/{neon_asteroids.json → neon_asteroids/page.json} +3 -3
- package/default-pages/{oregon_trail.html → oregon_trail/page.html} +14 -12
- 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} +14 -11
- 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.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 +57 -0
- package/dist/customizer/Customizer.d.ts.map +1 -0
- package/dist/customizer/Customizer.js +124 -0
- package/dist/customizer/Customizer.js.map +1 -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.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +10 -6
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +96 -113
- 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 +30 -7
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +177 -55
- package/dist/pages.js.map +1 -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 +5 -2
- package/dist/service/useAgentRoutes.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +237 -136
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.js +6 -6
- package/dist/service/useConnectorRoutes.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.map +1 -1
- package/dist/service/usePageRoutes.js +648 -67
- 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 +104 -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 +1 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +1 -0
- package/dist/settings.js.map +1 -1
- 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 +28 -15
- 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 +151 -0
- package/src/customizer/index.ts +5 -0
- package/src/files.ts +57 -0
- package/src/index.ts +2 -1
- package/src/init.ts +137 -123
- 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 +176 -54
- package/src/service/server.ts +36 -9
- package/src/service/transformPage.ts +557 -326
- package/src/service/useAgentRoutes.ts +7 -2
- package/src/service/useApiRoutes.ts +150 -41
- package/src/service/useConnectorRoutes.ts +7 -7
- package/src/service/useFileRoutes.ts +127 -0
- package/src/service/usePageRoutes.ts +720 -73
- package/src/service/useSharedDataRoutes.ts +106 -0
- package/src/service/useSharedFileRoutes.ts +126 -0
- package/src/settings.ts +2 -0
- package/src/synthos-cli.ts +4 -3
- package/src/themes.ts +25 -14
- 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 +8 -8
- 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/agents/a2a/a2aProvider.d.ts +0 -3
- package/dist/agents/discovery.d.ts +0 -30
- package/dist/agents/openclaw/openclawProvider.d.ts +0 -3
- package/dist/agents/types.d.ts +0 -64
- package/dist/connectors/index.d.ts +0 -3
- package/dist/connectors/types.d.ts +0 -84
- package/dist/index.d.ts +0 -7
- package/dist/migrations.d.ts +0 -12
- package/dist/models/chainOfThought.d.ts +0 -12
- package/dist/models/fireworksai.d.ts +0 -30
- package/dist/models/logCompletePrompt.d.ts +0 -3
- package/dist/models/providers.d.ts +0 -8
- package/dist/models/utils.d.ts +0 -6
- package/dist/scripts.d.ts +0 -15
- package/dist/service/createCompletePrompt.d.ts +0 -5
- package/dist/service/debugLog.d.ts +0 -11
- package/dist/service/generateImage.d.ts +0 -32
- package/dist/service/index.d.ts +0 -8
- package/dist/service/modelInstructions.d.ts +0 -7
- package/dist/service/requiresSettings.d.ts +0 -3
- package/dist/service/server.d.ts +0 -4
- package/dist/service/useApiRoutes.d.ts +0 -4
- package/dist/service/useConnectorRoutes.d.ts +0 -4
- package/dist/service/useDataRoutes.d.ts +0 -4
- 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/dist/service/usePageRoutes.d.ts +0 -5
- package/dist/synthos-cli.d.ts +0 -2
- 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
|
@@ -23,161 +23,165 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.
|
|
27
|
-
const scripts_1 = require("../scripts");
|
|
26
|
+
exports.serverAPIs = exports.buildRouteHints = exports.DEFAULT_ROUTE_HINTS = exports.AGENT_API_REFERENCE = exports.getTransformInstr = exports.getMessageFormat = exports.parseChangeList = exports.applyChangeList = exports.ensureScriptsBeforeBodyClose = exports.deduplicateInlineScripts = exports.normalizedIndexOf = exports.stripNodeIds = exports.assignNodeIds = exports.simpleMarkdown = exports.transformPage = void 0;
|
|
28
27
|
const cheerio = __importStar(require("cheerio"));
|
|
29
|
-
const connectors_1 = require("../connectors");
|
|
30
28
|
async function transformPage(args) {
|
|
31
|
-
const {
|
|
29
|
+
const { message, builder, additionalSections } = args;
|
|
30
|
+
// 0. Strip the early error-capture script so the LLM never sees it
|
|
31
|
+
const pageState = stripErrorCapture(args.pageState);
|
|
32
32
|
// 1. Assign data-node-id to every element
|
|
33
33
|
const { html: annotatedHtml } = assignNodeIds(pageState);
|
|
34
34
|
try {
|
|
35
|
-
// 2. Build
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
let themeBlock = '<THEME>\n';
|
|
41
|
-
if (args.themeInfo) {
|
|
42
|
-
const { mode, colors } = args.themeInfo;
|
|
43
|
-
const colorList = Object.entries(colors)
|
|
44
|
-
.map(([name, value]) => ` --${name}: ${value}`)
|
|
45
|
-
.join('\n');
|
|
46
|
-
themeBlock += `Mode: ${mode}\nCSS custom properties (use instead of hardcoded values):\n${colorList}\n\nShared shell classes (pre-styled by theme, do not redefine):\n .chat-panel — Left sidebar container (30% width)\n .chat-header — Chat panel title bar\n .chat-messages — Scrollable message container\n .chat-message — Individual message wrapper\n .link-group — Navigation links row (Save, Pages, Reset)\n .chat-input — Message text input\n .chat-submit — Send button\n .viewer-panel — Right content area (70% width)\n .loading-overlay — Full-screen loading overlay\n .spinner — Animated loading spinner\n .modal-overlay — Full-screen modal backdrop (position:fixed, z-index:2000, backdrop-filter:blur). Add class "show" to display.\n .modal-content — Centered modal container\n .modal-header — Gradient header bar\n .modal-body — Modal content area\n .modal-footer — Bottom action bar (flex, space-between)\n .modal-footer-right — Right-aligned button group\n\nModals and popups: ALWAYS use the theme\'s .modal-overlay class for any modal or popup overlay. Do NOT create custom overlay classes with position:fixed and z-index. Structure:\n <div class="modal-overlay" id="myModal">\n <div class="modal-content">\n <div class="modal-header">Title</div>\n <div class="modal-body">Content</div>\n <div class="modal-footer"><div class="modal-footer-right"><button>OK</button></div></div>\n </div>\n </div>\nShow/hide by toggling the "show" class: el.classList.add(\'show\') / el.classList.remove(\'show\'). This ensures correct z-index layering above the chat toggle and other UI elements.\n\nPage title bars: To align with the chat header, apply these styles:\n min-height: var(--header-min-height);\n padding: var(--header-padding-vertical) var(--header-padding-horizontal);\n line-height: var(--header-line-height);\n display: flex; align-items: center; justify-content: center; box-sizing: border-box;\n\nFull-viewer mode: For games, animations, or full-screen content, add class "full-viewer" to the viewer-panel element to remove its padding.\n\nChat panel behaviours (auto-injected via page script — do NOT recreate in page code):\n The server injects page-v2.js after transformation. It provides:\n - Form submit handler: sets action to window.location.pathname, shows #loadingOverlay, disables inputs\n - Save/Reset link handlers (#saveLink, #resetLink)\n - Chat scroll to bottom (#chatMessages)\n - Chat toggle button (.chat-toggle) — created dynamically if not in markup\n - .chat-input-wrapper — wraps #chatInput with a brainstorm icon button\n - Brainstorm modal (#brainstormModal) — LLM-powered brainstorm UI, created dynamically\n - Focus management — keeps keyboard input directed to #chatInput\n\n Do NOT:\n - Create your own form submit handler, toggle button, or input wrapper\n - Modify or replace .chat-panel, .chat-header, .link-group, #chatForm, or .chat-toggle\n - INSERT new <script> blocks that duplicate existing ones — when fixing JavaScript, UPDATE or REPLACE the existing script's nodeId instead. Always give inline scripts a unique id attribute.\n - Set the form action attribute (page-v2.js sets it dynamically)\n - Include these CSS rules (in the theme): #loadingOverlay position, .chat-submit:disabled, .chat-input:disabled\n\n To add chat messages: use insert with parentId of #chatMessages and position "append".\n #chatMessages is the only unlocked element inside .chat-panel.\n\nThe <html> element has class "${mode}-mode". Always add .light-mode CSS overrides for any page-specific styles so the page works in both light and dark themes, unless the user has explicitly requested a very specific color scheme.`;
|
|
47
|
-
}
|
|
48
|
-
// Build configured-connectors block
|
|
49
|
-
let connectorsBlock = '';
|
|
50
|
-
if (args.configuredConnectors) {
|
|
51
|
-
const entries = Object.entries(args.configuredConnectors)
|
|
52
|
-
.filter(([, cfg]) => cfg.enabled && cfg.apiKey);
|
|
53
|
-
if (entries.length > 0) {
|
|
54
|
-
const blocks = entries.map(([id, cfg]) => {
|
|
55
|
-
const def = connectors_1.CONNECTOR_REGISTRY.find(d => d.id === id);
|
|
56
|
-
if (!def)
|
|
57
|
-
return `- ${id}`;
|
|
58
|
-
let block = `- ${def.name} (id: "${id}", category: ${def.category})\n Base URL: ${def.baseUrl}`;
|
|
59
|
-
if (def.hints) {
|
|
60
|
-
block += `\n Usage:\n${def.hints.split('\n').map(l => ' ' + l).join('\n')}`;
|
|
61
|
-
}
|
|
62
|
-
// Append dynamic OAuth context
|
|
63
|
-
if (def.authStrategy === 'oauth2') {
|
|
64
|
-
const oauthCfg = cfg;
|
|
65
|
-
block += '\n Auth: The proxy attaches the access token automatically. Do NOT pass access_token in body or query params.';
|
|
66
|
-
if (oauthCfg.userId) {
|
|
67
|
-
block += `\n User ID: ${oauthCfg.userId} — use this directly in API paths (e.g. /${oauthCfg.userId}/media).`;
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
block += '\n User ID: Not yet resolved. Call GET /me/accounts to discover it, then GET /{page-id}?fields=instagram_business_account to get the IG user ID.';
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return block;
|
|
74
|
-
});
|
|
75
|
-
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.`;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
// Build configured-agents block (only enabled agents)
|
|
79
|
-
let agentsBlock = '';
|
|
80
|
-
const enabledAgents = (args.configuredAgents ?? []).filter(a => a.enabled);
|
|
81
|
-
if (enabledAgents.length > 0) {
|
|
82
|
-
const agentBlocks = enabledAgents.map(a => {
|
|
83
|
-
let block = `- ${a.name} (id: "${a.id}", provider: ${a.provider})`;
|
|
84
|
-
block += `\n Description: ${a.description}`;
|
|
85
|
-
if (a.capabilities?.streaming) {
|
|
86
|
-
block += `\n Supports streaming: yes`;
|
|
87
|
-
}
|
|
88
|
-
if (a.skills && a.skills.length > 0) {
|
|
89
|
-
const skillList = a.skills.map(s => ` - ${s.name}: ${s.description}`).join('\n');
|
|
90
|
-
block += `\n Skills:\n${skillList}`;
|
|
91
|
-
}
|
|
92
|
-
return block;
|
|
93
|
-
});
|
|
94
|
-
agentsBlock = `<CONFIGURED_AGENTS>\nThe user has configured these agents:\n\n${agentBlocks.join('\n\n')}\n\n${AGENT_API_REFERENCE}`;
|
|
95
|
-
}
|
|
96
|
-
const systemMessage = [currentPage, serverAPIs, serverScripts, connectorsBlock, agentsBlock, themeBlock, messageFormat].filter(s => s).join('\n\n');
|
|
97
|
-
const system = {
|
|
98
|
-
role: 'system',
|
|
99
|
-
content: systemMessage
|
|
35
|
+
// 2. Build CURRENT_PAGE section
|
|
36
|
+
const currentPage = {
|
|
37
|
+
title: '<CURRENT_PAGE>',
|
|
38
|
+
content: annotatedHtml,
|
|
39
|
+
instructions: '',
|
|
100
40
|
};
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
};
|
|
108
|
-
// 3. Call model
|
|
109
|
-
const result = await completePrompt({ prompt, system });
|
|
110
|
-
if (!result.completed) {
|
|
111
|
-
return { completed: false, error: result.error };
|
|
41
|
+
// 3. Determine newBuild: if isBuilder, count .chat-message in annotated HTML
|
|
42
|
+
let newBuild = false;
|
|
43
|
+
if (args.isBuilder) {
|
|
44
|
+
const $ = cheerio.load(annotatedHtml, { decodeEntities: false });
|
|
45
|
+
const messageCount = $('#chatMessages .chat-message').length;
|
|
46
|
+
newBuild = messageCount <= 1;
|
|
112
47
|
}
|
|
113
|
-
// 4.
|
|
114
|
-
const
|
|
115
|
-
// 5.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
content: `<CURRENT_PAGE>\n${reAnnotatedHtml}\n\n<FAILED_OPERATIONS>\n${failedSummary}`
|
|
132
|
-
};
|
|
133
|
-
const repairPrompt = {
|
|
134
|
-
role: 'user',
|
|
135
|
-
content: repairUSER_MESSAGE
|
|
136
|
-
};
|
|
137
|
-
const repairResult = await completePrompt({ prompt: repairPrompt, system: repairSystem });
|
|
138
|
-
if (repairResult.completed) {
|
|
139
|
-
const repairChanges = parseChangeList(repairResult.value);
|
|
140
|
-
if (repairChanges.length > 0) {
|
|
141
|
-
const repairPass = applyChangeListWithReport(reAnnotatedHtml, repairChanges);
|
|
142
|
-
const repairSuccessCount = repairChanges.length - repairPass.failedOps.length;
|
|
143
|
-
if (repairPass.failedOps.length > 0) {
|
|
144
|
-
console.warn(`transformPage: repair pass had ${repairPass.failedOps.length} remaining failure(s) — keeping partial result`);
|
|
145
|
-
}
|
|
146
|
-
finalHtml = repairPass.html;
|
|
147
|
-
successCount += repairSuccessCount;
|
|
148
|
-
console.log(`transformPage: repair pass applied ${repairSuccessCount} fix(es)`);
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
console.log('transformPage: repair pass returned no changes (model deemed repairs unnecessary)');
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
console.warn('transformPage: repair LLM call failed — keeping partial result from first pass');
|
|
156
|
-
}
|
|
48
|
+
// 4. Call builder
|
|
49
|
+
const result = await builder.run(currentPage, additionalSections, message, newBuild, args.attachments);
|
|
50
|
+
// 5. Switch on result kind
|
|
51
|
+
switch (result.kind) {
|
|
52
|
+
case 'transforms': {
|
|
53
|
+
const applied = applyChangeList(annotatedHtml, result.changes);
|
|
54
|
+
const clean = stripNodeIds(applied);
|
|
55
|
+
const deduped = deduplicateInlineScripts(clean);
|
|
56
|
+
const safe = ensureScriptsBeforeBodyClose(deduped);
|
|
57
|
+
return { completed: true, value: { html: safe, changeCount: result.changes.length } };
|
|
58
|
+
}
|
|
59
|
+
case 'reply': {
|
|
60
|
+
const productName = args.productName ?? 'SynthOS';
|
|
61
|
+
const withReply = appendChatReply(annotatedHtml, message, result.text, productName);
|
|
62
|
+
const clean = stripNodeIds(withReply);
|
|
63
|
+
const deduped = deduplicateInlineScripts(clean);
|
|
64
|
+
const safe = ensureScriptsBeforeBodyClose(deduped);
|
|
65
|
+
return { completed: true, value: { html: safe, changeCount: -1 } };
|
|
157
66
|
}
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
67
|
+
case 'error': {
|
|
68
|
+
const productName = args.productName ?? 'SynthOS';
|
|
69
|
+
const errorHtml = appendChatError(annotatedHtml, message, result.error.message, productName);
|
|
70
|
+
const clean = stripNodeIds(errorHtml);
|
|
71
|
+
return { completed: true, value: { html: clean, changeCount: -1 } };
|
|
161
72
|
}
|
|
162
73
|
}
|
|
163
|
-
// 7. Strip data-node-id attributes
|
|
164
|
-
const cleanHtml = stripNodeIds(finalHtml);
|
|
165
|
-
// 8. Remove duplicate inline scripts (LLM may insert instead of update)
|
|
166
|
-
const dedupedHtml = deduplicateInlineScripts(cleanHtml);
|
|
167
|
-
// 9. Ensure page-helpers and page-script are last in <body>
|
|
168
|
-
const safeHtml = ensureScriptsBeforeBodyClose(dedupedHtml);
|
|
169
|
-
return { completed: true, value: { html: safeHtml, changeCount: successCount } };
|
|
170
74
|
}
|
|
171
75
|
catch (err) {
|
|
172
|
-
// On any error:
|
|
173
|
-
const
|
|
76
|
+
// On any error: append error message to chat
|
|
77
|
+
const productName = args.productName ?? 'SynthOS';
|
|
174
78
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
175
|
-
const errorHtml =
|
|
176
|
-
|
|
79
|
+
const errorHtml = appendChatError(annotatedHtml, message, errorMessage, productName);
|
|
80
|
+
const clean = stripNodeIds(errorHtml);
|
|
81
|
+
return { completed: true, value: { html: clean, changeCount: -1 } };
|
|
177
82
|
}
|
|
178
83
|
}
|
|
179
84
|
exports.transformPage = transformPage;
|
|
180
85
|
// ---------------------------------------------------------------------------
|
|
86
|
+
// Chat reply helper
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
/**
|
|
89
|
+
* Append a user message and a reply to #chatMessages using cheerio.
|
|
90
|
+
*/
|
|
91
|
+
function appendChatReply(annotatedHtml, userMessage, replyText, productName) {
|
|
92
|
+
const $ = cheerio.load(annotatedHtml, { decodeEntities: false });
|
|
93
|
+
const chatMessages = $('#chatMessages');
|
|
94
|
+
if (chatMessages.length > 0) {
|
|
95
|
+
chatMessages.append(`<div class="chat-message"><p><strong>User:</strong> ${escapeHtml(userMessage)}</p></div>`);
|
|
96
|
+
const replyHtml = simpleMarkdown(replyText);
|
|
97
|
+
chatMessages.append(`<div class="chat-message"><p><strong>${escapeHtml(productName)}:</strong> ${replyHtml}</p></div>`);
|
|
98
|
+
}
|
|
99
|
+
return $.html();
|
|
100
|
+
}
|
|
101
|
+
function escapeHtml(text) {
|
|
102
|
+
return text
|
|
103
|
+
.replace(/&/g, '&')
|
|
104
|
+
.replace(/</g, '<')
|
|
105
|
+
.replace(/>/g, '>')
|
|
106
|
+
.replace(/"/g, '"');
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Lightweight markdown-to-HTML converter for chat reply text.
|
|
110
|
+
* Handles: code blocks, inline code, bold, italic, links, unordered/ordered lists, paragraphs.
|
|
111
|
+
*/
|
|
112
|
+
function simpleMarkdown(text) {
|
|
113
|
+
// Extract fenced code blocks first to protect their contents
|
|
114
|
+
const codeBlocks = [];
|
|
115
|
+
let processed = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
|
|
116
|
+
const idx = codeBlocks.length;
|
|
117
|
+
const escaped = escapeHtml(code.replace(/\n$/, ''));
|
|
118
|
+
const langAttr = lang ? ` class="language-${escapeHtml(lang)}"` : '';
|
|
119
|
+
codeBlocks.push(`<pre><code${langAttr}>${escaped}</code></pre>`);
|
|
120
|
+
return `\x00CODEBLOCK${idx}\x00`;
|
|
121
|
+
});
|
|
122
|
+
// Split into paragraphs by blank lines
|
|
123
|
+
const blocks = processed.split(/\n{2,}/);
|
|
124
|
+
const htmlBlocks = [];
|
|
125
|
+
for (const block of blocks) {
|
|
126
|
+
const trimmed = block.trim();
|
|
127
|
+
if (!trimmed)
|
|
128
|
+
continue;
|
|
129
|
+
// Code block placeholder
|
|
130
|
+
if (/^\x00CODEBLOCK\d+\x00$/.test(trimmed)) {
|
|
131
|
+
htmlBlocks.push(trimmed);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
// Unordered list (lines starting with - or *)
|
|
135
|
+
if (/^[\-\*] /m.test(trimmed) && trimmed.split('\n').every(l => /^[\-\*] /.test(l.trim()) || l.trim() === '')) {
|
|
136
|
+
const items = trimmed.split('\n')
|
|
137
|
+
.map(l => l.trim())
|
|
138
|
+
.filter(l => l)
|
|
139
|
+
.map(l => `<li>${inlineMarkdown(l.replace(/^[\-\*] /, ''))}</li>`)
|
|
140
|
+
.join('');
|
|
141
|
+
htmlBlocks.push(`<ul>${items}</ul>`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
// Ordered list (lines starting with 1. 2. etc.)
|
|
145
|
+
if (/^\d+\. /m.test(trimmed) && trimmed.split('\n').every(l => /^\d+\. /.test(l.trim()) || l.trim() === '')) {
|
|
146
|
+
const items = trimmed.split('\n')
|
|
147
|
+
.map(l => l.trim())
|
|
148
|
+
.filter(l => l)
|
|
149
|
+
.map(l => `<li>${inlineMarkdown(l.replace(/^\d+\. /, ''))}</li>`)
|
|
150
|
+
.join('');
|
|
151
|
+
htmlBlocks.push(`<ol>${items}</ol>`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
// Regular paragraph
|
|
155
|
+
htmlBlocks.push(`<p>${inlineMarkdown(trimmed.replace(/\n/g, '<br>'))}</p>`);
|
|
156
|
+
}
|
|
157
|
+
// Restore code blocks
|
|
158
|
+
let result = htmlBlocks.join('');
|
|
159
|
+
result = result.replace(/\x00CODEBLOCK(\d+)\x00/g, (_m, idx) => codeBlocks[parseInt(idx)]);
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
exports.simpleMarkdown = simpleMarkdown;
|
|
163
|
+
/** Apply inline markdown formatting: bold, italic, inline code, links. */
|
|
164
|
+
function inlineMarkdown(text) {
|
|
165
|
+
// Inline code (protect from further processing)
|
|
166
|
+
const codes = [];
|
|
167
|
+
let result = text.replace(/`([^`]+)`/g, (_m, code) => {
|
|
168
|
+
const idx = codes.length;
|
|
169
|
+
codes.push(`<code>${escapeHtml(code)}</code>`);
|
|
170
|
+
return `\x00CODE${idx}\x00`;
|
|
171
|
+
});
|
|
172
|
+
// Bold (**text** or __text__)
|
|
173
|
+
result = result.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
174
|
+
result = result.replace(/__(.+?)__/g, '<strong>$1</strong>');
|
|
175
|
+
// Italic (*text* or _text_)
|
|
176
|
+
result = result.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
177
|
+
result = result.replace(/(?<!\w)_(.+?)_(?!\w)/g, '<em>$1</em>');
|
|
178
|
+
// Links [text](url)
|
|
179
|
+
result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
180
|
+
// Restore inline code
|
|
181
|
+
result = result.replace(/\x00CODE(\d+)\x00/g, (_m, idx) => codes[parseInt(idx)]);
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
181
185
|
// Internal helpers
|
|
182
186
|
// ---------------------------------------------------------------------------
|
|
183
187
|
/**
|
|
@@ -204,6 +208,65 @@ function stripNodeIds(html) {
|
|
|
204
208
|
return $.html();
|
|
205
209
|
}
|
|
206
210
|
exports.stripNodeIds = stripNodeIds;
|
|
211
|
+
/**
|
|
212
|
+
* Remove the early error-capture script injected into <head> so the LLM
|
|
213
|
+
* never sees it during page transformation.
|
|
214
|
+
*/
|
|
215
|
+
function stripErrorCapture(html) {
|
|
216
|
+
const id = 'synthos-error-capture';
|
|
217
|
+
if (!html.includes(id))
|
|
218
|
+
return html;
|
|
219
|
+
const $ = cheerio.load(html, { decodeEntities: false });
|
|
220
|
+
$(`#${id}`).remove();
|
|
221
|
+
return $.html();
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Find `needle` in `haystack` using whitespace-normalized comparison.
|
|
225
|
+
* Collapses runs of whitespace (spaces, tabs, newlines) into a single space
|
|
226
|
+
* for comparison purposes but returns the position in the original string.
|
|
227
|
+
* Returns -1 if no match found.
|
|
228
|
+
*/
|
|
229
|
+
function normalizedIndexOf(haystack, needle) {
|
|
230
|
+
// Build a mapping from normalized-string positions to original-string positions
|
|
231
|
+
const normChars = [];
|
|
232
|
+
const origPositions = []; // origPositions[i] = original index of normChars[i]
|
|
233
|
+
let inWhitespace = false;
|
|
234
|
+
for (let i = 0; i < haystack.length; i++) {
|
|
235
|
+
const ch = haystack[i];
|
|
236
|
+
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
237
|
+
if (!inWhitespace) {
|
|
238
|
+
normChars.push(' ');
|
|
239
|
+
origPositions.push(i);
|
|
240
|
+
inWhitespace = true;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
normChars.push(ch);
|
|
245
|
+
origPositions.push(i);
|
|
246
|
+
inWhitespace = false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const normHaystack = normChars.join('');
|
|
250
|
+
// Normalize the needle the same way
|
|
251
|
+
const normNeedle = needle.replace(/\s+/g, ' ');
|
|
252
|
+
const idx = normHaystack.indexOf(normNeedle);
|
|
253
|
+
if (idx === -1)
|
|
254
|
+
return null;
|
|
255
|
+
const start = origPositions[idx];
|
|
256
|
+
// The end position: find the original position of the last matched char, then go one past
|
|
257
|
+
const lastNormIdx = idx + normNeedle.length - 1;
|
|
258
|
+
const lastOrigPos = origPositions[lastNormIdx];
|
|
259
|
+
// Advance past any trailing whitespace that was collapsed in the original
|
|
260
|
+
let end = lastOrigPos + 1;
|
|
261
|
+
if (haystack[lastOrigPos] === ' ' || haystack[lastOrigPos] === '\t' || haystack[lastOrigPos] === '\n' || haystack[lastOrigPos] === '\r') {
|
|
262
|
+
// The last matched normalized char was a whitespace collapse — extend to include all original whitespace
|
|
263
|
+
while (end < haystack.length && (haystack[end] === ' ' || haystack[end] === '\t' || haystack[end] === '\n' || haystack[end] === '\r')) {
|
|
264
|
+
end++;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return { start, end };
|
|
268
|
+
}
|
|
269
|
+
exports.normalizedIndexOf = normalizedIndexOf;
|
|
207
270
|
/**
|
|
208
271
|
* Remove duplicate inline `<script>` blocks using a two-pass approach.
|
|
209
272
|
*
|
|
@@ -331,6 +394,29 @@ exports.ensureScriptsBeforeBodyClose = ensureScriptsBeforeBodyClose;
|
|
|
331
394
|
function isElementLocked(el, $) {
|
|
332
395
|
return el.attr('data-locked') !== undefined;
|
|
333
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* If the target element is a <script> or <style> and the html is wrapped in a
|
|
399
|
+
* redundant matching tag, strip the outer tag to avoid nesting (e.g.
|
|
400
|
+
* `<script>` inside `<script>`). Returns the inner content when unwrapped.
|
|
401
|
+
*/
|
|
402
|
+
function unwrapRedundantTag(tagName, html) {
|
|
403
|
+
if (tagName !== 'script' && tagName !== 'style')
|
|
404
|
+
return html;
|
|
405
|
+
const re = new RegExp(`^\\s*<${tagName}[^>]*>([\\s\\S]*)</${tagName}>\\s*$`, 'i');
|
|
406
|
+
const match = html.match(re);
|
|
407
|
+
return match ? match[1] : html;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Strip any `<script>` or `<style>` tags from text that will be injected
|
|
411
|
+
* inside an existing script/style block (search-replace, search-insert).
|
|
412
|
+
*/
|
|
413
|
+
function stripNestedBlockTags(tagName, text) {
|
|
414
|
+
if (tagName !== 'script' && tagName !== 'style')
|
|
415
|
+
return text;
|
|
416
|
+
return text
|
|
417
|
+
.replace(new RegExp(`<${tagName}[^>]*>`, 'gi'), '')
|
|
418
|
+
.replace(new RegExp(`</${tagName}>`, 'gi'), '');
|
|
419
|
+
}
|
|
334
420
|
/**
|
|
335
421
|
* Apply a list of CRUD operations to annotated HTML (elements must have `data-node-id`).
|
|
336
422
|
*/
|
|
@@ -344,7 +430,8 @@ function applyChangeList(html, changes) {
|
|
|
344
430
|
console.warn(`applyChangeList: skipping update — node ${change.nodeId} not found (already removed?)`);
|
|
345
431
|
break;
|
|
346
432
|
}
|
|
347
|
-
el.
|
|
433
|
+
const tag = el.prop('tagName')?.toLowerCase();
|
|
434
|
+
el.html(unwrapRedundantTag(tag, change.html));
|
|
348
435
|
break;
|
|
349
436
|
}
|
|
350
437
|
case 'replace': {
|
|
@@ -357,7 +444,17 @@ function applyChangeList(html, changes) {
|
|
|
357
444
|
console.warn(`applyChangeList: skipping replace — node ${change.nodeId} is data-locked`);
|
|
358
445
|
break;
|
|
359
446
|
}
|
|
360
|
-
|
|
447
|
+
// If the target is a <script> or <style> and the html doesn't
|
|
448
|
+
// include the outer tag (or wraps it redundantly), treat as an
|
|
449
|
+
// update (set inner content) instead of replacing the element.
|
|
450
|
+
const tagName = el.prop('tagName')?.toLowerCase();
|
|
451
|
+
const cleaned = unwrapRedundantTag(tagName, change.html);
|
|
452
|
+
if ((tagName === 'script' || tagName === 'style') && !cleaned.trimStart().startsWith('<')) {
|
|
453
|
+
el.html(cleaned);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
el.replaceWith(change.html);
|
|
457
|
+
}
|
|
361
458
|
break;
|
|
362
459
|
}
|
|
363
460
|
case 'delete': {
|
|
@@ -375,22 +472,28 @@ function applyChangeList(html, changes) {
|
|
|
375
472
|
}
|
|
376
473
|
case 'insert': {
|
|
377
474
|
const parent = $(`[data-node-id="${change.parentId}"]`);
|
|
378
|
-
if (parent.length === 0)
|
|
379
|
-
|
|
475
|
+
if (parent.length === 0) {
|
|
476
|
+
console.warn(`applyChangeList: skipping insert — parent node ${change.parentId} not found`);
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
// Unwrap redundant tags when inserting into script/style
|
|
480
|
+
const parentTag = parent.prop('tagName')?.toLowerCase();
|
|
481
|
+
const insertHtml = unwrapRedundantTag(parentTag, change.html);
|
|
380
482
|
switch (change.position) {
|
|
381
483
|
case 'prepend':
|
|
382
|
-
parent.prepend(
|
|
484
|
+
parent.prepend(insertHtml);
|
|
383
485
|
break;
|
|
384
486
|
case 'append':
|
|
385
|
-
parent.append(
|
|
487
|
+
parent.append(insertHtml);
|
|
386
488
|
break;
|
|
387
489
|
case 'before':
|
|
388
|
-
parent.before(
|
|
490
|
+
parent.before(insertHtml);
|
|
389
491
|
break;
|
|
390
492
|
case 'after':
|
|
391
|
-
parent.after(
|
|
493
|
+
parent.after(insertHtml);
|
|
392
494
|
break;
|
|
393
|
-
default:
|
|
495
|
+
default:
|
|
496
|
+
console.warn(`applyChangeList: skipping insert — unknown position "${change.position}"`);
|
|
394
497
|
}
|
|
395
498
|
break;
|
|
396
499
|
}
|
|
@@ -407,134 +510,75 @@ function applyChangeList(html, changes) {
|
|
|
407
510
|
el.attr('style', change.style);
|
|
408
511
|
break;
|
|
409
512
|
}
|
|
410
|
-
|
|
411
|
-
throw new Error(`Unknown change op: "${change.op}"`);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
return $.html();
|
|
415
|
-
}
|
|
416
|
-
exports.applyChangeList = applyChangeList;
|
|
417
|
-
/**
|
|
418
|
-
* Apply a list of CRUD operations and report any ops that failed due to
|
|
419
|
-
* missing nodes (instead of throwing). Unknown op types still throw.
|
|
420
|
-
*/
|
|
421
|
-
function applyChangeListWithReport(html, changes) {
|
|
422
|
-
const $ = cheerio.load(html, { decodeEntities: false });
|
|
423
|
-
const failedOps = [];
|
|
424
|
-
for (const change of changes) {
|
|
425
|
-
switch (change.op) {
|
|
426
|
-
case 'update': {
|
|
513
|
+
case 'search-replace': {
|
|
427
514
|
const el = $(`[data-node-id="${change.nodeId}"]`);
|
|
428
515
|
if (el.length === 0) {
|
|
429
|
-
|
|
430
|
-
console.warn(`applyChangeListWithReport: skipping update — ${reason}`);
|
|
431
|
-
failedOps.push({ op: change, reason });
|
|
516
|
+
console.warn(`applyChangeList: skipping search-replace — node ${change.nodeId} not found (already removed?)`);
|
|
432
517
|
break;
|
|
433
518
|
}
|
|
434
|
-
el.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const reason = `node ${change.nodeId} not found (already removed?)`;
|
|
441
|
-
console.warn(`applyChangeListWithReport: skipping replace — ${reason}`);
|
|
442
|
-
failedOps.push({ op: change, reason });
|
|
443
|
-
break;
|
|
519
|
+
const srTag = el.prop('tagName')?.toLowerCase();
|
|
520
|
+
const replaceText = stripNestedBlockTags(srTag, change.replace);
|
|
521
|
+
const content = el.html() ?? '';
|
|
522
|
+
const exactIdx = content.indexOf(change.search);
|
|
523
|
+
if (exactIdx !== -1) {
|
|
524
|
+
el.html(content.slice(0, exactIdx) + replaceText + content.slice(exactIdx + change.search.length));
|
|
444
525
|
}
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
526
|
+
else {
|
|
527
|
+
const norm = normalizedIndexOf(content, change.search);
|
|
528
|
+
if (norm) {
|
|
529
|
+
el.html(content.slice(0, norm.start) + replaceText + content.slice(norm.end));
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
console.warn(`applyChangeList: skipping search-replace — search text not found in node ${change.nodeId}`);
|
|
533
|
+
}
|
|
450
534
|
}
|
|
451
|
-
el.replaceWith(change.html);
|
|
452
535
|
break;
|
|
453
536
|
}
|
|
454
|
-
case '
|
|
537
|
+
case 'search-insert': {
|
|
455
538
|
const el = $(`[data-node-id="${change.nodeId}"]`);
|
|
456
539
|
if (el.length === 0) {
|
|
457
|
-
|
|
458
|
-
console.warn(`applyChangeListWithReport: skipping delete — ${reason}`);
|
|
459
|
-
failedOps.push({ op: change, reason });
|
|
460
|
-
break;
|
|
461
|
-
}
|
|
462
|
-
if (isElementLocked(el, $)) {
|
|
463
|
-
const reason = `node ${change.nodeId} is data-locked`;
|
|
464
|
-
console.warn(`applyChangeListWithReport: skipping delete — ${reason}`);
|
|
465
|
-
failedOps.push({ op: change, reason });
|
|
466
|
-
break;
|
|
467
|
-
}
|
|
468
|
-
el.remove();
|
|
469
|
-
break;
|
|
470
|
-
}
|
|
471
|
-
case 'insert': {
|
|
472
|
-
const parent = $(`[data-node-id="${change.parentId}"]`);
|
|
473
|
-
if (parent.length === 0) {
|
|
474
|
-
const reason = `parent node ${change.parentId} not found`;
|
|
475
|
-
console.warn(`applyChangeListWithReport: skipping insert — ${reason}`);
|
|
476
|
-
failedOps.push({ op: change, reason });
|
|
540
|
+
console.warn(`applyChangeList: skipping search-insert — node ${change.nodeId} not found (already removed?)`);
|
|
477
541
|
break;
|
|
478
542
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
case 'before':
|
|
487
|
-
parent.before(change.html);
|
|
488
|
-
break;
|
|
489
|
-
case 'after':
|
|
490
|
-
parent.after(change.html);
|
|
491
|
-
break;
|
|
492
|
-
default: throw new Error(`insert: unknown position "${change.position}"`);
|
|
493
|
-
}
|
|
494
|
-
break;
|
|
495
|
-
}
|
|
496
|
-
case 'style-element': {
|
|
497
|
-
const el = $(`[data-node-id="${change.nodeId}"]`);
|
|
498
|
-
if (el.length === 0) {
|
|
499
|
-
const reason = `node ${change.nodeId} not found (already removed?)`;
|
|
500
|
-
console.warn(`applyChangeListWithReport: skipping style-element — ${reason}`);
|
|
501
|
-
failedOps.push({ op: change, reason });
|
|
502
|
-
break;
|
|
543
|
+
const siTag = el.prop('tagName')?.toLowerCase();
|
|
544
|
+
const insertContent = stripNestedBlockTags(siTag, change.content);
|
|
545
|
+
const content = el.html() ?? '';
|
|
546
|
+
const exactIdx = content.indexOf(change.after);
|
|
547
|
+
if (exactIdx !== -1) {
|
|
548
|
+
const insertPos = exactIdx + change.after.length;
|
|
549
|
+
el.html(content.slice(0, insertPos) + insertContent + content.slice(insertPos));
|
|
503
550
|
}
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
551
|
+
else {
|
|
552
|
+
const norm = normalizedIndexOf(content, change.after);
|
|
553
|
+
if (norm) {
|
|
554
|
+
el.html(content.slice(0, norm.end) + insertContent + content.slice(norm.end));
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
console.warn(`applyChangeList: skipping search-insert — after text not found in node ${change.nodeId}`);
|
|
558
|
+
}
|
|
509
559
|
}
|
|
510
|
-
el.attr('style', change.style);
|
|
511
560
|
break;
|
|
512
561
|
}
|
|
513
562
|
default:
|
|
514
563
|
throw new Error(`Unknown change op: "${change.op}"`);
|
|
515
564
|
}
|
|
516
565
|
}
|
|
517
|
-
return
|
|
566
|
+
return $.html();
|
|
518
567
|
}
|
|
568
|
+
exports.applyChangeList = applyChangeList;
|
|
519
569
|
/**
|
|
520
|
-
*
|
|
570
|
+
* Append a user message and a styled error message to #chatMessages.
|
|
521
571
|
*/
|
|
522
|
-
function
|
|
572
|
+
function appendChatError(html, userMessage, errorDetails, productName) {
|
|
523
573
|
const $ = cheerio.load(html, { decodeEntities: false });
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if ($('body').length > 0) {
|
|
530
|
-
$('body').append(scriptTag);
|
|
531
|
-
}
|
|
532
|
-
else {
|
|
533
|
-
return html + scriptTag;
|
|
574
|
+
const chatMessages = $('#chatMessages');
|
|
575
|
+
if (chatMessages.length > 0) {
|
|
576
|
+
chatMessages.append(`<div class="chat-message"><p><strong>User:</strong> ${escapeHtml(userMessage)}</p></div>`);
|
|
577
|
+
chatMessages.append(`<div class="chat-message chat-message-error"><p><strong>${escapeHtml(productName)}:</strong> Something went wrong \u2014 please try again.</p>`
|
|
578
|
+
+ `<p class="chat-error-details">${escapeHtml(errorDetails)}</p></div>`);
|
|
534
579
|
}
|
|
535
580
|
return $.html();
|
|
536
581
|
}
|
|
537
|
-
exports.injectError = injectError;
|
|
538
582
|
/**
|
|
539
583
|
* Parse a JSON change list from the model's raw response text.
|
|
540
584
|
* Handles responses that may include markdown fences or extra text around the JSON.
|
|
@@ -567,16 +611,20 @@ exports.parseChangeList = parseChangeList;
|
|
|
567
611
|
// ---------------------------------------------------------------------------
|
|
568
612
|
// Prompt constants
|
|
569
613
|
// ---------------------------------------------------------------------------
|
|
570
|
-
|
|
571
|
-
|
|
614
|
+
function getMessageFormat(productName) {
|
|
615
|
+
return `<MESSAGE_FORMAT>
|
|
616
|
+
<div class="chat-message"><p><strong>{${productName}: | User:}</strong> {message contents}</p></div>
|
|
572
617
|
`;
|
|
573
|
-
|
|
618
|
+
}
|
|
619
|
+
exports.getMessageFormat = getMessageFormat;
|
|
620
|
+
function getTransformInstr(productName) {
|
|
621
|
+
return `Apply the users <USER_MESSAGE> to the .viewerPanel of the <CURRENT_PAGE> by generating a list of changes in JSON format.
|
|
574
622
|
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.
|
|
575
623
|
|
|
576
|
-
If the <USER_MESSAGE> involves clearning the chat history, remove all .chat-message elements inside the #chatMessages container except for the first
|
|
577
|
-
If there's no <USER_MESSAGE> add a
|
|
578
|
-
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
|
|
579
|
-
If there is a <USER_MESSAGE> with clear intent, add a User: message with the <USER_MESSAGE> to the chat and add a
|
|
624
|
+
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.
|
|
625
|
+
If there's no <USER_MESSAGE> add a ${productName}: message to the chat with aasking the user what they would like to do.
|
|
626
|
+
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.
|
|
627
|
+
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.
|
|
580
628
|
If a <USER_MESSAGE> is overly long, summarize the User: message.
|
|
581
629
|
|
|
582
630
|
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.
|
|
@@ -600,30 +648,11 @@ Do not add duplicate script blocks with the same logic! Consolidate inline scrip
|
|
|
600
648
|
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.
|
|
601
649
|
If you're trying to assign an id to script or style block, use "replace" not "update".
|
|
602
650
|
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.
|
|
603
|
-
Return a JSON array of change operations to apply to the page. Do NOT return the full HTML page.
|
|
604
|
-
|
|
605
|
-
Each operation must be one of:
|
|
606
|
-
{ "op": "update", "nodeId": "<data-node-id>", "html": "<new innerHTML>" }
|
|
607
|
-
— replaces the innerHTML of the target element
|
|
608
|
-
|
|
609
|
-
{ "op": "replace", "nodeId": "<data-node-id>", "html": "<new outerHTML>" }
|
|
610
|
-
— replaces the entire element (outerHTML) with new markup
|
|
611
|
-
|
|
612
|
-
{ "op": "delete", "nodeId": "<data-node-id>" }
|
|
613
|
-
— removes the element from the page
|
|
614
|
-
|
|
615
|
-
{ "op": "insert", "parentId": "<data-node-id>", "position": "prepend"|"append"|"before"|"after", "html": "<new element HTML>" }
|
|
616
|
-
— inserts new HTML relative to the parent element
|
|
617
651
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
[
|
|
623
|
-
{ "op": "update", "nodeId": "5", "html": "<p>Hello world</p>" },
|
|
624
|
-
{ "op": "insert", "parentId": "3", "position": "append", "html": "<div class=\\"msg\\">New message</div>" }
|
|
625
|
-
]`;
|
|
626
|
-
const AGENT_API_REFERENCE = `## Agent API
|
|
652
|
+
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.`;
|
|
653
|
+
}
|
|
654
|
+
exports.getTransformInstr = getTransformInstr;
|
|
655
|
+
exports.AGENT_API_REFERENCE = `## Agent API
|
|
627
656
|
|
|
628
657
|
Check availability first (required):
|
|
629
658
|
const agents = await synthos.agents.list({ enabled: true });
|
|
@@ -655,7 +684,207 @@ Stream with attachments:
|
|
|
655
684
|
|
|
656
685
|
IMPORTANT: Always check synthos.agents.list({ enabled: true }) before calling an agent.
|
|
657
686
|
If no agents are configured, show the user a link to Settings > Agents (/settings?tab=agents).`;
|
|
658
|
-
|
|
687
|
+
// ---------------------------------------------------------------------------
|
|
688
|
+
// Route hint blocks — keyed by feature group so they can be filtered
|
|
689
|
+
// ---------------------------------------------------------------------------
|
|
690
|
+
exports.DEFAULT_ROUTE_HINTS = new Map([
|
|
691
|
+
['data', `GET /api/data/:page/:table
|
|
692
|
+
description: Retrieve all rows from a page-scoped table (tables are stored per-page). Supports pagination via query params.
|
|
693
|
+
query params: limit (number, optional) — max rows to return; offset (number, optional, default 0) — rows to skip
|
|
694
|
+
response (without limit): Array of JSON rows [{ id: string, ... }]
|
|
695
|
+
response (with limit): { items: [{ id: string, ... }], total: number, offset: number, limit: number, hasMore: boolean }
|
|
696
|
+
|
|
697
|
+
GET /api/data/:page/:table/:id
|
|
698
|
+
description: Retrieve a single row from a page-scoped table
|
|
699
|
+
response: JSON row { id: string, ... }
|
|
700
|
+
|
|
701
|
+
POST /api/data/:page/:table
|
|
702
|
+
description: Replaces or adds a single row to a page-scoped table and returns the row
|
|
703
|
+
request: JSON row { id?: string, ... }
|
|
704
|
+
response: { id: string, ... }
|
|
705
|
+
|
|
706
|
+
DELETE /api/data/:page/:table/:id
|
|
707
|
+
description: Delete a single row from a page-scoped table
|
|
708
|
+
response: { success: true }
|
|
709
|
+
|
|
710
|
+
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 })
|
|
711
|
+
synthos.data.get(table, id) — GET /api/data/:page/:table/:id (auto-scoped to current page)
|
|
712
|
+
synthos.data.save(table, row) — POST /api/data/:page/:table (auto-scoped to current page)
|
|
713
|
+
synthos.data.remove(table, id) — DELETE /api/data/:page/:table/:id (auto-scoped to current page)`],
|
|
714
|
+
['api', `POST /api/generate/image
|
|
715
|
+
description: Generate an image based on a prompt
|
|
716
|
+
request: { prompt: string, shape: 'square' | 'portrait' | 'landscape', style: 'vivid' | 'natural' }
|
|
717
|
+
response: { url: string }
|
|
718
|
+
|
|
719
|
+
POST /api/generate/completion
|
|
720
|
+
description: Generates a text completion based on a prompt
|
|
721
|
+
request: { prompt: string, temperature?: number }
|
|
722
|
+
response: { answer: string, explanation: string }
|
|
723
|
+
|
|
724
|
+
synthos.generate.image({ prompt, shape, style }) — POST /api/generate/image
|
|
725
|
+
synthos.generate.completion({ prompt, temperature? }) — POST /api/generate/completion`],
|
|
726
|
+
['pages', `GET /api/pages
|
|
727
|
+
description: Retrieve a list of all pages with metadata
|
|
728
|
+
response: Array of { name: string, title: string, categories: string[], pinned: boolean, createdDate: string, lastModified: string, pageVersion: number, mode: 'unlocked' | 'locked' }
|
|
729
|
+
|
|
730
|
+
GET /api/pages/:name
|
|
731
|
+
description: Retrieve metadata for a single page
|
|
732
|
+
response: { title: string, categories: string[], pinned: boolean, createdDate: string, lastModified: string, pageVersion: number, mode: 'unlocked' | 'locked' }
|
|
733
|
+
|
|
734
|
+
POST /api/pages/:name
|
|
735
|
+
description: Update page metadata (merge semantics — send only fields to change; lastModified is auto-set)
|
|
736
|
+
request: { title?: string, categories?: string[], pinned?: boolean, mode?: 'unlocked' | 'locked' }
|
|
737
|
+
response: Full metadata object
|
|
738
|
+
|
|
739
|
+
DELETE /api/pages/:name
|
|
740
|
+
description: Delete a user page (cannot delete required/system pages)
|
|
741
|
+
response: { deleted: true }
|
|
742
|
+
|
|
743
|
+
POST /api/pages/:name/ask
|
|
744
|
+
description: Ask a question about a page with full HTML context
|
|
745
|
+
request: { question: string }
|
|
746
|
+
response: { answer: string }
|
|
747
|
+
|
|
748
|
+
synthos.pages.list() — GET /api/pages
|
|
749
|
+
synthos.pages.get(name) — GET /api/pages/:name
|
|
750
|
+
synthos.pages.update(name, metadata) — POST /api/pages/:name
|
|
751
|
+
synthos.pages.remove(name) — DELETE /api/pages/:name
|
|
752
|
+
synthos.pages.ask(name, question) — POST /api/pages/:name/ask`],
|
|
753
|
+
['scripts', `POST /api/scripts/:id
|
|
754
|
+
description: Execute a script with the passed in variables
|
|
755
|
+
request: { [key: string]: string }
|
|
756
|
+
response: string
|
|
757
|
+
|
|
758
|
+
synthos.scripts.run(id, variables) — POST /api/scripts/:id`],
|
|
759
|
+
['search', `POST /api/search/web
|
|
760
|
+
description: Search the web using Brave Search (must be enabled in Settings > Connectors)
|
|
761
|
+
request: { query: string, count?: number, country?: string, freshness?: string }
|
|
762
|
+
response: { results: [{ title: string, url: string, description: string }] }
|
|
763
|
+
|
|
764
|
+
synthos.search.web(query, opts?) — POST /api/search/web (opts: { count?, country?, freshness? })`],
|
|
765
|
+
['agents', `GET /api/agents
|
|
766
|
+
description: List configured agents (A2A and OpenClaw). Supports ?enabled=true and ?provider=a2a|openclaw filters.
|
|
767
|
+
response: [{ id: string, name: string, description: string, url: string, enabled: boolean, provider: 'a2a'|'openclaw', capabilities?: object }]
|
|
768
|
+
|
|
769
|
+
POST /api/agents/:id/send
|
|
770
|
+
description: Send a text message to an agent (works for both A2A and OpenClaw protocols) and receive a normalized response
|
|
771
|
+
request: { message: string, attachments?: [{ fileName: string, mimeType: string, content: string }] }
|
|
772
|
+
response: { kind: 'message'|'task', text?: string, raw: object }
|
|
773
|
+
|
|
774
|
+
POST /api/agents/:id/stream
|
|
775
|
+
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 }
|
|
776
|
+
request: { message: string, attachments?: [{ fileName: string, mimeType: string, content: string }] }
|
|
777
|
+
response: SSE stream
|
|
778
|
+
|
|
779
|
+
synthos.agents.list(opts?) — GET /api/agents (returns configured agents; opts: { enabled?, provider? }; returns [{ id, name, description, url, enabled, provider, capabilities }])
|
|
780
|
+
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 }])
|
|
781
|
+
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 }])
|
|
782
|
+
synthos.agents.isEnabled(agentId) — checks if an agent is enabled (returns Promise<boolean>)
|
|
783
|
+
synthos.agents.getCapabilities(agentId) — returns agent capabilities object (streaming, skills, etc.)`],
|
|
784
|
+
['connectors', `GET /api/connectors
|
|
785
|
+
description: List available connectors (REST API proxies). Supports ?category=X and ?id=X filters.
|
|
786
|
+
response: [{ id: string, name: string, category: string, configured: boolean }]
|
|
787
|
+
|
|
788
|
+
GET /api/connectors/:id
|
|
789
|
+
description: Get full detail for a connector including its definition and configuration status
|
|
790
|
+
response: { id, name, category, description, baseUrl, authStrategy, authKey, fields, configured, enabled, hasKey }
|
|
791
|
+
|
|
792
|
+
POST /api/connectors (proxy call)
|
|
793
|
+
description: Proxy a request through a configured connector. The connector attaches auth automatically.
|
|
794
|
+
request: { connector: string, method: string, path: string, headers?: object, body?: any, query?: object }
|
|
795
|
+
response: Upstream API response (JSON or text)
|
|
796
|
+
|
|
797
|
+
synthos.connectors.call(connector, method, path, opts?) — POST /api/connectors (proxy call; opts: { headers?, body?, query? })
|
|
798
|
+
synthos.connectors.list(opts?) — GET /api/connectors (opts: { category?, id? })`],
|
|
799
|
+
['files', `GET /api/files/:page
|
|
800
|
+
description: List files stored for a page (with sizes)
|
|
801
|
+
response: { files: [{ name: string, size: number }] }
|
|
802
|
+
|
|
803
|
+
POST /api/files/:page
|
|
804
|
+
description: Upload a file to a page's file storage (raw body + x-filename header)
|
|
805
|
+
request: Raw binary body with x-filename header
|
|
806
|
+
response: { name: string, size: number }
|
|
807
|
+
|
|
808
|
+
GET /api/files/:page/:filename
|
|
809
|
+
description: Download/serve a specific file from a page's file storage
|
|
810
|
+
response: File content (served with appropriate content-type)
|
|
811
|
+
|
|
812
|
+
DELETE /api/files/:page/:filename
|
|
813
|
+
description: Delete a file from a page's file storage
|
|
814
|
+
response: { deleted: true }
|
|
815
|
+
|
|
816
|
+
synthos.files.list() — GET /api/files/:page (auto-scoped to current page)
|
|
817
|
+
synthos.files.upload(filename, blob) — POST /api/files/:page (auto-scoped to current page; sends raw body with x-filename header)
|
|
818
|
+
synthos.files.url(filename) — returns URL string /api/files/:page/:filename (for <img src>, <a href>, etc.)
|
|
819
|
+
synthos.files.remove(filename) — DELETE /api/files/:page/:filename (auto-scoped to current page)`],
|
|
820
|
+
['shared-data', `GET /api/shared/data/:table
|
|
821
|
+
description: Retrieve all rows from a shared (cross-page) table. Supports pagination via query params.
|
|
822
|
+
query params: limit (number, optional) — max rows to return; offset (number, optional, default 0) — rows to skip
|
|
823
|
+
response (without limit): Array of JSON rows [{ id: string, ... }]
|
|
824
|
+
response (with limit): { items: [{ id: string, ... }], total: number, offset: number, limit: number, hasMore: boolean }
|
|
825
|
+
|
|
826
|
+
GET /api/shared/data/:table/:id
|
|
827
|
+
description: Retrieve a single row from a shared table
|
|
828
|
+
response: JSON row { id: string, ... }
|
|
829
|
+
|
|
830
|
+
POST /api/shared/data/:table
|
|
831
|
+
description: Replaces or adds a single row to a shared table and returns the row
|
|
832
|
+
request: JSON row { id?: string, ... }
|
|
833
|
+
response: { id: string, ... }
|
|
834
|
+
|
|
835
|
+
DELETE /api/shared/data/:table/:id
|
|
836
|
+
description: Delete a single row from a shared table
|
|
837
|
+
response: { success: true }
|
|
838
|
+
|
|
839
|
+
synthos.shared.data.list(table, opts?) — GET /api/shared/data/:table (opts: { limit?, offset? } — when limit is set, returns { items, total, offset, limit, hasMore })
|
|
840
|
+
synthos.shared.data.get(table, id) — GET /api/shared/data/:table/:id
|
|
841
|
+
synthos.shared.data.save(table, row) — POST /api/shared/data/:table
|
|
842
|
+
synthos.shared.data.remove(table, id) — DELETE /api/shared/data/:table/:id`],
|
|
843
|
+
['shared-files', `GET /api/shared/files
|
|
844
|
+
description: List files in shared (cross-page) file storage (with sizes)
|
|
845
|
+
response: { files: [{ name: string, size: number }] }
|
|
846
|
+
|
|
847
|
+
POST /api/shared/files
|
|
848
|
+
description: Upload a file to shared file storage (raw body + x-filename header)
|
|
849
|
+
request: Raw binary body with x-filename header
|
|
850
|
+
response: { name: string, size: number }
|
|
851
|
+
|
|
852
|
+
GET /api/shared/files/:filename
|
|
853
|
+
description: Download/serve a specific file from shared file storage
|
|
854
|
+
response: File content (served with appropriate content-type)
|
|
855
|
+
|
|
856
|
+
DELETE /api/shared/files/:filename
|
|
857
|
+
description: Delete a file from shared file storage
|
|
858
|
+
response: { deleted: true }
|
|
859
|
+
|
|
860
|
+
synthos.shared.files.list() — GET /api/shared/files
|
|
861
|
+
synthos.shared.files.upload(filename, blob) — POST /api/shared/files (sends raw body with x-filename header)
|
|
862
|
+
synthos.shared.files.url(filename) — returns URL string /api/shared/files/:filename (for <img src>, <a href>, etc.)
|
|
863
|
+
synthos.shared.files.remove(filename) — DELETE /api/shared/files/:filename`],
|
|
864
|
+
]);
|
|
865
|
+
/**
|
|
866
|
+
* Assemble the <SERVER_APIS> prompt block, including only hints for enabled
|
|
867
|
+
* feature groups and any custom route hints from the Customizer.
|
|
868
|
+
*/
|
|
869
|
+
function buildRouteHints(customizer) {
|
|
870
|
+
const blocks = ['<SERVER_APIS>'];
|
|
871
|
+
// Built-in hints — only include enabled groups
|
|
872
|
+
for (const [group, hints] of exports.DEFAULT_ROUTE_HINTS) {
|
|
873
|
+
if (customizer.isEnabled(group)) {
|
|
874
|
+
blocks.push(hints);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
// Custom route hints from fork
|
|
878
|
+
for (const hint of customizer.getRouteHints()) {
|
|
879
|
+
blocks.push(hint);
|
|
880
|
+
}
|
|
881
|
+
blocks.push('PAGE HELPERS (available globally as window.synthos):');
|
|
882
|
+
blocks.push('All methods return Promises. Prefer these helpers over raw fetch().');
|
|
883
|
+
return blocks.join('\n\n');
|
|
884
|
+
}
|
|
885
|
+
exports.buildRouteHints = buildRouteHints;
|
|
886
|
+
// Backward-compatible full serverAPIs string (used when no Customizer is passed)
|
|
887
|
+
exports.serverAPIs = `<SERVER_APIS>
|
|
659
888
|
GET /api/data/:page/:table
|
|
660
889
|
description: Retrieve all rows from a page-scoped table (tables are stored per-page). Supports pagination via query params.
|
|
661
890
|
query params: limit (number, optional) — max rows to return; offset (number, optional, default 0) — rows to skip
|
|
@@ -702,6 +931,11 @@ DELETE /api/pages/:name
|
|
|
702
931
|
description: Delete a user page (cannot delete required/system pages)
|
|
703
932
|
response: { deleted: true }
|
|
704
933
|
|
|
934
|
+
POST /api/pages/:name/ask
|
|
935
|
+
description: Ask a question about a page with full HTML context
|
|
936
|
+
request: { question: string }
|
|
937
|
+
response: { answer: string }
|
|
938
|
+
|
|
705
939
|
POST /api/scripts/:id
|
|
706
940
|
description: Execute a script with the passed in variables
|
|
707
941
|
request: { [key: string]: string }
|
|
@@ -751,6 +985,7 @@ PAGE HELPERS (available globally as window.synthos):
|
|
|
751
985
|
synthos.pages.get(name) — GET /api/pages/:name
|
|
752
986
|
synthos.pages.update(name, metadata) — POST /api/pages/:name
|
|
753
987
|
synthos.pages.remove(name) — DELETE /api/pages/:name
|
|
988
|
+
synthos.pages.ask(name, question) — POST /api/pages/:name/ask
|
|
754
989
|
synthos.search.web(query, opts?) — POST /api/search/web (opts: { count?, country?, freshness? })
|
|
755
990
|
synthos.connectors.call(connector, method, path, opts?) — POST /api/connectors (proxy call; opts: { headers?, body?, query? })
|
|
756
991
|
synthos.connectors.list(opts?) — GET /api/connectors (opts: { category?, id? })
|
|
@@ -760,18 +995,4 @@ PAGE HELPERS (available globally as window.synthos):
|
|
|
760
995
|
synthos.agents.isEnabled(agentId) — checks if an agent is enabled (returns Promise<boolean>)
|
|
761
996
|
synthos.agents.getCapabilities(agentId) — returns agent capabilities object (streaming, skills, etc.)
|
|
762
997
|
All methods return Promises. Prefer these helpers over raw fetch().`;
|
|
763
|
-
const repairUSER_MESSAGE = `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).
|
|
764
|
-
|
|
765
|
-
Below is the CURRENT state of the page after the successful operations were applied, followed by the list of operations that failed and why.
|
|
766
|
-
|
|
767
|
-
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.
|
|
768
|
-
If a failed operation is no longer needed (e.g. the intended change was already accomplished by another op), omit it.
|
|
769
|
-
Return an empty JSON array [] if no repairs are needed.
|
|
770
|
-
|
|
771
|
-
Return ONLY a JSON array of change operations using the same format:
|
|
772
|
-
{ "op": "update", "nodeId": "<data-node-id>", "html": "<new innerHTML>" }
|
|
773
|
-
{ "op": "replace", "nodeId": "<data-node-id>", "html": "<new outerHTML>" }
|
|
774
|
-
{ "op": "delete", "nodeId": "<data-node-id>" }
|
|
775
|
-
{ "op": "insert", "parentId": "<data-node-id>", "position": "prepend"|"append"|"before"|"after", "html": "<new element HTML>" }
|
|
776
|
-
{ "op": "style-element", "nodeId": "<data-node-id>", "style": "<css style string>" }`;
|
|
777
998
|
//# sourceMappingURL=transformPage.js.map
|