@nodaro/shared 2.16.0 → 2.18.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.
@@ -295,6 +295,35 @@ export const NATIVE_NEGATIVE_VIDEO_PROVIDERS = new Set<string>([
295
295
  "wan-s2v",
296
296
  ])
297
297
 
298
+ /**
299
+ * The `[style]` section header, verbatim. A composed prompt that ends with that
300
+ * section has NO terminator (`@nodaro/prompts`' `prompt-style-section.ts` owns
301
+ * the shape and the reasoning), so a `"\n"`-joined `Avoid:` would read as one
302
+ * more look clause under its header. Duplicated rather than imported because
303
+ * `@nodaro/prompts` depends on THIS package and never the reverse; the two are
304
+ * pinned together behaviourally by that package's
305
+ * `__tests__/style-section-boundary.test.ts`, which runs the real composer
306
+ * through this clamp.
307
+ */
308
+ const STYLE_SECTION_HEADER_LINE = "[style]:\n"
309
+
310
+ /**
311
+ * The `Avoid: …` suffix `applyVideoNegativePrompt` folds into a prompt, with the
312
+ * separator the base prompt calls for: a blank line when the base ends INSIDE an
313
+ * unterminated `[style]` section, a single newline otherwise.
314
+ *
315
+ * `base` omitted = the WIDEST form, which is what a caller reserving room before
316
+ * the prompt exists must budget for (`effectiveVideoPromptCeiling`).
317
+ */
318
+ export function videoNegativeSuffix(negativePrompt: string, base?: string): string {
319
+ const gapAt = base?.lastIndexOf("\n\n") ?? -1
320
+ const lastBlock = base === undefined ? undefined : (gapAt >= 0 ? base.slice(gapAt + 2) : base)
321
+ const separator = lastBlock === undefined || lastBlock.startsWith(STYLE_SECTION_HEADER_LINE)
322
+ ? "\n\n"
323
+ : "\n"
324
+ return `${separator}Avoid: ${negativePrompt}`
325
+ }
326
+
298
327
  /**
299
328
  * Apply the negative prompt to a video-provider request.
300
329
  *
@@ -306,6 +335,8 @@ export const NATIVE_NEGATIVE_VIDEO_PROVIDERS = new Set<string>([
306
335
  * Otherwise, the helper appends `Avoid: <negativePrompt>` to the prompt
307
336
  * (consistent with `buildImagePrompt` in `prompt-builder.ts`) so the model
308
337
  * still sees the negative intent, and returns `nativeNegativePrompt = undefined`.
338
+ * The line is separated by {@link videoNegativeSuffix}, which closes an open
339
+ * `[style]` section with a blank line rather than reading as a look clause.
309
340
  *
310
341
  * Empty / missing negative is a no-op.
311
342
  *
@@ -338,7 +369,10 @@ export function applyVideoNegativePrompt(
338
369
  // assembler), then clamp the whole to the model cap.
339
370
  const base = prompt && prompt.trim().length > 0 ? prompt : ""
340
371
  if (!base) return { prompt: `Avoid: ${neg}`.slice(0, promptMax), nativeNegativePrompt: undefined }
341
- const avoid = `\nAvoid: ${neg}`
372
+ // The separator is decided on the WHOLE base: the clamp below can only cut the
373
+ // tail, and a cut either leaves the `[style]` section behind or lands inside
374
+ // it, so the suffix never needs to grow after the reservation.
375
+ const avoid = videoNegativeSuffix(neg, base)
342
376
  const room = Math.max(0, promptMax - avoid.length)
343
377
  const injected = `${base.slice(0, room)}${avoid}`.slice(0, promptMax)
344
378
  return { prompt: injected, nativeNegativePrompt: undefined }
@@ -130,6 +130,12 @@ export const DYNAMIC_PRODUCER_TYPES: ReadonlySet<string> = new Set([
130
130
  // EVERY producer set or its outputs can't connect (was the "cannot connect
131
131
  // the outputs of voice-changer-pro" bug). Guarded by producer-types.test.ts.
132
132
  "voice-changer-pro",
133
+ // Dubbing joined the dual-mode family with the full-surface upgrade:
134
+ // audio in → dubbed audio; video in (or a video sourceUrl) → dubbed VIDEO
135
+ // (+ audio sidecar). Same wiring contract as voice-changer; stays in
136
+ // AUDIO_PRODUCER_TYPES as its default. Explicitly asserted in
137
+ // producer-types.test.ts (the suite does not fail on omission).
138
+ "dubbing",
133
139
  "reduce",
134
140
  // Dual-output time chunker (UI label "Split into Chunks"; type id stays
135
141
  // "split-media"): video in → video chunks, audio in → audio chunks — two
package/src/types.ts CHANGED
@@ -163,13 +163,19 @@ export interface ConnectedReference {
163
163
  */
