@pinepaper.studio/mcp-server 1.6.19 → 1.6.21

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.
Files changed (113) hide show
  1. package/README.de.md +1 -1
  2. package/README.es.md +1 -1
  3. package/README.fr.md +1 -1
  4. package/README.hi.md +1 -1
  5. package/README.ja.md +1 -1
  6. package/README.ko.md +1 -1
  7. package/README.md +161 -5
  8. package/README.pt-BR.md +1 -1
  9. package/README.zh-CN.md +1 -1
  10. package/dist/build-info.json +2 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +7 -4
  13. package/dist/index.js.map +1 -1
  14. package/dist/ontology/upstream-vocabulary.d.ts +2 -2
  15. package/dist/ontology/upstream-vocabulary.js +3 -3
  16. package/dist/ontology/upstream-vocabulary.js.map +1 -1
  17. package/dist/ontology/vocabulary.d.ts.map +1 -1
  18. package/dist/ontology/vocabulary.js +9 -1
  19. package/dist/ontology/vocabulary.js.map +1 -1
  20. package/dist/tools/choreograph.d.ts +64 -0
  21. package/dist/tools/choreograph.d.ts.map +1 -0
  22. package/dist/tools/choreograph.js +346 -0
  23. package/dist/tools/choreograph.js.map +1 -0
  24. package/dist/tools/compact-descriptions.js +1 -1
  25. package/dist/tools/craft-tools.d.ts +55 -0
  26. package/dist/tools/craft-tools.d.ts.map +1 -0
  27. package/dist/tools/craft-tools.js +139 -0
  28. package/dist/tools/craft-tools.js.map +1 -0
  29. package/dist/tools/definitions.d.ts +10 -1
  30. package/dist/tools/definitions.d.ts.map +1 -1
  31. package/dist/tools/definitions.js +401 -32
  32. package/dist/tools/definitions.js.map +1 -1
  33. package/dist/tools/effect-check.d.ts +26 -0
  34. package/dist/tools/effect-check.d.ts.map +1 -0
  35. package/dist/tools/effect-check.js +129 -0
  36. package/dist/tools/effect-check.js.map +1 -0
  37. package/dist/tools/effect-class.d.ts +31 -0
  38. package/dist/tools/effect-class.d.ts.map +1 -0
  39. package/dist/tools/effect-class.js +176 -0
  40. package/dist/tools/effect-class.js.map +1 -0
  41. package/dist/tools/engine-lists.d.ts +6 -6
  42. package/dist/tools/engine-lists.d.ts.map +1 -1
  43. package/dist/tools/engine-lists.js +11 -9
  44. package/dist/tools/engine-lists.js.map +1 -1
  45. package/dist/tools/engine-methods.d.ts.map +1 -1
  46. package/dist/tools/engine-methods.js +12 -0
  47. package/dist/tools/engine-methods.js.map +1 -1
  48. package/dist/tools/engine-surface.d.ts +1 -1
  49. package/dist/tools/engine-surface.d.ts.map +1 -1
  50. package/dist/tools/engine-surface.js +221 -10
  51. package/dist/tools/engine-surface.js.map +1 -1
  52. package/dist/tools/handlers/export.d.ts.map +1 -1
  53. package/dist/tools/handlers/export.js +8 -1
  54. package/dist/tools/handlers/export.js.map +1 -1
  55. package/dist/tools/handlers/generate.d.ts +28 -28
  56. package/dist/tools/handlers.d.ts +23 -0
  57. package/dist/tools/handlers.d.ts.map +1 -1
  58. package/dist/tools/handlers.js +301 -6
  59. package/dist/tools/handlers.js.map +1 -1
  60. package/dist/tools/index.d.ts +2 -2
  61. package/dist/tools/index.d.ts.map +1 -1
  62. package/dist/tools/index.js +2 -2
  63. package/dist/tools/index.js.map +1 -1
  64. package/dist/tools/minimal-descriptions.d.ts.map +1 -1
  65. package/dist/tools/minimal-descriptions.js +16 -3
  66. package/dist/tools/minimal-descriptions.js.map +1 -1
  67. package/dist/tools/shape-params.d.ts +2 -2
  68. package/dist/tools/shape-params.d.ts.map +1 -1
  69. package/dist/tools/shape-params.js +25 -2
  70. package/dist/tools/shape-params.js.map +1 -1
  71. package/dist/tools/stick-vocabulary.d.ts +1 -1
  72. package/dist/tools/stick-vocabulary.js +1 -1
  73. package/dist/tools/toolkits.d.ts.map +1 -1
  74. package/dist/tools/toolkits.js +15 -0
  75. package/dist/tools/toolkits.js.map +1 -1
  76. package/dist/types/code-generator.d.ts +44 -1
  77. package/dist/types/code-generator.d.ts.map +1 -1
  78. package/dist/types/code-generator.js +1140 -128
  79. package/dist/types/code-generator.js.map +1 -1
  80. package/dist/types/schemas.d.ts +3091 -584
  81. package/dist/types/schemas.d.ts.map +1 -1
  82. package/dist/types/schemas.js +308 -6
  83. package/dist/types/schemas.js.map +1 -1
  84. package/dist/vendor/design/design-style.d.ts +2 -2
  85. package/dist/vendor/design/design-style.d.ts.map +1 -1
  86. package/dist/vendor/design/design-style.js +7 -2
  87. package/dist/vendor/design/design-style.js.map +1 -1
  88. package/dist/vendor/design/subdivision-tool-schema.d.ts +16 -0
  89. package/dist/vendor/design/subdivision-tool-schema.d.ts.map +1 -0
  90. package/dist/vendor/design/subdivision-tool-schema.js +104 -0
  91. package/dist/vendor/design/subdivision-tool-schema.js.map +1 -0
  92. package/dist/vendor/design/textile-tool-schema.d.ts +14 -0
  93. package/dist/vendor/design/textile-tool-schema.d.ts.map +1 -0
  94. package/dist/vendor/design/textile-tool-schema.js +195 -0
  95. package/dist/vendor/design/textile-tool-schema.js.map +1 -0
  96. package/dist/vendor/design/ui-kit-runtime.d.ts +49 -0
  97. package/dist/vendor/design/ui-kit-runtime.d.ts.map +1 -0
  98. package/dist/vendor/design/ui-kit-runtime.js +761 -0
  99. package/dist/vendor/design/ui-kit-runtime.js.map +1 -0
  100. package/dist/vendor/design/ui-kit-tool-schema.d.ts +280 -0
  101. package/dist/vendor/design/ui-kit-tool-schema.d.ts.map +1 -0
  102. package/dist/vendor/design/ui-kit-tool-schema.js +209 -0
  103. package/dist/vendor/design/ui-kit-tool-schema.js.map +1 -0
  104. package/manifest.json +53 -1
  105. package/package.json +5 -4
  106. package/dist/assets/repositories/openclipart.d.ts +0 -41
  107. package/dist/assets/repositories/openclipart.d.ts.map +0 -1
  108. package/dist/assets/repositories/openclipart.js +0 -139
  109. package/dist/assets/repositories/openclipart.js.map +0 -1
  110. package/dist/assets/repositories/svgrepo.d.ts +0 -61
  111. package/dist/assets/repositories/svgrepo.d.ts.map +0 -1
  112. package/dist/assets/repositories/svgrepo.js +0 -276
  113. package/dist/assets/repositories/svgrepo.js.map +0 -1
