@neta-art/cohub 11.0.0 → 11.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +31 -5
  2. package/dist/board/editor/creation.d.ts +2 -2
  3. package/dist/board/editor/creation.js +3 -3
  4. package/dist/board/editor/editor.d.ts +4 -4
  5. package/dist/board/editor/editor.js +1 -1
  6. package/dist/board/editor/index.d.ts +2 -2
  7. package/dist/board/editor/interaction.d.ts +8 -2
  8. package/dist/board/editor/interaction.js +34 -1
  9. package/dist/board/editor/snapshots.d.ts +0 -2
  10. package/dist/board/editor/snapshots.js +4 -20
  11. package/dist/board/export/scene.js +4 -1
  12. package/dist/board/model/image-key.js +1 -1
  13. package/dist/board/model/index.d.ts +8 -7
  14. package/dist/board/model/index.js +6 -5
  15. package/dist/board/model/items/create.d.ts +16 -3
  16. package/dist/board/model/items/create.js +125 -5
  17. package/dist/board/model/items/index.d.ts +2 -2
  18. package/dist/board/model/items/index.js +2 -2
  19. package/dist/board/model/media-playback.js +1 -1
  20. package/dist/board/model/overlay-placement.d.ts +25 -0
  21. package/dist/board/model/overlay-placement.js +35 -0
  22. package/dist/board/model/replay.d.ts +1 -1
  23. package/dist/board/model/replay.js +1 -0
  24. package/dist/board/model/script-hit.d.ts +20 -3
  25. package/dist/board/model/script-hit.js +81 -24
  26. package/dist/board/model/script-observers.d.ts +18 -0
  27. package/dist/board/model/script-observers.js +92 -0
  28. package/dist/board/model/script-scene.d.ts +1 -1
  29. package/dist/board/model/script-scene.js +35 -8
  30. package/dist/board/model/script.d.ts +51 -7
  31. package/dist/board/render/board-background.js +5 -4
  32. package/dist/board/render/index.d.ts +2 -2
  33. package/dist/board/render/index.js +2 -2
  34. package/dist/board/render/media-interaction.js +1 -1
  35. package/dist/board/render/renderers/board-renderer-registry.d.ts +17 -2
  36. package/dist/board/render/renderers/board-renderer-registry.js +8 -1
  37. package/dist/board/render/renderers/custom-card-renderer.js +2 -1
  38. package/dist/board/render/renderers/task-card-renderer.d.ts +2 -1
  39. package/dist/board/render/renderers/task-card-renderer.js +220 -71
  40. package/dist/board/render/script-host.d.ts +1 -1
  41. package/dist/board/render/script-host.js +53 -28
  42. package/dist/board/render/script-runtime.js +13 -3
  43. package/dist/board/render/script-texture.d.ts +11 -0
  44. package/dist/board/render/script-texture.js +33 -0
  45. package/dist/board/stage/assets.js +1 -1
  46. package/dist/board/stage/scene.d.ts +2 -0
  47. package/dist/board/stage/scene.js +17 -1
  48. package/dist/board/stage/script-pointer.d.ts +1 -0
  49. package/dist/board/stage/script-pointer.js +8 -2
  50. package/dist/board/stage/stage.d.ts +11 -3
  51. package/dist/board/stage/stage.js +118 -21
  52. package/dist/chunks/environment.d.ts +40 -5
  53. package/dist/chunks/environment.js +12 -4
  54. package/dist/chunks/http.d.ts +3 -1
  55. package/dist/chunks/http.js +2 -1
  56. package/dist/chunks/transport.js +1 -1
  57. package/dist/chunks/websocket.d.ts +1 -1
  58. package/dist/http.d.ts +2 -2
  59. package/dist/index.d.ts +2 -2
  60. package/dist/index.js +151 -139
  61. package/dist/protocol/dist/board-model.d.ts +36 -1
  62. package/dist/protocol/dist/board-model.js +14 -4
  63. package/dist/protocol/dist/board-task.d.ts +22 -0
  64. package/dist/{board/model/task.js → protocol/dist/board-task.js} +34 -7
  65. package/dist/protocol/dist/generation/blocks.d.ts +8 -0
  66. package/dist/{generation-blocks.js → protocol/dist/generation/blocks.js} +14 -15
  67. package/dist/protocol/dist/index.d.ts +5 -2
  68. package/dist/protocol/dist/task/index.d.ts +4 -0
  69. package/package.json +1 -1
  70. package/dist/board/model/task.d.ts +0 -11
  71. package/dist/types.d.ts +0 -36
package/dist/index.js CHANGED
@@ -381,8 +381,16 @@ const fileProps = {
381
381
  coverUrl: z.string().optional()
382
382
  }).strict().optional()
383
383
  };
