dsh-motion-ui 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +63 -3
  3. package/VERIFICATION.md +26 -0
  4. package/client.js +876 -272
  5. package/index.js +43 -20
  6. package/lib/assets/mermaid.js +382 -382
  7. package/lib/assets/standalone-runtime.js +1 -1
  8. package/lib/assets/visual.js +13 -13
  9. package/lib/author-expression.js +7 -7
  10. package/lib/authoring-guide.js +3 -17
  11. package/lib/build-info.js +1 -1
  12. package/lib/canvas-design.js +2 -2
  13. package/lib/canvas-export.js +3 -3
  14. package/lib/canvas-system.js +1 -1
  15. package/lib/catalog.js +1 -2
  16. package/lib/delivery.js +1 -1
  17. package/lib/export-runtime-bundle.js +1 -1
  18. package/lib/fence-feedback.js +8 -2
  19. package/lib/freeform.js +4 -10
  20. package/lib/inspection.js +19 -6
  21. package/lib/motion-craft.js +31 -0
  22. package/lib/motion-design-skill.js +23 -0
  23. package/lib/motion-library.js +5 -4
  24. package/lib/normalize-authoring.js +9 -0
  25. package/lib/project-archive.js +16 -0
  26. package/lib/prompt-contract.js +22 -0
  27. package/lib/protocol.js +3 -3
  28. package/lib/refinement.js +36 -3
  29. package/lib/sandbox.js +4 -1
  30. package/lib/scene-entry.js +41 -0
  31. package/lib/style-system.js +2 -2
  32. package/lib/text-guide.js +9 -13
  33. package/lib/third-party/taste-skill-NOTICE.md +10 -0
  34. package/lib/timeline.js +29 -0
  35. package/lib/tool-json.js +5 -0
  36. package/lib/tool-schema.js +7 -7
  37. package/lib/ui-intent.js +4 -15
  38. package/lib/ui-skill.js +19 -26
  39. package/lib/video-tools.js +21 -0
  40. package/lib/video.js +296 -0
  41. package/lib/visual-audit.js +15 -4
  42. package/lib/visual-styles.js +1 -1
  43. package/lib/voice.js +72 -0
  44. package/package.json +17 -9
  45. package/skills/intelligent-ui/SKILL.md +14 -107
  46. package/skills/motion-design/ADAPTER.md +35 -0
  47. package/skills/motion-design/LICENSE +201 -0
  48. package/skills/motion-design/NOTICE.md +9 -0
  49. package/skills/motion-design/SKILL.md +388 -0
  50. package/skills/motion-design/SOURCES.md +16 -0
  51. package/skills/motion-design/upstream.json +8 -0
package/index.js CHANGED
@@ -1,3 +1,10 @@
1
+ import {sceneDocument,SCENE_PARAMETERS,sceneReference} from './lib/scene-entry.js';
2
+ import {registerVideoTools} from './lib/video-tools.js';
3
+ import {VIDEO_GUIDE,PROMO_GUIDE} from './lib/motion-craft.js';
4
+ import {loadMotionDesignSkill,MOTION_DESIGN_REFERENCE} from './lib/motion-design-skill.js';
5
+ import {normalizeNodeEdits} from './lib/refinement.js';
6
+ import {TEXT_GUIDE} from './lib/text-guide.js';
7
+ import {CANVAS_CONTRACT,TOOL_DIRECTORY} from './lib/prompt-contract.js';
1
8
  import {mechanismDocument} from './lib/mechanism-entry.js';
2
9
  import {mechanismCatalog} from './lib/mechanism-catalog.js';
3
10
  import {motionCatalog} from './lib/motion-library.js';
@@ -7,25 +14,26 @@ import { validateDocument, fallbackText } from './lib/protocol.js';
7
14
  import { UiStore, stateRoute, documentRoute, historyRoute } from './lib/store.js';
8
15
  import { evaluate } from './lib/protocol.js';
9
16
  import { componentCatalog } from './lib/components.js';
17
+ import {CANVAS_SYSTEM_GUIDE} from './lib/canvas-system.js';
10
18
  import { CANVAS_DESIGN_GUIDE } from './lib/canvas-design.js';
11
19
  import { architectureExample, scatterExample } from './lib/visual-examples.js';
12
20
  import { BUILD_INFO } from './lib/build-info.js';
