@compilr-dev/sdk 0.29.8 → 0.30.0

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.
@@ -10,7 +10,15 @@
10
10
  * subpath `@compilr-dev/sdk/canvas` (the Desktop renderer needs the control types
11
11
  * + validation without pulling node-only deps — same pattern as `.../errors`).
12
12
  */
13
- export type CanvasType = 'infographic' | 'carousel' | 'board';
13
+ /**
14
+ * `scene` is a 3D scene (3d-canvas-spec §3): its `content` is scene JSON (see `scene.ts`), not
15
+ * HTML — it is edited only through the scene writer / `scene_*` tools and drawn by the host's
16
+ * 3D viewer, never the HTML iframe.
17
+ */
18
+ export type CanvasType = 'infographic' | 'carousel' | 'board' | 'scene';
19
+ /** The canvas types whose content is HTML/SVG (everything but `scene`). */
20
+ export type HtmlCanvasType = Exclude<CanvasType, 'scene'>;
21
+ export declare const HTML_CANVAS_TYPES: readonly HtmlCanvasType[];
14
22
  /** A control's runtime value. */
15
23
  export type ParamValue = string | number | boolean;
16
24
  export interface SliderControl {
@@ -10,6 +10,7 @@
10
10
  * subpath `@compilr-dev/sdk/canvas` (the Desktop renderer needs the control types
11
11
  * + validation without pulling node-only deps — same pattern as `.../errors`).
12
12
  */
13
+ export const HTML_CANVAS_TYPES = ['infographic', 'carousel', 'board'];
13
14
  export const DEFAULT_PAGE_SIZE = '16:9';
14
15
  /** Pixel dimensions per page size (used for render aspect + export sizing). */
15
16
  export const PAGE_SIZE_DIMENSIONS = {
@@ -4,7 +4,7 @@
4
4
  * Maps each tool group (from tool-config.ts) to a capability pack with
5
5
  * associated prompt modules and usage guidance.
6
6
  *
7
- * 24 packs total, matching 1:1 with TOOL_GROUPS keys.
7
+ * 33 packs total, matching 1:1 with TOOL_GROUPS keys.
8
8
  */
9
9
  import type { CapabilityPack } from './types.js';
10
10
  /**
@@ -4,7 +4,7 @@
4
4
  * Maps each tool group (from tool-config.ts) to a capability pack with
5
5
  * associated prompt modules and usage guidance.
6
6
  *
7
- * 24 packs total, matching 1:1 with TOOL_GROUPS keys.
7
+ * 33 packs total, matching 1:1 with TOOL_GROUPS keys.
8
8
  */
9
9
  /**
10
10
  * All capability pack definitions.
@@ -261,10 +261,36 @@ export const CAPABILITY_PACKS = {
261
261
  // ⚠️ Real images go in as ASSETS, never as base64 in the markup.
262
262
  'When the user has supplied real images, put them in with canvas_asset_add and reference ' +
263
263
  'them as <img src="asset:REF"> — never paste base64, never draw a grey box instead. ' +
264
- 'Create/replace with canvas_write (raw HTML only — host injects the CSP; inline <style>/<script> only, no network/remote assets; optional Tweaks controls manifest). EDIT via canvas_get (outline=true or startLine/maxLines) then canvas_edit (str_replace/append/prepend) — never re-send the whole document. After authoring, canvas_validate (static) then canvas_screenshot to SEE the render (vision models) — fix overflow/readability with canvas_edit.',
265
- estimatedPromptTokens: 190,
264
+ 'Create/replace with canvas_write (raw HTML only — host injects the CSP; inline <style>/<script> only, no network/remote assets; optional Tweaks controls manifest). EDIT via canvas_get (outline=true or startLine/maxLines) then canvas_edit (str_replace/append/prepend) — never re-send the whole document. After authoring, canvas_validate (static) then canvas_screenshot to SEE the render (vision models) — fix overflow/readability with canvas_edit. ' +
265
+ // Last, so the guide-first order above is untouched (canvas-guide.test).
266
+ '3D scenes are JSON, not HTML — they use the scene tools (scene_create, scene_add_object…), never these.',
267
+ estimatedPromptTokens: 215,
266
268
  estimatedToolTokens: 1500,
267
269
  },
270
+ // A separate pack, not folded into `canvas`: that pack is ~1.5k tokens of HTML guidance a
271
+ // scene agent never needs, and packs load lazily (3d-canvas-spec §6.3).
272
+ scene: {
273
+ id: 'scene',
274
+ label: '3D Scene',
275
+ tools: [
276
+ 'scene_create',
277
+ 'scene_get',
278
+ 'scene_add_object',
279
+ 'scene_update_object',
280
+ 'scene_remove_object',
281
+ 'scene_set_camera',
282
+ 'scene_screenshot',
283
+ ],
284
+ readOnly: false,
285
+ promptModules: ['platform-tool-hints'],
286
+ promptSnippet: "3D scenes are JSON, not code. Units metres; position.y is the object's BOTTOM (0 = on the floor); " +
287
+ 'rotation in degrees (rotate about Y). Types: box(size w,h,d), sphere(radius), cylinder/cone(radius, ' +
288
+ 'height), torus(radius, tube; flat), extrude(points [[x,z],…], height) for walls and floors. Read with ' +
289
+ 'scene_get before editing — the user edits too. Add up to 50 objects per scene_add_object call ' +
290
+ '(objects: [...]). Keep neutral colours unless asked. After building, scene_screenshot to check.',
291
+ estimatedPromptTokens: 120,
292
+ estimatedToolTokens: 1400,
293
+ },
268
294
  plans: {
269
295
  id: 'plans',
270
296
  label: 'Planning',
@@ -476,6 +502,7 @@ export const FORBIDDEN_PACK_SUGGESTIONS = {
476
502
  backlog_read: '$pm',
477
503
  documents: '$pm',
478
504
  canvas: '$default',
505
+ scene: '$default',
479
506
  plans: '$pm',
480
507
  search: '$arch',
481
508
  dependencies: '$arch',
package/dist/index.d.ts CHANGED
@@ -53,7 +53,7 @@ export { CapabilityManager, CapabilityContext, createCapabilityHook, autoDetectC
53
53
  export type { CapabilityPack, CapabilityTier, LoadedCapability, CapabilityCatalogEntry, CapabilityLoadResult, CapabilityManagerConfig, CapabilityContextConfig, CapabilityHookConfig, ConditionalModule, AutoDetectResult, } from './capabilities/index.js';
54
54
  export { SystemPromptBuilder, buildSystemPrompt, detectGitRepository, getModuleStats, ALL_MODULES, IDENTITY_MODULE, STYLE_MODULE, TASK_EXECUTION_MODULE, TODO_MANAGEMENT_MODULE, TOOL_USAGE_DIRECT_MODULE, TOOL_USAGE_HINTS_MODULE, PLATFORM_TOOL_HINTS_MODULE, FACTORY_TOOL_HINTS_MODULE, TOOL_USAGE_META_MODULE, DELEGATION_MODULE, GIT_SAFETY_MODULE, SUGGEST_MODULE, IMPORTANT_RULES_MODULE, VISUAL_OUTPUT_MODULE, ENVIRONMENT_MODULE, shouldIncludeModule, getEstimatedTokensForConditions, getTotalEstimatedTokens, } from './system-prompt/index.js';
55
55
  export type { SystemPromptContext, BuildResult, SystemPromptModule, ModuleConditions, } from './system-prompt/index.js';
56
- export type { ProjectType, ProjectStatus, RepoPattern, WorkflowMode, LifecycleState, WorkItemType, WorkItemStatus, WorkItemPriority, GuidedStep, DocumentType, PlanStatus, Project, WorkItem, ProjectDocument, Plan, PlanSummary, PlanWithWorkItem, HistoryEntry, CreateProjectInput, UpdateProjectInput, ProjectListOptions, CreateWorkItemInput, UpdateWorkItemInput, QueryWorkItemsInput, CreateDocumentInput, UpdateDocumentInput, CreatePlanInput, UpdatePlanInput, ListPlansOptions, WorkItemQueryResult, ProjectListResult, BulkCreateItem, WorkItemComment, CreateCommentInput, UpdateCommentInput, IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, ICanvasRepository, IAnchorService, IArtifactService, IEpisodeService, AnchorData, ArtifactType, ArtifactData, ArtifactSummaryData, WorkEpisode, ProjectWorkSummary, PlatformContext, PlatformToolsConfig, PlatformHooks, StepCriteria, } from './platform/index.js';
56
+ export type { ProjectType, ProjectStatus, RepoPattern, WorkflowMode, LifecycleState, WorkItemType, WorkItemStatus, WorkItemPriority, GuidedStep, DocumentType, PlanStatus, Project, WorkItem, ProjectDocument, Plan, PlanSummary, PlanWithWorkItem, HistoryEntry, CreateProjectInput, UpdateProjectInput, ProjectListOptions, CreateWorkItemInput, UpdateWorkItemInput, QueryWorkItemsInput, CreateDocumentInput, UpdateDocumentInput, CreatePlanInput, UpdatePlanInput, ListPlansOptions, WorkItemQueryResult, ProjectListResult, BulkCreateItem, WorkItemComment, CreateCommentInput, UpdateCommentInput, IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, ICanvasRepository, IAnchorService, IArtifactService, IEpisodeService, AnchorData, ArtifactType, ArtifactData, ArtifactSummaryData, ICanvasRenderer, RenderedCanvasImage, WorkEpisode, ProjectWorkSummary, PlatformContext, PlatformToolsConfig, PlatformHooks, StepCriteria, } from './platform/index.js';
57
57
  export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemRepository, SQLiteDocumentRepository, SQLitePlanRepository, SQLiteCommentRepository, getDatabase, closeDatabase, closeAllDatabases, databaseExists, SCHEMA_VERSION, SCHEMA_SQL, } from './platform/index.js';
58
58
  export type { SQLiteRepositories, CreateSQLiteRepositoriesOptions, ProjectDeleteHooks, ProjectRecord, WorkItemRecord, ProjectDocumentRecord, WorkItemCommentRecord, } from './platform/index.js';
59
59
  export { createAskUserTool, createAskUserSimpleTool, createProposeAlternativesTool, createInteractiveFlowTool, validateFlow, INTERACTIVE_FLOW_INPUT_SCHEMA, } from './tools/index.js';
@@ -64,7 +64,7 @@ export { detectProject, suggestProjectType, detectCommon } from './detection/ind
64
64
  export type { DetectProjectOptions, DetectionResult, ContentSummary } from './detection/index.js';
65
65
  export { createGuideTool, SHARED_GUIDE_ENTRIES, searchGuideEntries, topicToGuideEntry, } from './guide/index.js';
66
66
  export type { GuideEntry, ContentTopic, ContentSection, GuideToolConfig } from './guide/index.js';
67
- export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
67
+ export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createSceneTools, SCENE_TOOL_NAMES, createSceneWriter, SCENE_JOURNAL_SIZE, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
68
68
  export type { ProjectAnchorStoreConfig, FileArtifactServiceConfig, ImageToolsConfig, ImageResizer, } from './platform/index.js';
69
69
  export { STEP_ORDER, GUIDED_STEP_CRITERIA, getNextStep, isValidTransition, getStepCriteria, formatStepDisplay, getStepNumber, } from './platform/index.js';
70
70
  export type { CustomSkill, CompilrSkillExtension, ForkedFromMarker, InstalledFromMarker, SkillEligibilityContext, SkillCollision, SkillDiffLine, SkillValidationIssue, ScopeConfig, SkillResolution, } from './skills/index.js';
@@ -96,6 +96,9 @@ export { defineTool, createSuccessResult, createErrorResult, mergeHooks, createL
96
96
  export { classifyAgentError, isApiKeyError } from './errors/classify.js';
97
97
  export type { AgentErrorCategory, AgentErrorInfo } from './errors/classify.js';
98
98
  export { validateControlManifest, seedValues } from './canvas/index.js';
99
+ export { validateScene, normalizeScene, serializeScene, emptyScene, sceneBounds, fitCamera, liftFor, describeScene, applySceneOp, applySceneOps, formatUserEditNotice, SCENE_MAX_OBJECTS, SCENE_MAX_BYTES, SCENE_MAX_BATCH, HTML_CANVAS_TYPES, } from './canvas/index.js';
100
+ export type { HtmlCanvasType, SceneFile, SceneObject, SceneObjectType, SceneCamera, SceneLight, SceneMaterial, SceneOp, SceneChange, SceneEditSource, } from './canvas/index.js';
101
+ export type { ISceneWriter, SceneReadResult, SceneApplyResult, SceneCreateResult, SceneLastEdit, } from './platform/index.js';
99
102
  export type { CanvasType, ParamValue, Control, ControlManifest, CanvasRecord, CanvasSummary, CreateCanvasInput, UpdateCanvasInput, SliderControl, NumberControl, ToggleControl, SelectControl, ColorControl, TextControl, ManifestValidationResult, } from './canvas/index.js';
100
103
  export type { Tool, HooksConfig, AgentEvent, Message, LLMProvider, AnchorInput, ToolExecutionResult, AgentRunResult, PermissionHandler, PermissionHandlerResponse, ToolPermission, AgentTypeConfig, GuardrailTriggeredHandler, BeforeLLMHookResult, BeforeToolHook, BeforeToolHookResult, AfterToolHook, AgentState, AgentConfig, SessionInfo, Anchor, AnchorScope, AnchorClearOptions, AnchorPriority, AnchorQueryOptions, FileAccessType, FileAccess, GuardrailResult, GuardrailContext, MCPClient, MCPToolDefinition, } from '@compilr-dev/agents';
101
104
  export { DEFAULT_PERMISSION_RULES, WRITE_TOOLS, findMatchingRule, permissionModeLabel, permissionLevelLabel, } from './permissions.js';
package/dist/index.js CHANGED
@@ -147,7 +147,7 @@ export { createGuideTool, SHARED_GUIDE_ENTRIES, searchGuideEntries, topicToGuide
147
147
  // =============================================================================
148
148
  // Platform Tools (runtime — createPlatformTools factory + individual factories)
149
149
  // =============================================================================
150
- export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
150
+ export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createSceneTools, SCENE_TOOL_NAMES, createSceneWriter, SCENE_JOURNAL_SIZE, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
151
151
  // =============================================================================
152
152
  // Platform Workflow (pure step-criteria logic)
153
153
  // =============================================================================
@@ -207,6 +207,8 @@ export { classifyAgentError, isApiKeyError } from './errors/classify.js';
207
207
  // Canvas contract — shared visual-canvas types + controls-manifest validation.
208
208
  // Also exposed at the renderer-safe subpath `@compilr-dev/sdk/canvas`.
209
209
  export { validateControlManifest, seedValues } from './canvas/index.js';
210
+ // 3D scenes (the `scene` canvas type) — contract + pure ops. Also at `@compilr-dev/sdk/canvas`.
211
+ export { validateScene, normalizeScene, serializeScene, emptyScene, sceneBounds, fitCamera, liftFor, describeScene, applySceneOp, applySceneOps, formatUserEditNotice, SCENE_MAX_OBJECTS, SCENE_MAX_BYTES, SCENE_MAX_BATCH, HTML_CANVAS_TYPES, } from './canvas/index.js';
210
212
  // =============================================================================
211
213
  // Shared Permission Defaults & Utilities
212
214
  // =============================================================================
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, ICanvasRepository } from './repositories.js';
8
8
  import type { IAnchorService, IArtifactService, IEpisodeService, ICanvasRenderer } from './services.js';
9
+ import type { ISceneWriter } from './scene-writer.js';
9
10
  export interface PlatformContext {
10
11
  readonly projects: IProjectRepository;
11
12
  readonly workItems: IWorkItemRepository;
@@ -17,8 +18,15 @@ export interface PlatformContext {
17
18
  readonly episodes?: IEpisodeService;
18
19
  readonly canvases?: ICanvasRepository;
19
20
  readonly comments?: ICommentRepository;
20
- /** Host-provided canvas → image renderer (for canvas_screenshot). */
21
+ /** Host-provided canvas → image renderer (for canvas_screenshot / scene_screenshot). */
21
22
  readonly canvasRenderer?: ICanvasRenderer;
23
+ /**
24
+ * The scene writer (3d-canvas-spec §4.2). ⚠️ Hosts that edit scenes themselves (Desktop's
25
+ * inspector) MUST pass the same instance they use, or the two paths hold separate locks and
26
+ * journals — edits interleave and the agent is never told about the user's changes. When
27
+ * absent, `createPlatformTools` builds one from `canvases`.
28
+ */
29
+ readonly sceneWriter?: ISceneWriter;
22
30
  }
23
31
  export interface PlatformHooks {
24
32
  /** Called when a project is resolved/selected as the current project */
@@ -53,6 +61,15 @@ export interface PlatformToolsConfig {
53
61
  cwd?: string | (() => string | undefined | null);
54
62
  /** Optional hooks for CLI-specific side effects */
55
63
  hooks?: PlatformHooks;
64
+ /**
65
+ * Which canvas surfaces get tools when `context.canvases` is present (both default true).
66
+ * The CLI passes `{ html: false, scene: true }`: HTML canvases have no terminal surface,
67
+ * but scene tools are data edits, useful without a viewer (3d-canvas-spec §7).
68
+ */
69
+ canvasSurfaces?: {
70
+ html?: boolean;
71
+ scene?: boolean;
72
+ };
56
73
  }
57
74
  /**
58
75
  * Resolve a `cwd` that may be a value or a getter.
@@ -3,9 +3,11 @@
3
3
  */
4
4
  export type { ProjectType, ProjectStatus, RepoPattern, WorkflowMode, LifecycleState, WorkItemType, WorkItemStatus, WorkItemPriority, GuidedStep, DocumentType, PlanStatus, Project, WorkItem, ProjectDocument, Plan, PlanSummary, PlanWithWorkItem, HistoryEntry, CreateProjectInput, UpdateProjectInput, ProjectListOptions, CreateWorkItemInput, UpdateWorkItemInput, QueryWorkItemsInput, CreateDocumentInput, UpdateDocumentInput, CreatePlanInput, UpdatePlanInput, ListPlansOptions, WorkItemQueryResult, ProjectListResult, BulkCreateItem, WorkItemComment, CreateCommentInput, UpdateCommentInput, } from './types.js';
5
5
  export type { IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, ICanvasRepository, } from './repositories.js';
6
- export type { IAnchorService, IArtifactService, IEpisodeService, AnchorData, ArtifactType, ArtifactData, ArtifactSummaryData, WorkEpisode, ProjectWorkSummary, } from './services.js';
6
+ export type { IAnchorService, IArtifactService, IEpisodeService, ICanvasRenderer, RenderedCanvasImage, AnchorData, ArtifactType, ArtifactData, ArtifactSummaryData, WorkEpisode, ProjectWorkSummary, } from './services.js';
7
7
  export type { PlatformContext, PlatformToolsConfig, PlatformHooks } from './context.js';
8
- export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createImageTools, } from './tools/index.js';
8
+ export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createSceneTools, SCENE_TOOL_NAMES, sceneOutline, createImageTools, } from './tools/index.js';
9
+ export { createSceneWriter, SCENE_JOURNAL_SIZE } from './scene-writer.js';
10
+ export type { ISceneWriter, SceneReadResult, SceneApplyResult, SceneCreateResult, SceneLastEdit, } from './scene-writer.js';
9
11
  export type { ImageToolsConfig, ImageResizer } from './tools/index.js';
