@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
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
|
|
3
|
+
import { canonicalJsonSha256 } from "./code/canonical-json.js"
|
|
4
|
+
import { createBoundedJsonValueSnapshot, deepFreezeJson } from "./code/json-snapshot.js"
|
|
5
|
+
|
|
6
|
+
export const SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS = Object.freeze({
|
|
7
|
+
bytes: 4 * 1024 * 1024,
|
|
8
|
+
commands: 256,
|
|
9
|
+
evidence: 64,
|
|
10
|
+
modules: 64,
|
|
11
|
+
operations: 512,
|
|
12
|
+
profiles: 128,
|
|
13
|
+
requirements: 32,
|
|
14
|
+
tools: 128,
|
|
15
|
+
workflows: 128,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const identifier = z.string().min(1).max(192).regex(/^[a-z][a-z0-9]*(?:[-_.:/@][a-z0-9]+)*$/u)
|
|
19
|
+
const commandName = z.string().min(1).max(128).regex(/^[a-z][a-z0-9-]*(?: [a-z][a-z0-9-]*){0,3}$/u)
|
|
20
|
+
const digest = z.string().regex(/^[a-f0-9]{64}$/u)
|
|
21
|
+
const positiveSafeInteger = z.number().int().safe().positive()
|
|
22
|
+
const nonnegativeSafeInteger = z.number().int().safe().nonnegative()
|
|
23
|
+
|
|
24
|
+
export const SlopcameraCapabilityEffectSchema = z.enum([
|
|
25
|
+
"pure",
|
|
26
|
+
"local-read",
|
|
27
|
+
"local-derived-write",
|
|
28
|
+
"project-mutation",
|
|
29
|
+
"paid-cloud",
|
|
30
|
+
"live-control",
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
export const SlopcameraCapabilityResumeSchema = z.enum([
|
|
34
|
+
"deterministic",
|
|
35
|
+
"verified-receipt",
|
|
36
|
+
"recoverable-transaction",
|
|
37
|
+
"ambiguous-after-dispatch",
|
|
38
|
+
"non-resumable-live",
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
export const SlopcameraCapabilityResourceSchema = z.enum([
|
|
42
|
+
"cpu",
|
|
43
|
+
"local-io",
|
|
44
|
+
"ffmpeg",
|
|
45
|
+
"vision",
|
|
46
|
+
"whisper",
|
|
47
|
+
"network",
|
|
48
|
+
"paid-call",
|
|
49
|
+
"project-render",
|
|
50
|
+
"project-publication",
|
|
51
|
+
"output-publication",
|
|
52
|
+
"capture-device",
|
|
53
|
+
"browser",
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
export const SlopcameraCapabilityPreparationSchema = z.enum([
|
|
57
|
+
"project-state",
|
|
58
|
+
"recording-metadata",
|
|
59
|
+
"screen-capture",
|
|
60
|
+
"camera",
|
|
61
|
+
"microphone",
|
|
62
|
+
"system-audio",
|
|
63
|
+
"typed-text",
|
|
64
|
+
"window-metadata",
|
|
65
|
+
"local-media",
|
|
66
|
+
"provider-options",
|
|
67
|
+
])
|
|
68
|
+
|
|
69
|
+
export const SlopcameraCapabilityOperationSchema = z.strictObject({
|
|
70
|
+
cache: z.enum(["none", "exact-run", "content-addressed"]),
|
|
71
|
+
cancellable: z.boolean(),
|
|
72
|
+
effect: SlopcameraCapabilityEffectSchema,
|
|
73
|
+
inputSchemaId: identifier,
|
|
74
|
+
key: z.string().min(3).max(256).regex(/^[a-z][a-z0-9]*(?:[-_.][a-z0-9]+)*@[1-9][0-9]*$/u),
|
|
75
|
+
kind: z.string().min(3).max(192).regex(/^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*(?:\.[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*)+$/u),
|
|
76
|
+
lifecycle: z.enum(["pure", "local-artifact", "project-transaction", "paid-dispatch", "live-control"]),
|
|
77
|
+
maxDurationMs: positiveSafeInteger,
|
|
78
|
+
maxFanOut: nonnegativeSafeInteger,
|
|
79
|
+
maxInputBytes: nonnegativeSafeInteger,
|
|
80
|
+
maxOutputBytes: nonnegativeSafeInteger,
|
|
81
|
+
outputSchemaId: identifier,
|
|
82
|
+
preparation: z.array(SlopcameraCapabilityPreparationSchema).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.requirements),
|
|
83
|
+
resources: z.array(z.strictObject({
|
|
84
|
+
amount: positiveSafeInteger,
|
|
85
|
+
resource: SlopcameraCapabilityResourceSchema,
|
|
86
|
+
})).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.requirements),
|
|
87
|
+
resume: SlopcameraCapabilityResumeSchema,
|
|
88
|
+
version: positiveSafeInteger,
|
|
89
|
+
}).superRefine((operation, context) => {
|
|
90
|
+
if (operation.key !== `${operation.kind}@${operation.version}`) {
|
|
91
|
+
context.addIssue({ code: "custom", path: ["key"], message: "Operation key must match kind and version." })
|
|
92
|
+
}
|
|
93
|
+
if (new Set(operation.preparation).size !== operation.preparation.length) {
|
|
94
|
+
context.addIssue({ code: "custom", path: ["preparation"], message: "Operation preparation entries must be unique." })
|
|
95
|
+
}
|
|
96
|
+
const resources = operation.resources.map(claim => claim.resource)
|
|
97
|
+
if (new Set(resources).size !== resources.length) {
|
|
98
|
+
context.addIssue({ code: "custom", path: ["resources"], message: "Operation resource claims must be unique by resource." })
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
export const SlopcameraCapabilityWorkflowSchema = z.strictObject({
|
|
103
|
+
id: identifier,
|
|
104
|
+
inputSchemaId: identifier,
|
|
105
|
+
key: z.string().min(3).max(256).regex(/^[a-z][a-z0-9]*(?:[-_.][a-z0-9]+)*@[1-9][0-9]*$/u),
|
|
106
|
+
version: positiveSafeInteger,
|
|
107
|
+
}).superRefine((workflow, context) => {
|
|
108
|
+
if (workflow.key !== `${workflow.id}@${workflow.version}`) {
|
|
109
|
+
context.addIssue({ code: "custom", path: ["key"], message: "Workflow key must match id and version." })
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
export const SlopcameraCapabilityToolSchema = z.strictObject({
|
|
114
|
+
destructive: z.boolean(),
|
|
115
|
+
idempotent: z.boolean(),
|
|
116
|
+
name: z.string().min(1).max(128).regex(/^[a-z][a-z0-9_]*$/u),
|
|
117
|
+
openWorld: z.boolean(),
|
|
118
|
+
readOnly: z.boolean(),
|
|
119
|
+
}).superRefine((tool, context) => {
|
|
120
|
+
if (tool.readOnly && tool.destructive) {
|
|
121
|
+
context.addIssue({ code: "custom", message: "A read-only tool cannot be destructive." })
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
export const SlopcameraCapabilityCommandSchema = z.strictObject({
|
|
126
|
+
effect: SlopcameraCapabilityEffectSchema,
|
|
127
|
+
name: commandName,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
export const SlopcameraCapabilityQualificationSchema = z.strictObject({
|
|
131
|
+
evidence: z.array(z.string().min(1).max(512)).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.evidence),
|
|
132
|
+
level: z.enum(["unit", "fixture", "browser", "native", "provider"]),
|
|
133
|
+
status: z.enum(["qualified", "conditional", "not-qualified"]),
|
|
134
|
+
}).superRefine((qualification, context) => {
|
|
135
|
+
if (qualification.status !== "not-qualified" && qualification.evidence.length === 0) {
|
|
136
|
+
context.addIssue({ code: "custom", path: ["evidence"], message: "Qualified or conditional profiles require evidence references." })
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
export const SlopcameraCapabilityProfileSchema = z.strictObject({
|
|
141
|
+
id: identifier,
|
|
142
|
+
kind: z.enum(["renderer", "native-adapter", "provider", "authoring", "interchange"]),
|
|
143
|
+
qualification: SlopcameraCapabilityQualificationSchema,
|
|
144
|
+
requirements: z.array(z.strictObject({
|
|
145
|
+
description: z.string().min(1).max(256),
|
|
146
|
+
id: identifier,
|
|
147
|
+
optional: z.boolean(),
|
|
148
|
+
})).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.requirements),
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
export const SlopcameraCapabilityModuleDeclarationSchema = z.strictObject({
|
|
152
|
+
commands: z.array(SlopcameraCapabilityCommandSchema).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.commands),
|
|
153
|
+
description: z.string().min(1).max(1_024),
|
|
154
|
+
moduleId: z.string().min(1).max(128).regex(/^slopcamera\.capability\.[a-z][a-z0-9-]*$/u),
|
|
155
|
+
operationKeys: z.array(z.string().min(3).max(256)).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.operations),
|
|
156
|
+
profiles: z.array(SlopcameraCapabilityProfileSchema).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.profiles),
|
|
157
|
+
stability: z.enum(["stable", "experimental"]),
|
|
158
|
+
title: z.string().min(1).max(160),
|
|
159
|
+
toolNames: z.array(z.string().min(1).max(128)).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.tools),
|
|
160
|
+
trust: z.enum(["portable", "local-effects", "trusted-authoring", "native-adapter", "paid-provider"]),
|
|
161
|
+
version: positiveSafeInteger,
|
|
162
|
+
workflowKeys: z.array(z.string().min(3).max(256)).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.workflows),
|
|
163
|
+
}).superRefine((module, context) => {
|
|
164
|
+
if (module.commands.length + module.operationKeys.length + module.profiles.length + module.toolNames.length + module.workflowKeys.length === 0) {
|
|
165
|
+
context.addIssue({ code: "custom", message: "A capability module must expose at least one command, operation, profile, tool, or workflow." })
|
|
166
|
+
}
|
|
167
|
+
for (const [name, values] of [["operationKeys", module.operationKeys], ["toolNames", module.toolNames], ["workflowKeys", module.workflowKeys]] as const) {
|
|
168
|
+
if (new Set(values).size !== values.length) {
|
|
169
|
+
context.addIssue({ code: "custom", path: [name], message: `${name} must be unique within a capability module.` })
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
export const SlopcameraCapabilityModuleSchema = z.strictObject({
|
|
175
|
+
commands: z.array(SlopcameraCapabilityCommandSchema).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.commands),
|
|
176
|
+
description: z.string().min(1).max(1_024),
|
|
177
|
+
moduleId: z.string().min(1).max(128).regex(/^slopcamera\.capability\.[a-z][a-z0-9-]*$/u),
|
|
178
|
+
operations: z.array(SlopcameraCapabilityOperationSchema).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.operations),
|
|
179
|
+
profiles: z.array(SlopcameraCapabilityProfileSchema).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.profiles),
|
|
180
|
+
stability: z.enum(["stable", "experimental"]),
|
|
181
|
+
title: z.string().min(1).max(160),
|
|
182
|
+
tools: z.array(SlopcameraCapabilityToolSchema).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.tools),
|
|
183
|
+
trust: z.enum(["portable", "local-effects", "trusted-authoring", "native-adapter", "paid-provider"]),
|
|
184
|
+
version: positiveSafeInteger,
|
|
185
|
+
workflows: z.array(SlopcameraCapabilityWorkflowSchema).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.workflows),
|
|
186
|
+
}).superRefine((module, context) => {
|
|
187
|
+
const identities = [
|
|
188
|
+
["commands", module.commands.map(value => value.name)],
|
|
189
|
+
["operations", module.operations.map(value => value.key)],
|
|
190
|
+
["profiles", module.profiles.map(value => value.id)],
|
|
191
|
+
["tools", module.tools.map(value => value.name)],
|
|
192
|
+
["workflows", module.workflows.map(value => value.key)],
|
|
193
|
+
] as const
|
|
194
|
+
if (identities.every(([, values]) => values.length === 0)) {
|
|
195
|
+
context.addIssue({ code: "custom", message: "A capability module must expose at least one command, operation, profile, tool, or workflow." })
|
|
196
|
+
}
|
|
197
|
+
for (const [name, values] of identities) {
|
|
198
|
+
if (new Set(values).size !== values.length) context.addIssue({ code: "custom", path: [name], message: `${name} must be unique within a capability module.` })
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
const manifestBodyBaseSchema = z.strictObject({
|
|
203
|
+
host: z.enum(["portable", "local"]),
|
|
204
|
+
kind: z.literal("slopcamera.capability-manifest"),
|
|
205
|
+
modules: z.array(SlopcameraCapabilityModuleSchema).min(1).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.modules),
|
|
206
|
+
schemaVersion: z.literal(1),
|
|
207
|
+
toolVersion: z.string().min(1).max(128),
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
type ManifestBody = z.infer<typeof manifestBodyBaseSchema>
|
|
211
|
+
|
|
212
|
+
function validateManifestOwnership(manifest: ManifestBody, context: z.RefinementCtx): void {
|
|
213
|
+
const ownership = new Map<string, string>()
|
|
214
|
+
for (const [moduleIndex, module] of manifest.modules.entries()) {
|
|
215
|
+
for (const identity of [
|
|
216
|
+
module.moduleId,
|
|
217
|
+
...module.commands.map(value => `command:${value.name}`),
|
|
218
|
+
...module.operations.map(value => `operation:${value.key}`),
|
|
219
|
+
...module.profiles.map(value => `profile:${value.id}`),
|
|
220
|
+
...module.tools.map(value => `tool:${value.name}`),
|
|
221
|
+
...module.workflows.map(value => `workflow:${value.key}`),
|
|
222
|
+
]) {
|
|
223
|
+
const owner = ownership.get(identity)
|
|
224
|
+
if (owner !== undefined) context.addIssue({ code: "custom", path: ["modules", moduleIndex], message: `${identity} is owned by both ${owner} and ${module.moduleId}.` })
|
|
225
|
+
ownership.set(identity, module.moduleId)
|
|
226
|
+
}
|
|
227
|
+
if (manifest.host === "portable" && module.trust !== "portable") {
|
|
228
|
+
context.addIssue({ code: "custom", path: ["modules", moduleIndex, "trust"], message: "Portable manifests may contain only portable capability modules." })
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const manifestBodySchema = manifestBodyBaseSchema.superRefine(validateManifestOwnership)
|
|
234
|
+
|
|
235
|
+
export const SlopcameraCapabilityManifestSchema = manifestBodyBaseSchema.extend({
|
|
236
|
+
manifestSha256: digest,
|
|
237
|
+
}).superRefine(validateManifestOwnership)
|
|
238
|
+
|
|
239
|
+
const compilerInputSchema = z.strictObject({
|
|
240
|
+
host: z.enum(["portable", "local"]),
|
|
241
|
+
modules: z.array(SlopcameraCapabilityModuleDeclarationSchema).min(1).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.modules),
|
|
242
|
+
operations: z.array(SlopcameraCapabilityOperationSchema).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.operations),
|
|
243
|
+
tools: z.array(SlopcameraCapabilityToolSchema).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.tools),
|
|
244
|
+
toolVersion: z.string().min(1).max(128),
|
|
245
|
+
workflows: z.array(SlopcameraCapabilityWorkflowSchema).max(SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.workflows),
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
export type SlopcameraCapabilityOperation = Readonly<z.infer<typeof SlopcameraCapabilityOperationSchema>>
|
|
249
|
+
export type SlopcameraCapabilityWorkflow = Readonly<z.infer<typeof SlopcameraCapabilityWorkflowSchema>>
|
|
250
|
+
export type SlopcameraCapabilityTool = Readonly<z.infer<typeof SlopcameraCapabilityToolSchema>>
|
|
251
|
+
export type SlopcameraCapabilityModuleDeclaration = Readonly<z.infer<typeof SlopcameraCapabilityModuleDeclarationSchema>>
|
|
252
|
+
export type SlopcameraCapabilityManifest = Readonly<z.infer<typeof SlopcameraCapabilityManifestSchema>>
|
|
253
|
+
|
|
254
|
+
function compareBy<Key extends string>(key: Key) {
|
|
255
|
+
return <Value extends Readonly<Record<Key, string>>>(left: Value, right: Value): number => left[key].localeCompare(right[key])
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function indexed<Value>(values: readonly Value[], key: (value: Value) => string, label: string): ReadonlyMap<string, Value> {
|
|
259
|
+
const result = new Map<string, Value>()
|
|
260
|
+
for (const value of values) {
|
|
261
|
+
const identity = key(value)
|
|
262
|
+
if (result.has(identity)) throw new TypeError(`Duplicate ${label}: ${identity}.`)
|
|
263
|
+
result.set(identity, value)
|
|
264
|
+
}
|
|
265
|
+
return result
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function assertExactOwnership(label: string, available: ReadonlyMap<string, unknown>, owners: ReadonlyMap<string, string>): void {
|
|
269
|
+
for (const key of available.keys()) {
|
|
270
|
+
if (!owners.has(key)) throw new TypeError(`Unowned ${label}: ${key}.`)
|
|
271
|
+
}
|
|
272
|
+
for (const key of owners.keys()) {
|
|
273
|
+
if (!available.has(key)) throw new TypeError(`Unknown ${label} declared by a capability module: ${key}.`)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function defineSlopcameraCapabilityModule(input: unknown): SlopcameraCapabilityModuleDeclaration {
|
|
278
|
+
return deepFreezeJson(SlopcameraCapabilityModuleDeclarationSchema.parse(
|
|
279
|
+
createBoundedJsonValueSnapshot(input, SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.bytes, "capability module").value,
|
|
280
|
+
))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function compileSlopcameraCapabilityManifest(input: unknown): SlopcameraCapabilityManifest {
|
|
284
|
+
const parsed = compilerInputSchema.parse(
|
|
285
|
+
createBoundedJsonValueSnapshot(input, SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.bytes, "capability manifest compiler input").value,
|
|
286
|
+
)
|
|
287
|
+
const operations = indexed(parsed.operations, value => value.key, "operation inventory key")
|
|
288
|
+
const workflows = indexed(parsed.workflows, value => value.key, "workflow inventory key")
|
|
289
|
+
const tools = indexed(parsed.tools, value => value.name, "tool inventory name")
|
|
290
|
+
const operationOwners = new Map<string, string>()
|
|
291
|
+
const workflowOwners = new Map<string, string>()
|
|
292
|
+
const toolOwners = new Map<string, string>()
|
|
293
|
+
const moduleIds = new Set<string>()
|
|
294
|
+
const commandOwners = new Map<string, string>()
|
|
295
|
+
const profileOwners = new Map<string, string>()
|
|
296
|
+
|
|
297
|
+
const claim = (owners: Map<string, string>, key: string, moduleId: string, label: string): void => {
|
|
298
|
+
const owner = owners.get(key)
|
|
299
|
+
if (owner !== undefined) throw new TypeError(`${label} ${key} is owned by both ${owner} and ${moduleId}.`)
|
|
300
|
+
owners.set(key, moduleId)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const modules = [...parsed.modules].sort(compareBy("moduleId")).map(module => {
|
|
304
|
+
if (moduleIds.has(module.moduleId)) throw new TypeError(`Duplicate capability module: ${module.moduleId}.`)
|
|
305
|
+
moduleIds.add(module.moduleId)
|
|
306
|
+
for (const command of module.commands) claim(commandOwners, command.name, module.moduleId, "Command")
|
|
307
|
+
for (const profile of module.profiles) claim(profileOwners, profile.id, module.moduleId, "Profile")
|
|
308
|
+
const resolvedOperations = module.operationKeys.map(key => {
|
|
309
|
+
claim(operationOwners, key, module.moduleId, "Operation")
|
|
310
|
+
const value = operations.get(key)
|
|
311
|
+
if (value === undefined) throw new TypeError(`Unknown operation declared by ${module.moduleId}: ${key}.`)
|
|
312
|
+
return value
|
|
313
|
+
}).sort(compareBy("key"))
|
|
314
|
+
const resolvedWorkflows = module.workflowKeys.map(key => {
|
|
315
|
+
claim(workflowOwners, key, module.moduleId, "Workflow")
|
|
316
|
+
const value = workflows.get(key)
|
|
317
|
+
if (value === undefined) throw new TypeError(`Unknown workflow declared by ${module.moduleId}: ${key}.`)
|
|
318
|
+
return value
|
|
319
|
+
}).sort(compareBy("key"))
|
|
320
|
+
const resolvedTools = module.toolNames.map(name => {
|
|
321
|
+
claim(toolOwners, name, module.moduleId, "Tool")
|
|
322
|
+
const value = tools.get(name)
|
|
323
|
+
if (value === undefined) throw new TypeError(`Unknown tool declared by ${module.moduleId}: ${name}.`)
|
|
324
|
+
return value
|
|
325
|
+
}).sort(compareBy("name"))
|
|
326
|
+
const { operationKeys: _operationKeys, toolNames: _toolNames, workflowKeys: _workflowKeys, ...metadata } = module
|
|
327
|
+
return {
|
|
328
|
+
...metadata,
|
|
329
|
+
commands: [...module.commands].sort(compareBy("name")),
|
|
330
|
+
operations: resolvedOperations,
|
|
331
|
+
profiles: [...module.profiles].sort(compareBy("id")),
|
|
332
|
+
tools: resolvedTools,
|
|
333
|
+
workflows: resolvedWorkflows,
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
assertExactOwnership("operation", operations, operationOwners)
|
|
338
|
+
assertExactOwnership("workflow", workflows, workflowOwners)
|
|
339
|
+
assertExactOwnership("tool", tools, toolOwners)
|
|
340
|
+
const body = manifestBodySchema.parse({
|
|
341
|
+
host: parsed.host,
|
|
342
|
+
kind: "slopcamera.capability-manifest",
|
|
343
|
+
modules,
|
|
344
|
+
schemaVersion: 1,
|
|
345
|
+
toolVersion: parsed.toolVersion,
|
|
346
|
+
})
|
|
347
|
+
return deepFreezeJson(SlopcameraCapabilityManifestSchema.parse({
|
|
348
|
+
...body,
|
|
349
|
+
manifestSha256: canonicalJsonSha256(body),
|
|
350
|
+
}))
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function parseSlopcameraCapabilityManifest(input: unknown): SlopcameraCapabilityManifest {
|
|
354
|
+
const manifest = SlopcameraCapabilityManifestSchema.parse(
|
|
355
|
+
createBoundedJsonValueSnapshot(input, SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS.bytes, "capability manifest").value,
|
|
356
|
+
)
|
|
357
|
+
const { manifestSha256, ...body } = manifest
|
|
358
|
+
const actual = canonicalJsonSha256(body)
|
|
359
|
+
if (actual !== manifestSha256) throw new TypeError(`Capability manifest digest mismatch: expected ${manifestSha256}, received ${actual}.`)
|
|
360
|
+
return deepFreezeJson(manifest)
|
|
361
|
+
}
|