13
21
  import { UI_SKILL_SECTIONS, promptSections } from './lib/ui-skill.js';
14
- import {DEFAULT_VISUAL_STYLE,VISUAL_STYLE_IDS} from './lib/visual-styles.js';
22
+ import {stylePrompt,styleExamples,DEFAULT_VISUAL_STYLE,VISUAL_STYLE_IDS} from './lib/visual-styles.js';
15
23
  import { canvasDocument } from './lib/freeform.js';
16
24
  import z from '@deepseek-ai/schemastery';
17
25
  import { toolJson } from './lib/tool-json.js';
18
26
  import { DOCUMENT_PARAMETER,TEXT_BLOCKS_PARAMETER } from './lib/tool-schema.js';
19
27
  import {textDocument} from './lib/text-document.js';
20
28
  import {TEXT_PRESETS} from './lib/typography.js';
21
- import { inspectRecord } from './lib/inspection.js';
29
+ import { inspectRecord,normalizeNodeIds } from './lib/inspection.js';
22
30
  import {QUICK_EXAMPLES,QUICK_GUIDE} from './lib/quick-examples.js';
23
31
  import { PROTOCOL_GUIDE, calculatorExample, comparisonExample, budgetExample, curveExample, sceneExamples, bicycleExample, oscillatorExample } from './lib/catalog.js';
24
32
  import {VISUAL_GUIDE,VISUAL_EXAMPLES,visualCatalog} from './lib/visual.js';
25
33
  import {registerVisualAssets} from './lib/visual-assets.js';
26
34
  import {diagnosticsRoute} from './lib/diagnostics-route.js';
27
35
  import {fenceRoute} from './lib/fence-route.js';
28
- import {sceneCatalog,SCENE_GUIDE} from './lib/scene-library.js';
36
+ import {sceneCatalog} from './lib/scene-library.js';
29
37
  import {registerFenceFeedback} from './lib/fence-feedback.js';
30
38
  import {diagnoseDelivery,deliveryDiagnostics} from './lib/delivery.js';
31
39
 
@@ -38,25 +46,26 @@ function defineRenderTool(options) {
38
46
  // string branch that encourages models to manually escape whole documents.
39
47
  // Canonicalize adapter quirks before native schema checks, so a lossless
40
48
  // items:{item:[...]} wrapper receives the same treatment as stored/replayed UI.
41
- const normalized=args&&Object.hasOwn(args,'document')?{...args,document:validateDocument(toolJson(args.document,'document'))}:args&&Object.hasOwn(args,'blocks')?{...args,blocks:textDocument({...args,blocks:toolJson(args.blocks,'blocks')}).nodes}:args;
49
+ const normalized=args&&Object.hasOwn(args,'document')?{...args,document:validateDocument(toolJson(args.document,'document'))}:args&&Object.hasOwn(args,'blocks')?{...args,blocks:textDocument({...args,blocks:toolJson(args.blocks,'blocks')}).nodes}:args&&Object.hasOwn(args,'changes')?{...args,changes:normalizeNodeEdits(args.changes)}:args&&Object.hasOwn(args,'nodeIds')?{...args,nodeIds:normalizeNodeIds(args.nodeIds)}:args;
42
50
  return execute(normalized,exec);
43
51
  }};
44
52
  }
45
53
  export const Config=z.object({autoRepair:z.boolean().default(true).description('自动反馈无法交付的 UI 围栏、渲染失败及仅思考没有正文或工具的空交付,最多两次去重提醒;不限制模型思考。'),visualStyle:z.union(VISUAL_STYLE_IDS).default(DEFAULT_VISUAL_STYLE).description('引导模式的视觉风格;同步原生组件外观、风格提示词与案例,自由模式不应用。'),defaultPreset:z.union(TEXT_PRESETS).default('reading').description('兼容旧配置的保留字段,已停用;排版由模型按内容选择。'),designMode:z.union(['guided','free']).default('guided').description('guided:设计技能与示例引导;free:只注入画布接口与交互约束,由模型自主设计。'),displayMode:z.union(['tail','inline','sidebar']).default('inline').description('兼容旧配置的保留字段,已停用;统一使用正文标记,侧栏随时可打开。'),showDetails:z.boolean().default(false).description('显示参数状态、原始说明、视觉诊断和版本历史。默认关闭,保持界面简洁。')}).volatile();
