@pinepaper.studio/mcp-server 1.6.18 → 1.6.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +1 -1
- package/README.es.md +1 -1
- package/README.fr.md +1 -1
- package/README.hi.md +1 -1
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +97 -10
- package/README.pt-BR.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/agent/export-manager.js +2 -2
- package/dist/agent/session-manager.d.ts +8 -0
- package/dist/agent/session-manager.d.ts.map +1 -1
- package/dist/agent/session-manager.js +17 -2
- package/dist/agent/session-manager.js.map +1 -1
- package/dist/assets/asset-manager.d.ts +28 -1
- package/dist/assets/asset-manager.d.ts.map +1 -1
- package/dist/assets/asset-manager.js +65 -25
- package/dist/assets/asset-manager.js.map +1 -1
- package/dist/assets/index.d.ts +1 -3
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +1 -3
- package/dist/assets/index.js.map +1 -1
- package/dist/assets/repositories/iconify.js +1 -1
- package/dist/assets/repositories/iconify.js.map +1 -1
- package/dist/assets/repositories/openclipart.js +1 -1
- package/dist/assets/repositories/openclipart.js.map +1 -1
- package/dist/assets/repositories/svgrepo.js +2 -2
- package/dist/assets/repositories/svgrepo.js.map +1 -1
- package/dist/assets/types.d.ts +2 -2
- package/dist/assets/types.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.d.ts +25 -0
- package/dist/browser/puppeteer-controller.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.js +107 -1
- package/dist/browser/puppeteer-controller.js.map +1 -1
- package/dist/build-info.json +2 -2
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/design/design-systems.d.ts +2 -0
- package/dist/design/design-systems.d.ts.map +1 -1
- package/dist/design/design-systems.js +2 -0
- package/dist/design/design-systems.js.map +1 -1
- package/dist/execution/canvas-state.d.ts.map +1 -1
- package/dist/execution/canvas-state.js +19 -43
- package/dist/execution/canvas-state.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -7
- package/dist/index.js.map +1 -1
- package/dist/ontology/design-graph.js +2 -2
- package/dist/ontology/design-graph.js.map +1 -1
- package/dist/ontology/upstream-vocabulary.d.ts +2 -2
- package/dist/ontology/upstream-vocabulary.js +2 -2
- package/dist/ontology/validator.js +1 -1
- package/dist/ontology/validator.js.map +1 -1
- package/dist/tools/compact-descriptions.d.ts.map +1 -1
- package/dist/tools/compact-descriptions.js +15 -14
- package/dist/tools/compact-descriptions.js.map +1 -1
- package/dist/tools/definitions.d.ts +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +422 -126
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/engine-lists.d.ts +17 -2
- package/dist/tools/engine-lists.d.ts.map +1 -1
- package/dist/tools/engine-lists.js +102 -2
- package/dist/tools/engine-lists.js.map +1 -1
- package/dist/tools/engine-methods.d.ts.map +1 -1
- package/dist/tools/engine-methods.js +14 -1
- package/dist/tools/engine-methods.js.map +1 -1
- package/dist/tools/engine-surface.d.ts +1 -1
- package/dist/tools/engine-surface.d.ts.map +1 -1
- package/dist/tools/engine-surface.js +49 -8
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/handlers/camera.js +8 -8
- package/dist/tools/handlers/camera.js.map +1 -1
- package/dist/tools/handlers/export.d.ts.map +1 -1
- package/dist/tools/handlers/export.js +77 -5
- package/dist/tools/handlers/export.js.map +1 -1
- package/dist/tools/handlers/font.js +10 -10
- package/dist/tools/handlers/font.js.map +1 -1
- package/dist/tools/handlers/generate.d.ts +12 -12
- package/dist/tools/handlers/maps.d.ts.map +1 -1
- package/dist/tools/handlers/maps.js +23 -3
- package/dist/tools/handlers/maps.js.map +1 -1
- package/dist/tools/handlers.d.ts +21 -0
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +240 -37
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +32 -25
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/shape-params.d.ts +2 -2
- package/dist/tools/shape-params.d.ts.map +1 -1
- package/dist/tools/shape-params.js +5 -2
- package/dist/tools/shape-params.js.map +1 -1
- package/dist/tools/stick-vocabulary.d.ts +1 -1
- package/dist/tools/stick-vocabulary.js +1 -1
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +5 -0
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +57 -3
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +1007 -253
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +1842 -890
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +229 -51
- package/dist/types/schemas.js.map +1 -1
- package/manifest.json +22 -2
- package/package.json +1 -1
package/dist/tools/handlers.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Supports browser execution via Puppeteer.
|
|
7
7
|
*/
|
|
8
8
|
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, 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,
|
|
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
10
|
// Diagram schemas
|
|
11
11
|
CreateDiagramShapeInputSchema, ConnectInputSchema, ConnectPortsInputSchema, AddPortsInputSchema, AutoLayoutInputSchema, GetDiagramShapesInputSchema, UpdateConnectorInputSchema, RemoveConnectorInputSchema, DiagramModeInputSchema,
|
|
12
12
|
// Agent flow mode schemas
|
|
@@ -27,7 +27,7 @@ ImportImageInputSchema,
|
|
|
27
27
|
// Scene management schemas
|
|
28
28
|
ManageScenesInputSchema, ScenePlaybackInputSchema, SceneGraphInputSchema, SequenceInputSchema, StaggerInputSchema, FlipInputSchema,
|
|
29
29
|
// New consolidated tool schemas
|
|
30
|
-
SelectionInputSchema, TransformInputSchema, HistoryInputSchema, ImageFilterInputSchema, LassoInputSchema, CutoutStyleInputSchema, PrecompInputSchema, BackgroundInputSchema, QueryInputSchema, QueryCapabilitiesInputSchema, DeformInputSchema, SpriteSheetInputSchema, InteractionInputSchema, ExportWidgetInputSchema, ExportWidgetHtmlInputSchema, CreateChartInputSchema, MagicInputSchema, PhysicsInputSchema, MeasurementInputSchema, ErrorCodes, } from '../types/schemas.js';
|
|
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
31
|
import { ZodError, z } from 'zod';
|
|
32
32
|
import { writeFile, mkdir, appendFile, unlink, readFile, stat } from 'node:fs/promises';
|
|
33
33
|
import { tmpdir } from 'node:os';
|
|
@@ -37,6 +37,7 @@ import { getBrowserController, resetBrowserController, } from '../browser/puppet
|
|
|
37
37
|
import { sameStudioTarget, validateStudioUrl } from '../browser/url-target.js';
|
|
38
38
|
import { getPerformanceTracker } from '../metrics/index.js';
|
|
39
39
|
import { formatErrorContext, captureCanvasState } from '../execution/index.js';
|
|
40
|
+
import { FILTER_PARAM_RANGES } from './engine-lists.js';
|
|
40
41
|
import { getSessionManager } from '../agent/session-manager.js';
|
|
41
42
|
import { vocabularyHintForPath, validateBatchVocabulary, detectBatchPropertyTypos } from '../ontology/hints.js';
|
|
42
43
|
import { cameraHandlers } from './handlers/camera.js';
|
|
@@ -560,10 +561,37 @@ export function getExecutionMode() {
|
|
|
560
561
|
// Default to 'puppeteer' for live browser execution
|
|
561
562
|
return 'puppeteer';
|
|
562
563
|
}
|
|
564
|
+
/** Where a generated script travels for a host to record, beside the agent-visible text. */
|
|
565
|
+
/** Relation types registered with register_custom_relation in this server session (gate D2). */
|
|
566
|
+
export const CUSTOM_RELATION_NAMES = new Set();
|
|
567
|
+
/**
|
|
568
|
+
* Tools whose browser work legitimately outlasts the governor's 10 s default.
|
|
569
|
+
* The first detection call downloads and initialises a model, which on a cold
|
|
570
|
+
* page took longer than 10 s and died as a timeout (gate C10, 1.6.19).
|
|
571
|
+
*/
|
|
572
|
+
const LONG_GOVERNOR_BUDGET_MS = {
|
|
573
|
+
pinepaper_detect_objects: 120_000,
|
|
574
|
+
pinepaper_extract_object: 120_000,
|
|
575
|
+
};
|
|
576
|
+
export const CODE_META_KEY = 'pinepaper.studio/code';
|
|
563
577
|
// =============================================================================
|
|
564
578
|
// RESULT HELPERS
|
|
565
579
|
// =============================================================================
|
|
566
|
-
function successResult(code, description) {
|
|
580
|
+
function successResult(code, description, deferred = false) {
|
|
581
|
+
if (deferred) {
|
|
582
|
+
return {
|
|
583
|
+
content: [{
|
|
584
|
+
type: 'text',
|
|
585
|
+
text: JSON.stringify({
|
|
586
|
+
success: true,
|
|
587
|
+
status: 'recorded',
|
|
588
|
+
step: description || 'canvas step',
|
|
589
|
+
note: 'Recorded on the scene. It runs in the studio when the scene renders or exports, not now; keep building, then render.',
|
|
590
|
+
}, null, 2),
|
|
591
|
+
}],
|
|
592
|
+
_meta: { [CODE_META_KEY]: code },
|
|
593
|
+
};
|
|
594
|
+
}
|
|
567
595
|
const content = [
|
|
568
596
|
{
|
|
569
597
|
type: 'text',
|
|
@@ -576,7 +604,7 @@ function successResult(code, description) {
|
|
|
576
604
|
text: `\n${description}`,
|
|
577
605
|
});
|
|
578
606
|
}
|
|
579
|
-
return { content };
|
|
607
|
+
return { content, _meta: { [CODE_META_KEY]: code } };
|
|
580
608
|
}
|
|
581
609
|
/**
|
|
582
610
|
* A plain data answer.
|
|
@@ -589,6 +617,18 @@ function successResult(code, description) {
|
|
|
589
617
|
function dataResult(value) {
|
|
590
618
|
return { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] };
|
|
591
619
|
}
|
|
620
|
+
/**
|
|
621
|
+
* Where a call's time went (O1, per-request trace). In the result's _meta, the
|
|
622
|
+
* spec's slot for metadata, under one namespaced key, so it adds nothing to
|
|
623
|
+
* the text a model reads. browserMs includes the screenshot when one was taken.
|
|
624
|
+
* index.ts adds toolMs, the whole call, for every tool.
|
|
625
|
+
*/
|
|
626
|
+
export const TIMING_META_KEY = 'pinepaper.studio/timing';
|
|
627
|
+
export function withTiming(out, t) {
|
|
628
|
+
const meta = (out._meta ?? {});
|
|
629
|
+
const prev = (meta[TIMING_META_KEY] ?? {});
|
|
630
|
+
return { ...out, _meta: { ...meta, [TIMING_META_KEY]: { ...prev, ...t } } };
|
|
631
|
+
}
|
|
592
632
|
/**
|
|
593
633
|
* A call that only succeeded after the browser session was recovered says so,
|
|
594
634
|
* in the result: the controller has reported `recovered` / `canvasReset` for
|
|
@@ -601,7 +641,7 @@ function withSessionNote(out, r) {
|
|
|
601
641
|
const note = r.relaunched
|
|
602
642
|
? `The browser session had been lost; the browser was relaunched and this call ran on an EMPTY canvas${r.canvasSizeRestored ? ` (size restored to ${r.canvasSizeRestored.width}x${r.canvasSizeRestored.height})` : ' at the default size'}. Item ids from before are gone: rebuild the scene.`
|
|
603
643
|
: r.canvasReset
|
|
604
|
-
? 'The page had reloaded and the canvas came back EMPTY. Item ids from before are gone: rebuild the scene.'
|
|
644
|
+
? 'The studio page had reloaded or navigated away, and the canvas came back EMPTY. Item ids from before are gone: rebuild the scene.'
|
|
605
645
|
: 'The page connection was re-established; the scene survived.';
|
|
606
646
|
const first = out.content[0];
|
|
607
647
|
if (first && first.type === 'text') {
|
|
@@ -894,6 +934,40 @@ async function brandFromUrl(url, shots, options) {
|
|
|
894
934
|
return errorResult(ErrorCodes.EXECUTION_ERROR, `could not read ${url}: ${e instanceof Error ? e.message : String(e)}`, {}, { toolName: 'pinepaper_brand_kit' });
|
|
895
935
|
}
|
|
896
936
|
}
|
|
937
|
+
/**
|
|
938
|
+
* If an executed result carries `data` (a data URL, or JSON/text), write it to
|
|
939
|
+
* a file in the export dir and replace it with filePath + bytes.
|
|
940
|
+
*/
|
|
941
|
+
async function writeResultDataToFile(out, ext) {
|
|
942
|
+
const first = out.content?.[0];
|
|
943
|
+
if (out.isError || !first || first.type !== 'text')
|
|
944
|
+
return out;
|
|
945
|
+
let obj;
|
|
946
|
+
try {
|
|
947
|
+
obj = JSON.parse(first.text);
|
|
948
|
+
}
|
|
949
|
+
catch {
|
|
950
|
+
return out;
|
|
951
|
+
}
|
|
952
|
+
const r = obj.result;
|
|
953
|
+
if (!r || r.data === undefined || r.data === null)
|
|
954
|
+
return out;
|
|
955
|
+
let bytes;
|
|
956
|
+
if (typeof r.data === 'string' && r.data.startsWith('data:'))
|
|
957
|
+
bytes = Buffer.from(r.data.slice(r.data.indexOf(',') + 1), 'base64');
|
|
958
|
+
else if (typeof r.data === 'string')
|
|
959
|
+
bytes = Buffer.from(r.data, 'utf-8');
|
|
960
|
+
else
|
|
961
|
+
bytes = Buffer.from(JSON.stringify(r.data), 'utf-8');
|
|
962
|
+
const dir = getExportDir();
|
|
963
|
+
await mkdir(dir, { recursive: true });
|
|
964
|
+
const filePath = join(dir, `pinepaper_${String(r.format ?? 'export').replace(/[^a-z0-9-]/gi, '')}_${Date.now()}.${ext}`);
|
|
965
|
+
await writeFile(filePath, bytes);
|
|
966
|
+
const { data: _d, ...rest } = r;
|
|
967
|
+
void _d;
|
|
968
|
+
obj.result = { ...rest, filePath, bytes: bytes.length };
|
|
969
|
+
return { ...out, content: [{ type: 'text', text: JSON.stringify(obj, null, 2) }, ...out.content.slice(1)] };
|
|
970
|
+
}
|
|
897
971
|
/**
|
|
898
972
|
* pinepaper_capture_frames with `sheet`: one tiled image of the film, saved
|
|
899
973
|
* to a file (an image is never useful inline, see ALWAYS_SAVE_FORMATS), with
|
|
@@ -998,7 +1072,7 @@ The code is ready to use. Each subsequent tool call will also generate code you
|
|
|
998
1072
|
timestamp: Date.now(),
|
|
999
1073
|
success: true,
|
|
1000
1074
|
});
|
|
1001
|
-
return successResult(code, description);
|
|
1075
|
+
return successResult(code, description, options.deferred === true);
|
|
1002
1076
|
}
|
|
1003
1077
|
const controller = browserController || getBrowserController();
|
|
1004
1078
|
// Auto-connect in agent mode if not connected
|
|
@@ -1081,18 +1155,29 @@ ${code}
|
|
|
1081
1155
|
// emitter is not (its verdict already describes what it left behind).
|
|
1082
1156
|
const rollbackOnFailure = toolName === 'pinepaper_execute_custom_code'
|
|
1083
1157
|
&& await markItemsBeforeRun(controller);
|
|
1084
|
-
const
|
|
1158
|
+
const governorTimeoutMs = LONG_GOVERNOR_BUDGET_MS[toolName];
|
|
1159
|
+
const result = await controller.executeCode(code, shouldTakeScreenshot, {
|
|
1160
|
+
...(stage ? { stage } : {}),
|
|
1161
|
+
...(governorTimeoutMs ? { governorTimeoutMs } : {}),
|
|
1162
|
+
});
|
|
1085
1163
|
const browserDuration = tracker.endTimer(`${timerId}_browser_execution`);
|
|
1164
|
+
// THE METRIC RECORDS THE CALL'S VERDICT, NOT THE TRANSPORT'S (gate H2,
|
|
1165
|
+
// 1.6.19). result.success only says the code RAN; an emitter that ran and
|
|
1166
|
+
// returned { success: false } is a failed call, and get_performance_metrics
|
|
1167
|
+
// reported "Success Rate: 100.0%" over exactly such a failure.
|
|
1168
|
+
const innerVerdict = result.success ? innerFailure(result.result) : null;
|
|
1086
1169
|
tracker.recordMetric({
|
|
1087
1170
|
toolName,
|
|
1088
1171
|
phase: 'browser_execution',
|
|
1089
1172
|
duration: browserDuration,
|
|
1090
1173
|
timestamp: Date.now(),
|
|
1091
|
-
success: result.success,
|
|
1092
|
-
error: result.error,
|
|
1174
|
+
success: result.success && !innerVerdict,
|
|
1175
|
+
error: result.error || innerVerdict || undefined,
|
|
1093
1176
|
});
|
|
1177
|
+
let screenshotMs;
|
|
1094
1178
|
if (shouldTakeScreenshot) {
|
|
1095
1179
|
const screenshotDuration = tracker.endTimer(`${timerId}_screenshot`);
|
|
1180
|
+
screenshotMs = screenshotDuration;
|
|
1096
1181
|
tracker.recordMetric({
|
|
1097
1182
|
toolName,
|
|
1098
1183
|
phase: 'screenshot',
|
|
@@ -1108,13 +1193,14 @@ ${code}
|
|
|
1108
1193
|
phase: 'total',
|
|
1109
1194
|
duration: totalDuration,
|
|
1110
1195
|
timestamp: Date.now(),
|
|
1111
|
-
success: result.success,
|
|
1112
|
-
error: result.error,
|
|
1196
|
+
success: result.success && !innerVerdict,
|
|
1197
|
+
error: result.error || innerVerdict || undefined,
|
|
1113
1198
|
});
|
|
1199
|
+
const timing = { browserMs: Math.round(browserDuration), ...(screenshotMs !== undefined ? { screenshotMs: Math.round(screenshotMs) } : {}) };
|
|
1114
1200
|
if (!result.success) {
|
|
1115
1201
|
// Capture canvas state for error context
|
|
1116
1202
|
const canvasState = await captureCanvasState(controller);
|
|
1117
|
-
return errorResult(ErrorCodes.EXECUTION_ERROR, result.error || 'Failed to execute code in browser',
|
|
1203
|
+
return withTiming(errorResult(ErrorCodes.EXECUTION_ERROR, result.error || 'Failed to execute code in browser',
|
|
1118
1204
|
// errorCode surfaces the governor's structured code (PP_ITEM_BUDGET,
|
|
1119
1205
|
// PP_LOOP_BUDGET, PP_TIMEOUT) so the model can correct on the next turn.
|
|
1120
1206
|
{
|
|
@@ -1123,7 +1209,7 @@ ${code}
|
|
|
1123
1209
|
}, {
|
|
1124
1210
|
toolName,
|
|
1125
1211
|
canvasState: canvasState || undefined,
|
|
1126
|
-
});
|
|
1212
|
+
}), timing);
|
|
1127
1213
|
}
|
|
1128
1214
|
// TWO VERDICTS. `result.success` says the CODE RAN. The emitted code has its
|
|
1129
1215
|
// own verdict, and almost every emitter in this repo returns
|
|
@@ -1137,15 +1223,24 @@ ${code}
|
|
|
1137
1223
|
// outer success and the agent kept building on a rig that did not exist.
|
|
1138
1224
|
// pinepaper_agent_export got this check in 1.6.9; it belongs HERE, where
|
|
1139
1225
|
// every tool inherits it.
|
|
1140
|
-
const inner =
|
|
1226
|
+
const inner = innerVerdict;
|
|
1141
1227
|
if (inner) {
|
|
1142
1228
|
const canvasState = await captureCanvasState(controller);
|
|
1143
|
-
return errorResult(ErrorCodes.EXECUTION_ERROR, inner, {
|
|
1229
|
+
return withTiming(errorResult(ErrorCodes.EXECUTION_ERROR, inner, {
|
|
1144
1230
|
code, result: result.result, governorReport: result.report,
|
|
1145
1231
|
...(rollbackOnFailure ? await customCodeRollback(controller, inner) : {}),
|
|
1146
|
-
}, { toolName, canvasState: canvasState || undefined });
|
|
1232
|
+
}, { toolName, canvasState: canvasState || undefined }), timing);
|
|
1147
1233
|
}
|
|
1148
|
-
|
|
1234
|
+
// Session item counts come from the governor's own count of what this call
|
|
1235
|
+
// created (gate H3: the session stats read 0 with items on the canvas).
|
|
1236
|
+
const created = result.report?.items?.created;
|
|
1237
|
+
if (typeof created === 'number' && created > 0) {
|
|
1238
|
+
try {
|
|
1239
|
+
getSessionManager().noteItemsCreated(created);
|
|
1240
|
+
}
|
|
1241
|
+
catch { /* no session */ }
|
|
1242
|
+
}
|
|
1243
|
+
return withTiming(withSessionNote(executedResult(code, result.result, result.screenshot, description, result.report), result), timing);
|
|
1149
1244
|
}
|
|
1150
1245
|
/**
|
|
1151
1246
|
* The emitted code's own verdict, when it reported one and it was a failure.
|
|
@@ -1703,6 +1798,28 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
1703
1798
|
}
|
|
1704
1799
|
case 'pinepaper_validate_scene': {
|
|
1705
1800
|
const input = ValidateSceneInputSchema.parse(args);
|
|
1801
|
+
// The validator reads { from, to, relation } on an addRelation op; the
|
|
1802
|
+
// add_relation tool's own names (source, target, relationType) are what
|
|
1803
|
+
// an agent reaches for, and the tool's example used them, so every
|
|
1804
|
+
// example op failed SOURCE_NOT_FOUND "undefined" (gate D8, 1.6.19).
|
|
1805
|
+
if (Array.isArray(input.ops)) {
|
|
1806
|
+
input.ops = input.ops.map((op) => {
|
|
1807
|
+
const o = { ...op };
|
|
1808
|
+
if (o.from === undefined && o.source !== undefined) {
|
|
1809
|
+
o.from = o.source;
|
|
1810
|
+
delete o.source;
|
|
1811
|
+
}
|
|
1812
|
+
if (o.to === undefined && o.target !== undefined) {
|
|
1813
|
+
o.to = o.target;
|
|
1814
|
+
delete o.target;
|
|
1815
|
+
}
|
|
1816
|
+
if (o.relation === undefined && o.relationType !== undefined) {
|
|
1817
|
+
o.relation = o.relationType;
|
|
1818
|
+
delete o.relationType;
|
|
1819
|
+
}
|
|
1820
|
+
return o;
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1706
1823
|
const code = codeGenerator.generateValidateScene(input);
|
|
1707
1824
|
const description = input.ops ? `Pre-validate ${input.ops.length} proposed op(s)` : 'Audit the live scene';
|
|
1708
1825
|
return executeOrGenerate(code, description, options, 'pinepaper_validate_scene');
|
|
@@ -1770,6 +1887,23 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
1770
1887
|
return executeOrGenerate(code, `Text effect: ${input.action}`, options, 'pinepaper_text_effect');
|
|
1771
1888
|
}
|
|
1772
1889
|
// ─── 1.6.4: agent surface for the Tier-2 engine features ───
|
|
1890
|
+
case 'pinepaper_relight': {
|
|
1891
|
+
const input = RelightInputSchema.parse(args);
|
|
1892
|
+
return executeOrGenerate(codeGenerator.generateRelight(input), `Relight: ${input.action}`, options, 'pinepaper_relight');
|
|
1893
|
+
}
|
|
1894
|
+
case 'pinepaper_shader_graph': {
|
|
1895
|
+
const input = ShaderGraphInputSchema.parse(args);
|
|
1896
|
+
return executeOrGenerate(codeGenerator.generateShaderGraph(input), `Shader graph: ${input.action}`, options, 'pinepaper_shader_graph');
|
|
1897
|
+
}
|
|
1898
|
+
case 'pinepaper_styled_scene': {
|
|
1899
|
+
const input = StyledSceneInputSchema.parse(args);
|
|
1900
|
+
return executeOrGenerate(codeGenerator.generateStyledScene(input), `Styled scene${input.style ? `: ${input.style}` : ''}`, options, 'pinepaper_styled_scene');
|
|
1901
|
+
}
|
|
1902
|
+
case 'pinepaper_place_on_surface': {
|
|
1903
|
+
const input = PlaceOnSurfaceInputSchema.parse(args);
|
|
1904
|
+
const code = codeGenerator.generatePlaceOnSurface(input);
|
|
1905
|
+
return executeOrGenerate(code, 'Place a UI onto a screen in a photo', options, 'pinepaper_place_on_surface');
|
|
1906
|
+
}
|
|
1773
1907
|
case 'pinepaper_brand_kit': {
|
|
1774
1908
|
const input = BrandKitInputSchema.parse(args);
|
|
1775
1909
|
if (input.action === 'from_url')
|
|
@@ -2031,8 +2165,17 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
2031
2165
|
// RELATION TOOLS
|
|
2032
2166
|
// -----------------------------------------------------------------------
|
|
2033
2167
|
case 'pinepaper_add_relation': {
|
|
2034
|
-
|
|
2035
|
-
|
|
2168
|
+
// A relation registered with register_custom_relation this session is
|
|
2169
|
+
// a valid type here (gate D2, 1.6.19: the enum refused the name just
|
|
2170
|
+
// registered). Only those names; an unknown one is still refused.
|
|
2171
|
+
const rt = args.relationType;
|
|
2172
|
+
// The schema's refinements check presence, not which type, so a custom
|
|
2173
|
+
// name is validated with a built-in stand-in and then put back.
|
|
2174
|
+
const isCustom = typeof rt === 'string' && CUSTOM_RELATION_NAMES.has(rt);
|
|
2175
|
+
const input = AddRelationInputSchema.parse(isCustom ? { ...args, relationType: 'orbits' } : args);
|
|
2176
|
+
if (isCustom)
|
|
2177
|
+
input.relationType = rt;
|
|
2178
|
+
const code = codeGenerator.generateAddRelation(input, isCustom);
|
|
2036
2179
|
const description = getLocalizedSuccessMessage(i18n, 'relationAdded', {
|
|
2037
2180
|
relationType: input.relationType ?? `preset:${input.presetId}`,
|
|
2038
2181
|
sourceId: input.sourceId,
|
|
@@ -2056,6 +2199,7 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
2056
2199
|
}
|
|
2057
2200
|
case 'pinepaper_register_custom_relation': {
|
|
2058
2201
|
const input = RegisterCustomRelationInputSchema.parse(args);
|
|
2202
|
+
CUSTOM_RELATION_NAMES.add(input.name);
|
|
2059
2203
|
const code = codeGenerator.generateRegisterCustomRelation(input);
|
|
2060
2204
|
return executeOrGenerate(code, `Registers custom relation type: ${input.name}`, options, 'pinepaper_register_custom_relation');
|
|
2061
2205
|
}
|
|
@@ -2197,10 +2341,9 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
2197
2341
|
case 'pinepaper_set_canvas_size': {
|
|
2198
2342
|
const input = SetCanvasSizeInputSchema.parse(args);
|
|
2199
2343
|
const code = codeGenerator.generateSetCanvasSize(input);
|
|
2200
|
-
const description =
|
|
2201
|
-
|
|
2202
|
-
height: input.height
|
|
2203
|
-
});
|
|
2344
|
+
const description = input.preset && input.width === undefined
|
|
2345
|
+
? `Set canvas size to preset ${input.preset}`
|
|
2346
|
+
: getLocalizedSuccessMessage(i18n, 'canvasSizeSet', { width: input.width ?? '', height: input.height ?? '' });
|
|
2204
2347
|
return executeOrGenerate(code, description, options, 'pinepaper_set_canvas_size');
|
|
2205
2348
|
}
|
|
2206
2349
|
case 'pinepaper_get_canvas_size': {
|
|
@@ -2306,6 +2449,15 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
2306
2449
|
// -----------------------------------------------------------------------
|
|
2307
2450
|
case 'pinepaper_add_filter': {
|
|
2308
2451
|
const input = AddFilterInputSchema.parse(args);
|
|
2452
|
+
// Out-of-range parameters are refused with the engine's own range
|
|
2453
|
+
// (gate A, 1.6.19: dither levels 99 was accepted and quietly clamped).
|
|
2454
|
+
const ranges = FILTER_PARAM_RANGES[input.filterType] ?? {};
|
|
2455
|
+
for (const [k, v] of Object.entries((input.params ?? {}))) {
|
|
2456
|
+
const r = ranges[k];
|
|
2457
|
+
if (r && typeof v === 'number' && (v < r[0] || v > r[1])) {
|
|
2458
|
+
return errorResult(ErrorCodes.VALIDATION_ERROR, `${input.filterType} ${k} must be ${r[0]}..${r[1]} (got ${v}).`, { filterType: input.filterType, param: k, range: r });
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2309
2461
|
const code = codeGenerator.generateAddFilter(input.filterType, input.params);
|
|
2310
2462
|
const description = `Adds ${input.filterType} filter to the canvas`;
|
|
2311
2463
|
return executeOrGenerate(code, description, options, 'pinepaper_add_filter');
|
|
@@ -2516,7 +2668,19 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
2516
2668
|
const perfTracker = getPerformanceTracker();
|
|
2517
2669
|
const recentMetrics = perfTracker.exportMetrics('json', { limit: metricsLimit });
|
|
2518
2670
|
const stats = perfTracker.getStats();
|
|
2519
|
-
|
|
2671
|
+
// exportMetrics('json') returns a JSON STRING; embedded as-is it reached
|
|
2672
|
+
// the report as one escaped string, not an array (gate H3).
|
|
2673
|
+
let entries = recentMetrics;
|
|
2674
|
+
if (typeof recentMetrics === 'string') {
|
|
2675
|
+
try {
|
|
2676
|
+
entries = JSON.parse(recentMetrics);
|
|
2677
|
+
}
|
|
2678
|
+
catch { /* keep the string */ }
|
|
2679
|
+
}
|
|
2680
|
+
if (entries && typeof entries === 'object' && !Array.isArray(entries) && Array.isArray(entries.metrics)) {
|
|
2681
|
+
entries = entries.metrics;
|
|
2682
|
+
}
|
|
2683
|
+
metricsSection = { entries, stats };
|
|
2520
2684
|
}
|
|
2521
2685
|
catch { /* metrics not available */ }
|
|
2522
2686
|
}
|
|
@@ -2571,17 +2735,25 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
2571
2735
|
},
|
|
2572
2736
|
recommendations: [],
|
|
2573
2737
|
};
|
|
2574
|
-
//
|
|
2738
|
+
// RECOMMEND ONLY WHAT SAVES (gate H4, 1.6.19). This said "switch to
|
|
2739
|
+
// agent" on full, which saves under 1% (agent omits three small
|
|
2740
|
+
// tools). Each alternative is measured against the active set and
|
|
2741
|
+
// offered only when it saves at least 10%, with the saving stated.
|
|
2575
2742
|
const recs = toolDefsSection.recommendations;
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2743
|
+
const alternatives = [];
|
|
2744
|
+
for (const v of ['compact', 'minimal']) {
|
|
2745
|
+
for (const k of ['full', 'agent', 'minimal']) {
|
|
2746
|
+
if (k === toolkit && v === verbosity)
|
|
2747
|
+
continue;
|
|
2748
|
+
const t = Math.ceil(Buffer.byteLength(JSON.stringify(getToolsForToolkit(getToolsForVerbosity(v), k)), 'utf-8') / 4);
|
|
2749
|
+
alternatives.push({ label: `${k}+${v}`, how: `PINEPAPER_TOOLKIT=${k} PINEPAPER_VERBOSITY=${v}`, tokens: t });
|
|
2750
|
+
}
|
|
2584
2751
|
}
|
|
2752
|
+
alternatives
|
|
2753
|
+
.filter((alt) => alt.tokens <= activeTokens * 0.9)
|
|
2754
|
+
.sort((x, y) => x.tokens - y.tokens)
|
|
2755
|
+
.slice(0, 3)
|
|
2756
|
+
.forEach((alt) => recs.push(`${alt.how}: ~${alt.tokens.toLocaleString()} tokens/turn (${Math.round((1 - alt.tokens / activeTokens) * 100)}% less)${alt.label.startsWith('minimal+') ? ' — a small core set; switch profiles at runtime with pinepaper_set_toolkit' : ''}`));
|
|
2585
2757
|
if (activeTokens > 20000) {
|
|
2586
2758
|
recs.push(`Tool definitions alone consume ~${activeTokens.toLocaleString()} tokens per API turn — this is a major context budget item`);
|
|
2587
2759
|
}
|
|
@@ -2700,11 +2872,19 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
2700
2872
|
// ASSET TOOLS
|
|
2701
2873
|
// -----------------------------------------------------------------------
|
|
2702
2874
|
case 'pinepaper_search_assets': {
|
|
2875
|
+
// Asked for by name, a removed repository says why rather than failing
|
|
2876
|
+
// the enum check with a bare "invalid option".
|
|
2877
|
+
{
|
|
2878
|
+
const { REMOVED_REPOSITORIES, REMOVED_REPOSITORY_REASON } = await import('../assets/index.js');
|
|
2879
|
+
if (typeof args.repository === 'string' && REMOVED_REPOSITORIES.includes(args.repository)) {
|
|
2880
|
+
return errorResult(ErrorCodes.INVALID_PARAMS, `${REMOVED_REPOSITORY_REASON} Search repository 'all', 'iconify' or 'fontawesome'.`, { repository: args.repository }, { toolName: 'pinepaper_search_assets' });
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2703
2883
|
const input = SearchAssetsInputSchema.parse(args);
|
|
2704
2884
|
const { getAssetManager } = await import('../assets/index.js');
|
|
2705
2885
|
const assetManager = getAssetManager();
|
|
2706
2886
|
try {
|
|
2707
|
-
const results = await assetManager.
|
|
2887
|
+
const { results, sources } = await assetManager.searchWithStatus(input.query, input.repository || 'all', input.limit || 10);
|
|
2708
2888
|
// If includeSvgContent is true, fetch SVG content for each result
|
|
2709
2889
|
let resultsWithContent = [];
|
|
2710
2890
|
if (input.includeSvgContent) {
|
|
@@ -2776,6 +2956,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
2776
2956
|
query: input.query,
|
|
2777
2957
|
repository: input.repository || 'all',
|
|
2778
2958
|
count: resultsWithContent.length,
|
|
2959
|
+
sources,
|
|
2779
2960
|
includeSvgContent: input.includeSvgContent || false,
|
|
2780
2961
|
attributionRequired: requiresAttribution.length > 0,
|
|
2781
2962
|
attributionNotice,
|
|
@@ -2836,6 +3017,15 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
2836
3017
|
svg = body;
|
|
2837
3018
|
metadata = { source: 'url', url: input.url };
|
|
2838
3019
|
}
|
|
3020
|
+
// `color` was accepted and never applied, so a recoloured icon
|
|
3021
|
+
// rendered black (gate D15, 1.6.19). A monochrome icon draws in
|
|
3022
|
+
// currentColor (stroke sets) or in the default fill (fill sets):
|
|
3023
|
+
// both take the colour.
|
|
3024
|
+
if (input.color) {
|
|
3025
|
+
const c = input.color.replace(/"/g, '');
|
|
3026
|
+
svg = svg.replace(/currentColor/g, c);
|
|
3027
|
+
svg = svg.replace(/<svg\b([^>]*)>/i, (m, attrs) => (/\sfill\s*=/.test(attrs) ? m : `<svg${attrs} fill="${c}">`));
|
|
3028
|
+
}
|
|
2839
3029
|
// Import the SVG onto canvas using existing import_svg tool
|
|
2840
3030
|
const code = codeGenerator.generateImportSVG(svg, undefined, input.position, input.scale);
|
|
2841
3031
|
// A URL import has no catalogue metadata (no title, no licence): this
|
|
@@ -3366,10 +3556,18 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
3366
3556
|
`Call action 'list_styles' and use one marked composable.`, { style: input.style }, { toolName: 'pinepaper_design_system' });
|
|
3367
3557
|
}
|
|
3368
3558
|
const ops = designSystems.sceneToOps(scene);
|
|
3559
|
+
// Some vendored styles lay out at their OWN fixed size and ignore
|
|
3560
|
+
// width/height (swiss_typographic is always 1280x720), so a caller
|
|
3561
|
+
// asking for 1080x1080 got a resized canvas (gate run 3). Say so.
|
|
3562
|
+
const sizeIgnored = (input.width !== undefined && input.width !== scene.width)
|
|
3563
|
+
|| (input.height !== undefined && input.height !== scene.height);
|
|
3564
|
+
const sizeWarning = sizeIgnored
|
|
3565
|
+
? `"${scene.style}" lays out at its own size, ${scene.width}x${scene.height}; the requested ${input.width ?? '?'}x${input.height ?? '?'} was not used`
|
|
3566
|
+
: undefined;
|
|
3369
3567
|
if (input.draw === false) {
|
|
3370
|
-
return dataResult({ scene, ops, drawn: false });
|
|
3568
|
+
return dataResult({ scene, ops, drawn: false, ...(sizeWarning ? { warning: sizeWarning } : {}) });
|
|
3371
3569
|
}
|
|
3372
|
-
const code = codeGenerator.generateDesignCompose(scene, ops, scene.style);
|
|
3570
|
+
const code = codeGenerator.generateDesignCompose(scene, ops, scene.style, sizeWarning);
|
|
3373
3571
|
return executeOrGenerate(code, `Composed a ${scene.style} scene (${ops.length} items)`, options, 'pinepaper_design_system');
|
|
3374
3572
|
}
|
|
3375
3573
|
}
|
|
@@ -3387,7 +3585,12 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
3387
3585
|
case 'pinepaper_interchange': {
|
|
3388
3586
|
const input = InterchangeInputSchema.parse(args);
|
|
3389
3587
|
const code = codeGenerator.generateInterchange(input);
|
|
3390
|
-
|
|
3588
|
+
const out = await executeOrGenerate(code, `Interchange: ${input.action}`, options, 'pinepaper_interchange');
|
|
3589
|
+
// AN EXPORT IS A FILE (gate D7, 1.6.19): .lottie, GLB and the PNG-sequence
|
|
3590
|
+
// zip came back as byte-index objects or inline data URLs, and a Lottie
|
|
3591
|
+
// JSON can be large. Written out; the result names the path.
|
|
3592
|
+
const ext = { export_lottie: 'json', export_dotlottie: 'lottie', export_glb: 'glb', export_png_sequence: 'zip', export_bvh: 'bvh' };
|
|
3593
|
+
return ext[input.action] ? await writeResultDataToFile(out, ext[input.action]) : out;
|
|
3391
3594
|
}
|
|
3392
3595
|
case 'pinepaper_sound': {
|
|
3393
3596
|
const input = SoundInputSchema.parse(args);
|