@pinepaper.studio/mcp-server 1.6.6 → 1.6.8
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 +24 -1
- package/README.es.md +24 -1
- package/README.fr.md +24 -1
- package/README.hi.md +21 -1
- package/README.ja.md +22 -1
- package/README.ko.md +22 -1
- package/README.md +329 -14
- package/README.pt-BR.md +24 -1
- package/README.zh-CN.md +20 -1
- package/assets/edit-whenever.svg +75 -0
- package/dist/agent/export-manager.d.ts.map +1 -1
- package/dist/agent/export-manager.js +2 -2
- package/dist/agent/export-manager.js.map +1 -1
- package/dist/agent/session-manager.d.ts.map +1 -1
- package/dist/agent/session-manager.js.map +1 -1
- package/dist/assets/repositories/fontawesome.d.ts.map +1 -1
- package/dist/assets/repositories/fontawesome.js.map +1 -1
- package/dist/assets/repositories/openclipart.d.ts.map +1 -1
- package/dist/character/data/concepts.json +1 -0
- package/dist/character/data/depictions.json +1 -0
- package/dist/character/data/styles.json +1 -0
- package/dist/character/duration-scale.d.ts +13 -0
- package/dist/character/duration-scale.d.ts.map +1 -0
- package/dist/character/duration-scale.js +13 -0
- package/dist/character/duration-scale.js.map +1 -0
- package/dist/character/path-bbox.d.ts +82 -0
- package/dist/character/path-bbox.d.ts.map +1 -0
- package/dist/character/path-bbox.js +496 -0
- package/dist/character/path-bbox.js.map +1 -0
- package/dist/character/perform.d.ts +149 -0
- package/dist/character/perform.d.ts.map +1 -0
- package/dist/character/perform.js +626 -0
- package/dist/character/perform.js.map +1 -0
- package/dist/character/resolve.d.ts +51 -0
- package/dist/character/resolve.d.ts.map +1 -0
- package/dist/character/resolve.js +239 -0
- package/dist/character/resolve.js.map +1 -0
- package/dist/design/design-systems.d.ts +126 -0
- package/dist/design/design-systems.d.ts.map +1 -0
- package/dist/design/design-systems.js +240 -0
- package/dist/design/design-systems.js.map +1 -0
- package/dist/execution/canvas-state.d.ts +7 -0
- package/dist/execution/canvas-state.d.ts.map +1 -1
- package/dist/execution/canvas-state.js +20 -8
- package/dist/execution/canvas-state.js.map +1 -1
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -5
- package/dist/index.js.map +1 -1
- package/dist/ontology/design-graph.d.ts +9 -0
- package/dist/ontology/design-graph.d.ts.map +1 -1
- package/dist/ontology/design-graph.js +98 -4
- package/dist/ontology/design-graph.js.map +1 -1
- package/dist/ontology/types.d.ts +42 -0
- package/dist/ontology/types.d.ts.map +1 -1
- package/dist/ontology/vocabulary.d.ts.map +1 -1
- package/dist/ontology/vocabulary.js +139 -0
- package/dist/ontology/vocabulary.js.map +1 -1
- package/dist/taxonomy/classifier.js +1 -1
- package/dist/taxonomy/classifier.js.map +1 -1
- package/dist/taxonomy/error-recovery.d.ts.map +1 -1
- package/dist/taxonomy/matcher.d.ts.map +1 -1
- package/dist/taxonomy/matcher.js.map +1 -1
- package/dist/tools/compact-descriptions.d.ts.map +1 -1
- package/dist/tools/compact-descriptions.js +25 -0
- 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 +1236 -45
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/engine-methods.d.ts +18 -0
- package/dist/tools/engine-methods.d.ts.map +1 -0
- package/dist/tools/engine-methods.js +239 -0
- package/dist/tools/engine-methods.js.map +1 -0
- package/dist/tools/handlers/character.d.ts +121 -0
- package/dist/tools/handlers/character.d.ts.map +1 -0
- package/dist/tools/handlers/character.js +315 -0
- package/dist/tools/handlers/character.js.map +1 -0
- package/dist/tools/handlers.d.ts +2 -0
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +386 -22
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +24 -4
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +21 -0
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +141 -2
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +2246 -78
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +9295 -603
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +1134 -61
- package/dist/types/schemas.js.map +1 -1
- package/dist/vendor/design/art-deco-generator.d.ts +52 -0
- package/dist/vendor/design/art-deco-generator.d.ts.map +1 -0
- package/dist/vendor/design/art-deco-generator.js +144 -0
- package/dist/vendor/design/art-deco-generator.js.map +1 -0
- package/dist/vendor/design/art-nouveau-generator.d.ts +46 -0
- package/dist/vendor/design/art-nouveau-generator.d.ts.map +1 -0
- package/dist/vendor/design/art-nouveau-generator.js +134 -0
- package/dist/vendor/design/art-nouveau-generator.js.map +1 -0
- package/dist/vendor/design/bauhaus-generator.d.ts +53 -0
- package/dist/vendor/design/bauhaus-generator.d.ts.map +1 -0
- package/dist/vendor/design/bauhaus-generator.js +126 -0
- package/dist/vendor/design/bauhaus-generator.js.map +1 -0
- package/dist/vendor/design/brutalist-generator.d.ts +53 -0
- package/dist/vendor/design/brutalist-generator.d.ts.map +1 -0
- package/dist/vendor/design/brutalist-generator.js +148 -0
- package/dist/vendor/design/brutalist-generator.js.map +1 -0
- package/dist/vendor/design/claymorphism-generator.d.ts +46 -0
- package/dist/vendor/design/claymorphism-generator.d.ts.map +1 -0
- package/dist/vendor/design/claymorphism-generator.js +135 -0
- package/dist/vendor/design/claymorphism-generator.js.map +1 -0
- package/dist/vendor/design/corporate-memphis-generator.d.ts +47 -0
- package/dist/vendor/design/corporate-memphis-generator.d.ts.map +1 -0
- package/dist/vendor/design/corporate-memphis-generator.js +144 -0
- package/dist/vendor/design/corporate-memphis-generator.js.map +1 -0
- package/dist/vendor/design/cyberpunk-generator.d.ts +52 -0
- package/dist/vendor/design/cyberpunk-generator.d.ts.map +1 -0
- package/dist/vendor/design/cyberpunk-generator.js +162 -0
- package/dist/vendor/design/cyberpunk-generator.js.map +1 -0
- package/dist/vendor/design/de-stijl-generator.d.ts +50 -0
- package/dist/vendor/design/de-stijl-generator.d.ts.map +1 -0
- package/dist/vendor/design/de-stijl-generator.js +154 -0
- package/dist/vendor/design/de-stijl-generator.js.map +1 -0
- package/dist/vendor/design/design-style.d.ts +3 -0
- package/dist/vendor/design/design-style.d.ts.map +1 -0
- package/dist/vendor/design/design-style.js +12 -0
- package/dist/vendor/design/design-style.js.map +1 -0
- package/dist/vendor/design/design-systems-dtcg.d.ts +59 -0
- package/dist/vendor/design/design-systems-dtcg.d.ts.map +1 -0
- package/dist/vendor/design/design-systems-dtcg.js +571 -0
- package/dist/vendor/design/design-systems-dtcg.js.map +1 -0
- package/dist/vendor/design/frutiger-aero-generator.d.ts +46 -0
- package/dist/vendor/design/frutiger-aero-generator.d.ts.map +1 -0
- package/dist/vendor/design/frutiger-aero-generator.js +133 -0
- package/dist/vendor/design/frutiger-aero-generator.js.map +1 -0
- package/dist/vendor/design/memphis-generator.d.ts +56 -0
- package/dist/vendor/design/memphis-generator.d.ts.map +1 -0
- package/dist/vendor/design/memphis-generator.js +158 -0
- package/dist/vendor/design/memphis-generator.js.map +1 -0
- package/dist/vendor/design/mid-century-upa-generator.d.ts +46 -0
- package/dist/vendor/design/mid-century-upa-generator.d.ts.map +1 -0
- package/dist/vendor/design/mid-century-upa-generator.js +137 -0
- package/dist/vendor/design/mid-century-upa-generator.js.map +1 -0
- package/dist/vendor/design/neumorphic-generator.d.ts +53 -0
- package/dist/vendor/design/neumorphic-generator.d.ts.map +1 -0
- package/dist/vendor/design/neumorphic-generator.js +182 -0
- package/dist/vendor/design/neumorphic-generator.js.map +1 -0
- package/dist/vendor/design/op-art-generator.d.ts +45 -0
- package/dist/vendor/design/op-art-generator.d.ts.map +1 -0
- package/dist/vendor/design/op-art-generator.js +116 -0
- package/dist/vendor/design/op-art-generator.js.map +1 -0
- package/dist/vendor/design/pixel-art-generator.d.ts +45 -0
- package/dist/vendor/design/pixel-art-generator.d.ts.map +1 -0
- package/dist/vendor/design/pixel-art-generator.js +143 -0
- package/dist/vendor/design/pixel-art-generator.js.map +1 -0
- package/dist/vendor/design/pop-art-generator.d.ts +53 -0
- package/dist/vendor/design/pop-art-generator.d.ts.map +1 -0
- package/dist/vendor/design/pop-art-generator.js +164 -0
- package/dist/vendor/design/pop-art-generator.js.map +1 -0
- package/dist/vendor/design/risograph-generator.d.ts +53 -0
- package/dist/vendor/design/risograph-generator.d.ts.map +1 -0
- package/dist/vendor/design/risograph-generator.js +170 -0
- package/dist/vendor/design/risograph-generator.js.map +1 -0
- package/dist/vendor/design/swiss-style-generator.d.ts +55 -0
- package/dist/vendor/design/swiss-style-generator.d.ts.map +1 -0
- package/dist/vendor/design/swiss-style-generator.js +147 -0
- package/dist/vendor/design/swiss-style-generator.js.map +1 -0
- package/dist/vendor/design/vaporwave-generator.d.ts +51 -0
- package/dist/vendor/design/vaporwave-generator.d.ts.map +1 -0
- package/dist/vendor/design/vaporwave-generator.js +139 -0
- package/dist/vendor/design/vaporwave-generator.js.map +1 -0
- package/manifest.json +65 -1
- package/package.json +18 -9
package/dist/tools/handlers.js
CHANGED
|
@@ -6,25 +6,30 @@
|
|
|
6
6
|
* Supports browser execution via Puppeteer.
|
|
7
7
|
*/
|
|
8
8
|
import { codeGenerator } from '../types/code-generator.js';
|
|
9
|
-
import { CreateItemInputSchema, ModifyItemInputSchema, DeleteItemInputSchema, AddRelationInputSchema, RemoveRelationInputSchema, QueryRelationsInputSchema, AnimateItemInputSchema, 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, ShatterImageInputSchema, ImportLayeredCharacterInputSchema, GameInputSchema, World3DInputSchema, BrandKitInputSchema, ComponentInputSchema, ArtboardInputSchema, CommentInputSchema, ProvenanceInputSchema, SceneDiffInputSchema, AudioBeatsInputSchema, TemplateParamsInputSchema, ComposeInputSchema, CropImageInputSchema, 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, ShatterImageInputSchema, ImportLayeredCharacterInputSchema, CharacterInputSchema, GameInputSchema, World3DInputSchema, BrandKitInputSchema, ComponentInputSchema, ArtboardInputSchema, CommentInputSchema, ProvenanceInputSchema, SceneDiffInputSchema, AudioBeatsInputSchema, TemplateParamsInputSchema, 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
|
|
13
13
|
AgentStartJobInputSchema, AgentEndJobInputSchema, AgentResetInputSchema, AgentBatchExecuteInputSchema, AgentExportInputSchema, AgentAnalyzeInputSchema,
|
|
14
14
|
// Letter collage schemas
|
|
15
|
-
CreateLetterCollageInputSchema, AnimateLetterCollageInputSchema,
|
|
15
|
+
CreateLetterCollageInputSchema, AnimateLetterCollageInputSchema,
|
|
16
|
+
// Map schemas
|
|
17
|
+
ApplyTemplateInputSchema,
|
|
18
|
+
// Map animation/CSV schemas
|
|
16
19
|
// Custom relation/code schemas
|
|
17
20
|
RegisterCustomRelationInputSchema, ExecuteCustomCodeInputSchema,
|
|
18
21
|
// Mask schemas
|
|
19
22
|
ApplyAnimatedMaskInputSchema, ApplyCustomMaskInputSchema, RemoveMaskInputSchema,
|
|
20
23
|
// Image import schemas
|
|
21
24
|
ImportImageInputSchema,
|
|
25
|
+
// Tool guide schema
|
|
26
|
+
// Ontology schemas
|
|
22
27
|
// Scene management schemas
|
|
23
|
-
ManageScenesInputSchema, ScenePlaybackInputSchema,
|
|
28
|
+
ManageScenesInputSchema, ScenePlaybackInputSchema, SceneGraphInputSchema, SequenceInputSchema, StaggerInputSchema, FlipInputSchema,
|
|
24
29
|
// New consolidated tool schemas
|
|
25
|
-
SelectionInputSchema, TransformInputSchema, HistoryInputSchema, ImageFilterInputSchema, LassoInputSchema, CutoutStyleInputSchema, PrecompInputSchema, BackgroundInputSchema, QueryInputSchema, 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, ErrorCodes, } from '../types/schemas.js';
|
|
26
31
|
import { ZodError } from 'zod';
|
|
27
|
-
import { writeFile, mkdir } from 'node:fs/promises';
|
|
32
|
+
import { writeFile, mkdir, appendFile, unlink } from 'node:fs/promises';
|
|
28
33
|
import { tmpdir } from 'node:os';
|
|
29
34
|
import { join } from 'node:path';
|
|
30
35
|
import { getBrowserController, resetBrowserController, } from '../browser/puppeteer-controller.js';
|
|
@@ -38,20 +43,24 @@ import { fontHandlers } from './handlers/font.js';
|
|
|
38
43
|
import { toolGuideHandlers } from './handlers/tool-guide.js';
|
|
39
44
|
import { mapHandlers } from './handlers/maps.js';
|
|
40
45
|
import { ontologyHandlers } from './handlers/ontology.js';
|
|
46
|
+
import * as designSystems from '../design/design-systems.js';
|
|
41
47
|
import { exportHandlers } from './handlers/export.js';
|
|
48
|
+
import { planCharacter, generateCharacterCode } from './handlers/character.js';
|
|
42
49
|
/**
|
|
43
50
|
* Registry of per-domain handler maps. Tools listed here short-circuit the
|
|
44
51
|
* main switch at dispatch time (see handleToolCallInner). New domains are
|
|
45
52
|
* folded in incrementally as they are migrated out of the monolithic switch.
|
|
46
53
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
function getDomainHandlers() {
|
|
55
|
+
return [
|
|
56
|
+
cameraHandlers,
|
|
57
|
+
fontHandlers,
|
|
58
|
+
toolGuideHandlers,
|
|
59
|
+
mapHandlers,
|
|
60
|
+
ontologyHandlers,
|
|
61
|
+
exportHandlers,
|
|
62
|
+
];
|
|
63
|
+
}
|
|
55
64
|
/**
|
|
56
65
|
* Get screenshot mode from environment variable.
|
|
57
66
|
* Defaults to 'on_request' for optimal performance.
|
|
@@ -70,10 +79,13 @@ export function getScreenshotMode() {
|
|
|
70
79
|
function getExportDir() {
|
|
71
80
|
return process.env.PINEPAPER_EXPORT_DIR || join(tmpdir(), 'pinepaper-exports');
|
|
72
81
|
}
|
|
73
|
-
|
|
82
|
+
// wav joins them: a minute of 48kHz 16-bit is ~5.8 MB of base64 and the ten
|
|
83
|
+
// minutes the schema allows is ~77 MB. Deliverable across the bridge, useless
|
|
84
|
+
// pasted into a response.
|
|
85
|
+
export const ALWAYS_SAVE_FORMATS = new Set(['mp4', 'webm', 'gif', 'pdf', 'wav']);
|
|
74
86
|
const SAVE_THRESHOLD_BYTES = 500_000; // ~500KB base64 ≈ 375KB decoded
|
|
75
|
-
function getFileExtension(format) {
|
|
76
|
-
const extMap = { mp4: 'mp4', webm: 'webm', gif: 'gif', pdf: 'pdf', png: 'png', svg: 'svg' };
|
|
87
|
+
export function getFileExtension(format) {
|
|
88
|
+
const extMap = { mp4: 'mp4', webm: 'webm', gif: 'gif', pdf: 'pdf', png: 'png', svg: 'svg', wav: 'wav' };
|
|
77
89
|
return extMap[format] || format;
|
|
78
90
|
}
|
|
79
91
|
async function saveExportToFile(data, format, platform) {
|
|
@@ -95,6 +107,78 @@ async function saveExportToFile(data, format, platform) {
|
|
|
95
107
|
return { filePath, fileSize: Buffer.byteLength(data, 'utf-8') };
|
|
96
108
|
}
|
|
97
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Page a RETAINED export out of the studio's export store into a local file.
|
|
112
|
+
*
|
|
113
|
+
* The bytes never cross the bridge as one string. The studio holds the encoded
|
|
114
|
+
* file in its origin-private store and hands back an id; this reads it a chunk
|
|
115
|
+
* at a time and appends each one, so at most one chunk (4 MB by default) is in
|
|
116
|
+
* memory on either side. The agent still made one tool call and still gets one
|
|
117
|
+
* filePath — the paging is entirely inside the tool, because an agent-facing
|
|
118
|
+
* pager would only expose a transport limit the agent cannot act on.
|
|
119
|
+
*
|
|
120
|
+
* On failure the export is deliberately LEFT HELD: the store is the only copy,
|
|
121
|
+
* so the id and the byte count reached go back in the error and the bytes stay
|
|
122
|
+
* recoverable. `releaseExport` runs only once the whole file is written.
|
|
123
|
+
*/
|
|
124
|
+
async function streamRetainedExportToFile(controller, retained, platform) {
|
|
125
|
+
const exportDir = getExportDir();
|
|
126
|
+
await mkdir(exportDir, { recursive: true });
|
|
127
|
+
const ext = getFileExtension(retained.format);
|
|
128
|
+
const fileName = `pinepaper_${platform}_${Date.now()}.${ext}`;
|
|
129
|
+
const filePath = join(exportDir, fileName);
|
|
130
|
+
const chunkBytes = retained.chunkBytes && retained.chunkBytes > 0 ? retained.chunkBytes : 4 * 1024 * 1024;
|
|
131
|
+
let offset = 0;
|
|
132
|
+
let chunks = 0;
|
|
133
|
+
// Start from empty: appendFile would otherwise extend a same-millisecond file.
|
|
134
|
+
await writeFile(filePath, Buffer.alloc(0));
|
|
135
|
+
// Returns the error rather than throwing it, so `throw await abandon(...)`
|
|
136
|
+
// reads as a throw to the type checker and narrows what follows.
|
|
137
|
+
const abandon = async (why) => {
|
|
138
|
+
try {
|
|
139
|
+
await unlink(filePath);
|
|
140
|
+
}
|
|
141
|
+
catch { /* nothing written yet */ }
|
|
142
|
+
return new Error(`${why} (export "${retained.exportId}" is still held in the studio, ${offset} of ${retained.size} bytes read; ` +
|
|
143
|
+
`it can be paged again, and is released only once a whole file is written)`);
|
|
144
|
+
};
|
|
145
|
+
for (;;) {
|
|
146
|
+
const code = codeGenerator.generateReadExportChunk(retained.exportId, offset, Math.min(chunkBytes, retained.size - offset));
|
|
147
|
+
const run = await controller.executeCode(code, false);
|
|
148
|
+
if (!run.success)
|
|
149
|
+
throw await abandon(`reading the export failed: ${run.error || 'unknown error'}`);
|
|
150
|
+
const chunk = run.result;
|
|
151
|
+
if (!chunk || chunk.ok !== true) {
|
|
152
|
+
// An evicted id is its own failure: a later export reclaimed the space.
|
|
153
|
+
// Re-exporting is the caller's decision, never this loop's.
|
|
154
|
+
throw await abandon(chunk?.evicted
|
|
155
|
+
? `the export was evicted mid-read: ${chunk.reason}`
|
|
156
|
+
: `the studio refused the read: ${chunk?.reason || 'no reason given'}`);
|
|
157
|
+
}
|
|
158
|
+
const buf = Buffer.from(chunk.data || '', 'base64');
|
|
159
|
+
if (buf.length === 0 && chunk.eof !== true)
|
|
160
|
+
throw await abandon('the studio returned an empty chunk before the end of the file');
|
|
161
|
+
await appendFile(filePath, buf);
|
|
162
|
+
offset += buf.length;
|
|
163
|
+
chunks++;
|
|
164
|
+
if (chunk.eof === true)
|
|
165
|
+
break;
|
|
166
|
+
if (offset >= retained.size)
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
if (offset !== retained.size) {
|
|
170
|
+
throw await abandon(`reassembled ${offset} bytes but the studio reported ${retained.size}`);
|
|
171
|
+
}
|
|
172
|
+
// Only now is it safe to drop the studio's copy.
|
|
173
|
+
try {
|
|
174
|
+
await controller.executeCode(codeGenerator.generateReleaseExport(retained.exportId), false);
|
|
175
|
+
}
|
|
176
|
+
catch (releaseError) {
|
|
177
|
+
// A held export costs quota, not correctness — the file is already whole.
|
|
178
|
+
console.error('[PinePaper] Export written but releaseExport failed:', releaseError);
|
|
179
|
+
}
|
|
180
|
+
return { filePath, fileSize: offset, chunks };
|
|
181
|
+
}
|
|
98
182
|
/**
|
|
99
183
|
* Get execution mode from environment variable.
|
|
100
184
|
* Defaults to 'puppeteer' for live execution.
|
|
@@ -125,6 +209,17 @@ function successResult(code, description) {
|
|
|
125
209
|
}
|
|
126
210
|
return { content };
|
|
127
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* A plain data answer.
|
|
214
|
+
*
|
|
215
|
+
* successResult() fences its payload as "Generated PinePaper code", which is
|
|
216
|
+
* right for an emitter and wrong for a tool that computes its answer here —
|
|
217
|
+
* a caller reading design tokens should not be handed something that looks
|
|
218
|
+
* like a script to paste.
|
|
219
|
+
*/
|
|
220
|
+
function dataResult(value) {
|
|
221
|
+
return { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] };
|
|
222
|
+
}
|
|
128
223
|
function executedResult(code, result, screenshot, description, report) {
|
|
129
224
|
const resultObj = { success: true, result };
|
|
130
225
|
if (description)
|
|
@@ -415,7 +510,15 @@ export function getLocalizedSuccessMessage(i18n, key, params) {
|
|
|
415
510
|
// =============================================================================
|
|
416
511
|
/**
|
|
417
512
|
* Execute a tool handler with automatic performance tracking for validation and code generation
|
|
513
|
+
*
|
|
514
|
+
* NOT CALLED ANYWHERE. Kept rather than deleted because it is a complete,
|
|
515
|
+
* working helper and nothing equivalent replaced it — the performance tracking
|
|
516
|
+
* it implements was written and never wired, which is a gap worth closing
|
|
517
|
+
* rather than a dead symbol worth removing. Deleting it would erase the intent
|
|
518
|
+
* along with the code; wiring it is a behaviour change and belongs in its own
|
|
519
|
+
* commit.
|
|
418
520
|
*/
|
|
521
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- written, never wired; see above
|
|
419
522
|
async function executeToolWithTracking(toolName, args, options, validator, codeGenerator, description) {
|
|
420
523
|
const tracker = getPerformanceTracker();
|
|
421
524
|
const baseTimerId = `${toolName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
@@ -490,7 +593,7 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
490
593
|
// Domain-module dispatch: tools migrated to per-domain handler files
|
|
491
594
|
// short-circuit before the main switch. See DOMAIN_HANDLERS for the
|
|
492
595
|
// current registry.
|
|
493
|
-
for (const domainMap of
|
|
596
|
+
for (const domainMap of getDomainHandlers()) {
|
|
494
597
|
const handler = domainMap[toolName];
|
|
495
598
|
if (handler)
|
|
496
599
|
return await handler(args, options);
|
|
@@ -587,6 +690,29 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
587
690
|
const code = codeGenerator.generateScenePlayback(input);
|
|
588
691
|
return executeOrGenerate(code, `Scene playback: ${input.action}`, options, 'pinepaper_scene_playback');
|
|
589
692
|
}
|
|
693
|
+
case 'pinepaper_sequence': {
|
|
694
|
+
const input = SequenceInputSchema.parse(args);
|
|
695
|
+
const code = codeGenerator.generateSequence(input);
|
|
696
|
+
return executeOrGenerate(code, `Sequence: ${input.action}`, options, 'pinepaper_sequence');
|
|
697
|
+
}
|
|
698
|
+
case 'pinepaper_stagger': {
|
|
699
|
+
const input = StaggerInputSchema.parse(args);
|
|
700
|
+
const code = codeGenerator.generateStagger(input);
|
|
701
|
+
return executeOrGenerate(code, `Stagger: ${input.action}`, options, 'pinepaper_stagger');
|
|
702
|
+
}
|
|
703
|
+
case 'pinepaper_flip': {
|
|
704
|
+
const input = FlipInputSchema.parse(args);
|
|
705
|
+
const code = codeGenerator.generateFlip(input);
|
|
706
|
+
return executeOrGenerate(code, `Flip: ${input.action}`, options, 'pinepaper_flip');
|
|
707
|
+
}
|
|
708
|
+
case 'pinepaper_scene_graph': {
|
|
709
|
+
const input = SceneGraphInputSchema.parse(args);
|
|
710
|
+
const code = codeGenerator.generateSceneGraph(input);
|
|
711
|
+
const summary = input.action === 'validate'
|
|
712
|
+
? `Scene graph: validating ${input.graph.nodes.length} node(s)`
|
|
713
|
+
: `Scene graph: ${input.graph.nodes.length} node(s)`;
|
|
714
|
+
return executeOrGenerate(code, summary, options, 'pinepaper_scene_graph');
|
|
715
|
+
}
|
|
590
716
|
// -----------------------------------------------------------------------
|
|
591
717
|
// SELECTION, TRANSFORM & HISTORY TOOLS
|
|
592
718
|
// -----------------------------------------------------------------------
|
|
@@ -644,6 +770,11 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
644
770
|
const code = codeGenerator.generateQuery(input);
|
|
645
771
|
return executeOrGenerate(code, `Query: ${input.action}`, options, 'pinepaper_query');
|
|
646
772
|
}
|
|
773
|
+
case 'pinepaper_query_capabilities': {
|
|
774
|
+
const input = QueryCapabilitiesInputSchema.parse(args);
|
|
775
|
+
const code = codeGenerator.generateQueryCapabilities(input);
|
|
776
|
+
return executeOrGenerate(code, `Capabilities: ${input.action || 'list'}`, options, 'pinepaper_query_capabilities');
|
|
777
|
+
}
|
|
647
778
|
// -----------------------------------------------------------------------
|
|
648
779
|
// DEFORMATION, SPRITE SHEETS, STORAGE, INTERACTION
|
|
649
780
|
// -----------------------------------------------------------------------
|
|
@@ -698,6 +829,28 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
698
829
|
// -----------------------------------------------------------------------
|
|
699
830
|
// BATCH OPERATION TOOLS
|
|
700
831
|
// -----------------------------------------------------------------------
|
|
832
|
+
case 'pinepaper_character': {
|
|
833
|
+
const input = CharacterInputSchema.parse(args);
|
|
834
|
+
const plan = planCharacter(input);
|
|
835
|
+
if (!plan.ok) {
|
|
836
|
+
// A refusal that names what WOULD have worked. The caller cannot see
|
|
837
|
+
// the design graph, and a bare "no" is how a model came to ask for a
|
|
838
|
+
// concept that has never had a drawing.
|
|
839
|
+
return {
|
|
840
|
+
content: [{ type: 'text', text: `pinepaper_character: ${plan.reason}` }],
|
|
841
|
+
isError: true,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
const code = generateCharacterCode(plan, {
|
|
845
|
+
generateCreateItem: (i) => codeGenerator.generateCreateItem(i),
|
|
846
|
+
generateKeyframeAnimate: (i) => codeGenerator.generateKeyframeAnimate(i),
|
|
847
|
+
}, {
|
|
848
|
+
createItem: (a) => CreateItemInputSchema.parse(a),
|
|
849
|
+
keyframeAnimate: (a) => KeyframeAnimateInputSchema.parse(a),
|
|
850
|
+
});
|
|
851
|
+
const description = `Places ${input.concept} as ${plan.parts.length} parts with ${plan.tracks} keyframe tracks`;
|
|
852
|
+
return executeOrGenerate(code, description, options, 'pinepaper_character');
|
|
853
|
+
}
|
|
701
854
|
case 'pinepaper_batch_create': {
|
|
702
855
|
const input = BatchCreateInputSchema.parse(args);
|
|
703
856
|
const code = codeGenerator.generateBatchCreate(input);
|
|
@@ -808,6 +961,16 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
808
961
|
const code = codeGenerator.generateTextStyle(input);
|
|
809
962
|
return executeOrGenerate(code, `Text style: ${input.action}`, options, 'pinepaper_text_style');
|
|
810
963
|
}
|
|
964
|
+
case 'pinepaper_design_medium': {
|
|
965
|
+
const input = DesignMediumInputSchema.parse(args);
|
|
966
|
+
const code = codeGenerator.generateDesignMedium(input);
|
|
967
|
+
return executeOrGenerate(code, `Medium: ${input.action}`, options, 'pinepaper_design_medium');
|
|
968
|
+
}
|
|
969
|
+
case 'pinepaper_text_effect': {
|
|
970
|
+
const input = TextEffectInputSchema.parse(args);
|
|
971
|
+
const code = codeGenerator.generateTextEffect(input);
|
|
972
|
+
return executeOrGenerate(code, `Text effect: ${input.action}`, options, 'pinepaper_text_effect');
|
|
973
|
+
}
|
|
811
974
|
// ─── 1.6.4: agent surface for the Tier-2 engine features ───
|
|
812
975
|
case 'pinepaper_brand_kit': {
|
|
813
976
|
const input = BrandKitInputSchema.parse(args);
|
|
@@ -896,7 +1059,7 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
896
1059
|
const input = AddRelationInputSchema.parse(args);
|
|
897
1060
|
const code = codeGenerator.generateAddRelation(input);
|
|
898
1061
|
const description = getLocalizedSuccessMessage(i18n, 'relationAdded', {
|
|
899
|
-
relationType: input.relationType
|
|
1062
|
+
relationType: input.relationType ?? `preset:${input.presetId}`,
|
|
900
1063
|
sourceId: input.sourceId,
|
|
901
1064
|
targetId: input.targetId || 'self',
|
|
902
1065
|
});
|
|
@@ -938,6 +1101,14 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
938
1101
|
});
|
|
939
1102
|
return executeOrGenerate(code, description, options, 'pinepaper_animate');
|
|
940
1103
|
}
|
|
1104
|
+
case 'pinepaper_import_motion_capture': {
|
|
1105
|
+
const input = ImportMotionCaptureInputSchema.parse(args);
|
|
1106
|
+
const code = codeGenerator.generateImportMotionCapture(input);
|
|
1107
|
+
const what = input.mode === 'retarget'
|
|
1108
|
+
? `Retargets a BVH clip onto rig ${input.skeletonId}`
|
|
1109
|
+
: 'Imports a BVH clip as a new animated skeleton';
|
|
1110
|
+
return executeOrGenerate(code, what, options, 'pinepaper_import_motion_capture');
|
|
1111
|
+
}
|
|
941
1112
|
case 'pinepaper_keyframe_animate': {
|
|
942
1113
|
const input = KeyframeAnimateInputSchema.parse(args);
|
|
943
1114
|
const code = codeGenerator.generateKeyframeAnimate(input);
|
|
@@ -945,7 +1116,7 @@ async function handleToolCallInner(toolName, args, options, tracker, baseTimerId
|
|
|
945
1116
|
}
|
|
946
1117
|
case 'pinepaper_play_timeline': {
|
|
947
1118
|
const input = PlayTimelineInputSchema.parse(args);
|
|
948
|
-
const code = codeGenerator.generatePlayTimeline(input.action, input.duration, input.loop, input.time, input.deterministic);
|
|
1119
|
+
const code = codeGenerator.generatePlayTimeline(input.action, input.duration, input.loop, input.time, input.deterministic, { rate: input.rate, progress: input.progress, scroll: input.scroll });
|
|
949
1120
|
return executeOrGenerate(code, `Timeline action: ${input.action}`, options, 'pinepaper_play_timeline');
|
|
950
1121
|
}
|
|
951
1122
|
// -----------------------------------------------------------------------
|
|
@@ -1867,7 +2038,7 @@ You can now start creating new items on a clean canvas.`,
|
|
|
1867
2038
|
try {
|
|
1868
2039
|
await controller.connect();
|
|
1869
2040
|
}
|
|
1870
|
-
catch (
|
|
2041
|
+
catch (_connectError) {
|
|
1871
2042
|
// Fall back to executeOrGenerate which handles the fallback gracefully
|
|
1872
2043
|
return executeOrGenerate(code, description, options, 'pinepaper_agent_export');
|
|
1873
2044
|
}
|
|
@@ -1885,16 +2056,52 @@ You can now start creating new items on a clean canvas.`,
|
|
|
1885
2056
|
const exportResult = exportBrowserResult.result;
|
|
1886
2057
|
const format = exportResult?.format || input.format || 'png';
|
|
1887
2058
|
const data = exportResult?.data;
|
|
2059
|
+
// RETAINED: the studio held the encoded file in its export store rather
|
|
2060
|
+
// than returning bytes, because base64 of a long video is a bigger
|
|
2061
|
+
// string than the video and it would cross the bridge as one value.
|
|
2062
|
+
// Page it into a file here. mp4/webm were always going to be written to
|
|
2063
|
+
// a file anyway (ALWAYS_SAVE_FORMATS), so the agent's result is the
|
|
2064
|
+
// same shape it has always been — a filePath — just reachable at sizes
|
|
2065
|
+
// that used to run out of memory.
|
|
2066
|
+
if (exportResult?.retained === true && typeof exportResult.exportId === 'string') {
|
|
2067
|
+
try {
|
|
2068
|
+
const { filePath, fileSize, chunks } = await streamRetainedExportToFile(controller, {
|
|
2069
|
+
exportId: exportResult.exportId,
|
|
2070
|
+
size: Number(exportResult.size) || 0,
|
|
2071
|
+
format,
|
|
2072
|
+
chunkBytes: Number(exportResult.chunkBytes) || undefined,
|
|
2073
|
+
}, input.platform || 'auto');
|
|
2074
|
+
const cleanResult = { ...exportResult, data: undefined, filePath, fileSize, chunks };
|
|
2075
|
+
return {
|
|
2076
|
+
content: [{
|
|
2077
|
+
type: 'text',
|
|
2078
|
+
text: `Export saved to file:\n\nFile: ${filePath}\nFormat: ${format}\nSize: ${(fileSize / 1024).toFixed(1)} KB\nPlatform: ${input.platform}\nPaged out of the studio's export store in ${chunks} chunk${chunks === 1 ? '' : 's'}\n\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
|
|
2079
|
+
}],
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
catch (streamError) {
|
|
2083
|
+
// The export is still held — the message says so and names the id,
|
|
2084
|
+
// because the bytes are recoverable and re-exporting is not free.
|
|
2085
|
+
const canvasState = await captureCanvasState(controller);
|
|
2086
|
+
return errorResult(ErrorCodes.EXECUTION_ERROR, streamError instanceof Error ? streamError.message : 'Paging the export out of the store failed', { code, exportId: exportResult.exportId, size: exportResult.size }, { toolName: 'pinepaper_agent_export', canvasState: canvasState || undefined });
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
1888
2089
|
const shouldSaveToFile = data && typeof data === 'string' && (ALWAYS_SAVE_FORMATS.has(format) ||
|
|
1889
2090
|
data.length > SAVE_THRESHOLD_BYTES);
|
|
1890
2091
|
if (shouldSaveToFile) {
|
|
1891
2092
|
try {
|
|
1892
2093
|
const { filePath, fileSize } = await saveExportToFile(data, format, input.platform || 'auto');
|
|
1893
2094
|
const cleanResult = { ...exportResult, data: undefined, filePath, fileSize };
|
|
2095
|
+
// A mix that dropped sounds wrote a perfectly good file that is
|
|
2096
|
+
// missing tracks. It rides the SUCCESS path, which is exactly why
|
|
2097
|
+
// it has to be said out loud here rather than left in the JSON.
|
|
2098
|
+
const dropNote = exportResult.incomplete;
|
|
1894
2099
|
return {
|
|
1895
2100
|
content: [{
|
|
1896
2101
|
type: 'text',
|
|
1897
|
-
text: `Export saved to file:\n\nFile: ${filePath}\nFormat: ${format}\nSize: ${(fileSize / 1024).toFixed(1)} KB\nPlatform: ${input.platform}\n
|
|
2102
|
+
text: `Export saved to file:\n\nFile: ${filePath}\nFormat: ${format}\nSize: ${(fileSize / 1024).toFixed(1)} KB\nPlatform: ${input.platform}\n`
|
|
2103
|
+
+ (dropNote ? `DROPPED: ${dropNote}\n` : '')
|
|
2104
|
+
+ `\nResult: ${JSON.stringify(cleanResult, null, 2)}`,
|
|
1898
2105
|
}],
|
|
1899
2106
|
};
|
|
1900
2107
|
}
|
|
@@ -1908,6 +2115,158 @@ You can now start creating new items on a clean canvas.`,
|
|
|
1908
2115
|
// Small export — return inline (existing behavior)
|
|
1909
2116
|
return executedResult(code, exportResult, exportBrowserResult.screenshot, description);
|
|
1910
2117
|
}
|
|
2118
|
+
case 'pinepaper_design_styles':
|
|
2119
|
+
return dataResult({ styles: designSystems.listStyles() });
|
|
2120
|
+
case 'pinepaper_design_systems':
|
|
2121
|
+
case 'pinepaper_design_system': {
|
|
2122
|
+
const rawArgs = { ...args };
|
|
2123
|
+
if (!rawArgs.action && rawArgs.systemName) {
|
|
2124
|
+
if (rawArgs.systemName === 'all') {
|
|
2125
|
+
rawArgs.action = 'list_systems';
|
|
2126
|
+
}
|
|
2127
|
+
else {
|
|
2128
|
+
rawArgs.action = 'get_system';
|
|
2129
|
+
rawArgs.systemId = String(rawArgs.systemName);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
if (!rawArgs.action && rawArgs.style && !rawArgs.title) {
|
|
2133
|
+
rawArgs.action = 'list_styles';
|
|
2134
|
+
}
|
|
2135
|
+
const input = DesignSystemInputSchema.parse(rawArgs);
|
|
2136
|
+
// Answered HERE, not in the browser — these are decisions over
|
|
2137
|
+
// vendored data, and only `compose` with draw:true needs the canvas.
|
|
2138
|
+
switch (input.action) {
|
|
2139
|
+
case 'list_systems':
|
|
2140
|
+
return dataResult({ systems: designSystems.listSystems() });
|
|
2141
|
+
case 'get_system': {
|
|
2142
|
+
const sys = designSystems.getSystem(input.systemId, input.tokenType);
|
|
2143
|
+
if (!sys) {
|
|
2144
|
+
return errorResult(ErrorCodes.INVALID_PARAMS, `no design system "${input.systemId}" — call action 'list_systems' for the ids`, { systemId: input.systemId }, { toolName: 'pinepaper_design_system' });
|
|
2145
|
+
}
|
|
2146
|
+
return dataResult(sys);
|
|
2147
|
+
}
|
|
2148
|
+
case 'list_easings':
|
|
2149
|
+
return dataResult({ easings: designSystems.listEasings(input.authoredOnly) });
|
|
2150
|
+
case 'list_styles':
|
|
2151
|
+
return dataResult({ styles: designSystems.listStyles() });
|
|
2152
|
+
case 'compose': {
|
|
2153
|
+
let scene;
|
|
2154
|
+
try {
|
|
2155
|
+
scene = designSystems.compose(input.style, {
|
|
2156
|
+
title: input.title, subtitle: input.subtitle, body: input.body,
|
|
2157
|
+
width: input.width, height: input.height, variant: input.variant,
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
catch (composeError) {
|
|
2161
|
+
return errorResult(ErrorCodes.EXECUTION_ERROR, composeError instanceof Error ? composeError.message : 'the style could not compose', { style: input.style }, { toolName: 'pinepaper_design_system' });
|
|
2162
|
+
}
|
|
2163
|
+
if (!scene) {
|
|
2164
|
+
return errorResult(ErrorCodes.INVALID_PARAMS, `"${input.style}" cannot compose — it is a style this surface can name but not build. ` +
|
|
2165
|
+
`Call action 'list_styles' and use one marked composable.`, { style: input.style }, { toolName: 'pinepaper_design_system' });
|
|
2166
|
+
}
|
|
2167
|
+
const ops = designSystems.sceneToOps(scene);
|
|
2168
|
+
if (input.draw === false) {
|
|
2169
|
+
return dataResult({ scene, ops, drawn: false });
|
|
2170
|
+
}
|
|
2171
|
+
const code = codeGenerator.generateDesignCompose(scene, ops, scene.style);
|
|
2172
|
+
return executeOrGenerate(code, `Composed a ${scene.style} scene (${ops.length} items)`, options, 'pinepaper_design_system');
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
break;
|
|
2176
|
+
}
|
|
2177
|
+
case 'pinepaper_stick_figure':
|
|
2178
|
+
case 'pinepaper_stick': {
|
|
2179
|
+
const input = StickInputSchema.parse(args);
|
|
2180
|
+
return executeOrGenerate(codeGenerator.generateStick(input), `Stick ${input.action}`, options, 'pinepaper_stick');
|
|
2181
|
+
}
|
|
2182
|
+
case 'pinepaper_story': {
|
|
2183
|
+
const input = StoryInputSchema.parse(args);
|
|
2184
|
+
return executeOrGenerate(codeGenerator.generateStory(input), `Story: ${input.action}`, options, 'pinepaper_story');
|
|
2185
|
+
}
|
|
2186
|
+
case 'pinepaper_interchange': {
|
|
2187
|
+
const input = InterchangeInputSchema.parse(args);
|
|
2188
|
+
const code = codeGenerator.generateInterchange(input);
|
|
2189
|
+
return executeOrGenerate(code, `Interchange: ${input.action}`, options, 'pinepaper_interchange');
|
|
2190
|
+
}
|
|
2191
|
+
case 'pinepaper_sound': {
|
|
2192
|
+
const input = SoundInputSchema.parse(args);
|
|
2193
|
+
const code = codeGenerator.generateSound(input);
|
|
2194
|
+
const soundDescription = `Sound: ${input.action}`;
|
|
2195
|
+
// A RENDERED SOUNDTRACK IS A FILE, not a response field. 48kHz 16-bit
|
|
2196
|
+
// mono is ~96 KB/s, so a minute is ~5.8 MB of base64 and the ten
|
|
2197
|
+
// minutes the schema allows is ~77 MB — deliverable across the bridge,
|
|
2198
|
+
// useless pasted into a conversation. This mirrors what agent_export
|
|
2199
|
+
// already does for video and PDF, and reuses the same helper rather
|
|
2200
|
+
// than growing a second copy of the save path.
|
|
2201
|
+
if (input.action === 'render_soundtrack') {
|
|
2202
|
+
const execMode = options.executionMode ?? getExecutionMode();
|
|
2203
|
+
if (execMode === 'code' || !options.executeInBrowser) {
|
|
2204
|
+
return executeOrGenerate(code, soundDescription, options, 'pinepaper_sound');
|
|
2205
|
+
}
|
|
2206
|
+
const soundController = options.browserController || getBrowserController();
|
|
2207
|
+
if (!soundController.connected) {
|
|
2208
|
+
try {
|
|
2209
|
+
await soundController.connect();
|
|
2210
|
+
}
|
|
2211
|
+
catch {
|
|
2212
|
+
return executeOrGenerate(code, soundDescription, options, 'pinepaper_sound');
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
const wavResult = await soundController.executeCode(code, false);
|
|
2216
|
+
if (!wavResult.success) {
|
|
2217
|
+
const canvasState = await captureCanvasState(soundController);
|
|
2218
|
+
return errorResult(ErrorCodes.EXECUTION_ERROR, wavResult.error || 'Rendering the soundtrack failed', { code }, { toolName: 'pinepaper_sound', canvasState: canvasState || undefined });
|
|
2219
|
+
}
|
|
2220
|
+
const soundtrack = wavResult.result;
|
|
2221
|
+
// A refusal is a real answer — "no sounds are placed" travels intact
|
|
2222
|
+
// rather than becoming an empty file.
|
|
2223
|
+
if (!soundtrack?.success || !soundtrack.wavBase64) {
|
|
2224
|
+
return executedResult(code, soundtrack, wavResult.screenshot, soundDescription);
|
|
2225
|
+
}
|
|
2226
|
+
try {
|
|
2227
|
+
const { filePath, fileSize } = await saveExportToFile(`data:audio/wav;base64,${soundtrack.wavBase64}`, 'wav', 'soundtrack');
|
|
2228
|
+
const clean = { ...soundtrack, wavBase64: undefined, filePath, fileSize };
|
|
2229
|
+
return {
|
|
2230
|
+
content: [{
|
|
2231
|
+
type: 'text',
|
|
2232
|
+
text: `Soundtrack rendered to file:\n\nFile: ${filePath}\n`
|
|
2233
|
+
+ `Size: ${(fileSize / 1024).toFixed(1)} KB\n`
|
|
2234
|
+
+ `Duration: ${soundtrack.duration}s at ${soundtrack.sampleRate} Hz\n`
|
|
2235
|
+
+ `Mixed: ${soundtrack.placed} sound(s)\n`
|
|
2236
|
+
// The drop count rides on the success path, because a file
|
|
2237
|
+
// that wrote fine while missing sounds is the case nobody
|
|
2238
|
+
// goes looking for.
|
|
2239
|
+
+ (soundtrack.dropped ? `DROPPED: ${soundtrack.incomplete}\n` : '')
|
|
2240
|
+
+ `\nResult: ${JSON.stringify(clean, null, 2)}`,
|
|
2241
|
+
}],
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2244
|
+
catch (saveError) {
|
|
2245
|
+
const stripped = {
|
|
2246
|
+
...soundtrack,
|
|
2247
|
+
wavBase64: `[${soundtrack.byteLength} bytes of WAV — file save failed: ${saveError instanceof Error ? saveError.message : 'unknown error'}]`,
|
|
2248
|
+
};
|
|
2249
|
+
return executedResult(code, stripped, wavResult.screenshot, soundDescription);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
return executeOrGenerate(code, soundDescription, options, 'pinepaper_sound');
|
|
2253
|
+
}
|
|
2254
|
+
case 'pinepaper_motion': {
|
|
2255
|
+
const input = MotionInputSchema.parse(args);
|
|
2256
|
+
const code = codeGenerator.generateMotion(input);
|
|
2257
|
+
const described = input.action === 'list'
|
|
2258
|
+
? 'Listed the generator motion catalogue'
|
|
2259
|
+
: `Applied motion ${input.motion}`;
|
|
2260
|
+
return executeOrGenerate(code, described, options, 'pinepaper_motion');
|
|
2261
|
+
}
|
|
2262
|
+
case 'pinepaper_path': {
|
|
2263
|
+
const input = PathOpInputSchema.parse(args);
|
|
2264
|
+
const code = codeGenerator.generatePathOp(input);
|
|
2265
|
+
const described = input.action === 'boolean'
|
|
2266
|
+
? `Path boolean (${input.op}) over ${input.itemIds?.length ?? 0} items`
|
|
2267
|
+
: `Path ${input.action}${input.itemId ? ` on ${input.itemId}` : ''}`;
|
|
2268
|
+
return executeOrGenerate(code, described, options, 'pinepaper_path');
|
|
2269
|
+
}
|
|
1911
2270
|
case 'pinepaper_agent_analyze': {
|
|
1912
2271
|
const input = AgentAnalyzeInputSchema.parse(args);
|
|
1913
2272
|
const code = codeGenerator.generateAgentAnalyze(input);
|
|
@@ -2041,6 +2400,10 @@ You can now start creating new items on a clean canvas.`,
|
|
|
2041
2400
|
'pinepaper_create_scene',
|
|
2042
2401
|
'pinepaper_manage_scenes',
|
|
2043
2402
|
'pinepaper_scene_playback',
|
|
2403
|
+
'pinepaper_scene_graph',
|
|
2404
|
+
'pinepaper_sequence',
|
|
2405
|
+
'pinepaper_stagger',
|
|
2406
|
+
'pinepaper_flip',
|
|
2044
2407
|
// Agent flow mode tools
|
|
2045
2408
|
'pinepaper_agent_start_job',
|
|
2046
2409
|
'pinepaper_agent_end_job',
|
|
@@ -2084,6 +2447,7 @@ You can now start creating new items on a clean canvas.`,
|
|
|
2084
2447
|
'pinepaper_background',
|
|
2085
2448
|
// Canvas query tools
|
|
2086
2449
|
'pinepaper_query',
|
|
2450
|
+
'pinepaper_query_capabilities',
|
|
2087
2451
|
// Deformation, sprite sheets, interaction
|
|
2088
2452
|
'pinepaper_deform',
|
|
2089
2453
|
'pinepaper_sprite_sheet',
|
|
@@ -2113,7 +2477,7 @@ You can now start creating new items on a clean canvas.`,
|
|
|
2113
2477
|
canvasState = await captureCanvasState(controller);
|
|
2114
2478
|
}
|
|
2115
2479
|
}
|
|
2116
|
-
catch (
|
|
2480
|
+
catch (_stateError) {
|
|
2117
2481
|
// Ignore errors from canvas state capture
|
|
2118
2482
|
}
|
|
2119
2483
|
if (error instanceof Error) {
|