@rslsp1/fa-app-tools 7.0.1 → 7.4.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.
package/dist/szenen.js CHANGED
@@ -40,6 +40,7 @@ __export(szenen_exports, {
40
40
  DERIVABLE_TYPES: () => DERIVABLE_TYPES,
41
41
  ELEMENT_VERSION_SCHEMA_FILE: () => ELEMENT_VERSION_SCHEMA_FILE,
42
42
  FLOW_EXECUTION_PATH: () => FLOW_EXECUTION_PATH,
43
+ MCP_CLIENT_PROVIDER: () => MCP_CLIENT_PROVIDER,
43
44
  PROBE_VERSION_ID: () => PROBE_VERSION_ID,
44
45
  PROMPT_RULE_EFFECT: () => PROMPT_RULE_EFFECT,
45
46
  REFERENCE_FIELD: () => REFERENCE_FIELD,
@@ -54,8 +55,12 @@ __export(szenen_exports, {
54
55
  SZENEN_EVENT_TYPES: () => SZENEN_EVENT_TYPES,
55
56
  SZENEN_ITEM_PREFIX: () => SZENEN_ITEM_PREFIX,
56
57
  SZENEN_SCHEMAS: () => SZENEN_SCHEMAS,
58
+ SZENEN_SNAPSHOT_FILE: () => SZENEN_SNAPSHOT_FILE,
59
+ SZENEN_SNAPSHOT_PREFIX: () => SZENEN_SNAPSHOT_PREFIX,
60
+ SZENEN_SNAPSHOT_SCHEMA_VERSION: () => SZENEN_SNAPSHOT_SCHEMA_VERSION,
57
61
  SzenenView: () => SzenenView,
58
62
  UNCHECKED_PUBLISH_RULES: () => UNCHECKED_PUBLISH_RULES,
63
+ UNVERIFIED_MODEL_ID: () => UNVERIFIED_MODEL_ID,
59
64
  UPLOAD_MAX_BYTES: () => UPLOAD_MAX_BYTES,
60
65
  UnsupportedSchemaError: () => UnsupportedSchemaError,
61
66
  VersionDagError: () => VersionDagError,
@@ -66,6 +71,7 @@ __export(szenen_exports, {
66
71
  buildDerivationSystemInstruction: () => buildSystemInstruction2,
67
72
  buildDerivationUserPrompt: () => buildUserPrompt2,
68
73
  buildSystemInstruction: () => buildSystemInstruction,
74
+ buildSzenenSnapshot: () => buildSzenenSnapshot,
69
75
  buildUserPrompt: () => buildUserPrompt,
70
76
  buildVersionDag: () => buildVersionDag,
71
77
  callOptional: () => callOptional,
@@ -96,14 +102,18 @@ __export(szenen_exports, {
96
102
  checkSzenenBatchOrigin: () => checkSzenenBatchOrigin,
97
103
  checkTitle: () => checkTitle,
98
104
  checkWriteDraftInput: () => checkWriteDraftInput,
105
+ claimWon: () => claimWon,
99
106
  collectImageBatches: () => collectImageBatches,
100
107
  collectImageGenerationOrders: () => collectImageGenerationOrders,
108
+ collectImageJobs: () => collectImageJobs,
109
+ collectPromptOperations: () => collectPromptOperations,
101
110
  collectPromptRuns: () => collectPromptRuns,
102
111
  collectProviderChecks: () => collectProviderChecks,
103
112
  collectSceneDerivationRecoveries: () => collectSceneDerivationRecoveries,
104
113
  collectSceneDerivations: () => collectSceneDerivations,
105
114
  collectVersionRefs: () => collectVersionRefs2,
106
115
  compileSchemas: () => compileSchemas,
116
+ completePromptOperation: () => completePromptOperation,
107
117
  createBrowserTextGenerationPort: () => createBrowserTextGenerationPort,
108
118
  createClientDerivationPort: () => createClientDerivationPort,
109
119
  createClientRunPort: () => createClientRunPort,
@@ -121,6 +131,7 @@ __export(szenen_exports, {
121
131
  createHfImageLibrary: () => createHfImageLibrary,
122
132
  createHfImageStore: () => createHfImageStore,
123
133
  createImageGenerationPort: () => createImageGenerationPort,
134
+ createImageJobPort: () => createImageJobPort,
124
135
  createInMemoryEventChannel: () => createInMemoryEventChannel,
125
136
  createMemoryViewPort: () => createMemoryViewPort,
126
137
  createSimulationSceneDerivationPort: () => createSimulationSceneDerivationPort,
@@ -139,6 +150,8 @@ __export(szenen_exports, {
139
150
  findExecutableImageModel: () => findExecutableImageModel,
140
151
  findImageBatch: () => findImageBatch,
141
152
  findImageGenerationOrder: () => findImageGenerationOrder,
153
+ findImageJob: () => findImageJob,
154
+ findPromptOperation: () => findPromptOperation,
142
155
  findPromptRun: () => findPromptRun,
143
156
  findProviderCheck: () => findProviderCheck,
144
157
  findSceneDerivation: () => findSceneDerivation,
@@ -148,6 +161,7 @@ __export(szenen_exports, {
148
161
  formatSeedListe: () => formatSeedListe,
149
162
  freezeDraftChain: () => freezeDraftChain,
150
163
  freezeSceneWorkingState: () => freezeSceneWorkingState,
164
+ generationBatchIdOf: () => generationBatchIdOf,
151
165
  gesamtHerkunft: () => gesamtHerkunft,
152
166
  getSzenenSchemas: () => getSzenenSchemas,
153
167
  gruppiereZuItems: () => gruppiereZuItems,
@@ -156,14 +170,20 @@ __export(szenen_exports, {
156
170
  isOk: () => isOk,
157
171
  isProjectionCurrent: () => isProjectionCurrent,
158
172
  isSzenenItemId: () => isSzenenItemId,
173
+ istSzenenSnapshotDatei: () => istSzenenSnapshotDatei,
159
174
  istZipDatei: () => istZipDatei,
160
175
  itemIdForRun: () => itemIdForRun,
176
+ itemsAusBestand: () => itemsAusBestand,
177
+ jobsForPromptRun: () => jobsForPromptRun,
161
178
  ladeVorschauen: () => ladeVorschauen,
162
179
  leseBilderAusZip: () => leseBilderAusZip,
163
180
  linkFor: () => linkFor,
181
+ locatorAus: () => locatorAus,
164
182
  locatorTeile: () => locatorTeile,
165
183
  logRevisionOf: () => logRevisionOf,
184
+ mergeSnapshotAndPending: () => mergeSnapshotAndPending,
166
185
  mergeSzenenBatchOrigin: () => mergeSzenenBatchOrigin,
186
+ mimeTypAus: () => mimeTypAus,
167
187
  mitDefaultVorlage: () => mitDefaultVorlage,
168
188
  noticeFor: () => noticeFor,
169
189
  ordersForPromptRun: () => ordersForPromptRun,
@@ -171,8 +191,10 @@ __export(szenen_exports, {
171
191
  parseDerivationResponse: () => parseDerivationResponse,
172
192
  parseRunResponse: () => parseRunResponse,
173
193
  parseSeedListe: () => parseSeedListe,
194
+ parseSzenenSnapshot: () => parseSzenenSnapshot,
174
195
  preferredHeadRevisionOf: () => preferredHeadRevisionOf,
175
196
  preferredVersionId: () => preferredVersionId,
197
+ preparePromptOperation: () => preparePromptOperation,
176
198
  promptDiagnosticsOf: () => promptDiagnosticsOf,
177
199
  publishedHeadsOf: () => publishedHeadsOf,
178
200
  recoveriesForRun: () => recoveriesForRun,
@@ -185,19 +207,23 @@ __export(szenen_exports, {
185
207
  resolveShotPayload: () => resolveShotPayload,
186
208
  resolveTargetModelOver: () => resolveTargetModelOver,
187
209
  resolverFromQueries: () => resolverFromQueries,
210
+ responseContractOf: () => responseContractOf,
188
211
  roundOfRun: () => roundOfRun,
189
212
  rulesWithoutPromptEffect: () => rulesWithoutPromptEffect,
190
213
  runElementStoreContract: () => runElementStoreContract,
214
+ runOfOperation: () => runOfOperation,
191
215
  runPromptDerivation: () => runPromptDerivation,
192
216
  runPublishChecks: () => runPublishChecks,
193
217
  runSceneDerivation: () => runSceneDerivation,
194
218
  runStoreDifferential: () => runStoreDifferential,
219
+ sameResponseAs: () => sameResponseAs,
195
220
  sceneOnlyCapabilities: () => sceneOnlyCapabilities,
196
221
  scopeOfPath: () => scopeOfPath,
197
222
  seedFehlerText: () => seedFehlerText,
198
223
  selectableTargetModels: () => selectableTargetModels,
199
224
  showChain: () => showChain,
200
225
  simulatedAnswerFor: () => simulatedAnswerFor,
226
+ szenenSnapshotFilename: () => szenenSnapshotFilename,
201
227
  totalModelCalls: () => totalModelCalls,
202
228
  typAusBytes: () => typAusBytes,
203
229
  unclassifiedPublishRules: () => unclassifiedPublishRules,
@@ -4798,11 +4824,11 @@ async function resolveShotPayload(input) {
4798
4824
  };
4799
4825
  }
4800
4826
  const shot = shotPayload;
4801
- for (const fehler4 of ohneKettenReferenz(
4827
+ for (const fehler5 of ohneKettenReferenz(
4802
4828
  validatePayload("shot", shotPayload).errors,
4803
4829
  chain ? "scene_setup_ref" : null
4804
4830
  )) {
4805
- diagnostics.push(diagnosticOfSchemaError(fehler4, `Shot ${shotElementId}`));
4831
+ diagnostics.push(diagnosticOfSchemaError(fehler5, `Shot ${shotElementId}`));
4806
4832
  }
4807
4833
  let setup = null;
4808
4834
  let report = null;
@@ -4914,8 +4940,8 @@ async function resolveScenePayload(input) {
4914
4940
  "Kein Scene Setup, kein Shot: Der Lauf entsteht aus der Szene allein. Bildaufbau, Kamera und Zielmodell wurden nicht ergaenzt, sondern sind nicht vorhanden (D-135)."
4915
4941
  ];
4916
4942
  const diagnostics = [];
4917
- for (const fehler4 of validatePayload("scene", scenePayload).errors) {
4918
- diagnostics.push(diagnosticOfSchemaError(fehler4, `Szene ${sceneElementId}`));
4943
+ for (const fehler5 of validatePayload("scene", scenePayload).errors) {
4944
+ diagnostics.push(diagnosticOfSchemaError(fehler5, `Szene ${sceneElementId}`));
4919
4945
  }
4920
4946
  const faehig = sceneOnlyCapabilities(scenePayload);
4921
4947
  return komponiere({
@@ -5805,7 +5831,60 @@ var SZENEN_EVENT_TYPES = {
5805
5831
  * Der Elementreducer ignoriert auch diesen Typ (`default:`-Zweig); gelesen
5806
5832
  * wird mit `collectImageBatches` (`imageBatchStore.ts`).
5807
5833
  */
5808
- imageBatchPersisted: "szenen.image_batch_persisted"
5834
+ imageBatchPersisted: "szenen.image_batch_persisted",
5835
+ /**
5836
+ * Ein vorbereiteter Promptvorgang — der Durchstich „Szene → ChatGPT → Lauf".
5837
+ *
5838
+ * Der bestehende Promptpfad ruft das Modell selbst
5839
+ * (`runPromptDerivation()`); ein aufrufendes Chatmodell formuliert dagegen
5840
+ * **zwischen** zwei Werkzeugaufrufen. Damit zerfaellt der eine Vorgang in
5841
+ * zwei, und der zweite braucht genau den Stand, den der erste eingefroren
5842
+ * hat — sonst loeste der Abschluss die inzwischen geaenderte Szene neu auf
5843
+ * und der Lauf behauptete eine Herkunft, die er nicht hat (D-22).
5844
+ *
5845
+ * Deshalb ein eigenes Ereignis und **kein** fluechtiger Prozesszustand: Der
5846
+ * Snapshot ueberlebt Neustart, Hostwechsel und Sitzungsende, weil er im
5847
+ * selben autoritativen Log liegt wie der Lauf, der daraus entsteht.
5848
+ *
5849
+ * Der Elementreducer ignoriert diesen Typ (`default:`-Zweig); gelesen wird
5850
+ * mit `collectPromptOperations` (`scenePromptOperation.ts`).
5851
+ */
5852
+ promptOperationPrepared: "szenen.prompt_operation_prepared",
5853
+ /**
5854
+ * Ein Bildauftrag wartet auf einen ausfuehrenden Flow-Host.
5855
+ *
5856
+ * Die vorhandene Auftragshistorie (`imageGenerationStarted`) beginnt
5857
+ * **unmittelbar vor** dem Provideraufruf und kennt deshalb keinen wartenden
5858
+ * Auftrag. Genau der entsteht hier: Ein Chatmodell beauftragt ueber MCP ein
5859
+ * Bild, der Auftrag wird dauerhaft abgelegt, und der Werkzeugaufruf endet
5860
+ * sofort mit seiner Kennung — er haelt keine Antwort minutenlang offen.
5861
+ *
5862
+ * Traegt **keinen** Prompt: Der steht im referenzierten Lauf, und ihn hier
5863
+ * zu kopieren waere eine zweite Wahrheit ueber denselben eingefrorenen Text
5864
+ * (D-103).
5865
+ */
5866
+ imageJobRequested: "szenen.image_job_requested",
5867
+ /**
5868
+ * Ein Flow-Host beansprucht einen wartenden Bildauftrag.
5869
+ *
5870
+ * Ein append-only Log kennt kein Lock: Zwei Tabs koennen beide anhaengen.
5871
+ * Deshalb ist die Uebernahme **nicht** der Append, sondern das Ergebnis
5872
+ * einer deterministischen Schiedsregel darueber (`winningClaimOf`) — der
5873
+ * erste Anspruch in Logreihenfolge gewinnt, und jeder Host liest nach
5874
+ * seinem Append erneut, bevor er einen bezahlten Provideraufruf startet.
5875
+ * Ein unterlegener Anspruch bleibt sichtbar stehen; er wird nicht geloescht,
5876
+ * weil das Log das nicht kann und weil der Versuch ein Beleg ist.
5877
+ */
5878
+ imageJobClaimed: "szenen.image_job_claimed",
5879
+ /**
5880
+ * Der Bildauftrag ist erledigt — mit dem Ausgang, den der Host gesehen hat.
5881
+ *
5882
+ * Getrennt von `imageGenerationFinished`: Jenes gehoert zum bezahlten
5883
+ * Provideraufruf, dieses zur Zustellung. Ein Auftrag kann fehlschlagen,
5884
+ * **bevor** ein Provider gerufen wurde (abgelehntes Profil), und dann gibt
5885
+ * es kein `imageGenerationFinished`, das den Wartenden erloesen wuerde.
5886
+ */
5887
+ imageJobSettled: "szenen.image_job_settled"
5809
5888
  };
5810
5889
  function sortForReplay(events) {
5811
5890
  return [...events].sort((a, b) => {
@@ -8291,10 +8370,89 @@ function bildEndungFuer(mimeType) {
8291
8370
  return "jpg";
8292
8371
  }
8293
8372
 
8373
+ // src/lib/szenen/szenenSnapshot.ts
8374
+ var SZENEN_SNAPSHOT_SCHEMA_VERSION = 1;
8375
+ var SZENEN_SNAPSHOT_PREFIX = "szenen-state-";
8376
+ var SZENEN_SNAPSHOT_FILE = "szenen-state.json";
8377
+ function szenenSnapshotFilename(maxTs) {
8378
+ const stamp = new Date(maxTs).toISOString().replace(/:/g, "-").replace(".", "-");
8379
+ return `${SZENEN_SNAPSHOT_PREFIX}${stamp}.json`;
8380
+ }
8381
+ function istSzenenSnapshotDatei(name) {
8382
+ return name.startsWith(SZENEN_SNAPSHOT_PREFIX) && name.endsWith(".json");
8383
+ }
8384
+ var maxTsOf = (events) => {
8385
+ let max = 0;
8386
+ for (const event of events) if (event.ts > max) max = event.ts;
8387
+ return max;
8388
+ };
8389
+ function buildSzenenSnapshot(base, neue, neuePfade = []) {
8390
+ const events = [...base?.events ?? [], ...neue];
8391
+ const paths = [...base?.consumed.paths ?? [], ...neuePfade.map(basisname)];
8392
+ return {
8393
+ schema_version: SZENEN_SNAPSHOT_SCHEMA_VERSION,
8394
+ consumed: { count: events.length, max_ts: maxTsOf(events), paths },
8395
+ events
8396
+ };
8397
+ }
8398
+ function basisname(pfad) {
8399
+ return pfad.split("/").pop() ?? pfad;
8400
+ }
8401
+ function aufgenommeneDateien(snapshot) {
8402
+ return new Set(snapshot ? snapshot.consumed.paths : []);
8403
+ }
8404
+ function parseSzenenSnapshot(raw, herkunft) {
8405
+ if (!raw || typeof raw !== "object") {
8406
+ throw new Error(`Szenen-Snapshot ${herkunft}: kein Objekt, sondern ${typeof raw}.`);
8407
+ }
8408
+ const obj = raw;
8409
+ if (obj.schema_version !== SZENEN_SNAPSHOT_SCHEMA_VERSION) {
8410
+ throw new Error(
8411
+ `Szenen-Snapshot ${herkunft}: schema_version ${String(obj.schema_version)} wird nicht gelesen, erwartet ${SZENEN_SNAPSHOT_SCHEMA_VERSION}.`
8412
+ );
8413
+ }
8414
+ if (!Array.isArray(obj.events)) {
8415
+ throw new Error(`Szenen-Snapshot ${herkunft}: "events" fehlt oder ist kein Array.`);
8416
+ }
8417
+ const events = obj.events;
8418
+ for (const [i, event] of events.entries()) {
8419
+ if (!event || typeof event.type !== "string" || typeof event.ts !== "number") {
8420
+ throw new Error(
8421
+ `Szenen-Snapshot ${herkunft}: Ereignis ${i} hat keinen Typ oder keinen Zeitstempel.`
8422
+ );
8423
+ }
8424
+ }
8425
+ const consumed = obj.consumed;
8426
+ if (!consumed || typeof consumed.count !== "number" || typeof consumed.max_ts !== "number") {
8427
+ throw new Error(`Szenen-Snapshot ${herkunft}: "consumed" fehlt oder ist unvollstaendig.`);
8428
+ }
8429
+ if (!Array.isArray(consumed.paths)) {
8430
+ throw new Error(
8431
+ `Szenen-Snapshot ${herkunft}: "consumed.paths" fehlt. Ohne die aufgenommenen Dateinamen laesst sich nicht entscheiden, welche Datei in events/ noch aussteht.`
8432
+ );
8433
+ }
8434
+ if (consumed.count !== events.length) {
8435
+ throw new Error(
8436
+ `Szenen-Snapshot ${herkunft}: consumed.count ${consumed.count} passt nicht zu ${events.length} Ereignis(sen) in der Datei \u2014 die Datei ist unvollstaendig.`
8437
+ );
8438
+ }
8439
+ const echt = maxTsOf(events);
8440
+ if (consumed.max_ts !== echt) {
8441
+ throw new Error(
8442
+ `Szenen-Snapshot ${herkunft}: consumed.max_ts ${consumed.max_ts} passt nicht zum groessten Zeitstempel ${echt} in der Datei.`
8443
+ );
8444
+ }
8445
+ return { schema_version: SZENEN_SNAPSHOT_SCHEMA_VERSION, consumed, events };
8446
+ }
8447
+ function mergeSnapshotAndPending(snapshot, pending, bereitsImSnapshot = 0) {
8448
+ if (!snapshot) return { events: pending, bereitsImSnapshot };
8449
+ return { events: [...snapshot.events, ...pending], bereitsImSnapshot };
8450
+ }
8451
+
8294
8452
  // src/lib/hfEventLog.ts
8295
- function treeUrl(namespace, subdir = "") {
8453
+ function treeUrl(namespace, subdir = "", revision = "main") {
8296
8454
  const parts = [namespace.replace(/\/$/, ""), subdir].filter(Boolean).join("/");
8297
- return `${HF_BASE}/api/datasets/${HF_REPO}/tree/main${parts ? "/" + parts : ""}`;
8455
+ return `${HF_BASE}/api/datasets/${HF_REPO}/tree/${encodeURIComponent(revision)}${parts ? "/" + parts : ""}`;
8298
8456
  }
8299
8457
  var DROSSEL_VERSUCHE = 4;
8300
8458
  var DROSSEL_BASIS_MS = 400;
@@ -8327,8 +8485,8 @@ async function mitGeduld(anfrage) {
8327
8485
  }
8328
8486
  return letzte;
8329
8487
  }
8330
- async function hfListDir(namespace, subdir, token) {
8331
- const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir), {
8488
+ async function hfListDir(namespace, subdir, token, revision = "main") {
8489
+ const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir, revision), {
8332
8490
  headers: { Authorization: `Bearer ${token}` }
8333
8491
  }));
8334
8492
  if (res.status === 404) return [];
@@ -8357,17 +8515,46 @@ function toBase64(text) {
8357
8515
  bytes.forEach((b) => binary += String.fromCharCode(b));
8358
8516
  return btoa(binary);
8359
8517
  }
8360
- async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`) {
8518
+ var HfPreconditionFailedError = class extends Error {
8519
+ constructor(message) {
8520
+ super(message);
8521
+ this.name = "HfPreconditionFailed";
8522
+ }
8523
+ };
8524
+ async function hfRepoHead(token, revision = "main") {
8525
+ const res = await mitGeduld(() => holeMitZeitlimit(
8526
+ `${HF_BASE}/api/datasets/${HF_REPO}/revision/${encodeURIComponent(revision)}`,
8527
+ { headers: { Authorization: `Bearer ${token}` } }
8528
+ ));
8529
+ if (!res.ok) {
8530
+ throw new Error(`HF revision failed: ${res.status} ${res.statusText} (${revision})`);
8531
+ }
8532
+ const daten = await res.json();
8533
+ if (typeof daten.sha !== "string" || daten.sha.length === 0) {
8534
+ throw new Error(
8535
+ `HF revision ${revision}: die Antwort traegt kein "sha". Erhaltene Felder: ${Object.keys(daten || {}).join(", ") || "(keine)"}.`
8536
+ );
8537
+ }
8538
+ return daten.sha;
8539
+ }
8540
+ async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`, options) {
8541
+ const kopf = { summary, description: "" };
8542
+ if (options?.parentCommit) kopf.parentCommit = options.parentCommit;
8361
8543
  const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
8362
8544
  method: "POST",
8363
8545
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
8364
8546
  body: [
8365
- JSON.stringify({ key: "header", value: { summary, description: "" } }),
8547
+ JSON.stringify({ key: "header", value: kopf }),
8366
8548
  JSON.stringify({ key: "file", value: { path: repoPath, encoding: "base64", content: toBase64(content) } })
8367
8549
  ].join("\n")
8368
8550
  });
8369
8551
  if (!res.ok) {
8370
8552
  const err = await res.text().catch(() => "");
8553
+ if (options?.parentCommit && (res.status === 412 || res.status === 409)) {
8554
+ throw new HfPreconditionFailedError(
8555
+ `HF commit abgelehnt: Der Branch hat sich seit ${options.parentCommit} bewegt (${res.status}). Es wurde nichts geschrieben.`
8556
+ );
8557
+ }
8371
8558
  throw new Error(`HF upload failed: ${res.status} \u2014 ${err.slice(0, 200)}`);
8372
8559
  }
8373
8560
  }
@@ -8399,8 +8586,8 @@ async function mitBegrenzterParallelitaet(count, limit, task) {
8399
8586
  await Promise.all(Array.from({ length: Math.min(limit, count) }, worker));
8400
8587
  if (fehlerAufgetreten) throw ersterFehler;
8401
8588
  }
8402
- async function loadPendingEvents(namespace, token, sinceTs, knownPaths) {
8403
- const files = await hfListDir(namespace, "events", token);
8589
+ async function loadPendingEvents(namespace, token, sinceTs, knownPaths, revision = "main") {
8590
+ const files = await hfListDir(namespace, "events", token, revision);
8404
8591
  const pending = files.filter((f) => f.type === "file" && tsFromEventPath(f.path) > sinceTs && !knownPaths?.has(f.path)).sort((a, b) => a.path.localeCompare(b.path));
8405
8592
  if (!pending.length) return [];
8406
8593
  const raw = new Array(pending.length);
@@ -8410,6 +8597,16 @@ async function loadPendingEvents(namespace, token, sinceTs, knownPaths) {
8410
8597
  for (const f of pending) knownPaths?.add(f.path);
8411
8598
  return raw.flatMap((e) => Array.isArray(e) ? e : [e]);
8412
8599
  }
8600
+ async function findLatestSzenenSnapshotPath(namespace, token) {
8601
+ const files = await hfListDir(namespace, "", token);
8602
+ const treffer = files.filter((f) => f.type === "file" && istSzenenSnapshotDatei(f.path.split("/").pop() || "")).map((f) => f.path).sort((a, b) => b.localeCompare(a));
8603
+ return treffer[0] ?? null;
8604
+ }
8605
+ async function loadLatestSzenenSnapshot(namespace, token) {
8606
+ const pfad = await findLatestSzenenSnapshotPath(namespace, token);
8607
+ if (!pfad) return null;
8608
+ return parseSzenenSnapshot(await hfDownloadJsonByPath(pfad, token), pfad);
8609
+ }
8413
8610
  var SESSION_CLIENT_ID = `client-${crypto.randomUUID().slice(0, 8)}`;
8414
8611
  function tsToFilename(ts) {
8415
8612
  return new Date(ts).toISOString().replace(/:/g, "-").replace(".", "-");
@@ -8432,18 +8629,33 @@ async function writeHFEvent(namespace, token, type, payload, prevTs, options) {
8432
8629
  const suffix = options?.ordered ? `${String(++writeSeq % 1e6).padStart(6, "0")}${uuid}` : uuid;
8433
8630
  const filename = `${tsToFilename(ts)}_${suffix}.json`;
8434
8631
  const repoPath = `${namespace}events/${filename}`;
8435
- await hfUploadSmallFile(repoPath, JSON.stringify(event, null, 2), token, `Event: ${type}`);
8632
+ await hfUploadSmallFile(
8633
+ repoPath,
8634
+ JSON.stringify(event, null, 2),
8635
+ token,
8636
+ `Event: ${type}`,
8637
+ options?.parentCommit ? { parentCommit: options.parentCommit } : void 0
8638
+ );
8436
8639
  return event;
8437
8640
  }
8438
8641
 
8439
8642
  // src/lib/szenen/hfEventChannel.ts
8440
8643
  function createHfEventChannel(options) {
8441
- const { namespace, token, sinceTs = 0 } = options;
8644
+ const { namespace, token, sinceTs = 0, ohneSnapshot = false } = options;
8442
8645
  const knownPaths = /* @__PURE__ */ new Set();
8443
8646
  let cachedRawEvents = [];
8444
8647
  let syncInFlight = null;
8445
- const refresh = async () => {
8446
- const neue = await loadPendingEvents(namespace, token, sinceTs, knownPaths);
8648
+ let snapshot = null;
8649
+ let snapshotGeladen = ohneSnapshot;
8650
+ const refresh = async (revision = "main") => {
8651
+ if (!snapshotGeladen) {
8652
+ snapshot = await loadLatestSzenenSnapshot(namespace, token);
8653
+ snapshotGeladen = true;
8654
+ for (const name of aufgenommeneDateien(snapshot)) {
8655
+ knownPaths.add(`${namespace}events/${name}`);
8656
+ }
8657
+ }
8658
+ const neue = await loadPendingEvents(namespace, token, sinceTs, knownPaths, revision);
8447
8659
  if (neue.length) cachedRawEvents = [...cachedRawEvents, ...neue];
8448
8660
  };
8449
8661
  const sync = () => {
@@ -8454,15 +8666,34 @@ function createHfEventChannel(options) {
8454
8666
  }
8455
8667
  return syncInFlight;
8456
8668
  };
8669
+ const sicht = () => {
8670
+ const pending = cachedRawEvents.filter((event) => typeof event.type === "string" && event.type.startsWith(SZENEN_EVENT_PREFIX)).map((event) => ({
8671
+ type: event.type,
8672
+ payload: event.payload,
8673
+ clientId: event.clientId,
8674
+ ts: event.ts
8675
+ }));
8676
+ return mergeSnapshotAndPending(snapshot, pending).events;
8677
+ };
8457
8678
  return {
8458
8679
  async readEvents() {
8459
8680
  await sync();
8460
- return cachedRawEvents.filter((event) => typeof event.type === "string" && event.type.startsWith(SZENEN_EVENT_PREFIX)).map((event) => ({
8461
- type: event.type,
8462
- payload: event.payload,
8463
- clientId: event.clientId,
8464
- ts: event.ts
8465
- }));
8681
+ return sicht();
8682
+ },
8683
+ async readHead() {
8684
+ return hfRepoHead(token);
8685
+ },
8686
+ async readEventsAt(revision) {
8687
+ await refresh(revision);
8688
+ return sicht();
8689
+ },
8690
+ async appendEventAt(type, payload, parentCommit) {
8691
+ if (!type.startsWith(SZENEN_EVENT_PREFIX)) {
8692
+ throw new Error(
8693
+ `Event-Typ "${type}" gehoert nicht zur Domain szenen. Der geteilte Kanal wird ueber das Praefix "${SZENEN_EVENT_PREFIX}" getrennt.`
8694
+ );
8695
+ }
8696
+ await writeHFEvent(namespace, token, type, payload, [], { ordered: true, parentCommit });
8466
8697
  },
8467
8698
  async appendEvent(type, payload) {
8468
8699
  if (!type.startsWith(SZENEN_EVENT_PREFIX)) {
@@ -8478,6 +8709,7 @@ function createHfEventChannel(options) {
8478
8709
  // src/lib/szenen/inMemoryEventChannel.ts
8479
8710
  function createInMemoryEventChannel(options = {}) {
8480
8711
  const log = options.shared ?? [];
8712
+ const snapshot = options.snapshot ?? null;
8481
8713
  let tick = 0;
8482
8714
  const now = options.now ?? (() => ++tick);
8483
8715
  const clientId = options.clientId ?? "client-a";
@@ -8486,7 +8718,8 @@ function createInMemoryEventChannel(options = {}) {
8486
8718
  return log;
8487
8719
  },
8488
8720
  async readEvents() {
8489
- return log.map((event) => ({ ...event }));
8721
+ const { events } = mergeSnapshotAndPending(snapshot, log);
8722
+ return events.map((event) => ({ ...event }));
8490
8723
  },
8491
8724
  async appendEvent(type, payload) {
8492
8725
  log.push({ type, payload, clientId, ts: now() });
@@ -8495,7 +8728,7 @@ function createInMemoryEventChannel(options = {}) {
8495
8728
  log.push(event);
8496
8729
  },
8497
8730
  withClient(otherClientId) {
8498
- return createInMemoryEventChannel({ clientId: otherClientId, now, shared: log });
8731
+ return createInMemoryEventChannel({ clientId: otherClientId, now, shared: log, snapshot });
8499
8732
  }
8500
8733
  };
8501
8734
  return channel;
@@ -10033,7 +10266,8 @@ function capabilitiesOf(port) {
10033
10266
  imageBatchHistory: typeof port.imageBatchHistory?.listBatchesForPromptRun === "function",
10034
10267
  imageOrderHistory: typeof port.imageBatchHistory?.listOrdersForPromptRun === "function",
10035
10268
  imageBatchWrite: typeof port.imageBatchWrite?.importBatch === "function",
10036
- imageGeneration: typeof port.imageGeneration?.startFromPromptRun === "function"
10269
+ imageGeneration: typeof port.imageGeneration?.startFromPromptRun === "function",
10270
+ imageJobs: typeof port.imageJobs?.claimAndRun === "function"
10037
10271
  };
10038
10272
  }
10039
10273
  function unsupportedReason(capability) {
@@ -10367,6 +10601,44 @@ function gruppiereZuItems(bilder) {
10367
10601
  items.sort((a, b) => b.juengstes - a.juengstes);
10368
10602
  return items.map((e) => e.item);
10369
10603
  }
10604
+ function locatorAus(itemId, position, anzahl) {
10605
+ return anzahl === 1 ? itemId : `${itemId}-${position}`;
10606
+ }
10607
+ function mimeTypAus(dateiname) {
10608
+ const n = dateiname.toLowerCase();
10609
+ if (n.endsWith(".png")) return "image/png";
10610
+ if (n.endsWith(".webp")) return "image/webp";
10611
+ return "image/jpeg";
10612
+ }
10613
+ function passt(item, suche) {
10614
+ const f = suche.trim().toLowerCase();
10615
+ if (!f) return true;
10616
+ return (item.title ?? "").toLowerCase().includes(f) || item.id.toLowerCase().includes(f);
10617
+ }
10618
+ function itemsAusBestand(items, options = {}) {
10619
+ const suche = options.search ?? "";
10620
+ const werke = [];
10621
+ const passende = items.filter((i) => i && typeof i.id === "string" && i.id.length > 0 && passt(i, suche)).sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0) || (a.id < b.id ? 1 : a.id > b.id ? -1 : 0));
10622
+ for (const item of passende) {
10623
+ const galerie = Array.isArray(item.gallery) ? item.gallery : [];
10624
+ const titel = item.title || item.id;
10625
+ const bilder = [];
10626
+ galerie.forEach((eintrag, position) => {
10627
+ const datei = eintrag?.file;
10628
+ if (!datei) return;
10629
+ bilder.push({
10630
+ mediaRef: locatorAus(item.id, position, galerie.length),
10631
+ title: galerie.length === 1 ? titel : `${titel} \xB7 Bild ${position + 1}`,
10632
+ mimeType: mimeTypAus(String(datei)),
10633
+ thumbUrl: options.thumbUrl ? options.thumbUrl(String(datei)) : null
10634
+ });
10635
+ });
10636
+ if (bilder.length === 0) continue;
10637
+ werke.push({ itemRef: item.id, title: titel, images: bilder });
10638
+ if (options.limit && werke.length >= options.limit) break;
10639
+ }
10640
+ return werke;
10641
+ }
10370
10642
 
10371
10643
  // src/lib/szenen/hfImageLibrary.ts
10372
10644
  function fehler(nachricht, retryable = false) {
@@ -12560,8 +12832,8 @@ function createFlowImageProvider(generateImage) {
12560
12832
  // Kein Seed: weder als Eingabe dokumentiert noch als Ausgabe
12561
12833
  // geliefert — nichts wird erfunden (D-107).
12562
12834
  });
12563
- } catch (fehler4) {
12564
- ersterFehler = fehler4 instanceof Error ? fehler4.message : String(fehler4);
12835
+ } catch (fehler5) {
12836
+ ersterFehler = fehler5 instanceof Error ? fehler5.message : String(fehler5);
12565
12837
  break;
12566
12838
  }
12567
12839
  }
@@ -12771,6 +13043,252 @@ function createHfImageBatchWrite(options) {
12771
13043
  };
12772
13044
  }
12773
13045
 
13046
+ // src/lib/szenen/imageJobStore.ts
13047
+ var istText7 = (wert) => typeof wert === "string" && wert.trim().length > 0;
13048
+ var SETTLED_STATUS = ["succeeded", "partial", "failed", "rejected"];
13049
+ function collectImageJobs(events) {
13050
+ const nachId = /* @__PURE__ */ new Map();
13051
+ for (const event of events) {
13052
+ if (event.type !== SZENEN_EVENT_TYPES.imageJobRequested) continue;
13053
+ const payload = event.payload;
13054
+ if (!payload || !istText7(payload.job_id) || !istText7(payload.prompt_run_id)) continue;
13055
+ if (nachId.has(payload.job_id)) continue;
13056
+ nachId.set(payload.job_id, {
13057
+ job_id: payload.job_id,
13058
+ prompt_run_id: payload.prompt_run_id,
13059
+ execution_target_model_key: payload.execution_target_model_key ?? "",
13060
+ execution_profile_id: payload.execution_profile_id ?? "",
13061
+ image_count: typeof payload.image_count === "number" ? payload.image_count : 0,
13062
+ ...payload.aspect_ratio ? { aspect_ratio: payload.aspect_ratio } : {},
13063
+ requested_by: payload.requested_by ?? "",
13064
+ store_id: payload.store_id ?? "",
13065
+ created_at: payload.created_at ?? "",
13066
+ status: "pending",
13067
+ claim_attempts: []
13068
+ });
13069
+ }
13070
+ for (const event of events) {
13071
+ if (event.type !== SZENEN_EVENT_TYPES.imageJobClaimed) continue;
13072
+ const payload = event.payload;
13073
+ if (!payload || !istText7(payload.job_id) || !istText7(payload.claim_id)) continue;
13074
+ const auftrag = nachId.get(payload.job_id);
13075
+ if (!auftrag) continue;
13076
+ const anspruch = {
13077
+ job_id: payload.job_id,
13078
+ claim_id: payload.claim_id,
13079
+ client_id: payload.client_id ?? "",
13080
+ claimed_at: payload.claimed_at ?? ""
13081
+ };
13082
+ nachId.set(payload.job_id, {
13083
+ ...auftrag,
13084
+ claim: auftrag.claim ?? anspruch,
13085
+ claim_attempts: [...auftrag.claim_attempts, anspruch],
13086
+ status: auftrag.status === "pending" ? "claimed" : auftrag.status
13087
+ });
13088
+ }
13089
+ for (const event of events) {
13090
+ if (event.type !== SZENEN_EVENT_TYPES.imageJobSettled) continue;
13091
+ const payload = event.payload;
13092
+ if (!payload || !istText7(payload.job_id)) continue;
13093
+ const auftrag = nachId.get(payload.job_id);
13094
+ if (!auftrag) continue;
13095
+ if (!auftrag.claim || auftrag.claim.claim_id !== payload.claim_id) continue;
13096
+ if (auftrag.status !== "claimed") continue;
13097
+ if (!SETTLED_STATUS.includes(payload.status)) continue;
13098
+ nachId.set(payload.job_id, {
13099
+ ...auftrag,
13100
+ status: payload.status,
13101
+ ...payload.generation_batch_id ? { generation_batch_id: payload.generation_batch_id } : {},
13102
+ ...payload.item_id ? { item_id: payload.item_id } : {},
13103
+ ...typeof payload.produced_count === "number" ? { produced_count: payload.produced_count } : {},
13104
+ ...payload.diagnostic ? { diagnostic: payload.diagnostic } : {},
13105
+ settled_at: payload.settled_at ?? ""
13106
+ });
13107
+ }
13108
+ return [...nachId.values()];
13109
+ }
13110
+ function jobsForPromptRun(events, runId) {
13111
+ return collectImageJobs(events).filter((j) => j.prompt_run_id === runId);
13112
+ }
13113
+ function findImageJob(events, jobId) {
13114
+ return collectImageJobs(events).find((j) => j.job_id === jobId) ?? null;
13115
+ }
13116
+ function claimWon(job, claimId) {
13117
+ return Boolean(job && job.claim && job.claim.claim_id === claimId);
13118
+ }
13119
+ function generationBatchIdOf(jobId) {
13120
+ return `job-${jobId}`;
13121
+ }
13122
+
13123
+ // src/lib/szenen/imageJobPort.ts
13124
+ var ANSPRUCH_VERSUCHE = 4;
13125
+ function fehler4(error, aktion) {
13126
+ return {
13127
+ message: `${aktion}: ${error instanceof Error ? error.message : String(error)}`
13128
+ };
13129
+ }
13130
+ function createImageJobPort(options) {
13131
+ const { channel, imageGeneration, storeId, clientId } = options;
13132
+ const newClaimId = options.newClaimId ?? (() => crypto.randomUUID());
13133
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
13134
+ return {
13135
+ async listJobs(runId) {
13136
+ try {
13137
+ return { kind: "ok", value: jobsForPromptRun(await channel.readEvents(), runId) };
13138
+ } catch (error) {
13139
+ return {
13140
+ kind: "error",
13141
+ ...fehler4(error, `Die Bildauftraege des Laufs ${runId} konnten nicht gelesen werden`),
13142
+ retryable: true
13143
+ };
13144
+ }
13145
+ },
13146
+ async claimAndRun(jobId) {
13147
+ let letzterGrund = "";
13148
+ for (let versuch = 1; versuch <= ANSPRUCH_VERSUCHE; versuch += 1) {
13149
+ let kopf;
13150
+ try {
13151
+ kopf = await channel.readHead();
13152
+ } catch (error) {
13153
+ return {
13154
+ kind: "error",
13155
+ ...fehler4(error, `Der Kopf-Commit des Bestands war nicht lesbar; Bildauftrag ${jobId} wurde nicht uebernommen`),
13156
+ retryable: true
13157
+ };
13158
+ }
13159
+ let auftrag;
13160
+ try {
13161
+ auftrag = findImageJob(await channel.readEventsAt(kopf), jobId);
13162
+ } catch (error) {
13163
+ return {
13164
+ kind: "error",
13165
+ ...fehler4(error, `Der Bildauftrag ${jobId} konnte nicht gelesen werden`),
13166
+ retryable: true
13167
+ };
13168
+ }
13169
+ if (!auftrag) {
13170
+ return {
13171
+ kind: "rejected",
13172
+ message: `Der Bildauftrag ${jobId} steht nicht im autoritativen Log dieses Bestands.`
13173
+ };
13174
+ }
13175
+ if (auftrag.store_id !== storeId) {
13176
+ return {
13177
+ kind: "rejected",
13178
+ message: `Der Bildauftrag ${jobId} gilt fuer den Bestand "${auftrag.store_id}", dieser Host arbeitet auf "${storeId}". Nutzer, Umgebung und Bestand muessen zusammenpassen.`
13179
+ };
13180
+ }
13181
+ if (auftrag.status !== "pending") {
13182
+ return {
13183
+ kind: "lost",
13184
+ job: auftrag,
13185
+ message: `Der Bildauftrag ${jobId} ist bereits "${auftrag.status}"` + (auftrag.claim ? ` (Client ${auftrag.claim.client_id})` : "") + ". Er wird nicht erneut ausgefuehrt \u2014 ein zweiter Lauf waere ein zweites Mal bezahlte Rechenzeit."
13186
+ };
13187
+ }
13188
+ const anspruch = {
13189
+ job_id: jobId,
13190
+ claim_id: newClaimId(),
13191
+ client_id: clientId,
13192
+ claimed_at: now().toISOString()
13193
+ };
13194
+ try {
13195
+ await channel.appendEventAt(
13196
+ SZENEN_EVENT_TYPES.imageJobClaimed,
13197
+ anspruch,
13198
+ kopf
13199
+ );
13200
+ } catch (error) {
13201
+ if (istPreconditionFehler(error)) {
13202
+ letzterGrund = error instanceof Error ? error.message : String(error);
13203
+ continue;
13204
+ }
13205
+ return {
13206
+ kind: "error",
13207
+ ...fehler4(error, `Der Anspruch auf Bildauftrag ${jobId} konnte nicht angehaengt werden`),
13208
+ retryable: true
13209
+ };
13210
+ }
13211
+ const gewonnen = {
13212
+ ...auftrag,
13213
+ status: "claimed",
13214
+ claim: anspruch,
13215
+ claim_attempts: [...auftrag.claim_attempts, anspruch]
13216
+ };
13217
+ return await fuehreAus(gewonnen, anspruch, jobId);
13218
+ }
13219
+ return {
13220
+ kind: "error",
13221
+ message: `Der Bildauftrag ${jobId} liess sich in ${ANSPRUCH_VERSUCHE} Versuchen nicht exklusiv uebernehmen \u2014 der Bestand wird gerade von anderer Seite beschrieben (${letzterGrund}). Es wurde nichts ausgefuehrt und nichts bezahlt; ein neuer Versuch ist gefahrlos.`,
13222
+ retryable: true
13223
+ };
13224
+ }
13225
+ };
13226
+ async function fuehreAus(gewonnen, anspruch, jobId) {
13227
+ const ergebnis = await imageGeneration.startFromPromptRun({
13228
+ runId: gewonnen.prompt_run_id,
13229
+ generationBatchId: generationBatchIdOf(jobId),
13230
+ executionTargetModelKey: gewonnen.execution_target_model_key,
13231
+ executionProfileId: gewonnen.execution_profile_id,
13232
+ imageCount: gewonnen.image_count,
13233
+ ...gewonnen.aspect_ratio ? { aspectRatio: gewonnen.aspect_ratio } : {}
13234
+ });
13235
+ const abschluss = {
13236
+ job_id: jobId,
13237
+ claim_id: anspruch.claim_id,
13238
+ ...abschlussFelder(ergebnis, jobId),
13239
+ settled_at: now().toISOString()
13240
+ };
13241
+ try {
13242
+ await channel.appendEvent(SZENEN_EVENT_TYPES.imageJobSettled, abschluss);
13243
+ } catch {
13244
+ }
13245
+ const gelesen = await channel.readEvents().catch(() => null);
13246
+ return {
13247
+ kind: "ok",
13248
+ job: (gelesen && findImageJob(gelesen, jobId)) ?? {
13249
+ ...gewonnen,
13250
+ status: abschluss.status,
13251
+ ...abschluss.generation_batch_id ? { generation_batch_id: abschluss.generation_batch_id } : {},
13252
+ ...abschluss.diagnostic ? { diagnostic: abschluss.diagnostic } : {},
13253
+ settled_at: abschluss.settled_at
13254
+ }
13255
+ };
13256
+ }
13257
+ }
13258
+ function istPreconditionFehler(error) {
13259
+ return Boolean(error && typeof error === "object" && error.name === "HfPreconditionFailed");
13260
+ }
13261
+ function abschlussFelder(ergebnis, jobId) {
13262
+ const batchId = generationBatchIdOf(jobId);
13263
+ switch (ergebnis.kind) {
13264
+ case "succeeded":
13265
+ return {
13266
+ status: "succeeded",
13267
+ generation_batch_id: batchId,
13268
+ item_id: ergebnis.batch.item_id,
13269
+ produced_count: ergebnis.batch.images.length
13270
+ };
13271
+ case "partial":
13272
+ return {
13273
+ status: "partial",
13274
+ generation_batch_id: batchId,
13275
+ item_id: ergebnis.batch.item_id,
13276
+ produced_count: ergebnis.produced,
13277
+ diagnostic: `Angefordert ${ergebnis.requested}, geliefert ${ergebnis.produced}.`
13278
+ };
13279
+ case "failed":
13280
+ return { status: "failed", produced_count: 0, diagnostic: ergebnis.message };
13281
+ case "unsupported":
13282
+ return {
13283
+ status: "rejected",
13284
+ produced_count: 0,
13285
+ diagnostic: `Zielmodell "${ergebnis.targetModelKey}" ist in dieser Umgebung nicht ausfuehrbar: ${ergebnis.reason}`
13286
+ };
13287
+ default:
13288
+ return { status: "rejected", produced_count: 0, diagnostic: ergebnis.message };
13289
+ }
13290
+ }
13291
+
12774
13292
  // src/lib/szenen/flowViewPort.ts
12775
13293
  var defaultContext = {
12776
13294
  async chainFor(contextId) {
@@ -12869,10 +13387,21 @@ function createFlowSzenenViewPort(options) {
12869
13387
  },
12870
13388
  ...options.now ? { now: options.now } : {}
12871
13389
  }) : void 0;
13390
+ const kanalMitRevision = typeof channel.readHead === "function" && typeof channel.readEventsAt === "function" && typeof channel.appendEventAt === "function";
13391
+ const imageJobs = imageGeneration && hatHfZugangsdaten && kanalMitRevision ? createImageJobPort({
13392
+ channel,
13393
+ imageGeneration,
13394
+ // Dieselbe Form, in der der Server seinen Bestand kennt (`storeId`).
13395
+ storeId: `hf:${options.namespace}`,
13396
+ clientId: options.imageJobClientId ?? crypto.randomUUID(),
13397
+ ...options.newImageJobClaimId ? { newClaimId: options.newImageJobClaimId } : {},
13398
+ ...options.now ? { now: options.now } : {}
13399
+ }) : void 0;
12872
13400
  const port = { elements, context, runs, derivation, imageBatchHistory };
12873
13401
  if (imageLibrary) port.imageLibrary = imageLibrary;
12874
13402
  if (imageBatchWrite) port.imageBatchWrite = imageBatchWrite;
12875
13403
  if (imageGeneration) port.imageGeneration = imageGeneration;
13404
+ if (imageJobs) port.imageJobs = imageJobs;
12876
13405
  return port;
12877
13406
  }
12878
13407
 
@@ -13012,7 +13541,7 @@ var VERBOTEN_JE_HERKUNFT = {
13012
13541
  draft_chain: ["shot_version_id", "scene_setup_version_id"],
13013
13542
  scene_direct: ["shot_version_id", "shot_element_id", "scene_setup_version_id", "camera_id"]
13014
13543
  };
13015
- function istText7(wert) {
13544
+ function istText8(wert) {
13016
13545
  return typeof wert === "string" && wert.trim().length > 0;
13017
13546
  }
13018
13547
  function isObject12(wert) {
@@ -13032,7 +13561,7 @@ function pruefeLaufherkunft(quelle) {
13032
13561
  }
13033
13562
  if (quelle.kind === "published") {
13034
13563
  for (const feld of ["shot_version_id", "scene_setup_version_id"]) {
13035
- if (!istText7(quelle[feld])) {
13564
+ if (!istText8(quelle[feld])) {
13036
13565
  return { ok: false, message: `"source.${feld}" fehlt oder ist leer.` };
13037
13566
  }
13038
13567
  }
@@ -13043,11 +13572,11 @@ function pruefeLaufherkunft(quelle) {
13043
13572
  if (!isObject12(szene)) {
13044
13573
  return { ok: false, message: '"source.scene" muss ein Objekt sein.' };
13045
13574
  }
13046
- if (!istText7(szene.element_id)) {
13575
+ if (!istText8(szene.element_id)) {
13047
13576
  return { ok: false, message: '"source.scene.element_id" fehlt oder ist leer.' };
13048
13577
  }
13049
- const ausEntwurf = istText7(szene.draft_id);
13050
- const ausVersion = istText7(szene.version_id);
13578
+ const ausEntwurf = istText8(szene.draft_id);
13579
+ const ausVersion = istText8(szene.version_id);
13051
13580
  if (ausEntwurf === ausVersion) {
13052
13581
  return {
13053
13582
  ok: false,
@@ -13068,7 +13597,7 @@ function pruefeLaufherkunft(quelle) {
13068
13597
  }
13069
13598
  return { ok: true };
13070
13599
  }
13071
- if (!istText7(quelle.chain_id)) {
13600
+ if (!istText8(quelle.chain_id)) {
13072
13601
  return { ok: false, message: '"source.chain_id" fehlt oder ist leer.' };
13073
13602
  }
13074
13603
  if (!Array.isArray(quelle.nodes) || quelle.nodes.length === 0) {
@@ -13087,7 +13616,7 @@ function pruefeLaufherkunft(quelle) {
13087
13616
  message: `"source.nodes[${index}].type" ist unbekannt: ${JSON.stringify(knoten.type)}.`
13088
13617
  };
13089
13618
  }
13090
- if (!istText7(knoten.element_id) || !istText7(knoten.draft_id)) {
13619
+ if (!istText8(knoten.element_id) || !istText8(knoten.draft_id)) {
13091
13620
  return {
13092
13621
  ok: false,
13093
13622
  message: `"source.nodes[${index}]" braucht element_id und draft_id.`
@@ -13100,10 +13629,10 @@ function pruefeLaufherkunft(quelle) {
13100
13629
  };
13101
13630
  }
13102
13631
  }
13103
- if (quelle.derivation_run_id !== void 0 && !istText7(quelle.derivation_run_id)) {
13632
+ if (quelle.derivation_run_id !== void 0 && !istText8(quelle.derivation_run_id)) {
13104
13633
  return { ok: false, message: '"source.derivation_run_id" ist gesetzt, aber leer.' };
13105
13634
  }
13106
- if (quelle.derivation_recovery_id !== void 0 && !istText7(quelle.derivation_recovery_id)) {
13635
+ if (quelle.derivation_recovery_id !== void 0 && !istText8(quelle.derivation_recovery_id)) {
13107
13636
  return { ok: false, message: '"source.derivation_recovery_id" ist gesetzt, aber leer.' };
13108
13637
  }
13109
13638
  return { ok: true };
@@ -13123,7 +13652,7 @@ function checkPromptRunRecord(candidate) {
13123
13652
  }
13124
13653
  const herkunft = isObject12(quelle) && typeof quelle.kind === "string" ? quelle.kind : "published";
13125
13654
  for (const feld of PFLICHTFELDER) {
13126
- if (!istText7(record[feld])) {
13655
+ if (!istText8(record[feld])) {
13127
13656
  return { ok: false, message: `"${feld}" fehlt oder ist leer.` };
13128
13657
  }
13129
13658
  }
@@ -13136,7 +13665,7 @@ function checkPromptRunRecord(candidate) {
13136
13665
  }
13137
13666
  }
13138
13667
  for (const feld of PFLICHT_JE_HERKUNFT[herkunft] ?? []) {
13139
- if (!istText7(record[feld])) {
13668
+ if (!istText8(record[feld])) {
13140
13669
  return {
13141
13670
  ok: false,
13142
13671
  message: `"${feld}" fehlt oder ist leer, wird bei Herkunft "${herkunft}" aber gebraucht.`
@@ -13144,7 +13673,7 @@ function checkPromptRunRecord(candidate) {
13144
13673
  }
13145
13674
  }
13146
13675
  for (const feld of VERBOTEN_JE_HERKUNFT[herkunft] ?? []) {
13147
- if (istText7(record[feld])) {
13676
+ if (istText8(record[feld])) {
13148
13677
  return {
13149
13678
  ok: false,
13150
13679
  message: `"${feld}" ist bei einem Lauf der Herkunft "${herkunft}" gesetzt. Dort gibt es diesen Wert nicht, und erfundene sind unzulaessig (D-98/D-135).`
@@ -13181,7 +13710,7 @@ function checkPromptRunRecord(candidate) {
13181
13710
  message: `"failure.class" ist unbekannt: ${JSON.stringify(failure.class)}. Erlaubt sind ${FAILURE_CLASSES.join(", ")}.`
13182
13711
  };
13183
13712
  }
13184
- if (!istText7(failure.message)) {
13713
+ if (!istText8(failure.message)) {
13185
13714
  return { ok: false, message: '"failure.message" fehlt oder ist leer.' };
13186
13715
  }
13187
13716
  return { ok: true };
@@ -13223,7 +13752,7 @@ function checkSceneDerivationRecord(candidate) {
13223
13752
  }
13224
13753
  const record = candidate;
13225
13754
  for (const feld of DERIVATION_PFLICHTFELDER) {
13226
- if (!istText7(record[feld])) {
13755
+ if (!istText8(record[feld])) {
13227
13756
  return { ok: false, message: `"${feld}" fehlt oder ist leer.` };
13228
13757
  }
13229
13758
  }
@@ -13278,13 +13807,13 @@ function checkSceneDerivationRecord(candidate) {
13278
13807
  return { ok: false, message: '"follows" muss ein Objekt sein.' };
13279
13808
  }
13280
13809
  const f = folgt;
13281
- if (!istText7(f.run_id)) {
13810
+ if (!istText8(f.run_id)) {
13282
13811
  return { ok: false, message: '"follows.run_id" fehlt oder ist leer.' };
13283
13812
  }
13284
13813
  if (f.run_id === record.run_id) {
13285
13814
  return { ok: false, message: '"follows.run_id" zeigt auf den Lauf selbst.' };
13286
13815
  }
13287
- if (!istText7(f.note)) {
13816
+ if (!istText8(f.note)) {
13288
13817
  return {
13289
13818
  ok: false,
13290
13819
  message: '"follows.note" fehlt oder ist leer \u2014 ohne Beanstandung ist ein Folgelauf nicht nachvollziehbar (D-89).'
@@ -13298,7 +13827,7 @@ function checkSceneDerivationRecord(candidate) {
13298
13827
  }
13299
13828
  }
13300
13829
  const reply = record.reply;
13301
- if (reply !== void 0 && !istText7(reply)) {
13830
+ if (reply !== void 0 && !istText8(reply)) {
13302
13831
  return {
13303
13832
  ok: false,
13304
13833
  message: `"reply" ist unbrauchbar: erwartet wird eine nicht leere Zeichenkette, angekommen ist ${JSON.stringify(reply)}. Ohne "reply" einreichen ist zulaessig.`
@@ -13318,7 +13847,7 @@ function checkSceneDerivationRecord(candidate) {
13318
13847
  message: `"failure.class" ist unbekannt: ${JSON.stringify(failure.class)}. Erlaubt sind ${FAILURE_CLASSES.join(", ")}.`
13319
13848
  };
13320
13849
  }
13321
- if (!istText7(failure.message)) {
13850
+ if (!istText8(failure.message)) {
13322
13851
  return { ok: false, message: '"failure.message" fehlt oder ist leer.' };
13323
13852
  }
13324
13853
  if (Array.isArray(record.proposals) && record.proposals.length > 0) {
@@ -13373,7 +13902,7 @@ function checkSceneDerivationRecord(candidate) {
13373
13902
  };
13374
13903
  }
13375
13904
  for (const [gapIndex, luecke] of gaps.entries()) {
13376
- if (!luecke || typeof luecke !== "object" || !istText7(luecke.pointer) || !istText7(luecke.reason)) {
13905
+ if (!luecke || typeof luecke !== "object" || !istText8(luecke.pointer) || !istText8(luecke.reason)) {
13377
13906
  return {
13378
13907
  ok: false,
13379
13908
  message: `"proposals[${index}].gaps[${gapIndex}]" braucht pointer und reason.`
@@ -13399,7 +13928,7 @@ function checkSceneDerivationRecoveryRecord(candidate) {
13399
13928
  }
13400
13929
  const record = candidate;
13401
13930
  for (const feld of ["recovery_id", "run_id", "created_at"]) {
13402
- if (!istText7(record[feld])) {
13931
+ if (!istText8(record[feld])) {
13403
13932
  return { ok: false, message: `"${feld}" fehlt oder ist leer.` };
13404
13933
  }
13405
13934
  }
@@ -13426,7 +13955,7 @@ function checkSceneDerivationRecoveryRecord(candidate) {
13426
13955
  return { ok: false, message: '"diagnostics" muss ein Array sein (auch ein leeres).' };
13427
13956
  }
13428
13957
  for (const [index, befund] of record.diagnostics.entries()) {
13429
- if (!befund || typeof befund !== "object" || !istText7(befund.code) || !istText7(befund.message)) {
13958
+ if (!befund || typeof befund !== "object" || !istText8(befund.code) || !istText8(befund.message)) {
13430
13959
  return {
13431
13960
  ok: false,
13432
13961
  message: `"diagnostics[${index}]" braucht code und message.`
@@ -13478,7 +14007,7 @@ function checkSceneDerivationRecoveryRecord(candidate) {
13478
14007
  }
13479
14008
  if (Array.isArray(gaps)) {
13480
14009
  for (const [gapIndex, luecke] of gaps.entries()) {
13481
- if (!luecke || typeof luecke !== "object" || !istText7(luecke.pointer) || !istText7(luecke.reason)) {
14010
+ if (!luecke || typeof luecke !== "object" || !istText8(luecke.pointer) || !istText8(luecke.reason)) {
13482
14011
  return {
13483
14012
  ok: false,
13484
14013
  message: `"proposals[${index}].gaps[${gapIndex}]" braucht pointer und reason.`
@@ -13514,6 +14043,120 @@ function checkSceneDerivationRecoveryRecord(candidate) {
13514
14043
  return { ok: true };
13515
14044
  }
13516
14045
 
14046
+ // src/lib/szenen/scenePromptOperation.ts
14047
+ var MCP_CLIENT_PROVIDER = "mcp_client";
14048
+ var UNVERIFIED_MODEL_ID = "mcp-client-unspecified";
14049
+ var istText9 = (wert) => typeof wert === "string" && wert.trim().length > 0;
14050
+ function collectPromptOperations(events) {
14051
+ const nachId = /* @__PURE__ */ new Map();
14052
+ for (const event of events) {
14053
+ if (event.type !== SZENEN_EVENT_TYPES.promptOperationPrepared) continue;
14054
+ const payload = event.payload;
14055
+ if (!payload || !istText9(payload.operation_id)) continue;
14056
+ if (nachId.has(payload.operation_id)) continue;
14057
+ nachId.set(payload.operation_id, payload);
14058
+ }
14059
+ return [...nachId.values()];
14060
+ }
14061
+ function findPromptOperation(events, operationId) {
14062
+ return collectPromptOperations(events).find((v) => v.operation_id === operationId) ?? null;
14063
+ }
14064
+ async function preparePromptOperation(input) {
14065
+ const now = input.now ?? (() => /* @__PURE__ */ new Date());
14066
+ const eingefroren = await freezeSceneWorkingState({
14067
+ sceneElementId: input.sceneElementId,
14068
+ store: input.store,
14069
+ resolver: input.resolver,
14070
+ contextChain: input.contextChain,
14071
+ userInstruction: input.userInstruction ?? null
14072
+ });
14073
+ if (eingefroren.kind !== "ok") return eingefroren;
14074
+ const rendererInput = eingefroren.input;
14075
+ return {
14076
+ kind: "ok",
14077
+ operation: {
14078
+ operation_id: input.operationId,
14079
+ scene: eingefroren.scene,
14080
+ context_id: input.contextId,
14081
+ user_instruction: input.userInstruction ?? null,
14082
+ renderer_input: rendererInput,
14083
+ renderer_instruction: buildSystemInstruction(rendererInput),
14084
+ renderer_payload: buildUserPrompt(rendererInput),
14085
+ response_contract: responseContractOf(rendererInput),
14086
+ prepared_by: input.preparedBy,
14087
+ created_at: now().toISOString()
14088
+ }
14089
+ };
14090
+ }
14091
+ function responseContractOf(input) {
14092
+ const model = input.target_model ?? {};
14093
+ return {
14094
+ format: "json",
14095
+ required_fields: ["positive", "negative", "feedback", "visibility", "notes"],
14096
+ negative_prompt_allowed: model.supports_negative_prompt === true,
14097
+ node_ids: input.scene.nodes.map((node) => node.node_id),
14098
+ prompt_language: typeof model.prompt_language === "string" ? model.prompt_language : "en"
14099
+ };
14100
+ }
14101
+ function completePromptOperation(input) {
14102
+ const now = input.now ?? (() => /* @__PURE__ */ new Date());
14103
+ const { operation } = input;
14104
+ const geparst = parseRunResponse(input.response);
14105
+ if (!geparst.ok) {
14106
+ return {
14107
+ kind: "rejected",
14108
+ message: `Die Antwort zu Vorgang ${operation.operation_id} ist nicht verwertbar: ${geparst.message}`
14109
+ };
14110
+ }
14111
+ const geprueft = checkRunResponse(geparst.value, operation.renderer_input);
14112
+ if (!geprueft.ok) {
14113
+ return {
14114
+ kind: "rejected",
14115
+ message: `Die Antwort zu Vorgang ${operation.operation_id} passt nicht zur eingefrorenen Eingabe: ${geprueft.message}`
14116
+ };
14117
+ }
14118
+ const origin = {
14119
+ host: "mcp",
14120
+ ...istText9(input.declaredModelId) ? { declared_model_id: input.declaredModelId } : {},
14121
+ renderer_instruction_was_system_prompt: false
14122
+ };
14123
+ return {
14124
+ kind: "ok",
14125
+ record: {
14126
+ run_id: input.runId,
14127
+ // Keine fingierten Kennungen (D-98/D-75) — genau wie beim
14128
+ // Szene-Direktlauf des bestehenden Pfads.
14129
+ shot_version_id: "",
14130
+ shot_element_id: "",
14131
+ scene_setup_version_id: "",
14132
+ camera_id: "",
14133
+ target_model_version_id: operation.renderer_input.target_model_resolution?.version_id ?? "",
14134
+ provider: MCP_CLIENT_PROVIDER,
14135
+ model_id: UNVERIFIED_MODEL_ID,
14136
+ // Die Renderer-Anweisung, wie sie der Vorgang eingefroren hat — nicht
14137
+ // eine zweite, jetzt neu gebaute Fassung.
14138
+ system_prompt: operation.renderer_instruction,
14139
+ input_payload: operation.renderer_input,
14140
+ status: "succeeded",
14141
+ failure: null,
14142
+ output: geprueft.output,
14143
+ raw_output: input.response,
14144
+ source: { kind: "scene_direct", scene: operation.scene },
14145
+ operation_id: operation.operation_id,
14146
+ execution_origin: origin,
14147
+ created_at: now().toISOString()
14148
+ }
14149
+ };
14150
+ }
14151
+ function runOfOperation(runs, operationId) {
14152
+ return runs.find(
14153
+ (run) => run.operation_id === operationId
14154
+ ) ?? null;
14155
+ }
14156
+ function sameResponseAs(run, response) {
14157
+ return (run.raw_output ?? "") === response;
14158
+ }
14159
+
13517
14160
  // src/lib/szenen/seedListe.ts
13518
14161
  var DEFAULT_SEED_VORLAGE = {
13519
14162
  name: "Default",
@@ -13608,9 +14251,9 @@ async function leseBilderAusZip(datei) {
13608
14251
  );
13609
14252
  }
13610
14253
  const { default: JSZip2 } = await import("jszip");
13611
- const archiv = await JSZip2.loadAsync(datei).catch((fehler4) => {
14254
+ const archiv = await JSZip2.loadAsync(datei).catch((fehler5) => {
13612
14255
  throw new Error(
13613
- `${name} konnte nicht als ZIP gelesen werden: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
14256
+ `${name} konnte nicht als ZIP gelesen werden: ${fehler5 instanceof Error ? fehler5.message : String(fehler5)}`
13614
14257
  );
13615
14258
  });
13616
14259
  const pfade = [];
@@ -13708,13 +14351,13 @@ function noticeFor(ergebnis, was) {
13708
14351
  };
13709
14352
  }
13710
14353
  case "rejected": {
13711
- const fehler4 = ergebnis.errors ?? [];
14354
+ const fehler5 = ergebnis.errors ?? [];
13712
14355
  const ungeprueft = ergebnis.unchecked ?? [];
13713
14356
  return {
13714
14357
  tone: "rejected",
13715
- text: `${was}: abgelehnt \u2014 ${fehler4.length} Befund${fehler4.length === 1 ? "" : "e"}.`,
14358
+ text: `${was}: abgelehnt \u2014 ${fehler5.length} Befund${fehler5.length === 1 ? "" : "e"}.`,
13716
14359
  details: [
13717
- ...fehler4.map((f) => `${f.path ?? "(ohne Pfad)"}: ${f.message ?? "ohne Meldung"}`),
14360
+ ...fehler5.map((f) => `${f.path ?? "(ohne Pfad)"}: ${f.message ?? "ohne Meldung"}`),
13718
14361
  // Nicht verschweigen: was nicht geprüft wurde, ist nicht in Ordnung,
13719
14362
  // sondern ungeprüft.
13720
14363
  ...ungeprueft.map((u) => `ungepr\xFCft: ${u}`)
@@ -15598,7 +16241,7 @@ var ERKLAERUNG = {
15598
16241
  };
15599
16242
  var CapabilityPanel = ({ port, elementId, setupPayload }) => {
15600
16243
  const [bericht, setBericht] = (0, import_react5.useState)(null);
15601
- const [fehler4, setFehler] = (0, import_react5.useState)(null);
16244
+ const [fehler5, setFehler] = (0, import_react5.useState)(null);
15602
16245
  (0, import_react5.useEffect)(() => {
15603
16246
  let abgemeldet = false;
15604
16247
  setBericht(null);
@@ -15620,10 +16263,10 @@ var CapabilityPanel = ({ port, elementId, setupPayload }) => {
15620
16263
  abgemeldet = true;
15621
16264
  };
15622
16265
  }, [port, elementId, setupPayload]);
15623
- if (fehler4) {
16266
+ if (fehler5) {
15624
16267
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { "data-testid": "capability-fehler", role: "alert", children: [
15625
16268
  "F\xE4higkeitsbericht nicht erstellbar: ",
15626
- fehler4
16269
+ fehler5
15627
16270
  ] });
15628
16271
  }
15629
16272
  if (!bericht) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { "data-testid": "capability-laden", children: "F\xE4higkeitsbericht wird erstellt \u2026" });
@@ -16030,6 +16673,7 @@ var RunImagesPanel = ({ port, lauf }) => {
16030
16673
  const neuLesen = () => setNachladen((n) => n + 1);
16031
16674
  const [erzeugenOffen, setErzeugenOffen] = (0, import_react8.useState)(null);
16032
16675
  const [zuordnenOffen, setZuordnenOffen] = (0, import_react8.useState)(null);
16676
+ const [chatAuftraegeOffen, setChatAuftraegeOffen] = (0, import_react8.useState)(null);
16033
16677
  const quelle = sourceOf(lauf);
16034
16678
  const [wahlPhase] = usePortQuery(
16035
16679
  () => port.imageGeneration ? port.imageGeneration.imageModelChoiceForRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: null }),
@@ -16044,10 +16688,17 @@ var RunImagesPanel = ({ port, lauf }) => {
16044
16688
  () => port.imageBatchHistory ? port.imageBatchHistory.listBatchesForPromptRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: [] }),
16045
16689
  [port, lauf.run_id, nachladen]
16046
16690
  );
16691
+ const [chatAuftraegePhase] = usePortQuery(
16692
+ () => port.imageJobs ? port.imageJobs.listJobs(lauf.run_id) : Promise.resolve({ kind: "ok", value: [] }),
16693
+ [port, lauf.run_id, nachladen]
16694
+ );
16047
16695
  const auftraege = auftraegePhase.status === "ready" ? auftraegePhase.value : [];
16048
16696
  const batches = batchesPhase.status === "ready" ? batchesPhase.value : [];
16697
+ const chatAuftraege = chatAuftraegePhase.status === "ready" ? chatAuftraegePhase.value : [];
16698
+ const offeneChatAuftraege = chatAuftraege.filter((a) => a.status === "pending").length;
16049
16699
  const auffaellige = auftraege.filter((a) => a.status !== "succeeded");
16050
16700
  const erzeugenAuf = erzeugenOffen ?? batches.length === 0;
16701
+ const chatAuftraegeAuf = chatAuftraegeOffen ?? offeneChatAuftraege > 0;
16051
16702
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "szenen-bilder", "data-testid": `bilder-${lauf.run_id}`, children: [
16052
16703
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { className: "szenen-bilder__eingang", "data-testid": `eingang-${lauf.run_id}`, children: [
16053
16704
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h5", { children: "Eingang \u2014 woraus dieser Prompt entstand" }),
@@ -16168,6 +16819,43 @@ var RunImagesPanel = ({ port, lauf }) => {
16168
16819
  }
16169
16820
  )
16170
16821
  ] }),
16822
+ faehig.imageJobs && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
16823
+ "details",
16824
+ {
16825
+ className: "szenen-bilder__aktion",
16826
+ "data-testid": `auftraege-chat-bereich-${lauf.run_id}`,
16827
+ open: chatAuftraegeAuf,
16828
+ children: [
16829
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
16830
+ "summary",
16831
+ {
16832
+ onClick: (e) => {
16833
+ e.preventDefault();
16834
+ setChatAuftraegeOffen(!chatAuftraegeAuf);
16835
+ },
16836
+ children: [
16837
+ "Bildauftraege aus dem Chat",
16838
+ offeneChatAuftraege > 0 ? ` (${offeneChatAuftraege} wartend)` : ""
16839
+ ]
16840
+ }
16841
+ ),
16842
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
16843
+ ChatAuftraegeAktion,
16844
+ {
16845
+ port,
16846
+ lauf,
16847
+ auftraege: chatAuftraege,
16848
+ fehler: chatAuftraegePhase.status === "error" ? chatAuftraegePhase.message : null,
16849
+ laedt: chatAuftraegePhase.status === "loading",
16850
+ onFertig: () => {
16851
+ setChatAuftraegeOffen(true);
16852
+ neuLesen();
16853
+ }
16854
+ }
16855
+ )
16856
+ ]
16857
+ }
16858
+ ),
16171
16859
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
16172
16860
  "details",
16173
16861
  {
@@ -16351,7 +17039,7 @@ function useBildQuellen(port, bilder) {
16351
17039
  erneutVersuchen: () => setVersuch((n) => n + 1)
16352
17040
  };
16353
17041
  }
16354
- var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "szenen-bilder__meta", "data-testid": `batchbild-${bild.media_ref}`, children: [
17042
+ var BildDetails = ({ bild, fehler: fehler5, aufloesbar }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "szenen-bilder__meta", "data-testid": `batchbild-${bild.media_ref}`, children: [
16355
17043
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: bild.media_ref }),
16356
17044
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
16357
17045
  "Position ",
@@ -16364,7 +17052,7 @@ var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ (0,
16364
17052
  "\xD7",
16365
17053
  bild.height
16366
17054
  ] }) : null,
16367
- fehler4 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("em", { "data-testid": `batchbild-fehler-${bild.media_ref}`, children: fehler4 }),
17055
+ fehler5 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("em", { "data-testid": `batchbild-fehler-${bild.media_ref}`, children: fehler5 }),
16368
17056
  !aufloesbar && // Capability-Grenze, kein Fehler: Dieser Host kann Batches lesen,
16369
17057
  // aber keine Bilder auflösen (E). Kein erfundener Fallback auf eine
16370
17058
  // Serverroute (A6).
@@ -16380,18 +17068,18 @@ var HERKUNFT_KURZ = {
16380
17068
  external_import: "extern importiert"
16381
17069
  };
16382
17070
  var BatchKachel = ({ port, batch }) => {
16383
- const { quellen, fehler: fehler4, wiederholbar, erneutVersuchen } = useBildQuellen(port, batch.images);
17071
+ const { quellen, fehler: fehler5, wiederholbar, erneutVersuchen } = useBildQuellen(port, batch.images);
16384
17072
  const aufloesbar = Boolean(port.imageLibrary);
16385
17073
  const bilder = batch.images.map((bild) => ({
16386
17074
  id: bild.media_ref,
16387
17075
  quelle: quellen.get(bild.media_ref) ?? null,
16388
17076
  titel: `Bild ${bild.position + 1}`,
16389
- ...fehler4.get(bild.media_ref) ? { leerText: "Nicht ladbar" } : {},
17077
+ ...fehler5.get(bild.media_ref) ? { leerText: "Nicht ladbar" } : {},
16390
17078
  details: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
16391
17079
  BildDetails,
16392
17080
  {
16393
17081
  bild,
16394
- fehler: fehler4.get(bild.media_ref) ?? null,
17082
+ fehler: fehler5.get(bild.media_ref) ?? null,
16395
17083
  aufloesbar
16396
17084
  }
16397
17085
  )
@@ -16451,7 +17139,7 @@ var BatchKachel = ({ port, batch }) => {
16451
17139
  BildDetails,
16452
17140
  {
16453
17141
  bild,
16454
- fehler: fehler4.get(bild.media_ref) ?? null,
17142
+ fehler: fehler5.get(bild.media_ref) ?? null,
16455
17143
  aufloesbar
16456
17144
  }
16457
17145
  ) }, bild.media_ref)) })
@@ -16720,11 +17408,11 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
16720
17408
  setSeedListe(DEFAULT_SEED_VORLAGE.seeds);
16721
17409
  setVorlageKey(DEFAULT_VORLAGE_KEY);
16722
17410
  }
16723
- } catch (fehler4) {
17411
+ } catch (fehler5) {
16724
17412
  if (veraltet()) return;
16725
17413
  setNotiz({
16726
17414
  tone: "error",
16727
- text: fehler4 instanceof Error ? fehler4.message : String(fehler4)
17415
+ text: fehler5 instanceof Error ? fehler5.message : String(fehler5)
16728
17416
  });
16729
17417
  }
16730
17418
  if (veraltet()) return;
@@ -16743,10 +17431,10 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
16743
17431
  setNotiz(
16744
17432
  ergebnis.kind === "ok" ? { tone: "ok", text: `Importiert als Item ${ergebnis.value.item_id} mit ${bilder.length} Bild(ern).` } : noticeFor(ergebnis, "Externe Bilder importieren")
16745
17433
  );
16746
- } catch (fehler4) {
17434
+ } catch (fehler5) {
16747
17435
  setNotiz({
16748
17436
  tone: "error",
16749
- text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
17437
+ text: `Externe Bilder importieren: ${fehler5 instanceof Error ? fehler5.message : String(fehler5)}`
16750
17438
  });
16751
17439
  }
16752
17440
  setLaeuft(false);
@@ -16768,10 +17456,10 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
16768
17456
  ...seed !== void 0 ? { seed } : {}
16769
17457
  });
16770
17458
  }
16771
- } catch (fehler4) {
17459
+ } catch (fehler5) {
16772
17460
  setNotiz({
16773
17461
  tone: "error",
16774
- text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
17462
+ text: `Externe Bilder importieren: ${fehler5 instanceof Error ? fehler5.message : String(fehler5)}`
16775
17463
  });
16776
17464
  return;
16777
17465
  }
@@ -17107,6 +17795,101 @@ function readAsBase64(datei) {
17107
17795
  leser.readAsDataURL(datei);
17108
17796
  });
17109
17797
  }
17798
+ var CHAT_AUFTRAG_TEXT = {
17799
+ pending: "wartet auf \xDCbernahme",
17800
+ // Wie bei `started` (D-119): laufend und abgebrochen sind aus dem Log
17801
+ // nicht unterscheidbar, und es gibt keinen automatischen Retry, der die
17802
+ // Unterscheidung bräuchte.
17803
+ claimed: "\xFCbernommen (l\xE4uft oder unterbrochen)",
17804
+ succeeded: "erfolgreich",
17805
+ partial: "teilweise",
17806
+ failed: "fehlgeschlagen",
17807
+ rejected: "abgelehnt \u2014 es wurde nichts ausgef\xFChrt"
17808
+ };
17809
+ var ChatAuftraegeAktion = ({
17810
+ port,
17811
+ lauf,
17812
+ auftraege,
17813
+ fehler: fehler5,
17814
+ laedt,
17815
+ onFertig
17816
+ }) => {
17817
+ const [laufend, setLaufend] = (0, import_react8.useState)(null);
17818
+ const [notiz, setNotiz] = (0, import_react8.useState)(null);
17819
+ async function uebernehmen(jobId) {
17820
+ if (!port.imageJobs) return;
17821
+ setLaufend(jobId);
17822
+ setNotiz(null);
17823
+ try {
17824
+ const ergebnis = await port.imageJobs.claimAndRun(jobId);
17825
+ if (ergebnis.kind === "ok") {
17826
+ setNotiz({
17827
+ tone: ergebnis.job.status === "succeeded" ? "ok" : "rejected",
17828
+ text: `Auftrag ${jobId}: ${CHAT_AUFTRAG_TEXT[ergebnis.job.status]}.` + (ergebnis.job.diagnostic ? ` ${ergebnis.job.diagnostic}` : "")
17829
+ });
17830
+ } else if (ergebnis.kind === "lost") {
17831
+ setNotiz({ tone: "rejected", text: ergebnis.message });
17832
+ } else {
17833
+ setNotiz({ tone: "error", text: ergebnis.message });
17834
+ }
17835
+ } catch (e) {
17836
+ setNotiz({
17837
+ tone: "error",
17838
+ text: `Die \xDCbernahme von Auftrag ${jobId} ist technisch gescheitert: ` + (e instanceof Error ? e.message : String(e))
17839
+ });
17840
+ } finally {
17841
+ setLaufend(null);
17842
+ onFertig();
17843
+ }
17844
+ }
17845
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "szenen-bilder__aktion-inhalt", "data-testid": `chat-auftraege-${lauf.run_id}`, children: [
17846
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "szenen-area__lead", children: "Auftr\xE4ge, die \xFCber den MCP aus dem Chat kamen. Der Prompt wird nicht kopiert \u2014 ausgef\xFChrt wird der eingefrorene Prompt dieses Laufs." }),
17847
+ fehler5 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { role: "alert", "data-testid": `chat-auftraege-fehler-${lauf.run_id}`, children: [
17848
+ "Bildauftr\xE4ge aus dem Chat konnten nicht gelesen werden: ",
17849
+ fehler5
17850
+ ] }),
17851
+ !fehler5 && !laedt && auftraege.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { "data-testid": `chat-auftraege-leer-${lauf.run_id}`, children: "F\xFCr diesen Lauf liegt kein Auftrag aus dem Chat vor." }),
17852
+ notiz && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
17853
+ "p",
17854
+ {
17855
+ role: notiz.tone === "error" ? "alert" : void 0,
17856
+ "data-testid": `chat-auftraege-notiz-${lauf.run_id}`,
17857
+ children: notiz.text
17858
+ }
17859
+ ),
17860
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { className: "szenen-bilder__auftraege", children: auftraege.map((auftrag) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { "data-testid": `chat-auftrag-${auftrag.job_id}`, children: [
17861
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: CHAT_AUFTRAG_TEXT[auftrag.status] }),
17862
+ " \u2014 ",
17863
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
17864
+ auftrag.image_count,
17865
+ " ",
17866
+ auftrag.image_count === 1 ? "Bild" : "Bilder",
17867
+ " mit",
17868
+ " ",
17869
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: auftrag.execution_target_model_key }),
17870
+ " / ",
17871
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: auftrag.execution_profile_id }),
17872
+ auftrag.aspect_ratio ? ` im Format ${auftrag.aspect_ratio}` : ""
17873
+ ] }),
17874
+ auftrag.diagnostic && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
17875
+ " ",
17876
+ auftrag.diagnostic
17877
+ ] }),
17878
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("br", {}),
17879
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { className: "szenen-bilder__auftragskennung", children: auftrag.job_id }),
17880
+ auftrag.status === "pending" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
17881
+ "button",
17882
+ {
17883
+ type: "button",
17884
+ disabled: laufend !== null,
17885
+ onClick: () => void uebernehmen(auftrag.job_id),
17886
+ "data-testid": `chat-auftrag-uebernehmen-${auftrag.job_id}`,
17887
+ children: laufend === auftrag.job_id ? "\xDCbernahme l\xE4uft\u2026" : "\xDCbernehmen und ausf\xFChren (kostet einen Bildaufruf)"
17888
+ }
17889
+ )
17890
+ ] }, auftrag.job_id)) })
17891
+ ] });
17892
+ };
17110
17893
 
17111
17894
  // src/features/szenen/areas/RunsPanel.tsx
17112
17895
  var import_jsx_runtime9 = require("react/jsx-runtime");
@@ -19389,6 +20172,7 @@ var SzenenView = ({
19389
20172
  DERIVABLE_TYPES,
19390
20173
  ELEMENT_VERSION_SCHEMA_FILE,
19391
20174
  FLOW_EXECUTION_PATH,
20175
+ MCP_CLIENT_PROVIDER,
19392
20176
  PROBE_VERSION_ID,
19393
20177
  PROMPT_RULE_EFFECT,
19394
20178
  REFERENCE_FIELD,
@@ -19403,8 +20187,12 @@ var SzenenView = ({
19403
20187
  SZENEN_EVENT_TYPES,
19404
20188
  SZENEN_ITEM_PREFIX,
19405
20189
  SZENEN_SCHEMAS,
20190
+ SZENEN_SNAPSHOT_FILE,
20191
+ SZENEN_SNAPSHOT_PREFIX,
20192
+ SZENEN_SNAPSHOT_SCHEMA_VERSION,
19406
20193
  SzenenView,
19407
20194
  UNCHECKED_PUBLISH_RULES,
20195
+ UNVERIFIED_MODEL_ID,
19408
20196
  UPLOAD_MAX_BYTES,
19409
20197
  UnsupportedSchemaError,
19410
20198
  VersionDagError,
@@ -19415,6 +20203,7 @@ var SzenenView = ({
19415
20203
  buildDerivationSystemInstruction,
19416
20204
  buildDerivationUserPrompt,
19417
20205
  buildSystemInstruction,
20206
+ buildSzenenSnapshot,
19418
20207
  buildUserPrompt,
19419
20208
  buildVersionDag,
19420
20209
  callOptional,
@@ -19445,14 +20234,18 @@ var SzenenView = ({
19445
20234
  checkSzenenBatchOrigin,
19446
20235
  checkTitle,
19447
20236
  checkWriteDraftInput,
20237
+ claimWon,
19448
20238
  collectImageBatches,
19449
20239
  collectImageGenerationOrders,
20240
+ collectImageJobs,
20241
+ collectPromptOperations,
19450
20242
  collectPromptRuns,
19451
20243
  collectProviderChecks,
19452
20244
  collectSceneDerivationRecoveries,
19453
20245
  collectSceneDerivations,
19454
20246
  collectVersionRefs,
19455
20247
  compileSchemas,
20248
+ completePromptOperation,
19456
20249
  createBrowserTextGenerationPort,
19457
20250
  createClientDerivationPort,
19458
20251
  createClientRunPort,
@@ -19470,6 +20263,7 @@ var SzenenView = ({
19470
20263
  createHfImageLibrary,
19471
20264
  createHfImageStore,
19472
20265
  createImageGenerationPort,
20266
+ createImageJobPort,
19473
20267
  createInMemoryEventChannel,
19474
20268
  createMemoryViewPort,
19475
20269
  createSimulationSceneDerivationPort,
@@ -19488,6 +20282,8 @@ var SzenenView = ({
19488
20282
  findExecutableImageModel,
19489
20283
  findImageBatch,
19490
20284
  findImageGenerationOrder,
20285
+ findImageJob,
20286
+ findPromptOperation,
19491
20287
  findPromptRun,
19492
20288
  findProviderCheck,
19493
20289
  findSceneDerivation,
@@ -19497,6 +20293,7 @@ var SzenenView = ({
19497
20293
  formatSeedListe,
19498
20294
  freezeDraftChain,
19499
20295
  freezeSceneWorkingState,
20296
+ generationBatchIdOf,
19500
20297
  gesamtHerkunft,
19501
20298
  getSzenenSchemas,
19502
20299
  gruppiereZuItems,
@@ -19505,14 +20302,20 @@ var SzenenView = ({
19505
20302
  isOk,
19506
20303
  isProjectionCurrent,
19507
20304
  isSzenenItemId,
20305
+ istSzenenSnapshotDatei,
19508
20306
  istZipDatei,
19509
20307
  itemIdForRun,
20308
+ itemsAusBestand,
20309
+ jobsForPromptRun,
19510
20310
  ladeVorschauen,
19511
20311
  leseBilderAusZip,
19512
20312
  linkFor,
20313
+ locatorAus,
19513
20314
  locatorTeile,
19514
20315
  logRevisionOf,
20316
+ mergeSnapshotAndPending,
19515
20317
  mergeSzenenBatchOrigin,
20318
+ mimeTypAus,
19516
20319
  mitDefaultVorlage,
19517
20320
  noticeFor,
19518
20321
  ordersForPromptRun,
@@ -19520,8 +20323,10 @@ var SzenenView = ({
19520
20323
  parseDerivationResponse,
19521
20324
  parseRunResponse,
19522
20325
  parseSeedListe,
20326
+ parseSzenenSnapshot,
19523
20327
  preferredHeadRevisionOf,
19524
20328
  preferredVersionId,
20329
+ preparePromptOperation,
19525
20330
  promptDiagnosticsOf,
19526
20331
  publishedHeadsOf,
19527
20332
  recoveriesForRun,
@@ -19534,19 +20339,23 @@ var SzenenView = ({
19534
20339
  resolveShotPayload,
19535
20340
  resolveTargetModelOver,
19536
20341
  resolverFromQueries,
20342
+ responseContractOf,
19537
20343
  roundOfRun,
19538
20344
  rulesWithoutPromptEffect,
19539
20345
  runElementStoreContract,
20346
+ runOfOperation,
19540
20347
  runPromptDerivation,
19541
20348
  runPublishChecks,
19542
20349
  runSceneDerivation,
19543
20350
  runStoreDifferential,
20351
+ sameResponseAs,
19544
20352
  sceneOnlyCapabilities,
19545
20353
  scopeOfPath,
19546
20354
  seedFehlerText,
19547
20355
  selectableTargetModels,
19548
20356
  showChain,
19549
20357
  simulatedAnswerFor,
20358
+ szenenSnapshotFilename,
19550
20359
  totalModelCalls,
19551
20360
  typAusBytes,
19552
20361
  unclassifiedPublishRules,