@@ -5,8 +5,9 @@
5
5
  * Supports i18n for localized error and success messages.
6
6
  * Supports browser execution via Puppeteer.
7
7
  */
8
+ import { CRAFT_FRAME_JS, generateDrawWeave, generateDrawKnots, generateDrawSashiko, generateDrawSubdivision, generateUiKit, uiKitCallProblems, } from './craft-tools.js';
8
9
  import { codeGenerator, generateQueryMutationsCode, SHEET_RUBRIC } from '../types/code-generator.js';
9
- import { CreateItemInputSchema, ModifyItemInputSchema, DeleteItemInputSchema, AddRelationInputSchema, RemoveRelationInputSchema, QueryRelationsInputSchema, AnimateItemInputSchema, ImportMotionCaptureInputSchema, KeyframeAnimateInputSchema, ExecuteGeneratorInputSchema, ApplyEffectInputSchema, GetItemsInputSchema, PlayTimelineInputSchema, SetBackgroundColorInputSchema, SetCanvasSizeInputSchema, ImportSVGInputSchema, ImportMermaidInputSchema, AddFilterInputSchema, CreateGlossySphereInputSchema, CreateDiagonalStripesInputSchema, BatchCreateInputSchema, BatchModifyInputSchema, CreateGridInputSchema, GeometryInputSchema, EquationPathInputSchema, EventInputSchema, ConstructionSequenceInputSchema, ValidateSceneInputSchema, CaptureFramesInputSchema, InstantiateOntologyInputSchema, LintSceneInputSchema, MediaInputSchema, TextStyleInputSchema, TextEffectInputSchema, DesignMediumInputSchema, RelightInputSchema, ShaderGraphInputSchema, ShatterImageInputSchema, ImportLayeredCharacterInputSchema, CharacterInputSchema, GameInputSchema, World3DInputSchema, BrandKitInputSchema, ComponentInputSchema, ArtboardInputSchema, CommentInputSchema, ProvenanceInputSchema, SceneDiffInputSchema, AudioBeatsInputSchema, TemplateParamsInputSchema, RenderBatchInputSchema, AccessibilityCheckInputSchema, QueryMutationsInputSchema, ComposeInputSchema, CropImageInputSchema, PathOpInputSchema, MotionInputSchema, SoundInputSchema, InterchangeInputSchema, StickInputSchema, DesignSystemInputSchema, StoryInputSchema, ChromaKeyInputSchema, RiggingInputSchema, GroupInputSchema, CameraDirectorInputSchema, DetectObjectsInputSchema, ExtractObjectInputSchema, ArrangeInputSchema, CreateSceneInputSchema, GetPerformanceMetricsInputSchema, DiagnosticReportInputSchema, SearchAssetsInputSchema, ImportAssetInputSchema, P5DrawInputSchema,
10
+ import { CreateItemInputSchema, ModifyItemInputSchema, DeleteItemInputSchema, AddRelationInputSchema, RemoveRelationInputSchema, QueryRelationsInputSchema, AnimateItemInputSchema, ImportMotionCaptureInputSchema, KeyframeAnimateInputSchema, ExecuteGeneratorInputSchema, ApplyEffectInputSchema, GetItemsInputSchema, PlayTimelineInputSchema, SetBackgroundColorInputSchema, SetCanvasSizeInputSchema, ImportSVGInputSchema, ImportMermaidInputSchema, AddFilterInputSchema, CreateGlossySphereInputSchema, CreateDiagonalStripesInputSchema, BatchCreateInputSchema, BatchModifyInputSchema, CreateGridInputSchema, GeometryInputSchema, EquationPathInputSchema, EventInputSchema, ConstructionSequenceInputSchema, ValidateSceneInputSchema, CaptureFramesInputSchema, InstantiateOntologyInputSchema, LintSceneInputSchema, MediaInputSchema, TextStyleInputSchema, TextEffectInputSchema, DesignMediumInputSchema, RelightInputSchema, ChoreographInputSchema, EmitterInputSchema, MorphSequenceInputSchema, OriginalCharacterInputSchema, LookInputSchema, RenderHookInputSchema, ShaderGraphInputSchema, ShatterImageInputSchema, ImportLayeredCharacterInputSchema, CharacterInputSchema, GameInputSchema, World3DInputSchema, BrandKitInputSchema, ComponentInputSchema, ArtboardInputSchema, CommentInputSchema, ProvenanceInputSchema, SceneDiffInputSchema, AudioBeatsInputSchema, TemplateParamsInputSchema, RenderBatchInputSchema, AccessibilityCheckInputSchema, QueryMutationsInputSchema, ComposeInputSchema, CropImageInputSchema, PathOpInputSchema, MotionInputSchema, SoundInputSchema, InterchangeInputSchema, StickInputSchema, DesignSystemInputSchema, StoryInputSchema, ChromaKeyInputSchema, RiggingInputSchema, GroupInputSchema, CameraDirectorInputSchema, DetectObjectsInputSchema, ExtractObjectInputSchema, ArrangeInputSchema, CreateSceneInputSchema, GetPerformanceMetricsInputSchema, DiagnosticReportInputSchema, SearchAssetsInputSchema, ImportAssetInputSchema, P5DrawInputSchema,
10
11
  // Diagram schemas
11
12
  CreateDiagramShapeInputSchema, ConnectInputSchema, ConnectPortsInputSchema, AddPortsInputSchema, AutoLayoutInputSchema, GetDiagramShapesInputSchema, UpdateConnectorInputSchema, RemoveConnectorInputSchema, DiagramModeInputSchema,
12
13
  // Agent flow mode schemas
@@ -27,8 +28,11 @@ ImportImageInputSchema,
27
28
  // Scene management schemas
28
29
  ManageScenesInputSchema, ScenePlaybackInputSchema, SceneGraphInputSchema, SequenceInputSchema, StaggerInputSchema, FlipInputSchema,
29
30
  // New consolidated tool schemas
30
- SelectionInputSchema, TransformInputSchema, HistoryInputSchema, ImageFilterInputSchema, LassoInputSchema, CutoutStyleInputSchema, PrecompInputSchema, BackgroundInputSchema, QueryInputSchema, QueryCapabilitiesInputSchema, DeformInputSchema, SpriteSheetInputSchema, InteractionInputSchema, ExportWidgetInputSchema, ExportWidgetHtmlInputSchema, CreateChartInputSchema, MagicInputSchema, PhysicsInputSchema, MeasurementInputSchema, PlaceOnSurfaceInputSchema, StyledSceneInputSchema, ErrorCodes, } from '../types/schemas.js';
31
+ SelectionInputSchema, TransformInputSchema, HistoryInputSchema, ImageFilterInputSchema, LassoInputSchema, CutoutStyleInputSchema, PrecompInputSchema, BackgroundInputSchema, QueryInputSchema, QueryCapabilitiesInputSchema, DeformInputSchema, HandDrawnInputSchema, PenFigureInputSchema, CreateCharacterInputSchema, CharacterActionInputSchema, CharacterPoseInputSchema, SpriteSheetInputSchema, InteractionInputSchema, ExportWidgetInputSchema, ExportWidgetHtmlInputSchema, CreateChartInputSchema, MagicInputSchema, PhysicsInputSchema, MeasurementInputSchema, PlaceOnSurfaceInputSchema, DrawWeaveInputSchema, DrawKnotsInputSchema, DrawSashikoInputSchema, DrawSubdivisionInputSchema, UiKitInputSchema, StyledSceneInputSchema, ErrorCodes, } from '../types/schemas.js';
31
32
  import { ZodError, z } from 'zod';
