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
|
@@ -9,11 +9,12 @@ import {
|
|
|
9
9
|
parseChangeList,
|
|
10
10
|
injectError,
|
|
11
11
|
deduplicateInlineScripts,
|
|
12
|
+
normalizedIndexOf,
|
|
12
13
|
transformPage,
|
|
13
14
|
ChangeList,
|
|
14
15
|
TransformPageArgs,
|
|
15
16
|
} from '../src/service/transformPage';
|
|
16
|
-
import {
|
|
17
|
+
import { Builder, BuilderResult, ContextSection } from '../src/builders/types';
|
|
17
18
|
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
19
20
|
// assignNodeIds
|
|
@@ -456,12 +457,142 @@ describe('deduplicateInlineScripts — ID-based dedup', () => {
|
|
|
456
457
|
});
|
|
457
458
|
|
|
458
459
|
// ---------------------------------------------------------------------------
|
|
459
|
-
//
|
|
460
|
+
// normalizedIndexOf
|
|
460
461
|
// ---------------------------------------------------------------------------
|
|
461
462
|
|
|
462
|
-
describe('
|
|
463
|
-
|
|
463
|
+
describe('normalizedIndexOf', () => {
|
|
464
|
+
it('returns null when needle is not found', () => {
|
|
465
|
+
assert.strictEqual(normalizedIndexOf('hello world', 'xyz'), null);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('finds exact match and returns correct positions', () => {
|
|
469
|
+
const result = normalizedIndexOf('let x = 1;', 'x = 1');
|
|
470
|
+
assert.ok(result !== null);
|
|
471
|
+
assert.strictEqual(result.start, 4);
|
|
472
|
+
assert.strictEqual(result.end, 9);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('matches despite whitespace differences', () => {
|
|
476
|
+
const haystack = 'let x =\n 1;';
|
|
477
|
+
const needle = 'x = 1;';
|
|
478
|
+
const result = normalizedIndexOf(haystack, needle);
|
|
479
|
+
assert.ok(result !== null);
|
|
480
|
+
// Should span from 'x' to end of ';'
|
|
481
|
+
const matched = haystack.slice(result.start, result.end);
|
|
482
|
+
assert.ok(matched.includes('x'));
|
|
483
|
+
assert.ok(matched.includes('1;'));
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('handles newlines vs spaces', () => {
|
|
487
|
+
const haystack = 'function foo() {\n return 1;\n}';
|
|
488
|
+
const needle = 'foo() { return 1; }';
|
|
489
|
+
const result = normalizedIndexOf(haystack, needle);
|
|
490
|
+
assert.ok(result !== null);
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
// applyChangeList — search-replace / search-insert ops
|
|
496
|
+
// ---------------------------------------------------------------------------
|
|
464
497
|
|
|
498
|
+
describe('applyChangeList — search-replace / search-insert ops', () => {
|
|
499
|
+
const scriptHtml = '<html><head></head><body>' +
|
|
500
|
+
'<script data-node-id="5">let a = 1;\nlet b = 2;\nlet c = 3;\nlet d = 4;\nlet e = 5;</script>' +
|
|
501
|
+
'</body></html>';
|
|
502
|
+
|
|
503
|
+
it('search-replace replaces exact text match', () => {
|
|
504
|
+
const changes: ChangeList = [
|
|
505
|
+
{ op: 'search-replace', nodeId: '5', search: 'let b = 2;', replace: 'let b = 20;' },
|
|
506
|
+
];
|
|
507
|
+
const result = applyChangeList(scriptHtml, changes);
|
|
508
|
+
assert.ok(result.includes('let b = 20;'));
|
|
509
|
+
assert.ok(!result.includes('let b = 2;'));
|
|
510
|
+
assert.ok(result.includes('let a = 1;'));
|
|
511
|
+
assert.ok(result.includes('let c = 3;'));
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('search-replace with empty replace deletes text', () => {
|
|
515
|
+
const changes: ChangeList = [
|
|
516
|
+
{ op: 'search-replace', nodeId: '5', search: 'let c = 3;\n', replace: '' },
|
|
517
|
+
];
|
|
518
|
+
const result = applyChangeList(scriptHtml, changes);
|
|
519
|
+
assert.ok(!result.includes('let c = 3;'));
|
|
520
|
+
assert.ok(result.includes('let b = 2;'));
|
|
521
|
+
assert.ok(result.includes('let d = 4;'));
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('search-replace falls back to normalized match', () => {
|
|
525
|
+
// Script has single spaces, search has different whitespace
|
|
526
|
+
const html = '<html><head></head><body>' +
|
|
527
|
+
'<script data-node-id="5">function foo() {\n return 1;\n}</script>' +
|
|
528
|
+
'</body></html>';
|
|
529
|
+
const changes: ChangeList = [
|
|
530
|
+
{ op: 'search-replace', nodeId: '5', search: 'function foo() { return 1; }', replace: 'function foo() { return 2; }' },
|
|
531
|
+
];
|
|
532
|
+
const result = applyChangeList(html, changes);
|
|
533
|
+
assert.ok(result.includes('return 2;'));
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('search-replace warns on no match found', () => {
|
|
537
|
+
const changes: ChangeList = [
|
|
538
|
+
{ op: 'search-replace', nodeId: '5', search: 'nonexistent text', replace: 'replacement' },
|
|
539
|
+
];
|
|
540
|
+
// Should not throw
|
|
541
|
+
const result = applyChangeList(scriptHtml, changes);
|
|
542
|
+
// Original content preserved
|
|
543
|
+
assert.ok(result.includes('let a = 1;'));
|
|
544
|
+
assert.ok(!result.includes('replacement'));
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('search-replace works on style blocks', () => {
|
|
548
|
+
const styleHtml = '<html><head><style data-node-id="3">.a { color: red; }\n.b { color: blue; }\n.c { color: green; }</style></head><body></body></html>';
|
|
549
|
+
const changes: ChangeList = [
|
|
550
|
+
{ op: 'search-replace', nodeId: '3', search: '.b { color: blue; }', replace: '.b { color: purple; }' },
|
|
551
|
+
];
|
|
552
|
+
const result = applyChangeList(styleHtml, changes);
|
|
553
|
+
assert.ok(result.includes('.b { color: purple; }'));
|
|
554
|
+
assert.ok(!result.includes('color: blue'));
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('search-insert inserts after matched text', () => {
|
|
558
|
+
const changes: ChangeList = [
|
|
559
|
+
{ op: 'search-insert', nodeId: '5', after: 'let b = 2;', content: '\nlet inserted = true;' },
|
|
560
|
+
];
|
|
561
|
+
const result = applyChangeList(scriptHtml, changes);
|
|
562
|
+
assert.ok(result.includes('let inserted = true;'));
|
|
563
|
+
const idxB = result.indexOf('let b = 2;');
|
|
564
|
+
const idxInserted = result.indexOf('let inserted = true;');
|
|
565
|
+
const idxC = result.indexOf('let c = 3;');
|
|
566
|
+
assert.ok(idxB < idxInserted);
|
|
567
|
+
assert.ok(idxInserted < idxC);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('search-insert warns on no match found', () => {
|
|
571
|
+
const changes: ChangeList = [
|
|
572
|
+
{ op: 'search-insert', nodeId: '5', after: 'nonexistent text', content: '\nnew stuff' },
|
|
573
|
+
];
|
|
574
|
+
const result = applyChangeList(scriptHtml, changes);
|
|
575
|
+
assert.ok(result.includes('let a = 1;'));
|
|
576
|
+
assert.ok(!result.includes('new stuff'));
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('warns but does not throw on missing node for search ops', () => {
|
|
580
|
+
const ops: ChangeList = [
|
|
581
|
+
{ op: 'search-replace', nodeId: '999', search: 'x', replace: 'y' },
|
|
582
|
+
{ op: 'search-insert', nodeId: '999', after: 'x', content: 'y' },
|
|
583
|
+
];
|
|
584
|
+
for (const change of ops) {
|
|
585
|
+
const result = applyChangeList(scriptHtml, [change]);
|
|
586
|
+
assert.ok(result.includes('let a = 1;'));
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// ---------------------------------------------------------------------------
|
|
592
|
+
// transformPage (integration with stub Builder)
|
|
593
|
+
// ---------------------------------------------------------------------------
|
|
594
|
+
|
|
595
|
+
describe('transformPage', () => {
|
|
465
596
|
// Minimal page with a viewer-panel and thoughts div
|
|
466
597
|
const testPage = `<html><head></head><body>
|
|
467
598
|
<div class="chat-panel" data-locked>
|
|
@@ -471,14 +602,6 @@ describe('transformPage', () => {
|
|
|
471
602
|
<div id="thoughts" style="display: none;"></div>
|
|
472
603
|
</body></html>`;
|
|
473
604
|
|
|
474
|
-
beforeEach(async () => {
|
|
475
|
-
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'synthos-tp-test-'));
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
afterEach(async () => {
|
|
479
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
480
|
-
});
|
|
481
|
-
|
|
482
605
|
/** Extract the data-node-id for an element identified by a CSS-style attribute (e.g. id="content") from annotated HTML. */
|
|
483
606
|
function findNodeId(annotatedHtml: string, idAttr: string): string {
|
|
484
607
|
// Match a tag that contains both data-node-id="X" and the target id, in either order
|
|
@@ -488,28 +611,30 @@ describe('transformPage', () => {
|
|
|
488
611
|
return m ? m[1] : '99999';
|
|
489
612
|
}
|
|
490
613
|
|
|
491
|
-
|
|
614
|
+
/** Create a stub builder that calls the given handler to produce a BuilderResult. */
|
|
615
|
+
function makeBuilder(handler: (currentPage: ContextSection, additionalSections: ContextSection[], userMessage: string, newBuild: boolean) => Promise<BuilderResult>): Builder {
|
|
616
|
+
return { run: handler };
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function makeArgs(builder: Builder, pageState?: string): TransformPageArgs {
|
|
492
620
|
return {
|
|
493
|
-
|
|
494
|
-
|
|
621
|
+
builder,
|
|
622
|
+
additionalSections: [],
|
|
495
623
|
pageState: pageState ?? testPage,
|
|
496
624
|
message: 'Change the content',
|
|
497
625
|
};
|
|
498
626
|
}
|
|
499
627
|
|
|
500
628
|
it('happy path — applies valid change list and returns transformed HTML', async () => {
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
const nodeId = findNodeId(sys, 'id="content"');
|
|
629
|
+
const builder = makeBuilder(async (currentPage) => {
|
|
630
|
+
const nodeId = findNodeId(currentPage.content, 'id="content"');
|
|
504
631
|
return {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
{ op: 'update', nodeId, html: 'Updated content' },
|
|
508
|
-
]),
|
|
632
|
+
kind: 'transforms',
|
|
633
|
+
changes: [{ op: 'update', nodeId, html: 'Updated content' }],
|
|
509
634
|
};
|
|
510
|
-
};
|
|
635
|
+
});
|
|
511
636
|
|
|
512
|
-
const result = await transformPage(makeArgs(
|
|
637
|
+
const result = await transformPage(makeArgs(builder));
|
|
513
638
|
assert.strictEqual(result.completed, true);
|
|
514
639
|
assert.ok(result.value);
|
|
515
640
|
assert.ok(result.value.html.includes('Updated content'));
|
|
@@ -518,239 +643,72 @@ describe('transformPage', () => {
|
|
|
518
643
|
assert.ok(!result.value.html.includes('data-node-id'));
|
|
519
644
|
});
|
|
520
645
|
|
|
521
|
-
it('returns error when
|
|
522
|
-
const
|
|
523
|
-
|
|
646
|
+
it('returns error HTML when builder returns error', async () => {
|
|
647
|
+
const builder = makeBuilder(async () => ({
|
|
648
|
+
kind: 'error',
|
|
524
649
|
error: new Error('API quota exceeded'),
|
|
525
|
-
});
|
|
650
|
+
}));
|
|
526
651
|
|
|
527
|
-
const result = await transformPage(makeArgs(
|
|
528
|
-
assert.strictEqual(result.completed, false);
|
|
529
|
-
assert.strictEqual(result.error?.message, 'API quota exceeded');
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
it('injects error block when response is not valid JSON', async () => {
|
|
533
|
-
const stub = async (): Promise<AgentCompletion<string>> => ({
|
|
534
|
-
completed: true,
|
|
535
|
-
value: 'I cannot help with that request.',
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
const result = await transformPage(makeArgs(stub));
|
|
539
|
-
// Should still complete (error is injected into HTML, not thrown)
|
|
652
|
+
const result = await transformPage(makeArgs(builder));
|
|
540
653
|
assert.strictEqual(result.completed, true);
|
|
541
654
|
assert.ok(result.value);
|
|
542
|
-
assert.strictEqual(result.value.changeCount, 0);
|
|
543
655
|
assert.ok(result.value.html.includes('id="error"'));
|
|
656
|
+
assert.strictEqual(result.value.changeCount, 0);
|
|
544
657
|
});
|
|
545
658
|
|
|
546
|
-
it('handles
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (callCount === 1) {
|
|
552
|
-
const contentNodeId = findNodeId(sys, 'id="content"');
|
|
553
|
-
return {
|
|
554
|
-
completed: true,
|
|
555
|
-
value: JSON.stringify([
|
|
556
|
-
{ op: 'update', nodeId: contentNodeId, html: 'First pass change' },
|
|
557
|
-
{ op: 'update', nodeId: '9999', html: 'Ghost node' }, // will fail
|
|
558
|
-
]),
|
|
559
|
-
};
|
|
560
|
-
} else {
|
|
561
|
-
// Repair call: target an element that exists in re-annotated HTML
|
|
562
|
-
const thoughtsNodeId = findNodeId(sys, 'id="thoughts"');
|
|
563
|
-
return {
|
|
564
|
-
completed: true,
|
|
565
|
-
value: JSON.stringify([
|
|
566
|
-
{ op: 'update', nodeId: thoughtsNodeId, html: 'Repaired content' },
|
|
567
|
-
]),
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
const result = await transformPage(makeArgs(stub));
|
|
573
|
-
assert.strictEqual(result.completed, true);
|
|
574
|
-
assert.ok(result.value);
|
|
575
|
-
// Should have made 2 calls (initial + repair)
|
|
576
|
-
assert.strictEqual(callCount, 2);
|
|
577
|
-
// changeCount should be 2 (1 success from first pass + 1 from repair)
|
|
578
|
-
assert.strictEqual(result.value.changeCount, 2);
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
it('keeps partial result when repair pass LLM call fails', async () => {
|
|
582
|
-
let callCount = 0;
|
|
583
|
-
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
584
|
-
callCount++;
|
|
585
|
-
if (callCount === 1) {
|
|
586
|
-
const sys = args.system?.content ?? '';
|
|
587
|
-
const contentNodeId = findNodeId(sys, 'id="content"');
|
|
588
|
-
return {
|
|
589
|
-
completed: true,
|
|
590
|
-
value: JSON.stringify([
|
|
591
|
-
{ op: 'update', nodeId: contentNodeId, html: 'Partial update' },
|
|
592
|
-
{ op: 'update', nodeId: '9999', html: 'Ghost' },
|
|
593
|
-
]),
|
|
594
|
-
};
|
|
595
|
-
} else {
|
|
596
|
-
// Repair call fails
|
|
597
|
-
return { completed: false, error: new Error('Repair failed') };
|
|
598
|
-
}
|
|
599
|
-
};
|
|
659
|
+
it('handles reply result by appending chat messages', async () => {
|
|
660
|
+
const builder = makeBuilder(async () => ({
|
|
661
|
+
kind: 'reply',
|
|
662
|
+
text: 'I cannot help with that.',
|
|
663
|
+
}));
|
|
600
664
|
|
|
601
|
-
const result = await transformPage(
|
|
665
|
+
const result = await transformPage({
|
|
666
|
+
...makeArgs(builder),
|
|
667
|
+
productName: 'SynthOS',
|
|
668
|
+
});
|
|
602
669
|
assert.strictEqual(result.completed, true);
|
|
603
670
|
assert.ok(result.value);
|
|
604
|
-
assert.ok(result.value.html.includes('
|
|
605
|
-
assert.
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
it('handles repair pass returning empty array (no repairs needed)', async () => {
|
|
609
|
-
let callCount = 0;
|
|
610
|
-
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
611
|
-
callCount++;
|
|
612
|
-
if (callCount === 1) {
|
|
613
|
-
return {
|
|
614
|
-
completed: true,
|
|
615
|
-
value: JSON.stringify([
|
|
616
|
-
{ op: 'update', nodeId: '9999', html: 'Ghost' },
|
|
617
|
-
]),
|
|
618
|
-
};
|
|
619
|
-
} else {
|
|
620
|
-
return { completed: true, value: '[]' };
|
|
621
|
-
}
|
|
622
|
-
};
|
|
623
|
-
|
|
624
|
-
const result = await transformPage(makeArgs(stub));
|
|
625
|
-
assert.strictEqual(result.completed, true);
|
|
626
|
-
assert.strictEqual(callCount, 2);
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
it('includes instructions and modelInstructions in prompt', async () => {
|
|
630
|
-
let capturedPrompt: string | undefined;
|
|
631
|
-
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
632
|
-
capturedPrompt = args.prompt.content;
|
|
633
|
-
return { completed: true, value: '[]' };
|
|
634
|
-
};
|
|
635
|
-
|
|
636
|
-
await transformPage({
|
|
637
|
-
...makeArgs(stub),
|
|
638
|
-
instructions: 'Be creative',
|
|
639
|
-
modelInstructions: 'Return concise JSON',
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
assert.ok(capturedPrompt);
|
|
643
|
-
assert.ok(capturedPrompt.includes('Be creative'));
|
|
644
|
-
assert.ok(capturedPrompt.includes('Return concise JSON'));
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
it('includes theme info in system prompt when provided', async () => {
|
|
648
|
-
let capturedSystem: string | undefined;
|
|
649
|
-
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
650
|
-
capturedSystem = args.system?.content;
|
|
651
|
-
return { completed: true, value: '[]' };
|
|
652
|
-
};
|
|
653
|
-
|
|
654
|
-
await transformPage({
|
|
655
|
-
...makeArgs(stub),
|
|
656
|
-
themeInfo: {
|
|
657
|
-
mode: 'dark',
|
|
658
|
-
colors: { 'accent-primary': '#ff0000', 'text-primary': '#ffffff' },
|
|
659
|
-
},
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
assert.ok(capturedSystem);
|
|
663
|
-
assert.ok(capturedSystem.includes('Mode: dark'));
|
|
664
|
-
assert.ok(capturedSystem.includes('--accent-primary: #ff0000'));
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
it('includes connector info in system prompt when provided', async () => {
|
|
668
|
-
let capturedSystem: string | undefined;
|
|
669
|
-
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
670
|
-
capturedSystem = args.system?.content;
|
|
671
|
-
return { completed: true, value: '[]' };
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
await transformPage({
|
|
675
|
-
...makeArgs(stub),
|
|
676
|
-
configuredConnectors: {
|
|
677
|
-
'brave-search': { enabled: true, apiKey: 'test-key' } as any,
|
|
678
|
-
},
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
assert.ok(capturedSystem);
|
|
682
|
-
assert.ok(capturedSystem.includes('CONFIGURED_CONNECTORS'));
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
it('includes agent info in system prompt when provided', async () => {
|
|
686
|
-
let capturedSystem: string | undefined;
|
|
687
|
-
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
688
|
-
capturedSystem = args.system?.content;
|
|
689
|
-
return { completed: true, value: '[]' };
|
|
690
|
-
};
|
|
691
|
-
|
|
692
|
-
await transformPage({
|
|
693
|
-
...makeArgs(stub),
|
|
694
|
-
configuredAgents: [{
|
|
695
|
-
id: 'test-agent',
|
|
696
|
-
name: 'Test Agent',
|
|
697
|
-
description: 'A test agent',
|
|
698
|
-
url: 'http://localhost:3000',
|
|
699
|
-
enabled: true,
|
|
700
|
-
provider: 'a2a' as any,
|
|
701
|
-
}],
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
assert.ok(capturedSystem);
|
|
705
|
-
assert.ok(capturedSystem.includes('CONFIGURED_AGENTS'));
|
|
706
|
-
assert.ok(capturedSystem.includes('Test Agent'));
|
|
671
|
+
assert.ok(result.value.html.includes('User:'));
|
|
672
|
+
assert.ok(result.value.html.includes('SynthOS:'));
|
|
673
|
+
assert.ok(result.value.html.includes('I cannot help with that.'));
|
|
674
|
+
assert.strictEqual(result.value.changeCount, 0);
|
|
707
675
|
});
|
|
708
676
|
|
|
709
|
-
it('
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
...makeArgs(stub),
|
|
718
|
-
configuredAgents: [{
|
|
719
|
-
id: 'agent-1',
|
|
720
|
-
name: 'Streaming Agent',
|
|
721
|
-
description: 'Agent with streaming',
|
|
722
|
-
url: 'http://localhost:3000',
|
|
723
|
-
enabled: true,
|
|
724
|
-
provider: 'a2a',
|
|
725
|
-
capabilities: { streaming: true },
|
|
726
|
-
skills: [
|
|
727
|
-
{ id: 'summarize', name: 'summarize', description: 'Summarizes text', tags: [] },
|
|
677
|
+
it('handles missing nodes gracefully (no repair pass)', async () => {
|
|
678
|
+
const builder = makeBuilder(async (currentPage) => {
|
|
679
|
+
const contentNodeId = findNodeId(currentPage.content, 'id="content"');
|
|
680
|
+
return {
|
|
681
|
+
kind: 'transforms',
|
|
682
|
+
changes: [
|
|
683
|
+
{ op: 'update', nodeId: contentNodeId, html: 'First pass change' },
|
|
684
|
+
{ op: 'update', nodeId: '9999', html: 'Ghost node' }, // will be skipped
|
|
728
685
|
],
|
|
729
|
-
}
|
|
686
|
+
};
|
|
730
687
|
});
|
|
731
688
|
|
|
732
|
-
|
|
733
|
-
assert.
|
|
734
|
-
assert.ok(
|
|
735
|
-
assert.ok(
|
|
689
|
+
const result = await transformPage(makeArgs(builder));
|
|
690
|
+
assert.strictEqual(result.completed, true);
|
|
691
|
+
assert.ok(result.value);
|
|
692
|
+
assert.ok(result.value.html.includes('First pass change'));
|
|
693
|
+
// changeCount counts all ops, including skipped ones
|
|
694
|
+
assert.strictEqual(result.value.changeCount, 2);
|
|
736
695
|
});
|
|
737
696
|
|
|
738
697
|
it('exercises replace, delete, insert, and style-element ops through pipeline', async () => {
|
|
739
|
-
const
|
|
740
|
-
const
|
|
741
|
-
const
|
|
742
|
-
const viewerNodeId = findNodeId(sys, 'class="viewer-panel"');
|
|
698
|
+
const builder = makeBuilder(async (currentPage) => {
|
|
699
|
+
const contentNodeId = findNodeId(currentPage.content, 'id="content"');
|
|
700
|
+
const viewerNodeId = findNodeId(currentPage.content, 'class="viewer-panel"');
|
|
743
701
|
return {
|
|
744
|
-
|
|
745
|
-
|
|
702
|
+
kind: 'transforms',
|
|
703
|
+
changes: [
|
|
746
704
|
{ op: 'replace', nodeId: contentNodeId, html: '<p id="content">Replaced</p>' },
|
|
747
705
|
{ op: 'insert', parentId: viewerNodeId, position: 'append', html: '<span>Appended</span>' },
|
|
748
706
|
{ op: 'style-element', nodeId: viewerNodeId, style: 'background: blue' },
|
|
749
|
-
]
|
|
707
|
+
],
|
|
750
708
|
};
|
|
751
|
-
};
|
|
709
|
+
});
|
|
752
710
|
|
|
753
|
-
const result = await transformPage(makeArgs(
|
|
711
|
+
const result = await transformPage(makeArgs(builder));
|
|
754
712
|
assert.strictEqual(result.completed, true);
|
|
755
713
|
assert.ok(result.value);
|
|
756
714
|
assert.ok(result.value.html.includes('Replaced'));
|
|
@@ -759,8 +717,7 @@ describe('transformPage', () => {
|
|
|
759
717
|
assert.strictEqual(result.value.changeCount, 3);
|
|
760
718
|
});
|
|
761
719
|
|
|
762
|
-
it('
|
|
763
|
-
// Use a page where the content is data-locked
|
|
720
|
+
it('skips replace on locked element', async () => {
|
|
764
721
|
const lockedPage = `<html><head></head><body>
|
|
765
722
|
<div class="chat-panel" data-locked>
|
|
766
723
|
<div id="chatMessages"><div class="chat-message"><p><strong>SynthOS:</strong> Welcome!</p></div></div>
|
|
@@ -769,77 +726,20 @@ describe('transformPage', () => {
|
|
|
769
726
|
<div id="thoughts" style="display: none;"></div>
|
|
770
727
|
</body></html>`;
|
|
771
728
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
completed: true,
|
|
780
|
-
value: JSON.stringify([
|
|
781
|
-
{ op: 'replace', nodeId: contentNodeId, html: '<p>Should fail</p>' },
|
|
782
|
-
]),
|
|
783
|
-
};
|
|
784
|
-
} else {
|
|
785
|
-
// Repair: return empty array (nothing to fix)
|
|
786
|
-
return { completed: true, value: '[]' };
|
|
787
|
-
}
|
|
788
|
-
};
|
|
729
|
+
const builder = makeBuilder(async (currentPage) => {
|
|
730
|
+
const contentNodeId = findNodeId(currentPage.content, 'id="content"');
|
|
731
|
+
return {
|
|
732
|
+
kind: 'transforms',
|
|
733
|
+
changes: [{ op: 'replace', nodeId: contentNodeId, html: '<p>Should fail</p>' }],
|
|
734
|
+
};
|
|
735
|
+
});
|
|
789
736
|
|
|
790
|
-
const result = await transformPage(makeArgs(
|
|
737
|
+
const result = await transformPage(makeArgs(builder, lockedPage));
|
|
791
738
|
assert.strictEqual(result.completed, true);
|
|
792
|
-
assert.strictEqual(callCount, 2);
|
|
793
|
-
// Original locked content should still be present
|
|
794
739
|
assert.ok(result.value!.html.includes('Locked content'));
|
|
795
740
|
});
|
|
796
741
|
|
|
797
|
-
it('
|
|
798
|
-
let callCount = 0;
|
|
799
|
-
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
800
|
-
callCount++;
|
|
801
|
-
if (callCount === 1) {
|
|
802
|
-
return {
|
|
803
|
-
completed: true,
|
|
804
|
-
value: JSON.stringify([
|
|
805
|
-
{ op: 'update', nodeId: '9999', html: 'Ghost' },
|
|
806
|
-
]),
|
|
807
|
-
};
|
|
808
|
-
} else {
|
|
809
|
-
// Repair call returns invalid JSON that will cause parseChangeList to throw
|
|
810
|
-
return { completed: true, value: 'not valid json at all' };
|
|
811
|
-
}
|
|
812
|
-
};
|
|
813
|
-
|
|
814
|
-
const result = await transformPage(makeArgs(stub));
|
|
815
|
-
assert.strictEqual(result.completed, true);
|
|
816
|
-
assert.strictEqual(callCount, 2);
|
|
817
|
-
// Should have kept partial result from first pass
|
|
818
|
-
assert.ok(result.value);
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
it('reports failed insert on missing parent through pipeline', async () => {
|
|
822
|
-
let callCount = 0;
|
|
823
|
-
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
824
|
-
callCount++;
|
|
825
|
-
if (callCount === 1) {
|
|
826
|
-
return {
|
|
827
|
-
completed: true,
|
|
828
|
-
value: JSON.stringify([
|
|
829
|
-
{ op: 'insert', parentId: '9999', position: 'append', html: '<span>Ghost</span>' },
|
|
830
|
-
]),
|
|
831
|
-
};
|
|
832
|
-
} else {
|
|
833
|
-
return { completed: true, value: '[]' };
|
|
834
|
-
}
|
|
835
|
-
};
|
|
836
|
-
|
|
837
|
-
const result = await transformPage(makeArgs(stub));
|
|
838
|
-
assert.strictEqual(result.completed, true);
|
|
839
|
-
assert.strictEqual(callCount, 2);
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
it('reports failed delete on locked element through pipeline', async () => {
|
|
742
|
+
it('skips delete on locked element', async () => {
|
|
843
743
|
const lockedPage = `<html><head></head><body>
|
|
844
744
|
<div class="chat-panel" data-locked>
|
|
845
745
|
<div id="chatMessages"><div class="chat-message"><p><strong>SynthOS:</strong> Welcome!</p></div></div>
|
|
@@ -848,29 +748,20 @@ describe('transformPage', () => {
|
|
|
848
748
|
<div id="thoughts" style="display: none;"></div>
|
|
849
749
|
</body></html>`;
|
|
850
750
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
completed: true,
|
|
859
|
-
value: JSON.stringify([
|
|
860
|
-
{ op: 'delete', nodeId: contentNodeId },
|
|
861
|
-
]),
|
|
862
|
-
};
|
|
863
|
-
} else {
|
|
864
|
-
return { completed: true, value: '[]' };
|
|
865
|
-
}
|
|
866
|
-
};
|
|
751
|
+
const builder = makeBuilder(async (currentPage) => {
|
|
752
|
+
const contentNodeId = findNodeId(currentPage.content, 'id="content"');
|
|
753
|
+
return {
|
|
754
|
+
kind: 'transforms',
|
|
755
|
+
changes: [{ op: 'delete', nodeId: contentNodeId }],
|
|
756
|
+
};
|
|
757
|
+
});
|
|
867
758
|
|
|
868
|
-
const result = await transformPage(makeArgs(
|
|
759
|
+
const result = await transformPage(makeArgs(builder, lockedPage));
|
|
869
760
|
assert.strictEqual(result.completed, true);
|
|
870
761
|
assert.ok(result.value!.html.includes('Locked'));
|
|
871
762
|
});
|
|
872
763
|
|
|
873
|
-
it('
|
|
764
|
+
it('skips style-element on locked element', async () => {
|
|
874
765
|
const lockedPage = `<html><head></head><body>
|
|
875
766
|
<div class="chat-panel" data-locked>
|
|
876
767
|
<div id="chatMessages"><div class="chat-message"><p><strong>SynthOS:</strong> Welcome!</p></div></div>
|
|
@@ -879,53 +770,101 @@ describe('transformPage', () => {
|
|
|
879
770
|
<div id="thoughts" style="display: none;"></div>
|
|
880
771
|
</body></html>`;
|
|
881
772
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
completed: true,
|
|
890
|
-
value: JSON.stringify([
|
|
891
|
-
{ op: 'style-element', nodeId: contentNodeId, style: 'color: red' },
|
|
892
|
-
]),
|
|
893
|
-
};
|
|
894
|
-
} else {
|
|
895
|
-
return { completed: true, value: '[]' };
|
|
896
|
-
}
|
|
897
|
-
};
|
|
773
|
+
const builder = makeBuilder(async (currentPage) => {
|
|
774
|
+
const contentNodeId = findNodeId(currentPage.content, 'id="content"');
|
|
775
|
+
return {
|
|
776
|
+
kind: 'transforms',
|
|
777
|
+
changes: [{ op: 'style-element', nodeId: contentNodeId, style: 'color: red' }],
|
|
778
|
+
};
|
|
779
|
+
});
|
|
898
780
|
|
|
899
|
-
const result = await transformPage(makeArgs(
|
|
781
|
+
const result = await transformPage(makeArgs(builder, lockedPage));
|
|
900
782
|
assert.strictEqual(result.completed, true);
|
|
901
|
-
// Locked element should not have the style applied
|
|
902
783
|
assert.ok(!result.value!.html.includes('color: red'));
|
|
903
784
|
});
|
|
904
785
|
|
|
905
|
-
it('
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
786
|
+
it('catches exceptions from builder and injects error', async () => {
|
|
787
|
+
const builder = makeBuilder(async () => {
|
|
788
|
+
throw new Error('Unexpected builder crash');
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
const result = await transformPage(makeArgs(builder));
|
|
792
|
+
assert.strictEqual(result.completed, true);
|
|
793
|
+
assert.ok(result.value);
|
|
794
|
+
assert.ok(result.value.html.includes('id="error"'));
|
|
795
|
+
assert.strictEqual(result.value.changeCount, 0);
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
it('detects newBuild when isBuilder is true and only one chat message', async () => {
|
|
799
|
+
let capturedNewBuild: boolean | undefined;
|
|
800
|
+
const builder = makeBuilder(async (_cp, _as, _msg, newBuild) => {
|
|
801
|
+
capturedNewBuild = newBuild;
|
|
802
|
+
return { kind: 'transforms', changes: [] };
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
await transformPage({
|
|
806
|
+
...makeArgs(builder),
|
|
807
|
+
isBuilder: true,
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
assert.strictEqual(capturedNewBuild, true);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
it('detects existing build when multiple chat messages present', async () => {
|
|
814
|
+
const multiMessagePage = `<html><head></head><body>
|
|
815
|
+
<div class="chat-panel" data-locked>
|
|
816
|
+
<div id="chatMessages">
|
|
817
|
+
<div class="chat-message"><p><strong>SynthOS:</strong> Welcome!</p></div>
|
|
818
|
+
<div class="chat-message"><p><strong>User:</strong> Build me a todo app</p></div>
|
|
819
|
+
<div class="chat-message"><p><strong>SynthOS:</strong> Here you go!</p></div>
|
|
820
|
+
</div>
|
|
821
|
+
</div>
|
|
822
|
+
<div class="viewer-panel"><p>Content</p></div>
|
|
823
|
+
</body></html>`;
|
|
824
|
+
|
|
825
|
+
let capturedNewBuild: boolean | undefined;
|
|
826
|
+
const builder = makeBuilder(async (_cp, _as, _msg, newBuild) => {
|
|
827
|
+
capturedNewBuild = newBuild;
|
|
828
|
+
return { kind: 'transforms', changes: [] };
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
await transformPage({
|
|
832
|
+
...makeArgs(builder, multiMessagePage),
|
|
833
|
+
isBuilder: true,
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
assert.strictEqual(capturedNewBuild, false);
|
|
837
|
+
});
|
|
926
838
|
|
|
927
|
-
|
|
839
|
+
it('applies search-replace through full pipeline', async () => {
|
|
840
|
+
const pageWithScript = `<html><head></head><body>
|
|
841
|
+
<div class="chat-panel" data-locked>
|
|
842
|
+
<div id="chatMessages"><div class="chat-message"><p><strong>SynthOS:</strong> Welcome!</p></div></div>
|
|
843
|
+
</div>
|
|
844
|
+
<div class="viewer-panel"><p id="content">Hello</p></div>
|
|
845
|
+
<script id="page-script">let count = 0;\nlet name = "test";\nfunction init() { return count; }</script>
|
|
846
|
+
</body></html>`;
|
|
847
|
+
|
|
848
|
+
const builder = makeBuilder(async (currentPage) => {
|
|
849
|
+
// The current page should NOT have line numbers
|
|
850
|
+
assert.ok(!currentPage.content.includes('01:'), 'currentPage should not contain line numbers');
|
|
851
|
+
// Find the script node id
|
|
852
|
+
const scriptNodeId = findNodeId(currentPage.content, 'id="page-script"');
|
|
853
|
+
return {
|
|
854
|
+
kind: 'transforms',
|
|
855
|
+
changes: [
|
|
856
|
+
{ op: 'search-replace', nodeId: scriptNodeId, search: 'let count = 0;', replace: 'let count = 42;' },
|
|
857
|
+
],
|
|
858
|
+
};
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
const result = await transformPage(makeArgs(builder, pageWithScript));
|
|
928
862
|
assert.strictEqual(result.completed, true);
|
|
929
|
-
assert.
|
|
863
|
+
assert.ok(result.value);
|
|
864
|
+
// Edit should be applied
|
|
865
|
+
assert.ok(result.value.html.includes('let count = 42;'));
|
|
866
|
+
// Node ids should be stripped
|
|
867
|
+
assert.ok(!result.value.html.includes('data-node-id'));
|
|
868
|
+
assert.strictEqual(result.value.changeCount, 1);
|
|
930
869
|
});
|
|
931
870
|
});
|