10
12
  export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemRepository, SQLiteDocumentRepository, SQLitePlanRepository, SQLiteCommentRepository, getDatabase, closeDatabase, closeAllDatabases, databaseExists, SCHEMA_VERSION, SCHEMA_SQL, } from './sqlite/index.js';
11
13
  export type { SQLiteRepositories, CreateSQLiteRepositoriesOptions, ProjectDeleteHooks, ProjectRecord, WorkItemRecord, ProjectDocumentRecord, WorkItemCommentRecord, } from './sqlite/index.js';
@@ -2,7 +2,10 @@
2
2
  * Platform — Repository interfaces, data models, tools, and workflow.
3
3
  */
4
4
  // Platform tools (runtime)
5
- export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createImageTools, } from './tools/index.js';
5
+ export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createCanvasTools, createSceneTools, SCENE_TOOL_NAMES, sceneOutline, createImageTools, } from './tools/index.js';
6
+ // The scene writer — one per host process, shared by the scene tools and the host's own
7
+ // edit path (Desktop's scene:apply IPC). See 3d-canvas-spec §4.2.
8
+ export { createSceneWriter, SCENE_JOURNAL_SIZE } from './scene-writer.js';
6
9
  // SQLite implementations (concrete repositories)
7
10
  export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemRepository, SQLiteDocumentRepository, SQLitePlanRepository, SQLiteCommentRepository, getDatabase, closeDatabase, closeAllDatabases, databaseExists, SCHEMA_VERSION, SCHEMA_SQL, } from './sqlite/index.js';
