@womp/spark-agent 4.1.2 → 4.2.1
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/.env.example +6 -0
- package/README.md +436 -406
- package/dist/agent-tools.js +7 -1
- package/dist/bounded-output.d.ts +37 -0
- package/dist/bounded-output.js +110 -0
- package/dist/capabilities/geometry.js +14 -14
- package/dist/capabilities/operations.js +38 -33
- package/dist/capabilities/scene.js +87 -66
- package/dist/color.d.ts +8 -0
- package/dist/color.js +29 -0
- package/dist/generated/scene-declarations.d.ts +1 -1
- package/dist/generated/scene-declarations.js +1 -1
- package/dist/geometry/compiler.js +6 -4
- package/dist/geometry/diagnostics.d.ts +18 -0
- package/dist/geometry/diagnostics.js +70 -0
- package/dist/geometry/schemas/backend-schema.json +3393 -3393
- package/dist/geometry/schemas/flowir-dialect.json +1023 -1023
- package/dist/geometry.js +6 -6
- package/dist/prompt.js +70 -70
- package/dist/scene-environment.d.ts +1 -1
- package/dist/scene-environment.js +8 -4
- package/dist/scene-health.d.ts +23 -0
- package/dist/scene-health.js +71 -0
- package/dist/sdk-geometry-host.js +5 -3
- package/dist/sdk-scene-health-host.js +27 -5
- package/dist/sdk-scene-host.d.ts +5 -5
- package/dist/sdk-scene-host.js +7 -2
- package/dist/server/fixtures/materials/matte-blue.json +14 -14
- package/dist/server/fixtures/materials/polished-copper.json +14 -14
- package/dist/server/sqlite-store.js +37 -37
- package/dist/womp-scene-api.d.ts +11 -10
- package/dist/womp-scene-api.js +11 -9
- package/package.json +139 -139
package/dist/geometry.js
CHANGED
|
@@ -7,12 +7,12 @@ export { migrateParameterOverrides, readParameterOverrides, } from "./geometry/p
|
|
|
7
7
|
let reference;
|
|
8
8
|
export function geometryReference() {
|
|
9
9
|
reference ??= {
|
|
10
|
-
rules: `Write one statement per line:
|
|
11
|
-
param Float width = 20.0 min=1 max=100 step=1
|
|
12
|
-
node box Primitive Type=Cube Position=Vec3(0.0,0.0,0.0) Rotation=Vec3(0.0,0.0,0.0) Size=width Roundness=0.0
|
|
13
|
-
out Geometry=box.Geometry
|
|
14
|
-
Use decimal components in Vec3 parameter/constant literals, e.g. Vec3(20.0,20.0,20.0). Output name is case-sensitive: Geometry.
|
|
15
|
-
Exactly one cast applies per port and casts do not chain: Float[] -> Vec3 is unavailable even though Float[] -> Float and Float -> Vec3 are both listed. Build the vector with CombineXYZ instead.
|
|
10
|
+
rules: `Write one statement per line:
|
|
11
|
+
param Float width = 20.0 min=1 max=100 step=1
|
|
12
|
+
node box Primitive Type=Cube Position=Vec3(0.0,0.0,0.0) Rotation=Vec3(0.0,0.0,0.0) Size=width Roundness=0.0
|
|
13
|
+
out Geometry=box.Geometry
|
|
14
|
+
Use decimal components in Vec3 parameter/constant literals, e.g. Vec3(20.0,20.0,20.0). Output name is case-sensitive: Geometry.
|
|
15
|
+
Exactly one cast applies per port and casts do not chain: Float[] -> Vec3 is unavailable even though Float[] -> Float and Float -> Vec3 are both listed. Build the vector with CombineXYZ instead.
|
|
16
16
|
${flowir.renderDslRulesBlock()}`,
|
|
17
17
|
catalog: flowir.renderSchemaBlock(flowirRegistry.getSchema()),
|
|
18
18
|
};
|
package/dist/prompt.js
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
export const SPARK_AGENT_PROMPT = `You are Spark, Womp's unified assistant.
|
|
2
|
-
Use read_skill to discover the APIs needed for the user's request. Skills provide TypeScript
|
|
3
|
-
declarations for documentation; execute_javascript runs JavaScript, with top-level await.
|
|
4
|
-
Only the listed capabilities are available. Reading a skill does not grant additional permissions.
|
|
5
|
-
Use only the supplied womp APIs and output helpers to access application services and state.
|
|
6
|
-
If a capability is unavailable, explain what is missing and ask the user to enable it when applicable.
|
|
7
|
-
Do not work around missing capabilities by inspecting page globals, DOM or framework internals,
|
|
8
|
-
or making direct network requests. Native execution is not a security sandbox; it does not expand
|
|
9
|
-
the supported API or the user's granted capabilities.
|
|
10
|
-
Give each execute_javascript call a concise present-progressive purpose, such as "Generating a car image", "Inspecting the scene", or "Importing the model". Write purpose before code.
|
|
11
|
-
Each execution has fresh local variables. Keep stable object, job, and artifact references.
|
|
12
|
-
Use print for concise, filtered observations; await emitImage or emitFile with a host-issued artifact
|
|
13
|
-
reference to show actual output. emitImage inspects by default; use { inspect: false } to display without adding image bytes to model context.
|
|
14
|
-
Completed generations are shown automatically to the user; image bytes are intentionally omitted from your context. Acknowledge completion without listing, re-emitting, or inspecting outputs unless the task requires it. Use returned artifact references for follow-up work.
|
|
15
|
-
Do not claim visual verification without inspecting the image. Missing artifact references must be recovered or reported; omitted image bytes alone are not a missing result.
|
|
16
|
-
Inspect the scene before editing and verify the committed result.
|
|
17
|
-
Preserve existing content unless the user asks to replace or delete it. Use the latest target revision
|
|
18
|
-
when a write requires one. Capabilities own transactions: an entire script is not atomic.
|
|
19
|
-
If an operation fails after earlier effects, use its receipts to inspect and retry only unresolved work.
|
|
20
|
-
Never claim an uncertain commit rolled back. Never resubmit a job just because waiting timed out.
|
|
21
|
-
Complete related work in this conversation without delegating to another agent. Report limits,
|
|
22
|
-
unsupported actions, cancellation, and incomplete work truthfully. Finish with a concise explanation
|
|
23
|
-
of the result in the user's language; keep implementation details out of that explanation.
|
|
24
|
-
|
|
25
|
-
Creation intent
|
|
26
|
-
|
|
27
|
-
Choose the workflow from the user's intended result and the conversation.
|
|
28
|
-
Describe choices by their outcome, not internal tools.
|
|
29
|
-
|
|
30
|
-
- Use editable CAD for printable, measured, mechanical, or real-world-fit
|
|
31
|
-
objects. Ask about missing dimensions only when a sensible default would
|
|
32
|
-
likely make the result wrong. CAD is the preferred printing workflow;
|
|
33
|
-
do not claim print readiness without supporting validation.
|
|
34
|
-
|
|
35
|
-
- Build directly in the scene for ordinary 3D requests, including “3D X”
|
|
36
|
-
and “make X in 3D,” and when the user wants individually editable shapes
|
|
37
|
-
or geometry that blends with existing objects. Printable, measured,
|
|
38
|
-
or mechanical intent takes precedence and routes to CAD.
|
|
39
|
-
|
|
40
|
-
- Use mesh generation when the user explicitly requests a mesh, asks to
|
|
41
|
-
turn an existing image into 3D, or chooses a sculpted result rather than
|
|
42
|
-
individually editable shapes.
|
|
43
|
-
|
|
44
|
-
- Use image generation for pictures, logos, concept art, wallpapers,
|
|
45
|
-
and other clearly visual output.
|
|
46
|
-
|
|
47
|
-
- For an ambiguous creative request without explicit 3D or printing intent,
|
|
48
|
-
generate one image preview first. Then offer one short, outcome-based
|
|
49
|
-
choice: refine the image, turn it into a sculpted 3D piece, or build it
|
|
50
|
-
from individually editable shapes. Offer adjustable CAD instead of
|
|
51
|
-
editable shapes when the subject is measured or must fit something real.
|
|
52
|
-
|
|
1
|
+
export const SPARK_AGENT_PROMPT = `You are Spark, Womp's unified assistant.
|
|
2
|
+
Use read_skill to discover the APIs needed for the user's request. Skills provide TypeScript
|
|
3
|
+
declarations for documentation; execute_javascript runs JavaScript, with top-level await.
|
|
4
|
+
Only the listed capabilities are available. Reading a skill does not grant additional permissions.
|
|
5
|
+
Use only the supplied womp APIs and output helpers to access application services and state.
|
|
6
|
+
If a capability is unavailable, explain what is missing and ask the user to enable it when applicable.
|
|
7
|
+
Do not work around missing capabilities by inspecting page globals, DOM or framework internals,
|
|
8
|
+
or making direct network requests. Native execution is not a security sandbox; it does not expand
|
|
9
|
+
the supported API or the user's granted capabilities.
|
|
10
|
+
Give each execute_javascript call a concise present-progressive purpose, such as "Generating a car image", "Inspecting the scene", or "Importing the model". Write purpose before code.
|
|
11
|
+
Each execution has fresh local variables. Keep stable object, job, and artifact references.
|
|
12
|
+
Use print for concise, filtered observations; await emitImage or emitFile with a host-issued artifact
|
|
13
|
+
reference to show actual output. emitImage inspects by default; use { inspect: false } to display without adding image bytes to model context.
|
|
14
|
+
Completed generations are shown automatically to the user; image bytes are intentionally omitted from your context. Acknowledge completion without listing, re-emitting, or inspecting outputs unless the task requires it. Use returned artifact references for follow-up work.
|
|
15
|
+
Do not claim visual verification without inspecting the image. Missing artifact references must be recovered or reported; omitted image bytes alone are not a missing result.
|
|
16
|
+
Inspect the scene before editing and verify the committed result.
|
|
17
|
+
Preserve existing content unless the user asks to replace or delete it. Use the latest target revision
|
|
18
|
+
when a write requires one. Capabilities own transactions: an entire script is not atomic.
|
|
19
|
+
If an operation fails after earlier effects, use its receipts to inspect and retry only unresolved work.
|
|
20
|
+
Never claim an uncertain commit rolled back. Never resubmit a job just because waiting timed out.
|
|
21
|
+
Complete related work in this conversation without delegating to another agent. Report limits,
|
|
22
|
+
unsupported actions, cancellation, and incomplete work truthfully. Finish with a concise explanation
|
|
23
|
+
of the result in the user's language; keep implementation details out of that explanation.
|
|
24
|
+
|
|
25
|
+
Creation intent
|
|
26
|
+
|
|
27
|
+
Choose the workflow from the user's intended result and the conversation.
|
|
28
|
+
Describe choices by their outcome, not internal tools.
|
|
29
|
+
|
|
30
|
+
- Use editable CAD for printable, measured, mechanical, or real-world-fit
|
|
31
|
+
objects. Ask about missing dimensions only when a sensible default would
|
|
32
|
+
likely make the result wrong. CAD is the preferred printing workflow;
|
|
33
|
+
do not claim print readiness without supporting validation.
|
|
34
|
+
|
|
35
|
+
- Build directly in the scene for ordinary 3D requests, including “3D X”
|
|
36
|
+
and “make X in 3D,” and when the user wants individually editable shapes
|
|
37
|
+
or geometry that blends with existing objects. Printable, measured,
|
|
38
|
+
or mechanical intent takes precedence and routes to CAD.
|
|
39
|
+
|
|
40
|
+
- Use mesh generation when the user explicitly requests a mesh, asks to
|
|
41
|
+
turn an existing image into 3D, or chooses a sculpted result rather than
|
|
42
|
+
individually editable shapes.
|
|
43
|
+
|
|
44
|
+
- Use image generation for pictures, logos, concept art, wallpapers,
|
|
45
|
+
and other clearly visual output.
|
|
46
|
+
|
|
47
|
+
- For an ambiguous creative request without explicit 3D or printing intent,
|
|
48
|
+
generate one image preview first. Then offer one short, outcome-based
|
|
49
|
+
choice: refine the image, turn it into a sculpted 3D piece, or build it
|
|
50
|
+
from individually editable shapes. Offer adjustable CAD instead of
|
|
51
|
+
editable shapes when the subject is measured or must fit something real.
|
|
52
|
+
|
|
53
53
|
- Choose construction methods by the user's requested workflow and the
|
|
54
54
|
available capabilities, not by assuming editable shapes require lower
|
|
55
55
|
fidelity. A sculpted appearance does not by itself require mesh generation.
|
|
@@ -57,22 +57,22 @@ Describe choices by their outcome, not internal tools.
|
|
|
57
57
|
and detail with that method. Offer alternatives only for a specific
|
|
58
58
|
demonstrated limitation; ask before switching or simplifying the requested
|
|
59
59
|
result. No method guarantees exact reconstruction from a single image.
|
|
60
|
-
|
|
61
|
-
- When the user chooses editable shapes or CAD after a preview, preserve
|
|
62
|
-
its palette, proportions, and defining features. When they choose a
|
|
63
|
-
sculpted result, use that preview as the image-to-mesh input rather
|
|
64
|
-
than starting again from a text prompt.
|
|
65
|
-
|
|
66
|
-
- If the user wants repeated generation variations and Flow is available,
|
|
67
|
-
offer a reusable Flow workflow.
|
|
68
|
-
|
|
69
|
-
- Ask at most one focused question at a time, only when a sensible default
|
|
70
|
-
would likely produce the wrong outcome. Do not repeat resolved questions.
|
|
71
|
-
Interpret replies using the preceding offer; if several options were
|
|
72
|
-
offered and “yes” does not identify one, clarify briefly.
|
|
73
|
-
|
|
74
|
-
- Treat a clear answer to your question as an instruction to proceed.
|
|
75
|
-
Continue from existing results rather than restarting the workflow.
|
|
76
|
-
|
|
77
|
-
Offer only workflows available in this host. Follow their authorization
|
|
60
|
+
|
|
61
|
+
- When the user chooses editable shapes or CAD after a preview, preserve
|
|
62
|
+
its palette, proportions, and defining features. When they choose a
|
|
63
|
+
sculpted result, use that preview as the image-to-mesh input rather
|
|
64
|
+
than starting again from a text prompt.
|
|
65
|
+
|
|
66
|
+
- If the user wants repeated generation variations and Flow is available,
|
|
67
|
+
offer a reusable Flow workflow.
|
|
68
|
+
|
|
69
|
+
- Ask at most one focused question at a time, only when a sensible default
|
|
70
|
+
would likely produce the wrong outcome. Do not repeat resolved questions.
|
|
71
|
+
Interpret replies using the preceding offer; if several options were
|
|
72
|
+
offered and “yes” does not identify one, clarify briefly.
|
|
73
|
+
|
|
74
|
+
- Treat a clear answer to your question as an instruction to proceed.
|
|
75
|
+
Continue from existing results rather than restarting the workflow.
|
|
76
|
+
|
|
77
|
+
Offer only workflows available in this host. Follow their authorization
|
|
78
78
|
and confirmation requirements before generation or scene changes.`;
|
|
@@ -17,7 +17,7 @@ export declare function createSceneEnvironment(scene: SceneEdit, hdrs: readonly
|
|
|
17
17
|
setHdrName: (name: string) => void;
|
|
18
18
|
setHdrMultiplier: (multiplier: number) => void;
|
|
19
19
|
setHdrOrientation: (orientation: Vec2) => void;
|
|
20
|
-
setBackgroundColor: (
|
|
20
|
+
setBackgroundColor: (rgb255: {
|
|
21
21
|
r: number;
|
|
22
22
|
g: number;
|
|
23
23
|
b: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { metadataObject } from "./scene-metadata.js";
|
|
2
|
+
import { assertColor255 } from "./color.js";
|
|
2
3
|
function finite(value, label) {
|
|
3
4
|
if (!Number.isFinite(value))
|
|
4
5
|
throw new Error(`${label} must be a finite number`);
|
|
@@ -65,10 +66,13 @@ export function createSceneEnvironment(scene, hdrs, print) {
|
|
|
65
66
|
write({ hdr_orientation: { x, y } });
|
|
66
67
|
print(`Set HDR orientation to: (${x}, ${y})`);
|
|
67
68
|
},
|
|
68
|
-
setBackgroundColor: (
|
|
69
|
-
const r = finite(
|
|
70
|
-
const g = finite(
|
|
71
|
-
const b = finite(
|
|
69
|
+
setBackgroundColor: (rgb255) => {
|
|
70
|
+
const r = finite(rgb255?.r, "Background red");
|
|
71
|
+
const g = finite(rgb255?.g, "Background green");
|
|
72
|
+
const b = finite(rgb255?.b, "Background blue");
|
|
73
|
+
// The same 0-255 domain as every other color here, so the same refusal
|
|
74
|
+
// rather than a silent near-black (./color.ts).
|
|
75
|
+
assertColor255([r, g, b], "Background color");
|
|
72
76
|
write({ backdrop_color1: { x: r / 255, y: g / 255, z: b / 255 } });
|
|
73
77
|
print(`Set background color to: (${r}, ${g}, ${b})`);
|
|
74
78
|
},
|
package/dist/scene-health.d.ts
CHANGED
|
@@ -40,6 +40,29 @@ export type SceneHealth = {
|
|
|
40
40
|
reason?: string;
|
|
41
41
|
};
|
|
42
42
|
export declare function unverifiedSceneHealth(affectedAreaIds: string[], reason: string): SceneHealth;
|
|
43
|
+
export declare const MAX_RECEIPT_THERMOMETER_OBJECTS = 8;
|
|
44
|
+
/**
|
|
45
|
+
* The same health reading, with the thermometer's per-Area table bounded for a
|
|
46
|
+
* receipt.
|
|
47
|
+
*
|
|
48
|
+
* WHY. Every scene, geometry and OpenSCAD receipt embeds the whole thermometer,
|
|
49
|
+
* one entry per Area, and a receipt is both rendered in the panel and read by
|
|
50
|
+
* the model. A 20-Area scene therefore repeats a table the model did not ask
|
|
51
|
+
* about on every edit. The totals, the limits and the overflow flags are what a
|
|
52
|
+
* decision is made on, so they are kept whole: what is bounded is the number of
|
|
53
|
+
* per-Area rows, affected and overflowing Areas first, with the omission stated
|
|
54
|
+
* in `reason` rather than left silent. Health used for a repair decision --
|
|
55
|
+
* `hasGeometryOverflow`, `hasSceneResourcePressure`, the host's `onHealth` --
|
|
56
|
+
* reads the untrimmed value, so no decision changes.
|
|
57
|
+
*/
|
|
58
|
+
export declare function receiptSceneHealth(health: SceneHealth, limit?: number): SceneHealth;
|
|
59
|
+
/**
|
|
60
|
+
* Receipt details with an embedded scene health reading bounded by
|
|
61
|
+
* `receiptSceneHealth`. Anything else, including details with no health, is
|
|
62
|
+
* returned untouched: a receipt is read back by hosts for recovery, so this
|
|
63
|
+
* changes the one payload that is unbounded by construction and nothing else.
|
|
64
|
+
*/
|
|
65
|
+
export declare function boundedReceiptDetails(details: Record<string, unknown>): Record<string, unknown>;
|
|
43
66
|
export declare function getSdkSceneAreaId(api: KakapoAPI, objectId: number): string;
|
|
44
67
|
export declare function getSdkSceneAreaIds(api: KakapoAPI): string[];
|
|
45
68
|
export declare function hasGeometryOverflow(health: SceneHealth): boolean;
|
package/dist/scene-health.js
CHANGED
|
@@ -34,6 +34,77 @@ export function unverifiedSceneHealth(affectedAreaIds, reason) {
|
|
|
34
34
|
reason,
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
export const MAX_RECEIPT_THERMOMETER_OBJECTS = 8;
|
|
38
|
+
/**
|
|
39
|
+
* The same health reading, with the thermometer's per-Area table bounded for a
|
|
40
|
+
* receipt.
|
|
41
|
+
*
|
|
42
|
+
* WHY. Every scene, geometry and OpenSCAD receipt embeds the whole thermometer,
|
|
43
|
+
* one entry per Area, and a receipt is both rendered in the panel and read by
|
|
44
|
+
* the model. A 20-Area scene therefore repeats a table the model did not ask
|
|
45
|
+
* about on every edit. The totals, the limits and the overflow flags are what a
|
|
46
|
+
* decision is made on, so they are kept whole: what is bounded is the number of
|
|
47
|
+
* per-Area rows, affected and overflowing Areas first, with the omission stated
|
|
48
|
+
* in `reason` rather than left silent. Health used for a repair decision --
|
|
49
|
+
* `hasGeometryOverflow`, `hasSceneResourcePressure`, the host's `onHealth` --
|
|
50
|
+
* reads the untrimmed value, so no decision changes.
|
|
51
|
+
*/
|
|
52
|
+
export function receiptSceneHealth(health, limit = MAX_RECEIPT_THERMOMETER_OBJECTS) {
|
|
53
|
+
const thermometer = health.thermometer;
|
|
54
|
+
if (thermometer === undefined)
|
|
55
|
+
return health;
|
|
56
|
+
const ids = Object.keys(thermometer.objects);
|
|
57
|
+
if (ids.length <= limit)
|
|
58
|
+
return health;
|
|
59
|
+
const overflowing = (id) => {
|
|
60
|
+
const object = thermometer.objects[id];
|
|
61
|
+
return (object !== undefined &&
|
|
62
|
+
(object.flag_prim_overflow > 0 ||
|
|
63
|
+
object.flag_depth_overflow > 0 ||
|
|
64
|
+
object.flag_vox_overflow > 0 ||
|
|
65
|
+
object.num_prims >= thermometer.max_num_prims ||
|
|
66
|
+
object.voxels >= thermometer.max_per_object));
|
|
67
|
+
};
|
|
68
|
+
const ordered = [
|
|
69
|
+
...health.affectedAreaIds.filter((id) => thermometer.objects[id] !== undefined),
|
|
70
|
+
...ids.filter((id) => !health.affectedAreaIds.includes(id) && overflowing(id)),
|
|
71
|
+
...ids.filter((id) => !health.affectedAreaIds.includes(id) && !overflowing(id)),
|
|
72
|
+
];
|
|
73
|
+
const kept = ordered.slice(0, limit);
|
|
74
|
+
const objects = {};
|
|
75
|
+
for (const id of kept)
|
|
76
|
+
objects[id] = thermometer.objects[id];
|
|
77
|
+
const note = `Thermometer rows bounded for this receipt: ${kept.length} of ${ids.length} Areas shown, affected and overflowing first; ${ids.length - kept.length} omitted. Totals, limits and overflow flags are complete. Use scene.health(objectIds) for a specific Area.`;
|
|
78
|
+
return {
|
|
79
|
+
...health,
|
|
80
|
+
thermometer: { ...thermometer, objects },
|
|
81
|
+
reason: health.reason === undefined ? note : `${health.reason} ${note}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Receipt details with an embedded scene health reading bounded by
|
|
86
|
+
* `receiptSceneHealth`. Anything else, including details with no health, is
|
|
87
|
+
* returned untouched: a receipt is read back by hosts for recovery, so this
|
|
88
|
+
* changes the one payload that is unbounded by construction and nothing else.
|
|
89
|
+
*/
|
|
90
|
+
export function boundedReceiptDetails(details) {
|
|
91
|
+
const health = details.sceneHealth;
|
|
92
|
+
if (typeof health !== "object" || health === null || Array.isArray(health))
|
|
93
|
+
return details;
|
|
94
|
+
const candidate = health;
|
|
95
|
+
if (Array.isArray(candidate.affectedAreaIds) === false)
|
|
96
|
+
return details;
|
|
97
|
+
const parsed = thermometerSchema.safeParse(candidate.thermometer);
|
|
98
|
+
if (parsed.success === false)
|
|
99
|
+
return details;
|
|
100
|
+
return {
|
|
101
|
+
...details,
|
|
102
|
+
sceneHealth: receiptSceneHealth({
|
|
103
|
+
...candidate,
|
|
104
|
+
thermometer: parsed.data,
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
37
108
|
export function getSdkSceneAreaId(api, objectId) {
|
|
38
109
|
let node = api.getNode(objectId);
|
|
39
110
|
let areaId = node.kind === "union" ? node.id : objectId;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { compileGeometry, migrateGraphExt, readEditableGeometry, } from "./geometry/compiler.js";
|
|
2
2
|
import { graphExtSchema } from "./geometry/graph.js";
|
|
3
|
+
import { boundedDiagnostics } from "./geometry/diagnostics.js";
|
|
3
4
|
import { migrateParameterOverrides, readParameterOverrides, } from "./geometry/parameters.js";
|
|
4
5
|
import { getSdkSceneAreaId, readStableSdkSceneHealth } from "./scene-health.js";
|
|
5
6
|
import { createGeometryRepairTracker, } from "./repair-state.js";
|
|
@@ -78,7 +79,8 @@ async function readTarget(api, objectId, target = resolveTarget(api, objectId))
|
|
|
78
79
|
}
|
|
79
80
|
catch (error) {
|
|
80
81
|
result.source = null;
|
|
81
|
-
|
|
82
|
+
// Bounded: a compiler diagnostic is unbounded text (./geometry/diagnostics.ts).
|
|
83
|
+
result.diagnostics.push(...boundedDiagnostics([errorMessage(error)]));
|
|
82
84
|
}
|
|
83
85
|
return structuredClone(result);
|
|
84
86
|
}
|
|
@@ -163,7 +165,7 @@ export function createSdkGeometryHost(options) {
|
|
|
163
165
|
instanceId: target.instanceId,
|
|
164
166
|
affectedObjectIds: [],
|
|
165
167
|
revision: target.revision,
|
|
166
|
-
diagnostics,
|
|
168
|
+
diagnostics: boundedDiagnostics(diagnostics),
|
|
167
169
|
});
|
|
168
170
|
let compiled;
|
|
169
171
|
let nextExt;
|
|
@@ -295,7 +297,7 @@ export function createSdkGeometryHost(options) {
|
|
|
295
297
|
status: "applied",
|
|
296
298
|
...applied.value,
|
|
297
299
|
revision: applied.operation.resultRevision ?? input.expectedRevision,
|
|
298
|
-
diagnostics,
|
|
300
|
+
diagnostics: boundedDiagnostics(diagnostics),
|
|
299
301
|
};
|
|
300
302
|
}
|
|
301
303
|
finally {
|
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
import { getSdkSceneAreaId, getSdkSceneAreaIds, readStableSdkSceneHealth, } from "./scene-health.js";
|
|
2
|
+
/**
|
|
3
|
+
* The Area that owns `objectId`, or undefined when the node is in no Area --
|
|
4
|
+
* the scene root 0, a root-level Group, a Light. The engine's thermometer has a
|
|
5
|
+
* row per Area only, so such an id has no row of its own and asking for one
|
|
6
|
+
* would report the whole scene as unverified.
|
|
7
|
+
*/
|
|
8
|
+
function owningAreaId(api, objectId) {
|
|
9
|
+
const areaId = getSdkSceneAreaId(api, objectId);
|
|
10
|
+
return api.getNode(Number(areaId)).kind === "union" ? areaId : undefined;
|
|
11
|
+
}
|
|
2
12
|
export function createSdkSceneHealthHost(editor, onHealth) {
|
|
3
13
|
return {
|
|
4
14
|
read: (objectIds, context) => editor.read(context, async (api) => {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
15
|
+
const owned = objectIds
|
|
16
|
+
.map((id) => ({ id, areaId: owningAreaId(api, id) }))
|
|
17
|
+
.filter((entry) => entry.areaId !== undefined);
|
|
18
|
+
// `scene.read()` publishes node 0, and ids outside any Area are legal
|
|
19
|
+
// arguments. They narrow nothing, so they are reported rather than
|
|
20
|
+
// allowed to make the whole reading unverified; with nothing left to
|
|
21
|
+
// narrow by, this is the whole-scene read `health()` already means.
|
|
22
|
+
const outside = objectIds.filter((id) => owned.some((entry) => entry.id === id) === false);
|
|
23
|
+
const areaIds = owned.length > 0 ? owned.map((entry) => entry.areaId) : getSdkSceneAreaIds(api);
|
|
8
24
|
const health = await readStableSdkSceneHealth(api, [...new Set(areaIds)], context.signal);
|
|
9
|
-
|
|
10
|
-
|
|
25
|
+
const note = outside.length === 0
|
|
26
|
+
? undefined
|
|
27
|
+
: `${outside.join(", ")} ${outside.length === 1 ? "is" : "are"} in no Area, and the engine reports health per Area, so ${owned.length > 0 ? "health is reported for the remaining ids" : "this is the whole scene's health"}.`;
|
|
28
|
+
const reported = note === undefined
|
|
29
|
+
? health
|
|
30
|
+
: { ...health, reason: health.reason === undefined ? note : `${health.reason} ${note}` };
|
|
31
|
+
onHealth?.(reported);
|
|
32
|
+
return reported;
|
|
11
33
|
}),
|
|
12
34
|
};
|
|
13
35
|
}
|
package/dist/sdk-scene-host.d.ts
CHANGED
|
@@ -115,7 +115,7 @@ export declare function createSdkSceneDraft(api: KakapoAPI, scene: SceneEdit, se
|
|
|
115
115
|
g: number;
|
|
116
116
|
b: number;
|
|
117
117
|
};
|
|
118
|
-
nodeSetColor: (id: number,
|
|
118
|
+
nodeSetColor: (id: number, rgb255: {
|
|
119
119
|
r: number;
|
|
120
120
|
g: number;
|
|
121
121
|
b: number;
|
|
@@ -206,7 +206,7 @@ export declare function createSdkSceneDraft(api: KakapoAPI, scene: SceneEdit, se
|
|
|
206
206
|
g: number;
|
|
207
207
|
b: number;
|
|
208
208
|
};
|
|
209
|
-
materialSetColor: (id: number,
|
|
209
|
+
materialSetColor: (id: number, rgb255: {
|
|
210
210
|
r: number;
|
|
211
211
|
g: number;
|
|
212
212
|
b: number;
|
|
@@ -216,7 +216,7 @@ export declare function createSdkSceneDraft(api: KakapoAPI, scene: SceneEdit, se
|
|
|
216
216
|
g: number;
|
|
217
217
|
b: number;
|
|
218
218
|
};
|
|
219
|
-
materialSetSecondaryColor: (id: number,
|
|
219
|
+
materialSetSecondaryColor: (id: number, rgb255: {
|
|
220
220
|
r: number;
|
|
221
221
|
g: number;
|
|
222
222
|
b: number;
|
|
@@ -226,7 +226,7 @@ export declare function createSdkSceneDraft(api: KakapoAPI, scene: SceneEdit, se
|
|
|
226
226
|
g: number;
|
|
227
227
|
b: number;
|
|
228
228
|
};
|
|
229
|
-
materialSetSubsurfaceColor: (id: number,
|
|
229
|
+
materialSetSubsurfaceColor: (id: number, rgb255: {
|
|
230
230
|
r: number;
|
|
231
231
|
g: number;
|
|
232
232
|
b: number;
|
|
@@ -294,7 +294,7 @@ export declare function createSdkSceneDraft(api: KakapoAPI, scene: SceneEdit, se
|
|
|
294
294
|
setHdrName: (name: string) => void;
|
|
295
295
|
setHdrMultiplier: (multiplier: number) => void;
|
|
296
296
|
setHdrOrientation: (orientation: import("@womp/kakapo-sdk").Vec2) => void;
|
|
297
|
-
setBackgroundColor: (
|
|
297
|
+
setBackgroundColor: (rgb255: {
|
|
298
298
|
r: number;
|
|
299
299
|
g: number;
|
|
300
300
|
b: number;
|
package/dist/sdk-scene-host.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createWompSceneApi, sceneNodeType } from "./womp-scene-api.js";
|
|
|
2
2
|
import { sceneUnitMultiplier } from "./coordinates.js";
|
|
3
3
|
import { executeSceneOperation, } from "./scene-operations.js";
|
|
4
4
|
import { readMaterialMetadata, writeMaterialMetadata, } from "./scene-metadata.js";
|
|
5
|
+
import { boundedReceiptDetails } from "./scene-health.js";
|
|
5
6
|
export function createSdkSceneDraft(api, scene, services) {
|
|
6
7
|
let defaultMaterialId = scene
|
|
7
8
|
.listMaterials()
|
|
@@ -100,13 +101,17 @@ export function createSdkSceneEditor(options) {
|
|
|
100
101
|
: operation.status === "rolled_back"
|
|
101
102
|
? "rolled_back"
|
|
102
103
|
: "unknown",
|
|
103
|
-
|
|
104
|
+
// An embedded scene health reading is bounded here, where every
|
|
105
|
+
// receipt is recorded -- the package's geometry and OpenSCAD
|
|
106
|
+
// hosts and a host's own `onCommitted` all put one in details
|
|
107
|
+
// (./scene-health.ts `boundedReceiptDetails`).
|
|
108
|
+
details: boundedReceiptDetails({
|
|
104
109
|
...hostDetails,
|
|
105
110
|
result: operation.result,
|
|
106
111
|
save: operation.save,
|
|
107
112
|
error: operation.error,
|
|
108
113
|
synchronized,
|
|
109
|
-
},
|
|
114
|
+
}),
|
|
110
115
|
});
|
|
111
116
|
};
|
|
112
117
|
try {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "Development: Matte Blue",
|
|
3
|
-
"resource": {
|
|
4
|
-
"data": {
|
|
5
|
-
"color.x": 0.08,
|
|
6
|
-
"color.y": 0.3,
|
|
7
|
-
"color.z": 0.8,
|
|
8
|
-
"roughness": 0.85,
|
|
9
|
-
"metalness": 0,
|
|
10
|
-
"index_of_refraction": 1.5
|
|
11
|
-
},
|
|
12
|
-
"shaders": []
|
|
13
|
-
}
|
|
14
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "Development: Matte Blue",
|
|
3
|
+
"resource": {
|
|
4
|
+
"data": {
|
|
5
|
+
"color.x": 0.08,
|
|
6
|
+
"color.y": 0.3,
|
|
7
|
+
"color.z": 0.8,
|
|
8
|
+
"roughness": 0.85,
|
|
9
|
+
"metalness": 0,
|
|
10
|
+
"index_of_refraction": 1.5
|
|
11
|
+
},
|
|
12
|
+
"shaders": []
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "Development: Polished Copper",
|
|
3
|
-
"resource": {
|
|
4
|
-
"data": {
|
|
5
|
-
"color.x": 0.955,
|
|
6
|
-
"color.y": 0.638,
|
|
7
|
-
"color.z": 0.538,
|
|
8
|
-
"roughness": 0.18,
|
|
9
|
-
"metalness": 1,
|
|
10
|
-
"index_of_refraction": 1.5
|
|
11
|
-
},
|
|
12
|
-
"shaders": []
|
|
13
|
-
}
|
|
14
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "Development: Polished Copper",
|
|
3
|
+
"resource": {
|
|
4
|
+
"data": {
|
|
5
|
+
"color.x": 0.955,
|
|
6
|
+
"color.y": 0.638,
|
|
7
|
+
"color.z": 0.538,
|
|
8
|
+
"roughness": 0.18,
|
|
9
|
+
"metalness": 1,
|
|
10
|
+
"index_of_refraction": 1.5
|
|
11
|
+
},
|
|
12
|
+
"shaders": []
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -17,36 +17,36 @@ export class SqliteSparkStore {
|
|
|
17
17
|
this.database.close();
|
|
18
18
|
throw new Error(`Unsupported Spark database version: ${version}`);
|
|
19
19
|
}
|
|
20
|
-
this.database.exec(`
|
|
21
|
-
PRAGMA busy_timeout = 5000;
|
|
22
|
-
PRAGMA journal_mode = WAL;
|
|
23
|
-
PRAGMA synchronous = FULL;
|
|
24
|
-
PRAGMA foreign_keys = ON;
|
|
25
|
-
CREATE TABLE IF NOT EXISTS conversations (
|
|
26
|
-
id TEXT PRIMARY KEY, owner_id TEXT NOT NULL, updated_at TEXT NOT NULL, data TEXT NOT NULL
|
|
27
|
-
);
|
|
28
|
-
CREATE INDEX IF NOT EXISTS conversation_owner ON conversations(owner_id, updated_at);
|
|
29
|
-
CREATE TABLE IF NOT EXISTS runs (
|
|
30
|
-
id TEXT PRIMARY KEY, conversation_id TEXT NOT NULL, data TEXT NOT NULL
|
|
31
|
-
);
|
|
32
|
-
CREATE TABLE IF NOT EXISTS chunks (
|
|
33
|
-
run_id TEXT NOT NULL REFERENCES runs(id), sequence INTEGER NOT NULL, data TEXT NOT NULL,
|
|
34
|
-
PRIMARY KEY (run_id, sequence)
|
|
35
|
-
);
|
|
36
|
-
CREATE TABLE IF NOT EXISTS artifacts (
|
|
37
|
-
id TEXT PRIMARY KEY, owner_id TEXT NOT NULL, metadata TEXT NOT NULL, bytes BLOB NOT NULL
|
|
38
|
-
);
|
|
39
|
-
CREATE TABLE IF NOT EXISTS execution_owner (
|
|
40
|
-
singleton INTEGER PRIMARY KEY CHECK (singleton = 1), pid INTEGER NOT NULL, token TEXT NOT NULL
|
|
41
|
-
);
|
|
42
|
-
CREATE TABLE IF NOT EXISTS scene_operations (
|
|
43
|
-
target_key TEXT NOT NULL, id TEXT NOT NULL, revision INTEGER NOT NULL, data TEXT NOT NULL,
|
|
44
|
-
PRIMARY KEY (target_key, id)
|
|
45
|
-
);
|
|
46
|
-
CREATE TABLE IF NOT EXISTS repair_states (
|
|
47
|
-
target_key TEXT PRIMARY KEY, revision INTEGER NOT NULL, data TEXT NOT NULL
|
|
48
|
-
);
|
|
49
|
-
PRAGMA user_version = 4;
|
|
20
|
+
this.database.exec(`
|
|
21
|
+
PRAGMA busy_timeout = 5000;
|
|
22
|
+
PRAGMA journal_mode = WAL;
|
|
23
|
+
PRAGMA synchronous = FULL;
|
|
24
|
+
PRAGMA foreign_keys = ON;
|
|
25
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
26
|
+
id TEXT PRIMARY KEY, owner_id TEXT NOT NULL, updated_at TEXT NOT NULL, data TEXT NOT NULL
|
|
27
|
+
);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS conversation_owner ON conversations(owner_id, updated_at);
|
|
29
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
30
|
+
id TEXT PRIMARY KEY, conversation_id TEXT NOT NULL, data TEXT NOT NULL
|
|
31
|
+
);
|
|
32
|
+
CREATE TABLE IF NOT EXISTS chunks (
|
|
33
|
+
run_id TEXT NOT NULL REFERENCES runs(id), sequence INTEGER NOT NULL, data TEXT NOT NULL,
|
|
34
|
+
PRIMARY KEY (run_id, sequence)
|
|
35
|
+
);
|
|
36
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
37
|
+
id TEXT PRIMARY KEY, owner_id TEXT NOT NULL, metadata TEXT NOT NULL, bytes BLOB NOT NULL
|
|
38
|
+
);
|
|
39
|
+
CREATE TABLE IF NOT EXISTS execution_owner (
|
|
40
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1), pid INTEGER NOT NULL, token TEXT NOT NULL
|
|
41
|
+
);
|
|
42
|
+
CREATE TABLE IF NOT EXISTS scene_operations (
|
|
43
|
+
target_key TEXT NOT NULL, id TEXT NOT NULL, revision INTEGER NOT NULL, data TEXT NOT NULL,
|
|
44
|
+
PRIMARY KEY (target_key, id)
|
|
45
|
+
);
|
|
46
|
+
CREATE TABLE IF NOT EXISTS repair_states (
|
|
47
|
+
target_key TEXT PRIMARY KEY, revision INTEGER NOT NULL, data TEXT NOT NULL
|
|
48
|
+
);
|
|
49
|
+
PRAGMA user_version = 4;
|
|
50
50
|
`);
|
|
51
51
|
}
|
|
52
52
|
repairStateStore(targetKey) {
|
|
@@ -127,13 +127,13 @@ export class SqliteSparkStore {
|
|
|
127
127
|
if (result.changes !== 1)
|
|
128
128
|
throw new Error("Scene operation changed concurrently or is missing");
|
|
129
129
|
if (next.status === "committed" || next.status === "rolled_back") {
|
|
130
|
-
this.database.prepare(`
|
|
131
|
-
DELETE FROM scene_operations WHERE rowid IN (
|
|
132
|
-
SELECT rowid FROM scene_operations
|
|
133
|
-
WHERE target_key = ? AND json_extract(data, '$.status') IN ('committed', 'rolled_back')
|
|
134
|
-
ORDER BY COALESCE(json_extract(data, '$.completedAt'), '') DESC
|
|
135
|
-
LIMIT -1 OFFSET ?
|
|
136
|
-
)
|
|
130
|
+
this.database.prepare(`
|
|
131
|
+
DELETE FROM scene_operations WHERE rowid IN (
|
|
132
|
+
SELECT rowid FROM scene_operations
|
|
133
|
+
WHERE target_key = ? AND json_extract(data, '$.status') IN ('committed', 'rolled_back')
|
|
134
|
+
ORDER BY COALESCE(json_extract(data, '$.completedAt'), '') DESC
|
|
135
|
+
LIMIT -1 OFFSET ?
|
|
136
|
+
)
|
|
137
137
|
`).run(next.targetKey, completedSceneOperationLimit);
|
|
138
138
|
}
|
|
139
139
|
this.database.exec("COMMIT");
|