@pinepaper.studio/mcp-server 1.6.35 → 1.6.37
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 +139 -7
- package/README.pt-BR.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/agent/types.d.ts +12 -12
- package/dist/browser/puppeteer-controller.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.js +7 -1
- package/dist/browser/puppeteer-controller.js.map +1 -1
- package/dist/build-info.json +2 -2
- package/dist/ontology/upstream-vocabulary.d.ts +2 -2
- package/dist/ontology/upstream-vocabulary.js +2 -2
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +141 -91
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/effect-class.d.ts.map +1 -1
- package/dist/tools/effect-class.js +4 -0
- package/dist/tools/effect-class.js.map +1 -1
- package/dist/tools/engine-lists.d.ts +30 -3
- package/dist/tools/engine-lists.d.ts.map +1 -1
- package/dist/tools/engine-lists.js +346 -4
- 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 +4 -0
- 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 +29 -7
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/export-file-receipt.d.ts +33 -0
- package/dist/tools/export-file-receipt.d.ts.map +1 -1
- package/dist/tools/export-file-receipt.js +49 -0
- package/dist/tools/export-file-receipt.js.map +1 -1
- package/dist/tools/frozen-output.d.ts +33 -0
- package/dist/tools/frozen-output.d.ts.map +1 -0
- package/dist/tools/frozen-output.js +73 -0
- package/dist/tools/frozen-output.js.map +1 -0
- package/dist/tools/handlers.d.ts +15 -0
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +141 -15
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +1 -0
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/shape-params.d.ts +2 -2
- package/dist/tools/shape-params.js +2 -2
- 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 +2 -1
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +18 -2
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +462 -198
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +112 -270
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +46 -145
- package/dist/types/schemas.js.map +1 -1
- package/dist/utils/asset-paths.js +1 -1
- package/dist/utils/asset-paths.js.map +1 -1
- package/manifest.json +5 -1
- package/package.json +1 -1
package/dist/tools/handlers.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Supports i18n for localized error and success messages.
|
|
6
6
|
* Supports browser execution via Puppeteer.
|
|
7
7
|
*/
|
|
8
|
+
import { SCENE_MOTION_PROBE_JS, frozenOutputCheck } from './frozen-output.js';
|
|
8
9
|
import { dropPrivateRelationParams, normalizeBatchOperationNames, renamedArgumentsNote, RENAMED_ARGUMENTS_META_KEY } from './batch-op-names.js';
|
|
9
10
|
import { nestedIgnoredPaths } from './nested-ignored.js';
|
|
10
11
|
import { CRAFT_FRAME_JS, generateDrawWeave, generateDrawKnots, generateDrawSashiko, generateDrawSubdivision, generateUiKit, uiKitCallProblems, } from './craft-tools.js';
|
|
@@ -12,7 +13,7 @@ import { exportPerScene } from './scene-cache-export.js';
|
|
|
12
13
|
import { boundedStructured, reportedIgnoredPaths } from './reply-fields.js';
|
|
13
14
|
import { codeGenerator, generateQueryMutationsCode, SHEET_RUBRIC } from '../types/code-generator.js';
|
|
14
15
|
import { expandBatchMacros, BatchMacroError, batchPreflightView, withKeyColumns } from './batch-macros.js';
|
|
15
|
-
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,
|
|
16
|
+
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, InstancesInputSchema, DesignSystemInputSchema, StoryInputSchema, ChromaKeyInputSchema, RiggingInputSchema, GroupInputSchema, CameraDirectorInputSchema, DetectObjectsInputSchema, ExtractObjectInputSchema, ArrangeInputSchema, CreateSceneInputSchema, GetPerformanceMetricsInputSchema, DiagnosticReportInputSchema, SearchAssetsInputSchema, ImportAssetInputSchema, P5DrawInputSchema,
|
|
16
17
|
// Diagram schemas
|
|
17
18
|
CreateDiagramShapeInputSchema, ConnectInputSchema, ConnectPortsInputSchema, AddPortsInputSchema, AutoLayoutInputSchema, GetDiagramShapesInputSchema, UpdateConnectorInputSchema, RemoveConnectorInputSchema, DiagramModeInputSchema,
|
|
18
19
|
// Agent flow mode schemas
|
|
@@ -38,7 +39,7 @@ import { ZodError, z } from 'zod';
|
|
|
38
39
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
39
40
|
import { effectClass } from './effect-class.js';
|
|
40
41
|
import { capabilityAllowed, capabilityRefusal, CAPABILITY_SWITCH } from '../utils/capabilities.js';
|
|
41
|
-
import { exportFileReceiptOrReason, receiptErrorLine } from './export-file-receipt.js';
|
|
42
|
+
import { exportFileReceiptOrReason, receiptErrorLine, deliverAacIfOpus } from './export-file-receipt.js';
|
|
42
43
|
import { ExportStepError, errorText, verifySavedFile, writeStep, bytesForSave } from './export-file-check.js';
|
|
43
44
|
import { EFFECT_BEFORE_JS, effectAfterJs, namedItemIds, idMayBeGone, NO_CHANGE_NOTE } from './effect-check.js';
|
|
44
45
|
import { writeFile, mkdir, appendFile, unlink, readFile, stat } from 'node:fs/promises';
|
|
@@ -746,6 +747,21 @@ export function withEngineReport(result, report) {
|
|
|
746
747
|
_meta: { ...(result._meta ?? {}), [RECEIPTS_META_KEY]: body },
|
|
747
748
|
};
|
|
748
749
|
}
|
|
750
|
+
/** B45 option D: the delivered file's audio, in the result and its words. */
|
|
751
|
+
function audioDeliveryFields(d) {
|
|
752
|
+
if (d.action === 'transcoded')
|
|
753
|
+
return { audioDelivered: { ...d.audio, preRemux: d.preRemux, note: 'file.sha256 is the delivered file. The studio file hash, bitExact and provenance describe the bytes before the audio was re-encoded; its frameHashes still describe the video, which was copied unchanged.' } };
|
|
754
|
+
if (d.action === 'kept-opus')
|
|
755
|
+
return { audioNote: d.note };
|
|
756
|
+
return {};
|
|
757
|
+
}
|
|
758
|
+
function audioDeliveryLine(d) {
|
|
759
|
+
if (d.action === 'transcoded')
|
|
760
|
+
return `Audio: re-encoded from Opus to AAC (${d.audio.bitrate}); the video is copied unchanged, and the file has a new sha256.\n`;
|
|
761
|
+
if (d.action === 'kept-opus')
|
|
762
|
+
return `Audio: ${d.note}\n`;
|
|
763
|
+
return '';
|
|
764
|
+
}
|
|
749
765
|
function executedResult(code, result, screenshot, description, report) {
|
|
750
766
|
const resultObj = { success: true, result };
|
|
751
767
|
if (description)
|
|
@@ -851,6 +867,43 @@ function redactGeneratedCode(details) {
|
|
|
851
867
|
* The studio's scene-lint warnings on an export (FxTool lintScene), one line each, so a centred title that runs
|
|
852
868
|
* out of the frame ("IGNAL" for "SIGNAL") is read on the success path rather than left in the JSON. Empty when none.
|
|
853
869
|
*/
|
|
870
|
+
/** Put a frozen-output finding on the result: in `warnings`, in the `warning` line the reply prints, and as `frozenOutput`. */
|
|
871
|
+
export function noteFrozenOutput(exportResult, sceneMotion) {
|
|
872
|
+
if (!exportResult || typeof exportResult !== 'object')
|
|
873
|
+
return;
|
|
874
|
+
const f = frozenOutputCheck(exportResult, sceneMotion);
|
|
875
|
+
if (!f)
|
|
876
|
+
return;
|
|
877
|
+
const r = exportResult;
|
|
878
|
+
r.frozenOutput = { frozenAt: f.frozenAt, exportEnd: f.exportEnd, itemId: f.track.id, trackUntil: f.track.until >= 1e8 ? 'loops' : f.track.until };
|
|
879
|
+
if (Array.isArray(r.warnings))
|
|
880
|
+
r.warnings.push(f.warning);
|
|
881
|
+
else
|
|
882
|
+
r.warnings = [f.warning];
|
|
883
|
+
r.warning = typeof r.warning === 'string' && r.warning ? r.warning + ' ' + f.warning : f.warning;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* A CLAIM THE NUMBERS NEXT TO IT CONTRADICT (tester, 1.6.36). The studio sets video.bitrateCeilingHeld: true
|
|
887
|
+
* whenever the file is within 10% OVER the target, and warns only past that, so a file 7% over its ceiling was
|
|
888
|
+
* reported as having held it. The flag is set from the file's own achievedBitrate and the target beside it.
|
|
889
|
+
*/
|
|
890
|
+
export function correctBitrateClaim(exportResult) {
|
|
891
|
+
const r = exportResult;
|
|
892
|
+
const v = r && typeof r === 'object' ? r.video : null;
|
|
893
|
+
if (!v || typeof v !== 'object' || v.bitrateCeilingHeld !== true)
|
|
894
|
+
return;
|
|
895
|
+
const target = Number(v.bitrate), achieved = Number(v.achievedBitrate);
|
|
896
|
+
if (!(target > 0) || !(achieved > target))
|
|
897
|
+
return;
|
|
898
|
+
const over = Math.round((achieved / target - 1) * 1000) / 10;
|
|
899
|
+
v.bitrateCeilingHeld = false;
|
|
900
|
+
v.bitrateOverPercent = over;
|
|
901
|
+
const msg = `bitrate_exceeded: the ${(target / 1e6).toFixed(2)} Mbps target was not held: the file is ${(achieved / 1e6).toFixed(2)} Mbps (${over}% over). The browser encoder treats the target loosely on content it cannot compress; pass a lower bitrate, or bitrateMode 'constant', which held closer in measurement.`;
|
|
902
|
+
if (Array.isArray(r.warnings))
|
|
903
|
+
r.warnings.push(msg);
|
|
904
|
+
else
|
|
905
|
+
r.warnings = [msg];
|
|
906
|
+
}
|
|
854
907
|
export function lintLines(exportResult) {
|
|
855
908
|
const w = exportResult?.lintWarnings;
|
|
856
909
|
if (!Array.isArray(w) || !w.length)
|
|
@@ -1262,6 +1315,34 @@ async function craftFrame(options) {
|
|
|
1262
1315
|
return undefined;
|
|
1263
1316
|
}
|
|
1264
1317
|
}
|
|
1318
|
+
/**
|
|
1319
|
+
* B136: Chromium draws a raster's smoothing 'low', 'medium' and 'high' the same, so a call asking for 'medium' or
|
|
1320
|
+
* 'high' is applied and looks like 'low'. Said in the reply's JSON, since the docs alone left it silent. Any
|
|
1321
|
+
* `smoothing` set to one of those words, at any depth (import, modify, create, batch ops); numeric smoothing
|
|
1322
|
+
* (relations, chroma key) is another parameter and is left alone.
|
|
1323
|
+
*/
|
|
1324
|
+
export function smoothingLevelField(args) {
|
|
1325
|
+
const seen = new Set();
|
|
1326
|
+
const walk = (v, depth) => {
|
|
1327
|
+
if (depth > 6 || !v || typeof v !== 'object')
|
|
1328
|
+
return;
|
|
1329
|
+
if (Array.isArray(v)) {
|
|
1330
|
+
v.forEach((x) => walk(x, depth + 1));
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
for (const [k, x] of Object.entries(v)) {
|
|
1334
|
+
if (k === 'smoothing' && (x === 'medium' || x === 'high'))
|
|
1335
|
+
seen.add(x);
|
|
1336
|
+
else
|
|
1337
|
+
walk(x, depth + 1);
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
walk(args, 0);
|
|
1341
|
+
if (!seen.size)
|
|
1342
|
+
return {};
|
|
1343
|
+
const asked = [...seen].map((x) => `'${x}'`).join(' and ');
|
|
1344
|
+
return { smoothingNote: `smoothing ${asked} was applied, but Chromium draws 'low', 'medium' and 'high' the same, so it looks like 'low'. The visible choice is 'off' (nearest-neighbour, hard pixel edges) or not.` };
|
|
1345
|
+
}
|
|
1265
1346
|
export async function executeOrGenerate(code, description, options, toolName,
|
|
1266
1347
|
/**
|
|
1267
1348
|
* Values too large to live inside the generated code. Written to
|
|
@@ -2010,6 +2091,7 @@ export async function handleToolCall(toolName, args, options = {}) {
|
|
|
2010
2091
|
...(dropped.length ? { droppedArguments: dropped } : {}),
|
|
2011
2092
|
...(ignoredPaths.length ? { ignoredArguments: ignoredPaths } : {}),
|
|
2012
2093
|
...await renderingBuildField(toolName, args, result, options),
|
|
2094
|
+
...smoothingLevelField(args),
|
|
2013
2095
|
});
|
|
2014
2096
|
if (!folded) {
|
|
2015
2097
|
const lines = [...(renamed.length ? [renamedArgumentsNote(renamed)] : []), ...(dropped.length ? [`Not sent to the studio, as its private state: ${dropped.join(', ')}.`] : [])];
|
|
@@ -2552,6 +2634,10 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
2552
2634
|
const input = MorphSequenceInputSchema.parse(args);
|
|
2553
2635
|
return executeOrGenerate(codeGenerator.generateMorphSequence(input), `Morph sequence: ${input.states.length} states`, options, 'pinepaper_morph_sequence');
|
|
2554
2636
|
}
|
|
2637
|
+
case 'pinepaper_instances': {
|
|
2638
|
+
const input = InstancesInputSchema.parse(args);
|
|
2639
|
+
return executeOrGenerate(codeGenerator.generateInstances(input), `Instances: ${input.action}`, options, 'pinepaper_instances');
|
|
2640
|
+
}
|
|
2555
2641
|
case 'pinepaper_emitter': {
|
|
2556
2642
|
const input = EmitterInputSchema.parse(args);
|
|
2557
2643
|
return executeOrGenerate(codeGenerator.generateEmitter(input), `Emitter: ${input.action}`, options, 'pinepaper_emitter');
|
|
@@ -4222,6 +4308,11 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
4222
4308
|
if (!exportSessionManager.hasActiveJob()) {
|
|
4223
4309
|
exportSessionManager.startJob({ name: 'auto_session', screenshotPolicy: 'on_complete' });
|
|
4224
4310
|
}
|
|
4311
|
+
// The scene's motion, read before a video export so the delivered frames can be checked against it
|
|
4312
|
+
// (frozen-output.ts). A probe that cannot run gives no verdict, never a false one.
|
|
4313
|
+
const sceneMotion = /^(mp4|webm|auto|)$/.test(String(input.format ?? ''))
|
|
4314
|
+
? await controller.executeCode(SCENE_MOTION_PROBE_JS, false).then((r) => (r.success ? r.result : null)).catch(() => null)
|
|
4315
|
+
: null;
|
|
4225
4316
|
const exportBrowserResult = await controller.executeCode(code, false, {
|
|
4226
4317
|
governorTimeoutMs: exportGovernorTimeoutMs(),
|
|
4227
4318
|
});
|
|
@@ -4230,6 +4321,8 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
4230
4321
|
return errorResult(ErrorCodes.EXECUTION_ERROR, exportBrowserResult.error || 'Export failed', { code }, { toolName: 'pinepaper_agent_export', canvasState: canvasState || undefined });
|
|
4231
4322
|
}
|
|
4232
4323
|
const exportResult = exportBrowserResult.result;
|
|
4324
|
+
correctBitrateClaim(exportResult);
|
|
4325
|
+
noteFrozenOutput(exportResult, sceneMotion);
|
|
4233
4326
|
// TWO VERDICTS, and only one of them was being read.
|
|
4234
4327
|
//
|
|
4235
4328
|
// `exportBrowserResult.success` says the CODE RAN. The export's own
|
|
@@ -4283,14 +4376,20 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
4283
4376
|
format,
|
|
4284
4377
|
chunkBytes: Number(exportResult.chunkBytes) || undefined,
|
|
4285
4378
|
}, input.platform || 'auto');
|
|
4379
|
+
const delivery = await deliverAacIfOpus(filePath, format, exportResult, input.platform);
|
|
4380
|
+
if (delivery.action === 'refuse') {
|
|
4381
|
+
await (await import('node:fs/promises')).unlink(filePath).catch(() => undefined);
|
|
4382
|
+
return errorResult(ErrorCodes.EXECUTION_ERROR, delivery.message, { code: 'audio_codec_unavailable', format, platform: input.platform }, { toolName: 'pinepaper_agent_export' });
|
|
4383
|
+
}
|
|
4286
4384
|
const receipt = await exportFileReceiptOrReason(filePath, exportResult);
|
|
4287
|
-
const
|
|
4385
|
+
const deliveredSize = 'bytes' in receipt ? receipt.bytes : fileSize;
|
|
4386
|
+
const cleanResult = { ...exportResult, data: undefined, filePath, fileSize: deliveredSize, chunks, file: receipt, ...audioDeliveryFields(delivery) };
|
|
4288
4387
|
return withEngineReport({
|
|
4289
4388
|
structuredContent: boundedStructured(cleanResult),
|
|
4290
4389
|
content: [{
|
|
4291
4390
|
type: 'text',
|
|
4292
|
-
text: `Export saved to file:\n\nFile: ${filePath}\nFormat: ${format}\nSize: ${(
|
|
4293
|
-
}, exportFileLink(filePath,
|
|
4391
|
+
text: `Export saved to file:\n\nFile: ${filePath}\nFormat: ${format}\nSize: ${(deliveredSize / 1024).toFixed(1)} KB\nPlatform: ${input.platform}\nPaged out of the studio's export store in ${chunks} chunk${chunks === 1 ? '' : 's'}\n${typeof exportResult.warning === 'string' ? `WARNING: ${exportResult.warning}\n` : ''}${audioDeliveryLine(delivery)}${receiptErrorLine(receipt)}${'receiptError' in receipt ? '' : probeLines(receipt)}${lintLines(exportResult)}\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
|
|
4392
|
+
}, exportFileLink(filePath, deliveredSize)],
|
|
4294
4393
|
}, exportBrowserResult.report);
|
|
4295
4394
|
}
|
|
4296
4395
|
catch (streamError) {
|
|
@@ -4345,9 +4444,15 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
4345
4444
|
data.length > SAVE_THRESHOLD_BYTES);
|
|
4346
4445
|
if (shouldSaveToFile) {
|
|
4347
4446
|
try {
|
|
4348
|
-
const { filePath, fileSize } = await saveExportToFile(data, format, input.platform || 'auto');
|
|
4447
|
+
const { filePath, fileSize: savedSize } = await saveExportToFile(data, format, input.platform || 'auto');
|
|
4448
|
+
const delivery = await deliverAacIfOpus(filePath, format, exportResult, input.platform);
|
|
4449
|
+
if (delivery.action === 'refuse') {
|
|
4450
|
+
await (await import('node:fs/promises')).unlink(filePath).catch(() => undefined);
|
|
4451
|
+
return errorResult(ErrorCodes.EXECUTION_ERROR, delivery.message, { code: 'audio_codec_unavailable', format, platform: input.platform }, { toolName: 'pinepaper_agent_export' });
|
|
4452
|
+
}
|
|
4349
4453
|
const receipt = await exportFileReceiptOrReason(filePath, exportResult);
|
|
4350
|
-
const
|
|
4454
|
+
const fileSize = 'bytes' in receipt ? receipt.bytes : savedSize;
|
|
4455
|
+
const cleanResult = { ...exportResult, data: undefined, filePath, fileSize, file: receipt, ...audioDeliveryFields(delivery) };
|
|
4351
4456
|
// A mix that dropped sounds wrote a perfectly good file that is
|
|
4352
4457
|
// missing tracks. It rides the SUCCESS path, which is exactly why
|
|
4353
4458
|
// it has to be said out loud here rather than left in the JSON.
|
|
@@ -4357,7 +4462,7 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
4357
4462
|
content: [{
|
|
4358
4463
|
type: 'text',
|
|
4359
4464
|
text: `Export saved to file:\n\nFile: ${filePath}\nFormat: ${format}\nSize: ${(fileSize / 1024).toFixed(1)} KB\nPlatform: ${input.platform}\n`
|
|
4360
|
-
+ (dropNote ? `DROPPED: ${dropNote}\n` : '')
|
|
4465
|
+
+ (dropNote ? `DROPPED: ${dropNote}\n` : '') + audioDeliveryLine(delivery)
|
|
4361
4466
|
// The studio's own warning on a saved file (a GIF over its maxBytes budget, GIFMAX34) was only
|
|
4362
4467
|
// inside the Result JSON below; a reader of the words missed it.
|
|
4363
4468
|
+ (typeof exportResult.warning === 'string' ? `WARNING: ${exportResult.warning}\n` : '')
|
|
@@ -4425,11 +4530,28 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
4425
4530
|
case 'list_styles':
|
|
4426
4531
|
return dataResult({ styles: designSystems.listStyles() });
|
|
4427
4532
|
case 'compose': {
|
|
4533
|
+
// NO SIZE GIVEN MEANS YOUR CANVAS, not the style's own (RISO-RESIZE, owner 2026-11-14). A style laid out
|
|
4534
|
+
// at its default size (1280x720 for most) and resized the canvas to it, so a 1080x1920 project became
|
|
4535
|
+
// 1280x720. Where the studio is reachable, an unsized compose uses the current canvas size.
|
|
4536
|
+
let width = input.width, height = input.height;
|
|
4537
|
+
let sizeFrom = width !== undefined || height !== undefined ? 'asked' : 'style';
|
|
4538
|
+
if (sizeFrom === 'style' && input.draw !== false && options.executeInBrowser) {
|
|
4539
|
+
const ctl = options.browserController || getBrowserController();
|
|
4540
|
+
if (ctl.connected) {
|
|
4541
|
+
const cs = await ctl.executeCode('(function () { const s = app.getCanvasSize ? app.getCanvasSize() : null; return s && s.width > 0 && s.height > 0 ? { width: s.width, height: s.height } : null; })()', false)
|
|
4542
|
+
.then((r) => (r.success ? r.result : null)).catch(() => null);
|
|
4543
|
+
if (cs) {
|
|
4544
|
+
width = cs.width;
|
|
4545
|
+
height = cs.height;
|
|
4546
|
+
sizeFrom = 'canvas';
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4428
4550
|
let scene;
|
|
4429
4551
|
try {
|
|
4430
4552
|
scene = designSystems.compose(input.style, {
|
|
4431
4553
|
title: input.title, subtitle: input.subtitle, body: input.body,
|
|
4432
|
-
width
|
|
4554
|
+
width, height, variant: input.variant,
|
|
4433
4555
|
});
|
|
4434
4556
|
}
|
|
4435
4557
|
catch (composeError) {
|
|
@@ -4443,10 +4565,12 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
4443
4565
|
// Some vendored styles lay out at their OWN fixed size and ignore
|
|
4444
4566
|
// width/height (swiss_typographic is always 1280x720), so a caller
|
|
4445
4567
|
// asking for 1080x1080 got a resized canvas (gate run 3). Say so.
|
|
4446
|
-
const sizeIgnored = (
|
|
4447
|
-
|| (
|
|
4568
|
+
const sizeIgnored = (width !== undefined && width !== scene.width)
|
|
4569
|
+
|| (height !== undefined && height !== scene.height);
|
|
4448
4570
|
const sizeWarning = sizeIgnored
|
|
4449
|
-
?
|
|
4571
|
+
? (sizeFrom === 'canvas'
|
|
4572
|
+
? `"${scene.style}" lays out at its own size, ${scene.width}x${scene.height}, not your canvas's ${width}x${height}, so the canvas is resized to it`
|
|
4573
|
+
: `"${scene.style}" lays out at its own size, ${scene.width}x${scene.height}; the requested ${input.width ?? '?'}x${input.height ?? '?'} was not used`)
|
|
4450
4574
|
: undefined;
|
|
4451
4575
|
// BAUHAUS-SUBTITLE: at a size the style was not laid out for, its
|
|
4452
4576
|
// proportions can put text across a shape (bauhaus at 1920x1080
|
|
@@ -4454,18 +4578,20 @@ You can now start creating new items on a clean canvas.${sizeNote}`,
|
|
|
4454
4578
|
// the result names each collision rather than drawing it as intended.
|
|
4455
4579
|
const collisions = designSystems.scalingCollisions(input.style, {
|
|
4456
4580
|
title: input.title, subtitle: input.subtitle, body: input.body,
|
|
4457
|
-
width
|
|
4581
|
+
width, height, variant: input.variant,
|
|
4458
4582
|
}, scene);
|
|
4459
4583
|
const layoutWarning = collisions.length
|
|
4460
4584
|
? `at ${scene.width}x${scene.height} the "${scene.style}" layout runs text onto a shape it does not touch at its own default size: `
|
|
4461
4585
|
+ collisions.map((c) => `${c.text} over ${c.shape}`).join(', ')
|
|
4462
|
-
+ '.
|
|
4586
|
+
+ '. For the layout as designed, compose at the style\'s own size (draw: false without width/height returns it), or move the text afterwards with pinepaper_modify_item.'
|
|
4463
4587
|
: undefined;
|
|
4464
4588
|
const collisionReport = collisions.length ? { layoutCollisions: collisions, layoutWarning } : {};
|
|
4465
4589
|
if (input.draw === false) {
|
|
4466
4590
|
return dataResult({ scene, ops, drawn: false, ...(sizeWarning ? { warning: sizeWarning } : {}), ...collisionReport });
|
|
4467
4591
|
}
|
|
4468
|
-
|
|
4592
|
+
// A style that cannot lay out at your canvas size is fitted into it rather than resizing it.
|
|
4593
|
+
const fitTo = sizeFrom === 'canvas' && sizeIgnored && width && height ? { width, height } : undefined;
|
|
4594
|
+
const code = codeGenerator.generateDesignCompose(scene, ops, scene.style, sizeWarning, collisionReport, fitTo);
|
|
4469
4595
|
return executeOrGenerate(code, `Composed a ${scene.style} scene (${ops.length} items)`, options, 'pinepaper_design_system');
|
|
4470
4596
|
}
|
|
4471
4597
|
}
|