8
11
  // File-based anchor service (shared by CLI and Desktop)
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The scene writer — the ONE path every scene edit takes (3d-canvas-spec §4.2).
3
+ *
4
+ * Agent tools and the user's inspector (Desktop's `scene:apply` IPC) both call the same
5
+ * instance, so both land in the same stored record and each sees the other's changes.
6
+ *
7
+ * - Serialised per canvas: `apply` is read → ops → validate → write, and the repository's
8
+ * `getById` and `update` are two awaits. Without the lock, two callers (an agent tool and an
9
+ * inspector edit, both in Desktop's main process) interleave between them and one edit is
10
+ * silently lost. Cross-PROCESS writers (CLI + Desktop on one projects.db) are not serialised —
11
+ * the same exposure every repository has today.
12
+ * - Atomic batches: all ops validate against the final scene, or nothing is stored.
13
+ * - Revisions: an in-memory per-canvas counter (0 at process start). Not storage — it exists
14
+ * for the user-edit notice and the inspector's stale-read check.
15
+ * - Journal: the last 50 USER changes per canvas, so an agent's next scene tool result can say
16
+ * "the user moved the sofa" instead of the agent overwriting it from a stale picture.
17
+ *
18
+ * One instance per host process. Hosts create it and pass it as `PlatformContext.sceneWriter`
19
+ * AND use it in their own edit path; `createPlatformTools` builds one only when absent.
20
+ */
21
+ import type { ICanvasRepository } from './repositories.js';
22
+ import type { CanvasRecord } from '../canvas/types.js';
23
+ import { type SceneFile, type SceneObject } from '../canvas/scene.js';
24
+ import { type SceneChange, type SceneEditSource, type SceneOp } from '../canvas/scene-ops.js';
25
+ /** How many user changes are kept per canvas. */
26
+ export declare const SCENE_JOURNAL_SIZE = 50;
27
+ export interface SceneLastEdit {
28
+ rev: number;
29
+ source: SceneEditSource;
30
+ ids: string[];
31
+ }
32
+ export type SceneReadResult = {
33
+ ok: true;
34
+ canvasId: number;
35
+ projectId: number;
36
+ title: string;
37
+ scene: SceneFile;
38
+ rev: number;
39
+ /** The most recent edit this process made (for P2's grow-in of agent-added ids). */
40
+ lastEdit?: SceneLastEdit;
41
+ } | {
42
+ ok: false;
43
+ reason: 'not_found' | 'not_scene' | 'invalid';
44
+ error: string;
45
+ };
46
+ export type SceneApplyResult = {
47
+ ok: true;
48
+ canvasId: number;
49
+ title: string;
50
+ scene: SceneFile;
51
+ rev: number;
52
+ summaries: string[];
53
+ ids: string[];
54
+ warnings: string[];
55
+ /** Objects removed by this apply, with their former index — for an undo (Q-4). */
56
+ removed: {
57
+ index: number;
58
+ object: SceneObject;
59
+ }[];
60
+ } | {
61
+ ok: false;
62
+ error: string;
63
+ };
64
+ export type SceneCreateResult = {
65
+ ok: true;
66
+ canvas: CanvasRecord;
67
+ scene: SceneFile;
68
+ rev: number;
69
+ } | {
70
+ ok: false;
71
+ error: string;
72
+ };
73
+ export interface ISceneWriter {
74
+ /** Create a scene canvas. `scene` is optional (empty scene) and validated when given. */
75
+ create(input: {
76
+ projectId: number;
77
+ title: string;
78
+ scene?: unknown;
79
+ }): Promise<SceneCreateResult>;
80
+ /** The latest stored scene and its revision. Never cached. */
81
+ read(canvasId: number): Promise<SceneReadResult>;
82
+ /** Apply ops atomically under the canvas's lock. */
83
+ apply(canvasId: number, ops: SceneOp[], source: SceneEditSource): Promise<SceneApplyResult>;
84
+ /** User changes with rev > `rev` (oldest first). */
85
+ userEditsSince(canvasId: number, rev: number): SceneChange[];
86
+ /**
87
+ * User changes the agent (`agentKey`, default "") has not been told about, and marks them
88
+ * told. Call it only after a SUCCESSFUL scene tool call — the notice rides on successes. An
89
+ * agent's first contact with a canvas returns nothing: it has no stale picture to correct.
90
+ */
91
+ consumeUserEdits(canvasId: number, agentKey?: string): SceneChange[];
92
+ /** The current in-memory revision (0 when this process has not written it). */
93
+ revision(canvasId: number): number;
94
+ }
95
+ export declare function createSceneWriter(repo: ICanvasRepository): ISceneWriter;
@@ -0,0 +1,184 @@
1
+ /**
2
+ * The scene writer — the ONE path every scene edit takes (3d-canvas-spec §4.2).
3
+ *
4
+ * Agent tools and the user's inspector (Desktop's `scene:apply` IPC) both call the same
5
+ * instance, so both land in the same stored record and each sees the other's changes.
6
+ *
7
+ * - Serialised per canvas: `apply` is read → ops → validate → write, and the repository's
8
+ * `getById` and `update` are two awaits. Without the lock, two callers (an agent tool and an
9
+ * inspector edit, both in Desktop's main process) interleave between them and one edit is
10
+ * silently lost. Cross-PROCESS writers (CLI + Desktop on one projects.db) are not serialised —
11
+ * the same exposure every repository has today.
12
+ * - Atomic batches: all ops validate against the final scene, or nothing is stored.
13
+ * - Revisions: an in-memory per-canvas counter (0 at process start). Not storage — it exists
14
+ * for the user-edit notice and the inspector's stale-read check.
15
+ * - Journal: the last 50 USER changes per canvas, so an agent's next scene tool result can say
16
+ * "the user moved the sofa" instead of the agent overwriting it from a stale picture.
17
+ *
18
+ * One instance per host process. Hosts create it and pass it as `PlatformContext.sceneWriter`
19
+ * AND use it in their own edit path; `createPlatformTools` builds one only when absent.
20
+ */
21
+ import { emptyScene, normalizeScene, serializeScene, validateScene, } from '../canvas/scene.js';
22
+ import { applySceneOps, formatValidationErrors, } from '../canvas/scene-ops.js';
23
+ /** How many user changes are kept per canvas. */
24
+ export const SCENE_JOURNAL_SIZE = 50;
25
+ function htmlCanvasError(c) {
26
+ return (`Canvas ${String(c.id)} is an HTML canvas (${c.type}), not a 3D scene; use canvas_edit ` +
27
+ '(or canvas_write) for it. 3D scenes are made with scene_create.');
28
+ }
29
+ function notFound(canvasId) {
30
+ return `Canvas ${String(canvasId)} not found. List canvases with canvas_list.`;
31
+ }
32
+ /** Parse + validate stored content. */
33
+ function parseStored(c) {
34
+ let raw;
35
+ try {
36
+ raw = JSON.parse(c.content);
37
+ }
38
+ catch (e) {
39
+ return {
40
+ error: `Canvas ${String(c.id)}'s stored scene is not valid JSON (${e instanceof Error ? e.message : String(e)}).`,
41
+ };
42
+ }
43
+ const v = validateScene(raw);
44
+ if (!v.ok) {
45
+ return {
46
+ error: `Canvas ${String(c.id)}'s stored scene is invalid:\n${v.errors
47
+ .slice(0, 8)
48
+ .map((e) => `- ${e}`)
49
+ .join('\n')}`,
50
+ };
51
+ }
52
+ return { scene: normalizeScene(v.scene) };
53
+ }
54
+ export function createSceneWriter(repo) {
55
+ const revs = new Map();
56
+ const journals = new Map();
57
+ const lastEdits = new Map();
58
+ /** `${canvasId}:${agentKey}` → the rev that agent was last told about. */
59
+ const seen = new Map();
60
+ /** Per-canvas promise chain — the in-process mutex. */
61
+ const chains = new Map();
62
+ function withLock(canvasId, fn) {
63
+ const prev = chains.get(canvasId) ?? Promise.resolve();
64
+ const run = prev.then(fn);
65
+ const tail = run.then(() => undefined, () => undefined);
66
+ chains.set(canvasId, tail);
67
+ void tail.then(() => {
68
+ if (chains.get(canvasId) === tail)
69
+ chains.delete(canvasId);
70
+ });
71
+ return run;
72
+ }
73
+ const revision = (canvasId) => revs.get(canvasId) ?? 0;
74
+ return {
75
+ revision,
76
+ async create(input) {
77
+ const title = input.title.trim();
78
+ if (!title)
79
+ return { ok: false, error: 'A scene needs a title.' };
80
+ let candidate;
81
+ if (input.scene === undefined || input.scene === null) {
82
+ candidate = emptyScene(title);
83
+ }
84
+ else if (typeof input.scene === 'object' && !Array.isArray(input.scene)) {
85
+ // Convenience: the envelope fields default from the title, so an agent can pass
86
+ // just { objects: [...] }. Everything else is validated as given.
87
+ candidate = { version: 1, units: 'm', name: title.slice(0, 80), ...input.scene };
88
+ }
89
+ else {
90
+ candidate = input.scene;
91
+ }
92
+ const v = validateScene(candidate);
93
+ if (!v.ok)
94
+ return { ok: false, error: formatValidationErrors(v.errors) };
95
+ const scene = normalizeScene(v.scene);
96
+ const canvas = await repo.create({
97
+ projectId: input.projectId,
98
+ type: 'scene',
99
+ title,
100
+ content: serializeScene(scene),
101
+ controls: { controls: [] },
102
+ values: {},
103
+ });
104
+ revs.set(canvas.id, 0);
105
+ return { ok: true, canvas, scene, rev: 0 };
106
+ },
107
+ async read(canvasId) {
108
+ const c = await repo.getById(canvasId);
109
+ if (!c)
110
+ return { ok: false, reason: 'not_found', error: notFound(canvasId) };
111
+ if (c.type !== 'scene')
112
+ return { ok: false, reason: 'not_scene', error: htmlCanvasError(c) };
113
+ const parsed = parseStored(c);
114
+ if ('error' in parsed)
115
+ return { ok: false, reason: 'invalid', error: parsed.error };
116
+ return {
117
+ ok: true,
118
+ canvasId: c.id,
119
+ projectId: c.projectId,
120
+ title: c.title,
121
+ scene: parsed.scene,
122
+ rev: revision(canvasId),
123
+ lastEdit: lastEdits.get(canvasId),
124
+ };
125
+ },
126
+ apply(canvasId, ops, source) {
127
+ return withLock(canvasId, async () => {
128
+ const c = await repo.getById(canvasId);
129
+ if (!c)
130
+ return { ok: false, error: notFound(canvasId) };
131
+ if (c.type !== 'scene')
132
+ return { ok: false, error: htmlCanvasError(c) };
133
+ const parsed = parseStored(c);
134
+ let current;
135
+ if ('error' in parsed) {
136
+ // A broken record can still be REPLACED — that is the way out of it.
137
+ if (ops[0]?.op !== 'replace')
138
+ return { ok: false, error: parsed.error };
139
+ current = null;
140
+ }
141
+ else
142
+ current = parsed.scene;
143
+ const r = applySceneOps(current, ops, { source, canvasId });
144
+ if (!r.ok)
145
+ return { ok: false, error: r.error };
146
+ const updated = await repo.update(canvasId, { content: serializeScene(r.scene) });
147
+ if (!updated)
148
+ return { ok: false, error: notFound(canvasId) };
149
+ const rev = revision(canvasId) + 1;
150
+ revs.set(canvasId, rev);
151
+ lastEdits.set(canvasId, { rev, source, ids: r.ids });
152
+ if (source.kind === 'user' && r.changes.length > 0) {
153
+ const journal = journals.get(canvasId) ?? [];
154
+ for (const ch of r.changes)
155
+ journal.push({ ...ch, rev });
156
+ journals.set(canvasId, journal.slice(-SCENE_JOURNAL_SIZE));
157
+ }
158
+ return {
159
+ ok: true,
160
+ canvasId,
161
+ title: updated.title,
162
+ scene: r.scene,
163
+ rev,
164
+ summaries: r.summaries,
165
+ ids: r.ids,
166
+ warnings: r.warnings,
167
+ removed: r.removed,
168
+ };
169
+ });
170
+ },
171
+ userEditsSince(canvasId, rev) {
172
+ return (journals.get(canvasId) ?? []).filter((c) => c.rev > rev);
173
+ },
174
+ consumeUserEdits(canvasId, agentKey = '') {
175
+ const key = `${String(canvasId)}:${agentKey}`;
176
+ const last = seen.get(key);
177
+ const now = revision(canvasId);
178
+ seen.set(key, now);
179
+ if (last === undefined)
180
+ return [];
181
+ return (journals.get(canvasId) ?? []).filter((c) => c.rev > last && c.rev <= now);
182
+ },
183
+ };
184
+ }
@@ -5,6 +5,8 @@
5
5
  * FileEpisodeStore) so tool definitions can live in the SDK.
