stillmade-sdk 0.1.4 → 0.1.6
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/.claude/settings.json +15 -0
- package/.cursor/hooks.json +11 -0
- package/.gitignore +2 -0
- package/.stillmade/sdk-baseline.json +65 -0
- package/AGENTS.md +29 -7
- package/AGENT_CONTROL.md +2 -1
- package/BROWSER_APPLICATIONS.md +128 -3
- package/GITHUB_APPLICATIONS.md +184 -1
- package/NATIVE_APPLICATION_ADAPTATION.md +565 -0
- package/PASTE_TO_LLM.md +29 -10
- package/PORTABLE.md +5 -4
- package/REMIX.md +16 -10
- package/SDK.md +511 -46
- package/UI_AESTHETIC.md +37 -6
- package/api-index.json +90 -0
- package/blocks/connected-app/versions/{1.2.0 → 1.5.0}/task-selection.js +5 -3
- package/blocks/connected-app/versions/{1.4.0 → 1.5.0}/view.js +4 -2
- package/blocks/native/sm.ai-decide/versions/1.0.0/package.js +149 -0
- package/blocks/native/sm.ai-write/versions/1.0.0/package.js +147 -0
- package/blocks/native/sm.connect-app/versions/1.0.0/package.js +55 -0
- package/blocks/native/sm.contact-fields/versions/1.0.0/package.js +197 -0
- package/blocks/native/sm.continue-if/versions/1.0.0/package.js +168 -0
- package/blocks/native/sm.generate-image/versions/2.1.0/package.js +101 -0
- package/blocks/native/sm.meta-creative/versions/1.0.0/package.js +206 -0
- package/blocks/native/sm.new-items/versions/1.0.0/package.js +191 -0
- package/blocks/native/sm.voiceover/versions/2.1.5/package.js +179 -0
- package/blocks/native/sm.voiceover/versions/2.1.6/package.js +179 -0
- package/blocks/native/sm.when/versions/1.0.0/package.js +129 -0
- package/blocks/native/sm.write-summary/versions/1.0.0/package.js +133 -0
- package/blocks/occasion-story/package.js +1 -1
- package/blocks/occasion-story/versions/1.0.2/package.js +1682 -0
- package/blocks/photo-reel-composer/versions/1.0.1/package.js +3026 -0
- package/blocks/release-visuals/package.js +1 -1
- package/blocks/release-visuals/versions/1.0.2/package.js +1500 -0
- package/blocks/short-form-finish/package.js +1 -1
- package/blocks/short-form-finish/versions/1.0.3/package.js +1026 -0
- package/package.json +1 -1
- package/packages/block-cli/cli.js +16 -1
- package/packages/block-cli/format.js +2 -0
- package/packages/block-cli/ready-gate.js +164 -0
- package/packages/block-platform/block-readiness.js +3 -2
- package/packages/block-platform/canvas.js +1 -1
- package/packages/block-platform/catalog.js +6 -4
- package/packages/block-platform/chat-requirements.js +8 -3
- package/packages/block-platform/collaboration.js +5 -5
- package/packages/block-platform/first-revenue-project-types.js +2 -2
- package/packages/block-platform/flow-inspection.js +2 -2
- package/packages/block-platform/native-application-validation-context.js +20 -0
- package/packages/block-platform/plain-descriptions.js +67 -0
- package/packages/block-platform/product-ad-variants-project-type.js +6 -3
- package/packages/block-platform/project-context.js +4 -4
- package/packages/block-platform/project-type-composer.js +21 -12
- package/packages/block-platform/project-type-draft.js +2 -2
- package/packages/block-platform/project-type-recommendations.js +1 -1
- package/packages/block-platform/project-types.js +9 -4
- package/packages/block-platform/revenue-review-project-types.js +6 -5
- package/packages/block-platform/segment-runner.js +35 -11
- package/packages/block-platform/step-groups.js +78 -0
- package/packages/block-platform/steps.js +12 -8
- package/packages/block-platform/type-workspace.js +4 -4
- package/packages/block-platform/workflow-loop.js +4 -0
- package/packages/block-platform/workspace-input-readset.js +18 -4
- package/packages/block-sdk/admission.js +5 -17
- package/packages/block-sdk/agent-inputs.js +2 -0
- package/packages/block-sdk/agent-inspection.js +4 -2
- package/packages/block-sdk/browser-application-artifacts.js +1 -1
- package/packages/block-sdk/browser-application-execution.d.ts +2 -2
- package/packages/block-sdk/browser-application-execution.js +30 -12
- package/packages/block-sdk/browser-application-guest.d.ts +2 -0
- package/packages/block-sdk/browser-application-guest.js +16 -9
- package/packages/block-sdk/browser-application.d.ts +12 -4
- package/packages/block-sdk/browser-application.js +27 -13
- package/packages/block-sdk/builder-contract.js +25 -17
- package/packages/block-sdk/capabilities.js +38 -19
- package/packages/block-sdk/connected-app.js +26 -6
- package/packages/block-sdk/connection-adapters.js +19 -2
- package/packages/block-sdk/contracts-core.js +8 -0
- package/packages/block-sdk/contracts.js +9 -5
- package/packages/block-sdk/document-drafts.js +7 -1
- package/packages/block-sdk/document-store-binding.js +2 -1
- package/packages/block-sdk/file-bundle.js +4 -1
- package/packages/block-sdk/first-party-packages.js +18 -2
- package/packages/block-sdk/generation-catalog.js +3 -2
- package/packages/block-sdk/index.d.ts +20 -1
- package/packages/block-sdk/index.js +1 -0
- package/packages/block-sdk/native-application-package.d.ts +22 -0
- package/packages/block-sdk/native-application-package.js +77 -0
- package/packages/block-sdk/native_adapter.py +76 -0
- package/packages/block-sdk/package.json +1 -0
- package/packages/block-sdk/pipeline-runtime-host.d.ts +2 -0
- package/packages/block-sdk/project-flow-contract.js +2 -0
- package/packages/block-sdk/remix-policy.js +3 -1
- package/packages/block-sdk/resolver-admission.js +17 -0
- package/packages/block-sdk/runtime.js +4 -1
- package/packages/block-sdk/schema-fields.js +34 -2
- package/packages/block-sdk/semantic-relationships.js +14 -0
- package/packages/block-sdk/shared-controls.js +3 -1
- package/packages/block-sdk/shared-view.js +6 -1
- package/packages/block-sdk/source-rights.js +6 -5
- package/packages/block-sdk/testing.js +3 -0
- package/packages/design-system/theme.css +12 -3
|
@@ -26,7 +26,8 @@ export const VIDEO_GENERATION_MODELS=Object.freeze({
|
|
|
26
26
|
'seedance-2.0-fast':video('Seedance 2.0 Fast',{aspectRatios:SEEDANCE_ASPECTS,resolutions:['480p','720p'],durations:SEEDANCE_DURATIONS,lastFrame:true,seed:true,nativeAudio:true}),
|
|
27
27
|
'seedance-2.0-mini':video('Seedance 2.0 Mini',{aspectRatios:SEEDANCE_ASPECTS,resolutions:['480p','720p'],durations:SEEDANCE_DURATIONS,lastFrame:true,seed:true,nativeAudio:true}),
|
|
28
28
|
'kling-v3-turbo':video('Kling 3.0 Turbo',{aspectRatios:['16:9','9:16','1:1'],durations:[5,10]}),
|
|
29
|
-
|
|
29
|
+
// Atlas serves Wan 2.6 Flash as image-to-video only (its text-to-video Flash was withdrawn).
|
|
30
|
+
'wan-2.6-flash':video('Wan 2.6 Flash',{aspectRatios:['16:9','9:16','1:1'],durations:[3,4,5],requiresFirstFrame:true}),
|
|
30
31
|
'veo-3.1-lite':video('Veo 3.1 Lite',{aspectRatios:['16:9','9:16'],resolutions:['720p','1080p'],durations:[4,6,8],lastFrame:true,nativeAudio:true}),
|
|
31
32
|
'vidu-q3-turbo':video('Vidu Q3 Turbo',{durations:[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],lastFrame:true,nativeAudio:true}),
|
|
32
33
|
'wan-2.7':video('Wan 2.7',{resolutions:['720p','1080p'],durations:[2,3,4,5,6,7,8,9,10,11,12,13,14,15],lastFrame:true,requiresFirstFrame:true,seed:true}),
|
|
@@ -38,7 +39,7 @@ export const DEFAULT_SETTING='default';
|
|
|
38
39
|
export const IMAGE_SETTING_KEYS=Object.freeze(['model','aspectRatio','resolution','quality']);
|
|
39
40
|
export const VIDEO_SETTING_KEYS=Object.freeze(['model','duration','resolution','aspectRatio','generateAudio']);
|
|
40
41
|
export const DEFAULT_IMAGE_MODEL='nano-banana-2';
|
|
41
|
-
export const DEFAULT_VIDEO_MODEL='
|
|
42
|
+
export const DEFAULT_VIDEO_MODEL='vidu-q3-turbo';
|
|
42
43
|
export const GENERATION_LIMITS=Object.freeze({references:8,negativePrompt:2000,seed:2147483647});
|
|
43
44
|
|
|
44
45
|
const pick=(allowed,value,fallback)=>allowed.length?(allowed.includes(value)?value:fallback??allowed[0]):DEFAULT_SETTING;
|
|
@@ -6,6 +6,7 @@ export * from './workflow-approvals.js';
|
|
|
6
6
|
export * from './package-semantics.js';
|
|
7
7
|
export * from './define-block.js';
|
|
8
8
|
export * from './adapter-registry.js';
|
|
9
|
+
export * from './native-application-package.js';
|
|
9
10
|
export * from './block-storage.js';
|
|
10
11
|
export type {BlockViewDocumentTools,DocumentDraftOperation,DocumentEntry,DocumentPath} from './document-drafts.js';
|
|
11
12
|
export type {BlockViewDocumentOptions,BlockViewDocumentConnection} from './document-store-binding.js';
|
|
@@ -26,7 +27,7 @@ export interface Manifest {
|
|
|
26
27
|
/** Events the Block can run on by itself, once the project owner turns them on. */
|
|
27
28
|
triggers?: import('./triggers.js').BlockTrigger[];
|
|
28
29
|
semantics?: import('./semantic-contracts.js').SemanticDefinition[];
|
|
29
|
-
provenance?: { remixSource?: BlockPackage; remixPolicy?: {allowRemixing: boolean; sourceInspection?: "yes"|"no"|"license-controlled"}; remixAncestors?: {id: string; version: string; digest: string}[]; remixedFrom?: { id: string; version: string; digest: string }; [key: string]: unknown };
|
|
30
|
+
provenance?: { remixSource?: BlockPackage; remixPolicy?: {allowRemixing: boolean; sourceInspection?: "yes"|"no"|"license-controlled"}; remixAncestors?: {id: string; version: string; digest: string}[]; remixedFrom?: { id: string; version: string; digest: string }; githubSource?: {repository: string; commit: string; path: string}; [key: string]: unknown };
|
|
30
31
|
summary?: Record<string, unknown>;
|
|
31
32
|
license?: string;
|
|
32
33
|
category?: string;
|
|
@@ -671,3 +672,21 @@ export function gainFor(measuredLufs: number, peak: number, targetLufs?: number)
|
|
|
671
672
|
export function wavWriter(channelCount: number, sampleRate: number, frames: number): Readonly<{buffer: ArrayBuffer; write(channels: Float32Array[], from?: number, to?: number, gain?: number): void}>;
|
|
672
673
|
export function encodeWav(channels: Float32Array[], sampleRate: number): ArrayBuffer;
|
|
673
674
|
export function decodeWav(buffer: ArrayBuffer): {channels: Float32Array[]; sampleRate: number};
|
|
675
|
+
/** Browser application bundle profiles. They describe a bundle format, not runtime admission.
|
|
676
|
+
* `desktop-browser-application/2` adds srcdoc frames (features: wasm, srcdocFrames);
|
|
677
|
+
* `desktop-browser-application/3` also declares local Web Workers (features: wasm, srcdocFrames, localWorkers: true). */
|
|
678
|
+
export const BROWSER_APPLICATION_EMBEDDED_PROFILE:'desktop-browser-application/2';
|
|
679
|
+
export const BROWSER_APPLICATION_LOCAL_WORKER_PROFILE:'desktop-browser-application/3';
|
|
680
|
+
/** Content digests of a validated browser application package: packageDigest covers the whole package, bundleDigest only its application bundle. */
|
|
681
|
+
export function getBrowserApplicationSourceDigests(value:unknown,options?:{signal?:AbortSignal}):Promise<Readonly<{packageDigest:string;bundleDigest:string}>>;
|
|
682
|
+
/** Static check that every input and output declares a semantic meaning, whether it is required, a description, and a field schema for json/object outputs, so the Block can take part in typed resolution. runtimeVerified is always false. */
|
|
683
|
+
export function reviewResolverContract(pkg:unknown):{name:string;runtimeVerified:false;contract:'semantic-resolver/1';passed:boolean;code?:'RESOLVER_CONTRACT';issues?:string[];message:string;manual?:{inputs:Array<{name:string;type:string;semantic:string;required:boolean;sources:string[]}>;outputs:Array<{name:string;type:string;semantic:string}>}};
|
|
684
|
+
/** Native (desktop-only) application packages: exactly { manifest, nativeApplication }, bound to an exact GitHub source pin.
|
|
685
|
+
* Ordinary SDK execution and admission refuse them (NATIVE_APPLICATION_RUNTIME_UNAVAILABLE); installation needs host-verified
|
|
686
|
+
* admission bound to the exact source and runtime plus a managed interactive runtime. See NATIVE_APPLICATION_ADAPTATION.md. */
|
|
687
|
+
export const NATIVE_APPLICATION_PACKAGE_CONTRACT:'native-application-package/1';
|
|
688
|
+
export const NATIVE_APPLICATION_PACKAGE_LIMITS:Readonly<{packageBytes:262144;manifestBytes:65536;adapterBytes:64000;adapterFiles:8;ports:16;operations:16;capturedFiles:8}>;
|
|
689
|
+
export const NATIVE_APPLICATION_RUNTIME_REQUIREMENT:string;
|
|
690
|
+
/** Validates a native application package's strict source syntax and returns its detached, frozen copy. Throws NATIVE_APPLICATION_PACKAGE errors. Not runtime admission. */
|
|
691
|
+
export function defineNativeApplicationPackage<T=unknown>(value:T):T;
|
|
692
|
+
export function validateNativeApplicationPackage(value:unknown):true;
|
|
@@ -148,6 +148,7 @@ export * from './repository-application.js';
|
|
|
148
148
|
export * from './browser-application.js';
|
|
149
149
|
export * from './browser-application-execution.js';
|
|
150
150
|
export * from './browser-application-guest.js';
|
|
151
|
+
export * from './native-application-package.js';
|
|
151
152
|
|
|
152
153
|
export * from './browser-application-artifacts.js';
|
|
153
154
|
export * from './connected-app.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type {Manifest,Port} from './index.js';
|
|
2
|
+
export type NativeApplicationPort=Omit<Port,'type'> & {type:'text'|'number'|'integer'|'boolean'|'json'|'object'|'image'};
|
|
3
|
+
export interface NativeApplicationSourcePin {repository:string;commit:string;path:string}
|
|
4
|
+
export interface NativeApplicationPackage {
|
|
5
|
+
manifest:Omit<Manifest,'runtime'|'kind'|'inputs'|'outputs'|'provenance'|'state'> & {
|
|
6
|
+
runtime:'native-application';kind:'workspace';inputs:Record<string,NativeApplicationPort>;outputs:Record<string,NativeApplicationPort>;
|
|
7
|
+
provenance:{githubSource:NativeApplicationSourcePin};
|
|
8
|
+
};
|
|
9
|
+
nativeApplication:{
|
|
10
|
+
schemaVersion:1;
|
|
11
|
+
build:{family:'cmake'|'qmake'|'upstream-artifact'|'other';sourcePaths:string[];arguments:string[];artifacts:string[];dependencies:{kind:'system-package'|'git-source'|'git-data'|'toolchain';name:string;sourcePaths:string[]}[];sourceEdits?:{path:string;baseSha256:string;edits:{match:string;replacement:string}[]}[]};
|
|
12
|
+
launch:{entry:string;args:string[]};adapterFiles:{path:string;content:string}[];documentPaths:string[];
|
|
13
|
+
operations:{id:string;files:{port:string;path:string;mimeType:'image/png'|'text/plain'|'application/json'}[]}[];
|
|
14
|
+
stateGuidance:string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export const NATIVE_APPLICATION_PACKAGE_CONTRACT:'native-application-package/1';
|
|
18
|
+
export const NATIVE_APPLICATION_PACKAGE_LIMITS:Readonly<{packageBytes:262144;manifestBytes:65536;adapterBytes:64000;adapterFiles:8;ports:16;operations:16;capturedFiles:8}>;
|
|
19
|
+
export const NATIVE_APPLICATION_RUNTIME_REQUIREMENT:string;
|
|
20
|
+
/** Source validation only; this never installs or admits a runtime. */
|
|
21
|
+
export function defineNativeApplicationPackage(value:unknown):Readonly<NativeApplicationPackage>;
|
|
22
|
+
export function validateNativeApplicationPackage(value:unknown):true;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {assert,plain,safeDocument,validateManifest} from './contracts.js';
|
|
2
|
+
|
|
3
|
+
// Source requests only. None of these limits, build flags or dependencies grant
|
|
4
|
+
// execution, installation, networking, filesystem access or paid calls.
|
|
5
|
+
export const NATIVE_APPLICATION_PACKAGE_CONTRACT='native-application-package/1';
|
|
6
|
+
export const NATIVE_APPLICATION_PACKAGE_LIMITS=Object.freeze({packageBytes:262144,manifestBytes:65536,adapterBytes:64000,adapterFiles:8,ports:16,operations:16,capturedFiles:8});
|
|
7
|
+
export const NATIVE_APPLICATION_RUNTIME_REQUIREMENT='Native application installation requires native package validation, host-verified admission bound to the exact source and runtime, and managed interactive application loading. A build or development-session report cannot grant installation. Single-capability extraction is a separate explicit request.';
|
|
8
|
+
const check=(ok,message)=>assert(ok,'NATIVE_APPLICATION_PACKAGE',message);
|
|
9
|
+
const exact=(v,keys)=>plain(v)&&Reflect.ownKeys(v).length===keys.length&&keys.every(k=>Object.hasOwn(v,k));
|
|
10
|
+
const bytes=v=>new TextEncoder().encode(typeof v==='string'?v:JSON.stringify(v)).length;
|
|
11
|
+
const scalar=new Set(['text','number','integer','boolean','json','object']);
|
|
12
|
+
const hash=v=>typeof v==='string'&&/^[a-f0-9]{64}$/.test(v);
|
|
13
|
+
const text=(v,max)=>typeof v==='string'&&v.trim().length>0&&v.length<=max&&!/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(v);
|
|
14
|
+
const utf8=v=>new TextDecoder('utf-8',{fatal:true}).decode(new TextEncoder().encode(v))===v;
|
|
15
|
+
const distinct=v=>new Set(v).size===v.length;
|
|
16
|
+
const noAliases=v=>distinct(v)&&!v.some(p=>v.some(q=>p!==q&&q.startsWith(p+'/')));
|
|
17
|
+
const path=v=>typeof v==='string'&&v.length>0&&v.length<=240&&/^[a-zA-Z0-9_.\/-]+$/.test(v)&&v.split('/').every(p=>p&&p!=='.'&&p!=='..');
|
|
18
|
+
const paths=(v,count)=>Array.isArray(v)&&v.length>0&&v.length<=count&&v.every(path)&&distinct(v);
|
|
19
|
+
const args=(v,count,itemBytes)=>Array.isArray(v)&&v.length<=count&&v.every(x=>typeof x==='string'&&bytes(x)<=itemBytes&&!/[\x00-\x1f\x7f]/.test(x))&&bytes(v)<=16384;
|
|
20
|
+
const freeze=v=>{if(v&&typeof v==='object'){Object.values(v).forEach(freeze);Object.freeze(v);}return v;};
|
|
21
|
+
const defined=new WeakSet();
|
|
22
|
+
|
|
23
|
+
/** Strict source syntax, not runtime admission. Keep the original package
|
|
24
|
+
* digest; root-owned runtime bindings and saved revisions are held separately. */
|
|
25
|
+
export function defineNativeApplicationPackage(value){
|
|
26
|
+
if(defined.has(value))return value;
|
|
27
|
+
safeDocument(value);check(exact(value,['manifest','nativeApplication']),'Use exactly manifest and nativeApplication. Reports, runtime grants and build results are not portable package fields.');
|
|
28
|
+
check(bytes(value)<=NATIVE_APPLICATION_PACKAGE_LIMITS.packageBytes,'Native package source exceeds 256 KiB.');
|
|
29
|
+
const source=structuredClone(value),m=source.manifest,a=source.nativeApplication;
|
|
30
|
+
check(plain(m)&&m.runtime==='native-application'&&bytes(m)<=65536,'Use a bounded native-application manifest.');
|
|
31
|
+
// Ordinary SDK execution stays unavailable. Retain every other common check.
|
|
32
|
+
const errors=validateManifest(m).errors.filter(error=>error.path!=='runtime');
|
|
33
|
+
check(!errors.length,JSON.stringify(errors));
|
|
34
|
+
check(m.kind==='workspace'&&!m.id.startsWith('sm.'),'Native applications use an independently versioned creator workspace identity.');
|
|
35
|
+
check(m.platforms?.desktop?.supported===true&&m.platforms?.browser?.supported===false&&m.platforms?.phone?.supported===false,'Declare this managed native profile desktop-only. Browser feasibility is assessed separately.');
|
|
36
|
+
check(exact(m.provenance?.githubSource,['repository','commit','path']),'Bind the package to its exact canonical GitHub source pin.');
|
|
37
|
+
check(m.entry===undefined&&!m.desktopTools&&!m.permissions.desktop?.length&&!m.permissions.capabilities?.length&&!m.permissions.actions?.length,'Native application source grants no host tool, paid integration or arbitrary action access. Launch is declared inside nativeApplication.');
|
|
38
|
+
check(Array.isArray(m.permissions.project)&&m.permissions.project.every(scope=>['asset.read','asset.create'].includes(scope)),'Only host-mediated input and output assets are supported by this native profile.');
|
|
39
|
+
for(const [direction,ports] of [['inputs',m.inputs],['outputs',m.outputs]])check(Object.keys(ports).length<=(NATIVE_APPLICATION_PACKAGE_LIMITS.ports)&&Object.values(ports).every(p=>scalar.has(p.type)||p.type==='image'),direction+' require bounded scalar/JSON or host-materialized PNG ports.');
|
|
40
|
+
check(Object.keys(m.outputs).length>0,'Declare at least one real typed output.');
|
|
41
|
+
check(!Object.values(m.inputs).some(p=>p.type==='image')||m.permissions.project.includes('asset.read'),'PNG inputs require asset.read.');
|
|
42
|
+
check(!Object.values(m.outputs).some(p=>p.type==='image')||m.permissions.project.includes('asset.create'),'PNG outputs require asset.create.');
|
|
43
|
+
check(exact(a,['schemaVersion','build','launch','adapterFiles','documentPaths','operations','stateGuidance'])&&a.schemaVersion===1,'Use the versioned nativeApplication schemaVersion 1 contract.');
|
|
44
|
+
check(text(a.stateGuidance,1000),'Describe actual document checkpoint/restore behavior in bounded text. This prose does not grant state or multiplayer authority.');
|
|
45
|
+
const b=a.build;
|
|
46
|
+
check(exact(b,['family','sourcePaths','arguments','artifacts','dependencies',...(Object.hasOwn(b??{},'sourceEdits')?['sourceEdits']:[])])&&['cmake','qmake','upstream-artifact','other'].includes(b.family),'Declare a bounded build-family request, not installation commands.');
|
|
47
|
+
const scoped=p=>!m.provenance.githubSource.path||p.startsWith(m.provenance.githubSource.path+'/')||p===m.provenance.githubSource.path;
|
|
48
|
+
check(paths(b.sourcePaths,128)&&b.sourcePaths.every(scoped)&&args(b.arguments,16,240)&&paths(b.artifacts,8)&&noAliases(b.artifacts),'Use scoped source paths, bounded build arguments and distinct relative build artifacts.');
|
|
49
|
+
check(Array.isArray(b.dependencies)&&b.dependencies.length<=64,'Bound source-backed dependency requests.');
|
|
50
|
+
for(const d of b.dependencies)check(exact(d,['kind','name','sourcePaths'])&&['system-package','git-source','git-data','toolchain'].includes(d.kind)&&text(d.name,240)&&paths(d.sourcePaths,128)&&d.sourcePaths.every(scoped),'Dependencies require bounded names and source evidence; they never authorize an installer.');
|
|
51
|
+
if(Object.hasOwn(b,'sourceEdits')){
|
|
52
|
+
check(b.family==='cmake'&&Array.isArray(b.sourceEdits)&&b.sourceEdits.length>0&&b.sourceEdits.length<=8&&distinct(b.sourceEdits.map(r=>r?.path))&&bytes(b.sourceEdits)<=64000,'Exact derived-source edits require the reviewed CMake profile and bounded distinct files.');
|
|
53
|
+
for(const row of b.sourceEdits){check(exact(row,['path','baseSha256','edits'])&&path(row.path)&&scoped(row.path)&&hash(row.baseSha256)&&Array.isArray(row.edits)&&row.edits.length>0&&row.edits.length<=32,'Source edits bind bounded literal replacements to exact original source bytes.');for(const e of row.edits)check(exact(e,['match','replacement'])&&text(e.match,8192)&&typeof e.replacement==='string'&&e.replacement.length<=8192&&!e.replacement.includes('\0'),'Source edits are literal text, never commands; the host independently verifies each match.');}
|
|
54
|
+
}
|
|
55
|
+
check(Array.isArray(a.adapterFiles)&&a.adapterFiles.length>0&&a.adapterFiles.length<=8&&distinct(a.adapterFiles.map(f=>f?.path)),'Declare distinct bounded adapter source files.');
|
|
56
|
+
let adapterBytes=0;
|
|
57
|
+
for(const f of a.adapterFiles){check(exact(f,['path','content'])&&path(f.path)&&typeof f.content==='string'&&f.content.length>0&&!f.content.includes('\0')&&utf8(f.content),'Adapters contain bounded relative UTF-8 source files.');adapterBytes+=bytes(f.content);}
|
|
58
|
+
check(adapterBytes<=64000,'Adapter source exceeds 64,000 bytes.');
|
|
59
|
+
const adapters=a.adapterFiles.map(f=>f.path);
|
|
60
|
+
check(noAliases([...adapters,...b.artifacts]),'Adapters and build artifacts cannot alias.');
|
|
61
|
+
check(exact(a.launch,['entry','args'])&&path(a.launch.entry)&&[...adapters,...b.artifacts].includes(a.launch.entry)&&args(a.launch.args,32,1024),'Launch a declared adapter or artifact with bounded arguments.');
|
|
62
|
+
check(adapters.includes(a.launch.entry)||a.launch.args.some(arg=>adapters.some(p=>arg===p||arg.endsWith('='+p))),'The launcher must load an adapter through its actual startup hook.');
|
|
63
|
+
check(paths(a.documentPaths,8)&&noAliases(a.documentPaths),'Saved document paths are bounded distinct literal files.');
|
|
64
|
+
check(a.documentPaths.every(p=>p!=='.stillmade-exports'&&!p.startsWith('.stillmade-exports/')),'Saved documents cannot occupy the host-owned fresh export namespace.');
|
|
65
|
+
check(Array.isArray(a.operations)&&a.operations.length>0&&a.operations.length<=16&&distinct(a.operations.map(o=>o?.id)),'Declare distinct bounded export operations.');
|
|
66
|
+
const covered=new Set();
|
|
67
|
+
for(const o of a.operations){
|
|
68
|
+
check(exact(o,['id','files'])&&/^[a-z][a-z0-9._-]{0,63}$/.test(o.id||'')&&Array.isArray(o.files)&&o.files.length>0&&o.files.length+a.documentPaths.length<=8,'Each operation declares an ID and at most eight captured documents/exports.');
|
|
69
|
+
const mapped=new Set(),files=[];
|
|
70
|
+
for(const f of o.files){check(exact(f,['port','path','mimeType'])&&Object.hasOwn(m.outputs,f.port)&&!mapped.has(f.port)&&path(f.path),'Map each declared output once to a literal export path.');const t=m.outputs[f.port].type;check(f.mimeType===(t==='image'?'image/png':t==='text'?'text/plain':'application/json'),'Export MIME must match its typed output port.');mapped.add(f.port);covered.add(f.port);files.push(f.path);}
|
|
71
|
+
check(noAliases([...a.documentPaths,...files]),'Exports cannot alias saved documents or another export.');
|
|
72
|
+
check(Object.entries(m.outputs).every(([p,v])=>mapped.has(p)||v.required===false),'An operation may omit only optional output ports.');
|
|
73
|
+
}
|
|
74
|
+
check(covered.size===Object.keys(m.outputs).length,'Every declared output must have a real export mapping. Saved documents belong in documentPaths.');
|
|
75
|
+
freeze(source);defined.add(source);return source;
|
|
76
|
+
}
|
|
77
|
+
export function validateNativeApplicationPackage(value){defineNativeApplicationPackage(value);return true;}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Unprivileged native application adapter protocol. Call poll() on the app's
|
|
2
|
+
event loop (e.g. a Qt timer). Handlers belong to the Block, never to the host.
|
|
3
|
+
Acknowledgements are untrusted; the host separately captures/validates outputs.
|
|
4
|
+
No host filesystem, credential, network or billing service is exposed here.
|
|
5
|
+
"""
|
|
6
|
+
import json, os, socket, stat
|
|
7
|
+
|
|
8
|
+
PROTOCOL = 'stillmade.native.adapter/1'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class NativeAdapter:
|
|
12
|
+
def __init__(self, handlers, directory='/run/stillmade-adapter'):
|
|
13
|
+
if not isinstance(handlers, dict) or not handlers or not all(isinstance(key, str) and callable(handler) for key, handler in handlers.items()):
|
|
14
|
+
raise ValueError('Declare native adapter handlers')
|
|
15
|
+
self.handlers = dict(handlers)
|
|
16
|
+
self.directory = directory
|
|
17
|
+
self.context = self._read('context.json', 4096)
|
|
18
|
+
if set(self.context) != {'schemaVersion', 'protocol', 'sessionId', 'applicationSha256'} or self.context['schemaVersion'] != 1 or self.context['protocol'] != PROTOCOL:
|
|
19
|
+
raise ValueError('Unsupported native adapter context')
|
|
20
|
+
self.seen = set()
|
|
21
|
+
|
|
22
|
+
def _read(self, name, limit):
|
|
23
|
+
fd = os.open(os.path.join(self.directory, name), os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)
|
|
24
|
+
try:
|
|
25
|
+
info = os.fstat(fd)
|
|
26
|
+
if not stat.S_ISREG(info.st_mode) or info.st_size > limit:
|
|
27
|
+
raise ValueError('Bounded native adapter message required')
|
|
28
|
+
chunks, count = [], 0
|
|
29
|
+
while True:
|
|
30
|
+
chunk = os.read(fd, min(4096, limit + 1 - count))
|
|
31
|
+
if not chunk:
|
|
32
|
+
break
|
|
33
|
+
count += len(chunk)
|
|
34
|
+
if count > limit:
|
|
35
|
+
raise ValueError('Native adapter message limit')
|
|
36
|
+
chunks.append(chunk)
|
|
37
|
+
return json.loads(b''.join(chunks))
|
|
38
|
+
finally:
|
|
39
|
+
os.close(fd)
|
|
40
|
+
|
|
41
|
+
def poll(self):
|
|
42
|
+
"""Handle at most one command. This never creates a background thread;
|
|
43
|
+
GUI adapters choose the application's own event-loop scheduling."""
|
|
44
|
+
try:
|
|
45
|
+
request = self._read('request.json', 32768)
|
|
46
|
+
except FileNotFoundError:
|
|
47
|
+
return False
|
|
48
|
+
if set(request) != {'schemaVersion', 'sessionId', 'applicationSha256', 'requestId', 'action', 'payload'} or request['schemaVersion'] != 1 or request['sessionId'] != self.context['sessionId'] or request['applicationSha256'] != self.context['applicationSha256'] or request['action'] not in ['checkpoint', 'execute'] or not isinstance(request['payload'], dict) or not isinstance(request['requestId'], str) or len(request['requestId']) != 36:
|
|
49
|
+
raise ValueError('Native adapter request binding changed')
|
|
50
|
+
if request['requestId'] in self.seen:
|
|
51
|
+
return False
|
|
52
|
+
if len(self.seen) >= 64:
|
|
53
|
+
raise ValueError('Native adapter request limit')
|
|
54
|
+
# Mark before dispatch: a handler must never repeat after an uncertain
|
|
55
|
+
# acknowledgement or a subsequent event-loop tick.
|
|
56
|
+
self.seen.add(request['requestId'])
|
|
57
|
+
reply = {key: request[key] for key in ['schemaVersion', 'sessionId', 'applicationSha256', 'requestId']}
|
|
58
|
+
try:
|
|
59
|
+
handler = self.handlers.get(request['action'])
|
|
60
|
+
if handler is None:
|
|
61
|
+
raise ValueError('This application has no handler for ' + request['action'])
|
|
62
|
+
if handler(request['payload']) is not True:
|
|
63
|
+
raise ValueError('The application did not confirm its native operation')
|
|
64
|
+
reply['phase'] = 'completed'
|
|
65
|
+
except Exception as error:
|
|
66
|
+
reply['phase'] = 'failed'
|
|
67
|
+
reply['message'] = str(error)[:512]
|
|
68
|
+
encoded = json.dumps(reply, separators=(',', ':')).encode('utf-8')
|
|
69
|
+
if len(encoded) > 8192:
|
|
70
|
+
raise ValueError('Native adapter response limit')
|
|
71
|
+
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as connection:
|
|
72
|
+
connection.settimeout(1.0)
|
|
73
|
+
connection.connect(os.path.join(self.directory, 'response.sock'))
|
|
74
|
+
connection.sendall(encoded)
|
|
75
|
+
connection.shutdown(socket.SHUT_WR)
|
|
76
|
+
return True
|
|
@@ -2,6 +2,8 @@ export const PIPELINE_RUNTIME_HOST_VERSION:1;
|
|
|
2
2
|
export const PIPELINE_RUNTIME_FUNCTIONS:readonly string[];
|
|
3
3
|
export interface PipelineRuntimeHost {
|
|
4
4
|
version:1;
|
|
5
|
+
/** Optional host-owned authenticated image projection; grants no artifact authority. */
|
|
6
|
+
BlockImagePreview?: (props:{value:unknown})=>unknown;
|
|
5
7
|
[key:string]:unknown;
|
|
6
8
|
}
|
|
7
9
|
export function requirePipelineRuntimeHost(host:unknown):PipelineRuntimeHost;
|
|
@@ -69,6 +69,8 @@ export function projectFlowSnapshot(value){
|
|
|
69
69
|
assert(snapshot.goalSourceMaterials===undefined||Array.isArray(snapshot.goalSourceMaterials)&&snapshot.goalSourceMaterials.length<=8&&new Set(snapshot.goalSourceMaterials.map(item=>item.key)).size===snapshot.goalSourceMaterials.length&&snapshot.goalSourceMaterials.every(item=>plain(item)&&Object.keys(item).sort().join(',')==='description,key,type'&&typeof item.key==='string'&&/^[a-z][a-z0-9_]{0,63}$/.test(item.key)&&knownType(item.type)&&typeof item.description==='string'&&item.description.length<=240),'PROJECT_FLOW_SNAPSHOT','Goal source descriptions are bounded assumptions, not selected files.');
|
|
70
70
|
assert(snapshot.goalSourceInputs===undefined||Array.isArray(snapshot.goalSourceInputs)&&snapshot.goalSourceInputs.length<=32&&new Set(snapshot.goalSourceInputs.map(item=>`${item.stageId}\0${item.inputKey}`)).size===snapshot.goalSourceInputs.length&&snapshot.goalSourceInputs.every(item=>plain(item)&&Object.keys(item).sort().join(',')==='blockId,inputKey,sourceKeys,stageId,version'&&typeof item.stageId==='string'&&item.stageId.length<=160&&typeof item.blockId==='string'&&item.blockId.length<=160&&typeof item.version==='string'&&item.version.length<=40&&typeof item.inputKey==='string'&&item.inputKey.length<=160&&Array.isArray(item.sourceKeys)&&item.sourceKeys.length>=1&&item.sourceKeys.length<=8&&new Set(item.sourceKeys).size===item.sourceKeys.length&&item.sourceKeys.every(key=>snapshot.goalSourceMaterials?.some(material=>material.key===key))),'PROJECT_FLOW_SNAPSHOT','Planned material-to-input links are bounded assumptions, not actual bindings.');
|
|
71
71
|
assert(typeof snapshot.partial==='boolean','PROJECT_FLOW_SNAPSHOT','The Flow completeness state is required.');
|
|
72
|
+
// One Step per job: the jobs a person sees, each naming the placements that are its parts.
|
|
73
|
+
assert(snapshot.jobs===undefined||Array.isArray(snapshot.jobs)&&snapshot.jobs.length<=50&&snapshot.jobs.every(job=>plain(job)&&Object.keys(job).length===3&&typeof job.id==='string'&&job.id.length<=80&&typeof job.label==='string'&&job.label.length<=40&&Array.isArray(job.parts)&&job.parts.length>=2&&job.parts.length<=12&&job.parts.every(id=>typeof id==='string')),'PROJECT_FLOW_SNAPSHOT','Each Flow job needs an ID, a name and its parts.');
|
|
72
74
|
assert(Array.isArray(snapshot.placements)&&snapshot.placements.length<=PROJECT_FLOW_LIMITS.placements,'PROJECT_FLOW_SNAPSHOT','The Flow placement list exceeds its limit.');
|
|
73
75
|
assert(Array.isArray(snapshot.edges)&&snapshot.edges.length<=PROJECT_FLOW_LIMITS.edges,'PROJECT_FLOW_SNAPSHOT','The Flow connection list exceeds its limit.');
|
|
74
76
|
assert(Array.isArray(snapshot.issues)&&snapshot.issues.length<=PROJECT_FLOW_LIMITS.issues,'PROJECT_FLOW_SNAPSHOT','The Flow issue list exceeds its limit.');
|
|
@@ -11,6 +11,8 @@ export function blockRemixPolicy(content,{checkPreference=true}={}) {
|
|
|
11
11
|
if(!m||content.stages)return blocked('BLOCK_ONLY','Select a Block to remix.');
|
|
12
12
|
if(content.protected||m.id===ROOT_CHAT_ID)return blocked('PROTECTED','Starting Chat cannot be remixed.');
|
|
13
13
|
if(!['recipe','javascript','module','capability','comfyui'].includes(m.runtime))return blocked('RUNTIME_RESTRICTED','This Block still requires a trusted application workspace. An independent sandboxed remix is not available.');
|
|
14
|
+
// A creator who chose to hide the code keeps remixing off: the remix draft still
|
|
15
|
+
// carries the package, so the choice is enforced here, not only in the builder.
|
|
14
16
|
if(checkPreference&&(m.provenance?.remixPolicy?.allowRemixing===false||m.provenance?.remixPolicy?.sourceInspection==='no'))return blocked('REMIX_DISABLED','The creator has disabled the in-app remix action for this version. Existing upstream license rights are unchanged.');
|
|
15
17
|
if(!licenses.has(m.license)&&!(content.portable&&isPortableLicenseExpression(m.license)))return blocked('LICENSE_RESTRICTED','This license needs review before automatic remixing. Missing, custom and copyleft terms cannot be cleared by a checkbox.');
|
|
16
18
|
try {
|
|
@@ -29,4 +31,4 @@ export function assertRemixReady(content) {
|
|
|
29
31
|
return policy;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
export const REMIX_AUTHORING_REQUIREMENT = 'Every finished Block must support automatic remixing through the standard SDK contract. Include complete editable runtime source/configuration, typed inputs/outputs, UI controls, fixtures, and a supported permissive manifest.license with its complete real copyright/license notice in manifest.provenance.notice (or verified portable evidence). Do not disable remixing
|
|
34
|
+
export const REMIX_AUTHORING_REQUIREMENT = 'Every finished Block must support automatic remixing through the standard SDK contract. Include complete editable runtime source/configuration, typed inputs/outputs, UI controls, fixtures, and a supported permissive manifest.license with its complete real copyright/license notice in manifest.provenance.notice (or verified portable evidence). Do not disable remixing. The code stays private to its creator; others remix only through StillMade’s AI builder. No custom remix endpoint or host callback is needed. Preserve inherited source, notices and lineage. Never invent ownership, copyright holders or permission, or apply a new license to unresolved third-party source; return unsupported with a clear licensing reason instead. The shared SDK admission validator enforces this requirement.';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function reviewResolverContract(pkg){
|
|
2
|
+
const base={name:'Input/output resolver contract',runtimeVerified:false,contract:'semantic-resolver/1'},manifest=pkg?.manifest||{},issues=[];
|
|
3
|
+
for(const [name,port] of Object.entries(manifest.inputs||{})){
|
|
4
|
+
if(typeof port?.semantic!=='string')issues.push(`${name} input needs an exact semantic meaning`);
|
|
5
|
+
if(typeof port?.required!=='boolean')issues.push(`${name} input must declare whether it is required`);
|
|
6
|
+
if(typeof port?.description!=='string'||!port.description.trim())issues.push(`${name} input needs a purpose/format description`);
|
|
7
|
+
}
|
|
8
|
+
for(const [name,port] of Object.entries(manifest.outputs||{})){
|
|
9
|
+
if(typeof port?.semantic!=='string')issues.push(`${name} output needs an exact semantic meaning`);
|
|
10
|
+
if(typeof port?.description!=='string'||!port.description.trim())issues.push(`${name} output needs a result description`);
|
|
11
|
+
if(['json','object'].includes(port?.type?.replace(/\[\]$/,''))){if(port.schema==null)issues.push(`${name} output needs a declared field schema for safe resolution`);else if(JSON.stringify(port.schema).length>4000)issues.push(`${name} output schema exceeds the resolver manual limit`);}
|
|
12
|
+
}
|
|
13
|
+
if(issues.length)return {...base,passed:false,code:'RESOLVER_CONTRACT',issues:issues.slice(0,20),message:`Import is blocked until every input and output can participate in typed resolution: ${issues.slice(0,8).join('; ')}${issues.length>8?`; ${issues.length-8} more`:''}. Physical JSON types alone do not establish compatible meaning.`};
|
|
14
|
+
const inputs=Object.entries(manifest.inputs||{}).map(([name,port])=>({name,type:port.type,semantic:port.semantic,required:port.required,sources:port.sources||[]}));
|
|
15
|
+
const outputs=Object.entries(manifest.outputs||{}).map(([name,port])=>({name,type:port.type,semantic:port.semantic}));
|
|
16
|
+
return {...base,passed:true,manual:{inputs,outputs},message:`All ${inputs.length} input${inputs.length===1?'':'s'} and ${outputs.length} output${outputs.length===1?'':'s'} declare physical types, semantic meanings and descriptions. Workflow composition must still validate each exact output-to-input pair, schema, adapter pin and required permission.`};
|
|
17
|
+
}
|
|
@@ -5,6 +5,7 @@ import { assert, validateValues, validateBlockOutputs } from './contracts.js';
|
|
|
5
5
|
import {validateComfyInput,testComfyOutputs} from './comfyui.js';
|
|
6
6
|
import {prepareCapabilityInvocation,validateCapabilityResult} from './capabilities.js';
|
|
7
7
|
import {requiresDesktopExecution,prepareDesktopExecution,validateDesktopExecutionResult} from './desktop-execution.js';
|
|
8
|
+
import {NATIVE_APPLICATION_RUNTIME_REQUIREMENT} from './native-application-package.js';
|
|
8
9
|
|
|
9
10
|
async function executeHostCall(pkg,input,{signal,execute}){
|
|
10
11
|
let abort;
|
|
@@ -18,9 +19,11 @@ async function executeHostCall(pkg,input,{signal,execute}){
|
|
|
18
19
|
export async function runPackageAsync(pkg, values, options = {}) {
|
|
19
20
|
assert(!options.signal?.aborted, 'CANCELLED', 'Run cancelled');
|
|
20
21
|
if(pkg?.manifest?.runtime==='native-application'){
|
|
22
|
+
// Native source runs only through a paired, reviewed local worker; without
|
|
23
|
+
// one (a browser or desktop download alone) it stays unavailable.
|
|
24
|
+
assert(options.experimentalBrowser,'NATIVE_APPLICATION_RUNTIME_UNAVAILABLE',NATIVE_APPLICATION_RUNTIME_REQUIREMENT);
|
|
21
25
|
const snapshot=structuredClone(pkg),input=structuredClone(values),scope={...options};
|
|
22
26
|
const source=await defineNativeApplicationSource(snapshot);
|
|
23
|
-
assert(scope.experimentalBrowser,'LOCAL_WORKER_REQUIRED','Pair a reviewed local worker for this application. A browser or desktop download alone cannot execute its native source.');
|
|
24
27
|
assert(typeof scope.check==='function','NATIVE_APPLICATION_AUTHORITY','Use the scoped project execution checkpoint.');
|
|
25
28
|
return executeExperimentalBrowser(scope.experimentalBrowser,source,validateValues(source.manifest.inputs,input),scope);
|
|
26
29
|
}
|
|
@@ -50,8 +50,40 @@ export function createSchemaFields(lockedInputKeys=[]) {
|
|
|
50
50
|
function fromState(input,state,bindings){const values=copy(input);inert(state);inert(bindings);for(const key of Object.keys(bindings))if(!locked.has(key)&&Object.hasOwn(state,key))values[key]=copy(state[key]);return values;}
|
|
51
51
|
function edits(values,state,bindings){inert(values);inert(state);inert(bindings);const patch={},before={};for(const key of Object.keys(bindings))if(!locked.has(key)&&Object.hasOwn(values,key)&&JSON.stringify(values[key])!==JSON.stringify(state[key])){patch[key]=copy(values[key]);before[key]=Object.hasOwn(state,key)?{exists:true,value:copy(state[key])}:{exists:false};}if(JSON.stringify(patch).length>393216)throw Error('Saved task fields exceed 384 KiB. Use an upstream source for larger data.');return {patch,before,changed:Object.keys(patch).length>0};}
|
|
52
52
|
function preview(value,{maximumFields=20}={}){
|
|
53
|
-
value=copy(value);if(!Number.isSafeInteger(maximumFields)||maximumFields<1||maximumFields>100)throw Error('Use a bounded result preview.');
|
|
54
|
-
|
|
53
|
+
value=copy(value);if(!Number.isSafeInteger(maximumFields)||maximumFields<1||maximumFields>100)throw Error('Use a bounded result preview.');
|
|
54
|
+
// Any app's data: lists of records become tables, other lists become bullet
|
|
55
|
+
// lists, objects become field tables with nested sections. Bounded in size.
|
|
56
|
+
const ROWS=50,COLUMNS=12;let budget=6000,omitted=false;
|
|
57
|
+
const label=key=>{const text=String(key).replaceAll('_',' ').slice(0,120);return text.charAt(0).toUpperCase()+text.slice(1);};
|
|
58
|
+
const scalar=item=>{const text=item===null||item===undefined?'—':typeof item==='boolean'?(item?'Yes':'No'):String(item);return text.length>500?text.slice(0,500)+'…':text;};
|
|
59
|
+
const record=item=>item!==null&&typeof item==='object'&&!Array.isArray(item);
|
|
60
|
+
const node=(tag,extra)=>{budget--;return {tag,...extra};};
|
|
61
|
+
const count=list=>`${list.length} ${list.length===1?'item':'items'}${list.length>ROWS?`, showing the first ${ROWS}`:''}`;
|
|
62
|
+
const render=(item,depth)=>{
|
|
63
|
+
if(budget<=0||depth>5){omitted=true;return node('p',{text:'…'});}
|
|
64
|
+
if(item===null||typeof item!=='object')return node('p',{text:scalar(item)});
|
|
65
|
+
if(Array.isArray(item)){
|
|
66
|
+
if(!item.length)return node('p',{text:'No items'});
|
|
67
|
+
const rows=item.slice(0,ROWS);if(item.length>ROWS)omitted=true;
|
|
68
|
+
if(rows.every(record)){
|
|
69
|
+
const columns=[...new Set(rows.flatMap(row=>Object.keys(row)))],shown=columns.slice(0,COLUMNS);if(columns.length>COLUMNS)omitted=true;
|
|
70
|
+
// Nested values in a cell read as short text, e.g. "link click: 540".
|
|
71
|
+
const brief=entry=>entry!==null&&typeof entry==='object'?(Array.isArray(entry)?entry.map(brief).join(', '):Object.values(entry).map(brief).join(': ')):scalar(entry);
|
|
72
|
+
const cell=entry=>{const text=brief(entry);return text.length>200?text.slice(0,200)+'…':text;};
|
|
73
|
+
return node('div',{className:'connection-table',children:[node('small',{text:count(item)}),node('table',{children:[node('thead',{children:[node('tr',{children:shown.map(column=>node('th',{text:label(column)}))})]}),node('tbody',{children:rows.map(row=>node('tr',{children:shown.map(column=>node('td',{text:cell(row[column])}))}))})]})]});
|
|
74
|
+
}
|
|
75
|
+
return node('div',{children:[node('small',{text:count(item)}),node('ul',{children:rows.map(entry=>node('li',{children:[render(entry,depth+1)]}))})]});
|
|
76
|
+
}
|
|
77
|
+
const entries=Object.entries(item),flat=entries.filter(([,entry])=>entry===null||typeof entry!=='object'),nested=entries.filter(([,entry])=>entry!==null&&typeof entry==='object');
|
|
78
|
+
const children=[];
|
|
79
|
+
if(flat.length)children.push(node('table',{className:'connection-fields',children:[node('tbody',{children:flat.map(([key,entry])=>node('tr',{children:[node('th',{text:label(key)}),node('td',{text:scalar(entry)})]}))})]}));
|
|
80
|
+
// The top level shows its sections openly; deeper detail stays collapsed.
|
|
81
|
+
for(const [key,entry] of nested)children.push(depth===0?node('section',{children:[node('h4',{text:label(key)}),render(entry,depth+1)]}):node('details',{children:[node('summary',{text:label(key)}),render(entry,depth+1)]}));
|
|
82
|
+
if(!children.length)children.push(node('p',{text:'No fields'}));
|
|
83
|
+
return node('div',{children});
|
|
84
|
+
};
|
|
85
|
+
const body=render(value,0);
|
|
86
|
+
return [{tag:'h3',text:'Result'},body,...(omitted?[{tag:'small',text:'Preview is bounded. The saved result retains its complete permitted data.'}]:[])];
|
|
55
87
|
}
|
|
56
88
|
function currencyDigits(currency){if(typeof currency!=='string'||!/^[A-Z]{3}$/.test(currency))throw Error('Choose a three-letter currency code.');return new Intl.NumberFormat('en',{style:'currency',currency}).resolvedOptions().maximumFractionDigits;}
|
|
57
89
|
function minorUnits(decimal,currency){const digits=currencyDigits(currency),parts=String(decimal).split('.');if(parts.length>2||!/^\d+$/.test(parts[0])||parts[1]!==undefined&&(!/^\d+$/.test(parts[1])||parts[1].length>digits))throw Error('Enter a budget using this currency’s supported decimal places.');const amount=Number(parts[0])*10**digits+Number((parts[1]||'').padEnd(digits,'0'));if(!Number.isSafeInteger(amount)||amount<=0)throw Error('Enter a positive supported budget.');return amount;}
|
|
@@ -6,10 +6,24 @@ export const STANDARD_SEMANTIC_RELATIONSHIPS=Object.freeze([
|
|
|
6
6
|
Object.freeze({name:'reference_image',parents:Object.freeze(['image'])}),
|
|
7
7
|
Object.freeze({name:'generated_image',parents:Object.freeze(['image','source_image'])}),
|
|
8
8
|
Object.freeze({name:'processed_image',parents:Object.freeze(['image','source_image'])}),
|
|
9
|
+
// A description of an image is plain text any text Step can work from (captions, copy).
|
|
10
|
+
Object.freeze({name:'visual_description',parents:Object.freeze(['plain_text'])}),
|
|
11
|
+
// A transcript's words are plain text once converted (transcript-to-text).
|
|
12
|
+
Object.freeze({name:'transcript_text',parents:Object.freeze(['plain_text'])}),
|
|
13
|
+
// Workspace timelines are timelines: Export video takes any of them.
|
|
14
|
+
Object.freeze({name:'editor_timeline',parents:Object.freeze(['timeline'])}),
|
|
15
|
+
Object.freeze({name:'legacy.timeline.updatededitor',parents:Object.freeze(['timeline'])}),
|
|
16
|
+
// A narration is audio that can be transcribed.
|
|
17
|
+
Object.freeze({name:'narration_audio',parents:Object.freeze(['source_audio'])}),
|
|
9
18
|
]);
|
|
10
19
|
|
|
11
20
|
const parents=new Map(STANDARD_SEMANTIC_RELATIONSHIPS.map(entry=>[entry.name,entry.parents]));
|
|
12
21
|
export function standardSemanticIsA(actual,expected) {
|
|
22
|
+
// A connected task's own input fields (email, phone, message…) take any value
|
|
23
|
+
// of the right type that the user wires to them, such as a lead's email.
|
|
24
|
+
if(/^external_input_[a-f0-9]{16}_\d+$/.test(expected))return true;
|
|
13
25
|
if(typeof actual!=='string' || typeof expected!=='string')return false;
|
|
26
|
+
// Every connected-app task names its own result meaning; all are external data.
|
|
27
|
+
if(expected==='external_data' && /^external_result_[a-f0-9]{16}$/.test(actual))return true;
|
|
14
28
|
return actual===expected || (parents.get(actual) || []).some(parent=>standardSemanticIsA(parent,expected));
|
|
15
29
|
}
|
|
@@ -46,7 +46,9 @@ export function installSharedControls(document,api,onError,textControls=null,loc
|
|
|
46
46
|
let base={exists:false},draft,dirty=false,saving=false,timer=null,disposed=false,canEdit=false,inFlight=null,problem=null,dirtySince=null,composing=false,compositionRevoked=false,retainedComposition;
|
|
47
47
|
const textual=node.tagName==='TEXTAREA'||node.tagName==='INPUT'&&node.type==='text';
|
|
48
48
|
const report=error=>{problem=error;node.setCustomValidity(error.message);onError(error.message);};
|
|
49
|
-
|
|
49
|
+
// A written value reaches the view through onShared (never an input event),
|
|
50
|
+
// after the listeners of this update, whatever order they subscribed in.
|
|
51
|
+
const write=value=>{const before=node.type==='checkbox'?node.checked:node.value;if(node.type==='checkbox')node.checked=!!value;else if(node.value!==String(value??''))node.value=String(value??'');if((node.type==='checkbox'?node.checked:node.value)!==before)queueMicrotask(()=>{if(!disposed)api.announce?.();});};
|
|
50
52
|
const read=()=>node.type==='checkbox'?node.checked:['number','range'].includes(node.type)?node.valueAsNumber:node.value;
|
|
51
53
|
let fallback=read();
|
|
52
54
|
const flush=()=>{
|
|
@@ -91,7 +91,7 @@ export function installSharedViewBridge(channel,session,limits=SHARED_VIEW_LIMIT
|
|
|
91
91
|
}
|
|
92
92
|
else if(data.op==='dispose'){closed=true;ready=false;listeners.clear();rejectQueue('Block closed');}
|
|
93
93
|
});
|
|
94
|
-
|
|
94
|
+
const api={
|
|
95
95
|
getShared(){return structuredClone(snapshot);},
|
|
96
96
|
onShared(fn){if(typeof fn!=='function')throw Error('Expected a function');listeners.add(fn);if(ready)fn(structuredClone(snapshot));return()=>listeners.delete(fn);},
|
|
97
97
|
updateShared(values,preconditions,options){
|
|
@@ -114,4 +114,9 @@ export function installSharedViewBridge(channel,session,limits=SHARED_VIEW_LIMIT
|
|
|
114
114
|
return new Promise((resolve,reject)=>{queue.push({id:String(++sequence),resolve,reject,values,before,merge,bytes});drain();});
|
|
115
115
|
}
|
|
116
116
|
};
|
|
117
|
+
// Shared text arrives through its own channel after the snapshot. Its control
|
|
118
|
+
// calls this once the text is in place, so views recompute from onShared. Not
|
|
119
|
+
// enumerable: the view API is spread from this object and does not offer it.
|
|
120
|
+
Object.defineProperty(api,'announce',{value:()=>{if(ready&&!closed)notify();}});
|
|
121
|
+
return api;
|
|
117
122
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// A Block's code is private to its creator (and StillMade admins): no one else
|
|
2
|
+
// views or downloads it, and others change it only by remixing through
|
|
3
|
+
// StillMade's AI builder. A creator's old preference still decides whether
|
|
4
|
+
// their releases must stay private; it no longer opens the code to anyone.
|
|
3
5
|
import {isPortableLicenseExpression} from './portable.js';
|
|
4
6
|
export const SOURCE_INSPECTION_CHOICES=Object.freeze(['yes','no','license-controlled']);
|
|
5
7
|
const permissive=new Set(['MIT','Apache-2.0','BSD-2-Clause','BSD-3-Clause','ISC']);
|
|
@@ -8,13 +10,12 @@ export function blockSourceRights(manifest) {
|
|
|
8
10
|
const preference=manifest?.provenance?.remixPolicy?.sourceInspection;
|
|
9
11
|
const declared=manifest?.license,recognized=permissive.has(declared)||isPortableLicenseExpression(declared)||sourceObligations.has(declared)||declared==='CC0-1.0';
|
|
10
12
|
return {preference:preference||'yes',
|
|
11
|
-
|
|
12
|
-
publicInspection:preference===undefined||preference==='yes'||preference==='license-controlled'&&recognized,
|
|
13
|
+
publicInspection:false,
|
|
13
14
|
requiresPrivateRelease:preference==='no'||preference==='license-controlled'&&!recognized,
|
|
14
15
|
commercialUse:recognized?'Subject to the declared license and retained upstream terms':'Unresolved — review the source license',
|
|
15
16
|
attribution:declared==='CC0-1.0'?'Retain source provenance and any third-party notices':recognized?'Retain required copyright, license and notice text':'Unresolved — retain all upstream notices pending review',
|
|
16
17
|
sourceObligations:sourceObligations.has(declared)?'Distribution may require corresponding source and other license obligations; automatic remix clearance remains restricted.':null,
|
|
17
|
-
explanation:preference==='no'?'
|
|
18
|
+
explanation:preference==='no'?'This Block is released privately using the existing account/team access rules. Authorized recipients keep their license rights.':preference==='license-controlled'&&!recognized?'The source license is unresolved. Choose a reviewed license or retain a private release.':'The code stays private to its creator. Others can remix it only through StillMade, and a remix belongs to the creator.'};
|
|
18
19
|
}
|
|
19
20
|
export function packageSourceRights(content) {
|
|
20
21
|
const packages=content?.stages?content.packages||[]:[content];
|
|
@@ -9,9 +9,11 @@ import { runPackageAsync } from './runtime.js';
|
|
|
9
9
|
import {validateView} from './view.js';
|
|
10
10
|
import {validateComfyWorkflow,validateComfyExpectations,testComfyOutputs} from './comfyui.js';
|
|
11
11
|
import {CAPABILITY_LIMITS,validateCapability,prepareCapabilityInvocation,validateCapabilityExpectations,testCapabilityOutputs} from './capabilities.js';
|
|
12
|
+
import {validateNativeApplicationPackage,NATIVE_APPLICATION_RUNTIME_REQUIREMENT} from './native-application-package.js';
|
|
12
13
|
import {validateModuleDescriptor,MODULE_LIMITS} from './module-runtime.js';
|
|
13
14
|
export function validatePackage(pkg) {
|
|
14
15
|
safeDocument(pkg);
|
|
16
|
+
if(pkg?.manifest?.runtime==='native-application')return validateNativeApplicationPackage(pkg);
|
|
15
17
|
assert(pkg && Object.keys(pkg).every(k => ['manifest', 'recipe', 'code', 'module', 'comfyui', 'capability', 'tests', 'view', 'portable', 'desktop'].includes(k)), 'PACKAGE', 'Unknown package field');
|
|
16
18
|
if(typeof pkg.manifest?.id==='string'&&pkg.manifest.id.startsWith('sm.'))assert(stableStringify(firstPartySdkPackage(pkg.manifest)??null)===stableStringify(pkg),'FIRST_PARTY_SOURCE','Use the exact published first-party package, or create a remix with a new identity.');
|
|
17
19
|
const result = validateManifest(pkg.manifest);
|
|
@@ -66,6 +68,7 @@ export function validatePackage(pkg) {
|
|
|
66
68
|
}
|
|
67
69
|
export async function testPackage(pkg, { timeoutMs = 10000, wasmLocation, comfyui, capability, desktop, module } = {}) {
|
|
68
70
|
validatePackage(pkg);
|
|
71
|
+
assert(pkg.manifest.runtime!=='native-application','NATIVE_APPLICATION_RUNTIME_UNAVAILABLE',NATIVE_APPLICATION_RUNTIME_REQUIREMENT);
|
|
69
72
|
const results = [];
|
|
70
73
|
// Module fixtures may run real WebAssembly work, so their suite may use the module run limit.
|
|
71
74
|
const ceiling = pkg.manifest.runtime === 'module' ? MODULE_LIMITS.wallClockMs : 10000;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
@font-face{font-family:"Hanken Grotesk";font-style:normal;font-weight:100 900;font-display:swap;src:url("/fonts/HankenGrotesk-Variable.ttf") format("truetype")}
|
|
2
2
|
@font-face{font-family:"Hanken Grotesk";font-style:italic;font-weight:100 900;font-display:swap;src:url("/fonts/HankenGrotesk-Italic-Variable.ttf") format("truetype")}
|
|
3
|
-
@font-face{font-family:"JetBrains Mono";font-style:normal;font-weight:100 900;font-display:swap;src:url("/fonts/JetBrainsMono-Variable.ttf") format("truetype")}
|
|
4
|
-
@font-face{font-family:"JetBrains Mono";font-style:italic;font-weight:100 900;font-display:swap;src:url("/fonts/JetBrainsMono-Italic-Variable.ttf") format("truetype")}
|
|
5
3
|
/* Shared StillMade foundations. App and public listing pages use this file. */
|
|
6
4
|
:root {
|
|
7
5
|
/* ---------- Raw palette: two anchors + tonal steps ---------- */
|
|
@@ -26,7 +24,8 @@
|
|
|
26
24
|
/* ---------- Type families ---------- */
|
|
27
25
|
--font-display: "Balgin", "Hanken Grotesk", ui-sans-serif, sans-serif;
|
|
28
26
|
--font-body: "Hanken Grotesk", ui-sans-serif, sans-serif;
|
|
29
|
-
|
|
27
|
+
/* Code only (source, code blocks). Interface text never uses it. */
|
|
28
|
+
--font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
30
29
|
--font: var(--font-body);
|
|
31
30
|
|
|
32
31
|
/* ---------- Type scale ---------- */
|
|
@@ -153,6 +152,11 @@
|
|
|
153
152
|
--success: var(--fg);
|
|
154
153
|
--warning: var(--signal);
|
|
155
154
|
--danger: var(--signal);
|
|
155
|
+
/* Block attention (placement-attention.js): yellow = needs an answer or rerun, red = needs a fix. Green = live or ready, blue = working. */
|
|
156
|
+
--attention-warning: #E0B24F;
|
|
157
|
+
--attention-error: #E26A5F;
|
|
158
|
+
--attention-ok: #6BC38E;
|
|
159
|
+
--attention-info: #7DA7E8;
|
|
156
160
|
--shadow: var(--shadow-1);
|
|
157
161
|
--shadow-lg: var(--shadow-2);
|
|
158
162
|
}
|
|
@@ -216,6 +220,10 @@
|
|
|
216
220
|
--success: var(--fg);
|
|
217
221
|
--warning: var(--signal);
|
|
218
222
|
--danger: var(--signal);
|
|
223
|
+
--attention-warning: #B98200;
|
|
224
|
+
--attention-error: #C2453D;
|
|
225
|
+
--attention-ok: #2E7D4F;
|
|
226
|
+
--attention-info: #2F67B8;
|
|
219
227
|
--shadow: var(--shadow-1);
|
|
220
228
|
--shadow-lg: var(--shadow-2);
|
|
221
229
|
}
|
|
@@ -241,6 +249,7 @@
|
|
|
241
249
|
--editor-standout-bg: #24211D; --editor-standout-fg: #FFFFFF;
|
|
242
250
|
--text-primary: var(--fg); --text-secondary: var(--fg-muted); --text-muted: var(--fg-subtle);
|
|
243
251
|
--success: var(--fg); --warning: var(--signal); --danger: var(--signal);
|
|
252
|
+
--attention-warning: #B98200; --attention-error: #C2453D; --attention-ok: #2E7D4F; --attention-info: #2F67B8;
|
|
244
253
|
--shadow: var(--shadow-1); --shadow-lg: var(--shadow-2);
|
|
245
254
|
}
|
|
246
255
|
|