164
164
  readonly defaultUsageMode?: UsageMode
165
165
  /**
166
- * Character node's HYBRID `defaultRole` (the role-dropdown pick: a
167
- * `REFERENCE_ROLE_PRESETS["wired-character"]` value or a sanitized Custom
168
- * slug) propagated into every entry derived from that node. Read via
166
+ * The reference's HYBRID `defaultRole` (the role-dropdown pick: a
167
+ * `REFERENCE_ROLE_PRESETS[source]` value or a sanitized Custom slug)
168
+ * propagated into every entry derived from that node. Read via
169
169
  * `resolveDefaultRole(defaultRole, defaultUsageMode, source)` at the hybrid
170
170
  * resolver sites — it takes precedence over `defaultUsageMode`. Absent for
171
- * non-character refs and for nodes that never set a role (fall back to the
172
- * `defaultUsageMode`-derived role, then the source default).
171
+ * nodes that never set a role (fall back to the `defaultUsageMode`-derived
172
+ * role, then the source default).
173
+ *
174
+ * Written by the character node's role dropdown, and accepted from any source
175
+ * on the wire (`connectedReferenceSchema`). The LOCATION paths read it through
176
+ * their own `resolveLocationRole` chain rather than `resolveDefaultRole` — the
177
+ * location role vocabulary is mode-derived and slug-normalized — so it is NOT
178
+ * a character-only field: do not re-narrow this doc to characters.
173
179
  */
174
180
  readonly defaultRole?: string
175
181
  /**
@@ -96,6 +96,22 @@ export interface WorkflowPortability {
96
96
  unreachableMedia: WorkflowMediaRef[]
97
97
  }
98
98
 
99
+ /** The four entity kinds a bundle's `assets` can carry. */
100
+ export type WorkflowAssetKind = "character" | "object" | "creature" | "location"
101
+
102
+ /**
103
+ * A bundled entity the import declined to re-create (#1088). The workflow
104
+ * still lands — the film is the thing the importer asked for — so this is how
105
+ * they hear that an `@` chip arrived unbound.
106
+ */
107
+ export interface WorkflowImportSkippedAsset {
108
+ kind: WorkflowAssetKind
109
+ /** The entity's id IN THE BUNDLE (the exporter's row id). */
110
+ id: string
111
+ name: string
112
+ reason: string
113
+ }
114
+
99
115
  /**
100
116
  * What the importer did about the bundle's media (#866). Publicly reachable
101
117
  * media that is not already on the importing instance's own storage is
@@ -103,11 +119,29 @@ export interface WorkflowPortability {
103
119
  * a host the importer cannot reach is left as-is and listed (`unreachable`);
104
120
  * anything declined for another reason (too large, not a media type, over
105
121
  * the per-import cap, upload failed) is listed with the reason (`skipped`).
122
+ *
123
+ * The bundle's ENTITY images (`assets`) ride the same walk (#1088) — with one
124
+ * rule of their own: they are copied even when they already sit on this
125
+ * instance, because they are the EXPORTER's bytes and the two accounts'
126
+ * lifecycles are uncorrelated (their delete, their quarantine sweep, their
127
+ * retention reaper). Only bytes the importer already owns are left alone.
106
128
  */
107
129
  export interface WorkflowImportReport {
108
130
  rehosted: number
109
131
  unreachable: WorkflowMediaRef[]
110
132
  skipped: Array<WorkflowMediaRef & { reason: string }>
133
+ /**
134
+ * Bundled entity id (the EXPORTER's) → the row created under the importer,
135
+ * for every entity the import re-created. Present whenever the bundle
136
+ * carried `assets`. A client that holds chips outside the graph — a
137
+ * timeline, a copy-a-shared-production path that never posts to
138
+ * `/v1/workflows/import` — re-points them with this; the server has already
139
+ * re-pointed every `references[]` entry inside the nodes it stored.
140
+ */
141
+ assetIdMap?: Record<string, string>
142
+ /** Entities the import declined to create (storage quota). The workflow
143
+ * still landed; chips bound to these arrive unresolvable. */
144
+ assetsSkipped?: WorkflowImportSkippedAsset[]
111
145
  /** Anything else the importer should know, e.g. copies were made but the
112
146
  * workflow could not be updated to use them. */
113
147
  notes?: string[]