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
package/package.json
CHANGED
|
@@ -37,6 +37,7 @@ import { validateProjectType } from '../block-platform/project-types.js';
|
|
|
37
37
|
import { testProjectType } from '../block-platform/type-testing.js';
|
|
38
38
|
import {previewProjectType} from '../block-platform/type-preview.js';
|
|
39
39
|
import {assessBlockReadiness} from '../block-platform/block-readiness.js';
|
|
40
|
+
import {defineBrowserApplicationSource} from '../block-sdk/browser-application-execution.js';
|
|
40
41
|
import { BUILTIN_BLOCKS } from '../block-platform/catalog.js';
|
|
41
42
|
import { RECIPE_TYPE_EXAMPLE, typeWorkspaceSupport } from '../block-platform/type-workspace.js';
|
|
42
43
|
import {PLATFORM_CHECK_POLICY,PLATFORM_CHECK_TARGETS,PLATFORM_CHECK_THEMES} from '../block-sdk/platform-checks.js';
|
|
@@ -206,7 +207,15 @@ export async function main(rawArgs) {
|
|
|
206
207
|
await mkdir(path,{recursive:true});
|
|
207
208
|
const names=['stillmade.block.json',...Object.keys(files).filter(name=>name!=='stillmade.block.json')];
|
|
208
209
|
for(const name of names){await mkdir(dirname(join(path,name)),{recursive:true});await writeFile(join(path,name),files[name],{flag:'wx'});}
|
|
209
|
-
|
|
210
|
+
// Coding agents read command output: restate the readiness rule where they will see it.
|
|
211
|
+
return { created: path, sdkVersion: '0.1.0', template:template.name, runtime:template.runtime, files:names, next: 'Build the Block, then run `stillmade-block gate .`. Do not call the Block ready until the gate passes; report the status it prints.' };
|
|
212
|
+
}
|
|
213
|
+
if (command === 'gate') {
|
|
214
|
+
// Every new or edited Block in a folder must pass readiness before anyone calls it ready.
|
|
215
|
+
const {runReadyGate,gateMessage}=await import('./ready-gate.js');
|
|
216
|
+
const verdict=await runReadyGate({root:path,main});
|
|
217
|
+
if(!verdict.passed)throw Object.assign(new Error(gateMessage(verdict)),{code:'READINESS_GATE_FAILED',report:{results:verdict.results}});
|
|
218
|
+
return {passed:true,checked:verdict.checked,results:verdict.results.map(({package:name,status,limitations})=>({package:name,status,limitations}))};
|
|
210
219
|
}
|
|
211
220
|
if(command==='run')return main(['preview',...args.slice(1),...(mockRequested?['--mock']:[])]);
|
|
212
221
|
if(command==='dev')return dev(path,args.slice(2));
|
|
@@ -226,6 +235,12 @@ export async function main(rawArgs) {
|
|
|
226
235
|
if(command==='ready'){
|
|
227
236
|
// The same Block builder contract readiness check StillMade runs before a release.
|
|
228
237
|
if(pkg.stages)throw new Error('ready checks one Block package; run it on each Block in the Project Type.');
|
|
238
|
+
if(pkg.manifest?.runtime==='browser-application'){
|
|
239
|
+
// An original browser application is judged only by StillMade's isolated
|
|
240
|
+
// review; locally its exact source contract is all that can be checked.
|
|
241
|
+
const source=await defineBrowserApplicationSource(pkg);
|
|
242
|
+
throw Object.assign(new Error(`${source.manifest.name} passed the local browser-application source contract. Browser applications are judged only by StillMade's isolated review: import this exact package in the StillMade desktop app and run its review. It is not ready until that review passes.`),{code:'APPLICATION_REVIEW_REQUIRED'});
|
|
243
|
+
}
|
|
229
244
|
const readiness=await assessBlockReadiness(pkg);
|
|
230
245
|
if(['FAILED','BLOCKED BY PLATFORM CAPABILITY'].includes(readiness.status))throw Object.assign(new Error(`Block readiness: ${readiness.status}`),{report:readiness});
|
|
231
246
|
return readiness;
|
|
@@ -18,6 +18,8 @@ Build and check
|
|
|
18
18
|
types [folder] Write stillmade-env.d.ts: TypeScript types for the ports and globals
|
|
19
19
|
inspect [folder] Show the manifest, runtime and operations
|
|
20
20
|
ready [folder] The readiness check StillMade runs before a release
|
|
21
|
+
gate [folder] Run ready on every new or edited Block in a folder; a Block is not
|
|
22
|
+
ready until this passes
|
|
21
23
|
|
|
22
24
|
Share
|
|
23
25
|
pack [folder] [output] Test, then write the file to import in StillMade (Create, then Import)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Readiness gate for Blocks built outside StillMade. It runs StillMade's own
|
|
3
|
+
// readiness check on every Block package in a folder that differs from what
|
|
4
|
+
// the SDK shipped, so a coding agent (Claude Code, Cursor, Codex...) cannot
|
|
5
|
+
// call a Block ready while that check fails. Results are cached by content
|
|
6
|
+
// digest, so an unchanged Block is never re-run.
|
|
7
|
+
import {createHash} from 'node:crypto';
|
|
8
|
+
import {readFile,readdir,writeFile,mkdir,lstat} from 'node:fs/promises';
|
|
9
|
+
import {realpathSync} from 'node:fs';
|
|
10
|
+
import {join,relative,resolve,sep} from 'node:path';
|
|
11
|
+
import {fileURLToPath} from 'node:url';
|
|
12
|
+
|
|
13
|
+
export const READY_GATE_STATE='.stillmade';
|
|
14
|
+
// Shipped in the SDK download: Claude Code and Cursor run the gate whenever the
|
|
15
|
+
// agent finishes a turn and hand any failures back to it.
|
|
16
|
+
export const AGENT_READY_GATE_HOOKS=Object.freeze({
|
|
17
|
+
claude:{hooks:{Stop:[{hooks:[{type:'command',command:'node "$CLAUDE_PROJECT_DIR/packages/block-cli/ready-gate.js" --claude',timeout:600}]}]}},
|
|
18
|
+
cursor:{version:1,hooks:{stop:[{command:'node packages/block-cli/ready-gate.js --cursor',timeout:600}]}}
|
|
19
|
+
});
|
|
20
|
+
export const SDK_BASELINE_FILE='.stillmade/sdk-baseline.json';
|
|
21
|
+
const SKIP=new Set(['node_modules','.git','.stillmade','.claude','.cursor','packages','dist','build','coverage']);
|
|
22
|
+
const PASSING=new Set(['PRODUCTION READY','READY WITH DOCUMENTED LIMITATIONS','PROJECT TYPE TESTS PASSED']);
|
|
23
|
+
const LIMITS=Object.freeze({directories:4000,depth:8,packageBytes:24*1024*1024});
|
|
24
|
+
|
|
25
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
26
|
+
const posix=path=>path.split(sep).join('/');
|
|
27
|
+
|
|
28
|
+
/** Digest of one package: a single file, or every file under a package folder. */
|
|
29
|
+
export async function packageDigest(path){
|
|
30
|
+
const info=await lstat(path);
|
|
31
|
+
if(!info.isDirectory())return sha256(await readFile(path));
|
|
32
|
+
const hash=createHash('sha256'),files=[];let bytes=0;
|
|
33
|
+
const walk=async(folder,prefix='')=>{for(const entry of await readdir(folder,{withFileTypes:true})){
|
|
34
|
+
if(entry.isSymbolicLink())throw new Error(`Package symlinks are unavailable: ${prefix}${entry.name}`);
|
|
35
|
+
const full=join(folder,entry.name);
|
|
36
|
+
if(entry.isDirectory())await walk(full,prefix+entry.name+'/');
|
|
37
|
+
else if(entry.isFile())files.push([prefix+entry.name,full]);
|
|
38
|
+
}};
|
|
39
|
+
await walk(path);
|
|
40
|
+
files.sort(([a],[b])=>a<b?-1:a>b?1:0);
|
|
41
|
+
for(const [name,full] of files){const data=await readFile(full);bytes+=data.length;if(bytes>LIMITS.packageBytes)throw new Error('Package exceeds the readiness gate size limit');hash.update(name+'\0'+data.length+'\0');hash.update(data);}
|
|
42
|
+
return hash.digest('hex');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Block and Project Type packages under root: folders holding stillmade.block.json
|
|
46
|
+
* or portable.json, and single .stillmade.json / .stillmade-block files. */
|
|
47
|
+
export async function findPackages(root){
|
|
48
|
+
const found=[];let visited=0;
|
|
49
|
+
const walk=async(folder,depth)=>{
|
|
50
|
+
if(++visited>LIMITS.directories||depth>LIMITS.depth)return;
|
|
51
|
+
const entries=await readdir(folder,{withFileTypes:true});
|
|
52
|
+
if(entries.some(entry=>entry.isFile()&&['stillmade.block.json','portable.json'].includes(entry.name))){found.push(folder);return;}
|
|
53
|
+
for(const entry of entries){
|
|
54
|
+
const full=join(folder,entry.name);
|
|
55
|
+
if(entry.isDirectory()&&!SKIP.has(entry.name)&&!entry.name.startsWith('.'))await walk(full,depth+1);
|
|
56
|
+
else if(entry.isFile()&&/\.(stillmade\.json|stillmade-block)$/i.test(entry.name))found.push(full);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
await walk(root,0);
|
|
60
|
+
return found.sort();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Baseline of the packages an SDK download ships, so only new or edited
|
|
64
|
+
* Blocks are judged. Keys are POSIX paths relative to the SDK root. */
|
|
65
|
+
export async function sdkBaseline(root){
|
|
66
|
+
const entries={};
|
|
67
|
+
for(const path of await findPackages(root))entries[posix(relative(root,path))]=await packageDigest(path);
|
|
68
|
+
return {version:1,packages:entries};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Baseline for an in-memory archive map, computed by the same rules on disk. */
|
|
72
|
+
export async function sdkBaselineForFiles(files){
|
|
73
|
+
const {mkdtemp,rm}=await import('node:fs/promises'),{tmpdir}=await import('node:os'),{dirname}=await import('node:path');
|
|
74
|
+
const folder=await mkdtemp(join(tmpdir(),'stillmade-sdk-baseline-'));
|
|
75
|
+
try{
|
|
76
|
+
for(const [name,bytes] of Object.entries(files)){if(name.endsWith('/'))continue;const target=join(folder,...name.split('/'));await mkdir(dirname(target),{recursive:true});await writeFile(target,bytes);}
|
|
77
|
+
return await sdkBaseline(folder);
|
|
78
|
+
}finally{await rm(folder,{recursive:true,force:true});}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function readJson(path,fallback){try{return JSON.parse(await readFile(path,'utf8'));}catch{return fallback;}}
|
|
82
|
+
const summarize=readiness=>(readiness?.items||[]).filter(item=>['failed','blocked'].includes(item.state)).slice(0,6).map(item=>`${item.id} ${item.title}: ${String(item.reason||'').slice(0,300)}`);
|
|
83
|
+
|
|
84
|
+
async function judge(path,main){
|
|
85
|
+
try{
|
|
86
|
+
const readiness=await main(['ready',path]);
|
|
87
|
+
return {ok:PASSING.has(readiness.status),status:readiness.status,failures:summarize(readiness),limitations:(readiness.limitations||[]).length};
|
|
88
|
+
}catch(error){
|
|
89
|
+
if(/^ready checks one Block package/.test(error.message)){
|
|
90
|
+
try{await main(['test',path]);return {ok:true,status:'PROJECT TYPE TESTS PASSED',failures:[]};}
|
|
91
|
+
catch(typeError){return {ok:false,status:'FAILED',failures:[String(typeError.message).slice(0,300)]};}
|
|
92
|
+
}
|
|
93
|
+
if(error.code==='APPLICATION_REVIEW_REQUIRED')return {ok:false,status:'NEEDS STILLMADE REVIEW',failures:[String(error.message).slice(0,400)]};
|
|
94
|
+
if(error.report?.status)return {ok:false,status:error.report.status,failures:summarize(error.report)};
|
|
95
|
+
return {ok:false,status:'FAILED',failures:[String(error.message).slice(0,300)]};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Judge every changed package. Never throws for a failing Block; returns a verdict. */
|
|
100
|
+
export async function runReadyGate({root=process.cwd(),main}={}){
|
|
101
|
+
root=resolve(root);
|
|
102
|
+
main??=(await import('./cli.js')).main;
|
|
103
|
+
const baseline=(await readJson(join(root,SDK_BASELINE_FILE),{packages:{}})).packages||{};
|
|
104
|
+
const cachePath=join(root,READY_GATE_STATE,'ready-cache.json'),cache=await readJson(cachePath,{version:1,entries:{}});
|
|
105
|
+
const results=[];
|
|
106
|
+
for(const path of await findPackages(root)){
|
|
107
|
+
const name=posix(relative(root,path)),digest=await packageDigest(path);
|
|
108
|
+
if(baseline[name]===digest)continue;
|
|
109
|
+
const cached=cache.entries[name];
|
|
110
|
+
const verdict=cached?.digest===digest?cached:{digest,...await judge(path,main),checkedAt:new Date().toISOString()};
|
|
111
|
+
cache.entries[name]=verdict;
|
|
112
|
+
results.push({package:name,...verdict});
|
|
113
|
+
}
|
|
114
|
+
await mkdir(join(root,READY_GATE_STATE),{recursive:true});
|
|
115
|
+
await writeFile(cachePath,JSON.stringify(cache,null,1)+'\n');
|
|
116
|
+
const failing=results.filter(result=>!result.ok);
|
|
117
|
+
return {checked:results.length,passed:failing.length===0,results,failing,signature:sha256(JSON.stringify(failing.map(result=>[result.package,result.digest])))};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function gateMessage(verdict){
|
|
121
|
+
const lines=verdict.failing.map(result=>`- ${result.package} (${result.status}):\n${result.failures.map(failure=>` ${failure}`).join('\n')||' readiness check failed'}`);
|
|
122
|
+
return `StillMade readiness gate: ${verdict.failing.length} Block${verdict.failing.length===1?'':'s'} you changed ${verdict.failing.length===1?'is':'are'} not ready.\n${lines.join('\n')}\nFix these and re-run \`node packages/block-cli/cli.js ready <folder>\`. Do not tell the user a Block is ready, done or working while this gate fails. If you cannot fix it, say plainly that the Block is NOT ready and list these failures.`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Hook entry. `agent` is claude, cursor or plain. Returns {exitCode, stdout}. */
|
|
126
|
+
export async function readyGateHook({agent='plain',input={},root,main}={}){
|
|
127
|
+
root=resolve(root||(agent==='cursor'?process.env.CURSOR_PROJECT_DIR||input.workspace_roots?.[0]:process.env.CLAUDE_PROJECT_DIR||input.cwd)||process.cwd());
|
|
128
|
+
const verdict=await runReadyGate({root,main});
|
|
129
|
+
const statePath=join(root,READY_GATE_STATE,'gate-state.json'),state=await readJson(statePath,{});
|
|
130
|
+
if(verdict.passed){
|
|
131
|
+
await writeFile(statePath,'{}\n');
|
|
132
|
+
return {exitCode:0,stdout:agent==='plain'?JSON.stringify({passed:true,checked:verdict.checked,results:verdict.results.map(({package:name,status})=>({package:name,status}))},null,2)+'\n':agent==='cursor'?'{}\n':''};
|
|
133
|
+
}
|
|
134
|
+
// An agent that already saw this exact failure set and made no change may
|
|
135
|
+
// stop (it has been told to report the Block as not ready); any edit re-arms.
|
|
136
|
+
const repeated=state.signature===verdict.signature;
|
|
137
|
+
if(agent==='claude'){
|
|
138
|
+
if(input.stop_hook_active&&repeated)return {exitCode:0,stdout:''};
|
|
139
|
+
await writeFile(statePath,JSON.stringify({signature:verdict.signature})+'\n');
|
|
140
|
+
return {exitCode:0,stdout:JSON.stringify({decision:'block',reason:gateMessage(verdict)})+'\n'};
|
|
141
|
+
}
|
|
142
|
+
if(agent==='cursor'){
|
|
143
|
+
if(Number(input.loop_count)>0&&repeated)return {exitCode:0,stdout:'{}\n'};
|
|
144
|
+
await writeFile(statePath,JSON.stringify({signature:verdict.signature})+'\n');
|
|
145
|
+
return {exitCode:0,stdout:JSON.stringify({followup_message:gateMessage(verdict)})+'\n'};
|
|
146
|
+
}
|
|
147
|
+
return {exitCode:1,stdout:gateMessage(verdict)+'\n'};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if(process.argv[1]&&(()=>{try{return realpathSync(process.argv[1])===fileURLToPath(import.meta.url);}catch{return false;}})()){
|
|
151
|
+
const agent=process.argv.includes('--claude')?'claude':process.argv.includes('--cursor')?'cursor':'plain';
|
|
152
|
+
let raw='';
|
|
153
|
+
if(agent!=='plain'&&!process.stdin.isTTY){for await(const chunk of process.stdin)raw+=chunk;}
|
|
154
|
+
let input={};try{input=raw.trim()?JSON.parse(raw):{};}catch{}
|
|
155
|
+
const rootArg=process.argv.slice(2).find(arg=>!arg.startsWith('--'));
|
|
156
|
+
readyGateHook({agent,input,root:rootArg}).then(({exitCode,stdout})=>{process.stdout.write(stdout);process.exitCode=exitCode;})
|
|
157
|
+
.catch(error=>{
|
|
158
|
+
const message=`StillMade readiness gate could not run: ${error.message}. Run \`node packages/block-cli/cli.js ready <folder>\` yourself before telling the user a Block is ready.`;
|
|
159
|
+
if(agent==='claude'&&!input.stop_hook_active)process.stdout.write(JSON.stringify({decision:'block',reason:message})+'\n');
|
|
160
|
+
else if(agent==='cursor'&&!(Number(input.loop_count)>0))process.stdout.write(JSON.stringify({followup_message:message})+'\n');
|
|
161
|
+
else process.stderr.write(message+'\n');
|
|
162
|
+
process.exitCode=agent==='plain'?1:0;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Project Type) and maps everything onto the builder contract's Definition of
|
|
6
6
|
// Done. Status is derived, never asserted: an item that did not run is
|
|
7
7
|
// "not-verified", so the report cannot claim more than was checked.
|
|
8
|
-
import {BUILDER_CONTRACT,READINESS_STATUSES,GAP_CLASSES} from '../block-sdk/builder-contract.js';
|
|
8
|
+
import {BUILDER_CONTRACT,READINESS_STATUSES,GAP_CLASSES,BLOCK_NAME_MAX_LENGTH} from '../block-sdk/builder-contract.js';
|
|
9
9
|
import {compatible,digest,stableStringify,validateBlockOutputs} from '../block-sdk/contracts.js';
|
|
10
10
|
import {validatePackage} from '../block-sdk/testing.js';
|
|
11
11
|
import {admitLocalPackage} from '../block-sdk/local-admission.js';
|
|
@@ -23,7 +23,7 @@ const OFFLINE_RUNTIMES=new Set(['recipe','javascript']);
|
|
|
23
23
|
// Conditions of the checking machine, not of the Block: they leave items unverified, never failed.
|
|
24
24
|
const ENVIRONMENT=new Set(['BUSY','READINESS_BUDGET','TIMEOUT_BUDGET']);
|
|
25
25
|
const environmental=entry=>ENVIRONMENT.has(entry?.code)||/runners are busy/i.test(entry?.message||'');
|
|
26
|
-
const HOST_REFUSALS=Object.freeze({RUNTIME_UNAVAILABLE:'E',DESKTOP_EXECUTION_REQUIRED:'E',LOCAL_WORKER_REQUIRED:'E'});
|
|
26
|
+
const HOST_REFUSALS=Object.freeze({RUNTIME_UNAVAILABLE:'E',DESKTOP_EXECUTION_REQUIRED:'E',LOCAL_WORKER_REQUIRED:'E',NATIVE_APPLICATION_RUNTIME_UNAVAILABLE:'E'});
|
|
27
27
|
const TITLES=Object.fromEntries(BUILDER_CONTRACT.definitionOfDone.map(item=>[item.id,item.title]));
|
|
28
28
|
const message=error=>String(error?.message||error).slice(0,300);
|
|
29
29
|
const offline=pkg=>OFFLINE_RUNTIMES.has(pkg?.manifest?.runtime)&&!requiresDesktopExecution(pkg);
|
|
@@ -98,6 +98,7 @@ export async function assessBlockReadiness(pkg,{catalog=FIRST_PARTY_SDK_PACKAGES
|
|
|
98
98
|
}
|
|
99
99
|
try{validatePackage(pkg);verified('DOD-02','Manifest, ports and fixtures validate against the SDK schema.');}
|
|
100
100
|
catch(error){failed('DOD-02',message(error));return result();}
|
|
101
|
+
if(pkg.manifest.name.trim().length>BLOCK_NAME_MAX_LENGTH)failed('DOD-02',`Shorten the Block name to ${BLOCK_NAME_MAX_LENGTH} characters or fewer. Put the detail in its description.`);
|
|
101
102
|
|
|
102
103
|
const manifest=pkg.manifest,sourceDigest=await digest(pkg),hosted=!offline(pkg);
|
|
103
104
|
let report=admission;
|
|
@@ -115,7 +115,7 @@ export function connectedBlockInputs(node, nodes, edges, {context={},raw=false,a
|
|
|
115
115
|
}
|
|
116
116
|
if(!pin&&allowOmissions && source.type==='programmableBlockNode' && !source.data.blockStale && output.required===false && node.data?.blockPackage?.manifest?.inputs[name]?.required===false && source.data.blockOutputs && !Object.hasOwn(source.data.blockOutputs,output.name) && /^[a-f0-9]{64}$/.test(source.data.blockDigest || '') && source.data.blockLastRun?.digest===source.data.blockDigest && Number.isFinite(Date.parse(source.data.blockLastRun.at))){omissions[name]=true;continue;}
|
|
117
117
|
inputs[name] = pinnedOutput.value;
|
|
118
|
-
if (pinnedOutput.value === undefined){errors.push(pin?`The pinned result for ${name} is unavailable. Choose a recorded result again.`:`Run the
|
|
118
|
+
if (pinnedOutput.value === undefined){errors.push(pin?`The pinned result for ${name} is unavailable. Choose a recorded result again.`:`Run ${source?.data?.label||source?.data?.blockPackage?.manifest?.name||'the Step before this one'} first.`);continue;}
|
|
119
119
|
if(node.type==='programmableBlockNode' && !raw){
|
|
120
120
|
try{inputs[name]=adaptConnectionValue(pinnedOutput,node.data.blockPackage.manifest.inputs[name],pinnedOutput.value,{context,semantics,permissions:node.data.blockPackage.manifest.permissions?.project || [],...(Object.hasOwn(edge.data || {},'stillmadeAdapter')?{adapter:edge.data.stillmadeAdapter}:{})});}
|
|
121
121
|
catch(error){inputs[name]=undefined;errors.push(error.message);}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {REVENUE_REVIEW_PROJECT_TYPES} from './revenue-review-project-types.js';
|
|
2
|
-
import {PRODUCT_AD_VARIANTS_PROJECT_TYPE,PRIOR_PRODUCT_AD_VARIANTS_PROJECT_TYPE,PREVIOUS_PRODUCT_AD_VARIANTS_PROJECT_TYPE} from './product-ad-variants-project-type.js';
|
|
2
|
+
import {PRODUCT_AD_VARIANTS_PROJECT_TYPE,PRODUCT_AD_VARIANTS_PROJECT_TYPE_1_0_3,PRIOR_PRODUCT_AD_VARIANTS_PROJECT_TYPE,PREVIOUS_PRODUCT_AD_VARIANTS_PROJECT_TYPE} from './product-ad-variants-project-type.js';
|
|
3
3
|
import {FIRST_PARTY_SDK_PACKAGES} from '../block-sdk/first-party-packages.js';
|
|
4
4
|
import {FIRST_REVENUE_PROJECT_TYPES} from './first-revenue-project-types.js';
|
|
5
5
|
import { SDK_VERSION, ROOT_CHAT_ID } from '../block-sdk/contracts.js';
|
|
@@ -86,7 +86,7 @@ HOST_WORKSPACE_BLOCKS.splice(HOST_WORKSPACE_BLOCKS.findIndex(block=>block.manife
|
|
|
86
86
|
// Promote a portable workspace after its accepted production MVP and rendered
|
|
87
87
|
// acceptance are complete; advanced host extensions can remain on old exact pins. Exact portable releases remain usable as normal
|
|
88
88
|
// Steps before promotion; this map only controls the default production route.
|
|
89
|
-
export const CURRENT_PORTABLE_WORKSPACE_VERSIONS=Object.freeze({'sm.chat':'2.0.3','sm.script':'2.1.4','sm.voiceover':'2.1.
|
|
89
|
+
export const CURRENT_PORTABLE_WORKSPACE_VERSIONS=Object.freeze({'sm.chat':'2.0.3','sm.script':'2.1.4','sm.voiceover':'2.1.6','sm.shot-plan':'2.1.3','sm.blueprint':'2.1.9','sm.pipeline':'2.2.5','sm.video-assets':'2.1.4','sm.panel-strip':'2.3.7','sm.board-editor':'2.2.10','sm.animated-scene':'2.3.8','sm.editor':'2.2.11'});
|
|
90
90
|
export const WORKSPACE_BLOCKS=HOST_WORKSPACE_BLOCKS.map(block=>{
|
|
91
91
|
const version=CURRENT_PORTABLE_WORKSPACE_VERSIONS[block.manifest.id];
|
|
92
92
|
if(!version)return block;
|
|
@@ -122,7 +122,9 @@ const GENERATION_HOST_RELEASES=Object.freeze(['sm.generate-image','sm.generate-v
|
|
|
122
122
|
const semverParts=version=>String(version).split(/[.-]/).slice(0,3).map(value=>Number(value)||0);
|
|
123
123
|
const newer=(left,right)=>{const a=semverParts(left),b=semverParts(right);for(let i=0;i<3;i++){if(a[i]!==b[i])return a[i]>b[i];}return false;};
|
|
124
124
|
const currentPortableById=new Map();
|
|
125
|
-
|
|
125
|
+
// An unset app connector is a project placeholder, not something to find or install.
|
|
126
|
+
export const UNDISCOVERABLE_SDK_BLOCKS=new Set(['sm.connect-app']);
|
|
127
|
+
for(const block of SDK_BUILTIN_BLOCKS){if(UNDISCOVERABLE_SDK_BLOCKS.has(block.manifest.id))continue;const prior=currentPortableById.get(block.manifest.id);if(!prior||newer(block.manifest.version,prior.manifest.version))currentPortableById.set(block.manifest.id,block);}
|
|
126
128
|
export const DISCOVERABLE_SDK_BUILTIN_BLOCKS = Object.freeze([...currentPortableById.values()]);
|
|
127
129
|
export const DISCOVERABLE_BLOCKS = [STARTING_CHAT_BLOCK,...WORKSPACE_BLOCKS,...DISCOVERABLE_SDK_BUILTIN_BLOCKS.filter(block=>!WORKSPACE_BLOCKS.some(current=>current.manifest.id===block.manifest.id&¤t.manifest.version===block.manifest.version))];
|
|
128
130
|
export const BUILTIN_BLOCKS = [STARTING_CHAT_BLOCK,...WORKSPACE_BLOCKS,...HISTORICAL_WORKSPACE_BLOCKS,DESCRIBE_MEDIA_HOST_1_1,YOUTUBE_HOST_1_1,...GENERATION_HOST_RELEASES,...SDK_BUILTIN_BLOCKS.filter(block=>!WORKSPACE_BLOCKS.some(current=>current.manifest.id===block.manifest.id&¤t.manifest.version===block.manifest.version)),...LEGACY_BLOCKS];
|
|
@@ -154,7 +156,7 @@ const SHARED_WORKSPACE_TYPES = currentBuiltinTypes('1.12.0','2.2.3',SCRIPT_WRITE
|
|
|
154
156
|
const PRIOR_CURRENT_TYPES = currentBuiltinTypes('1.13.0','2.2.3',SCRIPT_WRITER_BLOCK.manifest.version,{'blueprint':'2.1.8','panel-strip':'2.3.6','board-editor':'2.2.9','animated-scene':'2.3.7','editor':'2.2.8'});
|
|
155
157
|
const PREVIOUS_PORTABLE_TYPES=currentBuiltinTypes('1.14.0','2.2.4',SCRIPT_WRITER_BLOCK.manifest.version,{'blueprint':'2.1.8','panel-strip':'2.3.6','board-editor':'2.2.9','animated-scene':'2.3.7','editor':'2.2.8'});
|
|
156
158
|
export const CURRENT_BUILTIN_TYPES = [...currentBuiltinTypes('1.15.0','2.2.4','2.1.4',{'voiceover':'2.1.4','shot-plan':'2.1.3','blueprint':'2.1.9','panel-strip':'2.3.7','board-editor':'2.2.10','animated-scene':'2.3.8','editor':'2.2.9'}),...REVENUE_REVIEW_PROJECT_TYPES,PRODUCT_AD_VARIANTS_PROJECT_TYPE,...FIRST_REVENUE_PROJECT_TYPES];
|
|
157
|
-
const HISTORICAL_CURRENT_TYPES=[PRIOR_PRODUCT_AD_VARIANTS_PROJECT_TYPE,PREVIOUS_PRODUCT_AD_VARIANTS_PROJECT_TYPE,...PREVIOUS_PORTABLE_TYPES,...PRIOR_CURRENT_TYPES,...SHARED_WORKSPACE_TYPES,...SHARED_CANVAS_TYPES,...SHARED_BLUEPRINT_TYPES,...PRIOR_SHARED_TYPES,...MEDIA_LIBRARY_TYPES,...EDITOR_AUDIO_TYPES,...PLAYABLE_EDITOR_TYPES,...ADVANCED_PORTABLE_TYPES,...PORTABLE_MVP_TYPES,...PRE_PROMOTION_CURRENT_TYPES,...PREVIOUS_BUILTIN_TYPES.filter(type=>flows[type.legacyMode].includes('shot-plan')),...currentBuiltinTypes('1.1.0','1.1.0','1.1.0',PRIOR_HOST_WORKSPACE_VERSIONS)];
|
|
159
|
+
const HISTORICAL_CURRENT_TYPES=[PRODUCT_AD_VARIANTS_PROJECT_TYPE_1_0_3,PRIOR_PRODUCT_AD_VARIANTS_PROJECT_TYPE,PREVIOUS_PRODUCT_AD_VARIANTS_PROJECT_TYPE,...PREVIOUS_PORTABLE_TYPES,...PRIOR_CURRENT_TYPES,...SHARED_WORKSPACE_TYPES,...SHARED_CANVAS_TYPES,...SHARED_BLUEPRINT_TYPES,...PRIOR_SHARED_TYPES,...MEDIA_LIBRARY_TYPES,...EDITOR_AUDIO_TYPES,...PLAYABLE_EDITOR_TYPES,...ADVANCED_PORTABLE_TYPES,...PORTABLE_MVP_TYPES,...PRE_PROMOTION_CURRENT_TYPES,...PREVIOUS_BUILTIN_TYPES.filter(type=>flows[type.legacyMode].includes('shot-plan')),...currentBuiltinTypes('1.1.0','1.1.0','1.1.0',PRIOR_HOST_WORKSPACE_VERSIONS)];
|
|
158
160
|
export const BUILTIN_TYPES = [...CURRENT_BUILTIN_TYPES,...HISTORICAL_CURRENT_TYPES,...LEGACY_TYPES];
|
|
159
161
|
export const workspaceBlock = (id,version)=>DISCOVERABLE_BLOCKS.find(block=>block.manifest.id===id && (!version || block.manifest.version===version));
|
|
160
162
|
export const discoverableBuiltin = item=>!!workspaceBlock((item.manifest || item).id,(item.manifest || item).version);
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {auditProjectTypeConnections} from './project-type-composer.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// `stepInputs` holds what each Step already has typed in ({stageId:{key:value}}):
|
|
4
|
+
// an input a person supplies that is already there reads as provided, not as
|
|
5
|
+
// still needing them.
|
|
6
|
+
const supplied=value=>value!==undefined&&value!==null&&(typeof value!=='string'||value.trim()!=='')&&(!Array.isArray(value)||value.length>0);
|
|
7
|
+
export function projectTypeRequirementsForChat(type,catalog=[],{currentStage,contextValues=[],stepInputs={}}={}) {
|
|
4
8
|
if(!type)return undefined;
|
|
5
9
|
const values=contextValues.map(value=>value.source?.kind==='block'?{...value,source:{...value.source,stageId:value.source.stepId || value.source.stageId}}:value);
|
|
6
10
|
const audit=auditProjectTypeConnections(type,catalog,{contextValues:values}),enabled=audit.stages.filter(step=>!step.paused),steps=[];
|
|
@@ -13,8 +17,9 @@ export function projectTypeRequirementsForChat(type,catalog=[],{currentStage,con
|
|
|
13
17
|
const issues=blocking.slice(0,10).map(issue=>({stageId:issue.stageId?String(issue.stageId).slice(0,160):null,message:String(issue.reason || issue.message).slice(0,240)}));
|
|
14
18
|
let size=4500+JSON.stringify(issues).length,metadataSize=0,truncated=blocking.length>issues.length || blocking.some(issue=>String(issue.reason || issue.message).length>240 || String(issue.stageId || '').length>160);
|
|
15
19
|
for(const step of relevant) {
|
|
16
|
-
const row={id:String(step.id).slice(0,160),label:String(step.label).slice(0,160),upcoming:currentIndex<0 || enabled.indexOf(step)>=currentIndex,requirements:step.requirements.slice(0,12).map(decision=>({key:String(decision.key || decision.requirement.key).slice(0,160),description:String(decision.requirement.description || '').slice(0,200),semantic:String(decision.requirement.semantic || '').slice(0,160),type:decision.requirement.type,required:decision.requirement.required,state:decision.state,reason:String(decision.reason || '').slice(0,240),source:summarizeSource(decision.selected),candidates:candidatesFor(decision).slice(0,3).map(summarizeSource)}))};
|
|
17
|
-
|
|
20
|
+
const row={id:String(step.id).slice(0,160),label:String(step.label).slice(0,160),upcoming:currentIndex<0 || enabled.indexOf(step)>=currentIndex,requirements:step.requirements.slice(0,12).map(decision=>({key:String(decision.key || decision.requirement.key).slice(0,160),description:String(decision.requirement.description || '').slice(0,200),semantic:String(decision.requirement.semantic || '').slice(0,160),type:decision.requirement.type,required:decision.requirement.required,...(decision.state==='needs-user-input'&&supplied(stepInputs[step.id]?.[decision.key || decision.requirement.key])?{state:'provided',reason:'Set on this Step.'}:{state:decision.state,reason:String(decision.reason || '').slice(0,240)}),source:summarizeSource(decision.selected),candidates:candidatesFor(decision).slice(0,3).map(summarizeSource)}))};
|
|
21
|
+
// Once the inputs a person supplies are all there, the Step no longer waits on them.
|
|
22
|
+
row.state=step.state==='needs-user-input'&&!row.requirements.some(item=>item.state==='needs-user-input'&&item.required)?['chat','auto-connected'].find(state=>row.requirements.some(item=>item.state===state&&(item.required||state==='auto-connected')))||'ready':step.state;
|
|
18
23
|
const issue=step.contractIssue?.message || step.connectionIssues?.[0]?.message;
|
|
19
24
|
if(issue)row.issue=String(issue).slice(0,240);
|
|
20
25
|
let length=JSON.stringify(row).length;
|
|
@@ -61,7 +61,7 @@ export function mergeLiveDocument(local, remote, scope = 'editor') {
|
|
|
61
61
|
return reconcileShared(local, result);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export function validateLiveDocument(document, scope = 'editor', {experimentalBrowser} = {}) {
|
|
64
|
+
export function validateLiveDocument(document, scope = 'editor', {experimentalBrowser,experimentalNative} = {}) {
|
|
65
65
|
safeDocument(document);
|
|
66
66
|
assert(plain(document), 'DOCUMENT', 'Shared workspace must be an object');
|
|
67
67
|
const collection = (items, name, maximum) => {
|
|
@@ -101,7 +101,7 @@ export function validateLiveDocument(document, scope = 'editor', {experimentalBr
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
if (document.projectType !== undefined) {
|
|
104
|
-
const result=validateProjectType(document.projectType,BUILTIN_BLOCKS,{experimentalBrowser});
|
|
104
|
+
const result=validateProjectType(document.projectType,BUILTIN_BLOCKS,{experimentalBrowser,experimentalNative});
|
|
105
105
|
assert(result.valid,'DOCUMENT',`Invalid shared Project Type: ${result.errors.map(error=>error.message).join(' ')}`);
|
|
106
106
|
}
|
|
107
107
|
collection(document.nodes,'nodes',5000); collection(document.edges,'edges',10000);
|
|
@@ -146,13 +146,13 @@ export function validateLiveDocument(document, scope = 'editor', {experimentalBr
|
|
|
146
146
|
// validates already hydrated exact source only; no serializable document flag
|
|
147
147
|
// opens admission, reads artifacts, verifies retained records or grants access.
|
|
148
148
|
// Existing server save routes do not supply it and remain closed to this runtime.
|
|
149
|
-
export async function validateCanvasWorkflowPair(document,{previous,complete=false,experimentalBrowser}={}) {
|
|
150
|
-
validateLiveDocument(document,'canvas',{experimentalBrowser});
|
|
149
|
+
export async function validateCanvasWorkflowPair(document,{previous,complete=false,experimentalBrowser,experimentalNative}={}) {
|
|
150
|
+
validateLiveDocument(document,'canvas',{experimentalBrowser,experimentalNative});
|
|
151
151
|
if(document.projectType === undefined) {assert(!complete,'DOCUMENT','A workflow handoff requires its Project Type');return document;}
|
|
152
152
|
const type=document.projectType;
|
|
153
153
|
for(const [index,old] of (previous?.projectType?.packages||[]).entries())
|
|
154
154
|
assert(stableStringify(type.packages?.[index])===stableStringify(old),'VERSION','A saved package changed or moved. Keep existing package slots unchanged and append any newly reviewed release.');
|
|
155
|
-
const {canvas}=await compileTypeWorkspace(type,{experimentalBrowser}),expected=new Map(canvas.nodes.map(node=>[node.id,node])),actual=new Map(document.nodes.map(node=>[node.id,node]));
|
|
155
|
+
const {canvas}=await compileTypeWorkspace(type,{experimentalBrowser,experimentalNative}),expected=new Map(canvas.nodes.map(node=>[node.id,node])),actual=new Map(document.nodes.map(node=>[node.id,node]));
|
|
156
156
|
for(const [id,node] of expected) {
|
|
157
157
|
const current=actual.get(id);
|
|
158
158
|
assert(current || !complete,'CONFLICT','The workflow is missing a required Block instance. Reload the project before applying it.');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import mediaImport from '../../blocks/native/sm.import-media/versions/2.0.2/package.js';
|
|
2
|
-
import photoReelComposer from '../../blocks/photo-reel-composer/versions/1.0.
|
|
2
|
+
import photoReelComposer from '../../blocks/photo-reel-composer/versions/1.0.1/package.js';
|
|
3
3
|
import editor from '../../blocks/native/sm.editor/versions/2.2.9/package.js';
|
|
4
4
|
|
|
5
5
|
// Each Type carries its exact executable dependencies. The shared host supplies
|
|
@@ -8,7 +8,7 @@ import editor from '../../blocks/native/sm.editor/versions/2.2.9/package.js';
|
|
|
8
8
|
// than collecting onboarding answers that the Block cannot consume.
|
|
9
9
|
function photoReelType({id,name,description,brief,purpose,importLabel}) {
|
|
10
10
|
return Object.freeze({
|
|
11
|
-
schemaVersion:1,id,version:'1.0.
|
|
11
|
+
schemaVersion:1,id,version:'1.0.2',name,description,license:'MIT',startingChat:'native',
|
|
12
12
|
summary:{text:description,origin:'human',generation:'authored',status:'ready'},
|
|
13
13
|
defaults:{aspectRatio:'9:16',brief},
|
|
14
14
|
onboarding:[],
|
|
@@ -244,7 +244,7 @@ export function inspectProjectFlow(type,canvas={nodes:[],edges:[]},{projectId=nu
|
|
|
244
244
|
const appliedActivity=last?.manualSelection?{applied:true,at:last.at||null,runId:null,...(selectedManually?{selection:selectedManually}:{})}:last?{applied:true,at:last.at||null,runId:last.executionRunId||last.semanticRunId||last.segmentRunId||last.runId||null,skipped:last.skipped===true&&!last.failureRecovery,execution:executionEvidence(last,{conditional:!!stage.condition,outputs:node?.data?.blockOutputs||{}})}:null;
|
|
245
245
|
const condition=stage.condition?{inputKey:stage.condition.input,inputLabel:manifest?.inputs?.[stage.condition.input]?.description||stage.condition.input,path:stage.condition.path.slice(0,8),operator:stage.condition.operator,outcome:last?(last.skipped===true&&!last.failureRecovery?'skipped':'ran'):'unknown'}:null;
|
|
246
246
|
const latestFailure=(node?.data?.blockFailedAttempts||[]).slice(-1)[0],failureIsLatest=typeof latestFailure?.runId==='string'&&Number.isFinite(Date.parse(latestFailure.at))&&(!last?.at||latestFailure.at>last.at)&&!failureRecovered(latestFailure),failedActivity=failureIsLatest?{runId:latestFailure.runId.slice(0,160),at:latestFailure.at,code:failureCode(latestFailure),durationMs:Number.isSafeInteger(latestFailure.diagnostic?.durationMs)?Math.max(0,Math.min(86400000,latestFailure.diagnostic.durationMs)):null,...Object.fromEntries(['attempt','batchIndex','batchCount'].filter(key=>failedCounter(latestFailure[key])).map(key=>[key,latestFailure[key]]))}:null;
|
|
247
|
-
return {id:row.id,nodeId:stageNodeId(stage),label:row.label,blockId:stage.blockId,version:stage.version,transformation:String(manifest?.description||'').slice(0,500),enabled:stage.enabled!==false,state:node?.data?.blockPrivacy?'restricted':active?'working':row.paused?'paused':node?.data?.blockStale||last&&last.digest!==node?.data?.blockDigest?'needs-updating':failureIsLatest?(failureCode(latestFailure)==='EXECUTION_FAILED'?'failed':'needs-recovery'):last?.skipped===true&&!last?.failureRecovery&&stage.condition?'skipped':last?'finished':row.state==='broken'||row.state==='needs-user-input'?'needs-choice':'waiting',...(condition?{condition}:{}),inputs:inputs.map(input=>({...input,valueType:manifest?.inputs?.[input.key]?.type||row.requirements.find(requirement=>requirement.key===input.key)?.requirement?.type||null})),outputs,unusedResults,unusedResultsRevision,unusedResultsCount,unusedResultsOffset,unusedResultsLimit,unusedResultsNextOffset,...(retainedPage?.candidateId?{inspectedRetainedResult}:{}),usedInputs,activity:active?{...(appliedActivity||{}),active,activeRuns,activeRunsOmitted:Math.max(0,(runsByPlacement.get(row.id)||[]).length-activeRuns.length)}:failedActivity?{...(appliedActivity||{}),failedAttempt:failedActivity}:appliedActivity,history:recentHistory,historyComplete,retainedLoopRuns:retainedWorkflowLoopSummaries(canvas,row.id)};
|
|
247
|
+
return {id:row.id,nodeId:stageNodeId(stage),label:row.label,blockId:stage.blockId,version:stage.version,...(manifest?.permissions?.capabilities?.includes('connection.execute')?{connector:true}:{}),transformation:String(manifest?.description||'').slice(0,500),enabled:stage.enabled!==false,state:node?.data?.blockPrivacy?'restricted':active?'working':row.paused?'paused':node?.data?.blockStale||last&&last.digest!==node?.data?.blockDigest?'needs-updating':failureIsLatest?(failureCode(latestFailure)==='EXECUTION_FAILED'?'failed':'needs-recovery'):last?.skipped===true&&!last?.failureRecovery&&stage.condition?'skipped':last?'finished':row.state==='broken'||row.state==='needs-user-input'?'needs-choice':'waiting',...(condition?{condition}:{}),inputs:inputs.map(input=>({...input,valueType:manifest?.inputs?.[input.key]?.type||row.requirements.find(requirement=>requirement.key===input.key)?.requirement?.type||null})),outputs,unusedResults,unusedResultsRevision,unusedResultsCount,unusedResultsOffset,unusedResultsLimit,unusedResultsNextOffset,...(retainedPage?.candidateId?{inspectedRetainedResult}:{}),usedInputs,activity:active?{...(appliedActivity||{}),active,activeRuns,activeRunsOmitted:Math.max(0,(runsByPlacement.get(row.id)||[]).length-activeRuns.length)}:failedActivity?{...(appliedActivity||{}),failedAttempt:failedActivity}:appliedActivity,history:recentHistory,historyComplete,retainedLoopRuns:retainedWorkflowLoopSummaries(canvas,row.id)};
|
|
248
248
|
});
|
|
249
249
|
if(retainedResultPage){const placement=placements.find(item=>item.id===retainedResultPage.placementId);if(!placement||placement.unusedResultsRevision!==retainedResultPage.revision||retainedResultPage.operation==='list'&&retainedResultPage.offset>placement.unusedResultsCount||retainedResultPage.candidateId&&!placement.inspectedRetainedResult)throw new Error('This retained result list or exact candidate changed. Inspect Result again.');}
|
|
250
250
|
if(receiptInputPage){const entry=placements.find(item=>item.id===receiptInputPage.placementId)?.history?.find(item=>projectFlowRunReceiptIdentity(item)===receiptInputPage.receiptId);if(!entry||entry.inputsRevision!==receiptInputPage.revision||!entry.inputs||receiptInputPage.offset>entry.inputsCount)throw new Error('This retained receipt or its captured inputs changed. Inspect the run again.');}
|
|
@@ -271,7 +271,7 @@ export function inspectProjectFlow(type,canvas={nodes:[],edges:[]},{projectId=nu
|
|
|
271
271
|
if(placement.condition)conditionalLabels.push(`Conditional Block · last recorded ${placement.condition.outcome==='unknown'?'not run':placement.condition.outcome}`);
|
|
272
272
|
return {from:input.sourcePlacementId,outputKey:input.sourceOutputKey,to:placement.id,inputKey:input.key,state:input.state,materialized:input.materialized===true,sourceRunId:input.pinRunId||sourceOutput?.runId||null,adapter:input.adapter,...(conditionalLabels.length?{conditionalLabel:conditionalLabels.join(' · ')}:{})};
|
|
273
273
|
}));
|
|
274
|
-
return projectFlowSnapshot({contractVersion:PROJECT_FLOW_CONTRACT_VERSION,projectId,definition:{id:type.id,version:type.version},acceptanceCriteria:(type.acceptanceCriteria||[]).map(item=>({...item,verified:false})),goalSourceMaterials:validateGoalSourceMaterials(type),goalSourceInputs:currentGoalSourceInputs(type,[...BUILTIN_BLOCKS,...catalog]),placements,edges,issues:audit.issues.slice(0,50).map(issue=>({placementId:issue.stageId||null,inputKey:issue.key||null,message:issue.message||issue.reason||'Connection needs attention'})),partial:audit.requirementsInspected===false});
|
|
274
|
+
return projectFlowSnapshot({contractVersion:PROJECT_FLOW_CONTRACT_VERSION,projectId,definition:{id:type.id,version:type.version},...(type.groups?.length?{jobs:type.groups.map(group=>({id:group.id,label:group.label,parts:[...group.stages]}))}:{}),acceptanceCriteria:(type.acceptanceCriteria||[]).map(item=>({...item,verified:false})),goalSourceMaterials:validateGoalSourceMaterials(type),goalSourceInputs:currentGoalSourceInputs(type,[...BUILTIN_BLOCKS,...catalog]),placements,edges,issues:audit.issues.filter(issue=>!(issue.state==='needs-user-input'&&placements.find(placement=>placement.id===issue.stageId)?.inputs?.some(input=>input.key===issue.key&&input.state==='ready'))).slice(0,50).map(issue=>({placementId:issue.stageId||null,inputKey:issue.key||null,...(issue.state?{state:issue.state}:{}),message:issue.message||issue.reason||'Connection needs attention'})),partial:audit.requirementsInspected===false});
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
// A read-only explanation of the current handoff. Each hop is an inspected
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {assert,plain,safeDocument,stableStringify} from '../block-sdk/contracts.js';
|
|
2
|
+
import {defineNativeApplicationPackage} from '../block-sdk/native-application-package.js';
|
|
3
|
+
|
|
4
|
+
// Source structure only. This opaque capability grants no execution, source
|
|
5
|
+
// access, installation, result authentication, state commit or runtime setup.
|
|
6
|
+
const contexts=new WeakMap();
|
|
7
|
+
export const isNativeApplicationPackage=value=>value?.manifest?.runtime==='native-application'&&Object.hasOwn(value,'nativeApplication');
|
|
8
|
+
export function createNativeApplicationValidationContext(options){
|
|
9
|
+
assert(plain(options)&&Reflect.ownKeys(options).length===1&&Object.hasOwn(options,'sources')&&Object.hasOwn(Object.getOwnPropertyDescriptor(options,'sources'),'value'),'NATIVE_APPLICATION_CONTEXT','Supply only exact native sources for structure validation.');
|
|
10
|
+
safeDocument(options.sources);
|
|
11
|
+
assert(Array.isArray(options.sources)&&options.sources.length>0&&options.sources.length<=100,'NATIVE_APPLICATION_CONTEXT','Prevalidate 1–100 exact native application sources.');
|
|
12
|
+
const entries=new Map();
|
|
13
|
+
for(const value of options.sources){const source=defineNativeApplicationPackage(value),key=stableStringify(source);assert(!entries.has(key),'NATIVE_APPLICATION_CONTEXT','Duplicate exact native source.');entries.set(key,source);}
|
|
14
|
+
const context=Object.freeze(Object.create(null));contexts.set(context,entries);return context;
|
|
15
|
+
}
|
|
16
|
+
export function nativeApplicationValidationSource(context,value){
|
|
17
|
+
const entries=contexts.get(context);
|
|
18
|
+
assert(entries,'NATIVE_APPLICATION_RUNTIME_UNAVAILABLE','Native source structure requires an opaque trusted validation context; package JSON cannot grant access or admission.');
|
|
19
|
+
const source=entries.get(stableStringify(value));assert(source,'NATIVE_APPLICATION_PIN','Native source differs from this host-prevalidated package.');return source;
|
|
20
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// What people (and Chat, which speaks to them) read about a StillMade Block.
|
|
2
|
+
// Published manifests keep their exact technical descriptions; releases are
|
|
3
|
+
// immutable, so the plain wording for first-party Blocks lives here, by Block id.
|
|
4
|
+
// A Block whose own description is already plain needs no entry.
|
|
5
|
+
export const PLAIN_BLOCK_DESCRIPTIONS=Object.freeze({
|
|
6
|
+
'sm.script':'Write and edit a script with AI. Set a length, add notes, and go back to earlier versions.',
|
|
7
|
+
'sm.shot-plan':'Turn a script into a list of timed shots, then edit what each shot shows and how long it lasts.',
|
|
8
|
+
'sm.blueprint':'Plan your video’s scenes, characters and look in one place, and lock the parts you want to keep.',
|
|
9
|
+
'sm.pipeline':'Lay out your project on a canvas and connect its pieces, keeping your Steps and content.',
|
|
10
|
+
'sm.video-assets':'Choose which images and clips go into your video, in order, from everything the project has made or imported.',
|
|
11
|
+
'sm.editor':'The full video editor: text and titles, captions, transitions, music, effects and export are built in.',
|
|
12
|
+
'sm.panel-strip':'Turn a script into pictures timed to the voice, then edit scenes, images and transitions.',
|
|
13
|
+
'sm.board-editor':'Make text and pictures move on a whiteboard in time with a voice, then send it to the Editor.',
|
|
14
|
+
'sm.animated-scene':'Build animated scenes from your images and characters, preview them, then send them to the Editor.',
|
|
15
|
+
'stillmade.product-ad-variants':'Make three short product ads with different opening hooks from your own footage. Nothing is invented.',
|
|
16
|
+
'sm.youtube':'Upload a finished video to your connected YouTube channel.',
|
|
17
|
+
'sm.stock-search':'Search free-to-use stock photos and videos and pick one.',
|
|
18
|
+
'sm.export-video':'Turn your timeline into a video file you can download and keep in the project.',
|
|
19
|
+
'sm.export-audio':'Turn your timeline into an audio file you can download and keep in the project.',
|
|
20
|
+
'sm.motion-graphics':'Add animated titles, text, charts and transitions to your timeline. You see the change before it applies.',
|
|
21
|
+
'sm.score':'Put music, sound effects and voiceover from your project on the timeline. You see the change before it applies.',
|
|
22
|
+
'stillmade.short-form-finish':'Turn your clips and subtitles into a captioned short video you can still edit.',
|
|
23
|
+
'stillmade.occasion-story':'Arrange photos and video messages into a birthday, anniversary or farewell video you can still edit.',
|
|
24
|
+
'stillmade.release-visuals':'Make a social teaser or lyric clip for a song from its cover art and recording.',
|
|
25
|
+
'stillmade.photo-reel-composer':'Arrange property or product photos and your text into a 20–30 second reel. No AI images.',
|
|
26
|
+
'sm.import-media':'Upload or choose your own photos, images, video and audio, and put them in order for the next Step.',
|
|
27
|
+
'sm.chat':'Work out a script by chatting with AI.',
|
|
28
|
+
'sm.rough-cut':'Put your chosen clips together into a first cut you can review.',
|
|
29
|
+
'sm.final-edit':'Have AI suggest a final polish for a finished timeline. You review the changes before they apply.',
|
|
30
|
+
'sm.edit-assistant':'Ask for a timeline change in your own words, such as trimming a section. You review it before it applies.',
|
|
31
|
+
'sm.grade':'Change the colors of chosen clips with ready-made looks or simple sliders. Your originals stay as they are.',
|
|
32
|
+
'sm.transition':'Pick transitions between clips, with their timing and sounds, then apply them in the Editor.',
|
|
33
|
+
'sm.quality-audit':'Check your images and clips for quality problems before you export.',
|
|
34
|
+
'sm.compare':'Compare two images with a slider and keep the one you prefer.',
|
|
35
|
+
'sm.export-timeline':'Download your timeline to open in another editor, with or without its media.',
|
|
36
|
+
'sm.captions':'Turn a transcript or text into captions with word highlighting, then add them to the timeline.',
|
|
37
|
+
'sm.thumbnail':'Make a square thumbnail image from a short description.',
|
|
38
|
+
'sm.write-summary':'Turn data or notes, such as an app’s results, into a short summary in plain words.',
|
|
39
|
+
'sm.generate-video':'Make a short video clip from a description.',
|
|
40
|
+
'sm.character':'Describe a character once so your images and videos keep them the same.',
|
|
41
|
+
'sm.location':'Describe a place once so your images and videos keep it the same.',
|
|
42
|
+
'sm.style':'Set a look, with colors, lighting and camera style, for your images to follow.',
|
|
43
|
+
'sm.research':'Collect your notes and a source link into research you can reuse. It does not browse the web.',
|
|
44
|
+
'sm.image-editor':'Adjust an image’s brightness and contrast.',
|
|
45
|
+
'sm.describe-media':'Have AI describe what is in an image.',
|
|
46
|
+
'sm.transcribe':'Turn speech in an audio file into text, with a time for each word.',
|
|
47
|
+
'sm.shot-plan-assistant':'Have AI suggest a shot list from your script, keeping the shots you already have.',
|
|
48
|
+
'sm.animated-scene-assistant':'Have AI suggest how an animated scene is laid out and how it moves.',
|
|
49
|
+
'sm.blueprint-assistant':'Have AI suggest edits to your video plan.',
|
|
50
|
+
'sm.whiteboard-assistant':'Have AI build whiteboard boards, timing and motion from what you ask for.',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Words that describe how StillMade works inside rather than what a person gets.
|
|
54
|
+
export const INTERNAL_WORDS=/\b(exact|bounded|portable|capabilit(y|ies)|fulfillment|source-bound|canonical|typed|stored|production prompt|approved|retained|semantic|workspace|handoff|sandbox|dossier)\b/i;
|
|
55
|
+
|
|
56
|
+
/** The description to show for a Block: a first-party Block's plain wording, else its own. */
|
|
57
|
+
// An app task's own description is written for developers (field rules, other
|
|
58
|
+
// task ids); people see its first sentence only.
|
|
59
|
+
export function firstSentence(text,max=200){
|
|
60
|
+
const plain=String(text||'').replace(/\s+/g,' ').trim(),end=plain.search(/[.!?](\s|$)/);
|
|
61
|
+
const sentence=end>=0?plain.slice(0,end+1):plain;
|
|
62
|
+
return sentence.length>max?`${sentence.slice(0,max-1).trimEnd()}…`:sentence;
|
|
63
|
+
}
|
|
64
|
+
export function plainBlockDescription(manifest){
|
|
65
|
+
if(PLAIN_BLOCK_DESCRIPTIONS[manifest?.id])return PLAIN_BLOCK_DESCRIPTIONS[manifest.id];
|
|
66
|
+
return /^connected\./.test(String(manifest?.id||''))?firstSentence(manifest?.description):manifest?.description||'';
|
|
67
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import shortFormV101 from '../../blocks/short-form-finish/versions/1.0.1/package.js';
|
|
2
|
+
import shortFormV103 from '../../blocks/short-form-finish/versions/1.0.3/package.js';
|
|
2
3
|
import variants from '../../blocks/product-ad-variants/versions/1.0.0/package.js';
|
|
3
4
|
import editorV229 from '../../blocks/native/sm.editor/versions/2.2.9/package.js';
|
|
4
5
|
import editorV2210 from '../../blocks/native/sm.editor/versions/2.2.10/package.js';
|
|
5
6
|
import editorV2211 from '../../blocks/native/sm.editor/versions/2.2.11/package.js';
|
|
6
7
|
|
|
7
8
|
const description='Make three distinct approved hook and real-footage opening variants from one supplied product demonstration, with a separate editable export for each.';
|
|
8
|
-
const productAdVariantsType=(version,editor)=>Object.freeze({
|
|
9
|
+
const productAdVariantsType=(version,editor,shortForm=shortFormV101)=>Object.freeze({
|
|
9
10
|
schemaVersion:1,id:'stillmade.product-ad-variants-workflow',version,name:'Product Footage Ad Variants',description,license:'MIT',startingChat:'native',
|
|
10
11
|
summary:{text:description,origin:'human',generation:'authored',status:'ready'},
|
|
11
12
|
defaults:{aspectRatio:'9:16',brief:'Upload one authorized real product demonstration. Prepare a complete 5–60 second portrait edit with full framing and exact approved copy. Optional captions must be reviewed against the original speech. Choose three different non-overlapping 1–3 second source moments and supply three distinct approved hooks. Each candidate labels that source preview, then retains the same complete demonstration. Review and export A, B and C separately. No generated UGC, invented claims, automatic winning-hook selection or ad launch is included.'},
|
|
@@ -23,5 +24,7 @@ const productAdVariantsType=(version,editor)=>Object.freeze({
|
|
|
23
24
|
});
|
|
24
25
|
export const PREVIOUS_PRODUCT_AD_VARIANTS_PROJECT_TYPE=productAdVariantsType('1.0.1',editorV229);
|
|
25
26
|
export const PRIOR_PRODUCT_AD_VARIANTS_PROJECT_TYPE=productAdVariantsType('1.0.2',editorV2210);
|
|
26
|
-
export const
|
|
27
|
+
export const PRODUCT_AD_VARIANTS_PROJECT_TYPE_1_0_3=productAdVariantsType('1.0.3',editorV2211);
|
|
28
|
+
// 1.0.4: rights are confirmed once in the Content & IP Policy at sign-up, not per project.
|
|
29
|
+
export const PRODUCT_AD_VARIANTS_PROJECT_TYPE=productAdVariantsType('1.0.4',editorV2211,shortFormV103);
|
|
27
30
|
export default PRODUCT_AD_VARIANTS_PROJECT_TYPE;
|
|
@@ -121,7 +121,7 @@ export function projectContextSnapshot(data={},options={}) {
|
|
|
121
121
|
// generic native-media loop too. Excluding Block results must exclude the
|
|
122
122
|
// whole programmable node, including its legacy versions and input claims.
|
|
123
123
|
if(options.includeBlockOutputs===false&&node.type==='programmableBlockNode')continue;
|
|
124
|
-
const d=object(node.data),label=d.title || d.name || d.label || node.id;
|
|
124
|
+
const d=object(node.data),label=d.title || d.name || d.label || d.blockPackage?.manifest?.name || node.id;
|
|
125
125
|
if(node.type==='programmableBlockNode')for(const value of [...Object.values(object(d.blockInputs)),...(d.blockStale?[]:Object.values(object(d.blockOutputs)))])addFiles(value,d,label);
|
|
126
126
|
const referenceField={characterReferenceNode:'characters',locationReferenceNode:'locations',styleNode:'styles'}[node.type];
|
|
127
127
|
const role=referenceField ? {characters:'character_reference',locations:'location_reference',styles:'style_reference'}[referenceField] : undefined;
|
|
@@ -176,7 +176,7 @@ export function projectContextSnapshot(data={},options={}) {
|
|
|
176
176
|
for(const [name,declared] of Object.entries(d.blockPackage?.manifest?.outputs || {})){
|
|
177
177
|
if(!['image','video','audio','asset','image[]','video[]','audio[]','asset[]'].includes(declared.type))continue;
|
|
178
178
|
const output=result.outputs?.[name];
|
|
179
|
-
for(const ref of Array.isArray(output) ? output : output ? [output] : [])addMedia({...ref,source:'block',owner,role:declared.role,label:entry.step?.label || node.id,record:{createdAt:result.at || entry.at},active:false});
|
|
179
|
+
for(const ref of Array.isArray(output) ? output : output ? [output] : [])addMedia({...ref,source:'block',owner,role:declared.role,label:entry.step?.label || d.blockPackage?.manifest?.name || node.id,record:{createdAt:result.at || entry.at},active:false});
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
}
|
|
@@ -275,7 +275,7 @@ export function projectContextSnapshot(data={},options={}) {
|
|
|
275
275
|
for(const [name,declared] of Object.entries(d.blockPackage?.manifest?.outputs || {})){
|
|
276
276
|
if(!['image','video','audio','asset','image[]','video[]','audio[]','asset[]'].includes(declared.type))continue;
|
|
277
277
|
const output=row.outputs?.[name];
|
|
278
|
-
for(const ref of Array.isArray(output)?output:output?[output]:[])addMedia({...ref,source:'block',owner,role:declared.role,label:d.title || d.label || node.id,record:{createdAt:row.at},active,generated:active});
|
|
278
|
+
for(const ref of Array.isArray(output)?output:output?[output]:[])addMedia({...ref,source:'block',owner,role:declared.role,label:d.title || d.label || d.blockPackage?.manifest?.name || node.id,record:{createdAt:row.at},active,generated:active});
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
for(const row of [...list(d.blockBatchResults),...list(d.blockUnusedResults),...list(d.blockPinnedResults).map(result=>({...result,active:false}))]){
|
|
@@ -283,7 +283,7 @@ export function projectContextSnapshot(data={},options={}) {
|
|
|
283
283
|
for(const [name,declared] of Object.entries(d.blockPackage?.manifest?.outputs||{})){
|
|
284
284
|
if(!['image','video','audio','asset','image[]','video[]','audio[]','asset[]'].includes(declared.type))continue;
|
|
285
285
|
const output=row.outputs?.[name];
|
|
286
|
-
for(const ref of Array.isArray(output)?output:output?[output]:[])addMedia({...ref,source:'block',owner,role:declared.role,label:d.title||d.label||node.id,record:{createdAt:row.at},active:false});
|
|
286
|
+
for(const ref of Array.isArray(output)?output:output?[output]:[])addMedia({...ref,source:'block',owner,role:declared.role,label:d.title||d.label||d.blockPackage?.manifest?.name||node.id,record:{createdAt:row.at},active:false});
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
289
|
}
|