synthos 0.10.0 → 0.11.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 +5 -5
- package/default-pages/elevenlabs_effects_studio/chat-history.json +1 -0
- package/default-pages/elevenlabs_effects_studio/page.html +1345 -1363
- package/default-pages/elevenlabs_effects_studio/page.json +13 -11
- package/default-pages/elevenlabs_voice_studio/chat-history.json +1 -0
- package/default-pages/elevenlabs_voice_studio/page.html +782 -801
- package/default-pages/elevenlabs_voice_studio/page.json +13 -11
- package/default-pages/json_tools/chat-history.json +1 -0
- package/default-pages/json_tools/page.html +70 -90
- package/default-pages/json_tools/page.json +12 -10
- package/default-pages/my_notes/chat-history.json +1 -0
- package/default-pages/my_notes/page.html +115 -131
- package/default-pages/my_notes/page.json +14 -12
- package/default-pages/neon_asteroids/chat-history.json +1 -0
- package/default-pages/neon_asteroids/page.html +1777 -1803
- package/default-pages/neon_asteroids/page.json +14 -12
- package/default-pages/oregon_trail/chat-history.json +1 -0
- package/default-pages/oregon_trail/page.html +290 -307
- package/default-pages/oregon_trail/page.json +14 -12
- package/default-pages/solar_explorer/chat-history.json +1 -0
- package/default-pages/solar_explorer/page.html +1929 -1951
- package/default-pages/solar_explorer/page.json +14 -12
- package/default-pages/solar_tutorial/chat-history.json +1 -0
- package/default-pages/solar_tutorial/page.html +464 -478
- package/default-pages/solar_tutorial/page.json +12 -10
- package/default-pages/us_map/chat-history.json +1 -0
- package/default-pages/us_map/page.html +170 -193
- package/default-pages/us_map/page.json +14 -12
- package/default-pages/us_map/page.light.png +0 -0
- package/default-pages/us_map_1850/chat-history.json +1 -0
- package/default-pages/us_map_1850/page.html +302 -326
- package/default-pages/us_map_1850/page.json +14 -12
- package/default-pages/western_cities_1850/chat-history.json +1 -0
- package/default-pages/western_cities_1850/page.html +503 -527
- package/default-pages/western_cities_1850/page.json +14 -12
- package/default-themes/aurora-dawn.v3.css +15 -14
- package/default-themes/aurora-dusk.v3.css +26 -26
- package/default-themes/cosmos-dawn.v3.css +15 -14
- package/default-themes/cosmos-dusk.v3.css +26 -26
- package/default-themes/elemental-dawn.v3.css +200 -0
- package/default-themes/nebula-dawn.v3.css +15 -14
- package/default-themes/nebula-dusk.v3.css +24 -24
- package/default-themes/solar-flare-dawn.v3.css +15 -14
- package/default-themes/solar-flare-dusk.v3.css +26 -26
- package/dist/builders/anthropic.d.ts +26 -2
- package/dist/builders/anthropic.d.ts.map +1 -1
- package/dist/builders/anthropic.js +132 -31
- package/dist/builders/anthropic.js.map +1 -1
- package/dist/builders/claudecode.d.ts +13 -0
- package/dist/builders/claudecode.d.ts.map +1 -0
- package/dist/builders/claudecode.js +253 -0
- package/dist/builders/claudecode.js.map +1 -0
- package/dist/builders/index.d.ts +2 -1
- package/dist/builders/index.d.ts.map +1 -1
- package/dist/builders/index.js +8 -1
- package/dist/builders/index.js.map +1 -1
- package/dist/builders/openai.js +2 -1
- package/dist/builders/openai.js.map +1 -1
- package/dist/builders/types.d.ts +31 -7
- package/dist/builders/types.d.ts.map +1 -1
- package/dist/builders/types.js +60 -28
- package/dist/builders/types.js.map +1 -1
- package/dist/connectors/types.d.ts +8 -0
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +13 -6
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +161 -14
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +1 -0
- package/dist/models/anthropic.d.ts.map +1 -1
- package/dist/models/anthropic.js +129 -29
- package/dist/models/anthropic.js.map +1 -1
- package/dist/models/chainOfThought.d.ts.map +1 -1
- package/dist/models/chainOfThought.js +32 -19
- package/dist/models/chainOfThought.js.map +1 -1
- package/dist/models/index.d.ts +2 -2
- 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/providers.d.ts +1 -0
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/providers.js +12 -4
- package/dist/models/providers.js.map +1 -1
- package/dist/models/types.d.ts +15 -1
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js.map +1 -1
- package/dist/pages.d.ts +57 -8
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +258 -45
- package/dist/pages.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +5 -0
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/mediaCache.d.ts +36 -0
- package/dist/service/mediaCache.d.ts.map +1 -0
- package/dist/service/mediaCache.js +182 -0
- package/dist/service/mediaCache.js.map +1 -0
- package/dist/service/pageValidator.d.ts +25 -0
- package/dist/service/pageValidator.d.ts.map +1 -0
- package/dist/service/pageValidator.js +315 -0
- package/dist/service/pageValidator.js.map +1 -0
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +4 -0
- package/dist/service/server.js.map +1 -1
- package/dist/service/sharedTableSchema.d.ts +73 -0
- package/dist/service/sharedTableSchema.d.ts.map +1 -0
- package/dist/service/sharedTableSchema.js +206 -0
- package/dist/service/sharedTableSchema.js.map +1 -0
- package/dist/service/transformPage.d.ts +49 -11
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +354 -241
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +288 -34
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.d.ts.map +1 -1
- package/dist/service/useConnectorRoutes.js +170 -32
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useDataRoutes.d.ts.map +1 -1
- package/dist/service/useDataRoutes.js +59 -2
- package/dist/service/useDataRoutes.js.map +1 -1
- package/dist/service/useExtractRoutes.d.ts +4 -0
- package/dist/service/useExtractRoutes.d.ts.map +1 -0
- package/dist/service/useExtractRoutes.js +304 -0
- package/dist/service/useExtractRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts +17 -0
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +1385 -483
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/service/useSharedDataRoutes.d.ts.map +1 -1
- package/dist/service/useSharedDataRoutes.js +54 -2
- package/dist/service/useSharedDataRoutes.js.map +1 -1
- package/dist/settings.d.ts +27 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +40 -1
- package/dist/settings.js.map +1 -1
- package/dist/themes.d.ts +0 -5
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +3 -95
- package/dist/themes.js.map +1 -1
- package/migration-rules/v2-to-v3.md +277 -119
- package/package.json +5 -1
- package/{default-pages/application → required-pages/_shell}/page.html +56 -42
- package/required-pages/_shell/page.json +14 -0
- package/required-pages/_starters/page.html +534 -0
- package/required-pages/_starters/page.json +12 -0
- package/required-pages/builder/page.html +353 -43
- package/required-pages/builder/page.json +12 -10
- package/required-pages/pages/page.html +697 -924
- package/required-pages/pages/page.json +12 -10
- package/required-pages/settings/page.html +1879 -1753
- package/required-pages/settings/page.json +12 -10
- package/required-pages/synthos_apis/page.html +834 -845
- package/required-pages/synthos_apis/page.json +12 -10
- package/required-pages/synthos_scripts/page.html +74 -88
- package/required-pages/synthos_scripts/page.json +12 -10
- package/scripts/append-instructions.py +90 -0
- package/scripts/audit-instructions.py +76 -0
- package/scripts/cleanup-shell-markup.mjs +112 -0
- package/service-connectors/buffer/connector.json +46 -0
- package/service-connectors/canva/connector.json +67 -0
- package/service-connectors/elevenlabs/connector.json +1 -1
- package/src/builders/anthropic.ts +155 -30
- package/src/builders/claudecode.ts +310 -0
- package/src/builders/index.ts +7 -1
- package/src/builders/openai.ts +2 -1
- package/src/builders/types.ts +93 -32
- package/src/connectors/types.ts +8 -0
- package/src/init.ts +13 -7
- package/src/migrations.ts +187 -16
- package/src/models/anthropic.ts +140 -30
- package/src/models/chainOfThought.ts +33 -18
- package/src/models/index.ts +2 -2
- package/src/models/providers.ts +12 -3
- package/src/models/types.ts +21 -1
- package/src/pages.ts +271 -35
- package/src/service/createCompletePrompt.ts +6 -0
- package/src/service/mediaCache.ts +206 -0
- package/src/service/pageValidator.ts +337 -0
- package/src/service/server.ts +4 -0
- package/src/service/sharedTableSchema.ts +236 -0
- package/src/service/transformPage.ts +370 -260
- package/src/service/useApiRoutes.ts +282 -32
- package/src/service/useConnectorRoutes.ts +189 -34
- package/src/service/useDataRoutes.ts +198 -116
- package/src/service/useExtractRoutes.ts +331 -0
- package/src/service/usePageRoutes.ts +1411 -394
- package/src/service/useSharedDataRoutes.ts +184 -109
- package/src/settings.ts +65 -0
- package/src/themes.ts +78 -180
- package/starters/blank_starter/chat-history.json +1 -0
- package/starters/blank_starter/page.dark.png +0 -0
- package/starters/blank_starter/page.html +47 -0
- package/starters/blank_starter/page.json +13 -0
- package/starters/blank_starter/page.light.png +0 -0
- package/starters/calculator_starter/chat-history.json +1 -0
- package/starters/calculator_starter/page.dark.png +0 -0
- package/starters/calculator_starter/page.html +232 -0
- package/starters/calculator_starter/page.json +13 -0
- package/starters/calculator_starter/page.light.png +0 -0
- package/starters/calendar_starter/chat-history.json +1 -0
- package/starters/calendar_starter/page.dark.png +0 -0
- package/starters/calendar_starter/page.html +495 -0
- package/starters/calendar_starter/page.json +13 -0
- package/starters/calendar_starter/page.light.png +0 -0
- package/starters/chat_starter/chat-history.json +1 -0
- package/starters/chat_starter/page.dark.png +0 -0
- package/starters/chat_starter/page.html +351 -0
- package/starters/chat_starter/page.json +13 -0
- package/starters/chat_starter/page.light.png +0 -0
- package/starters/checklist_starter/chat-history.json +1 -0
- package/starters/checklist_starter/page.dark.png +0 -0
- package/starters/checklist_starter/page.html +437 -0
- package/starters/checklist_starter/page.json +13 -0
- package/starters/checklist_starter/page.light.png +0 -0
- package/starters/dashboard_starter/chat-history.json +1 -0
- package/starters/dashboard_starter/page.dark.png +0 -0
- package/starters/dashboard_starter/page.html +195 -0
- package/starters/dashboard_starter/page.json +13 -0
- package/starters/dashboard_starter/page.light.png +0 -0
- package/starters/form_starter/chat-history.json +1 -0
- package/starters/form_starter/page.dark.png +0 -0
- package/starters/form_starter/page.html +313 -0
- package/starters/form_starter/page.json +13 -0
- package/starters/form_starter/page.light.png +0 -0
- package/starters/gallery_starter/chat-history.json +1 -0
- package/starters/gallery_starter/page.dark.png +0 -0
- package/starters/gallery_starter/page.html +418 -0
- package/starters/gallery_starter/page.json +13 -0
- package/starters/gallery_starter/page.light.png +0 -0
- package/starters/generator_starter/chat-history.json +1 -0
- package/starters/generator_starter/page.dark.png +0 -0
- package/starters/generator_starter/page.html +261 -0
- package/starters/generator_starter/page.json +13 -0
- package/starters/generator_starter/page.light.png +0 -0
- package/starters/index.html +538 -0
- package/starters/kanban_starter/chat-history.json +1 -0
- package/starters/kanban_starter/page.dark.png +0 -0
- package/starters/kanban_starter/page.html +432 -0
- package/starters/kanban_starter/page.json +13 -0
- package/starters/kanban_starter/page.light.png +0 -0
- package/starters/presentation_builder/chat-history.json +1 -0
- package/starters/presentation_builder/page.dark.png +0 -0
- package/starters/presentation_builder/page.html +970 -0
- package/starters/presentation_builder/page.json +15 -0
- package/starters/presentation_builder/page.light.png +0 -0
- package/starters/presentation_builder/presentation_voice/voice_config.json +9 -0
- package/starters/pulse_starter/chat-history.json +1 -0
- package/starters/pulse_starter/page.dark.png +0 -0
- package/starters/pulse_starter/page.html +698 -0
- package/starters/pulse_starter/page.json +13 -0
- package/starters/pulse_starter/page.light.png +0 -0
- package/starters/quiz_starter/chat-history.json +1 -0
- package/starters/quiz_starter/page.dark.png +0 -0
- package/starters/quiz_starter/page.html +292 -0
- package/starters/quiz_starter/page.json +13 -0
- package/starters/quiz_starter/page.light.png +0 -0
- package/starters/reference_starter/chat-history.json +1 -0
- package/starters/reference_starter/page.dark.png +0 -0
- package/starters/reference_starter/page.html +250 -0
- package/starters/reference_starter/page.json +13 -0
- package/starters/reference_starter/page.light.png +0 -0
- package/starters/retro_game_starter/chat-history.json +1 -0
- package/starters/retro_game_starter/page.dark.png +0 -0
- package/{default-pages → starters}/retro_game_starter/page.html +1281 -1308
- package/starters/retro_game_starter/page.json +15 -0
- package/starters/retro_game_starter/page.light.png +0 -0
- package/starters/roster_starter/chat-history.json +1 -0
- package/starters/roster_starter/page.dark.png +0 -0
- package/starters/roster_starter/page.html +600 -0
- package/starters/roster_starter/page.json +13 -0
- package/starters/roster_starter/page.light.png +0 -0
- package/starters/server.js +182 -0
- package/starters/start.cmd +1 -0
- package/starters/timeline_starter/chat-history.json +1 -0
- package/starters/timeline_starter/page.dark.png +0 -0
- package/starters/timeline_starter/page.html +446 -0
- package/starters/timeline_starter/page.json +13 -0
- package/starters/timeline_starter/page.light.png +0 -0
- package/starters/tutorial_starter/chat-history.json +1 -0
- package/starters/tutorial_starter/page.dark.png +0 -0
- package/starters/tutorial_starter/page.html +283 -0
- package/starters/tutorial_starter/page.json +13 -0
- package/starters/tutorial_starter/page.light.png +0 -0
- package/static-files/agent.v3.js +122 -0
- package/static-files/connector.v3.js +48 -0
- package/static-files/extract.v3.js +188 -0
- package/static-files/helpers.v3.js +50 -6
- package/static-files/page-bridge.js +114 -0
- package/static-files/page.v3.js +1292 -1290
- package/static-files/script.v3.js +32 -0
- package/static-files/server.v3.js +89 -0
- package/static-files/shell-bridge.v3.js +174 -0
- package/static-files/shell-modals.v3.js +521 -0
- package/static-files/{shell.css → shell.v3.css} +271 -22
- package/static-files/shell.v3.js +1865 -0
- package/static-files/storage.v3.js +176 -0
- package/tests/anthropic.spec.ts +42 -7
- package/tests/builders.spec.ts +72 -4
- package/tests/pageValidator.spec.ts +548 -0
- package/tests/profiles.spec.ts +122 -0
- package/tests/providers.spec.ts +1 -1
- package/tests/sharedTableSchema.spec.ts +242 -0
- package/tests/transformPage.spec.ts +62 -81
- package/default-pages/application/page.json +0 -10
- package/default-pages/retro_game_starter/page.json +0 -12
- package/default-pages/sidebar_page/page.html +0 -51
- package/default-pages/sidebar_page/page.json +0 -10
- package/default-pages/two-panel_page/page.html +0 -68
- package/default-pages/two-panel_page/page.json +0 -10
|
@@ -23,169 +23,85 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.serverAPIs = exports.buildRouteHints = exports.DEFAULT_ROUTE_HINTS = exports.AGENT_API_REFERENCE = exports.getTransformInstr = exports.
|
|
26
|
+
exports.serverAPIs = exports.buildRouteHints = exports.DEFAULT_ROUTE_HINTS = exports.AGENT_API_REFERENCE = exports.getTransformInstr = exports.parseChangeList = exports.validateChangeOps = exports.applyChangeListWithReport = exports.applyChangeList = exports.ensureScriptsBeforeBodyClose = exports.deduplicateInlineScripts = exports.normalizedIndexOf = exports.stripNodeIds = exports.assignNodeIds = exports.transformPage = void 0;
|
|
27
27
|
const cheerio = __importStar(require("cheerio"));
|
|
28
28
|
async function transformPage(args) {
|
|
29
29
|
const { message, builder, additionalSections } = args;
|
|
30
30
|
// 0. Strip the early error-capture script so the LLM never sees it
|
|
31
31
|
const pageState = stripErrorCapture(args.pageState);
|
|
32
|
-
// 1. Assign data-
|
|
32
|
+
// 1. Assign data-nid to every element
|
|
33
33
|
const { html: annotatedHtml } = assignNodeIds(pageState);
|
|
34
|
+
// 1b. Strip HTML comments from the annotated copy sent to the LLM (saves tokens).
|
|
35
|
+
// The original pageState is preserved — changes are applied to the annotated copy
|
|
36
|
+
// and then node IDs are stripped, so comments survive in the saved page.
|
|
37
|
+
const llmHtml = annotatedHtml.replace(/<!--[\s\S]*?-->/g, '');
|
|
34
38
|
try {
|
|
35
39
|
// 2. Build CURRENT_PAGE section
|
|
36
40
|
const currentPage = {
|
|
37
41
|
title: '<CURRENT_PAGE>',
|
|
38
|
-
content:
|
|
42
|
+
content: llmHtml,
|
|
43
|
+
sketch: null,
|
|
44
|
+
mode: 'always-full',
|
|
39
45
|
instructions: '',
|
|
40
46
|
};
|
|
41
|
-
// 3. Determine newBuild: if isBuilder,
|
|
47
|
+
// 3. Determine newBuild: if isBuilder, check chat history length
|
|
42
48
|
let newBuild = false;
|
|
43
49
|
if (args.isBuilder) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
if (args.history) {
|
|
51
|
+
// Shell provides explicit history — empty or greeting-only means new build
|
|
52
|
+
newBuild = args.history.length <= 1;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Fallback: count .chat-message in annotated HTML (legacy pages without chat-history.json)
|
|
56
|
+
const $ = cheerio.load(annotatedHtml, { decodeEntities: false });
|
|
57
|
+
const messageCount = $('#chatMessages .chat-message').length;
|
|
58
|
+
newBuild = messageCount <= 1;
|
|
59
|
+
}
|
|
47
60
|
}
|
|
48
|
-
// 4. Call builder
|
|
49
|
-
const
|
|
61
|
+
// 4. Call builder (with timeout guard)
|
|
62
|
+
const TRANSFORM_TIMEOUT_MS = 600_000; // 10 minutes
|
|
63
|
+
const result = await Promise.race([
|
|
64
|
+
builder.run(currentPage, additionalSections, message, newBuild, args.attachments, args.tools, args.toolHandlers, args.onToolCall),
|
|
65
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Page transform timed out — the AI took too long to respond. Please try again.')), TRANSFORM_TIMEOUT_MS)),
|
|
66
|
+
]);
|
|
50
67
|
// 5. Switch on result kind
|
|
51
68
|
switch (result.kind) {
|
|
52
69
|
case 'transforms': {
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
70
|
+
const report = applyChangeListWithReport(annotatedHtml, result.changes);
|
|
71
|
+
const safe = postProcessHtml(report.html);
|
|
72
|
+
return { completed: true, value: {
|
|
73
|
+
html: safe,
|
|
74
|
+
changeCount: result.changes.length,
|
|
75
|
+
replyText: result.message,
|
|
76
|
+
skippedOps: report.skippedOps,
|
|
77
|
+
skipReasons: report.skipReasons,
|
|
78
|
+
} };
|
|
58
79
|
}
|
|
59
80
|
case 'reply': {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
const clean = stripNodeIds(withReply);
|
|
63
|
-
const deduped = deduplicateInlineScripts(clean);
|
|
64
|
-
const safe = ensureScriptsBeforeBodyClose(deduped);
|
|
65
|
-
return { completed: true, value: { html: safe, changeCount: -1 } };
|
|
81
|
+
const safe = postProcessHtml(annotatedHtml);
|
|
82
|
+
return { completed: true, value: { html: safe, changeCount: -1, replyText: result.text } };
|
|
66
83
|
}
|
|
67
84
|
case 'error': {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
const clean = stripNodeIds(errorHtml);
|
|
71
|
-
return { completed: true, value: { html: clean, changeCount: -1 } };
|
|
85
|
+
const clean = stripNodeIds(annotatedHtml);
|
|
86
|
+
return { completed: true, value: { html: clean, changeCount: -1, errorText: result.error.message } };
|
|
72
87
|
}
|
|
73
88
|
}
|
|
74
89
|
}
|
|
75
90
|
catch (err) {
|
|
76
|
-
// On any error: append error message to chat
|
|
77
|
-
const productName = args.productName ?? 'SynthOS';
|
|
78
91
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
return { completed: true, value: { html: clean, changeCount: -1 } };
|
|
92
|
+
const clean = stripNodeIds(annotatedHtml);
|
|
93
|
+
return { completed: true, value: { html: clean, changeCount: -1, errorText: errorMessage } };
|
|
82
94
|
}
|
|
83
95
|
}
|
|
84
96
|
exports.transformPage = transformPage;
|
|
85
97
|
// ---------------------------------------------------------------------------
|
|
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
|
-
// ---------------------------------------------------------------------------
|
|
185
98
|
// Internal helpers
|
|
186
99
|
// ---------------------------------------------------------------------------
|
|
100
|
+
/** Tags the LLM will never target — skip annotation to save tokens. */
|
|
101
|
+
const SKIP_ANNOTATION_TAGS = new Set(['br', 'wbr', 'col', 'source']);
|
|
187
102
|
/**
|
|
188
|
-
* Assign sequential `data-
|
|
103
|
+
* Assign sequential `data-nid` to every element in the HTML.
|
|
104
|
+
* Skips trivial elements (br, wbr, col, source) that the LLM never targets.
|
|
189
105
|
*/
|
|
190
106
|
function assignNodeIds(html) {
|
|
191
107
|
const $ = cheerio.load(html, { decodeEntities: false });
|
|
@@ -193,21 +109,116 @@ function assignNodeIds(html) {
|
|
|
193
109
|
$('*').each(function () {
|
|
194
110
|
const el = $(this);
|
|
195
111
|
if (this.type === 'tag' || this.type === 'script' || this.type === 'style') {
|
|
196
|
-
|
|
112
|
+
const tag = this.tagName?.toLowerCase() ?? this.name?.toLowerCase();
|
|
113
|
+
if (SKIP_ANNOTATION_TAGS.has(tag))
|
|
114
|
+
return;
|
|
115
|
+
el.attr('data-nid', String(counter++));
|
|
197
116
|
}
|
|
198
117
|
});
|
|
199
118
|
return { html: $.html(), nodeCount: counter };
|
|
200
119
|
}
|
|
201
120
|
exports.assignNodeIds = assignNodeIds;
|
|
202
121
|
/**
|
|
203
|
-
* Remove all `data-
|
|
122
|
+
* Remove all `data-nid` attributes from the HTML.
|
|
204
123
|
*/
|
|
205
124
|
function stripNodeIds(html) {
|
|
206
125
|
const $ = cheerio.load(html, { decodeEntities: false });
|
|
207
|
-
$('[data-
|
|
126
|
+
$('[data-nid]').removeAttr('data-nid');
|
|
208
127
|
return $.html();
|
|
209
128
|
}
|
|
210
129
|
exports.stripNodeIds = stripNodeIds;
|
|
130
|
+
/**
|
|
131
|
+
* Consolidated post-processing: strip node IDs, deduplicate scripts, and
|
|
132
|
+
* ensure script ordering — all in a single cheerio parse/serialize cycle.
|
|
133
|
+
*/
|
|
134
|
+
function postProcessHtml(html) {
|
|
135
|
+
const $ = cheerio.load(html, { decodeEntities: false });
|
|
136
|
+
// Strip data-nid attributes
|
|
137
|
+
$('[data-nid]').removeAttr('data-nid');
|
|
138
|
+
// Deduplicate inline scripts (ID-based pass)
|
|
139
|
+
const SYSTEM_IDS = new Set(['page-info', 'page-helpers', 'page-script', 'error', 'shell-v3', 'server-v3', 'storage-v3', 'script-v3', 'connector-v3', 'agent-v3']);
|
|
140
|
+
const idGroups = new Map();
|
|
141
|
+
$('script').each(function (_, rawEl) {
|
|
142
|
+
const el = $(rawEl);
|
|
143
|
+
if (el.attr('src'))
|
|
144
|
+
return;
|
|
145
|
+
const id = el.attr('id');
|
|
146
|
+
if (!id || SYSTEM_IDS.has(id))
|
|
147
|
+
return;
|
|
148
|
+
if (!idGroups.has(id))
|
|
149
|
+
idGroups.set(id, []);
|
|
150
|
+
idGroups.get(id).push(el);
|
|
151
|
+
});
|
|
152
|
+
for (const [id, group] of idGroups) {
|
|
153
|
+
if (group.length < 2)
|
|
154
|
+
continue;
|
|
155
|
+
for (let i = 0; i < group.length - 1; i++) {
|
|
156
|
+
console.log(`deduplicateInlineScripts: removing duplicate script id="${id}" (keeping last of ${group.length})`);
|
|
157
|
+
group[i].remove();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Deduplicate inline scripts (declaration-overlap pass for id-less scripts)
|
|
161
|
+
const declPattern = /(?:^|;|\n)\s*(?:let|const|var|function|class)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
|
162
|
+
const scripts = [];
|
|
163
|
+
$('script').each(function (_, rawEl) {
|
|
164
|
+
const el = $(rawEl);
|
|
165
|
+
if (el.attr('src'))
|
|
166
|
+
return;
|
|
167
|
+
if (el.attr('id'))
|
|
168
|
+
return;
|
|
169
|
+
if ((el.attr('type') ?? '').toLowerCase() === 'application/json')
|
|
170
|
+
return;
|
|
171
|
+
const code = (el.html() ?? '').trim();
|
|
172
|
+
if (!code)
|
|
173
|
+
return;
|
|
174
|
+
const declarations = new Set();
|
|
175
|
+
let m;
|
|
176
|
+
declPattern.lastIndex = 0;
|
|
177
|
+
while ((m = declPattern.exec(code)) !== null)
|
|
178
|
+
declarations.add(m[1]);
|
|
179
|
+
scripts.push({ el, declarations });
|
|
180
|
+
});
|
|
181
|
+
const toRemove = new Set();
|
|
182
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
183
|
+
if (toRemove.has(i))
|
|
184
|
+
continue;
|
|
185
|
+
for (let j = i + 1; j < scripts.length; j++) {
|
|
186
|
+
if (toRemove.has(j))
|
|
187
|
+
continue;
|
|
188
|
+
const a = scripts[i].declarations, b = scripts[j].declarations;
|
|
189
|
+
if (a.size < 2 || b.size < 2)
|
|
190
|
+
continue;
|
|
191
|
+
let overlap = 0;
|
|
192
|
+
for (const name of a)
|
|
193
|
+
if (b.has(name))
|
|
194
|
+
overlap++;
|
|
195
|
+
if (overlap / Math.min(a.size, b.size) >= 0.6) {
|
|
196
|
+
console.log(`deduplicateInlineScripts: removing duplicate script (${overlap}/${Math.min(a.size, b.size)} declaration overlap)`);
|
|
197
|
+
toRemove.add(i);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
for (const idx of toRemove)
|
|
203
|
+
scripts[idx].el.remove();
|
|
204
|
+
// Ensure page-helpers and page-script are last children of <body>
|
|
205
|
+
const body = $('body');
|
|
206
|
+
if (body.length > 0) {
|
|
207
|
+
const helpers = $('script#page-helpers');
|
|
208
|
+
const pageScript = $('script#page-script');
|
|
209
|
+
const helpersHtml = helpers.length > 0 ? $.html(helpers.first()) : '';
|
|
210
|
+
const pageScriptHtml = pageScript.length > 0 ? $.html(pageScript.first()) : '';
|
|
211
|
+
if (helpers.length > 0)
|
|
212
|
+
helpers.remove();
|
|
213
|
+
if (pageScript.length > 0)
|
|
214
|
+
pageScript.remove();
|
|
215
|
+
if (helpersHtml)
|
|
216
|
+
body.append(helpersHtml);
|
|
217
|
+
if (pageScriptHtml)
|
|
218
|
+
body.append(pageScriptHtml);
|
|
219
|
+
}
|
|
220
|
+
return $.html();
|
|
221
|
+
}
|
|
211
222
|
/**
|
|
212
223
|
* Remove the early error-capture script injected into <head> so the LLM
|
|
213
224
|
* never sees it during page transformation.
|
|
@@ -282,7 +293,7 @@ exports.normalizedIndexOf = normalizedIndexOf;
|
|
|
282
293
|
*/
|
|
283
294
|
function deduplicateInlineScripts(html) {
|
|
284
295
|
const $ = cheerio.load(html, { decodeEntities: false });
|
|
285
|
-
const SYSTEM_IDS = new Set(['page-info', 'page-helpers', 'page-script', 'error']);
|
|
296
|
+
const SYSTEM_IDS = new Set(['page-info', 'page-helpers', 'page-script', 'error', 'shell-v3', 'server-v3', 'storage-v3', 'script-v3', 'connector-v3', 'agent-v3']);
|
|
286
297
|
// ── Pass 1: ID-based dedup ──────────────────────────────────────────
|
|
287
298
|
const idGroups = new Map();
|
|
288
299
|
$('script').each(function (_, rawEl) {
|
|
@@ -374,8 +385,8 @@ function ensureScriptsBeforeBodyClose(html) {
|
|
|
374
385
|
// Capture outer HTML before removing so we can re-append
|
|
375
386
|
const helpers = $('script#page-helpers');
|
|
376
387
|
const pageScript = $('script#page-script');
|
|
377
|
-
const helpersHtml = helpers.length > 0 ? $.html(helpers) : '';
|
|
378
|
-
const pageScriptHtml = pageScript.length > 0 ? $.html(pageScript) : '';
|
|
388
|
+
const helpersHtml = helpers.length > 0 ? $.html(helpers.first()) : '';
|
|
389
|
+
const pageScriptHtml = pageScript.length > 0 ? $.html(pageScript.first()) : '';
|
|
379
390
|
// Remove from current position and re-append at end of <body>
|
|
380
391
|
if (helpers.length > 0)
|
|
381
392
|
helpers.remove();
|
|
@@ -395,16 +406,40 @@ function isElementLocked(el, $) {
|
|
|
395
406
|
return el.attr('data-locked') !== undefined;
|
|
396
407
|
}
|
|
397
408
|
/**
|
|
398
|
-
* If the
|
|
399
|
-
*
|
|
400
|
-
*
|
|
409
|
+
* If the provided html is wrapped in a redundant tag that matches the target
|
|
410
|
+
* element, strip the outer tag to avoid nesting (e.g. `<div id="x">` inside
|
|
411
|
+
* the existing `<div id="x">`). For script/style elements the tag name alone
|
|
412
|
+
* is sufficient; for other elements we require an id or class match to avoid
|
|
413
|
+
* false positives.
|
|
401
414
|
*/
|
|
402
|
-
function unwrapRedundantTag(tagName, html) {
|
|
403
|
-
if (tagName
|
|
415
|
+
function unwrapRedundantTag(tagName, html, targetId, targetClass) {
|
|
416
|
+
if (!tagName)
|
|
404
417
|
return html;
|
|
405
|
-
const re = new RegExp(`^\\s*<${tagName}[^>]
|
|
418
|
+
const re = new RegExp(`^\\s*<${tagName}\\b([^>]*)>([\\s\\S]*)</${tagName}>\\s*$`, 'i');
|
|
406
419
|
const match = html.match(re);
|
|
407
|
-
|
|
420
|
+
if (!match)
|
|
421
|
+
return html;
|
|
422
|
+
// For script/style, always unwrap (original behaviour)
|
|
423
|
+
if (tagName === 'script' || tagName === 'style') {
|
|
424
|
+
return match[2];
|
|
425
|
+
}
|
|
426
|
+
// For other elements, require an id or class match to avoid false positives
|
|
427
|
+
const outerAttrs = match[1];
|
|
428
|
+
if (targetId) {
|
|
429
|
+
const idMatch = outerAttrs.match(/\bid=["']([^"']*)["']/);
|
|
430
|
+
if (idMatch && idMatch[1] === targetId)
|
|
431
|
+
return match[2];
|
|
432
|
+
}
|
|
433
|
+
if (targetClass) {
|
|
434
|
+
const classMatch = outerAttrs.match(/\bclass=["']([^"']*)["']/);
|
|
435
|
+
if (classMatch) {
|
|
436
|
+
const targetClasses = targetClass.split(/\s+/);
|
|
437
|
+
const outerClasses = classMatch[1].split(/\s+/);
|
|
438
|
+
if (targetClasses.some(c => outerClasses.includes(c)))
|
|
439
|
+
return match[2];
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return html;
|
|
408
443
|
}
|
|
409
444
|
/**
|
|
410
445
|
* Strip any `<script>` or `<style>` tags from text that will be injected
|
|
@@ -418,30 +453,47 @@ function stripNestedBlockTags(tagName, text) {
|
|
|
418
453
|
.replace(new RegExp(`</${tagName}>`, 'gi'), '');
|
|
419
454
|
}
|
|
420
455
|
/**
|
|
421
|
-
* Apply a list of CRUD operations to annotated HTML (elements must have `data-
|
|
456
|
+
* Apply a list of CRUD operations to annotated HTML (elements must have `data-nid`).
|
|
457
|
+
* Returns just the resulting HTML string. For diagnostic info about skipped ops,
|
|
458
|
+
* use `applyChangeListWithReport` instead.
|
|
422
459
|
*/
|
|
423
460
|
function applyChangeList(html, changes) {
|
|
461
|
+
return applyChangeListWithReport(html, changes).html;
|
|
462
|
+
}
|
|
463
|
+
exports.applyChangeList = applyChangeList;
|
|
464
|
+
/**
|
|
465
|
+
* Apply a list of CRUD operations and return the resulting HTML plus a report
|
|
466
|
+
* of any ops that were silently skipped (missing target node, locked element,
|
|
467
|
+
* search text not found, etc.). Callers can surface the skip report to the
|
|
468
|
+
* user so that partial/broken builds do not fail silently.
|
|
469
|
+
*/
|
|
470
|
+
function applyChangeListWithReport(html, changes) {
|
|
424
471
|
const $ = cheerio.load(html, { decodeEntities: false });
|
|
472
|
+
const skipReasons = [];
|
|
473
|
+
const recordSkip = (reason) => {
|
|
474
|
+
console.warn(`applyChangeList: ${reason}`);
|
|
475
|
+
skipReasons.push(reason);
|
|
476
|
+
};
|
|
425
477
|
for (const change of changes) {
|
|
426
478
|
switch (change.op) {
|
|
427
479
|
case 'update': {
|
|
428
|
-
const el = $(`[data-
|
|
480
|
+
const el = $(`[data-nid="${change.nodeId}"]`);
|
|
429
481
|
if (el.length === 0) {
|
|
430
|
-
|
|
482
|
+
recordSkip(`skipping update — node ${change.nodeId} not found (already removed?)`);
|
|
431
483
|
break;
|
|
432
484
|
}
|
|
433
485
|
const tag = el.prop('tagName')?.toLowerCase();
|
|
434
|
-
el.html(unwrapRedundantTag(tag, change.html));
|
|
486
|
+
el.html(unwrapRedundantTag(tag, change.html, el.attr('id'), el.attr('class')));
|
|
435
487
|
break;
|
|
436
488
|
}
|
|
437
489
|
case 'replace': {
|
|
438
|
-
const el = $(`[data-
|
|
490
|
+
const el = $(`[data-nid="${change.nodeId}"]`);
|
|
439
491
|
if (el.length === 0) {
|
|
440
|
-
|
|
492
|
+
recordSkip(`skipping replace — node ${change.nodeId} not found (already removed?)`);
|
|
441
493
|
break;
|
|
442
494
|
}
|
|
443
495
|
if (isElementLocked(el, $)) {
|
|
444
|
-
|
|
496
|
+
recordSkip(`skipping replace — node ${change.nodeId} is data-locked`);
|
|
445
497
|
break;
|
|
446
498
|
}
|
|
447
499
|
// If the target is a <script> or <style> and the html doesn't
|
|
@@ -458,22 +510,22 @@ function applyChangeList(html, changes) {
|
|
|
458
510
|
break;
|
|
459
511
|
}
|
|
460
512
|
case 'delete': {
|
|
461
|
-
const el = $(`[data-
|
|
513
|
+
const el = $(`[data-nid="${change.nodeId}"]`);
|
|
462
514
|
if (el.length === 0) {
|
|
463
|
-
|
|
515
|
+
recordSkip(`skipping delete — node ${change.nodeId} not found (already removed?)`);
|
|
464
516
|
break;
|
|
465
517
|
}
|
|
466
518
|
if (isElementLocked(el, $)) {
|
|
467
|
-
|
|
519
|
+
recordSkip(`skipping delete — node ${change.nodeId} is data-locked`);
|
|
468
520
|
break;
|
|
469
521
|
}
|
|
470
522
|
el.remove();
|
|
471
523
|
break;
|
|
472
524
|
}
|
|
473
525
|
case 'insert': {
|
|
474
|
-
const parent = $(`[data-
|
|
526
|
+
const parent = $(`[data-nid="${change.parentId}"]`);
|
|
475
527
|
if (parent.length === 0) {
|
|
476
|
-
|
|
528
|
+
recordSkip(`skipping insert — parent node ${change.parentId} not found`);
|
|
477
529
|
break;
|
|
478
530
|
}
|
|
479
531
|
// Unwrap redundant tags when inserting into script/style
|
|
@@ -493,27 +545,27 @@ function applyChangeList(html, changes) {
|
|
|
493
545
|
parent.after(insertHtml);
|
|
494
546
|
break;
|
|
495
547
|
default:
|
|
496
|
-
|
|
548
|
+
recordSkip(`skipping insert — unknown position "${change.position}"`);
|
|
497
549
|
}
|
|
498
550
|
break;
|
|
499
551
|
}
|
|
500
552
|
case 'style-element': {
|
|
501
|
-
const el = $(`[data-
|
|
553
|
+
const el = $(`[data-nid="${change.nodeId}"]`);
|
|
502
554
|
if (el.length === 0) {
|
|
503
|
-
|
|
555
|
+
recordSkip(`skipping style-element — node ${change.nodeId} not found (already removed?)`);
|
|
504
556
|
break;
|
|
505
557
|
}
|
|
506
558
|
if (isElementLocked(el, $)) {
|
|
507
|
-
|
|
559
|
+
recordSkip(`skipping style-element — node ${change.nodeId} is data-locked`);
|
|
508
560
|
break;
|
|
509
561
|
}
|
|
510
562
|
el.attr('style', change.style);
|
|
511
563
|
break;
|
|
512
564
|
}
|
|
513
565
|
case 'search-replace': {
|
|
514
|
-
const el = $(`[data-
|
|
566
|
+
const el = $(`[data-nid="${change.nodeId}"]`);
|
|
515
567
|
if (el.length === 0) {
|
|
516
|
-
|
|
568
|
+
recordSkip(`skipping search-replace — node ${change.nodeId} not found (already removed?)`);
|
|
517
569
|
break;
|
|
518
570
|
}
|
|
519
571
|
const srTag = el.prop('tagName')?.toLowerCase();
|
|
@@ -529,15 +581,15 @@ function applyChangeList(html, changes) {
|
|
|
529
581
|
el.html(content.slice(0, norm.start) + replaceText + content.slice(norm.end));
|
|
530
582
|
}
|
|
531
583
|
else {
|
|
532
|
-
|
|
584
|
+
recordSkip(`skipping search-replace — search text not found in node ${change.nodeId}`);
|
|
533
585
|
}
|
|
534
586
|
}
|
|
535
587
|
break;
|
|
536
588
|
}
|
|
537
589
|
case 'search-insert': {
|
|
538
|
-
const el = $(`[data-
|
|
590
|
+
const el = $(`[data-nid="${change.nodeId}"]`);
|
|
539
591
|
if (el.length === 0) {
|
|
540
|
-
|
|
592
|
+
recordSkip(`skipping search-insert — node ${change.nodeId} not found (already removed?)`);
|
|
541
593
|
break;
|
|
542
594
|
}
|
|
543
595
|
const siTag = el.prop('tagName')?.toLowerCase();
|
|
@@ -554,7 +606,7 @@ function applyChangeList(html, changes) {
|
|
|
554
606
|
el.html(content.slice(0, norm.end) + insertContent + content.slice(norm.end));
|
|
555
607
|
}
|
|
556
608
|
else {
|
|
557
|
-
|
|
609
|
+
recordSkip(`skipping search-insert — after text not found in node ${change.nodeId}`);
|
|
558
610
|
}
|
|
559
611
|
}
|
|
560
612
|
break;
|
|
@@ -563,32 +615,73 @@ function applyChangeList(html, changes) {
|
|
|
563
615
|
throw new Error(`Unknown change op: "${change.op}"`);
|
|
564
616
|
}
|
|
565
617
|
}
|
|
566
|
-
return $.html();
|
|
618
|
+
return { html: $.html(), skippedOps: skipReasons.length, skipReasons };
|
|
567
619
|
}
|
|
568
|
-
exports.
|
|
620
|
+
exports.applyChangeListWithReport = applyChangeListWithReport;
|
|
621
|
+
/** Known op types and their required fields (beyond `op`). */
|
|
622
|
+
const CHANGE_OP_REQUIRED_FIELDS = {
|
|
623
|
+
'update': ['nodeId', 'html'],
|
|
624
|
+
'replace': ['nodeId', 'html'],
|
|
625
|
+
'delete': ['nodeId'],
|
|
626
|
+
'insert': ['parentId', 'position', 'html'],
|
|
627
|
+
'style-element': ['nodeId', 'style'],
|
|
628
|
+
'search-replace': ['nodeId', 'search', 'replace'],
|
|
629
|
+
'search-insert': ['nodeId', 'after', 'content'],
|
|
630
|
+
};
|
|
631
|
+
const VALID_INSERT_POSITIONS = new Set(['prepend', 'append', 'before', 'after']);
|
|
569
632
|
/**
|
|
570
|
-
*
|
|
633
|
+
* Validate and filter a raw parsed array into a well-formed ChangeList.
|
|
634
|
+
* Invalid ops are logged as warnings and dropped rather than crashing.
|
|
571
635
|
*/
|
|
572
|
-
function
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
636
|
+
function validateChangeOps(raw) {
|
|
637
|
+
const valid = [];
|
|
638
|
+
for (let i = 0; i < raw.length; i++) {
|
|
639
|
+
const item = raw[i];
|
|
640
|
+
if (!item || typeof item !== 'object') {
|
|
641
|
+
console.warn(`validateChangeOps: skipping op[${i}] — not an object`);
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
const op = item.op;
|
|
645
|
+
if (typeof op !== 'string') {
|
|
646
|
+
console.warn(`validateChangeOps: skipping op[${i}] — missing or non-string 'op' field`);
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
const requiredFields = CHANGE_OP_REQUIRED_FIELDS[op];
|
|
650
|
+
if (!requiredFields) {
|
|
651
|
+
console.warn(`validateChangeOps: skipping op[${i}] — unknown op type '${op}'`);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
let missingField = false;
|
|
655
|
+
for (const field of requiredFields) {
|
|
656
|
+
if (typeof item[field] !== 'string') {
|
|
657
|
+
console.warn(`validateChangeOps: skipping op[${i}] (${op}) — missing or non-string '${field}' field`);
|
|
658
|
+
missingField = true;
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (missingField)
|
|
663
|
+
continue;
|
|
664
|
+
// Extra validation for insert position
|
|
665
|
+
if (op === 'insert' && !VALID_INSERT_POSITIONS.has(item.position)) {
|
|
666
|
+
console.warn(`validateChangeOps: skipping op[${i}] (insert) — invalid position '${item.position}'`);
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
valid.push(item);
|
|
579
670
|
}
|
|
580
|
-
return
|
|
671
|
+
return valid;
|
|
581
672
|
}
|
|
673
|
+
exports.validateChangeOps = validateChangeOps;
|
|
582
674
|
/**
|
|
583
675
|
* Parse a JSON change list from the model's raw response text.
|
|
584
676
|
* Handles responses that may include markdown fences or extra text around the JSON.
|
|
677
|
+
* Invalid ops are logged as warnings and filtered out.
|
|
585
678
|
*/
|
|
586
679
|
function parseChangeList(response) {
|
|
587
680
|
// Try direct parse first
|
|
588
681
|
try {
|
|
589
682
|
const parsed = JSON.parse(response);
|
|
590
683
|
if (Array.isArray(parsed))
|
|
591
|
-
return parsed;
|
|
684
|
+
return validateChangeOps(parsed);
|
|
592
685
|
}
|
|
593
686
|
catch {
|
|
594
687
|
// fall through to extraction
|
|
@@ -599,7 +692,7 @@ function parseChangeList(response) {
|
|
|
599
692
|
try {
|
|
600
693
|
const parsed = JSON.parse(match[0]);
|
|
601
694
|
if (Array.isArray(parsed))
|
|
602
|
-
return parsed;
|
|
695
|
+
return validateChangeOps(parsed);
|
|
603
696
|
}
|
|
604
697
|
catch {
|
|
605
698
|
// fall through
|
|
@@ -611,43 +704,55 @@ exports.parseChangeList = parseChangeList;
|
|
|
611
704
|
// ---------------------------------------------------------------------------
|
|
612
705
|
// Prompt constants
|
|
613
706
|
// ---------------------------------------------------------------------------
|
|
614
|
-
function getMessageFormat(productName) {
|
|
615
|
-
return `<MESSAGE_FORMAT>
|
|
616
|
-
<div class="chat-message"><p><strong>{${productName}: | User:}</strong> {message contents}</p></div>
|
|
617
|
-
`;
|
|
618
|
-
}
|
|
619
|
-
exports.getMessageFormat = getMessageFormat;
|
|
620
707
|
function getTransformInstr(productName) {
|
|
621
708
|
return `Apply the users <USER_MESSAGE> to the .viewerPanel of the <CURRENT_PAGE> by generating a list of changes in JSON format.
|
|
622
|
-
|
|
709
|
+
Your response is a JSON object with two fields: "message" and "changes". The "message" field is REQUIRED on every response and must be a brief (1 sentence) message written directly to the user explaining what you did — it is shown in the chat feed as your reply. Do not describe the JSON; speak to the user (e.g. "Added a dark-mode toggle to the header."). The "changes" field is the array of change operations.
|
|
710
|
+
Never remove any element that has a data-locked attribute. You may modify the inner text of a data-locked element or any of its unlocked child elements.
|
|
623
711
|
|
|
624
|
-
|
|
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.
|
|
628
|
-
If a <USER_MESSAGE> is overly long, summarize the User: message.
|
|
712
|
+
Your page runs inside an iframe. The chat panel, toolbar, and all shell chrome are in the parent frame — they are NOT part of your page HTML. Do not generate chat messages, chat-message divs, or any shell markup. Focus only on the page content inside .viewerPanel.
|
|
629
713
|
|
|
630
|
-
When updating the .viewerPanel you may
|
|
714
|
+
When updating the .viewerPanel you may also add/remove/update style blocks in the head unless they're data-locked. Use inline styles if you need to modify the .viewerPanel itself.
|
|
631
715
|
You may add/remove new script blocks to the body but all script & style blocks should have a unique id.
|
|
632
716
|
You may modify the contents of a data-locked script block but may not remove it.
|
|
633
717
|
|
|
634
718
|
Every <CURRENT_PAGE> has hidden data-locked "thoughts" and "instructions" divs.
|
|
635
|
-
The
|
|
719
|
+
The instructions div, if present, contains custom <INSTRUCTIONS> for that page that should be followed in addition to these general instructions. You may modify the instructions div if needed (e.g. to add new instructions or update existing ones), but do not remove it. Add it if it's missing.
|
|
636
720
|
The thoughts block is for your internal use only — you can write anything in there to help you reason through the user's request, but it is not visible to the user. You can also use it to keep track of any relevant state or information that may be useful across multiple turns.
|
|
637
721
|
If the <USER_MESSAGE> indicates that a change didn't work, use your thoughts to diagnose the problem before fixing the issue.
|
|
722
|
+
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.
|
|
723
|
+
|
|
724
|
+
Patchable Data Convention — When a page stores editable metadata that should survive round-trips and be independently updatable, use data-* attributes on identifiable elements (elements with an id or a unique, stable CSS selector). This allows the patch API to update individual values without a full page re-render. Do NOT store patchable data as text content inside elements.
|
|
725
|
+
Examples: data-notes on .slide elements (speaker notes), data-duration on .slide elements (slide timing), data-label on interactive elements (accessible names).
|
|
638
726
|
|
|
639
|
-
The <MESSAGE_FORMAT> section provides the HTML structure for chat messages in the chat panel. Use this format when generating new messages to ensure they display correctly.
|
|
640
727
|
The <SERVER_APIS> section provides a list of available server APIs and helper functions you can call from injected scripts. You should use the synthos.* helper functions for any server API calls instead of raw fetch().
|
|
641
|
-
The <SERVER_SCRIPTS> section provides a list of available scripts you can call from injected scripts. These are user-created scripts stored on the server that can be executed by calling synthos.
|
|
642
|
-
The <THEME> section provides details on the current theme's color scheme
|
|
643
|
-
The viewer panel can be resized by the user, so for animations, games, and presentations should always add the "
|
|
644
|
-
window.themeInfo is available and has a structure like this: { mode: 'light' | 'dark', colors: { primary: '#hex', secondary: '#hex', background: '#hex', text: '#hex', ... } }. Use these colors instead of hardcoded values to ensure your page works with the user's selected theme and any custom themes they may have.
|
|
728
|
+
The <SERVER_SCRIPTS> section provides a list of available scripts you can call from injected scripts. These are user-created scripts stored on the server that can be executed by calling synthos.script.run(id, variables).
|
|
729
|
+
The <THEME> section provides details on the current theme's color scheme to help you generate theme-aware pages.
|
|
730
|
+
The viewer panel can be resized by the user, so for animations, games, and presentations should always add the "full-viewer" class to the viewer-panel element and ensure content stays centered and uses the maximum available space (use 100% width/height, flexbox centering, or viewport-relative sizing as appropriate).
|
|
731
|
+
window.themeInfo is available and has a structure like this: { mode: 'light' | 'dark', colors: { primary: '#hex', secondary: '#hex', background: '#hex', text: '#hex', ... } }. Use these colors instead of hardcoded values to ensure your page works with the user's selected theme and any custom themes they may have.
|
|
645
732
|
|
|
646
|
-
|
|
733
|
+
Visual Design Guide — CRITICAL: Follow these rules exactly so pages look polished. Pages that ignore spacing rules look broken.
|
|
734
|
+
- SPACING TOKENS: --spacingS2: 4px, --spacingS1: 8px, --spacingM: 16px, --spacingL1: 20px, --spacingL2: 32px.
|
|
735
|
+
- PAGE HEADER: padding MUST be at least var(--spacingM) vertically and var(--spacingL1) horizontally (e.g. padding: var(--spacingM) var(--spacingL1)). Never use --spacingS1 for page header padding — it looks cramped.
|
|
736
|
+
- CONTENT AREAS: Main content sections (tab panels, card bodies, scroll areas) MUST have at least padding: var(--spacingL1) (20px). Use var(--spacingL2) (32px) for primary page content padding when there is room.
|
|
737
|
+
- SECTION GAPS: Leave at least var(--spacingL1) (20px) gap between major sections (header-to-tabs, tabs-to-content, between card groups). Use var(--spacingM) (16px) minimum between related items within a section.
|
|
738
|
+
- CARD/PANEL PADDING: Cards and panels MUST have at least padding: var(--spacingM) (16px) inside. Use var(--spacingL1) for larger content cards.
|
|
739
|
+
- TAB BARS (flm-pivot): Tab container should have padding: var(--spacingM) var(--spacingL1) — never --spacingS1 which makes tabs look squished.
|
|
740
|
+
- TYPOGRAPHY HIERARCHY: Use FluentLM text classes — flm-text--xxLarge or flm-text--xLarge + flm-text--semibold for page titles, flm-text--large for section headings, flm-text (default 14px) for body, flm-text--small + flm-text--secondary for captions. Always apply flm-text--block on block-level text.
|
|
741
|
+
- SURFACES & CARDS: Use theme color variables only: background var(--white) with border 1px solid var(--neutralLight), or box-shadow var(--elevation4) for elevated cards. Section backgrounds: var(--neutralLighterAlt). NEVER use custom variables like --bg-secondary, --border-color, --text-primary — these do not exist. Always use the real theme variables: --white, --black, --neutralLight, --neutralLighter, --neutralLighterAlt, --neutralPrimary, --neutralSecondary, --themePrimary, --themeLight, --themeLighter, --themeLighterAlt, --themeDark, --themeDarker.
|
|
742
|
+
- ELEVATION: var(--elevation4) for cards, var(--elevation8) for dropdowns, var(--elevation16) for modals.
|
|
743
|
+
- BORDER RADIUS: 4px on containers, 2px on badges/chips, 4px on buttons.
|
|
744
|
+
- DATA TABLES: Headers: background var(--neutralLighterAlt), font-weight 600, padding var(--spacingS1) var(--spacingM). Rows: border-bottom 1px solid var(--neutralLight), cell padding var(--spacingS1) var(--spacingM).
|
|
745
|
+
- LAYOUT: Use flm-stack / flm-stack--horizontal with gap via spacing tokens. CSS grid with gap: var(--spacingM). Use max-width on wide layouts.
|
|
746
|
+
- VISUAL WEIGHT: var(--themePrimary) for interactive elements only, not large backgrounds. var(--neutralPrimary) for body text, var(--neutralSecondary) for secondary text.
|
|
747
|
+
- EMPTY STATES: Center the message vertically and horizontally with an icon. Add generous padding (at least var(--spacingL2)) around empty state messages.
|
|
647
748
|
|
|
648
|
-
|
|
749
|
+
Do not add duplicate script blocks with the same logic! Consolidate inline scripts if needed and double check that variables and functions are defined in the correct order.
|
|
750
|
+
Prefer a single inline <script> block for all of your page's JavaScript. If you must split logic across two blocks, that's acceptable, but one is strongly preferred.
|
|
751
|
+
Inline <script> blocks on your page are automatically wrapped in an IIFE (except scripts with src, data-locked scripts, or scripts already wrapped in (function(){ ... })()). This means top-level \`function\`, \`const\`, \`let\`, and \`var\` declarations inside a script are LOCAL to that script and cannot be called from another script. Put functions that need to call each other in the same <script> block, or explicitly attach them to \`window\` (e.g. \`window.myFunc = function(){...}\`) if they must be shared across blocks.
|
|
752
|
+
Place all <script> blocks at the end of <body>, never in <head> — scripts in <head> run before the DOM is parsed and will crash if they reference body elements.
|
|
753
|
+
|
|
754
|
+
Each element in the CURRENT_PAGE has a data-nid attribute. Don't use the id attribute for targeting nodes (reserve it for scripts and styles) — use data-nid.
|
|
649
755
|
If you're trying to assign an id to script or style block, use "replace" not "update".
|
|
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.
|
|
651
756
|
|
|
652
757
|
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
758
|
}
|
|
@@ -655,19 +760,19 @@ exports.getTransformInstr = getTransformInstr;
|
|
|
655
760
|
exports.AGENT_API_REFERENCE = `## Agent API
|
|
656
761
|
|
|
657
762
|
Check availability first (required):
|
|
658
|
-
const agents = await synthos.
|
|
763
|
+
const agents = await synthos.agent.list({ enabled: true });
|
|
659
764
|
|
|
660
765
|
Send a message (returns full response):
|
|
661
|
-
const result = await synthos.
|
|
766
|
+
const result = await synthos.agent.send(agentId, message);
|
|
662
767
|
// result: { kind: 'message', text: 'response text', raw: {...} }
|
|
663
768
|
|
|
664
769
|
Send with file/image attachments:
|
|
665
|
-
const result = await synthos.
|
|
770
|
+
const result = await synthos.agent.send(agentId, message, [
|
|
666
771
|
{ fileName: 'photo.jpg', mimeType: 'image/jpeg', content: '<base64-string>' }
|
|
667
772
|
]);
|
|
668
773
|
|
|
669
774
|
Stream a response (token-by-token deltas):
|
|
670
|
-
const handle = synthos.
|
|
775
|
+
const handle = synthos.agent.sendStream(agentId, message, function(event) {
|
|
671
776
|
switch (event.kind) {
|
|
672
777
|
case 'text': // event.data = text delta string — append to output
|
|
673
778
|
case 'status': // event.data = status info object
|
|
@@ -678,11 +783,11 @@ Stream a response (token-by-token deltas):
|
|
|
678
783
|
handle.close(); // call to abort the stream early
|
|
679
784
|
|
|
680
785
|
Stream with attachments:
|
|
681
|
-
synthos.
|
|
786
|
+
synthos.agent.sendStream(agentId, message, onEvent, [
|
|
682
787
|
{ fileName: 'doc.pdf', mimeType: 'application/pdf', content: '<base64>' }
|
|
683
788
|
]);
|
|
684
789
|
|
|
685
|
-
IMPORTANT: Always check synthos.
|
|
790
|
+
IMPORTANT: Always check synthos.agent.list({ enabled: true }) before calling an agent.
|
|
686
791
|
If no agents are configured, show the user a link to Settings > Agents (/settings?tab=agents).`;
|
|
687
792
|
// ---------------------------------------------------------------------------
|
|
688
793
|
// Route hint blocks — keyed by feature group so they can be filtered
|
|
@@ -717,12 +822,19 @@ request: { prompt: string, shape: 'square' | 'portrait' | 'landscape', style: 'v
|
|
|
717
822
|
response: { url: string }
|
|
718
823
|
|
|
719
824
|
POST /api/generate/completion
|
|
720
|
-
description: Generates a
|
|
721
|
-
request: { prompt: string, temperature?: number }
|
|
722
|
-
response: { answer: string
|
|
825
|
+
description: Generates a completion based on a prompt. When \`schema\` is provided, the model is constrained to emit JSON conforming to it (structured output) and the parsed object is returned directly. Without \`schema\`, returns plain text.
|
|
826
|
+
request: { prompt: string, temperature?: number, schema?: object (JSON schema) }
|
|
827
|
+
response (no schema): { answer: string }
|
|
828
|
+
response (with schema): the parsed JSON object matching \`schema\` (e.g. \`{ items: [...] }\`)
|
|
829
|
+
|
|
830
|
+
Schema notes:
|
|
831
|
+
- Use this when you need structured data — never tell the model "return JSON with these fields" via the prompt and parse \`answer\`.
|
|
832
|
+
- Top-level schema must be \`type: 'object'\`. To return a list, wrap it: \`{ type: 'object', additionalProperties: false, required: ['items'], properties: { items: { type: 'array', items: {...} } } }\`.
|
|
833
|
+
- Every nested \`type: 'object'\` MUST set \`additionalProperties: false\` — Anthropic structured-output rejects schemas that omit it.
|
|
834
|
+
- Mark every required field in \`required: [...]\` so the model is forced to emit them.
|
|
723
835
|
|
|
724
|
-
synthos.generate.image({ prompt, shape, style })
|
|
725
|
-
synthos.generate.completion({ prompt, temperature? })
|
|
836
|
+
synthos.generate.image({ prompt, shape, style }) — POST /api/generate/image
|
|
837
|
+
synthos.generate.completion({ prompt, temperature?, schema? }) — POST /api/generate/completion`],
|
|
726
838
|
['pages', `GET /api/pages
|
|
727
839
|
description: Retrieve a list of all pages with metadata
|
|
728
840
|
response: Array of { name: string, title: string, categories: string[], pinned: boolean, createdDate: string, lastModified: string, pageVersion: number, mode: 'unlocked' | 'locked' }
|
|
@@ -745,17 +857,17 @@ description: Ask a question about a page with full HTML context
|
|
|
745
857
|
request: { question: string }
|
|
746
858
|
response: { answer: string }
|
|
747
859
|
|
|
748
|
-
synthos.
|
|
749
|
-
synthos.
|
|
750
|
-
synthos.
|
|
751
|
-
synthos.
|
|
752
|
-
synthos.
|
|
860
|
+
synthos.page.list() — GET /api/pages
|
|
861
|
+
synthos.page.get(name) — GET /api/pages/:name
|
|
862
|
+
synthos.page.update(name, metadata) — POST /api/pages/:name
|
|
863
|
+
synthos.page.remove(name) — DELETE /api/pages/:name
|
|
864
|
+
synthos.page.ask(name, question) — POST /api/pages/:name/ask`],
|
|
753
865
|
['scripts', `POST /api/scripts/:id
|
|
754
866
|
description: Execute a script with the passed in variables
|
|
755
867
|
request: { [key: string]: string }
|
|
756
868
|
response: string
|
|
757
869
|
|
|
758
|
-
synthos.
|
|
870
|
+
synthos.script.run(id, variables) — POST /api/scripts/:id`],
|
|
759
871
|
['search', `POST /api/search/web
|
|
760
872
|
description: Search the web using Brave Search (must be enabled in Settings > Connectors)
|
|
761
873
|
request: { query: string, count?: number, country?: string, freshness?: string }
|
|
@@ -776,11 +888,11 @@ description: Send a message and receive a streaming SSE response (text/event-str
|
|
|
776
888
|
request: { message: string, attachments?: [{ fileName: string, mimeType: string, content: string }] }
|
|
777
889
|
response: SSE stream
|
|
778
890
|
|
|
779
|
-
synthos.
|
|
780
|
-
synthos.
|
|
781
|
-
synthos.
|
|
782
|
-
synthos.
|
|
783
|
-
synthos.
|
|
891
|
+
synthos.agent.list(opts?) — GET /api/agents (returns configured agents; opts: { enabled?, provider? }; returns [{ id, name, description, url, enabled, provider, capabilities }])
|
|
892
|
+
synthos.agent.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 }])
|
|
893
|
+
synthos.agent.sendStream(agentId, message, onEvent, attachments?) — POST /api/agents/:id/stream (SSE streaming; onEvent receives { kind, data }; returns { close() } handle; attachments: [{ fileName, mimeType, content }])
|
|
894
|
+
synthos.agent.isEnabled(agentId) — checks if an agent is enabled (returns Promise<boolean>)
|
|
895
|
+
synthos.agent.getCapabilities(agentId) — returns agent capabilities object (streaming, skills, etc.)`],
|
|
784
896
|
['connectors', `GET /api/connectors
|
|
785
897
|
description: List available connectors (REST API proxies). Supports ?category=X and ?id=X filters.
|
|
786
898
|
response: [{ id: string, name: string, category: string, configured: boolean }]
|
|
@@ -794,8 +906,8 @@ description: Proxy a request through a configured connector. The connector attac
|
|
|
794
906
|
request: { connector: string, method: string, path: string, headers?: object, body?: any, query?: object }
|
|
795
907
|
response: Upstream API response (JSON or text)
|
|
796
908
|
|
|
797
|
-
synthos.
|
|
798
|
-
synthos.
|
|
909
|
+
synthos.connector.call(connector, method, path, opts?) — POST /api/connectors (proxy call; opts: { headers?, body?, query? })
|
|
910
|
+
synthos.connector.list(opts?) — GET /api/connectors (opts: { category?, id? })`],
|
|
799
911
|
['files', `GET /api/files/:page
|
|
800
912
|
description: List files stored for a page (with sizes)
|
|
801
913
|
response: { files: [{ name: string, size: number }] }
|
|
@@ -910,9 +1022,10 @@ request: { prompt: string, shape: 'square' | 'portrait' | 'landscape', style: 'v
|
|
|
910
1022
|
response: { url: string }
|
|
911
1023
|
|
|
912
1024
|
POST /api/generate/completion
|
|
913
|
-
description: Generates a
|
|
914
|
-
request: { prompt: string, temperature?: number }
|
|
915
|
-
response: { answer: string
|
|
1025
|
+
description: Generates a completion based on a prompt. When \`schema\` is provided, the model is constrained to emit JSON conforming to it and the parsed object is returned directly.
|
|
1026
|
+
request: { prompt: string, temperature?: number, schema?: object (JSON schema) }
|
|
1027
|
+
response (no schema): { answer: string }
|
|
1028
|
+
response (with schema): the parsed JSON object matching \`schema\`. Top-level must be \`type: 'object'\`; every nested object MUST set \`additionalProperties: false\`.
|
|
916
1029
|
|
|
917
1030
|
GET /api/pages
|
|
918
1031
|
description: Retrieve a list of all pages with metadata
|
|
@@ -978,21 +1091,21 @@ PAGE HELPERS (available globally as window.synthos):
|
|
|
978
1091
|
synthos.data.get(table, id) — GET /api/data/:page/:table/:id (auto-scoped to current page)
|
|
979
1092
|
synthos.data.save(table, row) — POST /api/data/:page/:table (auto-scoped to current page)
|
|
980
1093
|
synthos.data.remove(table, id) — DELETE /api/data/:page/:table/:id (auto-scoped to current page)
|
|
981
|
-
synthos.generate.image({ prompt, shape, style })
|
|
982
|
-
synthos.generate.completion({ prompt, temperature? })
|
|
983
|
-
synthos.
|
|
984
|
-
synthos.
|
|
985
|
-
synthos.
|
|
986
|
-
synthos.
|
|
987
|
-
synthos.
|
|
988
|
-
synthos.
|
|
1094
|
+
synthos.generate.image({ prompt, shape, style }) — POST /api/generate/image
|
|
1095
|
+
synthos.generate.completion({ prompt, temperature?, schema? }) — POST /api/generate/completion (schema: optional JSON schema for structured output; returns parsed object)
|
|
1096
|
+
synthos.script.run(id, variables) — POST /api/scripts/:id
|
|
1097
|
+
synthos.page.list() — GET /api/pages
|
|
1098
|
+
synthos.page.get(name) — GET /api/pages/:name
|
|
1099
|
+
synthos.page.update(name, metadata) — POST /api/pages/:name
|
|
1100
|
+
synthos.page.remove(name) — DELETE /api/pages/:name
|
|
1101
|
+
synthos.page.ask(name, question) — POST /api/pages/:name/ask
|
|
989
1102
|
synthos.search.web(query, opts?) — POST /api/search/web (opts: { count?, country?, freshness? })
|
|
990
|
-
synthos.
|
|
991
|
-
synthos.
|
|
992
|
-
synthos.
|
|
993
|
-
synthos.
|
|
994
|
-
synthos.
|
|
995
|
-
synthos.
|
|
996
|
-
synthos.
|
|
1103
|
+
synthos.connector.call(connector, method, path, opts?) — POST /api/connectors (proxy call; opts: { headers?, body?, query? })
|
|
1104
|
+
synthos.connector.list(opts?) — GET /api/connectors (opts: { category?, id? })
|
|
1105
|
+
synthos.agent.list(opts?) — GET /api/agents (returns configured agents; opts: { enabled?, provider? }; returns [{ id, name, description, url, enabled, provider, capabilities }])
|
|
1106
|
+
synthos.agent.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 }])
|
|
1107
|
+
synthos.agent.sendStream(agentId, message, onEvent, attachments?) — POST /api/agents/:id/stream (SSE streaming; onEvent receives { kind, data }; returns { close() } handle; attachments: [{ fileName, mimeType, content }])
|
|
1108
|
+
synthos.agent.isEnabled(agentId) — checks if an agent is enabled (returns Promise<boolean>)
|
|
1109
|
+
synthos.agent.getCapabilities(agentId) — returns agent capabilities object (streaming, skills, etc.)
|
|
997
1110
|
All methods return Promises. Prefer these helpers over raw fetch().`;
|
|
998
1111
|
//# sourceMappingURL=transformPage.js.map
|