384
- const timedFileProps = {
384
+ const BoardMediaOriginSchema = z.object({
385
+ taskRunId: z.string().min(1).max(160),
386
+ artifactId: z.string().min(1).max(240)
387
+ }).strict();
388
+ const mediaProps = {
385
389
  ...fileProps,
390
+ origin: BoardMediaOriginSchema.optional()
391
+ };
392
+ const timedFileProps = {
393
+ ...mediaProps,
386
394
  time: nonNegative.optional()
387
395
  };
388
396
  const BoardCropSchema = z.object({
@@ -395,7 +403,7 @@ const BoardImageItemSchema = z.object({
395
403
  ...boxed(640, 360),
396
404
  type: z.literal("image"),
397
405
  props: z.object({
398
- ...fileProps,
406
+ ...mediaProps,
399
407
  crop: BoardCropSchema.optional()
400
408
  }).strict()
401
409
  }).strict();
@@ -460,8 +468,10 @@ const BoardTaskSnapshotSchema = z.object({
460
468
  title: z.string().min(1).max(240),
461
469
  model: z.string().max(160).optional(),
462
470
  promptExcerpt: z.string().max(480).optional(),
471
+ errorExcerpt: z.string().max(240).optional(),
463
472
  artifactCount: z.number().int().nonnegative(),
464
473
  artifacts: z.array(BoardTaskArtifactSchema).max(6).default([]),
474
+ startedAt: z.string().max(64).optional(),
465
475
  updatedAt: z.string().optional()
466
476
  }).strict();
467
477
  const BoardTaskItemSchema = z.object({
@@ -469,7 +479,7 @@ const BoardTaskItemSchema = z.object({
469
479
  type: z.literal("task"),
470
480
  props: z.object({
471
481
  taskRunId: z.string().min(1).max(160),
472
- snapshot: BoardTaskSnapshotSchema
482
+ snapshot: BoardTaskSnapshotSchema.describe("Maintained by the server for tasks in the Board's Space; writers may omit it.")
473
483
  }).strict()
474
484
  }).strict();
475
485
  const BOARD_EFFECT_KINDS = [
@@ -1751,10 +1761,132 @@ function parseBoardDocument(input) {
1751
1761
  } : result;
1752
1762
  }
1753
1763
  //#endregion
1764
+ //#region ../protocol/dist/generation/blocks.js
1765
+ const EXCERPT_LIMIT = 240;
1766
+ function asRecord(value) {
1767
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
1768
+ }
1769
+ function cleanExcerpt(value, limit = EXCERPT_LIMIT) {
1770
+ if (typeof value !== "string") return void 0;
1771
+ const clean = value.replace(/\s+/g, " ").trim();
1772
+ if (!clean) return void 0;
1773
+ return clean.length > limit ? `${clean.slice(0, limit - 3).trimEnd()}...` : clean;
1774
+ }
1775
+ function blockText(block) {
1776
+ return cleanExcerpt(block.text ?? block.content ?? block.value);
1777
+ }
1778
+ function blockUrl(block) {
1779
+ const source = asRecord(block.source);
1780
+ return normalizeBoardRemoteUrl(source?.url ?? source?.src ?? block.url ?? block.src);
1781
+ }
1782
+ function blockMimeType(block) {
1783
+ const source = asRecord(block.source);
1784
+ const value = source?.mediaType ?? source?.media_type ?? source?.mimeType ?? block.mediaType ?? block.media_type ?? block.mimeType;
1785
+ return typeof value === "string" ? value : void 0;
1786
+ }
1787
+ function positiveNumber(value) {
1788
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
1789
+ }
1790
+ function blockNaturalSize(block) {
1791
+ const source = asRecord(block.source);
1792
+ const naturalWidth = positiveNumber(source?.width ?? source?.naturalWidth ?? block.width ?? block.naturalWidth);
1793
+ const naturalHeight = positiveNumber(source?.height ?? source?.naturalHeight ?? block.height ?? block.naturalHeight);
1794
+ return {
1795
+ ...naturalWidth ? { naturalWidth } : {},
1796
+ ...naturalHeight ? { naturalHeight } : {}
1797
+ };
1798
+ }
1799
+ function contentBlocks(value) {
1800
+ return Array.isArray(value) ? value.filter((item) => asRecord(item)) : [];
1801
+ }
1802
+ function taskData(run) {
1803
+ const payload = asRecord(run.payload);
1804
+ return asRecord(payload?.data) ?? payload;
1805
+ }
1806
+ function generationOutput(run) {
1807
+ return contentBlocks(asRecord(run.result)?.output);
1808
+ }
1809
+ function generationPrompt(run) {
1810
+ for (const block of contentBlocks(taskData(run)?.content)) {
1811
+ const text = blockText(block);
1812
+ if (text) return text;
1813
+ }
1814
+ }
1815
+ function blockMeta(block) {
1816
+ return asRecord(block.meta);
1817
+ }
1818
+ function blockIdentity(block) {
1819
+ const meta = blockMeta(block);
1820
+ const value = meta?.id ?? meta?.clip_id ?? meta?.clipId ?? block.id ?? block.clip_id ?? block.clipId;
1821
+ if (typeof value !== "string" && typeof value !== "number") return void 0;
1822
+ return cleanExcerpt(String(value), 220);
1823
+ }
1824
+ function blockTitle(block) {
1825
+ return cleanExcerpt(blockMeta(block)?.title ?? block.title ?? block.name, 240);
1826
+ }
1827
+ function blockDurationMs(block) {
1828
+ const source = asRecord(block.source);
1829
+ const meta = blockMeta(block);
1830
+ const milliseconds = positiveNumber(source?.durationMs ?? meta?.durationMs ?? block.durationMs);
1831
+ if (milliseconds) return Math.round(milliseconds);
1832
+ const seconds = positiveNumber(source?.duration ?? meta?.duration ?? block.duration);
1833
+ return seconds ? Math.round(seconds * 1e3) : void 0;
1834
+ }
1835
+ function blockPreviewUrl(block) {
1836
+ const source = asRecord(block.source);
1837
+ return normalizeBoardRemoteUrl(source?.poster ?? source?.thumbnail ?? source?.previewUrl ?? block.poster ?? block.thumbnail ?? block.previewUrl);
1838
+ }
1839
+ /**
1840
+ * Image roles that depict playable media, by the media types they may cover;
1841
+ * `last_frame` stays a result of its own.
1842
+ */
1843
+ const COVER_ROLES = /* @__PURE__ */ new Map([
1844
+ ["first_frame", /* @__PURE__ */ new Set(["video"])],
1845
+ ["cover", /* @__PURE__ */ new Set(["video", "audio"])],
1846
+ ["poster", /* @__PURE__ */ new Set(["video", "audio"])],
1847
+ ["thumbnail", /* @__PURE__ */ new Set(["video", "audio"])]
1848
+ ]);
1849
+ function isPlayable(block) {
1850
+ return block.type === "video" || block.type === "audio";
1851
+ }
1852
+ /**
1853
+ * Pair playable media with its cover image, returning media index → cover
1854
+ * index. An image sharing the provider id wins (music tracks and their art,
1855
+ * shared by every media of that id); otherwise cover-role images pair in
1856
+ * order with remaining media their role fits (a video and its `first_frame`).
1857
+ * Covers need a remote URL, so an inline image is never folded away.
1858
+ */
1859
+ function generationCovers(blocks) {
1860
+ const covers = /* @__PURE__ */ new Map();
1861
+ const isCover = (block) => block.type === "image" && blockUrl(block) !== void 0 && blockMeta(block)?.role !== "last_frame";
1862
+ blocks.forEach((block, index) => {
1863
+ const id = blockIdentity(block);
1864
+ if (!isPlayable(block) || id === void 0) return;
1865
+ const cover = blocks.findIndex((candidate) => isCover(candidate) && blockIdentity(candidate) === id);
1866
+ if (cover >= 0) covers.set(index, cover);
1867
+ });
1868
+ const paired = new Set(covers.values());
1869
+ const roleCovers = blocks.flatMap((block, index) => {
1870
+ const role = blockMeta(block)?.role;
1871
+ const types = typeof role === "string" ? COVER_ROLES.get(role) : void 0;
1872
+ return isCover(block) && !paired.has(index) && types ? [{
1873
+ index,
1874
+ types
1875
+ }] : [];
1876
+ });
1877
+ blocks.forEach((block, index) => {
1878
+ if (!isPlayable(block) || covers.has(index)) return;
1879
+ const at = roleCovers.findIndex(({ types }) => types.has(String(block.type)));
1880
+ const [cover] = at < 0 ? [] : roleCovers.splice(at, 1);
1881
+ if (cover) covers.set(index, cover.index);
1882
+ });
1883
+ return covers;
1884
+ }
1885
+ //#endregion
1754
1886
  //#region ../protocol/dist/turn-origin.js
1755
- const record$2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
1887
+ const record$1 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
1756
1888
  const normalizeSessionTurnOrigin = (value) => {
1757
- const input = record$2(value);
1889
+ const input = record$1(value);
1758
1890
  if (!input || ![
1759
1891
  "prompt",
1760
1892
  "scheduled_prompt",
@@ -1774,12 +1906,12 @@ const normalizeSessionTurnOrigin = (value) => {
1774
1906
  };
1775
1907
  /** Reads durable provenance; legacy background tasks only knew their caller's Session/Turn. */
1776
1908
  const readSessionTurnOrigin = (meta, spaceId) => {
1777
- const input = record$2(meta);
1909
+ const input = record$1(meta);
1778
1910
  const origin = normalizeSessionTurnOrigin(input?.origin);
1779
1911
  if (origin) return origin;
1780
- const context = record$2(input?.context);
1912
+ const context = record$1(input?.context);
1781
1913
  if (context?.kind !== "background_bash_task" || !isUuidLike(spaceId)) return null;
1782
- const legacy = record$2(context.origin);
1914
+ const legacy = record$1(context.origin);
1783
1915
  return normalizeSessionTurnOrigin({
1784
1916
  ...legacy,
1785
1917
  spaceId,
@@ -1794,9 +1926,9 @@ const KINDS = [
1794
1926
  "background_task",
1795
1927
  "hook"
1796
1928
  ];
1797
- const record$1 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
1929
+ const record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
1798
1930
  const normalizeSentTurnRef = (value) => {
1799
- const input = record$1(value);
1931
+ const input = record(value);
1800
1932
  if (!input) return null;
1801
1933
  if (!isUuidLike(input.sessionId) || !isUuidLike(input.turnId)) return null;
1802
1934
  const kind = String(input.kind);
@@ -1815,7 +1947,7 @@ const normalizeSentTurnRef = (value) => {
1815
1947
  };
1816
1948
  /** Deduplicates by child Turn, so a retried request cannot render one twice. */
1817
1949
  const readSentTurns = (meta) => {
1818
- const raw = record$1(meta)?.messagesSent;
1950
+ const raw = record(meta)?.messagesSent;
1819
1951
  if (!Array.isArray(raw)) return [];
1820
1952
  const seen = /* @__PURE__ */ new Set();
1821
1953
  const refs = [];
@@ -7031,7 +7163,10 @@ var CohubClient = class {
7031
7163
  const appTransport = resolveAppTransport(appRuntime, appIdResolver, createOriginAppResolver({
7032
7164
  apiBaseUrl,
7033
7165
  fetch: options.fetch
7034
- }), resolveWebBaseUrl({ env: options.env }));
7166
+ }), resolveWebBaseUrl({
7167
+ env: options.env,
7168
+ url: options.webBaseUrl
7169
+ }));
7035
7170
  this.appRuntime = createAppRuntime(appTransport, appRuntime?.appId, appIdResolver);
7036
7171
  this.appWindow = new AppWindowApi(appTransport, {
7037
7172
  context: () => this.appRuntime.context(),
@@ -8871,129 +9006,6 @@ function createAppBridgeCore(config) {
8871
9006
  };
8872
9007
  }
8873
9008
  //#endregion
8874
- //#region src/generation-blocks.ts
8875
- /** Tolerant readers for generation payloads, shared by every task projection. */
8876
- const EXCERPT_LIMIT = 240;
8877
- function record(value) {
8878
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
8879
- }
8880
- function cleanExcerpt(value, limit = EXCERPT_LIMIT) {
8881
- if (typeof value !== "string") return void 0;
8882
- const clean = value.replace(/\s+/g, " ").trim();
8883
- if (!clean) return void 0;
8884
- return clean.length > limit ? `${clean.slice(0, limit - 3).trimEnd()}...` : clean;
8885
- }
8886
- function blockText(block) {
8887
- return cleanExcerpt(block.text ?? block.content ?? block.value);
8888
- }
8889
- function blockUrl(block) {
8890
- const source = record(block.source);
8891
- return normalizeBoardRemoteUrl(source?.url ?? source?.src ?? block.url ?? block.src);
8892
- }
8893
- function blockMimeType(block) {
8894
- const source = record(block.source);
8895
- const value = source?.mediaType ?? source?.media_type ?? source?.mimeType ?? block.mediaType ?? block.media_type ?? block.mimeType;
8896
- return typeof value === "string" ? value : void 0;
8897
- }
8898
- function positiveNumber(value) {
8899
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
8900
- }
8901
- function blockNaturalSize(block) {
8902
- const source = record(block.source);
8903
- const naturalWidth = positiveNumber(source?.width ?? source?.naturalWidth ?? block.width ?? block.naturalWidth);
8904
- const naturalHeight = positiveNumber(source?.height ?? source?.naturalHeight ?? block.height ?? block.naturalHeight);
8905
- return {
8906
- ...naturalWidth ? { naturalWidth } : {},
8907
- ...naturalHeight ? { naturalHeight } : {}
8908
- };
8909
- }
8910
- function contentBlocks(value) {
8911
- return Array.isArray(value) ? value.filter((item) => record(item)) : [];
8912
- }
8913
- function taskData(run) {
8914
- const payload = record(run.payload);
8915
- return record(payload?.data) ?? payload;
8916
- }
8917
- function generationOutput(run) {
8918
- return contentBlocks(record(run.result)?.output);
8919
- }
8920
- function generationPrompt(run) {
8921
- for (const block of contentBlocks(taskData(run)?.content)) {
8922
- const text = blockText(block);
8923
- if (text) return text;
8924
- }
8925
- }
8926
- function blockMeta(block) {
8927
- return record(block.meta);
8928
- }
8929
- function blockIdentity(block) {
8930
- const meta = blockMeta(block);
8931
- const value = meta?.id ?? meta?.clip_id ?? meta?.clipId ?? block.id ?? block.clip_id ?? block.clipId;
8932
- if (typeof value !== "string" && typeof value !== "number") return void 0;
8933
- return cleanExcerpt(String(value), 220);
8934
- }
8935
- function blockTitle(block) {
8936
- return cleanExcerpt(blockMeta(block)?.title ?? block.title ?? block.name, 240);
8937
- }
8938
- function blockDurationMs(block) {
8939
- const source = record(block.source);
8940
- const meta = blockMeta(block);
8941
- const milliseconds = positiveNumber(source?.durationMs ?? meta?.durationMs ?? block.durationMs);
8942
- if (milliseconds) return Math.round(milliseconds);
8943
- const seconds = positiveNumber(source?.duration ?? meta?.duration ?? block.duration);
8944
- return seconds ? Math.round(seconds * 1e3) : void 0;
8945
- }
8946
- function blockPreviewUrl(block) {
8947
- const source = record(block.source);
8948
- return normalizeBoardRemoteUrl(source?.poster ?? source?.thumbnail ?? source?.previewUrl ?? block.poster ?? block.thumbnail ?? block.previewUrl);
8949
- }
8950
- /**
8951
- * Image roles that depict playable media, by the media types they may cover;
8952
- * `last_frame` stays a result of its own.
8953
- */
8954
- const COVER_ROLES = /* @__PURE__ */ new Map([
8955
- ["first_frame", /* @__PURE__ */ new Set(["video"])],
8956
- ["cover", /* @__PURE__ */ new Set(["video", "audio"])],
8957
- ["poster", /* @__PURE__ */ new Set(["video", "audio"])],
8958
- ["thumbnail", /* @__PURE__ */ new Set(["video", "audio"])]
8959
- ]);
8960
- function isPlayable(block) {
8961
- return block.type === "video" || block.type === "audio";
8962
- }
8963
- /**
8964
- * Pair playable media with its cover image, returning media index → cover
8965
- * index. An image sharing the provider id wins (music tracks and their art,
8966
- * shared by every media of that id); otherwise cover-role images pair in
8967
- * order with remaining media their role fits (a video and its `first_frame`).
8968
- * Covers need a remote URL, so an inline image is never folded away.
8969
- */
8970
- function generationCovers(blocks) {
8971
- const covers = /* @__PURE__ */ new Map();
8972
- const isCover = (block) => block.type === "image" && blockUrl(block) !== void 0 && blockMeta(block)?.role !== "last_frame";
8973
- blocks.forEach((block, index) => {
8974
- const id = blockIdentity(block);
8975
- if (!isPlayable(block) || id === void 0) return;
8976
- const cover = blocks.findIndex((candidate) => isCover(candidate) && blockIdentity(candidate) === id);
8977
- if (cover >= 0) covers.set(index, cover);
8978
- });
8979
- const paired = new Set(covers.values());
8980
- const roleCovers = blocks.flatMap((block, index) => {
8981
- const role = blockMeta(block)?.role;
8982
- const types = typeof role === "string" ? COVER_ROLES.get(role) : void 0;
8983
- return isCover(block) && !paired.has(index) && types ? [{
8984
- index,
8985
- types
8986
- }] : [];
8987
- });
8988
- blocks.forEach((block, index) => {
8989
- if (!isPlayable(block) || covers.has(index)) return;
8990
- const at = roleCovers.findIndex(({ types }) => types.has(String(block.type)));
8991
- const [cover] = at < 0 ? [] : roleCovers.splice(at, 1);
8992
- if (cover) covers.set(index, cover.index);
8993
- });
8994
- return covers;
8995
- }
8996
- //#endregion
8997
9009
  //#region src/generation-task.ts
8998
9010
  const INLINE_KEYS = [
8999
9011
  "data",
@@ -9001,7 +9013,7 @@ const INLINE_KEYS = [
9001
9013
  "contentBase64"
9002
9014
  ];
9003
9015
  function isDeferred(block) {
9004
- return block.deferredBase64 === true || record(block.source)?.deferredBase64 === true;
9016
+ return block.deferredBase64 === true || asRecord(block.source)?.deferredBase64 === true;
9005
9017
  }
9006
9018
  const MEDIA_TYPE_PATTERN = /^(image|video|audio)\/[\w.+-]+$/;
9007
9019
  const DEFAULT_MEDIA_TYPES = {
@@ -9044,7 +9056,7 @@ function projectOutputs(run) {
9044
9056
  }
9045
9057
  function toGenerationTaskView(run) {
9046
9058
  const data = taskData(run);
9047
- const result = record(run.result);
9059
+ const result = asRecord(run.result);
9048
9060
  const model = data?.model ?? result?.model;
9049
9061
  return {
9050
9062
  id: run.id,
@@ -9067,11 +9079,11 @@ function isActiveGenerationTask(task) {
9067
9079
  }
9068
9080
  /** Playable source of an output: its URL, or a data URL for inline payloads. */
9069
9081
  function generationOutputSource(result, outputIndex) {
9070
- const block = contentBlocks(record(result)?.output)[outputIndex];
9082
+ const block = contentBlocks(asRecord(result)?.output)[outputIndex];
9071
9083
  if (!block) return null;
9072
9084
  const url = blockUrl(block);
9073
9085
  if (url) return url;
9074
- const source = record(block.source);
9086
+ const source = asRecord(block.source);
9075
9087
  const data = INLINE_KEYS.map((key) => source?.[key] ?? block[key]).find((value) => typeof value === "string" && value !== "");
9076
9088
  if (!data) return null;
9077
9089
  const declared = blockMimeType(block);
@@ -481,6 +481,11 @@ declare const BoardMediaSnapshotSchema: z.ZodObject<{
481
481
  coverUrl: z.ZodOptional<z.ZodString>;
482
482
  }, z.core.$strict>;
483
483
  type BoardMediaSnapshot = z.infer<typeof BoardMediaSnapshotSchema>;
484
+ declare const BoardMediaOriginSchema: z.ZodObject<{
485
+ taskRunId: z.ZodString;
486
+ artifactId: z.ZodString;
487
+ }, z.core.$strict>;
488
+ type BoardMediaOrigin = z.infer<typeof BoardMediaOriginSchema>;
484
489
  declare const BoardCropSchema: z.ZodObject<{
485
490
  x: z.ZodNumber;
486
491
  y: z.ZodNumber;
@@ -551,6 +556,10 @@ declare const BoardImageItemSchema: z.ZodObject<{
551
556
  coverPath: z.ZodOptional<z.ZodString>;
552
557
  coverUrl: z.ZodOptional<z.ZodString>;
553
558
  }, z.core.$strict>>;
559
+ origin: z.ZodOptional<z.ZodObject<{
560
+ taskRunId: z.ZodString;
561
+ artifactId: z.ZodString;
562
+ }, z.core.$strict>>;
554
563
  crop: z.ZodOptional<z.ZodObject<{
555
564
  x: z.ZodNumber;
556
565
  y: z.ZodNumber;
@@ -623,6 +632,10 @@ declare const BoardVideoItemSchema: z.ZodObject<{
623
632
  coverPath: z.ZodOptional<z.ZodString>;
624
633
  coverUrl: z.ZodOptional<z.ZodString>;
625
634
  }, z.core.$strict>>;
635
+ origin: z.ZodOptional<z.ZodObject<{
636
+ taskRunId: z.ZodString;
637
+ artifactId: z.ZodString;
638
+ }, z.core.$strict>>;
626
639
  time: z.ZodOptional<z.ZodNumber>;
627
640
  }, z.core.$strict>;
628
641
  }, z.core.$strict>;
@@ -690,6 +703,10 @@ declare const BoardAudioItemSchema: z.ZodObject<{
690
703
  coverPath: z.ZodOptional<z.ZodString>;
691
704
  coverUrl: z.ZodOptional<z.ZodString>;
692
705
  }, z.core.$strict>>;
706
+ origin: z.ZodOptional<z.ZodObject<{
707
+ taskRunId: z.ZodString;
708
+ artifactId: z.ZodString;
709
+ }, z.core.$strict>>;
693
710
  time: z.ZodOptional<z.ZodNumber>;
694
711
  }, z.core.$strict>;
695
712
  }, z.core.$strict>;
@@ -804,6 +821,7 @@ declare const BoardTaskSnapshotSchema: z.ZodObject<{
804
821
  title: z.ZodString;
805
822
  model: z.ZodOptional<z.ZodString>;
806
823
  promptExcerpt: z.ZodOptional<z.ZodString>;
824
+ errorExcerpt: z.ZodOptional<z.ZodString>;
807
825
  artifactCount: z.ZodNumber;
808
826
  artifacts: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
809
827
  id: z.ZodString;
@@ -837,6 +855,7 @@ declare const BoardTaskSnapshotSchema: z.ZodObject<{
837
855
  title: z.ZodOptional<z.ZodString>;
838
856
  textExcerpt: z.ZodString;
839
857
  }, z.core.$strict>], "type">>>;
858
+ startedAt: z.ZodOptional<z.ZodString>;
840
859
  updatedAt: z.ZodOptional<z.ZodString>;
841
860
  }, z.core.$strict>;
842
861
  type BoardTaskSnapshot = z.infer<typeof BoardTaskSnapshotSchema>;
@@ -903,6 +922,7 @@ declare const BoardTaskItemSchema: z.ZodObject<{
903
922
  title: z.ZodString;
904
923
  model: z.ZodOptional<z.ZodString>;
905
924
  promptExcerpt: z.ZodOptional<z.ZodString>;
925
+ errorExcerpt: z.ZodOptional<z.ZodString>;
906
926
  artifactCount: z.ZodNumber;
907
927
  artifacts: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
908
928
  id: z.ZodString;
@@ -936,6 +956,7 @@ declare const BoardTaskItemSchema: z.ZodObject<{
936
956
  title: z.ZodOptional<z.ZodString>;
937
957
  textExcerpt: z.ZodString;
938
958
  }, z.core.$strict>], "type">>>;
959
+ startedAt: z.ZodOptional<z.ZodString>;
939
960
  updatedAt: z.ZodOptional<z.ZodString>;
940
961
  }, z.core.$strict>;
941
962
  }, z.core.$strict>;
@@ -1581,6 +1602,10 @@ declare const BOARD_ITEM_SCHEMAS: {
1581
1602
  coverPath: z.ZodOptional<z.ZodString>;
1582
1603
  coverUrl: z.ZodOptional<z.ZodString>;
1583
1604
  }, z.core.$strict>>;
1605
+ origin: z.ZodOptional<z.ZodObject<{
1606
+ taskRunId: z.ZodString;
1607
+ artifactId: z.ZodString;
1608
+ }, z.core.$strict>>;
1584
1609
  crop: z.ZodOptional<z.ZodObject<{
1585
1610
  x: z.ZodNumber;
1586
1611
  y: z.ZodNumber;
@@ -1653,6 +1678,10 @@ declare const BOARD_ITEM_SCHEMAS: {
1653
1678
  coverPath: z.ZodOptional<z.ZodString>;
1654
1679
  coverUrl: z.ZodOptional<z.ZodString>;
1655
1680
  }, z.core.$strict>>;
1681
+ origin: z.ZodOptional<z.ZodObject<{
1682
+ taskRunId: z.ZodString;
1683
+ artifactId: z.ZodString;
1684
+ }, z.core.$strict>>;
1656
1685
  time: z.ZodOptional<z.ZodNumber>;
1657
1686
  }, z.core.$strict>;
1658
1687
  }, z.core.$strict>;
@@ -1720,6 +1749,10 @@ declare const BOARD_ITEM_SCHEMAS: {
1720
1749
  coverPath: z.ZodOptional<z.ZodString>;
1721
1750
  coverUrl: z.ZodOptional<z.ZodString>;
1722
1751
  }, z.core.$strict>>;
1752
+ origin: z.ZodOptional<z.ZodObject<{
1753
+ taskRunId: z.ZodString;
1754
+ artifactId: z.ZodString;
1755
+ }, z.core.$strict>>;
1723
1756
  time: z.ZodOptional<z.ZodNumber>;
1724
1757
  }, z.core.$strict>;
1725
1758
  }, z.core.$strict>;
@@ -1852,6 +1885,7 @@ declare const BOARD_ITEM_SCHEMAS: {
1852
1885
  title: z.ZodString;
1853
1886
  model: z.ZodOptional<z.ZodString>;
1854
1887
  promptExcerpt: z.ZodOptional<z.ZodString>;
1888
+ errorExcerpt: z.ZodOptional<z.ZodString>;
1855
1889
  artifactCount: z.ZodNumber;
1856
1890
  artifacts: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
1857
1891
  id: z.ZodString;
@@ -1885,6 +1919,7 @@ declare const BOARD_ITEM_SCHEMAS: {
1885
1919
  title: z.ZodOptional<z.ZodString>;
1886
1920
  textExcerpt: z.ZodString;
1887
1921
  }, z.core.$strict>], "type">>>;
1922
+ startedAt: z.ZodOptional<z.ZodString>;
1888
1923
  updatedAt: z.ZodOptional<z.ZodString>;
1889
1924
  }, z.core.$strict>;
1890
1925
  }, z.core.$strict>;
@@ -2239,4 +2274,4 @@ type BoardDelta = {
2239
2274
  };
2240
2275
  declare function isEmptyBoardDelta(delta: BoardDelta): boolean;
2241
2276
  //#endregion
2242
- export { BOARD_ANCHOR_SIDES, BOARD_ARROW_ROUTES, BOARD_CAMERA_TARGET, BOARD_COLOR_TOKENS, BOARD_DASHES, BOARD_DERIVED_SIZE_TYPES, BOARD_EFFECT_KINDS, BOARD_EFFECT_KIND_INFO, BOARD_ENTER_PRESETS, BOARD_EXTENSION_TYPE_PATTERN, BOARD_GRID_DEFAULT_SIZE, BOARD_GRID_MAX_SIZE, BOARD_GRID_MIN_SIZE, BOARD_ID_MAX_LENGTH, BOARD_ITEM_SCHEMAS, BOARD_ITEM_TYPES, BOARD_PLAY_MODES, BOARD_SHAPE_GEOMETRIES, BOARD_TASK_ARTIFACT_LIMIT, BOARD_TEXT_ALIGNS, BoardAnchorSide, BoardAnimation, BoardAnimationHeader, BoardAnimationHeaderSchema, BoardAnimationSchema, BoardArrowAnchor, BoardArrowAnchorSchema, BoardArrowBinding, BoardArrowBindingSchema, BoardArrowEnd, BoardArrowEndSchema, BoardArrowItem, BoardArrowItemSchema, BoardArrowRoute, BoardAudioItem, BoardAudioItemSchema, BoardBackgroundSchema, BoardBuiltinItem, BoardBuiltinItemType, BoardColor, BoardColorSchema, BoardColorToken, BoardCropSchema, BoardCustomItem, BoardCustomItemSchema, BoardDash, BoardDelta, BoardDiagnostic, BoardDocument, BoardDrawItem, BoardDrawItemSchema, BoardDrawPoint, BoardDrawPointSchema, BoardEaseSchema, BoardEffectItem, BoardEffectItemSchema, BoardEffectKind, BoardEnterPreset, BoardExtensionItem, BoardExtensionItemSchema, BoardFileBackedItem, BoardFileItem, BoardFileItemSchema, BoardFrameItem, BoardFrameItemSchema, BoardIdSchema, BoardImageItem, BoardImageItemSchema, BoardItem, BoardItemType, BoardKeySchema, BoardKeyframe, BoardKeyframeSchema, BoardMarker, BoardMarkerSchema, BoardMediaSnapshot, BoardMediaSnapshotSchema, BoardMediaSource, BoardMediaSourceSchema, BoardPatch, BoardPatchSchema, BoardPlayMode, BoardScript, BoardScriptSchema, BoardSettings, BoardSettingsSchema, BoardShapeGeometry, BoardShapeItem, BoardShapeItemSchema, BoardSize, BoardSizeSchema, BoardStyle, BoardStyleSchema, BoardTaskArtifact, BoardTaskArtifactSchema, BoardTaskItem, BoardTaskItemSchema, BoardTaskSnapshot, BoardTaskSnapshotSchema, BoardTextItem, BoardTextItemSchema, BoardTrack, BoardTrackSchema, BoardVec2, BoardVec2Schema, BoardVideoItem, BoardVideoItemSchema, boardItemSchema, emptyBoardDocument, hasAuthoredSize, isBoardColorString, isBoardEase, isBuiltinItem, isBuiltinItemType, isEmptyBoardDelta, isFileBackedItem, isMediaItem, isSafeBoardSourcePath, parseBoardItem, parseBoardMediaSource };
2277
+ export { BOARD_ANCHOR_SIDES, BOARD_ARROW_ROUTES, BOARD_CAMERA_TARGET, BOARD_COLOR_TOKENS, BOARD_DASHES, BOARD_DERIVED_SIZE_TYPES, BOARD_EFFECT_KINDS, BOARD_EFFECT_KIND_INFO, BOARD_ENTER_PRESETS, BOARD_EXTENSION_TYPE_PATTERN, BOARD_GRID_DEFAULT_SIZE, BOARD_GRID_MAX_SIZE, BOARD_GRID_MIN_SIZE, BOARD_ID_MAX_LENGTH, BOARD_ITEM_SCHEMAS, BOARD_ITEM_TYPES, BOARD_PLAY_MODES, BOARD_SHAPE_GEOMETRIES, BOARD_TASK_ARTIFACT_LIMIT, BOARD_TEXT_ALIGNS, BoardAnchorSide, BoardAnimation, BoardAnimationHeader, BoardAnimationHeaderSchema, BoardAnimationSchema, BoardArrowAnchor, BoardArrowAnchorSchema, BoardArrowBinding, BoardArrowBindingSchema, BoardArrowEnd, BoardArrowEndSchema, BoardArrowItem, BoardArrowItemSchema, BoardArrowRoute, BoardAudioItem, BoardAudioItemSchema, BoardBackgroundSchema, BoardBuiltinItem, BoardBuiltinItemType, BoardColor, BoardColorSchema, BoardColorToken, BoardCropSchema, BoardCustomItem, BoardCustomItemSchema, BoardDash, BoardDelta, BoardDiagnostic, BoardDocument, BoardDrawItem, BoardDrawItemSchema, BoardDrawPoint, BoardDrawPointSchema, BoardEaseSchema, BoardEffectItem, BoardEffectItemSchema, BoardEffectKind, BoardEnterPreset, BoardExtensionItem, BoardExtensionItemSchema, BoardFileBackedItem, BoardFileItem, BoardFileItemSchema, BoardFrameItem, BoardFrameItemSchema, BoardIdSchema, BoardImageItem, BoardImageItemSchema, BoardItem, BoardItemType, BoardKeySchema, BoardKeyframe, BoardKeyframeSchema, BoardMarker, BoardMarkerSchema, BoardMediaOrigin, BoardMediaOriginSchema, BoardMediaSnapshot, BoardMediaSnapshotSchema, BoardMediaSource, BoardMediaSourceSchema, BoardPatch, BoardPatchSchema, BoardPlayMode, BoardScript, BoardScriptSchema, BoardSettings, BoardSettingsSchema, BoardShapeGeometry, BoardShapeItem, BoardShapeItemSchema, BoardSize, BoardSizeSchema, BoardStyle, BoardStyleSchema, BoardTaskArtifact, BoardTaskArtifactSchema, BoardTaskItem, BoardTaskItemSchema, BoardTaskSnapshot, BoardTaskSnapshotSchema, BoardTextItem, BoardTextItemSchema, BoardTrack, BoardTrackSchema, BoardVec2, BoardVec2Schema, BoardVideoItem, BoardVideoItemSchema, boardItemSchema, emptyBoardDocument, hasAuthoredSize, isBoardColorString, isBoardEase, isBuiltinItem, isBuiltinItemType, isEmptyBoardDelta, isFileBackedItem, isMediaItem, isSafeBoardSourcePath, parseBoardItem, parseBoardMediaSource };
@@ -222,8 +222,16 @@ const fileProps = {
222
222
  src: BoardMediaSourceSchema,
223
223
  snapshot: BoardMediaSnapshotSchema.optional()
224
224
  };
225
- const timedFileProps = {
225
+ const BoardMediaOriginSchema = z.object({
226
+ taskRunId: z.string().min(1).max(160),
227
+ artifactId: z.string().min(1).max(240)
228
+ }).strict();
229
+ const mediaProps = {
226
230
  ...fileProps,
231
+ origin: BoardMediaOriginSchema.optional()
232
+ };
233
+ const timedFileProps = {
234
+ ...mediaProps,
227
235
  time: nonNegative.optional()
228
236
  };
229
237
  const BoardCropSchema = z.object({
@@ -236,7 +244,7 @@ const BoardImageItemSchema = z.object({
236
244
  ...boxed(640, 360),
237
245
  type: z.literal("image"),
238
246
  props: z.object({
239
- ...fileProps,
247
+ ...mediaProps,
240
248
  crop: BoardCropSchema.optional()
241
249
  }).strict()
242
250
  }).strict();
@@ -302,8 +310,10 @@ const BoardTaskSnapshotSchema = z.object({
302
310
  title: z.string().min(1).max(240),
303
311
  model: z.string().max(160).optional(),
304
312
  promptExcerpt: z.string().max(480).optional(),
313
+ errorExcerpt: z.string().max(240).optional(),
305
314
  artifactCount: z.number().int().nonnegative(),
306
315
  artifacts: z.array(BoardTaskArtifactSchema).max(6).default([]),
316
+ startedAt: z.string().max(64).optional(),
307
317
  updatedAt: z.string().optional()
308
318
  }).strict();
309
319
  const BoardTaskItemSchema = z.object({
@@ -311,7 +321,7 @@ const BoardTaskItemSchema = z.object({
311
321
  type: z.literal("task"),
312
322
  props: z.object({
313
323
  taskRunId: z.string().min(1).max(160),
314
- snapshot: BoardTaskSnapshotSchema
324
+ snapshot: BoardTaskSnapshotSchema.describe("Maintained by the server for tasks in the Board's Space; writers may omit it.")
315
325
  }).strict()
316
326
  }).strict();
317
327
  const BOARD_EFFECT_KINDS = [
@@ -608,4 +618,4 @@ function isEmptyBoardDelta(delta) {
608
618
  return !delta.board && Object.keys(delta.items ?? {}).length === 0 && Object.keys(delta.animations ?? {}).length === 0 && Object.keys(delta.tracks ?? {}).length === 0;
609
619
  }
610
620
  //#endregion
611
- export { BOARD_ANCHOR_SIDES, BOARD_ARROW_ROUTES, BOARD_CAMERA_TARGET, BOARD_COLOR_TOKENS, BOARD_DASHES, BOARD_DERIVED_SIZE_TYPES, BOARD_EFFECT_KINDS, BOARD_EFFECT_KIND_INFO, BOARD_ENTER_PRESETS, BOARD_EXTENSION_TYPE_PATTERN, BOARD_GRID_DEFAULT_SIZE, BOARD_GRID_MAX_SIZE, BOARD_GRID_MIN_SIZE, BOARD_ID_MAX_LENGTH, BOARD_ITEM_SCHEMAS, BOARD_ITEM_TYPES, BOARD_PLAY_MODES, BOARD_SHAPE_GEOMETRIES, BOARD_TASK_ARTIFACT_LIMIT, BOARD_TEXT_ALIGNS, BoardAnimationHeaderSchema, BoardAnimationSchema, BoardArrowAnchorSchema, BoardArrowBindingSchema, BoardArrowEndSchema, BoardArrowItemSchema, BoardAudioItemSchema, BoardBackgroundSchema, BoardColorSchema, BoardCropSchema, BoardCustomItemSchema, BoardDrawItemSchema, BoardDrawPointSchema, BoardEaseSchema, BoardEffectItemSchema, BoardExtensionItemSchema, BoardFileItemSchema, BoardFrameItemSchema, BoardIdSchema, BoardImageItemSchema, BoardKeySchema, BoardKeyframeSchema, BoardMarkerSchema, BoardMediaSnapshotSchema, BoardMediaSourceSchema, BoardPatchSchema, BoardScriptSchema, BoardSettingsSchema, BoardShapeItemSchema, BoardSizeSchema, BoardStyleSchema, BoardTaskArtifactSchema, BoardTaskItemSchema, BoardTaskSnapshotSchema, BoardTextItemSchema, BoardTrackSchema, BoardVec2Schema, BoardVideoItemSchema, boardItemSchema, emptyBoardDocument, hasAuthoredSize, isBoardColorString, isBoardEase, isBuiltinItem, isBuiltinItemType, isEmptyBoardDelta, isFileBackedItem, isMediaItem, isSafeBoardSourcePath, parseBoardItem, parseBoardMediaSource };
621
+ export { BOARD_ANCHOR_SIDES, BOARD_ARROW_ROUTES, BOARD_CAMERA_TARGET, BOARD_COLOR_TOKENS, BOARD_DASHES, BOARD_DERIVED_SIZE_TYPES, BOARD_EFFECT_KINDS, BOARD_EFFECT_KIND_INFO, BOARD_ENTER_PRESETS, BOARD_EXTENSION_TYPE_PATTERN, BOARD_GRID_DEFAULT_SIZE, BOARD_GRID_MAX_SIZE, BOARD_GRID_MIN_SIZE, BOARD_ID_MAX_LENGTH, BOARD_ITEM_SCHEMAS, BOARD_ITEM_TYPES, BOARD_PLAY_MODES, BOARD_SHAPE_GEOMETRIES, BOARD_TASK_ARTIFACT_LIMIT, BOARD_TEXT_ALIGNS, BoardAnimationHeaderSchema, BoardAnimationSchema, BoardArrowAnchorSchema, BoardArrowBindingSchema, BoardArrowEndSchema, BoardArrowItemSchema, BoardAudioItemSchema, BoardBackgroundSchema, BoardColorSchema, BoardCropSchema, BoardCustomItemSchema, BoardDrawItemSchema, BoardDrawPointSchema, BoardEaseSchema, BoardEffectItemSchema, BoardExtensionItemSchema, BoardFileItemSchema, BoardFrameItemSchema, BoardIdSchema, BoardImageItemSchema, BoardKeySchema, BoardKeyframeSchema, BoardMarkerSchema, BoardMediaOriginSchema, BoardMediaSnapshotSchema, BoardMediaSourceSchema, BoardPatchSchema, BoardScriptSchema, BoardSettingsSchema, BoardShapeItemSchema, BoardSizeSchema, BoardStyleSchema, BoardTaskArtifactSchema, BoardTaskItemSchema, BoardTaskSnapshotSchema, BoardTextItemSchema, BoardTrackSchema, BoardVec2Schema, BoardVideoItemSchema, boardItemSchema, emptyBoardDocument, hasAuthoredSize, isBoardColorString, isBoardEase, isBuiltinItem, isBuiltinItemType, isEmptyBoardDelta, isFileBackedItem, isMediaItem, isSafeBoardSourcePath, parseBoardItem, parseBoardMediaSource };
@@ -0,0 +1,22 @@
1
+ import { BoardTaskArtifact, BoardTaskSnapshot } from "./board-model.js";
2
+ import { GenerationRunPayload } from "./generation/blocks.js";
3
+ import { TaskRunStatus } from "./task/index.js";
4
+ //#region ../protocol/dist/board-task.d.ts
5
+ type BoardTaskRunSource = GenerationRunPayload & {
6
+ taskType: string;
7
+ status: TaskRunStatus;
8
+ errorMessage?: string | null;
9
+ startedAt?: string | null;
10
+ updatedAt: string;
11
+ };
12
+ declare const BOARD_TASK_SOURCE_PREFIX = "cohub-task:";
13
+ declare const boardTaskSource: (taskRunId: string) => string;
14
+ declare function taskArtifacts(blocks: Record<string, unknown>[]): BoardTaskArtifact[];
15
+ declare function rankedTaskArtifacts(artifacts: readonly BoardTaskArtifact[]): BoardTaskArtifact[];
16
+ declare function featuredTaskArtifact(artifacts: readonly BoardTaskArtifact[]): BoardTaskArtifact | undefined;
17
+ declare function taskArtifactPreviewUrl(artifact: BoardTaskArtifact | undefined): string | undefined;
18
+ declare function taskRunArtifacts(run: Pick<BoardTaskRunSource, "taskType" | "result">): BoardTaskArtifact[];
19
+ declare function taskRunToBoardTaskSnapshot(run: BoardTaskRunSource): BoardTaskSnapshot;
20
+ declare function withObservedArtifactFacts(snapshot: BoardTaskSnapshot, observed: BoardTaskSnapshot | undefined): BoardTaskSnapshot;
21
+ //#endregion
22
+ export { BOARD_TASK_SOURCE_PREFIX, BoardTaskRunSource, boardTaskSource, featuredTaskArtifact, rankedTaskArtifacts, taskArtifactPreviewUrl, taskArtifacts, taskRunArtifacts, taskRunToBoardTaskSnapshot, withObservedArtifactFacts };
@@ -1,6 +1,8 @@
1
- import "../../protocol/dist/board-model.js";
2
- import { blockDurationMs, blockIdentity, blockMimeType, blockNaturalSize, blockPreviewUrl, blockText, blockTitle, blockUrl, cleanExcerpt, generationCovers, generationOutput, generationPrompt, taskData } from "../../generation-blocks.js";
3
- //#region src/board/model/task.ts
1
+ import "./board-model.js";
2
+ import { blockDurationMs, blockIdentity, blockMimeType, blockNaturalSize, blockPreviewUrl, blockText, blockTitle, blockUrl, cleanExcerpt, generationCovers, generationOutput, generationPrompt, taskData } from "./generation/blocks.js";
3
+ //#region ../protocol/dist/board-task.js
4
+ const BOARD_TASK_SOURCE_PREFIX = "cohub-task:";
5
+ const boardTaskSource = (taskRunId) => `${BOARD_TASK_SOURCE_PREFIX}${taskRunId}`;
4
6
  function artifactId(base, used, suffix) {
5
7
  const stem = cleanExcerpt(base, 220) ?? "output";
6
8
  let candidate = suffix ? `${stem}-${suffix}` : stem;
@@ -137,23 +139,48 @@ function taskArtifactPreviewUrl(artifact) {
137
139
  function taskTypeTitle(taskType) {
138
140
  return taskType.replace(/[._-]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
139
141
  }
142
+ function taskRunArtifacts(run) {
143
+ return run.taskType === "generation" ? rankedTaskArtifacts(taskArtifacts(generationOutput(run))) : [];
144
+ }
140
145
  function taskRunToBoardTaskSnapshot(run) {
141
146
  const data = taskData(run);
142
- const blocks = run.taskType === "generation" ? generationOutput(run) : [];
143
147
  const promptExcerpt = run.taskType === "generation" ? generationPrompt(run) : cleanExcerpt(data?.command ?? data?.prompt ?? data?.title);
144
148
  const model = typeof data?.model === "string" ? data.model : void 0;
145
- const allArtifacts = taskArtifacts(blocks);
146
- const artifacts = rankedTaskArtifacts(allArtifacts).slice(0, 6);
149
+ const allArtifacts = taskRunArtifacts(run);
150
+ const artifacts = allArtifacts.slice(0, 6);
151
+ const errorExcerpt = run.status === "failed" ? cleanExcerpt(run.errorMessage, 240) : void 0;
147
152
  return {
148
153
  taskType: run.taskType,
149
154
  status: run.status,
150
155
  title: promptExcerpt ?? taskTypeTitle(run.taskType),
151
156
  ...model ? { model } : {},
152
157
  ...promptExcerpt ? { promptExcerpt } : {},
158
+ ...errorExcerpt ? { errorExcerpt } : {},
153
159
  artifactCount: allArtifacts.length,
154
160
  artifacts,
161
+ ...run.startedAt ? { startedAt: run.startedAt } : {},
155
162
  updatedAt: run.updatedAt
156
163
  };
157
164
  }
165
+ function withObservedArtifactFacts(snapshot, observed) {
166
+ if (!observed?.artifacts.length) return snapshot;
167
+ let changed = false;
168
+ const artifacts = snapshot.artifacts.map((artifact) => {
169
+ if (artifact.type !== "image" && artifact.type !== "video") return artifact;
170
+ if (artifact.naturalWidth && artifact.naturalHeight) return artifact;
171
+ const match = observed.artifacts.find((candidate) => candidate.id === artifact.id && candidate.type === artifact.type && candidate.url === artifact.url);
172
+ if (!match || match.type !== "image" && match.type !== "video" || !match.naturalWidth || !match.naturalHeight) return artifact;
173
+ changed = true;
174
+ return {
175
+ ...artifact,
176
+ naturalWidth: match.naturalWidth,
177
+ naturalHeight: match.naturalHeight
178
+ };
179
+ });
180
+ return changed ? {
181
+ ...snapshot,
182
+ artifacts
183
+ } : snapshot;
184
+ }
158
185
  //#endregion
159
- export { featuredTaskArtifact, rankedTaskArtifacts, taskArtifactPreviewUrl, taskArtifacts, taskRunToBoardTaskSnapshot };
186
+ export { BOARD_TASK_SOURCE_PREFIX, boardTaskSource, featuredTaskArtifact, rankedTaskArtifacts, taskArtifactPreviewUrl, taskArtifacts, taskRunArtifacts, taskRunToBoardTaskSnapshot, withObservedArtifactFacts };