@hraness/slopcamera 3.2.7 → 3.3.1
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/NOTICE.md +1 -7
- package/PRIVACY.md +16 -7
- package/README.md +65 -20
- package/apps/desktop/README.md +14 -130
- package/apps/desktop/application/context.ts +6 -9
- package/apps/desktop/application/default-registry.ts +26 -10
- package/apps/desktop/application/operation.ts +14 -5
- package/apps/desktop/application/operations/index.ts +4 -1
- package/apps/desktop/application/operations/spatial-behavior.ts +151 -0
- package/apps/desktop/application/operations/spatial-direction.ts +222 -0
- package/apps/desktop/application/operations/spatial-rendered-audit.ts +174 -0
- package/apps/desktop/application/operations/spatial-review.ts +194 -0
- package/apps/desktop/application/operations/spatial-scene.ts +25 -1
- package/apps/desktop/application/registry.ts +2 -2
- package/apps/desktop/application/spatial-asset-admission.ts +201 -0
- package/apps/desktop/application/spatial-assets.ts +160 -31
- package/apps/desktop/application/spatial-render.ts +217 -20
- package/apps/desktop/application/spatial-rendered-audit.ts +273 -0
- package/apps/desktop/application/spatial-review.ts +361 -0
- package/apps/desktop/application/spatial-spz.ts +48 -3
- package/apps/desktop/application/spatial-world-import.ts +26 -5
- package/apps/desktop/application/spatial-world-metadata.ts +105 -0
- package/apps/desktop/cli/args.ts +528 -97
- package/apps/desktop/cli/capability-manifest.ts +374 -0
- package/apps/desktop/cli/cinema-renderer.ts +608 -0
- package/apps/desktop/cli/command-host-resources.ts +31 -2
- package/apps/desktop/cli/commands.ts +1215 -70
- package/apps/desktop/cli/gateway-review-provider.ts +334 -0
- package/apps/desktop/cli/help.ts +183 -23
- package/apps/desktop/cli/html-scene.ts +139 -28
- package/apps/desktop/cli/io.ts +2 -1
- package/apps/desktop/cli/main.ts +26 -50
- package/apps/desktop/cli/menubar.ts +244 -0
- package/apps/desktop/cli/music-analysis-service.ts +61 -0
- package/apps/desktop/cli/portable-surface.ts +18 -5
- package/apps/desktop/cli/spatial-asset-service.ts +124 -0
- package/apps/desktop/cli/spatial-design-service.ts +237 -0
- package/apps/desktop/cli/spatial-generate-service.ts +370 -0
- package/apps/desktop/cli/spatial-scene-service.ts +268 -4
- package/apps/desktop/cli/workflow-code.ts +1 -1
- package/apps/desktop/code/application-node-planner.ts +0 -47
- package/apps/desktop/code/host-source-layout.ts +46 -0
- package/apps/desktop/code/public.ts +0 -1
- package/apps/desktop/code/runtime-identity.ts +50 -8
- package/apps/desktop/code/semantic-builder.ts +24 -43
- package/apps/desktop/code/source-bundle.ts +23 -6
- package/apps/desktop/code/source-typecheck.ts +131 -43
- package/apps/desktop/code/worker-client.ts +14 -2
- package/apps/desktop/contracts/cinema.ts +736 -0
- package/apps/desktop/contracts/index.ts +1 -0
- package/apps/desktop/contracts/spatial-asset.ts +16 -0
- package/apps/desktop/contracts/spatial-world.ts +26 -3
- package/apps/desktop/core/cinema-plan.ts +1320 -0
- package/apps/desktop/core/index.ts +1 -0
- package/apps/desktop/core/music-analysis.ts +124 -1
- package/apps/desktop/core/storage.ts +75 -0
- package/apps/desktop/dist/cli/main.js +737 -291
- package/apps/desktop/html-overlay/audio-reactivity.ts +127 -0
- package/apps/desktop/html-overlay/index.ts +9 -0
- package/apps/desktop/html-overlay/runtime.ts +2 -0
- package/apps/desktop/html-overlay/scene.ts +29 -0
- package/apps/desktop/html-overlay/spatial.ts +619 -57
- package/apps/desktop/workflows/cinematic-world.ts +127 -0
- package/apps/desktop/workflows/index.ts +6 -0
- package/dist/cli.js +32 -434
- package/dist/code/advanced.js +1 -269
- package/dist/code/index.js +16 -2239
- package/dist/generate.js +1 -23
- package/dist/host-resources.js +1 -25
- package/dist/index-0bbesn4m.js +35 -0
- package/dist/index-296qhpf2.js +3 -0
- package/dist/index-3cxf2c58.js +3 -0
- package/dist/index-77fjfg9f.js +3 -0
- package/dist/index-80a36bc0.js +3 -0
- package/dist/index-a3vvs3rf.js +3 -0
- package/dist/index-b3geqczn.js +5 -0
- package/dist/index-cgpkyjqa.js +3 -0
- package/dist/index-f0ptv8dm.js +7 -0
- package/dist/index-grk47tgs.js +3 -0
- package/dist/index-qry58nj2.js +10 -0
- package/dist/index.js +1 -240
- package/dist/operations.js +1 -28
- package/dist/skill-install-gx7afxsd.js +2 -0
- package/dist/vectorize/worker.js +1 -141
- package/dist/workflow.js +1 -15
- package/docs/README.md +5 -1
- package/docs/studio.md +1 -1
- package/package.json +11 -24
- package/skills/slopcamera/SKILL.md +14 -1
- package/skills/slopcamera/references/diagrams.md +5 -14
- package/skills/slopcamera/references/directed-scenes.md +22 -2
- package/skills/slopcamera/references/gateway-media.md +15 -1
- package/skills/slopcamera/references/image-galleries.md +166 -0
- package/skills/slopcamera/references/install.md +2 -2
- package/skills/slopcamera/references/music-video.md +23 -3
- package/skills/slopcamera/references/parametric-design.md +14 -0
- package/skills/slopcamera/references/reference-led-3d.md +1 -1
- package/skills/slopcamera/references/scene-building.md +32 -0
- package/skills/slopcamera/references/social-collage-banners.md +204 -0
- package/skills/slopcamera/references/support.md +25 -0
- package/skills/slopcamera/references/web-media-excerpts.md +120 -0
- package/skills/slopcamera/references/workflows-sdk.md +3 -3
- package/skills/slopcamera/scripts/compose-social-collage-banner.ts +728 -0
- package/src/capability-manifest.ts +361 -0
- package/src/cli.ts +250 -102
- package/src/code/canonical-json.ts +9 -0
- package/src/generate.ts +2 -2
- package/src/icon.ts +1111 -0
- package/src/image-gallery.ts +1049 -0
- package/src/index.ts +11 -23
- package/src/mcp/boundary.ts +11 -11
- package/src/mcp/index.ts +3 -0
- package/src/mcp/server.ts +2 -1
- package/src/mcp/tools.ts +1642 -130
- package/src/mcp/types.ts +19 -0
- package/src/operations.ts +412 -0
- package/src/portable-capability-manifest.ts +67 -0
- package/src/scene-gallery.ts +184 -0
- package/src/spatial-scene/asset-admission.ts +183 -0
- package/src/spatial-scene/audit-rendered.ts +692 -0
- package/src/spatial-scene/audit.ts +617 -0
- package/src/spatial-scene/behavior-audit.ts +252 -0
- package/src/spatial-scene/behavior-bake.ts +233 -0
- package/src/spatial-scene/behavior-fns.ts +585 -0
- package/src/spatial-scene/behavior-gallery.ts +109 -0
- package/src/spatial-scene/behavior-stdlib.ts +323 -0
- package/src/spatial-scene/behavior-trace.ts +325 -0
- package/src/spatial-scene/behavior.ts +618 -0
- package/src/spatial-scene/build.ts +632 -0
- package/src/spatial-scene/camera-rig.ts +125 -0
- package/src/spatial-scene/camera-track.ts +4 -2
- package/src/spatial-scene/character.ts +124 -0
- package/src/spatial-scene/contracts.ts +75 -7
- package/src/spatial-scene/design-templates.ts +351 -0
- package/src/spatial-scene/design.ts +327 -0
- package/src/spatial-scene/direction-compile.ts +584 -0
- package/src/spatial-scene/direction.ts +122 -0
- package/src/spatial-scene/effects.ts +149 -0
- package/src/spatial-scene/evaluate.ts +109 -29
- package/src/spatial-scene/gallery.ts +277 -0
- package/src/spatial-scene/generate.ts +239 -0
- package/src/spatial-scene/geometry-native.ts +165 -0
- package/src/spatial-scene/geometry.ts +1216 -0
- package/src/spatial-scene/gltf.ts +600 -77
- package/src/spatial-scene/identity.ts +55 -2
- package/src/spatial-scene/index.ts +36 -0
- package/src/spatial-scene/inspect.ts +35 -6
- package/src/spatial-scene/material-lighting.ts +609 -0
- package/src/spatial-scene/math.ts +21 -4
- package/src/spatial-scene/motion-evidence.ts +88 -0
- package/src/spatial-scene/parametric.ts +765 -0
- package/src/spatial-scene/particle-preparation.ts +183 -0
- package/src/spatial-scene/particle.ts +195 -0
- package/src/spatial-scene/patch.ts +109 -15
- package/src/spatial-scene/performance.ts +1613 -0
- package/src/spatial-scene/probe.ts +137 -0
- package/src/spatial-scene/recipe-pack.ts +86 -0
- package/src/spatial-scene/render-effects.ts +240 -0
- package/src/spatial-scene/review.ts +639 -0
- package/src/spatial-scene/simulation-bake.ts +206 -0
- package/src/spatial-scene/simulation.ts +187 -0
- package/src/spatial-scene/solve.ts +366 -0
- package/src/spatial-scene/temporal-audit.ts +354 -0
- package/src/support-completion.ts +16 -0
- package/src/support.ts +42 -0
- package/src/version.ts +1 -1
- package/apps/desktop/application/operations/recording/index.ts +0 -5
- package/apps/desktop/application/operations/recording/pause.ts +0 -34
- package/apps/desktop/application/operations/recording/resume.ts +0 -34
- package/apps/desktop/application/operations/recording/shared.ts +0 -199
- package/apps/desktop/application/operations/recording/start.ts +0 -169
- package/apps/desktop/application/operations/recording/stop.ts +0 -34
- package/apps/desktop/capture/protocol.ts +0 -689
- package/apps/desktop/cli/capture-bundle.ts +0 -1117
- package/apps/desktop/cli/recording-controller.ts +0 -1567
- package/apps/desktop/cli/recording-daemon.ts +0 -931
- package/dist/index-42zsesc1.js +0 -2257
- package/dist/index-7308egqr.js +0 -48
- package/dist/index-8txs6fkn.js +0 -712
- package/dist/index-mcy8z0br.js +0 -192
- package/dist/index-p63wavx0.js +0 -2029
- package/dist/index-r7gdhmsp.js +0 -424
- package/dist/index-rvghzt7b.js +0 -2013
- package/dist/index-sh6xbav6.js +0 -1219
- package/dist/index-z1w83f81.js +0 -4
- package/dist/index-z7239b4h.js +0 -2363
- package/dist/skill-install-dh0ntqba.js +0 -10
- package/src/desktop.ts +0 -253
|
@@ -2,16 +2,33 @@ import { constants } from "node:fs";
|
|
|
2
2
|
import { open, realpath } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, relative, resolve } from "node:path";
|
|
4
4
|
import { canonicalJson } from "../../../src/code/canonical-json";
|
|
5
|
+
import { normalizeSpatialAuditAssetBounds, spatialEntityLocalBounds } from "../../../src/spatial-scene/audit";
|
|
5
6
|
import { createSpatialSceneStarter } from "../../../src/spatial-scene/authoring";
|
|
6
|
-
import { SPATIAL_SCENE_LIMITS } from "../../../src/spatial-scene/contracts";
|
|
7
|
-
import { parseSpatialScene, spatialSceneSha256 } from "../../../src/spatial-scene/index";
|
|
8
7
|
import { sampleSpatialCameraTrack } from "../../../src/spatial-scene/camera-track";
|
|
8
|
+
import { SPATIAL_SCENE_LIMITS, SpatialCameraIdSchema } from "../../../src/spatial-scene/contracts";
|
|
9
|
+
import { checkSpatialBehavior } from "../../../src/spatial-scene/behavior";
|
|
10
|
+
import { auditSpatialBehaviorTrace } from "../../../src/spatial-scene/behavior-audit";
|
|
11
|
+
import { bakeSpatialBehavior } from "../../../src/spatial-scene/behavior-bake";
|
|
12
|
+
import { spatialBehaviorFnSignatures } from "../../../src/spatial-scene/behavior-fns";
|
|
13
|
+
import { planSpatialBehaviorGallery, spatialBehaviorGalleryPlanSha256 } from "../../../src/spatial-scene/behavior-gallery";
|
|
14
|
+
import { parseSpatialBehaviorChannelMap, spatialBehaviorBakeSha256 } from "../../../src/spatial-scene/behavior-trace";
|
|
15
|
+
import { checkSpatialDirection, compileSpatialDirection, spatialDirectionCompilationSha256 } from "../../../src/spatial-scene/direction-compile";
|
|
16
|
+
import { createSpatialEvaluationContext, evaluateSpatialSceneInContext } from "../../../src/spatial-scene/evaluate";
|
|
17
|
+
import { planSpatialDirectionGallery, spatialGalleryPlanSha256 } from "../../../src/spatial-scene/gallery";
|
|
18
|
+
import { checkSpatialRenderEffects, planSpatialRenderEffects } from "../../../src/spatial-scene/render-effects";
|
|
19
|
+
import { SPATIAL_REVIEW_LIMITS } from "../../../src/spatial-scene/review";
|
|
20
|
+
import { bakeSpatialSimulation, spatialSimulationBakeSha256 } from "../../../src/spatial-scene/simulation-bake";
|
|
21
|
+
import { auditSpatialTemporalEvidence, SpatialTemporalContactsFileSchema, spatialTemporalAuditReportSha256 } from "../../../src/spatial-scene/temporal-audit";
|
|
22
|
+
import { parseSpatialScene, parseSpatialValue, spatialSceneSha256 } from "../../../src/spatial-scene/index";
|
|
23
|
+
import { diffSpatialScenes } from "../../../src/spatial-scene/patch";
|
|
24
|
+
import { SpatialSolveError, SpatialSolveGoalsFileSchema, solveSpatialRelations, type SpatialSolveResult } from "../../../src/spatial-scene/solve";
|
|
9
25
|
import type { ApplicationContext } from "../application/context";
|
|
10
26
|
import { createApplicationOperationRegistry } from "../application/default-registry";
|
|
11
27
|
import { planSpatialRender } from "../application/spatial-render";
|
|
12
28
|
import { createNodeBundleFileSystem } from "../core/storage";
|
|
13
29
|
import type { SpatialCliExecutionProfile, SpatialSceneCommand } from "./args";
|
|
14
|
-
import { CliError } from "./errors";
|
|
30
|
+
import { CliError, type CliErrorCode } from "./errors";
|
|
31
|
+
import { executeSpatialGenerateCommand } from "./spatial-generate-service";
|
|
15
32
|
|
|
16
33
|
/** Capture an explicit local regular file once, without following a leaf symlink. */
|
|
17
34
|
export async function readSpatialJson(path: string, maximumBytes: number = SPATIAL_SCENE_LIMITS.sourceBytes): Promise<unknown> {
|
|
@@ -54,15 +71,166 @@ function assertCameraTrackActive(signal: AbortSignal | undefined): void {
|
|
|
54
71
|
if (signal?.aborted === true) throw new CliError("cancelled", "Camera track export was cancelled.");
|
|
55
72
|
}
|
|
56
73
|
|
|
74
|
+
/** Solver codes collapse onto the CLI code set; the typed code stays in the bracketed message prefix. */
|
|
75
|
+
const SOLVE_ERROR_CODES: Readonly<Record<string, CliErrorCode>> = {
|
|
76
|
+
"invalid-data": "invalid-data",
|
|
77
|
+
"unknown-entity": "not-found",
|
|
78
|
+
"bounds-unknown": "invalid-data",
|
|
79
|
+
"relation-cycle": "conflict",
|
|
80
|
+
"duplicate-goal": "conflict",
|
|
81
|
+
"relation-failed": "invalid-data",
|
|
82
|
+
};
|
|
83
|
+
|
|
57
84
|
export async function executeSpatialSceneCommand(application: ApplicationContext, command: SpatialSceneCommand, signal?: AbortSignal): Promise<unknown> {
|
|
85
|
+
if (command.action === "generate") return executeSpatialGenerateCommand(application, command);
|
|
86
|
+
// Fail closed before touching the filesystem, renderer, or provider: review
|
|
87
|
+
// uploads the selected bounded rendered beauty frames to a vision model and
|
|
88
|
+
// requires --allow-cloud-upload on this exact invocation.
|
|
89
|
+
if (command.action === "review" && !command.allowCloudUpload) {
|
|
90
|
+
throw new CliError("authorization-required", "scene review uploads bounded rendered beauty frames to a vision model and requires --allow-cloud-upload on this invocation.", { command: "scene review" });
|
|
91
|
+
}
|
|
92
|
+
if (command.action === "behavior-audit") {
|
|
93
|
+
const bake = await readSpatialJson(resolve(application.paths.repositoryRoot, command.bake));
|
|
94
|
+
const report = auditSpatialBehaviorTrace(bake);
|
|
95
|
+
if (command.output === undefined) return report;
|
|
96
|
+
const output = resolve(application.paths.repositoryRoot, command.output);
|
|
97
|
+
await publishSpatialSource(output, report);
|
|
98
|
+
return { path: output, findings: report.findings.length, channels: report.channelCount, emitted: report.emittedCount };
|
|
99
|
+
}
|
|
58
100
|
if (command.action === "camera-track") assertCameraTrackActive(signal);
|
|
59
|
-
const sourcePath = resolve(application.paths.repositoryRoot, command.path);
|
|
101
|
+
const sourcePath = resolve(application.paths.repositoryRoot, "path" in command ? command.path : command.scene);
|
|
60
102
|
if (command.action === "init") {
|
|
61
103
|
const scene = createSpatialSceneStarter();
|
|
62
104
|
await publishSpatialSource(sourcePath, scene);
|
|
63
105
|
return { path: sourcePath, sceneSha256: spatialSceneSha256(scene) };
|
|
64
106
|
}
|
|
107
|
+
if (command.action === "review") {
|
|
108
|
+
const acknowledgedAt = application.clock.now().toISOString();
|
|
109
|
+
const authorizedApplication: ApplicationContext = {
|
|
110
|
+
...application,
|
|
111
|
+
spatialReviewAuthorization: {
|
|
112
|
+
authorize: async request => (typeof request.sceneSha256 === "string" && /^[a-f0-9]{64}$/u.test(request.sceneSha256)
|
|
113
|
+
&& SpatialCameraIdSchema.safeParse(request.cameraId).success
|
|
114
|
+
&& Array.isArray(request.timesUs)
|
|
115
|
+
&& request.timesUs.length >= 1
|
|
116
|
+
&& request.timesUs.length <= SPATIAL_REVIEW_LIMITS.frames
|
|
117
|
+
&& request.maximumFrames >= 1
|
|
118
|
+
&& request.maximumFrames <= SPATIAL_REVIEW_LIMITS.frames
|
|
119
|
+
&& request.maximumFrameBytes <= SPATIAL_REVIEW_LIMITS.pngBytes
|
|
120
|
+
&& request.maximumUploadBytes <= SPATIAL_REVIEW_LIMITS.uploadBytes
|
|
121
|
+
? { acknowledgedAt }
|
|
122
|
+
: undefined),
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
const result = await createApplicationOperationRegistry().execute({ application: authorizedApplication, abortSignal: signal ?? new AbortController().signal }, {
|
|
126
|
+
kind: "scene.review", version: 1,
|
|
127
|
+
input: {
|
|
128
|
+
source: { path: relative(application.paths.repositoryRoot, sourcePath) },
|
|
129
|
+
request: { cameraId: command.camera, ...(command.timesUs === undefined ? {} : { timesUs: [...command.timesUs] }) },
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
return result.output;
|
|
133
|
+
}
|
|
134
|
+
if (command.action === "render-audit") {
|
|
135
|
+
const result = await createApplicationOperationRegistry().execute({ application, abortSignal: signal ?? new AbortController().signal }, {
|
|
136
|
+
kind: "scene.render-audit", version: 1,
|
|
137
|
+
input: {
|
|
138
|
+
source: { path: relative(application.paths.repositoryRoot, sourcePath) },
|
|
139
|
+
request: { cameraId: command.camera, ...(command.timesUs === undefined ? {} : { timesUs: [...command.timesUs] }) },
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
return result.output;
|
|
143
|
+
}
|
|
65
144
|
const scene = parseSpatialScene(await readSpatialJson(sourcePath));
|
|
145
|
+
if (command.action === "diff") {
|
|
146
|
+
const other = parseSpatialScene(await readSpatialJson(resolve(application.paths.repositoryRoot, command.other)));
|
|
147
|
+
return { sceneSha256: spatialSceneSha256(scene), otherSha256: spatialSceneSha256(other), diff: diffSpatialScenes(scene, other) };
|
|
148
|
+
}
|
|
149
|
+
if (command.action === "solve") {
|
|
150
|
+
const goalsFile = parseSpatialValue(SpatialSolveGoalsFileSchema, await readSpatialJson(resolve(application.paths.repositoryRoot, command.goals)), "solve goals");
|
|
151
|
+
const assetBounds = command.assetBounds === undefined
|
|
152
|
+
? {}
|
|
153
|
+
: normalizeSpatialAuditAssetBounds(await readSpatialJson(resolve(application.paths.repositoryRoot, command.assetBounds)));
|
|
154
|
+
const entities = new Map(scene.entities.map(entity => [entity.entityId, entity]));
|
|
155
|
+
const cameras = new Map(scene.cameras.map(camera => [camera.cameraId, camera]));
|
|
156
|
+
const transformOverrides = new Map(
|
|
157
|
+
scene.overrides.filter(override => override.property === "transform").map(override => [override.entityId, override.value]),
|
|
158
|
+
);
|
|
159
|
+
const animatedTransforms = new Map<string, string[]>();
|
|
160
|
+
for (const channel of scene.animations) {
|
|
161
|
+
if (channel.property === "position" || channel.property === "rotation" || channel.property === "scale") {
|
|
162
|
+
animatedTransforms.set(channel.targetId, [...(animatedTransforms.get(channel.targetId) ?? []), channel.channelId]);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const referenced = new Set<string>();
|
|
166
|
+
for (const goal of goalsFile.goals) {
|
|
167
|
+
referenced.add(goal.entityKey);
|
|
168
|
+
for (const relation of goal.relations) if ("target" in relation) referenced.add(relation.target);
|
|
169
|
+
}
|
|
170
|
+
for (const goal of goalsFile.goals) {
|
|
171
|
+
const entity = entities.get(goal.entityKey);
|
|
172
|
+
if (entity === undefined) throw new CliError("not-found", `Solve goal ${goal.entityKey} is not an entity in the scene.`);
|
|
173
|
+
if (entity.origin.kind === "generated") {
|
|
174
|
+
throw new CliError("conflict", `Goal entity ${entity.entityId} is generated; edit it through declared overrides or retained output replacement.`);
|
|
175
|
+
}
|
|
176
|
+
if (entity.parentId !== null) {
|
|
177
|
+
throw new CliError("conflict", `Goal entity ${entity.entityId} is parented under ${entity.parentId}; solver bases are world transforms — pass an explicit base entry or reparent first.`);
|
|
178
|
+
}
|
|
179
|
+
if (entity.placement.kind !== "world") {
|
|
180
|
+
throw new CliError("conflict", `Goal entity ${entity.entityId} uses view placement; solver transforms are world-space.`);
|
|
181
|
+
}
|
|
182
|
+
if (transformOverrides.has(entity.entityId)) {
|
|
183
|
+
throw new CliError("conflict", `Goal entity ${entity.entityId} has a transform override; a set-transform patch would stay shadowed.`);
|
|
184
|
+
}
|
|
185
|
+
const writers = animatedTransforms.get(entity.entityId);
|
|
186
|
+
if (writers !== undefined) {
|
|
187
|
+
throw new CliError("conflict", `Goal entity ${entity.entityId} is animated (${writers.join(", ")}); a solved transform would not take effect.`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const bases: Record<string, unknown> = {};
|
|
191
|
+
for (const key of [...referenced].sort()) {
|
|
192
|
+
const entity = entities.get(key);
|
|
193
|
+
if (entity !== undefined) {
|
|
194
|
+
// Local bounds are transform-agnostic, so they seed whenever derivable;
|
|
195
|
+
// the authored transform only seeds a base when it is the world transform.
|
|
196
|
+
const explicit = goalsFile.bases?.[key]?.transform !== undefined;
|
|
197
|
+
if (!explicit && (entity.parentId !== null || entity.placement.kind !== "world")) {
|
|
198
|
+
throw new CliError("conflict", `Referenced entity ${key} is ${entity.parentId === null ? "view-placed" : `parented under ${entity.parentId}`}; solver bases are world transforms — pass an explicit base entry.`);
|
|
199
|
+
}
|
|
200
|
+
const enclosure = spatialEntityLocalBounds(entity, assetBounds);
|
|
201
|
+
bases[key] = {
|
|
202
|
+
...(explicit ? {} : { transform: transformOverrides.get(key) ?? entity.transform }),
|
|
203
|
+
...(enclosure.status === "bounded" ? { bounds: enclosure.bounds } : {}),
|
|
204
|
+
};
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const camera = cameras.get(key);
|
|
208
|
+
if (camera !== undefined) {
|
|
209
|
+
bases[key] = { transform: { position: camera.pose.position, rotation: camera.pose.rotation, scale: [1, 1, 1] } };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
for (const [key, basePatch] of Object.entries(goalsFile.bases ?? {})) {
|
|
213
|
+
const existing = bases[key] as { transform?: unknown; bounds?: unknown } | undefined;
|
|
214
|
+
bases[key] = {
|
|
215
|
+
...(existing ?? {}),
|
|
216
|
+
...(basePatch.transform === undefined ? {} : { transform: basePatch.transform }),
|
|
217
|
+
...(basePatch.bounds === undefined ? {} : { bounds: basePatch.bounds }),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
let result: SpatialSolveResult;
|
|
221
|
+
try {
|
|
222
|
+
result = solveSpatialRelations({ goals: goalsFile.goals, bases });
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (error instanceof SpatialSolveError) {
|
|
225
|
+
throw new CliError(SOLVE_ERROR_CODES[error.code] ?? "invalid-data", `[${error.code}] ${error.message}`);
|
|
226
|
+
}
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
if (command.output === undefined) return result.patch;
|
|
230
|
+
const output = resolve(application.paths.repositoryRoot, command.output);
|
|
231
|
+
await publishSpatialSource(output, result.patch);
|
|
232
|
+
return { path: output, sceneSha256: spatialSceneSha256(scene), solvedEntities: Object.keys(result.transforms) };
|
|
233
|
+
}
|
|
66
234
|
if (command.action === "camera-track") {
|
|
67
235
|
const fence = async () => {
|
|
68
236
|
assertCameraTrackActive(signal);
|
|
@@ -78,6 +246,91 @@ export async function executeSpatialSceneCommand(application: ApplicationContext
|
|
|
78
246
|
await fence();
|
|
79
247
|
return { path: output, sceneSha256: track.sceneSha256, cameraId: track.cameraId, clock: track.clock, executed: false };
|
|
80
248
|
}
|
|
249
|
+
if (command.action === "direction-check") {
|
|
250
|
+
const direction = await readSpatialJson(resolve(application.paths.repositoryRoot, command.direction));
|
|
251
|
+
return checkSpatialDirection({ direction, scene });
|
|
252
|
+
}
|
|
253
|
+
if (command.action === "direction-plan") {
|
|
254
|
+
const direction = await readSpatialJson(resolve(application.paths.repositoryRoot, command.direction));
|
|
255
|
+
const compilation = compileSpatialDirection({ direction, scene, ...(command.camera === undefined ? {} : { cameraId: command.camera }) });
|
|
256
|
+
if (command.output === undefined) return compilation;
|
|
257
|
+
const output = resolve(application.paths.repositoryRoot, command.output);
|
|
258
|
+
await publishSpatialSource(output, compilation);
|
|
259
|
+
return { path: output, compilationSha256: spatialDirectionCompilationSha256(compilation), proposals: compilation.proposals, advisories: compilation.advisories };
|
|
260
|
+
}
|
|
261
|
+
if (command.action === "direction-gallery") {
|
|
262
|
+
const direction = await readSpatialJson(resolve(application.paths.repositoryRoot, command.direction));
|
|
263
|
+
const plan = planSpatialDirectionGallery({ axis: command.axis, direction, scene, ...(command.camera === undefined ? {} : { cameraId: command.camera }) });
|
|
264
|
+
if (command.output === undefined) return plan;
|
|
265
|
+
const output = resolve(application.paths.repositoryRoot, command.output);
|
|
266
|
+
await publishSpatialSource(output, plan);
|
|
267
|
+
return { path: output, galleryPlanSha256: spatialGalleryPlanSha256(plan), candidates: plan.candidates.map(candidate => ({ candidateId: candidate.candidateId, documentSha256: candidate.documentSha256 })) };
|
|
268
|
+
}
|
|
269
|
+
if (command.action === "effects-check") {
|
|
270
|
+
const effects = await readSpatialJson(resolve(application.paths.repositoryRoot, command.effects));
|
|
271
|
+
return checkSpatialRenderEffects({ effects, scene });
|
|
272
|
+
}
|
|
273
|
+
if (command.action === "effects-plan") {
|
|
274
|
+
const draft = await readSpatialJson(resolve(application.paths.repositoryRoot, command.draft));
|
|
275
|
+
if (typeof draft !== "object" || draft === null || Array.isArray(draft)) throw new CliError("invalid-data", "An effects draft must be a JSON object with renderPlan, particleSystems, and simulationBakes fields.");
|
|
276
|
+
const binding = planSpatialRenderEffects({ ...draft, scene });
|
|
277
|
+
if (command.output === undefined) return binding;
|
|
278
|
+
const output = resolve(application.paths.repositoryRoot, command.output);
|
|
279
|
+
await publishSpatialSource(output, binding);
|
|
280
|
+
return { path: output, documentSha256: binding.documentSha256 };
|
|
281
|
+
}
|
|
282
|
+
if (command.action === "effects-bake") {
|
|
283
|
+
const plan = await readSpatialJson(resolve(application.paths.repositoryRoot, command.simulation));
|
|
284
|
+
const result = bakeSpatialSimulation(plan);
|
|
285
|
+
const output = resolve(application.paths.repositoryRoot, command.output);
|
|
286
|
+
await publishSpatialSource(output, result.document);
|
|
287
|
+
return { path: output, documentSha256: spatialSimulationBakeSha256(result.document), receipt: result.receipt };
|
|
288
|
+
}
|
|
289
|
+
if (command.action === "behavior-check") {
|
|
290
|
+
const behavior = await readSpatialJson(resolve(application.paths.repositoryRoot, command.behavior));
|
|
291
|
+
return checkSpatialBehavior({ behavior, scene }, spatialBehaviorFnSignatures());
|
|
292
|
+
}
|
|
293
|
+
if (command.action === "behavior-bake" || command.action === "behavior-gallery") {
|
|
294
|
+
const behavior = await readSpatialJson(resolve(application.paths.repositoryRoot, command.behavior));
|
|
295
|
+
const channelMap = command.channelMap === undefined
|
|
296
|
+
? undefined
|
|
297
|
+
: parseSpatialBehaviorChannelMap(await readSpatialJson(resolve(application.paths.repositoryRoot, command.channelMap)));
|
|
298
|
+
if (command.action === "behavior-bake") {
|
|
299
|
+
const { bake } = await bakeSpatialBehavior({ behavior, scene, ...(channelMap === undefined ? {} : { channelMap }) });
|
|
300
|
+
const output = resolve(application.paths.repositoryRoot, command.output);
|
|
301
|
+
await publishSpatialSource(output, bake);
|
|
302
|
+
return {
|
|
303
|
+
path: output,
|
|
304
|
+
bakeSha256: spatialBehaviorBakeSha256(bake),
|
|
305
|
+
emitted: bake.emitted.length,
|
|
306
|
+
directives: bake.directives.length,
|
|
307
|
+
unresolvedIntents: bake.unresolvedIntents.length,
|
|
308
|
+
receipt: bake.receipt,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const plan = await planSpatialBehaviorGallery({ behavior, scene, ...(channelMap === undefined ? {} : { channelMap }) });
|
|
312
|
+
if (command.output === undefined) return plan;
|
|
313
|
+
const output = resolve(application.paths.repositoryRoot, command.output);
|
|
314
|
+
await publishSpatialSource(output, plan);
|
|
315
|
+
return { path: output, galleryPlanSha256: spatialBehaviorGalleryPlanSha256(plan), candidates: plan.candidates.map(candidate => ({ candidateId: candidate.candidateId, documentSha256: candidate.documentSha256, parameter: candidate.parameter })) };
|
|
316
|
+
}
|
|
317
|
+
if (command.action === "temporal-audit") {
|
|
318
|
+
const times = command.timesUs ?? (() => {
|
|
319
|
+
const count = Math.min(17, Math.max(2, Math.ceil(scene.durationUs / 1_000_000) + 1));
|
|
320
|
+
return Array.from({ length: count }, (_, index) => Math.floor((scene.durationUs * index) / (count - 1)));
|
|
321
|
+
})();
|
|
322
|
+
if (times.length < 2) throw new CliError("invalid-data", "scene temporal-audit requires at least two sample times.");
|
|
323
|
+
const contacts = command.contacts === undefined
|
|
324
|
+
? []
|
|
325
|
+
: parseSpatialValue(SpatialTemporalContactsFileSchema, await readSpatialJson(resolve(application.paths.repositoryRoot, command.contacts)), "temporal contacts").contacts;
|
|
326
|
+
const context = createSpatialEvaluationContext(scene);
|
|
327
|
+
const snapshots = [...times].sort((left, right) => left - right).map(timeUs => evaluateSpatialSceneInContext(context, { cameraId: command.camera, timeUs }));
|
|
328
|
+
const report = auditSpatialTemporalEvidence({
|
|
329
|
+
snapshots,
|
|
330
|
+
options: { contacts, ...(command.cutBeforeUs === undefined ? {} : { cutBeforeUs: [...command.cutBeforeUs] }) },
|
|
331
|
+
});
|
|
332
|
+
return { ...report, reportSha256: spatialTemporalAuditReportSha256(report) };
|
|
333
|
+
}
|
|
81
334
|
if (command.action === "plan" || command.action === "render") {
|
|
82
335
|
const request = bindSpatialCliExecutionProfile(await readSpatialJson(resolve(application.paths.repositoryRoot, command.request)), command.executionProfile);
|
|
83
336
|
if (command.action === "plan") return planSpatialRender(scene, request);
|
|
@@ -90,6 +343,17 @@ export async function executeSpatialSceneCommand(application: ApplicationContext
|
|
|
90
343
|
return result.output;
|
|
91
344
|
}
|
|
92
345
|
const registry = createApplicationOperationRegistry();
|
|
346
|
+
if (command.action === "audit") {
|
|
347
|
+
const result = await registry.execute({ application, abortSignal: new AbortController().signal }, {
|
|
348
|
+
kind: "scene.audit", version: 1,
|
|
349
|
+
input: {
|
|
350
|
+
scene, cameraId: command.camera,
|
|
351
|
+
...(command.timesUs === undefined ? {} : { timesUs: [...command.timesUs] }),
|
|
352
|
+
...(command.assetBounds === undefined ? {} : { assetBounds: normalizeSpatialAuditAssetBounds(await readSpatialJson(resolve(application.paths.repositoryRoot, command.assetBounds))) }),
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
return result.output;
|
|
356
|
+
}
|
|
93
357
|
const input = command.action === "patch"
|
|
94
358
|
? { scene, patch: await readSpatialJson(resolve(application.paths.repositoryRoot, command.patch)) }
|
|
95
359
|
: command.action === "evaluate" ? { scene, cameraId: command.camera, timeUs: command.timeUs } : { scene };
|
|
@@ -308,7 +308,7 @@ export function workflowCatalogDescription(workflow: BuiltInWorkflow) {
|
|
|
308
308
|
return {
|
|
309
309
|
...workflowCatalogEntry(workflow),
|
|
310
310
|
inputJsonSchema: {
|
|
311
|
-
...z.toJSONSchema(workflow.inputSchema),
|
|
311
|
+
...z.toJSONSchema(workflow.inputSchema, { io: "input" }),
|
|
312
312
|
$id: workflow.inputSchemaId,
|
|
313
313
|
},
|
|
314
314
|
} as const;
|
|
@@ -51,7 +51,6 @@ import {
|
|
|
51
51
|
bindProjectRenderInputV2,
|
|
52
52
|
bindProjectRenderInputV3,
|
|
53
53
|
bindProjectRenderToolchain,
|
|
54
|
-
parseRecordingOperationOutput,
|
|
55
54
|
projectEditCommitReceipt,
|
|
56
55
|
projectEditTransactionId,
|
|
57
56
|
reconcileVariantSelectionPromotion,
|
|
@@ -105,7 +104,6 @@ import type {
|
|
|
105
104
|
import { workflowBoundary, workflowValidation, type WorkflowFailure } from "./workflow-effects";
|
|
106
105
|
|
|
107
106
|
const ANALYSIS_ID_DOMAIN = "studio.workflow.analysis-id/v1";
|
|
108
|
-
type RecordingOperationKind = Extract<OperationKind, `recording.${string}`>;
|
|
109
107
|
type GatewayOperationKind = Extract<OperationKind, `gateway.${string}`>;
|
|
110
108
|
|
|
111
109
|
export const APPLICATION_VERIFIED_RECEIPT_RECONCILER_KINDS =
|
|
@@ -114,15 +112,6 @@ export const APPLICATION_VERIFIED_RECEIPT_RECONCILER_KINDS =
|
|
|
114
112
|
"render.project",
|
|
115
113
|
] as const satisfies readonly OperationKind[]);
|
|
116
114
|
|
|
117
|
-
function isRecordingOperationKind(
|
|
118
|
-
kind: OperationKind,
|
|
119
|
-
): kind is RecordingOperationKind {
|
|
120
|
-
return kind === "recording.start"
|
|
121
|
-
|| kind === "recording.pause"
|
|
122
|
-
|| kind === "recording.resume"
|
|
123
|
-
|| kind === "recording.stop";
|
|
124
|
-
}
|
|
125
|
-
|
|
126
115
|
function isGatewayOperationKind(
|
|
127
116
|
kind: OperationKind,
|
|
128
117
|
): kind is GatewayOperationKind {
|
|
@@ -934,36 +923,6 @@ function renderNodeReconciliation(result: ProjectRenderReconciliation | Awaited<
|
|
|
934
923
|
};
|
|
935
924
|
}
|
|
936
925
|
|
|
937
|
-
async function reconcileRecordingNode(
|
|
938
|
-
application: ApplicationContext,
|
|
939
|
-
operationKind: RecordingOperationKind,
|
|
940
|
-
): Promise<NodeReconciliation> {
|
|
941
|
-
const controller = application.recordingController;
|
|
942
|
-
if (controller === undefined) {
|
|
943
|
-
return {
|
|
944
|
-
kind: "ambiguous",
|
|
945
|
-
message: "Recording controller is unavailable for live-state reconciliation.",
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
let output;
|
|
949
|
-
try {
|
|
950
|
-
output = parseRecordingOperationOutput(await controller.status());
|
|
951
|
-
} catch (error) {
|
|
952
|
-
return {
|
|
953
|
-
kind: "ambiguous",
|
|
954
|
-
message: error instanceof Error
|
|
955
|
-
? `Recording state could not be reconciled: ${error.message}`
|
|
956
|
-
: "Recording state could not be reconciled.",
|
|
957
|
-
};
|
|
958
|
-
}
|
|
959
|
-
return {
|
|
960
|
-
kind: "ambiguous",
|
|
961
|
-
message: `Current controller state ${output.state} for ${
|
|
962
|
-
output.recordingId ?? "no active recording"
|
|
963
|
-
} is not an exact receipt proving ${operationKind} completed; issue a new explicit action.`,
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
|
|
967
926
|
async function reconcileGatewayNode(
|
|
968
927
|
application: ApplicationContext,
|
|
969
928
|
request: NodeReconciliationRequest,
|
|
@@ -1223,12 +1182,6 @@ async function reconcileApplicationNode(
|
|
|
1223
1182
|
request.operation.kind,
|
|
1224
1183
|
);
|
|
1225
1184
|
}
|
|
1226
|
-
if (isRecordingOperationKind(request.operation.kind)) {
|
|
1227
|
-
return await reconcileRecordingNode(
|
|
1228
|
-
application,
|
|
1229
|
-
request.operation.kind,
|
|
1230
|
-
);
|
|
1231
|
-
}
|
|
1232
1185
|
if (request.operation.kind === "project.commit-edits") {
|
|
1233
1186
|
return await reconcileProjectCommit(application, request);
|
|
1234
1187
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
2
|
+
|
|
3
|
+
export interface HostSourceRoots {
|
|
4
|
+
/**
|
|
5
|
+
* True when the running module is inside the committed bundle at
|
|
6
|
+
* apps/desktop/dist/cli. A bundled host still spawns the checked TypeScript
|
|
7
|
+
* sources below apps/desktop and src/.
|
|
8
|
+
*/
|
|
9
|
+
readonly bundled: boolean;
|
|
10
|
+
/** The installed apps/desktop source directory. */
|
|
11
|
+
readonly desktopRoot: string;
|
|
12
|
+
/** The checkout or package root containing src/, packages/, and package.json. */
|
|
13
|
+
readonly repositoryRoot: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Walks to the apps/desktop anchor instead of assuming a fixed module depth.
|
|
18
|
+
* From a checkout, caller modules sit below apps/desktop/<area>/; inside the
|
|
19
|
+
* committed bundle they all share apps/desktop/dist/cli.
|
|
20
|
+
*/
|
|
21
|
+
export function hostSourceRoots(moduleDirectory: string): HostSourceRoots {
|
|
22
|
+
const requested = resolve(moduleDirectory);
|
|
23
|
+
let candidate = requested;
|
|
24
|
+
while (true) {
|
|
25
|
+
if (basename(candidate) === "desktop" && basename(dirname(candidate)) === "apps") {
|
|
26
|
+
return {
|
|
27
|
+
bundled: requested === join(candidate, "dist", "cli"),
|
|
28
|
+
desktopRoot: candidate,
|
|
29
|
+
repositoryRoot: dirname(dirname(candidate)),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const parent = dirname(candidate);
|
|
33
|
+
if (parent === candidate) {
|
|
34
|
+
// Embedded compiled-binary modules and foreign callers keep the
|
|
35
|
+
// historical assumption that the module sits one level below
|
|
36
|
+
// apps/desktop.
|
|
37
|
+
const desktopRoot = resolve(requested, "..");
|
|
38
|
+
return {
|
|
39
|
+
bundled: false,
|
|
40
|
+
desktopRoot,
|
|
41
|
+
repositoryRoot: resolve(desktopRoot, "../.."),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
candidate = parent;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
extname,
|
|
10
10
|
join,
|
|
11
11
|
relative,
|
|
12
|
-
resolve,
|
|
13
12
|
sep,
|
|
14
13
|
} from "node:path";
|
|
15
14
|
|
|
@@ -31,6 +30,7 @@ import {
|
|
|
31
30
|
WorkflowRuntimeIdentitySchema,
|
|
32
31
|
type WorkflowRuntimeIdentity,
|
|
33
32
|
} from "./contracts";
|
|
33
|
+
import { hostSourceRoots } from "./host-source-layout";
|
|
34
34
|
import type { WorkflowSourceBundle } from "./source-bundle";
|
|
35
35
|
|
|
36
36
|
export interface CreateWorkflowRuntimeIdentityOptions {
|
|
@@ -38,7 +38,7 @@ export interface CreateWorkflowRuntimeIdentityOptions {
|
|
|
38
38
|
readonly bundle: WorkflowSourceBundle;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const APPLICATION_BUILD_DOMAIN = "studio.application-build/
|
|
41
|
+
const APPLICATION_BUILD_DOMAIN = "studio.application-build/v2";
|
|
42
42
|
const HOST_APPLICATION_BUILD_DOMAIN = "studio.host-application-build/v1";
|
|
43
43
|
const APPLICATION_SOURCE_EXTENSIONS = new Set([
|
|
44
44
|
".json",
|
|
@@ -52,17 +52,23 @@ const APPLICATION_SOURCE_EXTENSIONS = new Set([
|
|
|
52
52
|
const DESKTOP_SOURCE_DIRECTORIES = [
|
|
53
53
|
"analysis",
|
|
54
54
|
"application",
|
|
55
|
-
"capture",
|
|
56
55
|
"cli",
|
|
57
56
|
"code",
|
|
58
57
|
"contracts",
|
|
59
58
|
"core",
|
|
60
|
-
"runtime",
|
|
61
59
|
"studio",
|
|
62
60
|
"workflows",
|
|
63
61
|
] as const;
|
|
64
62
|
const MAXIMUM_APPLICATION_SOURCE_FILES = 4_096;
|
|
65
63
|
const MAXIMUM_APPLICATION_SOURCE_FILE_BYTES = 64 * 1024 * 1024;
|
|
64
|
+
// The reviewed packed boundary never ships the Zig runtime build tree or the
|
|
65
|
+
// root lockfile, while the committed CLI bundle ships beside the checked
|
|
66
|
+
// sources in both layouts. Each of these inputs stays hash-bound wherever it
|
|
67
|
+
// is present and is skipped only when it is absent.
|
|
68
|
+
const OPTIONAL_APPLICATION_SOURCE_FILES = new Set<string>([
|
|
69
|
+
"apps/desktop/dist/cli/main.js",
|
|
70
|
+
"bun.lock",
|
|
71
|
+
]);
|
|
66
72
|
let cachedApplicationBuild: Promise<string> | undefined;
|
|
67
73
|
|
|
68
74
|
function isWithin(root: string, candidate: string): boolean {
|
|
@@ -137,6 +143,22 @@ async function collectSourceFiles(
|
|
|
137
143
|
}
|
|
138
144
|
}
|
|
139
145
|
|
|
146
|
+
async function presentDesktopDirectory(
|
|
147
|
+
desktopRoot: string,
|
|
148
|
+
directory: string,
|
|
149
|
+
): Promise<string | undefined> {
|
|
150
|
+
const path = join(desktopRoot, directory);
|
|
151
|
+
try {
|
|
152
|
+
if ((await lstat(path)).isDirectory()) return path;
|
|
153
|
+
return undefined;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
140
162
|
async function computeApplicationBuildIdentity(options: {
|
|
141
163
|
readonly desktopRoot: string;
|
|
142
164
|
readonly repositoryRoot: string;
|
|
@@ -151,9 +173,16 @@ async function computeApplicationBuildIdentity(options: {
|
|
|
151
173
|
}
|
|
152
174
|
const sources: Array<{ readonly logicalPath: string; readonly path: string }> = [];
|
|
153
175
|
for (const directory of DESKTOP_SOURCE_DIRECTORIES) {
|
|
176
|
+
const path = await presentDesktopDirectory(desktopRoot, directory);
|
|
177
|
+
if (path === undefined) {
|
|
178
|
+
throw new ApplicationError(
|
|
179
|
+
"not-found",
|
|
180
|
+
`Application build source directory is missing: apps/desktop/${directory}`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
154
183
|
await collectSourceFiles(
|
|
155
184
|
desktopRoot,
|
|
156
|
-
|
|
185
|
+
path,
|
|
157
186
|
`apps/desktop/${directory}`,
|
|
158
187
|
sources,
|
|
159
188
|
);
|
|
@@ -176,10 +205,20 @@ async function computeApplicationBuildIdentity(options: {
|
|
|
176
205
|
);
|
|
177
206
|
}
|
|
178
207
|
for (const [logicalPath, path] of [
|
|
208
|
+
["apps/desktop/dist/cli/main.js", join(desktopRoot, "dist", "cli", "main.js")],
|
|
179
209
|
["bun.lock", join(repositoryRoot, "bun.lock")],
|
|
180
210
|
["package.json", join(repositoryRoot, "package.json")],
|
|
181
211
|
] as const) {
|
|
182
|
-
|
|
212
|
+
let physical: string;
|
|
213
|
+
try {
|
|
214
|
+
physical = await realpath(path);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (
|
|
217
|
+
OPTIONAL_APPLICATION_SOURCE_FILES.has(logicalPath)
|
|
218
|
+
&& error instanceof Error && "code" in error && error.code === "ENOENT"
|
|
219
|
+
) continue;
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
183
222
|
if (!isWithin(repositoryRoot, physical)) {
|
|
184
223
|
throw new ApplicationError("unsafe-path", `Application build source escapes: ${logicalPath}`);
|
|
185
224
|
}
|
|
@@ -227,8 +266,11 @@ export async function createApplicationBuildIdentity(options: {
|
|
|
227
266
|
readonly desktopRoot?: string;
|
|
228
267
|
readonly repositoryRoot?: string;
|
|
229
268
|
} = {}): Promise<string> {
|
|
230
|
-
|
|
231
|
-
|
|
269
|
+
// Inside the committed bundle, import.meta.dir is apps/desktop/dist/cli;
|
|
270
|
+
// the checked host sources stay at their installed paths below apps/desktop.
|
|
271
|
+
const defaults = hostSourceRoots(import.meta.dir);
|
|
272
|
+
const defaultDesktopRoot = defaults.desktopRoot;
|
|
273
|
+
const defaultRepositoryRoot = defaults.repositoryRoot;
|
|
232
274
|
if (options.desktopRoot !== undefined || options.repositoryRoot !== undefined) {
|
|
233
275
|
return await computeApplicationBuildIdentity({
|
|
234
276
|
desktopRoot: options.desktopRoot ?? defaultDesktopRoot,
|