6
6
  */
7
7
  import type { AnchorPriority, WorkEpisode, ProjectWorkSummary } from '@compilr-dev/agents';
8
+ import type { HtmlCanvasType } from '../canvas/types.js';
9
+ import type { SceneFile } from '../canvas/scene.js';
8
10
  export interface AnchorData {
9
11
  id: string;
10
12
  content: string;
@@ -115,7 +117,23 @@ export interface ICanvasRenderer {
115
117
  renderToImage(input: {
116
118
  /** Raw canvas HTML/SVG (the stored content). */
117
119
  content: string;
118
- /** Canvas type — the host sizes/wraps accordingly. */
119
- type: 'infographic' | 'carousel' | 'board';
120
+ /**
121
+ * Canvas type — the host sizes/wraps accordingly. Derived from `CanvasType` (it used to
122
+ * be a second hand-written copy of the union); scenes never reach here — they go to
123
+ * `renderSceneToImage`.
124
+ */
125
+ type: HtmlCanvasType;
126
+ }): Promise<RenderedCanvasImage>;
127
+ /**
128
+ * Render a 3D scene (3d-canvas-spec §8) for `scene_screenshot` / `canvas_screenshot`.
129
+ * OPTIONAL: a host without a 3D viewer omits it and the tools answer "not available in
130
+ * this environment". The scene is validated and normalised; the host uses its own camera
131
+ * when present, else `fitCamera(sceneBounds(scene))`, grid on, neutral light theme.
132
+ */
133
+ renderSceneToImage?(input: {
134
+ scene: SceneFile;
135
+ /** CSS px. Defaults chosen by the tool: 1280 × 800. */
136
+ width: number;
137
+ height: number;
120
138
  }): Promise<RenderedCanvasImage>;
121
139
  }