46
54
  export function apply(ctx,config={}) {
47
55
  const store = new UiStore(dshHomePath('plugins', 'dsh-generative-ui'));
56
+
48
57
  const settings=()=>typeof config.get==='function'?config.get():config;
49
58
  const feedback=registerFenceFeedback(ctx,()=>settings().autoRepair!==false);
50
59
  const placement=value=>`\nPlace this exact fenced marker in your answer at the intended UI position:\n\`\`\`dsh-ui\n${value.documentId}\n\`\`\``;
51
60
  ctx.effect(()=>ctx.tools.register(defineRenderTool({
52
- name:'render_text',description:'Quickly format everyday answers as readable native text, small comparisons, lists, callouts or link buttons. No HTML/CSS/script needed. Multiple calls in one answer create independent UIs; use separate successful IDs for inline markers. blocks accepts plain strings or native v1 nodes with optional IDs; a text block is {type:"text",text:"Your content"}. Extended title/items visuals use render_ui instead. Text supports size/weight/color/align/lineHeight and spans for inline emphasis. Prefer this short path for ordinary structured explanations.',
61
+ name:'render_text',description:"Format native text, comparisons, steps, copyable text/code and link buttons without HTML. blocks is a direct array of strings/native nodes with optional IDs. State/formulas require render_ui; custom graphics require render_canvas. Several calls create independent documents.",
53
62
  parameters:{title:{type:'string'},preset:{type:'string',enum:TEXT_PRESETS},accent:{type:'string',enum:['blue','teal','violet','amber','rose']},blocks:TEXT_BLOCKS_PARAMETER},
54
63
  output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:v.document.fallback+`\nUI documentId=${v.documentId}; documentRevision=${v.documentRevision??0}.`+placement(v)}],presentationMeta:(_a,v)=>({dshGenerativeUi:v})},
55
64
  async execute(args,exec){exec.signal.throwIfAborted();for(const key of Object.keys(args))if(!['title','preset','accent','blocks'].includes(key))throw Error(`DSH UI: unknown render_text argument ${key}; put all content/styling inside blocks or children`);const document=textDocument({...args,blocks:toolJson(args.blocks,'blocks'),preset:args.preset??'reading'});fallbackText(document);const record=await store.create(document,{sessionId:exec.agent?.id??'unscoped',callId:exec.callId});exec.signal.throwIfAborted();return record;}
56
65
  })));
57
66
  ctx.effect(() => ctx.tools.register(defineRenderTool({
58
67
  name: 'render_ui',
59
- description: 'Render a static or interactive DSH UI v1 document in this conversation. Proactively use when visual relationships or changing parameters clearly explain the question better, even without an explicit UI request. For everyday styled text use render_text; simple facts can stay plain text. Deliver the smallest complete useful UI first. ui_catalog supplies optional details. Direct native calls only; PTC returns text. Calculations stay local. document accepts v1 {version:1,title,fallback,nodes} OR extended {title,items:[components]} for charts, plots, quiz, forms, diagrams and media. Repair errors at most twice or simplify/answer in text.',
68
+ description: "Render one complete native v1 {version:1,title,fallback,nodes,state?,derived?} OR extended {title,items:[components]} document. Do not mix root languages. Use for useful static/interactive explanations; native formulas stay local. Direct call only. ui_catalog supplies optional fields/examples.",
60
69
  parameters: { document: DOCUMENT_PARAMETER },
61
70
  output: {
62
71
  schema: { type: 'json' },
@@ -71,49 +80,63 @@ export function apply(ctx,config={}) {
71
80
  }
72
81
  })));
73
82
  ctx.effect(()=>ctx.tools.register(defineTool({
74
- name:'render_canvas',description:'Create a static or interactive HTML/SVG/CSS/JavaScript canvas in the conversation. You design all content, controls, layout and motion. No fixed UI composition. Same persistence, isolation and inspect/patch support as render_ui. Use the injected canvas contract; no external resources or host APIs. Direct native calls only.',
83
+ name:'render_canvas',description:"Render custom HTML/SVG/CSS/JS in an isolated canvas, with optional persisted state. Separate html/css/script fields; no external resources or host APIs. Use the injected canvas contract; libraries:[\"mechanism\"] enables the optional local helper.",
75
84
  parameters:{title:{type:'string',required:true},fallback:{type:'string',required:true},state:{type:'json'},data:{type:'json'},derived:{type:'json'},libraries:{type:'array',items:{type:'string',enum:['mechanism']}},height:{type:'number'},css:{type:'string',description:'Canvas CSS; use an empty string when no custom styles are needed. Styled static previews appear progressively; interaction activates after the assistant turn ends.'},html:{type:'string',required:true},script:{type:'string'}},
76
85
  output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:fallbackText(v.document)+`\nUI documentId=${v.documentId}; documentRevision=${v.documentRevision??0}; canvas nodeId=canvas.`+placement(v)}],presentationMeta:(_a,v)=>({dshGenerativeUi:v})},
77
86
  async execute(args,exec){exec.signal.throwIfAborted();const record=await store.create(canvasDocument(args),{sessionId:exec.agent?.id??'unscoped',callId:exec.callId});exec.signal.throwIfAborted();return record;}
78
87
  })));
