synthos 0.7.2 → 0.8.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 +215 -65
- package/default-pages/application.json +1 -0
- package/default-pages/json_tools.json +1 -1
- package/default-pages/oregon_trail.html +321 -0
- package/default-pages/oregon_trail.json +12 -0
- package/default-pages/sidebar_page.json +1 -0
- package/default-pages/solar_explorer.html +10 -18
- package/default-pages/solar_explorer.json +2 -2
- package/default-pages/two-panel_page.json +1 -0
- package/default-pages/us_map.html +192 -0
- package/default-pages/us_map.json +12 -0
- package/default-pages/us_map_1850.html +325 -0
- package/default-pages/us_map_1850.json +12 -0
- package/default-pages/western_cities_1850.html +526 -0
- package/default-pages/western_cities_1850.json +12 -0
- package/default-themes/{nebula-dawn.css → nebula-dawn.v2.css} +24 -0
- package/default-themes/{nebula-dusk.css → nebula-dusk.v2.css} +24 -0
- package/dist/agents/a2a/a2aProvider.d.ts +3 -0
- package/dist/agents/a2a/a2aProvider.d.ts.map +1 -0
- package/dist/agents/a2a/a2aProvider.js +126 -0
- package/dist/agents/a2a/a2aProvider.js.map +1 -0
- package/dist/agents/discovery.d.ts +30 -0
- package/dist/agents/discovery.d.ts.map +1 -0
- package/dist/agents/discovery.js +52 -0
- package/dist/agents/discovery.js.map +1 -0
- package/dist/agents/index.d.ts +7 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +19 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/openclaw/gatewayManager.d.ts +113 -0
- package/dist/agents/openclaw/gatewayManager.d.ts.map +1 -0
- package/dist/agents/openclaw/gatewayManager.js +470 -0
- package/dist/agents/openclaw/gatewayManager.js.map +1 -0
- package/dist/agents/openclaw/openclawProvider.d.ts +3 -0
- package/dist/agents/openclaw/openclawProvider.d.ts.map +1 -0
- package/dist/agents/openclaw/openclawProvider.js +239 -0
- package/dist/agents/openclaw/openclawProvider.js.map +1 -0
- package/dist/agents/openclaw/sshTunnelManager.d.ts +23 -0
- package/dist/agents/openclaw/sshTunnelManager.d.ts.map +1 -0
- package/dist/agents/openclaw/sshTunnelManager.js +340 -0
- package/dist/agents/openclaw/sshTunnelManager.js.map +1 -0
- package/dist/agents/types.d.ts +64 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +6 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/connectors/airtable/connector.json +27 -0
- package/dist/connectors/alpha-vantage/connector.json +26 -0
- package/dist/connectors/brave-search/connector.json +26 -0
- package/dist/connectors/cloudinary/connector.json +27 -0
- package/dist/connectors/deepl/connector.json +28 -0
- package/dist/connectors/elevenlabs/connector.json +30 -0
- package/dist/connectors/giphy/connector.json +27 -0
- package/dist/connectors/github/connector.json +29 -0
- package/dist/connectors/huggingface/connector.json +27 -0
- package/dist/connectors/imgur/connector.json +29 -0
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/instagram/connector.json +43 -0
- package/dist/connectors/jira/connector.json +28 -0
- package/dist/connectors/mapbox/connector.json +26 -0
- package/dist/connectors/nasa/connector.json +27 -0
- package/dist/connectors/newsapi/connector.json +27 -0
- package/dist/connectors/notion/connector.json +28 -0
- package/dist/connectors/open-exchange-rates/connector.json +27 -0
- package/dist/connectors/openweathermap/connector.json +26 -0
- package/dist/connectors/pexels/connector.json +27 -0
- package/dist/connectors/registry.d.ts.map +1 -1
- package/dist/connectors/registry.js +42 -96
- package/dist/connectors/registry.js.map +1 -1
- package/dist/connectors/resend/connector.json +29 -0
- package/dist/connectors/rss2json/connector.json +27 -0
- package/dist/connectors/sendgrid/connector.json +27 -0
- package/dist/connectors/spoonacular/connector.json +28 -0
- package/dist/connectors/stability-ai/connector.json +27 -0
- package/dist/connectors/twilio/connector.json +28 -0
- package/dist/connectors/types.d.ts +23 -0
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/connectors/unsplash/connector.json +27 -0
- package/dist/connectors/wolfram-alpha/connector.json +26 -0
- package/dist/connectors/youtube-data/connector.json +30 -0
- package/dist/files.d.ts +1 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +16 -1
- package/dist/files.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +28 -0
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts +3 -2
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +122 -138
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +22 -0
- package/dist/models/anthropic.d.ts.map +1 -0
- package/dist/models/anthropic.js +76 -0
- package/dist/models/anthropic.js.map +1 -0
- package/dist/models/chainOfThought.d.ts +12 -0
- package/dist/models/chainOfThought.d.ts.map +1 -0
- package/dist/models/chainOfThought.js +45 -0
- package/dist/models/chainOfThought.js.map +1 -0
- package/dist/models/fireworksai.d.ts +30 -0
- package/dist/models/fireworksai.d.ts.map +1 -0
- package/dist/models/fireworksai.js +133 -0
- package/dist/models/fireworksai.js.map +1 -0
- package/dist/models/index.d.ts +7 -1
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +19 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/logCompletePrompt.d.ts +3 -0
- package/dist/models/logCompletePrompt.d.ts.map +1 -0
- package/dist/models/logCompletePrompt.js +23 -0
- package/dist/models/logCompletePrompt.js.map +1 -0
- package/dist/models/openai.d.ts +24 -0
- package/dist/models/openai.d.ts.map +1 -0
- package/dist/models/openai.js +80 -0
- package/dist/models/openai.js.map +1 -0
- 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 +34 -2
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js +16 -0
- package/dist/models/types.js.map +1 -1
- package/dist/models/utils.d.ts +6 -0
- package/dist/models/utils.d.ts.map +1 -0
- package/dist/models/utils.js +21 -0
- package/dist/models/utils.js.map +1 -0
- package/dist/scripts.d.ts +2 -1
- package/dist/scripts.d.ts.map +1 -1
- package/dist/scripts.js +4 -3
- package/dist/scripts.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts +1 -1
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +9 -6
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/generateImage.d.ts +1 -1
- package/dist/service/generateImage.d.ts.map +1 -1
- package/dist/service/generateImage.js +3 -3
- package/dist/service/generateImage.js.map +1 -1
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +3 -0
- package/dist/service/server.js.map +1 -1
- package/dist/service/transformPage.d.ts +4 -2
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +74 -6
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useAgentRoutes.d.ts +4 -0
- package/dist/service/useAgentRoutes.d.ts.map +1 -0
- package/dist/service/useAgentRoutes.js +389 -0
- package/dist/service/useAgentRoutes.js.map +1 -0
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +157 -16
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.d.ts.map +1 -1
- package/dist/service/useConnectorRoutes.js +14 -3
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useGatewayRoutes.d.ts +4 -0
- package/dist/service/useGatewayRoutes.d.ts.map +1 -0
- package/dist/service/useGatewayRoutes.js +168 -0
- package/dist/service/useGatewayRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +16 -5
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/settings.d.ts +2 -1
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +4 -8
- package/dist/settings.js.map +1 -1
- package/dist/themes.d.ts +14 -0
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +86 -13
- package/dist/themes.js.map +1 -1
- package/package.json +8 -5
- package/page-scripts/helpers-v2.js +101 -0
- package/page-scripts/page-v2.js +47 -6
- package/required-pages/builder.html +1 -27
- package/required-pages/pages.html +745 -22
- package/required-pages/settings.html +819 -21
- package/required-pages/synthos_apis.html +56 -1
- package/src/agents/a2a/a2aProvider.ts +110 -0
- package/src/agents/discovery.ts +74 -0
- package/src/agents/index.ts +6 -0
- package/src/agents/openclaw/gatewayManager.ts +559 -0
- package/src/agents/openclaw/openclawProvider.ts +261 -0
- package/src/agents/openclaw/sshTunnelManager.ts +385 -0
- package/src/agents/types.ts +82 -0
- package/src/connectors/airtable/connector.json +27 -0
- package/src/connectors/alpha-vantage/connector.json +26 -0
- package/src/connectors/brave-search/connector.json +26 -0
- package/src/connectors/cloudinary/connector.json +27 -0
- package/src/connectors/deepl/connector.json +28 -0
- package/src/connectors/elevenlabs/connector.json +30 -0
- package/src/connectors/giphy/connector.json +27 -0
- package/src/connectors/github/connector.json +29 -0
- package/src/connectors/huggingface/connector.json +27 -0
- package/src/connectors/imgur/connector.json +29 -0
- package/src/connectors/index.ts +2 -0
- package/src/connectors/instagram/connector.json +43 -0
- package/src/connectors/jira/connector.json +28 -0
- package/src/connectors/mapbox/connector.json +26 -0
- package/src/connectors/nasa/connector.json +27 -0
- package/src/connectors/newsapi/connector.json +27 -0
- package/src/connectors/notion/connector.json +28 -0
- package/src/connectors/open-exchange-rates/connector.json +27 -0
- package/src/connectors/openweathermap/connector.json +26 -0
- package/src/connectors/pexels/connector.json +27 -0
- package/src/connectors/registry.ts +21 -97
- package/src/connectors/resend/connector.json +29 -0
- package/src/connectors/rss2json/connector.json +27 -0
- package/src/connectors/sendgrid/connector.json +27 -0
- package/src/connectors/spoonacular/connector.json +28 -0
- package/src/connectors/stability-ai/connector.json +27 -0
- package/src/connectors/twilio/connector.json +28 -0
- package/src/connectors/types.ts +25 -0
- package/src/connectors/unsplash/connector.json +27 -0
- package/src/connectors/wolfram-alpha/connector.json +26 -0
- package/src/connectors/youtube-data/connector.json +30 -0
- package/src/files.ts +14 -0
- package/src/init.ts +27 -0
- package/src/migrations.ts +121 -138
- package/src/models/anthropic.ts +89 -0
- package/src/models/chainOfThought.ts +56 -0
- package/src/models/fireworksai.ts +136 -0
- package/src/models/index.ts +7 -1
- package/src/models/logCompletePrompt.ts +25 -0
- package/src/models/openai.ts +90 -0
- package/src/models/providers.ts +12 -3
- package/src/models/types.ts +67 -2
- package/src/models/utils.ts +16 -0
- package/src/scripts.ts +2 -2
- package/src/service/createCompletePrompt.ts +3 -1
- package/src/service/generateImage.ts +2 -2
- package/src/service/server.ts +4 -0
- package/src/service/transformPage.ts +81 -8
- package/src/service/useAgentRoutes.ts +423 -0
- package/src/service/useApiRoutes.ts +173 -18
- package/src/service/useConnectorRoutes.ts +14 -3
- package/src/service/usePageRoutes.ts +20 -6
- package/src/settings.ts +6 -10
- package/src/themes.ts +84 -12
- package/tests/anthropic.spec.ts +84 -0
- package/tests/chainOfThought.spec.ts +108 -0
- package/tests/ensureScripts.spec.ts +82 -0
- package/tests/files.spec.ts +233 -0
- package/tests/fireworksai.spec.ts +92 -0
- package/tests/logCompletePrompt.spec.ts +74 -0
- package/tests/migrations.spec.ts +79 -1
- package/tests/openai.spec.ts +71 -0
- package/tests/pages.spec.ts +226 -1
- package/tests/providers.spec.ts +144 -0
- package/tests/scripts.spec.ts +209 -0
- package/tests/transformPage.spec.ts +517 -0
- package/tests/types.spec.ts +23 -0
- package/default-pages/app_builder.json +0 -1
- package/default-pages/sidebar_builder.json +0 -1
- package/default-pages/two-panel_builder.json +0 -1
- package/images/home.png +0 -0
- package/images/page-management.png +0 -0
- package/images/settings.png +0 -0
- package/images/synthos-square.png +0 -0
- /package/default-pages/{app_builder.html → application.html} +0 -0
- /package/default-pages/{sidebar_builder.html → sidebar_page.html} +0 -0
- /package/default-pages/{two-panel_builder.html → two-panel_page.html} +0 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import assert from 'assert';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import path from 'path';
|
|
2
5
|
import {
|
|
3
6
|
assignNodeIds,
|
|
4
7
|
stripNodeIds,
|
|
@@ -6,8 +9,11 @@ import {
|
|
|
6
9
|
parseChangeList,
|
|
7
10
|
injectError,
|
|
8
11
|
deduplicateInlineScripts,
|
|
12
|
+
transformPage,
|
|
9
13
|
ChangeList,
|
|
14
|
+
TransformPageArgs,
|
|
10
15
|
} from '../src/service/transformPage';
|
|
16
|
+
import { AgentCompletion, PromptCompletionArgs } from '../src/models/types';
|
|
11
17
|
|
|
12
18
|
// ---------------------------------------------------------------------------
|
|
13
19
|
// assignNodeIds
|
|
@@ -229,6 +235,42 @@ describe('applyChangeList', () => {
|
|
|
229
235
|
assert.ok(!result.includes('Old child'));
|
|
230
236
|
});
|
|
231
237
|
|
|
238
|
+
it('warns but does not throw on missing node for replace', () => {
|
|
239
|
+
const changes: ChangeList = [
|
|
240
|
+
{ op: 'replace', nodeId: '999', html: '<span>Ghost</span>' },
|
|
241
|
+
];
|
|
242
|
+
const result = applyChangeList(annotated, changes);
|
|
243
|
+
assert.ok(!result.includes('Ghost'));
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('skips replace on a data-locked element', () => {
|
|
247
|
+
const lockedHtml = '<html><head></head><body>' +
|
|
248
|
+
'<div data-node-id="10"><p data-node-id="11" data-locked>Locked</p></div>' +
|
|
249
|
+
'</body></html>';
|
|
250
|
+
const changes: ChangeList = [
|
|
251
|
+
{ op: 'replace', nodeId: '11', html: '<span>Replaced</span>' },
|
|
252
|
+
];
|
|
253
|
+
const result = applyChangeList(lockedHtml, changes);
|
|
254
|
+
assert.ok(result.includes('Locked'));
|
|
255
|
+
assert.ok(!result.includes('Replaced'));
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('warns but does not throw on missing node for delete', () => {
|
|
259
|
+
const changes: ChangeList = [
|
|
260
|
+
{ op: 'delete', nodeId: '999' },
|
|
261
|
+
];
|
|
262
|
+
const result = applyChangeList(annotated, changes);
|
|
263
|
+
// Should not throw, original content preserved
|
|
264
|
+
assert.ok(result.includes('Old text'));
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('throws on unknown insert position', () => {
|
|
268
|
+
const changes = [
|
|
269
|
+
{ op: 'insert', parentId: '10', position: 'sideways', html: '<em>Oops</em>' },
|
|
270
|
+
] as unknown as ChangeList;
|
|
271
|
+
assert.throws(() => applyChangeList(annotated, changes), /unknown position/);
|
|
272
|
+
});
|
|
273
|
+
|
|
232
274
|
it('throws on unknown op', () => {
|
|
233
275
|
const changes = [{ op: 'explode', nodeId: '10' }] as unknown as ChangeList;
|
|
234
276
|
assert.throws(() => applyChangeList(annotated, changes), /Unknown change op/);
|
|
@@ -412,3 +454,478 @@ describe('deduplicateInlineScripts — ID-based dedup', () => {
|
|
|
412
454
|
}
|
|
413
455
|
});
|
|
414
456
|
});
|
|
457
|
+
|
|
458
|
+
// ---------------------------------------------------------------------------
|
|
459
|
+
// transformPage (integration with stub completePrompt)
|
|
460
|
+
// ---------------------------------------------------------------------------
|
|
461
|
+
|
|
462
|
+
describe('transformPage', () => {
|
|
463
|
+
let tmpDir: string;
|
|
464
|
+
|
|
465
|
+
// Minimal page with a viewer-panel and thoughts div
|
|
466
|
+
const testPage = `<html><head></head><body>
|
|
467
|
+
<div class="chat-panel" data-locked>
|
|
468
|
+
<div id="chatMessages"><div class="chat-message"><p><strong>SynthOS:</strong> Welcome!</p></div></div>
|
|
469
|
+
</div>
|
|
470
|
+
<div class="viewer-panel"><p id="content">Hello world</p></div>
|
|
471
|
+
<div id="thoughts" style="display: none;"></div>
|
|
472
|
+
</body></html>`;
|
|
473
|
+
|
|
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
|
+
/** Extract the data-node-id for an element identified by a CSS-style attribute (e.g. id="content") from annotated HTML. */
|
|
483
|
+
function findNodeId(annotatedHtml: string, idAttr: string): string {
|
|
484
|
+
// Match a tag that contains both data-node-id="X" and the target id, in either order
|
|
485
|
+
const pattern1 = new RegExp(`data-node-id="(\\d+)"[^>]*${idAttr}`);
|
|
486
|
+
const pattern2 = new RegExp(`${idAttr}[^>]*data-node-id="(\\d+)"`);
|
|
487
|
+
const m = annotatedHtml.match(pattern1) || annotatedHtml.match(pattern2);
|
|
488
|
+
return m ? m[1] : '99999';
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function makeArgs(stub: (args: PromptCompletionArgs) => Promise<AgentCompletion<string>>, pageState?: string): TransformPageArgs {
|
|
492
|
+
return {
|
|
493
|
+
completePrompt: stub,
|
|
494
|
+
pagesFolder: tmpDir,
|
|
495
|
+
pageState: pageState ?? testPage,
|
|
496
|
+
message: 'Change the content',
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
it('happy path — applies valid change list and returns transformed HTML', async () => {
|
|
501
|
+
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
502
|
+
const sys = args.system?.content ?? '';
|
|
503
|
+
const nodeId = findNodeId(sys, 'id="content"');
|
|
504
|
+
return {
|
|
505
|
+
completed: true,
|
|
506
|
+
value: JSON.stringify([
|
|
507
|
+
{ op: 'update', nodeId, html: 'Updated content' },
|
|
508
|
+
]),
|
|
509
|
+
};
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
const result = await transformPage(makeArgs(stub));
|
|
513
|
+
assert.strictEqual(result.completed, true);
|
|
514
|
+
assert.ok(result.value);
|
|
515
|
+
assert.ok(result.value.html.includes('Updated content'));
|
|
516
|
+
assert.strictEqual(result.value.changeCount, 1);
|
|
517
|
+
// Should not contain data-node-id attributes
|
|
518
|
+
assert.ok(!result.value.html.includes('data-node-id'));
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('returns error when completePrompt fails', async () => {
|
|
522
|
+
const stub = async (): Promise<AgentCompletion<string>> => ({
|
|
523
|
+
completed: false,
|
|
524
|
+
error: new Error('API quota exceeded'),
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const result = await transformPage(makeArgs(stub));
|
|
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)
|
|
540
|
+
assert.strictEqual(result.completed, true);
|
|
541
|
+
assert.ok(result.value);
|
|
542
|
+
assert.strictEqual(result.value.changeCount, 0);
|
|
543
|
+
assert.ok(result.value.html.includes('id="error"'));
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('handles failed ops and triggers repair pass', async () => {
|
|
547
|
+
let callCount = 0;
|
|
548
|
+
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
549
|
+
callCount++;
|
|
550
|
+
const sys = args.system?.content ?? '';
|
|
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
|
+
};
|
|
600
|
+
|
|
601
|
+
const result = await transformPage(makeArgs(stub));
|
|
602
|
+
assert.strictEqual(result.completed, true);
|
|
603
|
+
assert.ok(result.value);
|
|
604
|
+
assert.ok(result.value.html.includes('Partial update'));
|
|
605
|
+
assert.strictEqual(result.value.changeCount, 1);
|
|
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'));
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
it('includes agent capabilities and skills in system prompt', async () => {
|
|
710
|
+
let capturedSystem: string | undefined;
|
|
711
|
+
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
712
|
+
capturedSystem = args.system?.content;
|
|
713
|
+
return { completed: true, value: '[]' };
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
await transformPage({
|
|
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: [] },
|
|
728
|
+
],
|
|
729
|
+
}],
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
assert.ok(capturedSystem);
|
|
733
|
+
assert.ok(capturedSystem.includes('Supports streaming: yes'));
|
|
734
|
+
assert.ok(capturedSystem.includes('summarize'));
|
|
735
|
+
assert.ok(capturedSystem.includes('Summarizes text'));
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
it('exercises replace, delete, insert, and style-element ops through pipeline', async () => {
|
|
739
|
+
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
740
|
+
const sys = args.system?.content ?? '';
|
|
741
|
+
const contentNodeId = findNodeId(sys, 'id="content"');
|
|
742
|
+
const viewerNodeId = findNodeId(sys, 'class="viewer-panel"');
|
|
743
|
+
return {
|
|
744
|
+
completed: true,
|
|
745
|
+
value: JSON.stringify([
|
|
746
|
+
{ op: 'replace', nodeId: contentNodeId, html: '<p id="content">Replaced</p>' },
|
|
747
|
+
{ op: 'insert', parentId: viewerNodeId, position: 'append', html: '<span>Appended</span>' },
|
|
748
|
+
{ op: 'style-element', nodeId: viewerNodeId, style: 'background: blue' },
|
|
749
|
+
]),
|
|
750
|
+
};
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const result = await transformPage(makeArgs(stub));
|
|
754
|
+
assert.strictEqual(result.completed, true);
|
|
755
|
+
assert.ok(result.value);
|
|
756
|
+
assert.ok(result.value.html.includes('Replaced'));
|
|
757
|
+
assert.ok(result.value.html.includes('Appended'));
|
|
758
|
+
assert.ok(result.value.html.includes('background: blue'));
|
|
759
|
+
assert.strictEqual(result.value.changeCount, 3);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it('reports failed replace on locked element and triggers repair', async () => {
|
|
763
|
+
// Use a page where the content is data-locked
|
|
764
|
+
const lockedPage = `<html><head></head><body>
|
|
765
|
+
<div class="chat-panel" data-locked>
|
|
766
|
+
<div id="chatMessages"><div class="chat-message"><p><strong>SynthOS:</strong> Welcome!</p></div></div>
|
|
767
|
+
</div>
|
|
768
|
+
<div class="viewer-panel"><p id="content" data-locked>Locked content</p></div>
|
|
769
|
+
<div id="thoughts" style="display: none;"></div>
|
|
770
|
+
</body></html>`;
|
|
771
|
+
|
|
772
|
+
let callCount = 0;
|
|
773
|
+
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
774
|
+
callCount++;
|
|
775
|
+
const sys = args.system?.content ?? '';
|
|
776
|
+
if (callCount === 1) {
|
|
777
|
+
const contentNodeId = findNodeId(sys, 'id="content"');
|
|
778
|
+
return {
|
|
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
|
+
};
|
|
789
|
+
|
|
790
|
+
const result = await transformPage(makeArgs(stub, lockedPage));
|
|
791
|
+
assert.strictEqual(result.completed, true);
|
|
792
|
+
assert.strictEqual(callCount, 2);
|
|
793
|
+
// Original locked content should still be present
|
|
794
|
+
assert.ok(result.value!.html.includes('Locked content'));
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
it('handles repair pass that throws an error', async () => {
|
|
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 () => {
|
|
843
|
+
const lockedPage = `<html><head></head><body>
|
|
844
|
+
<div class="chat-panel" data-locked>
|
|
845
|
+
<div id="chatMessages"><div class="chat-message"><p><strong>SynthOS:</strong> Welcome!</p></div></div>
|
|
846
|
+
</div>
|
|
847
|
+
<div class="viewer-panel"><p id="content" data-locked>Locked</p></div>
|
|
848
|
+
<div id="thoughts" style="display: none;"></div>
|
|
849
|
+
</body></html>`;
|
|
850
|
+
|
|
851
|
+
let callCount = 0;
|
|
852
|
+
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
853
|
+
callCount++;
|
|
854
|
+
const sys = args.system?.content ?? '';
|
|
855
|
+
if (callCount === 1) {
|
|
856
|
+
const contentNodeId = findNodeId(sys, 'id="content"');
|
|
857
|
+
return {
|
|
858
|
+
completed: true,
|
|
859
|
+
value: JSON.stringify([
|
|
860
|
+
{ op: 'delete', nodeId: contentNodeId },
|
|
861
|
+
]),
|
|
862
|
+
};
|
|
863
|
+
} else {
|
|
864
|
+
return { completed: true, value: '[]' };
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
const result = await transformPage(makeArgs(stub, lockedPage));
|
|
869
|
+
assert.strictEqual(result.completed, true);
|
|
870
|
+
assert.ok(result.value!.html.includes('Locked'));
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
it('reports failed style-element on locked element through pipeline', async () => {
|
|
874
|
+
const lockedPage = `<html><head></head><body>
|
|
875
|
+
<div class="chat-panel" data-locked>
|
|
876
|
+
<div id="chatMessages"><div class="chat-message"><p><strong>SynthOS:</strong> Welcome!</p></div></div>
|
|
877
|
+
</div>
|
|
878
|
+
<div class="viewer-panel"><p id="content" data-locked>Styled</p></div>
|
|
879
|
+
<div id="thoughts" style="display: none;"></div>
|
|
880
|
+
</body></html>`;
|
|
881
|
+
|
|
882
|
+
let callCount = 0;
|
|
883
|
+
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
884
|
+
callCount++;
|
|
885
|
+
const sys = args.system?.content ?? '';
|
|
886
|
+
if (callCount === 1) {
|
|
887
|
+
const contentNodeId = findNodeId(sys, 'id="content"');
|
|
888
|
+
return {
|
|
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
|
+
};
|
|
898
|
+
|
|
899
|
+
const result = await transformPage(makeArgs(stub, lockedPage));
|
|
900
|
+
assert.strictEqual(result.completed, true);
|
|
901
|
+
// Locked element should not have the style applied
|
|
902
|
+
assert.ok(!result.value!.html.includes('color: red'));
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
it('repair pass with remaining failures keeps partial result', async () => {
|
|
906
|
+
let callCount = 0;
|
|
907
|
+
const stub = async (args: PromptCompletionArgs): Promise<AgentCompletion<string>> => {
|
|
908
|
+
callCount++;
|
|
909
|
+
if (callCount === 1) {
|
|
910
|
+
return {
|
|
911
|
+
completed: true,
|
|
912
|
+
value: JSON.stringify([
|
|
913
|
+
{ op: 'update', nodeId: '9999', html: 'Ghost' },
|
|
914
|
+
]),
|
|
915
|
+
};
|
|
916
|
+
} else {
|
|
917
|
+
// Repair also fails — targets non-existent node
|
|
918
|
+
return {
|
|
919
|
+
completed: true,
|
|
920
|
+
value: JSON.stringify([
|
|
921
|
+
{ op: 'update', nodeId: '8888', html: 'Still ghost' },
|
|
922
|
+
]),
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
const result = await transformPage(makeArgs(stub));
|
|
928
|
+
assert.strictEqual(result.completed, true);
|
|
929
|
+
assert.strictEqual(callCount, 2);
|
|
930
|
+
});
|
|
931
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
import { RequestError } from '../src/models/types';
|
|
3
|
+
|
|
4
|
+
describe('RequestError', () => {
|
|
5
|
+
it('constructor sets message, status, and name', () => {
|
|
6
|
+
const err = new RequestError('Not found', 404, 'NotFoundError');
|
|
7
|
+
assert.strictEqual(err.message, 'Not found');
|
|
8
|
+
assert.strictEqual(err.status, 404);
|
|
9
|
+
assert.strictEqual(err.name, 'NotFoundError');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('default name is "RequestError" when not provided', () => {
|
|
13
|
+
const err = new RequestError('Bad request', 400);
|
|
14
|
+
assert.strictEqual(err.name, 'RequestError');
|
|
15
|
+
assert.strictEqual(err.status, 400);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('is an instance of Error', () => {
|
|
19
|
+
const err = new RequestError('fail', 500);
|
|
20
|
+
assert.ok(err instanceof Error);
|
|
21
|
+
assert.ok(err instanceof RequestError);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "title": "App Builder", "categories": ["Builders"], "pinned": false, "showInAll": true, "pageVersion": 2, "mode": "unlocked" }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "title": "Sidebar Builder", "categories": ["Builders"], "pinned": false, "showInAll": true, "pageVersion": 2, "mode": "unlocked" }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "title": "Two-Panel Builder", "categories": ["Builders"], "pinned": false, "showInAll": true, "pageVersion": 2, "mode": "unlocked" }
|
package/images/home.png
DELETED
|
Binary file
|
|
Binary file
|
package/images/settings.png
DELETED
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|