@slatesvideo/shared 0.6.3 → 0.6.4
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/dist/api-url.d.ts +9 -0
- package/dist/api-url.js +9 -0
- package/dist/auth.d.ts +13 -1
- package/dist/auth.js +9 -5
- package/dist/clients/cloud.d.ts +3 -0
- package/dist/clients/cloud.js +34 -3
- package/dist/clients/desktop.js +3 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +19 -2
- package/dist/operations/index.d.ts +242 -30
- package/dist/operations/index.js +1275 -133
- package/dist/operations/surface.d.ts +69 -0
- package/dist/operations/surface.js +227 -0
- package/dist/prompts/agent-doctrine.js +7 -0
- package/dist/prompts/asset-label.d.ts +23 -0
- package/dist/prompts/asset-label.js +70 -0
- package/dist/prompts/banned-tokens.d.ts +15 -3
- package/dist/prompts/banned-tokens.js +76 -9
- package/dist/prompts/character-sheet.d.ts +0 -2
- package/dist/prompts/character-sheet.js +0 -2
- package/dist/prompts/craft-cards.d.ts +20 -0
- package/dist/prompts/craft-cards.js +82 -0
- package/dist/prompts/environment-sheet.js +16 -0
- package/dist/prompts/index.d.ts +1 -0
- package/dist/prompts/index.js +4 -0
- package/dist/prompts/model-capabilities.d.ts +52 -0
- package/dist/prompts/model-capabilities.js +42 -0
- package/dist/prompts/model-facts.d.ts +0 -4
- package/dist/prompts/model-facts.js +8 -4
- package/dist/prompts/partials.generated.js +2 -1
- package/dist/prompts/prompting-tips.d.ts +1 -1
- package/dist/prompts/prompting-tips.js +58 -0
- package/dist/prompts/reference-composer.d.ts +36 -7
- package/dist/prompts/reference-composer.js +75 -20
- package/dist/prompts/reference-rules.d.ts +15 -26
- package/dist/prompts/reference-rules.js +15 -93
- package/dist/prompts/shot-grammar.d.ts +154 -0
- package/dist/prompts/shot-grammar.js +184 -0
- package/dist/prompts/shot-spec.d.ts +265 -0
- package/dist/prompts/shot-spec.js +303 -0
- package/dist/skills/content.js +25 -23
- package/exports/slates-prompt-builder/generated/reference-content-policy.md +6 -0
- package/exports/slates-prompt-builder/generated/reference-kling.md +22 -0
- package/exports/slates-prompt-builder/generated/reference-nano-banana.md +17 -0
- package/exports/slates-prompt-builder/generated/reference-seedance.md +17 -0
- package/exports/slates-prompt-builder/generated/slates-prompt-builder-manifest.json +15 -15
- package/exports/slates-prompt-builder/generated/slates-prompt-builder.skill +0 -0
- package/package.json +83 -73
- package/skills/_partials/decision-log.md +5 -4
- package/skills/_partials/thresholds.md +19 -0
- package/skills/slates-content-policy.md +15 -1
- package/skills/slates-cost-discipline.md +26 -4
- package/skills/slates-model-selection.md +2 -2
- package/skills/slates-one-prompt-film.md +20 -12
- package/skills/slates-project-organization.md +1 -1
- package/skills/slates-prompting-elevenlabs.md +61 -2
- package/skills/slates-prompting-flux-2-max.md +39 -0
- package/skills/slates-prompting-gpt-image-2.md +109 -70
- package/skills/slates-prompting-inworld-tts.md +166 -0
- package/skills/slates-prompting-kling-v3.md +39 -0
- package/skills/slates-prompting-lip-sync.md +38 -0
- package/skills/slates-prompting-ltx-2-5.md +38 -0
- package/skills/slates-prompting-minimax-h3.md +39 -0
- package/skills/slates-prompting-motion-transfer.md +38 -0
- package/skills/slates-prompting-nano-banana-2.md +26 -0
- package/skills/slates-prompting-omni-flash.md +41 -0
- package/skills/slates-prompting-seed-audio.md +38 -0
- package/skills/slates-prompting-seedance-2-5.md +38 -0
- package/skills/slates-prompting-seedance.md +26 -0
- package/skills/slates-prompting-seedream-5-lite.md +38 -0
- package/skills/slates-prompting-veo-3.md +39 -0
- package/skills/slates-shot-variety.md +53 -0
- package/skills/slates-storyboard-from-script.md +31 -15
- package/skills/slates-style-prompting.md +1 -1
- package/skills/slates-vision-feedback-loop.md +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { OperationAnnotations, OperationGroup, OperationTier } from './index.js';
|
|
3
|
+
/** Structural shape of an op, declared locally so this module never imports a
|
|
4
|
+
* VALUE from the registry (which imports this one). */
|
|
5
|
+
export interface SurfaceOp {
|
|
6
|
+
id: string;
|
|
7
|
+
description: string;
|
|
8
|
+
input: z.ZodType<unknown>;
|
|
9
|
+
billable?: boolean;
|
|
10
|
+
tier?: OperationTier;
|
|
11
|
+
group?: OperationGroup;
|
|
12
|
+
annotations?: OperationAnnotations;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Derive an op's four hints.
|
|
16
|
+
*
|
|
17
|
+
* `openWorldHint` means "reaches something outside this system": every billable
|
|
18
|
+
* op hits a provider, and every Blender op hits a separate process over a
|
|
19
|
+
* socket. Everything else touches only the user's own workspace.
|
|
20
|
+
*/
|
|
21
|
+
export declare function annotate(id: string, billable: boolean | undefined): OperationAnnotations;
|
|
22
|
+
/**
|
|
23
|
+
* The deferred groups, by op id. Everything NOT listed here is `core`.
|
|
24
|
+
*
|
|
25
|
+
* The cut is "what a session needs to do the work" versus "what it needs once,
|
|
26
|
+
* when the user asks for that thing". Creating, generating, reviewing and
|
|
27
|
+
* shot-listing are core; tidying the library, cutting a timeline, renaming and
|
|
28
|
+
* deleting, and driving Blender are not — and each is a coherent thing a user
|
|
29
|
+
* asks for by name, which is what makes a group loadable in one call.
|
|
30
|
+
*
|
|
31
|
+
* 🚨 A GROUP IS A BUDGET, NOT A LAW. If `final_state` falls on timeline or
|
|
32
|
+
* library tasks after this shipped, move those ops back to core. The point is
|
|
33
|
+
* the per-turn byte count, and a deferral that costs a turn every time is not
|
|
34
|
+
* paying for itself.
|
|
35
|
+
*/
|
|
36
|
+
export declare const OPERATION_GROUPS: Record<OperationGroup, readonly string[]>;
|
|
37
|
+
export declare function groupFor(id: string): OperationGroup | undefined;
|
|
38
|
+
/** `extended` iff the op names a group. Absent from every group ⇒ `core`, so a
|
|
39
|
+
* new op is visible until someone deliberately defers it. */
|
|
40
|
+
export declare function tierFor(id: string): OperationTier;
|
|
41
|
+
/** One-line summary of each group, for `slates_load_tools`' own description. */
|
|
42
|
+
export declare const GROUP_SUMMARY: Record<OperationGroup, string>;
|
|
43
|
+
export interface ToolDefinition {
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
/** JSON Schema. Both surfaces read the same object under their own key name. */
|
|
47
|
+
inputSchema: Record<string, unknown>;
|
|
48
|
+
annotations: OperationAnnotations;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Render one op's tool definition.
|
|
52
|
+
*
|
|
53
|
+
* `$refStrategy: 'none'` is the shape both surfaces now emit: inlined, so no
|
|
54
|
+
* client has to resolve a `$ref`, and byte-identical between them — which is
|
|
55
|
+
* what makes "the two surfaces expose the same tools" checkable rather than
|
|
56
|
+
* merely asserted about the id set.
|
|
57
|
+
*/
|
|
58
|
+
export declare function toolDefinition(op: SurfaceOp): ToolDefinition;
|
|
59
|
+
/**
|
|
60
|
+
* Render a tool surface.
|
|
61
|
+
*
|
|
62
|
+
* `desktop` sends `core` plus whatever groups have been loaded this run; `mcp`
|
|
63
|
+
* sends everything, because a stdio server has no run to append to.
|
|
64
|
+
*/
|
|
65
|
+
export declare function toolDefinitions(ops: readonly SurfaceOp[], opts: {
|
|
66
|
+
surface: 'desktop' | 'mcp';
|
|
67
|
+
groups?: readonly OperationGroup[];
|
|
68
|
+
}): ToolDefinition[];
|
|
69
|
+
//# sourceMappingURL=surface.d.ts.map
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// THE TOOL SURFACE: annotations, tiers, and the ONE schema renderer.
|
|
3
|
+
//
|
|
4
|
+
// Three jobs that all answer "what does a client see, and when":
|
|
5
|
+
//
|
|
6
|
+
// 1. ANNOTATIONS (MCP spec 2025-06-18). `readOnlyHint` / `destructiveHint` /
|
|
7
|
+
// `idempotentHint` / `openWorldHint`, so a host can auto-approve a read
|
|
8
|
+
// and warn on a delete. Without them `slates_list_assets` prompts exactly
|
|
9
|
+
// like `slates_delete_project`, every prompt looks the same, and the user
|
|
10
|
+
// learns to click through all of them.
|
|
11
|
+
//
|
|
12
|
+
// 2. TIERS. 90 ops is 112 KB of descriptions and JSON schemas on EVERY
|
|
13
|
+
// desktop Studio Agent turn. `core` is what a session needs to work;
|
|
14
|
+
// `extended` is deferred behind `slates_load_tools` and appended to the
|
|
15
|
+
// run's tool list once a group loads. The MCP server still registers
|
|
16
|
+
// everything — a stdio server has no run to append to, and Claude Code
|
|
17
|
+
// already defers stdio tool schemas through its own tool search.
|
|
18
|
+
//
|
|
19
|
+
// 3. ONE SCHEMA RENDERER. The desktop rendered `$refStrategy: 'none'` and
|
|
20
|
+
// the MCP server rendered `target: 'openApi3'`, so "the two surfaces
|
|
21
|
+
// expose the same tools" was true of the ID SET and unproven of the
|
|
22
|
+
// BYTES. `toolDefinitions()` is now the only renderer either one calls.
|
|
23
|
+
//
|
|
24
|
+
// 🚨 NOTHING HERE IS HAND-SET PER OP. Annotations are derived from the op id
|
|
25
|
+
// by the rules below, and `scripts/agent-surface-lockstep-check.mjs` re-derives
|
|
26
|
+
// them INDEPENDENTLY from each op's own transport verbs — a `readOnlyHint` on
|
|
27
|
+
// an op whose `run` body posts is a lie that lets a host auto-approve a
|
|
28
|
+
// mutation, so the check has to be able to catch it, and it is mutation-tested.
|
|
29
|
+
// ============================================================
|
|
30
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
31
|
+
// ── 1. Annotations ──────────────────────────────────────────────────────────
|
|
32
|
+
/** Ops that only read. Prefixes, because the verb IS the first word of the id. */
|
|
33
|
+
const READ_ONLY_PREFIXES = [
|
|
34
|
+
'slates_get_',
|
|
35
|
+
'slates_list_',
|
|
36
|
+
'slates_estimate_',
|
|
37
|
+
'slates_blender_status',
|
|
38
|
+
'slates_blender_scene',
|
|
39
|
+
'slates_blender_docs',
|
|
40
|
+
'slates_blender_search_docs',
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Ops whose effect a user cannot undo from inside Slates.
|
|
44
|
+
*
|
|
45
|
+
* Moves are here on purpose: `slates_move_assets_to_project` relocates files on
|
|
46
|
+
* disk, and an agent that guessed the wrong project has no "put it back"
|
|
47
|
+
* without knowing where they came from. A rename is recoverable; a move across
|
|
48
|
+
* projects, in practice, is not.
|
|
49
|
+
*/
|
|
50
|
+
const DESTRUCTIVE_IDS = new Set([
|
|
51
|
+
'slates_delete_project',
|
|
52
|
+
'slates_delete_asset',
|
|
53
|
+
'slates_delete_folder',
|
|
54
|
+
'slates_delete_character',
|
|
55
|
+
'slates_delete_environment',
|
|
56
|
+
'slates_delete_style',
|
|
57
|
+
'slates_delete_storyboard',
|
|
58
|
+
'slates_delete_scene',
|
|
59
|
+
'slates_delete_frame',
|
|
60
|
+
'slates_move_assets_to_folder',
|
|
61
|
+
'slates_move_assets_to_project',
|
|
62
|
+
'slates_move_entity_to_project',
|
|
63
|
+
'slates_merge_shots',
|
|
64
|
+
'slates_split_shot',
|
|
65
|
+
]);
|
|
66
|
+
/** Same input, same end state — `update`/`set`/`reorder`/`rename` all overwrite
|
|
67
|
+
* rather than append, so re-running one is a no-op rather than a second edit. */
|
|
68
|
+
const IDEMPOTENT_PREFIXES = [
|
|
69
|
+
'slates_update_',
|
|
70
|
+
'slates_set_',
|
|
71
|
+
'slates_reorder_',
|
|
72
|
+
'slates_rename_',
|
|
73
|
+
'slates_batch_update_',
|
|
74
|
+
];
|
|
75
|
+
const startsWithAny = (id, prefixes) => prefixes.some((p) => id.startsWith(p));
|
|
76
|
+
/**
|
|
77
|
+
* Derive an op's four hints.
|
|
78
|
+
*
|
|
79
|
+
* `openWorldHint` means "reaches something outside this system": every billable
|
|
80
|
+
* op hits a provider, and every Blender op hits a separate process over a
|
|
81
|
+
* socket. Everything else touches only the user's own workspace.
|
|
82
|
+
*/
|
|
83
|
+
export function annotate(id, billable) {
|
|
84
|
+
const readOnlyHint = startsWithAny(id, READ_ONLY_PREFIXES);
|
|
85
|
+
return {
|
|
86
|
+
readOnlyHint,
|
|
87
|
+
// A read is never destructive, whatever the id looks like.
|
|
88
|
+
destructiveHint: !readOnlyHint && DESTRUCTIVE_IDS.has(id),
|
|
89
|
+
idempotentHint: readOnlyHint || startsWithAny(id, IDEMPOTENT_PREFIXES),
|
|
90
|
+
openWorldHint: !!billable || id.startsWith('slates_blender_'),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// ── 2. Tiers ────────────────────────────────────────────────────────────────
|
|
94
|
+
/**
|
|
95
|
+
* The deferred groups, by op id. Everything NOT listed here is `core`.
|
|
96
|
+
*
|
|
97
|
+
* The cut is "what a session needs to do the work" versus "what it needs once,
|
|
98
|
+
* when the user asks for that thing". Creating, generating, reviewing and
|
|
99
|
+
* shot-listing are core; tidying the library, cutting a timeline, renaming and
|
|
100
|
+
* deleting, and driving Blender are not — and each is a coherent thing a user
|
|
101
|
+
* asks for by name, which is what makes a group loadable in one call.
|
|
102
|
+
*
|
|
103
|
+
* 🚨 A GROUP IS A BUDGET, NOT A LAW. If `final_state` falls on timeline or
|
|
104
|
+
* library tasks after this shipped, move those ops back to core. The point is
|
|
105
|
+
* the per-turn byte count, and a deferral that costs a turn every time is not
|
|
106
|
+
* paying for itself.
|
|
107
|
+
*/
|
|
108
|
+
export const OPERATION_GROUPS = {
|
|
109
|
+
// Folders, styles, and moving assets around. Reached when the user says
|
|
110
|
+
// "tidy this up", never while making the thing.
|
|
111
|
+
library: [
|
|
112
|
+
'slates_list_folders',
|
|
113
|
+
'slates_create_folder',
|
|
114
|
+
'slates_rename_folder',
|
|
115
|
+
'slates_delete_folder',
|
|
116
|
+
'slates_set_folder_cover',
|
|
117
|
+
'slates_move_assets_to_folder',
|
|
118
|
+
'slates_move_assets_to_project',
|
|
119
|
+
'slates_copy_assets_to_project',
|
|
120
|
+
'slates_move_entity_to_project',
|
|
121
|
+
'slates_list_styles',
|
|
122
|
+
'slates_create_style',
|
|
123
|
+
'slates_update_style',
|
|
124
|
+
'slates_delete_style',
|
|
125
|
+
'slates_get_project_directory',
|
|
126
|
+
'slates_reveal_file',
|
|
127
|
+
],
|
|
128
|
+
// The cut and the export. A generation session never touches these.
|
|
129
|
+
timeline: [
|
|
130
|
+
'slates_get_timeline',
|
|
131
|
+
'slates_add_clip_to_timeline',
|
|
132
|
+
'slates_reorder_clips',
|
|
133
|
+
'slates_remove_clip',
|
|
134
|
+
'slates_add_timeline_track',
|
|
135
|
+
'slates_update_timeline_track',
|
|
136
|
+
'slates_remove_timeline_track',
|
|
137
|
+
'slates_update_timeline_settings',
|
|
138
|
+
'slates_export_video',
|
|
139
|
+
'slates_export_timeline_xml',
|
|
140
|
+
'slates_trim_video',
|
|
141
|
+
],
|
|
142
|
+
// Rename, delete, reorder. The 63-op CRUD tail the review measured at 39.5 KB.
|
|
143
|
+
admin: [
|
|
144
|
+
'slates_update_project',
|
|
145
|
+
'slates_delete_project',
|
|
146
|
+
'slates_delete_asset',
|
|
147
|
+
'slates_update_character',
|
|
148
|
+
'slates_delete_character',
|
|
149
|
+
'slates_update_environment',
|
|
150
|
+
'slates_delete_environment',
|
|
151
|
+
'slates_update_storyboard',
|
|
152
|
+
'slates_delete_storyboard',
|
|
153
|
+
'slates_update_scene',
|
|
154
|
+
'slates_delete_scene',
|
|
155
|
+
'slates_reorder_scenes',
|
|
156
|
+
'slates_delete_frame',
|
|
157
|
+
'slates_batch_update_frames',
|
|
158
|
+
'slates_duplicate_shot',
|
|
159
|
+
'slates_split_shot',
|
|
160
|
+
'slates_merge_shots',
|
|
161
|
+
],
|
|
162
|
+
// A third transport nobody without Blender installed can reach.
|
|
163
|
+
blender: [
|
|
164
|
+
'slates_blender_status',
|
|
165
|
+
'slates_blender_execute',
|
|
166
|
+
'slates_blender_scene',
|
|
167
|
+
'slates_blender_docs',
|
|
168
|
+
'slates_blender_search_docs',
|
|
169
|
+
'slates_blender_render_blocking',
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
const GROUP_BY_OP = new Map();
|
|
173
|
+
for (const [group, ids] of Object.entries(OPERATION_GROUPS)) {
|
|
174
|
+
for (const id of ids)
|
|
175
|
+
GROUP_BY_OP.set(id, group);
|
|
176
|
+
}
|
|
177
|
+
export function groupFor(id) {
|
|
178
|
+
return GROUP_BY_OP.get(id);
|
|
179
|
+
}
|
|
180
|
+
/** `extended` iff the op names a group. Absent from every group ⇒ `core`, so a
|
|
181
|
+
* new op is visible until someone deliberately defers it. */
|
|
182
|
+
export function tierFor(id) {
|
|
183
|
+
return GROUP_BY_OP.has(id) ? 'extended' : 'core';
|
|
184
|
+
}
|
|
185
|
+
/** One-line summary of each group, for `slates_load_tools`' own description. */
|
|
186
|
+
export const GROUP_SUMMARY = {
|
|
187
|
+
library: 'folders, styles, moving and copying assets between projects, revealing files on disk',
|
|
188
|
+
timeline: 'the timeline (tracks, clips, settings), video export and NLE XML export, clip trimming',
|
|
189
|
+
admin: 'rename / delete / reorder for projects, characters, environments, storyboards, scenes and frames; shot duplicate, split and merge',
|
|
190
|
+
blender: 'the Blender previs bridge — scene inspection, bpy execution, API docs, grey-box render',
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Render one op's tool definition.
|
|
194
|
+
*
|
|
195
|
+
* `$refStrategy: 'none'` is the shape both surfaces now emit: inlined, so no
|
|
196
|
+
* client has to resolve a `$ref`, and byte-identical between them — which is
|
|
197
|
+
* what makes "the two surfaces expose the same tools" checkable rather than
|
|
198
|
+
* merely asserted about the id set.
|
|
199
|
+
*/
|
|
200
|
+
export function toolDefinition(op) {
|
|
201
|
+
const schema = zodToJsonSchema(op.input, { $refStrategy: 'none' });
|
|
202
|
+
delete schema.$schema;
|
|
203
|
+
return {
|
|
204
|
+
name: op.id,
|
|
205
|
+
description: op.description,
|
|
206
|
+
inputSchema: schema,
|
|
207
|
+
annotations: op.annotations ?? annotate(op.id, op.billable),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Render a tool surface.
|
|
212
|
+
*
|
|
213
|
+
* `desktop` sends `core` plus whatever groups have been loaded this run; `mcp`
|
|
214
|
+
* sends everything, because a stdio server has no run to append to.
|
|
215
|
+
*/
|
|
216
|
+
export function toolDefinitions(ops, opts) {
|
|
217
|
+
const loaded = new Set(opts.groups ?? []);
|
|
218
|
+
return ops
|
|
219
|
+
.filter((op) => {
|
|
220
|
+
if (opts.surface === 'mcp')
|
|
221
|
+
return true;
|
|
222
|
+
const group = groupFor(op.id);
|
|
223
|
+
return group === undefined || loaded.has(group);
|
|
224
|
+
})
|
|
225
|
+
.map(toolDefinition);
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=surface.js.map
|
|
@@ -101,6 +101,7 @@ export const HARD_RULES = [
|
|
|
101
101
|
fork(`- COST DISCIPLINE (slates-cost-discipline): never fire a billable generation outside an approved plan. Estimate before you promise. Batch related generations into one plan.`, `- COST DISCIPLINE (slates-cost-discipline): never fire a billable generation the user has not agreed to. Estimate before you promise. Batch related generations into one quote so the user approves a total, not a drip.`),
|
|
102
102
|
both(`- CONTENT POLICY: before writing prompts involving real people/celebrities, minors, brands/logos, weapons, or gore, load slates-content-policy and build the scene safe from the first word. If a provider rejects (e.g. real-face detection), explain in plain language — refunds for provider rejections are automatic.`),
|
|
103
103
|
both(`- PROMPT IS LAW (reference doctrine): references are cited inline by name and image number ("Marcus (images 1 and 2)"); the prompt text leads. Never write role-essays about what each reference is "for".`),
|
|
104
|
+
both(`- NAMES AND DESCRIPTIONS ARE THE USER'S UI, NOT YOUR NOTEPAD. A project, storyboard or shot name is rendered at the top of the user's screen at all times. Name it after the piece ("Kaiju selfie"), never after your process, and NEVER append your own status or housekeeping — no "shot list", no "v2", no "Written from IMG-A172 and IMG-A182", no "Delete freely", no "scratch". Leave the description empty unless the user gave you one worth keeping: a note-to-self at the top of the screen reads as part of the product, and the user has to look at it every day. Say that kind of thing in your reply to them instead.`),
|
|
104
105
|
both(`- RESOLUTION DEFAULT IS UNIFORM: 1080p on the best available video model. Do not crank resolution the user didn't ask for.`),
|
|
105
106
|
// ⛔ THE MODEL ROUTING BLOCK IS GONE FROM HERE, DELIBERATELY (2026-08-30).
|
|
106
107
|
//
|
|
@@ -131,6 +132,12 @@ export const HARD_RULES = [
|
|
|
131
132
|
// fabricate: what the generation LOOKS like.
|
|
132
133
|
fork(`- REAL NUMBERS ONLY — NEVER approximate, estimate, or recall any figure. Every cost, balance, count, or duration you state must be copied verbatim from a tool result IN THIS SESSION: generation costs come from the cost_credits field in generate/status results (whole credits); the balance comes from a fresh slates_get_credit_balance call at summary time (never arithmetic you did yourself); orchestration cost is displayed by the app automatically — NEVER state or estimate it. No "approx", no "~", no rounded guesses. A number you cannot point to in a tool result is a number you do not say.`, `- REAL NUMBERS ONLY — NEVER approximate, estimate, or recall any figure. Every cost, balance, count, or duration you state must be copied verbatim from a tool result IN THIS SESSION: generation costs come from the cost_credits field in generate/status results (whole credits); the balance comes from a fresh slates_get_credit_balance call at summary time (never arithmetic you did yourself). No "approx", no "~", no rounded guesses. A number you cannot point to in a tool result is a number you do not say. The same rule covers what you SAW: never describe how a generation looks unless you fetched it this session with slates_get_asset_image / slates_get_asset_video_frames.`),
|
|
133
134
|
both(`- MODEL IDS ARE FIXED: slates_generate_video takes exactly ${VIDEO_MODELS.join(' | ')}, with duration + videoResolution as separate params. slates_generate_audio takes exactly ${AUDIO_MODELS.join(' | ')}. Registry entries like "kling-v3-standard-8s" or "seed-audio-15s" are billing keys, not model ids.`),
|
|
135
|
+
// ONE LINE, NOT A SECTION. The counts themselves ride the op RESULT, where
|
|
136
|
+
// the agent is already reading — measured: a rule inlined into an op moved
|
|
137
|
+
// compliance 0/8 → 30/32, while the same guidance behind a guide fetch sat at
|
|
138
|
+
// 13% before and after. This sentence exists only to say the numbers are
|
|
139
|
+
// there and must be looked at; the craft is the skill.
|
|
140
|
+
both(`- READ THE VARIETY COUNTS BEFORE FIRING A SET: slates_list_shots and slates_get_storyboard_with_frames return the distribution (shot sizes, camera moves, runs of three or more) with the rows. If one bucket is the plurality, fix the board before you spend — slates-shot-variety is the craft.`),
|
|
134
141
|
both(`- AUDIO LENGTH IS PROMPT-DRIVEN ON SEED AUDIO: it has no duration parameter, so the durationSeconds you pass is written into the prompt AND is what the user is charged, whatever comes back. Choose it deliberately and load slates-prompting-seed-audio before the first call.`),
|
|
135
142
|
];
|
|
136
143
|
// ── Surface-only footers ───────────────────────────────────────────
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A readable label for a generated asset, from its prompt.
|
|
3
|
+
*
|
|
4
|
+
* Strips a reference preamble, then takes the first CLAUSE rather than a fixed
|
|
5
|
+
* character slice — so the label ends on a word instead of mid-syllable.
|
|
6
|
+
* Returns null for a prompt with nothing usable in it; the caller decides what
|
|
7
|
+
* to show instead (a Shot name, "Untitled").
|
|
8
|
+
*/
|
|
9
|
+
export declare function labelFromPrompt(prompt: string | null | undefined): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* The caption for one asset row, in priority order: the label it was given, the
|
|
12
|
+
* Shot it came from, then the prompt reduced to a clause.
|
|
13
|
+
*
|
|
14
|
+
* The Shot name sits second because an author who named a Shot chose that word
|
|
15
|
+
* on purpose, and it is the word on the row in the storyboard — so the gallery
|
|
16
|
+
* and the document call the same thing the same thing.
|
|
17
|
+
*/
|
|
18
|
+
export declare function assetCaption(a: {
|
|
19
|
+
label?: string | null;
|
|
20
|
+
shotName?: string | null;
|
|
21
|
+
prompt?: string | null;
|
|
22
|
+
}): string;
|
|
23
|
+
//# sourceMappingURL=asset-label.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// ASSET LABELS — what a generated thing is CALLED in a list.
|
|
3
|
+
//
|
|
4
|
+
// 🚨 THE FIRST 40 CHARACTERS OF A PROMPT IS NOT A LABEL, and on
|
|
5
|
+
// reference-driven work it is actively misleading: every one of those prompts
|
|
6
|
+
// opens with the same composed preamble, so a whole gallery reads
|
|
7
|
+
// "Reference image 1 is a character ide" — the same string on every row, which
|
|
8
|
+
// is worse than no label because it looks like information.
|
|
9
|
+
//
|
|
10
|
+
// One rule, two consumers: the op surface's `compactAsset` (what an agent reads
|
|
11
|
+
// back from `slates_list_assets`) and the desktop gallery caption. A second
|
|
12
|
+
// copy of this would drift the way every other hand-mirrored rule in this
|
|
13
|
+
// workspace has.
|
|
14
|
+
//
|
|
15
|
+
// Dependency-free LEAF, same as `shot-grammar`: the desktop RENDERER imports it
|
|
16
|
+
// directly, so it may never reach for `node:` anything.
|
|
17
|
+
// ============================================================
|
|
18
|
+
/**
|
|
19
|
+
* A composed prompt's reference preamble.
|
|
20
|
+
*
|
|
21
|
+
* The composer writes "Reference image 1 is a character identity sheet." — so
|
|
22
|
+
* the optional leading "Reference" has to be matched SEPARATELY from the noun,
|
|
23
|
+
* or the pattern only catches the bare "Image 1 is …" form and every real
|
|
24
|
+
* composed prompt walks straight past it. (It did, until the labels were
|
|
25
|
+
* exercised on actual prompts.)
|
|
26
|
+
*/
|
|
27
|
+
const REFERENCE_PREAMBLE_RE = /^\s*(?:reference\s*)?(?:image|video|audio|clip)?\s*\d*\s*is\b[^.;]*[.;]\s*/i;
|
|
28
|
+
/** A sentence boundary always ends the label. */
|
|
29
|
+
const SENTENCE_BOUNDARY_RE = /(?<=[.;:])\s/;
|
|
30
|
+
/**
|
|
31
|
+
* An em-dash ends it too — but only once there is enough label to be worth
|
|
32
|
+
* keeping. Composed prompts join a subject to its treatment with one, and the
|
|
33
|
+
* subject alone is the label; a hand-written "wide shot — the barn at dawn"
|
|
34
|
+
* would otherwise be cut down to "wide shot", which names nothing.
|
|
35
|
+
*/
|
|
36
|
+
const DASH_BOUNDARY_RE = /\s—\s/;
|
|
37
|
+
const MIN_BEFORE_DASH = 25;
|
|
38
|
+
const MAX = 60;
|
|
39
|
+
/**
|
|
40
|
+
* A readable label for a generated asset, from its prompt.
|
|
41
|
+
*
|
|
42
|
+
* Strips a reference preamble, then takes the first CLAUSE rather than a fixed
|
|
43
|
+
* character slice — so the label ends on a word instead of mid-syllable.
|
|
44
|
+
* Returns null for a prompt with nothing usable in it; the caller decides what
|
|
45
|
+
* to show instead (a Shot name, "Untitled").
|
|
46
|
+
*/
|
|
47
|
+
export function labelFromPrompt(prompt) {
|
|
48
|
+
if (!prompt)
|
|
49
|
+
return null;
|
|
50
|
+
const body = (prompt.replace(REFERENCE_PREAMBLE_RE, '').trim() || prompt).trim();
|
|
51
|
+
let clause = body.split(SENTENCE_BOUNDARY_RE)[0].trim();
|
|
52
|
+
const dash = clause.split(DASH_BOUNDARY_RE)[0].trim();
|
|
53
|
+
if (dash.length >= MIN_BEFORE_DASH)
|
|
54
|
+
clause = dash;
|
|
55
|
+
if (!clause)
|
|
56
|
+
return null;
|
|
57
|
+
return clause.length > MAX ? `${clause.slice(0, MAX - 1).trimEnd()}…` : clause;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The caption for one asset row, in priority order: the label it was given, the
|
|
61
|
+
* Shot it came from, then the prompt reduced to a clause.
|
|
62
|
+
*
|
|
63
|
+
* The Shot name sits second because an author who named a Shot chose that word
|
|
64
|
+
* on purpose, and it is the word on the row in the storyboard — so the gallery
|
|
65
|
+
* and the document call the same thing the same thing.
|
|
66
|
+
*/
|
|
67
|
+
export function assetCaption(a) {
|
|
68
|
+
return a.label?.trim() || a.shotName?.trim() || labelFromPrompt(a.prompt) || 'Untitled';
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=asset-label.js.map
|
|
@@ -10,8 +10,20 @@ export interface BannedToken {
|
|
|
10
10
|
/** Every banned token, in skill-document order. Integrity asserted at load. */
|
|
11
11
|
export declare const BANNED_PROMPT_TOKENS: readonly BannedToken[];
|
|
12
12
|
export declare function bannedTokensFor(scope: BannedTokenScope): readonly BannedToken[];
|
|
13
|
-
/** The
|
|
14
|
-
export declare function
|
|
13
|
+
/** The never-use list a single skill declares. Empty for a skill with no block. */
|
|
14
|
+
export declare function bannedTokensForSkill(skill: string): readonly BannedToken[];
|
|
15
|
+
/**
|
|
16
|
+
* A single model's never-use list, for the estimate RESULT.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately NOT the cross-model list — that one already rides the op
|
|
19
|
+
* description on every call. This is the half that could not: the quirk that is
|
|
20
|
+
* true of Veo and false of Kling.
|
|
21
|
+
*/
|
|
22
|
+
export declare function describeBannedTokensForSkill(skill: string): string;
|
|
23
|
+
/** The tokens a submitted prompt actually contains. `skill` adds that model's
|
|
24
|
+
* own list to the modality-wide one — the two overlap for nano-banana-2 and
|
|
25
|
+
* seedance, so hits are deduplicated by token. */
|
|
26
|
+
export declare function findBannedTokens(prompt: string, scope: BannedTokenScope, skill?: string): BannedToken[];
|
|
15
27
|
/**
|
|
16
28
|
* The list as it appears INSIDE an op description — always in context, on both
|
|
17
29
|
* surfaces, with no call required to see it. Generated, never hand-typed.
|
|
@@ -24,5 +36,5 @@ export declare function describeBannedTokens(scope: BannedTokenScope): string;
|
|
|
24
36
|
* rather than raised as an error. The generation proceeds either way: the
|
|
25
37
|
* sandbox doctrine says make state visible, never block.
|
|
26
38
|
*/
|
|
27
|
-
export declare function bannedTokenWarning(prompt: string, scope: BannedTokenScope): string;
|
|
39
|
+
export declare function bannedTokenWarning(prompt: string, scope: BannedTokenScope, skill?: string): string;
|
|
28
40
|
//# sourceMappingURL=banned-tokens.d.ts.map
|
|
@@ -30,18 +30,52 @@
|
|
|
30
30
|
// ============================================================
|
|
31
31
|
import { SKILLS } from '../skills/content.js';
|
|
32
32
|
/**
|
|
33
|
-
* Where each scope's list lives.
|
|
33
|
+
* Where each scope's CROSS-MODEL list lives.
|
|
34
34
|
*
|
|
35
35
|
* One skill per scope on purpose: these are the two lists that are GENERIC to
|
|
36
36
|
* their modality (Stable-Diffusion-era tag soup for images, quality
|
|
37
|
-
* incantations for video), not model-specific quirks. A per-model list
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* incantations for video), not model-specific quirks. A per-model list cannot
|
|
38
|
+
* ride the op DESCRIPTION — a description is one static string for every call,
|
|
39
|
+
* so it cannot change with the `model` argument. That is what
|
|
40
|
+
* `bannedTokensForSkill` below is for: the per-model list rides the estimate
|
|
41
|
+
* RESULT, where the model has just been named.
|
|
40
42
|
*/
|
|
41
43
|
const BANNED_TOKEN_SOURCES = [
|
|
42
44
|
{ skill: 'slates-prompting-nano-banana-2', scope: 'image' },
|
|
43
45
|
{ skill: 'slates-prompting-seedance', scope: 'video' },
|
|
44
46
|
];
|
|
47
|
+
/**
|
|
48
|
+
* 🚨 THE ENFORCEMENT THAT WORKED COVERED TWO SKILLS OF FIFTEEN.
|
|
49
|
+
*
|
|
50
|
+
* `describeBannedTokens('image')` was Nano Banana's list and `('video')` was
|
|
51
|
+
* Seedance's, so a Veo, Kling, LTX, MiniMax, FLUX, Seedream, GPT-Image or audio
|
|
52
|
+
* generation was matched against another model's never-use list and its own was
|
|
53
|
+
* enforced by nothing — while four skills (content-policy, lip-sync,
|
|
54
|
+
* minimax-h3, motion-transfer) carried never-use prose with no markers at all,
|
|
55
|
+
* which is a rule an LLM has to notice.
|
|
56
|
+
*
|
|
57
|
+
* Every skill that carries an `@banned` block now contributes to a per-skill
|
|
58
|
+
* list, delivered on the estimate result beside the craft card. The two above
|
|
59
|
+
* stay ALSO on the op descriptions, because a modality-wide list is true of
|
|
60
|
+
* every call that op can make.
|
|
61
|
+
*/
|
|
62
|
+
function extractPerSkill() {
|
|
63
|
+
const out = new Map();
|
|
64
|
+
for (const [skill, content] of Object.entries(SKILLS)) {
|
|
65
|
+
// Cheap pre-test: only pay the regex for files that carry the marker.
|
|
66
|
+
if (!content.includes('@banned:start'))
|
|
67
|
+
continue;
|
|
68
|
+
const scope = inferScope(skill);
|
|
69
|
+
out.set(skill, extractFromSkill(skill).map((token) => ({ token, skill, scope })));
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
/** Image-lane skills prompt for pixels; everything else is a time-based lane.
|
|
74
|
+
* Only used to tag a token for the warning text — the per-skill list is
|
|
75
|
+
* matched by SKILL, never by scope, so a wrong guess here cannot mis-enforce. */
|
|
76
|
+
function inferScope(skill) {
|
|
77
|
+
return /nano-banana|gpt-image|flux|seedream/.test(skill) ? 'image' : 'video';
|
|
78
|
+
}
|
|
45
79
|
const FENCE_RE = /<!--\s*@banned:start\s*-->([\s\S]*?)<!--\s*@banned:end\s*-->/g;
|
|
46
80
|
const HTML_COMMENT_RE = /<!--[\s\S]*?-->/g;
|
|
47
81
|
const BACKTICKED_RE = /`([^`\n]+)`/g;
|
|
@@ -115,9 +149,42 @@ for (const { token, skill } of BANNED_PROMPT_TOKENS) {
|
|
|
115
149
|
`matcherFor() the new shape.`);
|
|
116
150
|
}
|
|
117
151
|
}
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
|
|
152
|
+
/** Every per-skill list, keyed by skill name. */
|
|
153
|
+
const bySkill = extractPerSkill();
|
|
154
|
+
/** The never-use list a single skill declares. Empty for a skill with no block. */
|
|
155
|
+
export function bannedTokensForSkill(skill) {
|
|
156
|
+
return bySkill.get(skill) ?? [];
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* A single model's never-use list, for the estimate RESULT.
|
|
160
|
+
*
|
|
161
|
+
* Deliberately NOT the cross-model list — that one already rides the op
|
|
162
|
+
* description on every call. This is the half that could not: the quirk that is
|
|
163
|
+
* true of Veo and false of Kling.
|
|
164
|
+
*/
|
|
165
|
+
export function describeBannedTokensForSkill(skill) {
|
|
166
|
+
const list = bannedTokensForSkill(skill);
|
|
167
|
+
if (list.length === 0)
|
|
168
|
+
return '';
|
|
169
|
+
return (`NEVER put these in a ${skill.replace('slates-prompting-', '')} prompt: ` +
|
|
170
|
+
list.map((b) => `"${b.token}"`).join(', ') +
|
|
171
|
+
`. Describe specifically instead (${skill}).`);
|
|
172
|
+
}
|
|
173
|
+
/** The tokens a submitted prompt actually contains. `skill` adds that model's
|
|
174
|
+
* own list to the modality-wide one — the two overlap for nano-banana-2 and
|
|
175
|
+
* seedance, so hits are deduplicated by token. */
|
|
176
|
+
export function findBannedTokens(prompt, scope, skill) {
|
|
177
|
+
const candidates = [...bannedTokensFor(scope), ...(skill ? bannedTokensForSkill(skill) : [])];
|
|
178
|
+
const seen = new Set();
|
|
179
|
+
const hits = [];
|
|
180
|
+
for (const b of candidates) {
|
|
181
|
+
if (seen.has(b.token))
|
|
182
|
+
continue;
|
|
183
|
+
seen.add(b.token);
|
|
184
|
+
if (matcherFor(b.token).test(prompt))
|
|
185
|
+
hits.push(b);
|
|
186
|
+
}
|
|
187
|
+
return hits;
|
|
121
188
|
}
|
|
122
189
|
/**
|
|
123
190
|
* The list as it appears INSIDE an op description — always in context, on both
|
|
@@ -139,8 +206,8 @@ export function describeBannedTokens(scope) {
|
|
|
139
206
|
* rather than raised as an error. The generation proceeds either way: the
|
|
140
207
|
* sandbox doctrine says make state visible, never block.
|
|
141
208
|
*/
|
|
142
|
-
export function bannedTokenWarning(prompt, scope) {
|
|
143
|
-
const hits = findBannedTokens(prompt, scope);
|
|
209
|
+
export function bannedTokenWarning(prompt, scope, skill) {
|
|
210
|
+
const hits = findBannedTokens(prompt, scope, skill);
|
|
144
211
|
if (hits.length === 0)
|
|
145
212
|
return '';
|
|
146
213
|
const skills = [...new Set(hits.map((b) => b.skill))].join(', ');
|
|
@@ -13,8 +13,6 @@
|
|
|
13
13
|
* removal and must be SCOPED TO THE FACE rather than to the whole body.
|
|
14
14
|
*/
|
|
15
15
|
export declare const CHARACTER_SHEET_PANELS_DESC: string;
|
|
16
|
-
/** Panel identifiers, in sheet order. */
|
|
17
|
-
export declare const BODY_POSE_LABELS: readonly ["portrait", "front", "back"];
|
|
18
16
|
/**
|
|
19
17
|
* The character identity sheet — one asset, three panels.
|
|
20
18
|
*
|
|
@@ -139,8 +139,6 @@ export const CHARACTER_SHEET_PANELS_DESC = 'a large chest-up portrait on the lef
|
|
|
139
139
|
'a full-body front view in a relaxed A-pose in the centre, cropped at the collarbone — ' +
|
|
140
140
|
'an invisible-mannequin presentation with just the face cropped out, ' +
|
|
141
141
|
'and a full-body back view on the right with the head and hair fully visible';
|
|
142
|
-
/** Panel identifiers, in sheet order. */
|
|
143
|
-
export const BODY_POSE_LABELS = ['portrait', 'front', 'back'];
|
|
144
142
|
// The sheet's style directive: a user transform REPLACES the inherit-source
|
|
145
143
|
// instruction (so the model isn't told to both preserve the medium AND change
|
|
146
144
|
// it); otherwise inherit the source medium.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hard ceiling per card, in characters.
|
|
3
|
+
*
|
|
4
|
+
* A card is a CARD. The largest skill is 5,736 words and loading it whole is
|
|
5
|
+
* exactly the cost this mechanism exists to avoid — if a card needs more than
|
|
6
|
+
* this, the extra belongs in the body of the skill, which is one
|
|
7
|
+
* `slates_get_prompting_guide` call away. Asserted at load, so an over-long
|
|
8
|
+
* card fails the build rather than quietly inflating every estimate result.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CRAFT_CARD_CEILING = 2400;
|
|
11
|
+
/** Every card, keyed by skill name. Built once at load; integrity asserted. */
|
|
12
|
+
export declare const CRAFT_CARDS: Readonly<Record<string, string>>;
|
|
13
|
+
/** The card for a skill, or null when that skill carries none. */
|
|
14
|
+
export declare function craftCard(skill: string): string | null;
|
|
15
|
+
/**
|
|
16
|
+
* The card as it appears in an op RESULT: the body, plus one line naming where
|
|
17
|
+
* the rest lives so the agent knows the card is a summary and not the guide.
|
|
18
|
+
*/
|
|
19
|
+
export declare function describeCraftCard(skill: string): string;
|
|
20
|
+
//# sourceMappingURL=craft-cards.d.ts.map
|