88
+ ctx.effect(()=>ctx.tools.register(defineTool({name:'render_scene',description:'Deliver a complete flow/comparison/allocation/budget UI from short task data; Host creates nodes, controls and formulas. No source or catalog needed. values/items must be complete; never invent prices. Custom UI remains render_ui/render_canvas.',parameters:SCENE_PARAMETERS,output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:fallbackText(v.document)+'\nUI documentId='+v.documentId+'; documentRevision='+v.documentRevision+'. To revise this saved scene, use inspect_ui then patch_ui/update_ui with this documentId; calling render_scene again creates an extra UI.'+placement(v)}],presentationMeta:(_a,v)=>({dshGenerativeUi:v})},async execute(args,exec){exec.signal.throwIfAborted();const parsed={...args};for(const key of['items','values'])if(parsed[key]!==undefined)parsed[key]=toolJson(parsed[key],key);const record=await store.create(sceneDocument(parsed),{sessionId:exec.agent?.id??'unscoped',callId:exec.callId});exec.signal.throwIfAborted();return record;}})));
79
89
  ctx.effect(()=>ctx.tools.register(defineTool({
80
- name:'render_mechanism',description:'Fast path for an educational exploded assembly, external gear pair or slider-crank mechanism. Delivers the complete interactive diagram, controls, accurate local kinematics and persisted parameters from a few arguments. Proactively useful for internal structure and working principles. No SVG/CSS/script or catalog lookup needed. scene=assembly/gears/crank. Gears use a fixed 16-tooth driver; crank uses a fixed connecting rod length70. For other geometry use render_canvas with libraries:["mechanism"] and ui_catalog topic=mechanism. Illustrative shapes, not engineering simulation. Direct native calls only.',
90
+ name:'render_mechanism',description:"Create an adjustable assembly/gears/crank diagram from parameters, no source or catalog needed. Gear driver16 teeth; crank rod70. Educational geometry/kinematics. For other structures use render_canvas libraries:[\"mechanism\"] and ui_catalog topic=mechanism.",
81
91
  parameters:{scene:{type:'string',required:true,enum:['assembly','gears','crank']},title:{type:'string'},rpm:{type:'number',description:'gears/crank: 0..90, default20'},teeth:{type:'number',description:'gears only: driven integer16..48, default32'},radius:{type:'number',description:'crank only: integer10..28, default20'},running:{type:'boolean',description:'gears/crank: defaultfalse'},angle:{type:'number',description:'gears/crank: integer0..360 degrees, default0'},explode:{type:'number',description:'0..1 in steps0.01; assembly default0.45, others0'}},
82
92
  output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:fallbackText(v.document)+'\nUI documentId='+v.documentId+'; documentRevision='+v.documentRevision+'.'+placement(v)}],presentationMeta:(_a,v)=>({dshGenerativeUi:v})},
83
93
  async execute(args,exec){exec.signal.throwIfAborted();const record=await store.create(mechanismDocument(args),{sessionId:exec.agent?.id??'unscoped',callId:exec.callId});exec.signal.throwIfAborted();return record;}
84
94
  })));
