@compilr-dev/sdk 0.29.8 → 0.31.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/dist/canvas/index.d.ts +2 -0
- package/dist/canvas/index.js +7 -0
- package/dist/canvas/scene-ops.d.ts +122 -0
- package/dist/canvas/scene-ops.js +420 -0
- package/dist/canvas/scene.d.ts +277 -0
- package/dist/canvas/scene.js +963 -0
- package/dist/canvas/types.d.ts +9 -1
- package/dist/canvas/types.js +1 -0
- package/dist/capabilities/packs.d.ts +1 -1
- package/dist/capabilities/packs.js +36 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -1
- package/dist/platform/context.d.ts +18 -1
- package/dist/platform/index.d.ts +4 -2
- package/dist/platform/index.js +4 -1
- package/dist/platform/scene-writer.d.ts +95 -0
- package/dist/platform/scene-writer.js +184 -0
- package/dist/platform/services.d.ts +20 -2
- package/dist/platform/tools/canvas-tools.js +101 -5
- package/dist/platform/tools/index.d.ts +1 -0
- package/dist/platform/tools/index.js +18 -2
- package/dist/platform/tools/scene-tools.d.ts +65 -0
- package/dist/platform/tools/scene-tools.js +511 -0
- package/dist/team/tool-config.js +21 -0
- package/package.json +1 -1
|
@@ -23,7 +23,41 @@ import { validateControlManifest } from '../../canvas/validate.js';
|
|
|
23
23
|
import { canvasMacro, infographicMacro, carouselMacro, boardMacro, canvasStylesMacro, } from '../../skills/canvas-macros.js';
|
|
24
24
|
import { canvasExemplarInfographicMacro, canvasExemplarAppScreenMacro, } from '../../skills/canvas-exemplars.js';
|
|
25
25
|
import { canvasIconsMacro } from '../../skills/canvas-icons.js';
|
|
26
|
-
|
|
26
|
+
import { HTML_CANVAS_TYPES } from '../../canvas/types.js';
|
|
27
|
+
import { validateScene, normalizeScene, describeScene } from '../../canvas/scene.js';
|
|
28
|
+
import { sceneOutline, screenshotScene } from './scene-tools.js';
|
|
29
|
+
/**
|
|
30
|
+
* The HTML canvas types. `scene` is deliberately NOT here: a scene's content is JSON written
|
|
31
|
+
* through the scene writer, and canvas_write/canvas_edit on it is how a scene gets corrupted.
|
|
32
|
+
*/
|
|
33
|
+
const CANVAS_TYPES = [...HTML_CANVAS_TYPES];
|
|
34
|
+
/** The refusal every HTML-content tool gives for a scene — names the tools that do work. */
|
|
35
|
+
function sceneRefusal(c) {
|
|
36
|
+
return (`Canvas ${String(c.id)} is a 3D scene; its content is JSON, not HTML. Edit it with ` +
|
|
37
|
+
'scene_update_object / scene_add_object / scene_remove_object (read it first with scene_get).');
|
|
38
|
+
}
|
|
39
|
+
/** Scenes carry no images (P1); the asset tools say so and point at the scene tools. */
|
|
40
|
+
function sceneAssetRefusal(c) {
|
|
41
|
+
return (`Canvas ${String(c.id)} is a 3D scene; scenes have no images. Shape it with ` +
|
|
42
|
+
'scene_add_object / scene_update_object instead.');
|
|
43
|
+
}
|
|
44
|
+
/** Static checks for scene JSON (canvas_validate on a scene) — the scene validator's errors. */
|
|
45
|
+
function sceneContentIssues(content) {
|
|
46
|
+
let raw;
|
|
47
|
+
try {
|
|
48
|
+
raw = JSON.parse(content);
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
return [
|
|
52
|
+
{
|
|
53
|
+
level: 'error',
|
|
54
|
+
message: `Scene content is not valid JSON (${e instanceof Error ? e.message : String(e)}).`,
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
const v = validateScene(raw);
|
|
59
|
+
return v.ok ? [] : v.errors.map((message) => ({ level: 'error', message }));
|
|
60
|
+
}
|
|
27
61
|
/** Render content as numbered lines (1-based), optionally a slice. */
|
|
28
62
|
function numberedLines(content, startLine, maxLines) {
|
|
29
63
|
const lines = content.split('\n');
|
|
@@ -142,6 +176,9 @@ function offCanvasNodes(src, bounds) {
|
|
|
142
176
|
}
|
|
143
177
|
/** Run deterministic static checks on canvas content. No rendering. */
|
|
144
178
|
export function runCanvasChecks(html, type, manifest) {
|
|
179
|
+
// A scene is JSON, not HTML: none of the HTML checks below apply.
|
|
180
|
+
if (type === 'scene')
|
|
181
|
+
return sceneContentIssues(html);
|
|
145
182
|
const issues = [];
|
|
146
183
|
const src = html.trim();
|
|
147
184
|
if (!src) {
|
|
@@ -285,6 +322,10 @@ export function createCanvasTools(config, imageConfig) {
|
|
|
285
322
|
},
|
|
286
323
|
execute: async (input) => {
|
|
287
324
|
try {
|
|
325
|
+
if (input.type === 'scene') {
|
|
326
|
+
return createErrorResult('canvas_write authors HTML canvases. 3D scenes are JSON — create one with scene_create, ' +
|
|
327
|
+
'then build it with scene_add_object.');
|
|
328
|
+
}
|
|
288
329
|
// Reject content that can't render in the sandbox (Mermaid/Markdown/
|
|
289
330
|
// external-CDN) with a corrective message → the agent retries with
|
|
290
331
|
// inline HTML/SVG instead of persisting a blank canvas.
|
|
@@ -298,6 +339,9 @@ export function createCanvasTools(config, imageConfig) {
|
|
|
298
339
|
}
|
|
299
340
|
}
|
|
300
341
|
if (input.canvas_id !== undefined) {
|
|
342
|
+
const existing = await canvases.getById(input.canvas_id);
|
|
343
|
+
if (existing?.type === 'scene')
|
|
344
|
+
return createErrorResult(sceneRefusal(existing));
|
|
301
345
|
const updated = await canvases.update(input.canvas_id, {
|
|
302
346
|
title: input.title,
|
|
303
347
|
content: input.html,
|
|
@@ -408,6 +452,20 @@ export function createCanvasTools(config, imageConfig) {
|
|
|
408
452
|
const c = await canvases.getById(input.canvas_id);
|
|
409
453
|
if (!c)
|
|
410
454
|
return createErrorResult(`Canvas ${String(input.canvas_id)} not found.`);
|
|
455
|
+
if (c.type === 'scene') {
|
|
456
|
+
// A scene is JSON — an outline of its objects, and the tool that reads it whole.
|
|
457
|
+
let body;
|
|
458
|
+
try {
|
|
459
|
+
const v = validateScene(JSON.parse(c.content));
|
|
460
|
+
body = v.ok
|
|
461
|
+
? `${describeScene(v.scene)}\n\n--- objects (id · type · name) ---\n${sceneOutline(normalizeScene(v.scene))}`
|
|
462
|
+
: `The stored scene is invalid (${v.errors[0] ?? 'unknown error'}).`;
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
body = 'The stored scene is not valid JSON.';
|
|
466
|
+
}
|
|
467
|
+
return createSuccessResult(`Canvas "${c.title}" (scene)\nID: ${String(c.id)} | ${body}\n\nRead the full scene with scene_get.`);
|
|
468
|
+
}
|
|
411
469
|
const totalLines = c.content.split('\n').length;
|
|
412
470
|
const header = `Canvas "${c.title}" (${c.type})\n` +
|
|
413
471
|
`ID: ${String(c.id)} | Controls: ${String(c.controls.controls.length)} | Lines: ${String(totalLines)}\n\n`;
|
|
@@ -474,6 +532,9 @@ export function createCanvasTools(config, imageConfig) {
|
|
|
474
532
|
const c = await canvases.getById(input.canvas_id);
|
|
475
533
|
if (!c)
|
|
476
534
|
return createErrorResult(`Canvas ${String(input.canvas_id)} not found.`);
|
|
535
|
+
// str_replace on scene JSON is exactly how a scene gets corrupted.
|
|
536
|
+
if (c.type === 'scene')
|
|
537
|
+
return createErrorResult(sceneRefusal(c));
|
|
477
538
|
let next;
|
|
478
539
|
let summary;
|
|
479
540
|
if (input.operation === 'str_replace') {
|
|
@@ -559,8 +620,8 @@ export function createCanvasTools(config, imageConfig) {
|
|
|
559
620
|
},
|
|
560
621
|
type: {
|
|
561
622
|
type: 'string',
|
|
562
|
-
enum: CANVAS_TYPES,
|
|
563
|
-
description: 'Canvas type (required when passing html; defaults to infographic).',
|
|
623
|
+
enum: [...CANVAS_TYPES, 'scene'],
|
|
624
|
+
description: 'Canvas type (required when passing html; defaults to infographic). For "scene", pass the scene JSON as html.',
|
|
564
625
|
},
|
|
565
626
|
},
|
|
566
627
|
required: [],
|
|
@@ -649,13 +710,41 @@ export function createCanvasTools(config, imageConfig) {
|
|
|
649
710
|
execute: async (input) => {
|
|
650
711
|
try {
|
|
651
712
|
const renderer = ctx.canvasRenderer;
|
|
713
|
+
// A scene goes to the 3D renderer — either tool works on a scene (3d-canvas-spec §3.1).
|
|
714
|
+
const maybeScene = await canvases.getById(input.canvas_id);
|
|
715
|
+
if (maybeScene?.type === 'scene') {
|
|
716
|
+
let scene;
|
|
717
|
+
try {
|
|
718
|
+
const v = validateScene(JSON.parse(maybeScene.content));
|
|
719
|
+
if (!v.ok)
|
|
720
|
+
return createErrorResult(`${v.errors[0] ?? 'Invalid scene.'} Read it with scene_get.`);
|
|
721
|
+
scene = normalizeScene(v.scene);
|
|
722
|
+
}
|
|
723
|
+
catch {
|
|
724
|
+
return createErrorResult('The stored scene is not valid JSON. Read it with scene_get.');
|
|
725
|
+
}
|
|
726
|
+
const shot = await screenshotScene(ctx, {
|
|
727
|
+
canvasId: maybeScene.id,
|
|
728
|
+
title: maybeScene.title,
|
|
729
|
+
scene,
|
|
730
|
+
});
|
|
731
|
+
if (!shot.available)
|
|
732
|
+
return createSuccessResult(shot.text);
|
|
733
|
+
return {
|
|
734
|
+
success: true,
|
|
735
|
+
result: shot.text,
|
|
736
|
+
imageBlocks: [{ ...shot.image, filename: `scene-${String(maybeScene.id)}.png` }],
|
|
737
|
+
};
|
|
738
|
+
}
|
|
652
739
|
if (!renderer) {
|
|
653
740
|
return createSuccessResult('Canvas rendering is not available in this environment — a visual self-review can’t be produced here. ' +
|
|
654
741
|
'Rely on canvas_validate (static checks) instead.');
|
|
655
742
|
}
|
|
656
|
-
const c =
|
|
743
|
+
const c = maybeScene;
|
|
657
744
|
if (!c)
|
|
658
745
|
return createErrorResult(`Canvas ${String(input.canvas_id)} not found.`);
|
|
746
|
+
// Scenes returned above; what is left is an HTML canvas.
|
|
747
|
+
const htmlType = c.type;
|
|
659
748
|
/*
|
|
660
749
|
⚠️ Resolve assets HERE, not in the host renderer. This tool has the canvas id and
|
|
661
750
|
the repository; `renderToImage` receives only html, so pushing substitution into
|
|
@@ -666,7 +755,7 @@ export function createCanvasTools(config, imageConfig) {
|
|
|
666
755
|
const shotAssets = canvases.listAssets ? await canvases.listAssets(c.id) : [];
|
|
667
756
|
const img = await renderer.renderToImage({
|
|
668
757
|
content: resolveCanvasAssets(c.content, shotAssets),
|
|
669
|
-
type:
|
|
758
|
+
type: htmlType,
|
|
670
759
|
});
|
|
671
760
|
return {
|
|
672
761
|
success: true,
|
|
@@ -812,6 +901,8 @@ export function createCanvasTools(config, imageConfig) {
|
|
|
812
901
|
const canvas = await canvases.getById(input.canvas_id);
|
|
813
902
|
if (!canvas)
|
|
814
903
|
return createErrorResult(`Canvas ${String(input.canvas_id)} not found.`);
|
|
904
|
+
if (canvas.type === 'scene')
|
|
905
|
+
return createErrorResult(sceneAssetRefusal(canvas));
|
|
815
906
|
const filePath = pathNode.isAbsolute(input.path)
|
|
816
907
|
? input.path
|
|
817
908
|
: pathNode.resolve(resolveCwd(config.cwd), input.path);
|
|
@@ -894,6 +985,8 @@ export function createCanvasTools(config, imageConfig) {
|
|
|
894
985
|
const canvas = await canvases.getById(input.canvas_id);
|
|
895
986
|
if (!canvas)
|
|
896
987
|
return createErrorResult(`Canvas ${String(input.canvas_id)} not found.`);
|
|
988
|
+
if (canvas.type === 'scene')
|
|
989
|
+
return createErrorResult(sceneAssetRefusal(canvas));
|
|
897
990
|
const assets = await canvases.listAssets(input.canvas_id);
|
|
898
991
|
if (assets.length === 0) {
|
|
899
992
|
return createSuccessResult('No images on this canvas. Add one with canvas_asset_add.');
|
|
@@ -925,6 +1018,9 @@ export function createCanvasTools(config, imageConfig) {
|
|
|
925
1018
|
if (!canvases.deleteAsset) {
|
|
926
1019
|
return createErrorResult('This host does not support canvas assets.');
|
|
927
1020
|
}
|
|
1021
|
+
const target = await canvases.getById(input.canvas_id);
|
|
1022
|
+
if (target?.type === 'scene')
|
|
1023
|
+
return createErrorResult(sceneAssetRefusal(target));
|
|
928
1024
|
const gone = await canvases.deleteAsset(input.canvas_id, input.ref.toLowerCase());
|
|
929
1025
|
return gone
|
|
930
1026
|
? createSuccessResult(`Removed "${input.ref}".`)
|
|
@@ -32,5 +32,6 @@ export { createAnchorTools } from './anchor-tools.js';
|
|
|
32
32
|
export { createArtifactTools } from './artifact-tools.js';
|
|
33
33
|
export { createEpisodeTools } from './episode-tools.js';
|
|
34
34
|
export { createCanvasTools } from './canvas-tools.js';
|
|
35
|
+
export { createSceneTools, SCENE_TOOL_NAMES, sceneOutline } from './scene-tools.js';
|
|
35
36
|
export { createImageTools } from './image-tools.js';
|
|
36
37
|
export type { ImageToolsConfig, ImageResizer } from './image-tools.js';
|
|
@@ -17,6 +17,8 @@ import { createAnchorTools } from './anchor-tools.js';
|
|
|
17
17
|
import { createArtifactTools } from './artifact-tools.js';
|
|
18
18
|
import { createEpisodeTools } from './episode-tools.js';
|
|
19
19
|
import { createCanvasTools } from './canvas-tools.js';
|
|
20
|
+
import { createSceneTools } from './scene-tools.js';
|
|
21
|
+
import { createSceneWriter } from '../scene-writer.js';
|
|
20
22
|
import { createImageTools } from './image-tools.js';
|
|
21
23
|
/**
|
|
22
24
|
* Create all platform tools operating against the given context.
|
|
@@ -63,8 +65,21 @@ export function createPlatformTools(config, options) {
|
|
|
63
65
|
tools.push(...createEpisodeTools(config));
|
|
64
66
|
}
|
|
65
67
|
if (config.context.canvases) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
const surfaces = config.canvasSurfaces ?? {};
|
|
69
|
+
if (surfaces.html !== false) {
|
|
70
|
+
// The resizer is shared with view_image — canvas_asset_add resizes to display size.
|
|
71
|
+
tools.push(...createCanvasTools(config, imageConfig));
|
|
72
|
+
}
|
|
73
|
+
if (surfaces.scene !== false) {
|
|
74
|
+
/*
|
|
75
|
+
One writer for the scene tools — the host's own when given (Desktop's inspector must
|
|
76
|
+
share it), else one built here, ONCE, so every scene tool shares its lock and journal.
|
|
77
|
+
⚠️ Passed alongside, never by spreading a new context: hosts mutate
|
|
78
|
+
`context.currentProjectId` after the tools are built, and a copy would freeze it.
|
|
79
|
+
*/
|
|
80
|
+
const sceneWriter = config.context.sceneWriter ?? createSceneWriter(config.context.canvases);
|
|
81
|
+
tools.push(...createSceneTools(config, { writer: sceneWriter }));
|
|
82
|
+
}
|
|
68
83
|
}
|
|
69
84
|
return tools;
|
|
70
85
|
}
|
|
@@ -78,4 +93,5 @@ export { createAnchorTools } from './anchor-tools.js';
|
|
|
78
93
|
export { createArtifactTools } from './artifact-tools.js';
|
|
79
94
|
export { createEpisodeTools } from './episode-tools.js';
|
|
80
95
|
export { createCanvasTools } from './canvas-tools.js';
|
|
96
|
+
export { createSceneTools, SCENE_TOOL_NAMES, sceneOutline } from './scene-tools.js';
|
|
81
97
|
export { createImageTools } from './image-tools.js';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene Tools — agents build 3D scenes as validated JSON (3d-canvas-spec §6).
|
|
3
|
+
*
|
|
4
|
+
* 7 tools: scene_create, scene_get, scene_add_object, scene_update_object,
|
|
5
|
+
* scene_remove_object, scene_set_camera, scene_screenshot.
|
|
6
|
+
*
|
|
7
|
+
* Every edit goes through the scene writer (`platform/scene-writer.ts`) — the same instance the
|
|
8
|
+
* host's inspector uses — so user and agent edits serialise and the agent is TOLD about the
|
|
9
|
+
* user's changes: a successful result starts with "The user edited this scene since your last
|
|
10
|
+
* call: …" when there are any.
|
|
11
|
+
*
|
|
12
|
+
* Names carry the `scene_` prefix: generic names (`add_object`) collide with MCP servers, and
|
|
13
|
+
* hosts refresh views on tool-name prefixes.
|
|
14
|
+
*/
|
|
15
|
+
import type { PlatformContext, PlatformToolsConfig } from '../context.js';
|
|
16
|
+
import { type ISceneWriter } from '../scene-writer.js';
|
|
17
|
+
import { type SceneFile } from '../../canvas/scene.js';
|
|
18
|
+
export declare const SCENE_TOOL_NAMES: readonly ["scene_create", "scene_get", "scene_add_object", "scene_update_object", "scene_remove_object", "scene_set_camera", "scene_screenshot"];
|
|
19
|
+
/** "sofa · box · Sofa" rows, indented under their group parent. */
|
|
20
|
+
export declare function sceneOutline(scene: SceneFile): string;
|
|
21
|
+
/**
|
|
22
|
+
* Render a scene through the host's `renderSceneToImage` — shared by scene_screenshot and
|
|
23
|
+
* canvas_screenshot (either tool works on a scene).
|
|
24
|
+
*/
|
|
25
|
+
export declare function screenshotScene(ctx: PlatformContext, args: {
|
|
26
|
+
canvasId: number;
|
|
27
|
+
title: string;
|
|
28
|
+
scene: SceneFile;
|
|
29
|
+
width?: number;
|
|
30
|
+
height?: number;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
available: false;
|
|
33
|
+
text: string;
|
|
34
|
+
} | {
|
|
35
|
+
available: true;
|
|
36
|
+
text: string;
|
|
37
|
+
image: {
|
|
38
|
+
data: string;
|
|
39
|
+
mediaType: string;
|
|
40
|
+
width?: number;
|
|
41
|
+
height?: number;
|
|
42
|
+
};
|
|
43
|
+
}>;
|
|
44
|
+
export declare function createSceneTools(config: PlatformToolsConfig, options?: {
|
|
45
|
+
writer?: ISceneWriter;
|
|
46
|
+
}): (import("@compilr-dev/agents").Tool<{
|
|
47
|
+
title: string;
|
|
48
|
+
scene?: unknown;
|
|
49
|
+
project_id?: number;
|
|
50
|
+
}> | import("@compilr-dev/agents").Tool<Record<string, unknown> & {
|
|
51
|
+
canvas_id: number;
|
|
52
|
+
}> | import("@compilr-dev/agents").Tool<{
|
|
53
|
+
canvas_id: number;
|
|
54
|
+
id: string;
|
|
55
|
+
patch: Record<string, unknown>;
|
|
56
|
+
}> | import("@compilr-dev/agents").Tool<{
|
|
57
|
+
canvas_id: number;
|
|
58
|
+
position?: unknown;
|
|
59
|
+
target?: unknown;
|
|
60
|
+
fit?: boolean;
|
|
61
|
+
}> | import("@compilr-dev/agents").Tool<{
|
|
62
|
+
canvas_id: number;
|
|
63
|
+
width?: number;
|
|
64
|
+
height?: number;
|
|
65
|
+
}>)[];
|