33
+ import { AsyncLocalStorage } from 'node:async_hooks';
34
+ import { effectClass } from './effect-class.js';
35
+ import { EFFECT_BEFORE_JS, effectAfterJs, namedItemIds, idMayBeGone, NO_CHANGE_NOTE } from './effect-check.js';
32
36
  import { writeFile, mkdir, appendFile, unlink, readFile, stat } from 'node:fs/promises';
33
37
  import { tmpdir } from 'node:os';
34
38
  import { join, extname } from 'node:path';
@@ -624,6 +628,17 @@ function dataResult(value) {
624
628
  * index.ts adds toolMs, the whole call, for every tool.
625
629
  */
626
630
  export const TIMING_META_KEY = 'pinepaper.studio/timing';
631
+ export const EFFECT_META_KEY = 'pinepaper.studio/effect';
632
+ /** The tool and action of the call in flight, for executeOrGenerate's effect check. */
633
+ const callContext = new AsyncLocalStorage();
634
+ let READ_ONLY_TOOLS = null;
635
+ async function readOnlyTools() {
636
+ if (!READ_ONLY_TOOLS) {
637
+ const { PINEPAPER_TOOLS } = await import('./definitions.js');
638
+ READ_ONLY_TOOLS = new Set(PINEPAPER_TOOLS.filter((t) => t.annotations?.readOnlyHint).map((t) => t.name));
639
+ }
640
+ return READ_ONLY_TOOLS;
641
+ }
627
642
  export function withTiming(out, t) {
628
643
  const meta = (out._meta ?? {});
629
644
  const prev = (meta[TIMING_META_KEY] ?? {});
@@ -844,6 +859,80 @@ const REMOVE_ITEMS_CREATED_SINCE_MARK = `(function () {
844
859
  * false when the page could not be marked (the rollback is then skipped and
845
860
  * the failure note says nothing about the scene it cannot vouch for).
846
861
  */
862
+ /**
863
+ * The studio's job gate (FxTool D92). PinePaperAgent.startJob is the one place
864
+ * a stale studio reloads, and only when the job is about to clear the canvas:
865
+ * it resolves {reloading: true, fromBuild, toBuild} and the PAGE reloads, so
866
+ * nothing running in it survives — the wait has to happen here. Poll the new
867
+ * page through waitForReady, open the job again, then let start_job's own
868
+ * setup (clear, canvas size) run on the fresh page.
869
+ */
870
+ export async function studioJobGate(input, options, wait = (ms) => new Promise((r) => setTimeout(r, ms))) {
871
+ const mode = options.executionMode ?? getExecutionMode();
872
+ if (mode === 'code' || !options.executeInBrowser || options.deferred)
873
+ return {};
874
+ const controller = options.browserController || getBrowserController();
875
+ if (!controller.connected) {
876
+ try {
877
+ await controller.connect();
878
+ }
879
+ catch {
880
+ return {};
881
+ }
882
+ }
883
+ const args = JSON.stringify({ ...(input.name ? { name: input.name } : {}), clearCanvas: input.clearCanvas !== false });
884
+ const START = `(async function () {
885
+ const A = typeof window !== 'undefined' ? window.PinePaperAgent : null;
886
+ if (!A || !A.startJob) return { legacy: true };
887
+ return await A.startJob(${args});
888
+ })();`;
889
+ const call = async (code) => {
890
+ try {
891
+ const r = await controller.executeCode(code, false, { bypassGovernor: true });
892
+ return r.success ? r.result : null;
893
+ }
894
+ catch {
895
+ return null;
896
+ }
897
+ };
898
+ const first = await call(START);
899
+ if (!first || first.legacy || (first.jobOpen === undefined && first.reloading === undefined))
900
+ return {};
901
+ const build = (j) => ({ buildId: j.buildId, liveBuildId: j.liveBuildId, stale: !!j.stale });
902
+ if (first.reloading) {
903
+ const READY = `(async function () {
904
+ const A = typeof window !== 'undefined' ? window.PinePaperAgent : null;
905
+ if (!A || !A.waitForReady) return { ready: false };
906
+ await A.waitForReady();
907
+ return { ready: true };
908
+ })();`;
909
+ let ready = false;
910
+ for (let i = 0; i < 60 && !ready; i++) { // ~30 s: a reload is ~100 ms plus a fresh boot
911
+ await wait(500);
912
+ const r = await call(READY);
913
+ ready = !!(r && r.ready);
914
+ }
915
+ if (!ready)
916
+ return { warning: `the studio was reloading from build ${first.fromBuild} to ${first.toBuild} and did not come back within 30 s; this job may run on the old page.`, build: build(first) };
917
+ const second = await call(START);
918
+ if (!second)
919
+ return { warning: 'the studio reloaded but did not open the job again.', build: build(first) };
920
+ return {
921
+ note: `Studio was on build ${first.fromBuild}, reloaded to ${first.toBuild} before this job.`,
922
+ ...(second.stale ? { warning: `the studio is still on an old build after reloading (${second.buildId}; live ${second.liveBuildId})${second.reason ? ': ' + second.reason : ''}.` } : {}),
923
+ build: build(second),
924
+ };
925
+ }
926
+ if (first.stale) {
927
+ return {
928
+ warning: input.clearCanvas === false
929
+ ? `studio is on old build ${first.buildId} (live: ${first.liveBuildId}); finish, then start a new job to update.`
930
+ : `studio is on old build ${first.buildId} (live: ${first.liveBuildId}) and was not reloaded${first.reason ? ': ' + first.reason : ''}.`,
931
+ build: build(first),
932
+ };
933
+ }
934
+ return { build: build(first) };
935
+ }
847
936
  async function markItemsBeforeRun(controller) {
848
937
  try {
849
938
  const r = await controller.executeCode(MARK_ITEMS_BEFORE_RUN, false, { bypassGovernor: true });
@@ -1011,6 +1100,24 @@ async function captureSheet(input, options) {
1011
1100
  }],
1012
1101
  };
1013
1102
  }
1103
+ /**
1104
+ * The canvas the craft tools fill when the call gives no bounds. Asked of the
1105
+ * studio, so a portrait or square canvas is filled rather than a 1280x720
1106
+ * corner of it; undefined (the generators' own default) without a studio.
1107
+ */
1108
+ async function craftFrame(options) {
1109
+ const ctl = options.browserController;
1110
+ if (!options.executeInBrowser || !ctl || !ctl.connected)
1111
+ return undefined;
1112
+ try {
1113
+ const r = await ctl.executeCode(CRAFT_FRAME_JS, false);
1114
+ const f = r.success ? r.result : null;
1115
+ return f && f.width > 0 && f.height > 0 ? f : undefined;
1116
+ }
1117
+ catch {
1118
+ return undefined;
1119
+ }
1120
+ }
1014
1121
  export async function executeOrGenerate(code, description, options, toolName,
1015
1122
  /**
1016
1123
  * Values too large to live inside the generated code. Written to
@@ -1156,6 +1263,23 @@ ${code}
1156
1263
  const rollbackOnFailure = toolName === 'pinepaper_execute_custom_code'
1157
1264
  && await markItemsBeforeRun(controller);
1158
1265
  const governorTimeoutMs = LONG_GOVERNOR_BUDGET_MS[toolName];
1266
+ // NO SILENT SUCCESS (effect-class.ts / effect-check.ts): fingerprint a
1267
+ // mutating call before and after it runs.
1268
+ const ctx = callContext.getStore();
1269
+ const action = ctx && ctx.toolName === toolName ? ctx.action : undefined;
1270
+ const effect = effectClass(toolName, action, (await readOnlyTools()).has(toolName));
1271
+ const checkEffect = effect === 'mutates' && !rollbackOnFailure;
1272
+ let effectCheckMs = 0;
1273
+ let effectArmed = false;
1274
+ if (checkEffect) {
1275
+ const t0 = Date.now();
1276
+ try {
1277
+ const r = await controller.executeCode(EFFECT_BEFORE_JS, false, { bypassGovernor: true });
1278
+ effectArmed = !!(r.success && r.result?.ok);
1279
+ }
1280
+ catch { /* unchecked this time */ }
1281
+ effectCheckMs += Date.now() - t0;
1282
+ }
1159
1283
  const result = await controller.executeCode(code, shouldTakeScreenshot, {
1160
1284
  ...(stage ? { stage } : {}),
1161
1285
  ...(governorTimeoutMs ? { governorTimeoutMs } : {}),
@@ -1226,7 +1350,15 @@ ${code}
1226
1350
  const inner = innerVerdict;
1227
1351
  if (inner) {
1228
1352
  const canvasState = await captureCanvasState(controller);
1229
- return withTiming(errorResult(ErrorCodes.EXECUTION_ERROR, inner, {
1353
+ // THE ENGINE'S OWN REASON, when it gave one. An emitter's refusal is often
1354
+ // generic ("the studio refused the time_expression relation") while the
1355
+ // engine said exactly why in a warning the governor captured ("unknown
1356
+ // property 'opacty' — did you mean 'opacity'?"; D78). Production strips
1357
+ // console.warn, so this is the only place the model can read it.
1358
+ const said = (Array.isArray(result.report?.warnings) ? result.report.warnings : [])
1359
+ .filter((w) => typeof w === 'string' && w.length > 0 && !inner.includes(w))
1360
+ .slice(0, 3);
1361
+ return withTiming(errorResult(ErrorCodes.EXECUTION_ERROR, said.length ? `${inner} The studio said: ${said.join(' | ')}` : inner, {
1230
1362
  code, result: result.result, governorReport: result.report,
1231
1363
  ...(rollbackOnFailure ? await customCodeRollback(controller, inner) : {}),
1232
1364
  }, { toolName, canvasState: canvasState || undefined }), timing);
@@ -1240,7 +1372,32 @@ ${code}
1240
1372
  }
1241
1373
  catch { /* no session */ }
1242
1374
  }
1243
- return withTiming(withSessionNote(executedResult(code, result.result, result.screenshot, description, result.report), result), timing);
1375
+ let noChangeNote = null;
1376
+ let effectMeta = null;
1377
+ if (checkEffect && effectArmed) {
1378
+ const t0 = Date.now();
1379
+ const ids = idMayBeGone(toolName, action) ? [] : namedItemIds(result.result);
1380
+ const verdict = await controller.executeCode(effectAfterJs(ids), false, { bypassGovernor: true })
1381
+ .then((r) => (r.success ? r.result : null))
1382
+ .catch(() => null);
1383
+ effectCheckMs += Date.now() - t0;
1384
+ if (verdict && verdict.ok) {
1385
+ // A CONTRADICTION, not idempotence: the call names an item that is not there.
1386
+ if (verdict.missing && verdict.missing.length) {
1387
+ return withTiming(errorResult(ErrorCodes.EXECUTION_ERROR, `the call reported ${verdict.missing.join(', ')}, which ${verdict.missing.length === 1 ? 'is' : 'are'} not on the canvas: it did not make what it said it made.`, { result: result.result }, { toolName }), { ...timing, effectCheckMs });
1388
+ }
1389
+ const changed = verdict.changed ?? [];
1390
+ effectMeta = { class: effect, changed: changed.length > 0, signals: changed, checked: verdict.checked ?? [] };
1391
+ if (!changed.length)
1392
+ noChangeNote = NO_CHANGE_NOTE(verdict.checked ?? []);
1393
+ }
1394
+ }
1395
+ let out = withSessionNote(executedResult(code, result.result, result.screenshot, description, result.report), result);
1396
+ if (noChangeNote)
1397
+ out = { ...out, content: [...(out.content ?? []), { type: 'text', text: noChangeNote }] };
1398
+ if (effectMeta)
1399
+ out = { ...out, _meta: { ...(out._meta ?? {}), [EFFECT_META_KEY]: effectMeta } };
1400
+ return withTiming(out, checkEffect ? { ...timing, effectCheckMs } : timing);
1244
1401
  }
1245
1402
  /**
1246
1403
  * The emitted code's own verdict, when it reported one and it was a failure.
@@ -1429,7 +1586,7 @@ export function ignoredArgumentsNote(toolName, args) {
1429
1586
  export async function handleToolCall(toolName, args, options = {}) {
1430
1587
  const tracker = getPerformanceTracker();
1431
1588
  const baseTimerId = `${toolName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1432
- const result = await handleToolCallInner(toolName, args, options, tracker, baseTimerId);
1589
+ const result = await callContext.run({ toolName, action: typeof args.action === 'string' ? args.action : undefined }, () => handleToolCallInner(toolName, args, options, tracker, baseTimerId));
1433
1590
  const ignoredNote = ignoredArgumentsNote(toolName, args);
1434
1591
  if (ignoredNote)
1435
1592
  result.content = [...(result.content ?? []), { type: 'text', text: ignoredNote }];
@@ -1650,6 +1807,17 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
1650
1807
  const code = codeGenerator.generateDeform(input);
1651
1808
  return executeOrGenerate(code, `Deform: ${input.action}`, options, 'pinepaper_deform');
1652
1809
  }
1810
+ case 'pinepaper_hand_drawn': {
1811
+ const input = HandDrawnInputSchema.parse(args);
1812
+ const code = codeGenerator.generateHandDrawn(input);
1813
+ return executeOrGenerate(code, `Hand-drawn style: ${input.style ?? input.mode ?? 'sketchy'}`, options, 'pinepaper_hand_drawn');
1814
+ }
1815
+ case 'pinepaper_pen_figure': {
1816
+ const input = PenFigureInputSchema.parse(args);
1817
+ const code = codeGenerator.generatePenFigure(input);
1818
+ const what = input.action === 'list' ? 'List the pen figures' : `Draw ${typeof input.figure === 'string' ? input.figure : 'a figure'} with a pen`;
1819
+ return executeOrGenerate(code, what, options, 'pinepaper_pen_figure');
1820
+ }
1653
1821
  case 'pinepaper_sprite_sheet': {
1654
1822
  const input = SpriteSheetInputSchema.parse(args);
1655
1823
  const code = codeGenerator.generateSpriteSheet(input);
@@ -1718,6 +1886,60 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
1718
1886
  const description = `Places ${input.concept} as ${plan.parts.length} parts with ${plan.tracks} keyframe tracks`;
1719
1887
  return executeOrGenerate(code, description, options, 'pinepaper_character');
1720
1888
  }
1889
+ case 'pinepaper_create_character': {
1890
+ const input = CreateCharacterInputSchema.parse(args);
1891
+ // If layered bundle provided, use importLayeredCharacter
1892
+ if (input.bundle) {
1893
+ const bundleInfo = input.bundle.info || input.bundle;
1894
+ const bundleImages = input.bundle.images || {};
1895
+ const code = codeGenerator.generateImportLayeredCharacter({ info: bundleInfo, images: bundleImages });
1896
+ return executeOrGenerate(code, `Import layered character: ${input.name || input.characterId || 'bundle'}`, options, 'pinepaper_create_character');
1897
+ }
1898
+ // If concept/preset matches a planCharacter concept, plan it
1899
+ const conceptName = input.concept || input.preset;
1900
+ if (conceptName) {
1901
+ const formattedConcept = conceptName.startsWith('pp:') ? conceptName : `pp:${conceptName.charAt(0).toUpperCase() + conceptName.slice(1)}`;
1902
+ const plan = planCharacter({
1903
+ concept: formattedConcept,
1904
+ at: { x: input.x, y: input.y },
1905
+ scale: input.scale,
1906
+ });
1907
+ if (plan.ok) {
1908
+ const code = generateCharacterCode(plan, {
1909
+ generateCreateItem: (i) => codeGenerator.generateCreateItem(i),
1910
+ generateKeyframeAnimate: (i) => codeGenerator.generateKeyframeAnimate(i),
1911
+ }, {
1912
+ createItem: (a) => CreateItemInputSchema.parse(a),
1913
+ keyframeAnimate: (a) => KeyframeAnimateInputSchema.parse(a),
1914
+ });
1915
+ return executeOrGenerate(code, `Places character ${formattedConcept}`, options, 'pinepaper_create_character');
1916
+ }
1917
+ }
1918
+ const code = codeGenerator.generateCreateCharacter(input);
1919
+ return executeOrGenerate(code, `Create character: ${input.name || input.characterId || 'Character'}`, options, 'pinepaper_create_character');
1920
+ }
1921
+ case 'pinepaper_character_action': {
1922
+ const input = CharacterActionInputSchema.parse(args);
1923
+ const act = String(input.action || 'walk').toLowerCase();
1924
+ const FACES = ['blink', 'frown', 'smile', 'surprise', 'talk', 'wink'];
1925
+ if (FACES.includes(act)) {
1926
+ return {
1927
+ content: [{
1928
+ type: 'text',
1929
+ text: `"${act}" is a facial expression — use pinepaper_character_pose with { expression: "${act}" }. Actions: walk, run, locomotion, wave, dance, jump, idle`,
1930
+ }],
1931
+ isError: true,
1932
+ };
1933
+ }
1934
+ const code = codeGenerator.generateCharacterAction(input);
1935
+ return executeOrGenerate(code, `Character action: ${act}`, options, 'pinepaper_character_action');
1936
+ }
1937
+ case 'pinepaper_character_pose': {
1938
+ const input = CharacterPoseInputSchema.parse(args);
1939
+ const expr = String(input.expression || 'smile').toLowerCase();
1940
+ const code = codeGenerator.generateCharacterPose(input);
1941
+ return executeOrGenerate(code, `Character pose/expression: ${expr}`, options, 'pinepaper_character_pose');
1942
+ }
1721
1943
  case 'pinepaper_batch_create': {
1722
1944
  const input = BatchCreateInputSchema.parse(args);
1723
1945
  const code = codeGenerator.generateBatchCreate(input);
@@ -1887,6 +2109,30 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
1887
2109
  return executeOrGenerate(code, `Text effect: ${input.action}`, options, 'pinepaper_text_effect');
1888
2110
  }
1889
2111
  // ─── 1.6.4: agent surface for the Tier-2 engine features ───
2112
+ case 'pinepaper_look': {
2113
+ const input = LookInputSchema.parse(args);
2114
+ return executeOrGenerate(codeGenerator.generateLook(input), `Look: ${input.action}`, options, 'pinepaper_look');
2115
+ }
2116
+ case 'pinepaper_original_character': {
2117
+ const input = OriginalCharacterInputSchema.parse(args);
2118
+ return executeOrGenerate(codeGenerator.generateOriginalCharacter(input), `Original character: ${input.action}`, options, 'pinepaper_original_character');
2119
+ }
2120
+ case 'pinepaper_morph_sequence': {
2121
+ const input = MorphSequenceInputSchema.parse(args);
2122
+ return executeOrGenerate(codeGenerator.generateMorphSequence(input), `Morph sequence: ${input.states.length} states`, options, 'pinepaper_morph_sequence');
2123
+ }
2124
+ case 'pinepaper_emitter': {
2125
+ const input = EmitterInputSchema.parse(args);
2126
+ return executeOrGenerate(codeGenerator.generateEmitter(input), `Emitter: ${input.action}`, options, 'pinepaper_emitter');
2127
+ }
2128
+ case 'pinepaper_render_hook': {
2129
+ const input = RenderHookInputSchema.parse(args);
2130
+ return executeOrGenerate(codeGenerator.generateRenderHook(input), `Render hook: ${input.action}`, options, 'pinepaper_render_hook');
2131
+ }
2132
+ case 'pinepaper_choreograph': {
2133
+ const input = ChoreographInputSchema.parse(args);
2134
+ return executeOrGenerate(codeGenerator.generateChoreograph(input), `Choreograph: ${input.beats.map((b) => b.verb).join(' → ')}`, options, 'pinepaper_choreograph');
2135
+ }
1890
2136
  case 'pinepaper_relight': {
1891
2137
  const input = RelightInputSchema.parse(args);
1892
2138
  return executeOrGenerate(codeGenerator.generateRelight(input), `Relight: ${input.action}`, options, 'pinepaper_relight');
@@ -1899,6 +2145,44 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
1899
2145
  const input = StyledSceneInputSchema.parse(args);
1900
2146
  return executeOrGenerate(codeGenerator.generateStyledScene(input), `Styled scene${input.style ? `: ${input.style}` : ''}`, options, 'pinepaper_styled_scene');
1901
2147
  }
2148
+ // The cloud's craft tools, shared (craft-tools.ts says what differs locally).
2149
+ case 'pinepaper_draw_weave': {
2150
+ const input = DrawWeaveInputSchema.parse(args);
2151
+ const code = generateDrawWeave(input, await craftFrame(options));
2152
+ return executeOrGenerate(code, `Draw a ${input.weave ?? 'plain'} weave`, options, 'pinepaper_draw_weave');
2153
+ }
2154
+ case 'pinepaper_draw_knots': {
2155
+ const input = DrawKnotsInputSchema.parse(args);
2156
+ if (!input.points?.length) {
2157
+ return {
2158
+ content: [{ type: 'text', text: 'pinepaper_draw_knots: give points — [{x, y}, …], one per knot. The tool places knots where you say and draws nothing without them.' }],
2159
+ isError: true,
2160
+ };
2161
+ }
2162
+ return executeOrGenerate(generateDrawKnots(input), `Draw ${input.points.length} embroidered knot(s)`, options, 'pinepaper_draw_knots');
2163
+ }
2164
+ case 'pinepaper_draw_sashiko': {
2165
+ const input = DrawSashikoInputSchema.parse(args);
2166
+ const code = generateDrawSashiko(input, await craftFrame(options));
2167
+ return executeOrGenerate(code, `Draw ${input.pattern ?? 'seigaiha'} sashiko`, options, 'pinepaper_draw_sashiko');
2168
+ }
2169
+ case 'pinepaper_draw_subdivision': {
2170
+ const input = DrawSubdivisionInputSchema.parse(args);
2171
+ const code = generateDrawSubdivision(input, await craftFrame(options));
2172
+ return executeOrGenerate(code, `Draw a ${input.preset ?? 'mondrian'} subdivision`, options, 'pinepaper_draw_subdivision');
2173
+ }
2174
+ case 'pinepaper_ui_kit': {
2175
+ const input = UiKitInputSchema.parse(args);
2176
+ const problems = uiKitCallProblems(input);
2177
+ const code = problems.length ? null : generateUiKit(input);
2178
+ if (!code) {
2179
+ return {
2180
+ content: [{ type: 'text', text: `pinepaper_ui_kit: ${problems.join(' ') || 'nothing to draw'}` }],
2181
+ isError: true,
2182
+ };
2183
+ }
2184
+ return executeOrGenerate(code, `UI kit: ${input.action}`, options, 'pinepaper_ui_kit');
2185
+ }
1902
2186
  case 'pinepaper_place_on_surface': {
1903
2187
  const input = PlaceOnSurfaceInputSchema.parse(args);
1904
2188
  const code = codeGenerator.generatePlaceOnSurface(input);
@@ -2179,7 +2463,7 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
2179
2463
  const description = getLocalizedSuccessMessage(i18n, 'relationAdded', {
2180
2464
  relationType: input.relationType ?? `preset:${input.presetId}`,
2181
2465
  sourceId: input.sourceId,
2182
- targetId: input.targetId || 'self',
2466
+ targetId: input.targetId || '(no target)',
2183
2467
  });
2184
2468
  return executeOrGenerate(code, description, options, 'pinepaper_add_relation');
2185
2469
  }
@@ -3130,11 +3414,20 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
3130
3414
  }
3131
3415
  const code = codeGenerator.generateAgentStartJob(input);
3132
3416
  const descriptionText = `Started agent job${input.name ? ` "${input.name}"` : ''} with ${input.screenshotPolicy || 'on_complete'} screenshot policy`;
3417
+ // A STALE STUDIO IS RELOADED HERE, AND ONLY HERE (D92): the canvas is
3418
+ // about to be cleared anyway. The setup below runs on the new page.
3419
+ const gate = await studioJobGate(input, options);
3133
3420
  const result = await executeOrGenerate(code, descriptionText, options, 'pinepaper_agent_start_job');
3134
3421
  // Append a 1-line workflow hint
3135
3422
  if (result.content && result.content.length > 0 && result.content[0].type === 'text') {
3136
3423
  result.content[0].text += `\n\nNEXT: Call batch_execute with all operations, then end_job.`;
3424
+ if (gate.note)
3425
+ result.content[0].text = `${gate.note}\n\n${result.content[0].text}`;
3426
+ if (gate.warning)
3427
+ result.content[0].text = `⚠️ ${gate.warning}\n\n${result.content[0].text}`;
3137
3428
  }
3429
+ if (gate.build)
3430
+ result._meta = { ...(result._meta ?? {}), 'pinepaper.studio/build': gate.build };
3138
3431
  return result;
3139
3432
  }
3140
3433
  case 'pinepaper_agent_end_job': {
@@ -3883,6 +4176,8 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
3883
4176
  'pinepaper_query_capabilities',
3884
4177
  // Deformation, sprite sheets, interaction
3885
4178
  'pinepaper_deform',
4179
+ 'pinepaper_hand_drawn',
4180
+ 'pinepaper_pen_figure',
3886
4181
  'pinepaper_sprite_sheet',
3887
4182
  'pinepaper_interaction',
3888
4183
  // Widget export