85
95
  ctx.effect(() => ctx.tools.register(defineTool({
86
- name: 'ui_catalog', description: 'Read the DSH UI protocol and registered components. Optional topic: mechanism (assembly/gears/crank structure and kinematics; add scene for one complete example), templates (eight short scene summaries; add scene to retrieve one), motion (seven interactive recipe summaries; add scene for one), visual (extended component fields), overview (default, compact static examples), quick (four minimal static examples), canvas (freeform design guide and four visual examples), data, scenes, all (fourteen examples). Prefer targeted topics to avoid unnecessary context. Read-only; omit for ordinary text answers.',
87
- parameters: { topic: { type: 'string', enum: ['overview', 'quick', 'visual', 'motion', 'mechanism', 'templates', 'canvas', 'data', 'scenes', 'all'], description: 'overview, quick (four lightweight static layouts), canvas, data, scenes or all; default overview' },scene:{type:'string',description:'templates: brief, comparison, flow, dashboard, quiz, form, plot, diagram; mechanism: assembly, gears, crank; motion: action, quantity, elastic, plan, checklist, copy, plot'} }, output: { schema: { type: 'json' }, render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }] },
96
+ name: 'ui_catalog', description: "Read one optional reference topic or scene. text=native typography; native=v1 fields/formulas; visual=extended components; canvas=API/construction/examples; style=selected guided style/examples; templates/motion/mechanism=targeted recipes. overview/quick/data/scenes/all remain available; avoid all unless needed.",
97
+ parameters: { topic: { type: 'string', enum: ['overview','quick','text','native','style','visual','motion','video','mechanism','templates','canvas','data','scenes','all'], description:'Optional topic; default overview.' },scene:{type:'string',description:'Scene for canvas/templates/motion/mechanism, or explicit style ID for style. Omit to get summaries/current guided style.'} }, output: { schema: { type: 'json' }, render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }] },
88
98
  isConcurrencySafe: () => true,
89
99
  async execute({ topic = 'overview',scene } = {}) {
100
+ if((topic==='motion'||topic==='video')&&scene==='motion-design')return {topic,designSkill:await loadMotionDesignSkill()};
101
+ if(topic==='video')return{topic,guide:VIDEO_GUIDE,designReference:MOTION_DESIGN_REFERENCE,...(scene==='promo'?{production:PROMO_GUIDE,designSkill:await loadMotionDesignSkill()}:{}),capabilities:await video.capabilities(),workflow:'Author render_canvas with exactly one DSHUI.timeline({id:"main",duration,draw}) and #stage. Then render_video({documentId,duration,...}). For input videos inspect_video then view sampled frames, process_video clips/audio/captions. No unrequested video export. Output URLs are authenticated same-host downloads; HTML sources are attachments, never executed in the Host origin.'};
102
+ if(topic==='text')return {topic,guide:TEXT_GUIDE};
103
+ if(topic==='native')return {topic,protocol:PROTOCOL_GUIDE,components:componentCatalog(),example:calculatorExample};
104
+ if(topic==='style'){if(settings().designMode==='free'&&!scene)return{topic,mode:'free',note:'No style is selected in free mode. An explicit scene equal to a style ID requests reference guidance only.',styles:VISUAL_STYLE_IDS};const id=scene??settings().visualStyle??DEFAULT_VISUAL_STYLE;return{topic,style:id,guide:stylePrompt(id),examples:styleExamples(id)}}
105
+ if(topic==='canvas'){
106
+ const docs={bicycle:bicycleExample,oscillator:oscillatorExample,architecture:architectureExample,scatter:scatterExample};
107
+ if(scene&&!Object.hasOwn(docs,scene))throw Error('Unknown canvas scene; use bicycle, oscillator, architecture or scatter');
108
+ return {topic,interface:CANVAS_CONTRACT,construction:CANVAS_SYSTEM_GUIDE,...(settings().designMode==='free'?{}:{design:CANVAS_DESIGN_GUIDE}),examples:scene?[docs[scene]]:Object.entries(docs).map(([scene,d])=>({scene,title:d.title,fallback:d.fallback})),usage:'Add scene to retrieve one complete example, not all four sources.'};
109
+ }
110
+ if(topic==='overview')return{topic,guide:TOOL_DIRECTORY,examples:QUICK_EXAMPLES.slice(0,2)};
111
+ if(topic==='quick')return{...sceneReference(),design:QUICK_GUIDE,fullDocuments:QUICK_EXAMPLES};
90
112
  if(topic==='templates')return sceneCatalog(scene);
91
113
  if(topic==='mechanism')return mechanismCatalog(scene);
92
- if(topic==='motion')return motionCatalog(scene);
114
+ if(topic==='motion')return {...motionCatalog(scene),...(!scene?{designReferences:[MOTION_DESIGN_REFERENCE]}:{})};
93
115
  const groups = { overview: [calculatorExample, oscillatorExample], canvas: [bicycleExample, oscillatorExample, architectureExample, scatterExample], data: [calculatorExample, comparisonExample, budgetExample, curveExample, scatterExample], scenes: sceneExamples, all: [calculatorExample, comparisonExample, budgetExample, curveExample, ...sceneExamples, bicycleExample, oscillatorExample, architectureExample, scatterExample] };
94
116
  groups.quick=QUICK_EXAMPLES;groups.overview=QUICK_EXAMPLES.slice(0,2);
95
117
  groups.visual=VISUAL_EXAMPLES;
96
- if (!Object.hasOwn(groups, topic)) throw new Error('Unknown ui_catalog topic; use templates, overview, quick, visual, canvas, data, scenes or all');
118
+ if (!Object.hasOwn(groups, topic)) throw new Error('Unknown ui_catalog topic; use text, native, style, templates, overview, quick, visual, canvas, data, scenes, motion, mechanism or all');
97
119
  if(topic==='visual')return{build:BUILD_INFO,topic,protocol:VISUAL_GUIDE,components:visualCatalog(),examples:VISUAL_EXAMPLES};
98
120
  return { build: BUILD_INFO, topic, protocol: PROTOCOL_GUIDE, components: componentCatalog(), design: topic === 'quick' ? QUICK_GUIDE : topic === 'canvas' || topic === 'all' ? CANVAS_DESIGN_GUIDE : 'Choose composition by the question: editorial reading, technical relationships, analytical data, exploratory objects, aligned comparisons or ordered instruction. Keep theme, accessible labels and readable Chinese. Use topic=canvas for the full visual design guide.', examples: groups[topic] };
99
121
  }
