@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/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: (color: {
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: (color) => {
69
- const r = finite(color?.r, "Background red");
70
- const g = finite(color?.g, "Background green");
71
- const b = finite(color?.b, "Background blue");
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
  },
@@ -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;
@@ -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
- result.diagnostics.push(errorMessage(error));
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 areaIds = objectIds.length > 0
6
- ? objectIds.map((id) => getSdkSceneAreaId(api, id))
7
- : getSdkSceneAreaIds(api);
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
- onHealth?.(health);
10
- return health;
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
  }
@@ -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, color: {
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, color: {
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, color: {
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, color: {
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: (color: {
297
+ setBackgroundColor: (rgb255: {
298
298
  r: number;
299
299
  g: number;
300
300
  b: number;
@@ -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
- details: {
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");