@slatesvideo/shared 0.7.1 → 0.7.3
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/clients/cloud.d.ts +4 -0
- package/dist/clients/cloud.js +11 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/manual/content.d.ts +1 -1
- package/dist/manual/content.js +1 -1
- package/dist/manual/index.d.ts +11 -2
- package/dist/manual/index.js +178 -14
- package/dist/operations/index.d.ts +292 -94
- package/dist/operations/index.js +870 -199
- package/dist/operations/surface.d.ts +6 -2
- package/dist/operations/surface.js +35 -5
- package/dist/prompts/agent-doctrine.d.ts +4 -4
- package/dist/prompts/agent-doctrine.js +18 -29
- package/dist/prompts/generation-policy.d.ts +1 -1
- package/dist/prompts/guide-discovery.d.ts +23 -0
- package/dist/prompts/guide-discovery.js +39 -0
- package/dist/prompts/guide-retrieval.js +1 -1
- package/dist/prompts/model-capabilities.d.ts +8 -9
- package/dist/prompts/model-capabilities.js +11 -51
- package/dist/prompts/model-facts.d.ts +2 -2
- package/dist/prompts/model-facts.js +15 -26
- package/dist/prompts/partials.generated.js +6 -3
- package/dist/prompts/prompting-tips.d.ts +1 -1
- package/dist/prompts/prompting-tips.js +21 -63
- package/dist/prompts/search-terms.d.ts +3 -0
- package/dist/prompts/search-terms.js +24 -0
- package/dist/skills/content.js +36 -37
- package/dist/skills/metadata.d.ts +7 -0
- package/dist/skills/metadata.js +29 -0
- package/exports/slates-chatgpt-images/generated/SKILL.md +7 -1
- package/exports/slates-chatgpt-images/generated/slates-chatgpt-images.skill +0 -0
- package/exports/slates-prompt-builder/generated/SKILL.md +28 -16
- package/exports/slates-prompt-builder/generated/reference-character.md +12 -13
- package/exports/slates-prompt-builder/generated/reference-content-policy.md +2 -2
- package/exports/slates-prompt-builder/generated/reference-gpt-image-2-5.md +191 -0
- package/exports/slates-prompt-builder/generated/reference-kling.md +32 -11
- package/exports/slates-prompt-builder/generated/reference-nano-banana.md +24 -6
- package/exports/slates-prompt-builder/generated/reference-omni-flash.md +65 -0
- package/exports/slates-prompt-builder/generated/reference-seedance-2-5.md +362 -0
- package/exports/slates-prompt-builder/generated/reference-seedance.md +34 -4
- package/exports/slates-prompt-builder/generated/slates-prompt-builder-manifest.json +77 -23
- package/exports/slates-prompt-builder/generated/slates-prompt-builder.skill +0 -0
- package/package.json +2 -1
- package/skills/_partials/blender-action-curves.md +24 -0
- package/skills/_partials/cinematic-card.md +1 -1
- package/skills/_partials/iteration-diagnosis.md +5 -0
- package/skills/_partials/model-routing.md +35 -0
- package/skills/_partials/seedance-25-timestamps.md +2 -2
- package/skills/_partials/still-gate.md +2 -2
- package/skills/_partials/thresholds.md +1 -1
- package/skills/slates-blocking-to-prompt.md +15 -13
- package/skills/slates-camera-language.md +45 -7
- package/skills/slates-character-identity.md +8 -6
- package/skills/slates-chatgpt-images.md +7 -1
- package/skills/slates-cinematic-look.md +1 -1
- package/skills/slates-content-policy.md +4 -6
- package/skills/slates-cost-discipline.md +18 -12
- package/skills/slates-dialogue-blocking.md +6 -6
- package/skills/slates-direct-response-ad.md +1 -1
- package/skills/slates-edit-and-iterate.md +12 -4
- package/skills/slates-model-selection.md +82 -90
- package/skills/slates-one-prompt-film.md +1 -1
- package/skills/slates-previs-blocking.md +44 -13
- package/skills/slates-project-organization.md +2 -2
- package/skills/slates-prompting-elevenlabs.md +4 -4
- package/skills/slates-prompting-flux-2-max.md +2 -3
- package/skills/slates-prompting-gpt-image-2-5.md +2 -2
- package/skills/slates-prompting-inworld-tts.md +1 -1
- package/skills/slates-prompting-kling-v3.md +11 -9
- package/skills/slates-prompting-lip-sync.md +15 -15
- package/skills/slates-prompting-ltx-2-5.md +5 -6
- package/skills/slates-prompting-minimax-h3.md +11 -11
- package/skills/slates-prompting-motion-transfer.md +8 -8
- package/skills/slates-prompting-nano-banana-2.md +8 -4
- package/skills/slates-prompting-omni-flash.md +9 -9
- package/skills/slates-prompting-seed-audio.md +24 -4
- package/skills/slates-prompting-seedance-2-5.md +40 -30
- package/skills/slates-prompting-seedance.md +4 -4
- package/skills/slates-prompting-seedream-5-lite.md +6 -6
- package/skills/slates-restyle-from-blocking.md +2 -2
- package/skills/slates-script-craft.md +1 -1
- package/skills/slates-shot-variety.md +1 -1
- package/skills/slates-storyboard-from-script.md +1 -1
- package/skills/slates-style-prompting.md +8 -6
- package/skills/slates-ugc-influencer-ad.md +1 -1
- package/skills/slates-vision-feedback-loop.md +118 -110
- package/skills/slates-prompting-veo-3.md +0 -224
|
@@ -61,12 +61,16 @@ export declare function toolDefinition(op: SurfaceOp): ToolDefinition;
|
|
|
61
61
|
* Render a tool surface.
|
|
62
62
|
*
|
|
63
63
|
* `desktop` sends `core` plus whatever groups have been loaded this run; `mcp`
|
|
64
|
-
* renders
|
|
65
|
-
* `--tools=compact`.
|
|
64
|
+
* renders every definition for the fixed MCP list.
|
|
66
65
|
*/
|
|
67
66
|
export declare function toolDefinitions(ops: readonly SurfaceOp[], opts: {
|
|
68
67
|
surface: 'desktop' | 'mcp';
|
|
69
68
|
groups?: readonly OperationGroup[];
|
|
70
69
|
names?: readonly string[];
|
|
71
70
|
}): ToolDefinition[];
|
|
71
|
+
/** Rank exact task words by rarity across the current registry, returning at most ten. */
|
|
72
|
+
export declare function searchTools<T extends Pick<SurfaceOp, 'id' | 'description'>>(operations: readonly T[], query: string): Array<{
|
|
73
|
+
op: T;
|
|
74
|
+
score: number;
|
|
75
|
+
}>;
|
|
72
76
|
//# sourceMappingURL=surface.d.ts.map
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
// mechanism: we build every turn there, so a load reaches the model. The
|
|
16
16
|
// MCP server lists every op and leaves hiding definitions to the host
|
|
17
17
|
// (tiering there left Claude and Codex users unable to generate in 0.6.0;
|
|
18
|
-
// see the TOOLS comment in packages/mcp/src/server.ts).
|
|
19
|
-
// is the opt-in exception.
|
|
18
|
+
// see the TOOLS comment in packages/mcp/src/server.ts).
|
|
20
19
|
//
|
|
21
20
|
// 3. ONE SCHEMA RENDERER. The desktop rendered `$refStrategy: 'none'` and
|
|
22
21
|
// the MCP server rendered `target: 'openApi3'`, so "the two surfaces
|
|
@@ -29,6 +28,7 @@
|
|
|
29
28
|
// an op whose `run` body posts is a lie that lets a host auto-approve a
|
|
30
29
|
// mutation, so the check has to be able to catch it, and it is mutation-tested.
|
|
31
30
|
// ============================================================
|
|
31
|
+
import { searchTerms } from '../prompts/search-terms.js';
|
|
32
32
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
33
33
|
// ── 1. Annotations ──────────────────────────────────────────────────────────
|
|
34
34
|
/** Ops that only read. Prefixes, because the verb IS the first word of the id. */
|
|
@@ -145,6 +145,9 @@ export const OPERATION_GROUPS = {
|
|
|
145
145
|
'slates_relocate_project',
|
|
146
146
|
'slates_undo_relocate_project',
|
|
147
147
|
'slates_reveal_file',
|
|
148
|
+
'slates_reorder_folders',
|
|
149
|
+
'slates_reorder_pins',
|
|
150
|
+
'slates_link_asset_source',
|
|
148
151
|
],
|
|
149
152
|
// The cut and the export. A generation session never touches these.
|
|
150
153
|
script: ['slates_get_script_document', 'slates_update_script_document', 'slates_get_script_sections', 'slates_update_script_section', 'slates_get_script_suggestions', 'slates_update_script_suggestions', 'slates_get_script_uses', 'slates_preview_script_variation', 'slates_create_script_variation', 'slates_get_shot_inputs', 'slates_reuse_shot_take'],
|
|
@@ -182,6 +185,9 @@ export const OPERATION_GROUPS = {
|
|
|
182
185
|
'slates_duplicate_shot',
|
|
183
186
|
'slates_split_shot',
|
|
184
187
|
'slates_merge_shots',
|
|
188
|
+
'slates_get_usage',
|
|
189
|
+
'slates_get_app_settings',
|
|
190
|
+
'slates_set_app_settings',
|
|
185
191
|
],
|
|
186
192
|
// A third transport nobody without Blender installed can reach.
|
|
187
193
|
blender: [
|
|
@@ -215,7 +221,7 @@ export function tierFor(id) {
|
|
|
215
221
|
export const GROUP_SUMMARY = {
|
|
216
222
|
library: 'folders, the Library (user-named categories of saved references: characters, locations, products, looks), moving and copying assets and Library items between projects, moving a project into the current projects folder, revealing files on disk',
|
|
217
223
|
script: 'rich script documents, anchored sections and their saved versions, reusable passages, variations and take input history',
|
|
218
|
-
timeline: 'named cuts and selected exports; the timeline (tracks, clips, markers, settings), video export and export for DaVinci
|
|
224
|
+
timeline: 'named cuts and selected exports; the timeline (tracks, clips, markers, settings), video export and FCP7 XML export for DaVinci Resolve or Premiere, clip trimming',
|
|
219
225
|
admin: 'rename / delete / reorder for projects, characters, locations, looks, boards, scenes and frames; shot duplicate, split and merge',
|
|
220
226
|
blender: 'the Blender previs bridge — scene inspection, bpy execution, API docs, grey-box render',
|
|
221
227
|
};
|
|
@@ -241,8 +247,7 @@ export function toolDefinition(op) {
|
|
|
241
247
|
* Render a tool surface.
|
|
242
248
|
*
|
|
243
249
|
* `desktop` sends `core` plus whatever groups have been loaded this run; `mcp`
|
|
244
|
-
* renders
|
|
245
|
-
* `--tools=compact`.
|
|
250
|
+
* renders every definition for the fixed MCP list.
|
|
246
251
|
*/
|
|
247
252
|
export function toolDefinitions(ops, opts) {
|
|
248
253
|
const loaded = new Set(opts.groups ?? []);
|
|
@@ -255,4 +260,29 @@ export function toolDefinitions(ops, opts) {
|
|
|
255
260
|
})
|
|
256
261
|
.map(toolDefinition);
|
|
257
262
|
}
|
|
263
|
+
// Natural brief words for capabilities whose API names use technical terms.
|
|
264
|
+
const TASK_ALIASES = {
|
|
265
|
+
slates_generate_video: 'create animate photo picture film movie ad advert advertisement',
|
|
266
|
+
slates_generate_from_shots: 'create make film movie ad advert advertisement',
|
|
267
|
+
slates_generate_image: 'photo picture illustration',
|
|
268
|
+
slates_edit_image: 'photo picture',
|
|
269
|
+
slates_use_pictures_as_first_frames: 'photo animate',
|
|
270
|
+
slates_generate_audio: 'voiceover narration speech ambience sound effect',
|
|
271
|
+
};
|
|
272
|
+
/** Rank exact task words by rarity across the current registry, returning at most ten. */
|
|
273
|
+
export function searchTools(operations, query) {
|
|
274
|
+
const terms = searchTerms(query);
|
|
275
|
+
const documents = operations.map(op => ({
|
|
276
|
+
op,
|
|
277
|
+
name: new Set(searchTerms(op.id)),
|
|
278
|
+
words: new Set(searchTerms(`${op.description} ${TASK_ALIASES[op.id] ?? ''}`)),
|
|
279
|
+
}));
|
|
280
|
+
const frequency = new Map(terms.map(term => [term, documents.filter(doc => doc.name.has(term) || doc.words.has(term)).length]));
|
|
281
|
+
return documents.map(doc => ({
|
|
282
|
+
op: doc.op,
|
|
283
|
+
score: terms.reduce((sum, term) => sum + (doc.name.has(term) || doc.words.has(term)
|
|
284
|
+
? Math.log(1 + documents.length / (1 + (frequency.get(term) ?? 0))) * (doc.name.has(term) ? 2 : 1)
|
|
285
|
+
: 0), 0),
|
|
286
|
+
})).filter(match => match.score > 0).sort((a, b) => b.score - a.score || a.op.id.localeCompare(b.op.id)).slice(0, 10);
|
|
287
|
+
}
|
|
258
288
|
//# sourceMappingURL=surface.js.map
|
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
* `desktop` — the in-app Studio Agent. Its loop enforces plan approval in
|
|
5
5
|
* code, auto-polls generation status, and displays orchestration cost itself.
|
|
6
6
|
* `mcp` — Claude Code / Claude Desktop / Cursor / Codex over stdio. No
|
|
7
|
-
* `present_plan` tool, no auto-poll, no app chrome
|
|
8
|
-
* op-level `requires_confirm`
|
|
9
|
-
* tool approval
|
|
7
|
+
* `present_plan` tool, no auto-poll, no app chrome. The user approves the
|
|
8
|
+
* itemized spend in chat; op-level `requires_confirm` is a threshold backstop.
|
|
9
|
+
* Host tool approval depends on the client. See slates-mcp/CLAUDE.md.
|
|
10
10
|
*/
|
|
11
11
|
export type AgentSurface = 'desktop' | 'mcp';
|
|
12
12
|
interface SkillIndexEntry {
|
|
13
13
|
name: string;
|
|
14
14
|
description: string;
|
|
15
15
|
}
|
|
16
|
-
/**
|
|
16
|
+
/** Generated from the same validated metadata used by discovery and installation. */
|
|
17
17
|
export declare function buildSkillIndex(): SkillIndexEntry[];
|
|
18
18
|
export declare const WORKING_METHOD: ReadonlyArray<Record<AgentSurface, string>>;
|
|
19
19
|
export declare const HARD_RULES: ReadonlyArray<Record<AgentSurface, string>>;
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
// the whole embedded SKILLS record. Root barrel only.
|
|
51
51
|
// ============================================================
|
|
52
52
|
import { SKILLS } from '../skills/content.js';
|
|
53
|
+
import { guideCatalog } from './guide-discovery.js';
|
|
53
54
|
import { VIDEO_MODELS, AUDIO_MODELS } from '../operations/index.js';
|
|
54
55
|
/** A line that is identical on both surfaces. The default. */
|
|
55
56
|
function both(text) {
|
|
@@ -59,21 +60,9 @@ function both(text) {
|
|
|
59
60
|
function fork(desktop, mcp) {
|
|
60
61
|
return { desktop, mcp };
|
|
61
62
|
}
|
|
62
|
-
/**
|
|
63
|
+
/** Generated from the same validated metadata used by discovery and installation. */
|
|
63
64
|
export function buildSkillIndex() {
|
|
64
|
-
|
|
65
|
-
for (const key of Object.keys(SKILLS).sort()) {
|
|
66
|
-
const content = SKILLS[key];
|
|
67
|
-
const fm = /^---\n([\s\S]*?)\n---/.exec(content);
|
|
68
|
-
let description = '';
|
|
69
|
-
if (fm) {
|
|
70
|
-
const m = /^description:\s*(.+)$/m.exec(fm[1]);
|
|
71
|
-
if (m)
|
|
72
|
-
description = m[1].trim().replace(/^['"]|['"]$/g, '');
|
|
73
|
-
}
|
|
74
|
-
entries.push({ name: key, description });
|
|
75
|
-
}
|
|
76
|
-
return entries;
|
|
65
|
+
return guideCatalog(SKILLS).map(({ name, description }) => ({ name, description }));
|
|
77
66
|
}
|
|
78
67
|
// ── Preamble ───────────────────────────────────────────────────────
|
|
79
68
|
// FORKED, and the MCP side is a SUMMARY THAT MUST FIT IN THE CUT. Claude Code
|
|
@@ -83,35 +72,35 @@ export function buildSkillIndex() {
|
|
|
83
72
|
// find a tool when the host shows names only, the spend gate, real numbers, the
|
|
84
73
|
// current project — sits here; the sections below expand it for clients that
|
|
85
74
|
// read everything. mcp-instructions-smoke asserts these lines land inside the
|
|
86
|
-
// cut
|
|
87
|
-
const PREAMBLE = fork(`You are the Slates Studio Agent — a production assistant living inside Slates, the AI video creation studio.
|
|
75
|
+
// cut when UPDATE AVAILABLE follows the first doctrine paragraph.
|
|
76
|
+
const PREAMBLE = fork(`You are the Slates Studio Agent — a production assistant living inside Slates, the AI video creation studio. Turn the user's vision into finished work using the optional Slates tools and relevant production craft. Choose the workflow for the brief; start from the user's existing material and bring results into their project or timeline when asked.`, `Turn the user's vision into finished work in Slates. Find craft with slates_get_prompting_guide(query: the brief); reuse current guidance. Tools generate real media and spend credits. Before generation, estimate every step, show the itemized total and wait for approval. Work in the current project. Choose tools to fit the brief.
|
|
88
77
|
|
|
89
78
|
## Essentials (the sections below expand on these)
|
|
90
79
|
|
|
91
|
-
- FINDING TOOLS: every Slates capability is its own slates_* tool. If your client shows tool names only, search your tools for the task (for example "slates generate video") and load that tool before calling it.
|
|
80
|
+
- FINDING TOOLS: every Slates capability is its own slates_* tool. If your client shows tool names only, search your tools for the task (for example "slates generate video") and load that tool before calling it. slates_load_tools searches Slates tools by task and returns exact schemas.
|
|
92
81
|
- SPENDING: before ANY generation, price every step with slates_estimate_generation_cost, show the user the itemized total in ONE message, and wait for their OK. Pass confirm: true only to relay an explicit user OK for that exact spend.
|
|
93
82
|
- REAL NUMBERS ONLY: every cost, balance or count you state is copied from a tool result in this session. Never describe how a generation looks unless you fetched it this session.
|
|
94
83
|
- PROJECT: call slates_get_workspace_state once, work in the user's current project, and never create a project unless asked.
|
|
95
|
-
- CRAFT:
|
|
84
|
+
- CRAFT: discover relevant guides with slates_get_prompting_guide(query: the brief). Reuse current cards and guidance; fetch sections for missing craft. For UI help, use topic "app-manual".
|
|
96
85
|
- Speak in the app's words and name assets by code and label (IMG-A12), never by tool name or UUID.`);
|
|
97
86
|
// ── The working method ─────────────────────────────────────────────
|
|
98
87
|
export const WORKING_METHOD = [
|
|
99
|
-
both(`For HOW/WHERE questions, load slates_get_prompting_guide with topic "app-manual" and relevant query keywords. Teach the documented buttons and tabs, preserving model-specific conditions; do not invent UI paths or mutate the project when the user only asks for instructions. Slates is a sandbox of optional tools, not a required pipeline.`),
|
|
88
|
+
both(`For HOW/WHERE questions, load slates_get_prompting_guide with topic "app-manual" and relevant query keywords. Teach the documented buttons and tabs, preserving model-specific conditions; do not invent UI paths or mutate the project when the user only asks for instructions. When the control is on screen, offer to point at it (slates_highlight_control); show a picture of a screen (slates_get_manual_picture) only when the user cannot find something or asks what it looks like. Slates is a sandbox of optional tools, not a required pipeline.`),
|
|
100
89
|
both(`1. UNDERSTAND the outcome the user wants. If intent is clear, act with sane defaults — don't interrogate. If genuinely ambiguous, batch every question into ONE message.`),
|
|
101
90
|
both(`2. ORIENT: call slates_get_workspace_state once at the start of a workflow. Work in the user's CURRENT project — this chat lives inside it. NEVER create a new project unless explicitly asked; if there's no current project, ask which to use.`),
|
|
102
91
|
// FORKED: the desktop sends core tools plus what `slates_load_tools` loaded,
|
|
103
92
|
// so its agent must load before calling. The MCP server lists every tool and
|
|
104
93
|
// the host decides what to show (Essentials, FINDING TOOLS), so telling an MCP
|
|
105
94
|
// client to load first costs it a turn for a tool it already has.
|
|
106
|
-
fork(`3. LOAD KNOWLEDGE ON DEMAND: slates_get_prompting_guide returns a short card
|
|
95
|
+
fork(`3. LOAD KNOWLEDGE ON DEMAND: discover craft from the brief with slates_get_prompting_guide(query: the need); no topic returns ranked guides, no query returns the catalog. With a topic, read a short card or query a section/technique when needed. Use slates_load_tools with query to find a capability, then names to load its exact schema. A load replaces the previous optional selection. Before quoting a model, load its tool schema or routing guide. Use workspace generationDefaults when the user has no preference. Read the model card delivered by the estimate; fetch a section or full guide for an unfamiliar mode or missing detail. Reuse guidance already in context; retrieve it again when omitted or stale.`, `3. LOAD KNOWLEDGE ON DEMAND: discover craft from the brief with slates_get_prompting_guide(query: the need); no topic returns ranked guides, no query returns the catalog. With a topic, read a short card or query a section/technique when needed. Before quoting a model, read its tool schema or routing guide. Use workspace generationDefaults when the user has no preference. Read the model card delivered by the estimate; fetch a section or full guide for an unfamiliar mode or missing detail. Reuse guidance already in context; retrieve it again when omitted or stale.`),
|
|
107
96
|
// FORKED: `present_plan` is a loop-level DESKTOP tool, deliberately not in
|
|
108
97
|
// ALL_OPERATIONS, so MCP never sees it and has no plan gate at all. Its
|
|
109
|
-
// substitute is the
|
|
110
|
-
//
|
|
98
|
+
// substitute is the itemized quote and explicit user approval in chat,
|
|
99
|
+
// backed by per-op `requires_confirm`. Describing the desktop gate to an MCP
|
|
111
100
|
// client would name a tool that does not exist.
|
|
112
101
|
fork(`4. PLAN + GET APPROVAL: before ANY generation, call present_plan with itemized credit costs (slates_estimate_generation_cost per step). One approval covers the plan's listed steps ONLY. Generation tools are rejected without an approved plan — and any user revision, question, or new instruction after an approval means you MUST re-present the plan BEFORE the next generation call (calling a generation op first just gets BLOCKED and wastes a turn).`, `4. PLAN + GET APPROVAL: before ANY generation, price every step with slates_estimate_generation_cost and put the itemized total in front of the user in ONE message, then wait for their answer. There is no present_plan tool on this surface — consent is per call: a generation over the confirm threshold returns requires_confirm, and confirm: true is only ever a relay of an explicit user OK for that exact spend. Never pass confirm: true to clear a gate the user has not seen.`),
|
|
113
102
|
fork(`5. EXECUTE: after approval, pass confirm: true (the approval IS the consent — never re-ask per step). Use background: true + status polling for video.`, `5. EXECUTE: run the approved steps, passing confirm: true only for the spend the user actually OK'd. Use background: true + status polling for video.`),
|
|
114
|
-
both(`6. QUALITY-CHECK: you have vision.
|
|
103
|
+
both(`6. QUALITY-CHECK: you have vision. Inspect images or sampled video frames against the brief (slates-vision-feedback-loop). Frames establish visible appearance, not continuous motion, lip sync or sound: review those through actual playback or an audio-capable host when available; otherwise state they remain unreviewed. Fix real problems and change ONE variable per regeneration.`),
|
|
115
104
|
both(`7. REPORT: when done, summarize what was made and where it landed. Concise and concrete.`),
|
|
116
105
|
];
|
|
117
106
|
// ── Hard rules ─────────────────────────────────────────────────────
|
|
@@ -125,7 +114,7 @@ export const HARD_RULES = [
|
|
|
125
114
|
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".`),
|
|
126
115
|
both(`- NAMES AND DESCRIPTIONS ARE THE USER'S UI, NOT YOUR NOTEPAD. A project, board 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.`),
|
|
127
116
|
both(`- SAY THE APP'S WORDS. Tool and parameter names keep older nouns; the screen does not, and the user only ever sees the screen. Say board (not storyboard), location (not environment) and look (not style) for Library items, timeline (a saved version of it is a cut), version (a saved rewrite of a script section; not alternative), Words or Words + shots (the Script page's switch), the Generate panel (not the quote), and tab (Media, Script and Board; not lens). Describe what you did in plain words ("Checked your project", "Priced 6 clips"), never a tool or parameter name.`),
|
|
128
|
-
both(`- RESOLUTION DEFAULT
|
|
117
|
+
both(`- RESOLUTION DEFAULT: each model's own default. Do not crank resolution the user didn't ask for.`),
|
|
129
118
|
// ⛔ THE MODEL ROUTING BLOCK IS GONE FROM HERE, DELIBERATELY (2026-08-30).
|
|
130
119
|
//
|
|
131
120
|
// It was 17,700 characters — 47% of the whole doctrine — and every lane of it
|
|
@@ -141,9 +130,9 @@ export const HARD_RULES = [
|
|
|
141
130
|
// The old `buildModelRouting()` renderer was DELETED rather than left
|
|
142
131
|
// exported "in case": an export nothing calls is an export nothing keeps
|
|
143
132
|
// honest. `describeRouting()` in model-facts.ts is the renderer now.
|
|
144
|
-
both(`- MODEL KINDS: image, video and audio models are disjoint — no image model makes a video, no video model makes a standalone image, no image or video model makes audio. Which SEAT to pick inside a kind is on each generate op's own \`model\` description, and the full table is the slates-model-selection skill.`),
|
|
133
|
+
both(`- MODEL KINDS: image, video and audio models are disjoint — no image model makes a video, no video model makes a standalone image, no image or video model makes a standalone audio file (video models can generate sound inside the clip). Which SEAT to pick inside a kind is on each generate op's own \`model\` description, and the full table is the slates-model-selection skill.`),
|
|
145
134
|
both(`- ASSET CODES + STALENESS: every asset carries a badge code (IMG-A12 / VID-V3 / AUD-S1, top-left of its gallery card); speak about assets by code + label. When the user says "these" or "this one", call slates_get_selection rather than asking which. Asset lists go STALE — the user creates assets in the Slates UI mid-conversation. When the user names a code you have NOT seen in a tool result this session, resolve it with slates_list_assets (use the search filter) BEFORE using it. NEVER guess an asset id or reuse a nearby UUID — pass the exact id a tool returned for that exact code. A wrong start frame burns real credits.`),
|
|
146
|
-
both(`- CREDITS ONLY: every generation you drive bills Slates credits (your tool calls enforce this). Never suggest BYOK keys for agent work.`),
|
|
135
|
+
both(`- CREDITS ONLY: every generation you drive bills Slates credits (your tool calls enforce this), except ChatGPT images, which use the person's ChatGPT account. Never suggest BYOK keys for agent work.`),
|
|
147
136
|
both(`- Do not invent tools, asset ids, or credit prices. If a tool errors, read the error and fix that exact issue; don't repeat the same call unchanged and never switch models to route around a parameter mistake.`),
|
|
148
137
|
// FORKED: only the desktop loop auto-polls generation status
|
|
149
138
|
// (loop.ts → autoPollUntilTerminal). Telling an MCP client "the app keeps
|
|
@@ -166,12 +155,12 @@ export const HARD_RULES = [
|
|
|
166
155
|
// ── Surface-only footers ───────────────────────────────────────────
|
|
167
156
|
//
|
|
168
157
|
// MCP clients have no Slates chrome and may have no skill FILES installed
|
|
169
|
-
// (`slates install-skills` covers Claude Code;
|
|
170
|
-
//
|
|
158
|
+
// (`slates install-skills` covers Claude Code and Codex; other hosts may
|
|
159
|
+
// use different native skill locations). The desktop always has the embedded
|
|
171
160
|
// record, so this paragraph would be dead weight in its cached prefix.
|
|
172
161
|
const MCP_FOOTER = `## Working without skill files
|
|
173
162
|
|
|
174
|
-
If slates-* skill files are not installed in this client, every one of them is still reachable as data: call slates_get_prompting_guide with the skill name (or a model id — it resolves aliases).
|
|
163
|
+
If slates-* skill files are not installed in this client, every one of them is still reachable as data: call slates_get_prompting_guide with the skill name (or a model id — it resolves aliases). Query the brief or browse topic "catalog" for generated discovery and entitled member playbooks. "No skill installed" is never a reason to prompt a model blind.
|
|
175
164
|
`;
|
|
176
165
|
/**
|
|
177
166
|
* THE doctrine string for a surface. The desktop's whole system prompt and the
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* This is NOT a model capability or provider batch limit. Desktop mirror is
|
|
3
3
|
* generated by slate/scripts/sync-generation-policy.mjs; never edit it there. */
|
|
4
4
|
export declare const IMAGE_QUANTITIES: readonly [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
5
|
-
export declare const MAX_IMAGE_VARIATIONS: 1 | 2 |
|
|
5
|
+
export declare const MAX_IMAGE_VARIATIONS: 1 | 2 | 4 | 5 | 3 | 10 | 8 | 7 | 6 | 9;
|
|
6
6
|
export type ImageQuantity = typeof IMAGE_QUANTITIES[number];
|
|
7
7
|
export declare const clampImageQuantity: (count?: number) => ImageQuantity;
|
|
8
8
|
/** Default saved-recipe framing, shared by composer restore, quote and dispatch. */
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface GuideCatalogEntry {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
tier: 'free' | 'paid';
|
|
5
|
+
}
|
|
6
|
+
/** Metadata is the catalog; headings supply technique vocabulary without returning whole guides. */
|
|
7
|
+
export declare function guideCatalog(skills: Readonly<Record<string, string>>): GuideCatalogEntry[];
|
|
8
|
+
export declare function discoverGuides(entries: readonly GuideCatalogEntry[], skills: Readonly<Record<string, string>>, query?: string, limit?: number, offset?: number): {
|
|
9
|
+
query: string | null;
|
|
10
|
+
fallback: boolean;
|
|
11
|
+
total: number;
|
|
12
|
+
offset: number;
|
|
13
|
+
nextOffset: number | null;
|
|
14
|
+
guides: {
|
|
15
|
+
matched: string[];
|
|
16
|
+
sections: string[];
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
tier: "free" | "paid";
|
|
20
|
+
}[];
|
|
21
|
+
rest: GuideCatalogEntry[];
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=guide-discovery.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { parseSkillMetadata } from '../skills/metadata.js';
|
|
2
|
+
import { guideSections } from './guide-retrieval.js';
|
|
3
|
+
import { searchTerms } from './search-terms.js';
|
|
4
|
+
// Brief filler that matches guides by accident ("30 second", "how much", "I need").
|
|
5
|
+
const GUIDE_FILLER = ['create', 'make', 'use', 'video', 'image', 'need', 'how', 'much', 'turn', 'keep', 'get', 'like', 'just', 'about', 'every', 'second', 'minute'];
|
|
6
|
+
const words = (text) => searchTerms(text, GUIDE_FILLER).filter(word => !/^\d{1,2}$/.test(word));
|
|
7
|
+
/** Metadata is the catalog; headings supply technique vocabulary without returning whole guides. */
|
|
8
|
+
export function guideCatalog(skills) {
|
|
9
|
+
return Object.keys(skills).sort().map(name => ({ ...parseSkillMetadata(skills[name], name), tier: 'free' }));
|
|
10
|
+
}
|
|
11
|
+
export function discoverGuides(entries, skills, query, limit, offset = 0) {
|
|
12
|
+
const terms = words(query ?? '');
|
|
13
|
+
const documents = entries.map(entry => {
|
|
14
|
+
const sections = skills[entry.name] ? guideSections(skills[entry.name]) : [];
|
|
15
|
+
const metadata = new Set(words(`${entry.name.replace(/-/g, ' ')} ${entry.description}`));
|
|
16
|
+
const headings = sections.map(section => ({ heading: section.title, terms: new Set(words(section.title)) }));
|
|
17
|
+
return { entry, metadata, headings };
|
|
18
|
+
});
|
|
19
|
+
// Rare terms in the corpus carry more weight than generic production vocabulary.
|
|
20
|
+
const frequency = new Map(terms.map(term => [term, documents.filter(doc => doc.metadata.has(term) || doc.headings.some(section => section.terms.has(term))).length]));
|
|
21
|
+
const ranked = documents.map(doc => {
|
|
22
|
+
const matched = terms.filter(term => doc.metadata.has(term) || doc.headings.some(section => section.terms.has(term)));
|
|
23
|
+
const score = matched.reduce((sum, term) => sum + Math.log(1 + documents.length / (1 + (frequency.get(term) ?? 0))) * (doc.metadata.has(term) ? 3 : 1), 0);
|
|
24
|
+
const sections = doc.headings.filter(section => matched.some(term => section.terms.has(term))).slice(0, 3).map(section => section.heading);
|
|
25
|
+
return { ...doc.entry, matched, sections, score };
|
|
26
|
+
}).filter(entry => !terms.length || entry.score > 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
|
|
27
|
+
// An unfamiliar brief still exposes the catalog and its pagination; it never asks the user to route it.
|
|
28
|
+
const fallback = terms.length > 0 && ranked.length === 0;
|
|
29
|
+
const candidates = fallback ? documents.map(doc => ({ ...doc.entry, matched: [], sections: [], score: 0 })) : ranked;
|
|
30
|
+
// Browsing, or a brief with no keyword match, returns the whole catalog unless the caller pages it.
|
|
31
|
+
const size = limit ?? (terms.length && !fallback ? 8 : candidates.length);
|
|
32
|
+
const page = candidates.slice(offset, offset + size).map(({ score: _score, ...entry }) => entry);
|
|
33
|
+
// Keyword ranking only sees shared words, so the first page of a search also carries
|
|
34
|
+
// every other guide's description: the model chooses by the brief, as with native skills.
|
|
35
|
+
const shown = new Set(page.map(entry => entry.name));
|
|
36
|
+
const rest = terms.length && !fallback && offset === 0 ? entries.filter(entry => !shown.has(entry.name)) : [];
|
|
37
|
+
return { query: query ?? null, fallback, total: candidates.length, offset, nextOffset: offset + page.length < candidates.length ? offset + page.length : null, guides: page, rest };
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=guide-discovery.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { craftCard } from './craft-cards.js';
|
|
2
2
|
/** Parse headings outside code fences; maintainer comments never reach agents. */
|
|
3
3
|
export function guideSections(content) {
|
|
4
|
-
const clean = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '').
|
|
4
|
+
const clean = content.replace(/<!--[\s\S]*?-->/g, '').trimStart().replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '').trim();
|
|
5
5
|
const sections = [];
|
|
6
6
|
let current = { title: 'Overview', body: '' };
|
|
7
7
|
let fenced = false;
|
|
@@ -35,9 +35,9 @@ export interface DurationCapability {
|
|
|
35
35
|
mode: 'continuous' | 'discrete';
|
|
36
36
|
/** For discrete mode: the exact allowed durations. */
|
|
37
37
|
values?: number[];
|
|
38
|
-
/** Resolution-dependent narrowing (Veo
|
|
38
|
+
/** Resolution-dependent narrowing (retired Veo forced 8s at 1080p AND 4k). */
|
|
39
39
|
resolutionOverrides?: Record<string, Pick<DurationCapability, 'min' | 'max' | 'mode' | 'values'>>;
|
|
40
|
-
/** Prompt-mode narrowing (Veo's reference-to-video endpoint
|
|
40
|
+
/** Prompt-mode narrowing (retired Veo's reference-to-video endpoint was 8s only). */
|
|
41
41
|
modeOverrides?: Record<string, Pick<DurationCapability, 'min' | 'max' | 'mode' | 'values'>>;
|
|
42
42
|
}
|
|
43
43
|
/** Video resolution constraints. */
|
|
@@ -188,15 +188,14 @@ export interface ModelCapability {
|
|
|
188
188
|
voiceClone?: VoiceCloneCapability;
|
|
189
189
|
}
|
|
190
190
|
/**
|
|
191
|
-
* The provider every AGENT generation actually lands on for Kling
|
|
191
|
+
* The provider every AGENT generation actually lands on for Kling.
|
|
192
192
|
*
|
|
193
193
|
* 🚨 THIS IS WHY `providerAspectRatios` MATTERS TO THE OP. MCP/CLI/Studio-Agent
|
|
194
194
|
* generations are credits-only (BYOK is retired on the agent surface), and the
|
|
195
|
-
* credits route carries Kling
|
|
196
|
-
* never sends `klingProvider`, so `handlers/video.ts` defaults it to `'fal'
|
|
197
|
-
*
|
|
198
|
-
* (
|
|
199
|
-
* TWO — not the eight and ten those models take on their direct APIs. Validating
|
|
195
|
+
* credits route carries Kling on fal: `slate/src/main/agent/routes.ts`
|
|
196
|
+
* never sends `klingProvider`, so `handlers/video.ts` defaults it to `'fal'`.
|
|
197
|
+
* So an agent gets Kling's THREE fal ratios, not the eight it takes on its
|
|
198
|
+
* direct API (retired Veo had the same split, two against ten). Validating
|
|
200
199
|
* against the direct sets would accept a ratio fal rejects, which is the exact
|
|
201
200
|
* failure this module exists to delete.
|
|
202
201
|
*/
|
|
@@ -257,7 +256,7 @@ export declare function checkDuration(model: string, duration: number | undefine
|
|
|
257
256
|
export declare function describeAspectRatios(models: readonly string[], provider?: string): string;
|
|
258
257
|
/** e.g. "seedance-2: 480p, 720p, 1080p, 4k (default 1080p) · omni-flash: 720p only (fixed)" */
|
|
259
258
|
export declare function describeVideoResolutions(models: readonly string[]): string;
|
|
260
|
-
/** e.g. "kling-v3.0-std: 3-15s ·
|
|
259
|
+
/** e.g. "kling-v3.0-std: 3-15s · ltx-2-5: 6s/8s/10s/12s/14s/16s/18s/20s (1440p/4k: 6s/8s/10s only)" */
|
|
261
260
|
export declare function describeDurations(models: readonly string[]): string;
|
|
262
261
|
/** e.g. "seedance-2: 9 · seedance-2.5: 30 · omni-flash: 7 · seedance-2.5-edit: 0 (prompt + source clip only)" */
|
|
263
262
|
export declare function describeReferenceImageCaps(models: readonly string[]): string;
|
|
@@ -67,8 +67,6 @@ const KLING_DIRECT_ASPECT_RATIOS = [
|
|
|
67
67
|
];
|
|
68
68
|
/** Kling carried on fal: three. This is the set the CREDITS route uses. */
|
|
69
69
|
const KLING_FAL_ASPECT_RATIOS = ['16:9', '9:16', '1:1'];
|
|
70
|
-
/** Veo carried on fal: two. The credits route again — Veo direct takes all ten. */
|
|
71
|
-
const VEO_FAL_ASPECT_RATIOS = ['16:9', '9:16'];
|
|
72
70
|
/** Gemini Omni Flash (fal schema, 16:9 default): two. */
|
|
73
71
|
const OMNI_FLASH_ASPECT_RATIOS = ['16:9', '9:16'];
|
|
74
72
|
/** Seedance (both seats, and the edit row): six — notably NO `4:5`. */
|
|
@@ -91,7 +89,7 @@ const MINIMAX_H3_ASPECT_RATIOS = ['21:9', '16:9', '4:3', '1:1', '3:4', '9:16'];
|
|
|
91
89
|
/**
|
|
92
90
|
* LTX-2.5, all four integrated endpoints: TWO. Read off fal's live OpenAPI
|
|
93
91
|
* 2026-08-29 — `text-to-video/{fast,pro}` declare exactly `['16:9','9:16']`,
|
|
94
|
-
* the narrowest video set in the roster
|
|
92
|
+
* the narrowest video set in the roster.
|
|
95
93
|
*
|
|
96
94
|
* `image-to-video/{fast,pro}` additionally offer `auto` (follow the start
|
|
97
95
|
* frame). We never send it and it is not an `AspectRatio` in this vocabulary —
|
|
@@ -201,15 +199,14 @@ export function falImageSize(model, aspectRatio, resolution) {
|
|
|
201
199
|
return computeFalDimensions(ar, 4);
|
|
202
200
|
}
|
|
203
201
|
/**
|
|
204
|
-
* The provider every AGENT generation actually lands on for Kling
|
|
202
|
+
* The provider every AGENT generation actually lands on for Kling.
|
|
205
203
|
*
|
|
206
204
|
* 🚨 THIS IS WHY `providerAspectRatios` MATTERS TO THE OP. MCP/CLI/Studio-Agent
|
|
207
205
|
* generations are credits-only (BYOK is retired on the agent surface), and the
|
|
208
|
-
* credits route carries Kling
|
|
209
|
-
* never sends `klingProvider`, so `handlers/video.ts` defaults it to `'fal'
|
|
210
|
-
*
|
|
211
|
-
* (
|
|
212
|
-
* TWO — not the eight and ten those models take on their direct APIs. Validating
|
|
206
|
+
* credits route carries Kling on fal: `slate/src/main/agent/routes.ts`
|
|
207
|
+
* never sends `klingProvider`, so `handlers/video.ts` defaults it to `'fal'`.
|
|
208
|
+
* So an agent gets Kling's THREE fal ratios, not the eight it takes on its
|
|
209
|
+
* direct API (retired Veo had the same split, two against ten). Validating
|
|
213
210
|
* against the direct sets would accept a ratio fal rejects, which is the exact
|
|
214
211
|
* failure this module exists to delete.
|
|
215
212
|
*/
|
|
@@ -366,43 +363,6 @@ export const MODEL_CAPABILITIES = {
|
|
|
366
363
|
duration: { min: 3, max: 10, mode: 'continuous' },
|
|
367
364
|
maxIngredientImages: 0,
|
|
368
365
|
},
|
|
369
|
-
// ── Veo ────────────────────────────────────────────────────────────────────
|
|
370
|
-
'veo-3.1-fast': {
|
|
371
|
-
aspectRatios: FULL_ASPECT_RATIOS,
|
|
372
|
-
providerAspectRatios: { fal: VEO_FAL_ASPECT_RATIOS },
|
|
373
|
-
videoResolution: { options: ['720p', '1080p', '4k'] },
|
|
374
|
-
duration: {
|
|
375
|
-
min: 4, max: 8, mode: 'discrete',
|
|
376
|
-
values: [4, 6, 8],
|
|
377
|
-
// BOTH 1080p and 4k force 8s. The op said "4K only at 8s" and quoted 4s
|
|
378
|
-
// at 1080p, which the provider rejects.
|
|
379
|
-
resolutionOverrides: {
|
|
380
|
-
'1080p': { min: 8, max: 8, mode: 'discrete', values: [8] },
|
|
381
|
-
'4k': { min: 8, max: 8, mode: 'discrete', values: [8] },
|
|
382
|
-
},
|
|
383
|
-
modeOverrides: {
|
|
384
|
-
ingredients: { min: 8, max: 8, mode: 'discrete', values: [8] },
|
|
385
|
-
},
|
|
386
|
-
},
|
|
387
|
-
maxIngredientImages: 3,
|
|
388
|
-
},
|
|
389
|
-
'veo-3.1-standard': {
|
|
390
|
-
aspectRatios: FULL_ASPECT_RATIOS,
|
|
391
|
-
providerAspectRatios: { fal: VEO_FAL_ASPECT_RATIOS },
|
|
392
|
-
videoResolution: { options: ['720p', '1080p', '4k'] },
|
|
393
|
-
duration: {
|
|
394
|
-
min: 4, max: 8, mode: 'discrete',
|
|
395
|
-
values: [4, 6, 8],
|
|
396
|
-
resolutionOverrides: {
|
|
397
|
-
'1080p': { min: 8, max: 8, mode: 'discrete', values: [8] },
|
|
398
|
-
'4k': { min: 8, max: 8, mode: 'discrete', values: [8] },
|
|
399
|
-
},
|
|
400
|
-
modeOverrides: {
|
|
401
|
-
ingredients: { min: 8, max: 8, mode: 'discrete', values: [8] },
|
|
402
|
-
},
|
|
403
|
-
},
|
|
404
|
-
maxIngredientImages: 3,
|
|
405
|
-
},
|
|
406
366
|
// ── Seedance ───────────────────────────────────────────────────────────────
|
|
407
367
|
'seedance-2': {
|
|
408
368
|
aspectRatios: SEEDANCE_ASPECT_RATIOS,
|
|
@@ -933,7 +893,7 @@ export function describeVideoResolutions(models) {
|
|
|
933
893
|
function fmtWindow(d) {
|
|
934
894
|
return d.mode === 'discrete' && d.values ? `${d.values.join('s/')}s` : `${d.min}-${d.max}s`;
|
|
935
895
|
}
|
|
936
|
-
/** e.g. "kling-v3.0-std: 3-15s ·
|
|
896
|
+
/** e.g. "kling-v3.0-std: 3-15s · ltx-2-5: 6s/8s/10s/12s/14s/16s/18s/20s (1440p/4k: 6s/8s/10s only)" */
|
|
937
897
|
export function describeDurations(models) {
|
|
938
898
|
return groupBy(models, (m) => {
|
|
939
899
|
const d = MODEL_CAPABILITIES[m]?.duration;
|
|
@@ -963,10 +923,10 @@ export function describeReferenceImageCaps(models) {
|
|
|
963
923
|
const cap = MODEL_CAPABILITIES[m];
|
|
964
924
|
if (!cap)
|
|
965
925
|
return '';
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
return n === 0 ? '0 (prompt
|
|
926
|
+
// A generate row with no reference cap (LTX) takes none: say so rather than
|
|
927
|
+
// dropping the row, which read as "no limit stated".
|
|
928
|
+
const n = cap.maxIngredientImages ?? cap.maxRefImages ?? 0;
|
|
929
|
+
return n === 0 ? '0 (prompt and frames only)' : String(n);
|
|
970
930
|
});
|
|
971
931
|
}
|
|
972
932
|
/** H3 Max reference accounting, fal's worked tables read 2026-09-09.
|
|
@@ -112,12 +112,12 @@ export declare function seedanceTaskIntentWords(prompt: string): string[];
|
|
|
112
112
|
export declare function multimodalRefModels(): string[];
|
|
113
113
|
export declare const MODEL_FACTS: ModelFact[];
|
|
114
114
|
/**
|
|
115
|
-
* The one `default` seat for a kind
|
|
115
|
+
* The one `default` seat for a kind and route, READ from `tier`.
|
|
116
116
|
* Anything that needs "the default image model" calls this instead of naming
|
|
117
117
|
* one: the op surface and slates-web both hand-typed nano-banana-2 for six days
|
|
118
118
|
* after the app picker moved to Sunburst.
|
|
119
119
|
*/
|
|
120
|
-
export declare function defaultModelFor(kind: ModelFact['kind']): string;
|
|
120
|
+
export declare function defaultModelFor(kind: ModelFact['kind'], route?: ModelFact['route']): string;
|
|
121
121
|
/**
|
|
122
122
|
* The seat a built-in tool renders on when its caller names no model. Resolves
|
|
123
123
|
* `TOOL_SEAT` (generation-policy.ts, THE home): `'default-image'` follows the
|