100
122
  })));
101
123
  ctx.effect(() => ctx.connection.fetch.register({ path: '/api/plugins/dsh-generative-ui/state', methods: ['GET', 'POST'], requestBody: 'buffered', fetch: stateRoute(store) }));
124
+ const video=registerVideoTools(ctx,store);
102
125
  ctx.effect(() => ctx.connection.fetch.register({ path: '/api/plugins/dsh-generative-ui/document', methods: ['GET', 'POST'], requestBody: 'buffered', fetch: documentRoute(store) }));
103
126
  ctx.effect(() => ctx.connection.fetch.register({ path: '/api/plugins/dsh-generative-ui/history', methods: ['GET', 'POST'], requestBody: 'buffered', fetch: historyRoute(store) }));
104
127
  registerVisualAssets(ctx);
105
128
  ctx.effect(()=>ctx.connection.fetch.register({path:'/api/plugins/dsh-generative-ui/diagnostics',methods:['GET'],requestBody:'buffered',fetch:diagnosticsRoute(feedback)}));
106
129
  ctx.effect(()=>ctx.connection.fetch.register({path:'/api/plugins/dsh-generative-ui/fence',methods:['POST'],requestBody:'buffered',fetch:fenceRoute(store)}));
107
- ctx.effect(()=>ctx.tools.register(defineTool({name:'validate_ui',description:'Read-only validation for Motion UI v1 or extended title/items visuals. Returns valid, complete, canonical document and precise diagnostics without saving or displaying a UI. valid=true with complete=false means rejected components, not complete delivery. Use when unsure about a complex schema; ordinary minimal UIs can render directly.',parameters:{document:{type:'json',required:true}},output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:JSON.stringify(v)}]},isConcurrencySafe:()=>true,async execute({document}){try{const report=diagnoseDelivery(toolJson(document,'document'));return {...report,...(report.valid?{fallback:fallbackText(report.document)}:{error:report.diagnostics[0].message})}}catch(e){return{valid:false,complete:false,error:e.message}}}})));
108
- ctx.effect(()=>ctx.tools.register(defineTool({
109
- name:'inspect_ui',description:'Read the current UI revision, saved state/derived and heuristic visual observations. Default summary omits source. Use detail=nodes to read exact nodes before code edits; omitted nodeIds selects the first 16 nodes, or full only when needed. No report means unobserved, not a pass. Do not poll empty reports; automatic repair is at most two rounds per user request.',
110
- parameters:{documentId:{type:'string',required:true},detail:{type:'string',enum:['summary','nodes','full']},nodeIds:{type:'json',description:'For nodes detail: up to 16 existing node IDs.'}},
130
+ ctx.effect(()=>ctx.tools.register(defineTool({name:'validate_ui',description:"Validate native or extended UI without saving/displaying. valid with complete=false means rejected components. Returns canonical document/diagnostics; a rendering tool still delivers the UI.",parameters:{document:{type:'json',required:true}},output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:JSON.stringify(v)}]},isConcurrencySafe:()=>true,async execute({document}){try{const report=diagnoseDelivery(toolJson(document,'document'));return {...report,...(report.valid?{fallback:fallbackText(report.document)}:{error:report.diagnostics[0].message})}}catch(e){return{valid:false,complete:false,error:e.message}}}})));
131
+ ctx.effect(()=>ctx.tools.register(defineRenderTool({
132
+ name:'inspect_ui',description:"Read current revision/state/derived and heuristic observations. Summary includes node paths/positions/editable fields. query finds text/labels/types (max200 chars); detail=nodes with query OR nodeIds reads matching source. Full only when needed. Missing observations mean unobserved; do not poll. Inspect before editing.",
133
+ parameters:{documentId:{type:'string',required:true},detail:{type:'string',enum:['summary','nodes','full']},query:{type:'string',description:'Find by text/label/title/type; mutually exclusive with nodeIds. Up to16 matches; blank means all.'},nodeIds:{type:'array',items:{type:'string'},description:'For nodes detail: direct array of up to16 existing node IDs, e.g. ["canvas"], not HTML DOM IDs.'}},
111
134
  output:{schema:{type:'json'},render:(_args,value)=>[{type:'text',text:JSON.stringify(value)}]},isConcurrencySafe:()=>true,
112
- async execute({documentId,detail='summary',nodeIds=[]},exec){const record=await store.owned(documentId,exec.agent?.id);const saved=await store.readState(documentId,record.sessionId);return inspectRecord(record,saved.state,evaluate(record.document,saved.state).derived,await store.audits(documentId),{detail,nodeIds:toolJson(nodeIds,'nodeIds')});}
135
+ async execute({documentId,detail='summary',nodeIds=[],query=''},exec){const record=await store.owned(documentId,exec.agent?.id);const saved=await store.readState(documentId,record.sessionId);return inspectRecord(record,saved.state,evaluate(record.document,saved.state).derived,await store.audits(documentId),{detail,nodeIds:toolJson(nodeIds,'nodeIds'),query});}
113
136
  })));
114
- ctx.effect(()=>ctx.tools.register(defineTool({name:'patch_ui',description:'Refine existing UI nodes without changing their IDs, structure, input bindings or actions. First inspect_ui for current documentRevision. changes is [{id,props}], with registered properties only. Replacing canvas html/css/script restarts that canvas; saved parameters and unaffected live nodes remain. Revision conflicts require inspect and retry. No partial scripts execute.',parameters:{documentId:{type:'string',required:true},documentRevision:{type:'number',required:true},changes:{type:'json',required:true}},output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:`Updated ${v.document.title}; documentId=${v.documentId}; documentRevision=${v.documentRevision}. Saved parameters retained.`}],presentationMeta:(_a,v)=>({dshGenerativeUiPatch:v})},async execute({documentId,documentRevision,changes},exec){exec.signal.throwIfAborted();return store.refine(documentId,exec.agent?.id,documentRevision,toolJson(changes,'changes'));}})));
115
- ctx.effect(()=>ctx.tools.register(defineTool({name:'update_ui',description:'Append or replace content of an existing UI without generating a duplicate document. Inspect its revision first. Use targetId to modify an existing native node; visual targetId accepts an extended title/items document. Without targetId, an extended fragment appends to the only root visual when unambiguous; otherwise append adds root nodes (IDs must be unique). replace updates the root content. Preserve existing state/derived definitions; new inputs receive defaults. operationId is a stable unique edit name, reuse it only for an identical retry; replay is idempotent for the latest 64 edits.',parameters:{documentId:{type:'string',required:true},documentRevision:{type:'number',required:true},operationId:{type:'string',required:true},mode:{type:'string',enum:['append','replace'],required:true},targetId:{type:'string'},document:{type:'json',required:true}},output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:'Updated existing UI '+v.documentId+'; documentRevision='+v.documentRevision+'. Keep its existing inline marker; do not duplicate it.'}],presentationMeta:(_a,v)=>({dshGenerativeUiPatch:v})},async execute({documentId,documentRevision,operationId,mode,targetId,document},exec){exec.signal.throwIfAborted();return store.update(documentId,exec.agent?.id,documentRevision,operationId,{mode,...(targetId?{targetId}:{}),document:toolJson(document,'document')})}})));
116
- for (const [i,section] of UI_SKILL_SECTIONS.entries())ctx.effect(()=>ctx.systemPrompt.section({...section,text:()=>promptSections(settings().designMode??'guided',settings().visualStyle??DEFAULT_VISUAL_STYLE)[i].text+(i===0&&settings().designMode!=='free'?'\n'+SCENE_GUIDE:'')+(i===0?'\nFor changes to an existing UI, inspect_ui then update_ui with its current revision and a stable operationId; preserve the original marker and user values. Never create a second copy for an edit. Read delivery diagnostics and correct rejected components before claiming the UI is complete.\nPresentation mode: inline. After a successful render_text/render_ui/render_canvas/render_mechanism, put a fenced dsh-ui block containing only its documentId in your answer at the best explanatory position. Never invent an ID or put source inside the marker. If omitted, the interface remains below the answer.':'')}));
137
+ ctx.effect(()=>ctx.tools.register(defineRenderTool({name:'patch_ui',description:"Edit existing scalar/source properties using the inspected documentRevision. changes:[{id,props}]. IDs/structure/bindings/actions stay unchanged; canvas source replacement restarts that frame. Saved parameters persist. Use update_ui for structure changes.",parameters:{documentId:{type:'string',required:true},documentRevision:{type:'number',required:true},changes:{type:'array',required:true,description:'Direct array [{id,props}]; combine changed properties for one node in props.',items:{type:'object',additionalProperties:false,properties:{id:{type:'string',required:true},props:{type:'json',required:true}}}}},output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:`Updated ${v.document.title}; documentId=${v.documentId}; documentRevision=${v.documentRevision}. Saved parameters retained.`}],presentationMeta:(_a,v)=>({dshGenerativeUiPatch:v})},async execute({documentId,documentRevision,changes},exec){exec.signal.throwIfAborted();return store.refine(documentId,exec.agent?.id,documentRevision,toolJson(changes,'changes'));}})));
138
+ ctx.effect(()=>ctx.tools.register(defineTool({name:'update_ui',description:"Append/replace existing content at the inspected revision, keeping documentId and compatible state. targetId selects a node; an extended fragment appends to the sole visual when unambiguous. Supply a stable operationId; reuse only for an identical retry (latest64 replay IDs). Preserve state/derived definitions; new inputs get defaults.",parameters:{documentId:{type:'string',required:true},documentRevision:{type:'number',required:true},operationId:{type:'string',required:true},mode:{type:'string',enum:['append','replace'],required:true},targetId:{type:'string'},document:{type:'json',required:true}},output:{schema:{type:'json'},render:(_a,v)=>[{type:'text',text:'Updated existing UI '+v.documentId+'; documentRevision='+v.documentRevision+'. Keep its existing inline marker; do not duplicate it.'}],presentationMeta:(_a,v)=>({dshGenerativeUiPatch:v})},async execute({documentId,documentRevision,operationId,mode,targetId,document},exec){exec.signal.throwIfAborted();return store.update(documentId,exec.agent?.id,documentRevision,operationId,{mode,...(targetId?{targetId}:{}),document:toolJson(document,'document')})}})));
139
+ for(const[i,section]of UI_SKILL_SECTIONS.entries())ctx.effect(()=>ctx.systemPrompt.section({...section,text:()=>promptSections(settings().designMode??'guided',settings().visualStyle??DEFAULT_VISUAL_STYLE)[i].text}));
117
140
  }
118
141
 
119
142