@neta-art/cohub 5.7.0 → 5.8.1
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/README.md +18 -5
- package/dist/board/core/palette.d.ts +3 -2
- package/dist/board/core/shape-types.d.ts +3 -1
- package/dist/board/core/shape-types.js +3 -7
- package/dist/board/core/tool-styles.d.ts +2 -1
- package/dist/board/image-key.js +3 -5
- package/dist/board/index.d.ts +9 -4
- package/dist/board/index.js +7 -3
- package/dist/board/media-playback.d.ts +22 -0
- package/dist/board/media-playback.js +67 -0
- package/dist/board/media.d.ts +7 -0
- package/dist/board/media.js +70 -0
- package/dist/board/nodes.d.ts +113 -0
- package/dist/board/nodes.js +154 -0
- package/dist/board/render/index.d.ts +3 -1
- package/dist/board/render/index.js +4 -2
- package/dist/board/render/media-interaction.d.ts +19 -0
- package/dist/board/render/media-interaction.js +26 -0
- package/dist/board/render/renderers/audio-card-renderer.js +1 -1
- package/dist/board/render/renderers/board-renderer-registry.js +2 -2
- package/dist/board/render/renderers/draw-card-renderer.js +1 -1
- package/dist/board/render/renderers/file-card-renderer.js +1 -1
- package/dist/board/render/renderers/frame-card-renderer.js +1 -1
- package/dist/board/render/renderers/geo-card-renderer.js +1 -1
- package/dist/board/render/renderers/image-card-renderer.js +1 -1
- package/dist/board/render/renderers/task-card-renderer.js +16 -13
- package/dist/board/render/renderers/text-card-renderer.js +1 -1
- package/dist/board/render/renderers/unknown-card-renderer.js +1 -1
- package/dist/board/render/renderers/video-card-renderer.js +1 -1
- package/dist/board/render/video-thumbnail.d.ts +16 -0
- package/dist/board/render/video-thumbnail.js +86 -0
- package/dist/board/task.d.ts +10 -5
- package/dist/board/task.js +159 -103
- package/dist/chunks/environment.d.ts +6 -6
- package/dist/chunks/environment.js +6 -6
- package/dist/chunks/http.d.ts +152 -5
- package/dist/chunks/http.js +1707 -169
- package/dist/chunks/transport.js +24 -4
- package/dist/chunks/websocket.d.ts +144 -2
- package/dist/chunks/websocket.js +1 -1
- package/dist/http.d.ts +3 -3
- package/dist/index.d.ts +273 -4
- package/dist/index.js +275 -741
- package/dist/protocol/dist/board-connection.d.ts +7 -0
- package/dist/protocol/dist/board-connection.js +4 -0
- package/dist/protocol/dist/board-content.d.ts +1 -0
- package/dist/protocol/dist/board-content.js +26 -0
- package/dist/protocol/dist/board-document.d.ts +157 -48
- package/dist/protocol/dist/board-document.js +40 -19
- package/dist/protocol/dist/board-node.d.ts +18 -0
- package/dist/protocol/dist/board-node.js +239 -0
- package/dist/protocol/dist/board-url.d.ts +12 -0
- package/dist/protocol/dist/board-url.js +83 -0
- package/dist/protocol/dist/board.d.ts +5 -0
- package/dist/protocol/dist/identifiers.js +10 -0
- package/dist/protocol/dist/index.d.ts +3 -1
- package/dist/protocol/dist/index.js +7 -1
- package/dist/protocol/dist/provenance.js +2 -0
- package/dist/protocol/dist/ui-command.js +2 -0
- package/dist/protocol/dist/work-promotion-stats.js +11 -0
- package/dist/protocol/dist/work-surface.js +2 -0
- package/dist/protocol/dist/work-view-stats.js +1 -0
- package/docs/work-runtime-guide.md +7 -7
- package/package.json +1 -1
package/dist/chunks/http.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { D as
|
|
1
|
+
import { D as getRealtimeBoardRoom, O as getRealtimeSpaceRoom, n as HttpTransport, t as HttpError, y as isUuid } from "./transport.js";
|
|
2
2
|
import { a as resolveApiBaseUrl } from "./environment.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import "perfect-freehand";
|
|
4
5
|
//#region src/apis/channels.ts
|
|
5
6
|
var ChannelsApi = class {
|
|
6
7
|
transport;
|
|
@@ -519,6 +520,8 @@ const BoardConnectionAnchorSchema = z.union([
|
|
|
519
520
|
const AUTO_BOARD_CONNECTION_ANCHOR = { kind: "auto" };
|
|
520
521
|
const BoardConnectionEndpointSchema = z.object({
|
|
521
522
|
nodeId: z.string().min(1).max(160),
|
|
523
|
+
/** Optional semantic port. Older connections omit it and remain valid. */
|
|
524
|
+
portId: z.string().min(1).max(120).optional(),
|
|
522
525
|
anchor: BoardConnectionAnchorSchema.default(AUTO_BOARD_CONNECTION_ANCHOR)
|
|
523
526
|
});
|
|
524
527
|
/**
|
|
@@ -615,10 +618,12 @@ function createBoardConnection(input) {
|
|
|
615
618
|
id: input.id,
|
|
616
619
|
source: {
|
|
617
620
|
nodeId: input.sourceNodeId,
|
|
621
|
+
...input.sourcePortId ? { portId: input.sourcePortId } : {},
|
|
618
622
|
anchor: input.sourceAnchor ?? AUTO_BOARD_CONNECTION_ANCHOR
|
|
619
623
|
},
|
|
620
624
|
target: {
|
|
621
625
|
nodeId: input.targetNodeId,
|
|
626
|
+
...input.targetPortId ? { portId: input.targetPortId } : {},
|
|
622
627
|
anchor: input.targetAnchor ?? AUTO_BOARD_CONNECTION_ANCHOR
|
|
623
628
|
},
|
|
624
629
|
relation: input.relation ?? "related",
|
|
@@ -641,6 +646,1549 @@ function ensureRealtimeConnected(websocketClient) {
|
|
|
641
646
|
});
|
|
642
647
|
}
|
|
643
648
|
//#endregion
|
|
649
|
+
//#region ../protocol/dist/board.js
|
|
650
|
+
const BOARD_DOCUMENT_KIND = "cohub.board";
|
|
651
|
+
const BOARD_MANIFEST_KIND = "cohub.board.manifest";
|
|
652
|
+
const idSchema$1 = z.string().min(1).max(160);
|
|
653
|
+
const extensionIdSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
|
|
654
|
+
const jsonObjectSchema = z.record(z.string(), z.unknown());
|
|
655
|
+
const finiteSchema$1 = z.number().finite();
|
|
656
|
+
z.object({
|
|
657
|
+
kind: z.literal(BOARD_MANIFEST_KIND),
|
|
658
|
+
version: z.literal(1),
|
|
659
|
+
boardId: z.string().uuid(),
|
|
660
|
+
title: z.string().min(1).max(255)
|
|
661
|
+
});
|
|
662
|
+
const BoardTargetSchema = z.discriminatedUnion("type", [
|
|
663
|
+
z.object({
|
|
664
|
+
type: z.literal("node"),
|
|
665
|
+
nodeId: idSchema$1
|
|
666
|
+
}),
|
|
667
|
+
z.object({
|
|
668
|
+
type: z.literal("effect"),
|
|
669
|
+
effectId: idSchema$1
|
|
670
|
+
}),
|
|
671
|
+
z.object({ type: z.literal("board") }),
|
|
672
|
+
z.object({ type: z.literal("camera") })
|
|
673
|
+
]);
|
|
674
|
+
const BoardAssetRefSchema = z.object({
|
|
675
|
+
type: z.enum(["space-file", "extension"]),
|
|
676
|
+
ref: z.string().min(1).max(4096),
|
|
677
|
+
digest: z.string().min(16).max(160).optional()
|
|
678
|
+
});
|
|
679
|
+
const BoardKeyframeSchema = z.object({
|
|
680
|
+
at: finiteSchema$1.nonnegative(),
|
|
681
|
+
value: z.unknown(),
|
|
682
|
+
easing: z.string().min(1).max(80).optional()
|
|
683
|
+
});
|
|
684
|
+
const BoardClipSchema = z.object({
|
|
685
|
+
id: idSchema$1,
|
|
686
|
+
sequenceId: idSchema$1,
|
|
687
|
+
kind: extensionIdSchema,
|
|
688
|
+
kindVersion: z.number().int().positive(),
|
|
689
|
+
target: BoardTargetSchema,
|
|
690
|
+
start: finiteSchema$1.nonnegative(),
|
|
691
|
+
duration: finiteSchema$1.positive(),
|
|
692
|
+
layer: z.enum([
|
|
693
|
+
"behind",
|
|
694
|
+
"content",
|
|
695
|
+
"front",
|
|
696
|
+
"screen"
|
|
697
|
+
]).default("content"),
|
|
698
|
+
fill: z.enum([
|
|
699
|
+
"none",
|
|
700
|
+
"backwards",
|
|
701
|
+
"forwards",
|
|
702
|
+
"both"
|
|
703
|
+
]).default("none"),
|
|
704
|
+
easing: z.string().min(1).max(80).default("linear"),
|
|
705
|
+
params: jsonObjectSchema.default({}),
|
|
706
|
+
keyframes: z.array(BoardKeyframeSchema).default([]),
|
|
707
|
+
assetRefs: z.array(BoardAssetRefSchema).default([]),
|
|
708
|
+
seed: z.string().min(1).max(160),
|
|
709
|
+
metadata: jsonObjectSchema.default({})
|
|
710
|
+
});
|
|
711
|
+
const BoardEffectSchema = z.object({
|
|
712
|
+
id: idSchema$1,
|
|
713
|
+
boardId: z.string().uuid(),
|
|
714
|
+
target: z.discriminatedUnion("type", [z.object({
|
|
715
|
+
type: z.literal("node"),
|
|
716
|
+
nodeId: idSchema$1
|
|
717
|
+
}), z.object({ type: z.literal("board") })]),
|
|
718
|
+
kind: extensionIdSchema,
|
|
719
|
+
kindVersion: z.number().int().positive(),
|
|
720
|
+
enabled: z.boolean().default(true),
|
|
721
|
+
lifecycle: z.enum([
|
|
722
|
+
"persistent",
|
|
723
|
+
"when-visible",
|
|
724
|
+
"manual"
|
|
725
|
+
]),
|
|
726
|
+
timeOrigin: z.enum([
|
|
727
|
+
"board",
|
|
728
|
+
"visible",
|
|
729
|
+
"activation"
|
|
730
|
+
]),
|
|
731
|
+
layer: z.enum([
|
|
732
|
+
"behind",
|
|
733
|
+
"front",
|
|
734
|
+
"screen"
|
|
735
|
+
]).default("front"),
|
|
736
|
+
seed: z.string().min(1).max(160),
|
|
737
|
+
params: jsonObjectSchema.default({}),
|
|
738
|
+
assetRefs: z.array(BoardAssetRefSchema).default([]),
|
|
739
|
+
metadata: jsonObjectSchema.default({}),
|
|
740
|
+
revision: z.number().int().nonnegative()
|
|
741
|
+
});
|
|
742
|
+
const BoardSequenceSchema = z.object({
|
|
743
|
+
id: idSchema$1,
|
|
744
|
+
boardId: z.string().uuid(),
|
|
745
|
+
name: z.string().min(1).max(255),
|
|
746
|
+
duration: finiteSchema$1.nonnegative(),
|
|
747
|
+
seed: z.string().min(1).max(160),
|
|
748
|
+
restPose: jsonObjectSchema.default({}),
|
|
749
|
+
metadata: jsonObjectSchema.default({}),
|
|
750
|
+
revision: z.number().int().nonnegative()
|
|
751
|
+
});
|
|
752
|
+
const BoardNodeInputSchema = z.object({
|
|
753
|
+
nodeId: idSchema$1,
|
|
754
|
+
type: z.string().min(1).max(40),
|
|
755
|
+
parentId: idSchema$1.nullable(),
|
|
756
|
+
orderKey: z.string().max(4096).nullable(),
|
|
757
|
+
x: finiteSchema$1,
|
|
758
|
+
y: finiteSchema$1,
|
|
759
|
+
width: finiteSchema$1.positive(),
|
|
760
|
+
height: finiteSchema$1.positive(),
|
|
761
|
+
rotation: finiteSchema$1,
|
|
762
|
+
refKind: z.string().max(40).nullable(),
|
|
763
|
+
refPath: z.string().max(4096).nullable(),
|
|
764
|
+
refUrl: z.string().max(4096).nullable(),
|
|
765
|
+
view: jsonObjectSchema,
|
|
766
|
+
style: jsonObjectSchema,
|
|
767
|
+
data: jsonObjectSchema
|
|
768
|
+
});
|
|
769
|
+
z.object({
|
|
770
|
+
path: z.string().min(1),
|
|
771
|
+
/** Reused by clients when board creation is interrupted and retried. */
|
|
772
|
+
mutationId: z.string().max(128).optional(),
|
|
773
|
+
title: z.string().min(1).max(255).optional(),
|
|
774
|
+
metadata: jsonObjectSchema.optional(),
|
|
775
|
+
nodes: z.array(BoardNodeInputSchema).max(5e4).optional(),
|
|
776
|
+
connections: z.array(BoardConnectionSchema).max(5e4).optional(),
|
|
777
|
+
effects: z.array(BoardEffectSchema.omit({
|
|
778
|
+
boardId: true,
|
|
779
|
+
revision: true
|
|
780
|
+
})).optional(),
|
|
781
|
+
sequences: z.array(z.object({
|
|
782
|
+
sequence: BoardSequenceSchema.omit({
|
|
783
|
+
boardId: true,
|
|
784
|
+
revision: true
|
|
785
|
+
}),
|
|
786
|
+
clips: z.array(BoardClipSchema.omit({ sequenceId: true }))
|
|
787
|
+
})).optional()
|
|
788
|
+
});
|
|
789
|
+
z.object({
|
|
790
|
+
include: z.array(z.enum([
|
|
791
|
+
"nodes",
|
|
792
|
+
"connections",
|
|
793
|
+
"effects",
|
|
794
|
+
"sequences",
|
|
795
|
+
"clips",
|
|
796
|
+
"playback"
|
|
797
|
+
])).optional(),
|
|
798
|
+
viewport: z.object({
|
|
799
|
+
x: finiteSchema$1,
|
|
800
|
+
y: finiteSchema$1,
|
|
801
|
+
width: finiteSchema$1.positive(),
|
|
802
|
+
height: finiteSchema$1.positive()
|
|
803
|
+
}).optional()
|
|
804
|
+
});
|
|
805
|
+
/** Persisted on `boards.metadata.playback`: how a Board plays when opened. */
|
|
806
|
+
const BoardPlaybackPolicySchema = z.object({
|
|
807
|
+
sequenceId: idSchema$1,
|
|
808
|
+
/** Delay before the first local playback after opening the Board, in milliseconds. */
|
|
809
|
+
delayMs: finiteSchema$1.nonnegative().default(0),
|
|
810
|
+
loop: z.boolean().default(false)
|
|
811
|
+
});
|
|
812
|
+
function parseBoardPlaybackPolicy(metadata) {
|
|
813
|
+
const parsed = BoardPlaybackPolicySchema.safeParse(metadata.playback);
|
|
814
|
+
return parsed.success ? parsed.data : null;
|
|
815
|
+
}
|
|
816
|
+
z.discriminatedUnion("type", [
|
|
817
|
+
z.object({
|
|
818
|
+
commandId: idSchema$1,
|
|
819
|
+
type: z.literal("play"),
|
|
820
|
+
sequenceId: idSchema$1,
|
|
821
|
+
position: finiteSchema$1.nonnegative().optional(),
|
|
822
|
+
timeScale: finiteSchema$1.positive().max(4).optional(),
|
|
823
|
+
shared: z.boolean().optional(),
|
|
824
|
+
seed: idSchema$1.optional()
|
|
825
|
+
}),
|
|
826
|
+
z.object({
|
|
827
|
+
commandId: idSchema$1,
|
|
828
|
+
type: z.literal("pause"),
|
|
829
|
+
playbackId: z.string().uuid()
|
|
830
|
+
}),
|
|
831
|
+
z.object({
|
|
832
|
+
commandId: idSchema$1,
|
|
833
|
+
type: z.literal("seek"),
|
|
834
|
+
playbackId: z.string().uuid(),
|
|
835
|
+
position: finiteSchema$1.nonnegative()
|
|
836
|
+
}),
|
|
837
|
+
z.object({
|
|
838
|
+
commandId: idSchema$1,
|
|
839
|
+
type: z.literal("stop"),
|
|
840
|
+
playbackId: z.string().uuid()
|
|
841
|
+
})
|
|
842
|
+
]);
|
|
843
|
+
const BoardContentKindSchema = z.enum([
|
|
844
|
+
"text",
|
|
845
|
+
"image",
|
|
846
|
+
"video",
|
|
847
|
+
"audio",
|
|
848
|
+
"file",
|
|
849
|
+
"json",
|
|
850
|
+
"collection"
|
|
851
|
+
]);
|
|
852
|
+
const BoardPortSchema = z.object({
|
|
853
|
+
id: z.string().min(1).max(120),
|
|
854
|
+
kind: BoardContentKindSchema,
|
|
855
|
+
role: z.string().min(1).max(80).optional(),
|
|
856
|
+
required: z.boolean().optional(),
|
|
857
|
+
multiple: z.boolean().optional(),
|
|
858
|
+
maxItems: z.number().int().positive().optional()
|
|
859
|
+
}).strict();
|
|
860
|
+
z.object({
|
|
861
|
+
inputs: z.array(BoardPortSchema),
|
|
862
|
+
outputs: z.array(BoardPortSchema)
|
|
863
|
+
}).strict();
|
|
864
|
+
//#endregion
|
|
865
|
+
//#region ../protocol/dist/board-url.js
|
|
866
|
+
const BOARD_REMOTE_URL_MAX_LENGTH = 4096;
|
|
867
|
+
function parseIpv4(host) {
|
|
868
|
+
const parts = host.split(".").map(Number);
|
|
869
|
+
return parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255) ? parts : null;
|
|
870
|
+
}
|
|
871
|
+
function isBlockedIpv4(host) {
|
|
872
|
+
const parts = parseIpv4(host);
|
|
873
|
+
if (!parts) return false;
|
|
874
|
+
const [first, second, third] = parts;
|
|
875
|
+
if (first === 0 || first === 10 || first === 127) return true;
|
|
876
|
+
if (first === 100 && second >= 64 && second <= 127) return true;
|
|
877
|
+
if (first === 169 && second === 254) return true;
|
|
878
|
+
if (first === 172 && second >= 16 && second <= 31) return true;
|
|
879
|
+
if (first === 192 && second === 168) return true;
|
|
880
|
+
if (first === 192 && second === 0 && (third === 0 || third === 2)) return true;
|
|
881
|
+
if (first === 192 && second === 88 && third === 99) return true;
|
|
882
|
+
if (first === 198 && (second === 18 || second === 19)) return true;
|
|
883
|
+
if (first === 198 && second === 51 && third === 100) return true;
|
|
884
|
+
if (first === 203 && second === 0 && third === 113) return true;
|
|
885
|
+
return first >= 224;
|
|
886
|
+
}
|
|
887
|
+
function expandIpv6(host) {
|
|
888
|
+
const [head, tail, extra] = host.toLowerCase().split("::");
|
|
889
|
+
if (extra !== void 0) return null;
|
|
890
|
+
const headParts = head ? head.split(":").filter(Boolean) : [];
|
|
891
|
+
const tailParts = tail ? tail.split(":").filter(Boolean) : [];
|
|
892
|
+
const missing = 8 - headParts.length - tailParts.length;
|
|
893
|
+
if (missing < 0 || tail === void 0 && missing !== 0) return null;
|
|
894
|
+
const parts = [
|
|
895
|
+
...headParts,
|
|
896
|
+
...Array.from({ length: missing }, () => "0"),
|
|
897
|
+
...tailParts
|
|
898
|
+
];
|
|
899
|
+
if (parts.length !== 8 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) return null;
|
|
900
|
+
return parts.map((part) => part.padStart(4, "0"));
|
|
901
|
+
}
|
|
902
|
+
function isBlockedIpv6(host) {
|
|
903
|
+
const parts = expandIpv6(host);
|
|
904
|
+
if (!parts) return true;
|
|
905
|
+
if (parts.every((part) => part === "0000")) return true;
|
|
906
|
+
if (parts.slice(0, 7).every((part) => part === "0000") && parts[7] === "0001") return true;
|
|
907
|
+
if (parts.slice(0, 5).every((part) => part === "0000") && parts[5] === "ffff") {
|
|
908
|
+
const high = Number.parseInt(parts[6] ?? "0", 16);
|
|
909
|
+
const low = Number.parseInt(parts[7] ?? "0", 16);
|
|
910
|
+
return isBlockedIpv4(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
|
|
911
|
+
}
|
|
912
|
+
if (parts.slice(0, 6).every((part) => part === "0000")) return true;
|
|
913
|
+
const first = Number.parseInt(parts[0] ?? "0", 16);
|
|
914
|
+
if ((first & 65024) === 64512) return true;
|
|
915
|
+
if ((first & 65472) === 65152 || (first & 65472) === 65216) return true;
|
|
916
|
+
if ((first & 65280) === 65280) return true;
|
|
917
|
+
return parts[0] === "2001" && parts[1] === "0db8";
|
|
918
|
+
}
|
|
919
|
+
function isBlockedHost(hostname) {
|
|
920
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
921
|
+
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
922
|
+
if (parseIpv4(host)) return isBlockedIpv4(host);
|
|
923
|
+
return host.includes(":") && isBlockedIpv6(host);
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Normalize a browser-loadable public HTTP(S) URL. This blocks explicit local
|
|
927
|
+
* addresses; any future server-side fetcher must additionally validate DNS
|
|
928
|
+
* resolution to defend against rebinding.
|
|
929
|
+
*/
|
|
930
|
+
function normalizeBoardRemoteUrl(value) {
|
|
931
|
+
if (typeof value !== "string") return void 0;
|
|
932
|
+
const input = value.trim();
|
|
933
|
+
if (!input || input.length > 4096) return void 0;
|
|
934
|
+
try {
|
|
935
|
+
const url = new URL(input);
|
|
936
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
|
|
937
|
+
if (url.username || url.password || isBlockedHost(url.hostname)) return;
|
|
938
|
+
const normalized = url.toString();
|
|
939
|
+
return normalized.length <= 4096 ? normalized : void 0;
|
|
940
|
+
} catch {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
const BoardRemoteUrlSchema = z.string().max(BOARD_REMOTE_URL_MAX_LENGTH).refine((value) => normalizeBoardRemoteUrl(value) !== void 0, { message: "URL must be a public HTTP(S) URL without credentials" });
|
|
945
|
+
//#endregion
|
|
946
|
+
//#region ../protocol/dist/board-document.js
|
|
947
|
+
const BoardFrameSchema = z.object({
|
|
948
|
+
x: z.number().finite(),
|
|
949
|
+
y: z.number().finite(),
|
|
950
|
+
width: z.number().finite().positive(),
|
|
951
|
+
height: z.number().finite().positive(),
|
|
952
|
+
rotation: z.number().finite().default(0)
|
|
953
|
+
});
|
|
954
|
+
/**
|
|
955
|
+
* The board camera. This is local UI state, not synced content: semantic ops
|
|
956
|
+
* (see diffBoardDocuments) never describe the viewport, and the editor holds
|
|
957
|
+
* the live camera separately from the persisted document. Here it only serves
|
|
958
|
+
* as an initial camera hint when a document is first loaded.
|
|
959
|
+
*/
|
|
960
|
+
const BoardViewportSchema = z.object({
|
|
961
|
+
x: z.number().finite(),
|
|
962
|
+
y: z.number().finite(),
|
|
963
|
+
zoom: z.number().finite().min(.05).max(8)
|
|
964
|
+
});
|
|
965
|
+
const BoardAppearanceSchema = z.object({
|
|
966
|
+
theme: z.string().min(1).default("clean"),
|
|
967
|
+
background: z.object({
|
|
968
|
+
kind: z.enum([
|
|
969
|
+
"solid",
|
|
970
|
+
"dots",
|
|
971
|
+
"grid",
|
|
972
|
+
"image",
|
|
973
|
+
"shader",
|
|
974
|
+
"custom"
|
|
975
|
+
]).default("dots"),
|
|
976
|
+
color: z.string().optional(),
|
|
977
|
+
imageUrl: z.string().url().optional()
|
|
978
|
+
}).default({ kind: "solid" }),
|
|
979
|
+
grid: z.object({
|
|
980
|
+
visible: z.boolean().default(false),
|
|
981
|
+
size: z.number().finite().min(4).default(24),
|
|
982
|
+
opacity: z.number().finite().min(0).max(1).default(.12)
|
|
983
|
+
}).default({
|
|
984
|
+
visible: false,
|
|
985
|
+
size: 24,
|
|
986
|
+
opacity: .12
|
|
987
|
+
}),
|
|
988
|
+
mood: z.enum([
|
|
989
|
+
"clean",
|
|
990
|
+
"playful",
|
|
991
|
+
"arcane",
|
|
992
|
+
"cyber",
|
|
993
|
+
"natural"
|
|
994
|
+
]).default("clean")
|
|
995
|
+
});
|
|
996
|
+
/** Optional visual chrome still carried by a few older shapes. */
|
|
997
|
+
const BoardItemStyleSchema = z.object({
|
|
998
|
+
variant: z.string().min(1).default("default"),
|
|
999
|
+
theme: z.string().min(1).optional(),
|
|
1000
|
+
accentColor: z.string().optional(),
|
|
1001
|
+
size: z.enum([
|
|
1002
|
+
"sm",
|
|
1003
|
+
"md",
|
|
1004
|
+
"lg"
|
|
1005
|
+
]).default("md"),
|
|
1006
|
+
emphasis: z.enum([
|
|
1007
|
+
"normal",
|
|
1008
|
+
"rare",
|
|
1009
|
+
"epic",
|
|
1010
|
+
"legendary"
|
|
1011
|
+
]).default("normal"),
|
|
1012
|
+
effects: z.array(z.string().min(1)).default([])
|
|
1013
|
+
});
|
|
1014
|
+
const SpaceFileRefSchema = z.object({
|
|
1015
|
+
kind: z.literal("space-file"),
|
|
1016
|
+
path: z.string().min(1)
|
|
1017
|
+
});
|
|
1018
|
+
const BoardMediaSnapshotSchema = z.object({
|
|
1019
|
+
title: z.string().optional(),
|
|
1020
|
+
mimeType: z.string().optional(),
|
|
1021
|
+
size: z.number().finite().nonnegative().optional(),
|
|
1022
|
+
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
1023
|
+
/** Intrinsic pixel size once known. */
|
|
1024
|
+
naturalWidth: z.number().finite().positive().optional(),
|
|
1025
|
+
naturalHeight: z.number().finite().positive().optional()
|
|
1026
|
+
});
|
|
1027
|
+
const BoardItemBaseSchema = z.object({
|
|
1028
|
+
id: z.string().min(1),
|
|
1029
|
+
frame: BoardFrameSchema,
|
|
1030
|
+
/** When true the shape cannot be moved, resized, or deleted. */
|
|
1031
|
+
locked: z.boolean().optional(),
|
|
1032
|
+
style: BoardItemStyleSchema.optional(),
|
|
1033
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
1034
|
+
});
|
|
1035
|
+
/** Freestanding text — no card chrome; its frame always follows the glyphs. */
|
|
1036
|
+
const BoardTextItemSchema = BoardItemBaseSchema.extend({
|
|
1037
|
+
type: z.literal("text"),
|
|
1038
|
+
text: z.string().default(""),
|
|
1039
|
+
color: z.string().min(1).default("neutral"),
|
|
1040
|
+
fontSize: z.number().finite().min(2).max(512).default(24)
|
|
1041
|
+
});
|
|
1042
|
+
const BoardGeoItemSchema = BoardItemBaseSchema.extend({
|
|
1043
|
+
type: z.literal("geo"),
|
|
1044
|
+
geo: z.string().min(1).default("rectangle"),
|
|
1045
|
+
text: z.string().default(""),
|
|
1046
|
+
color: z.string().min(1).default("brand"),
|
|
1047
|
+
fillOpacity: z.number().finite().min(0).max(1).default(0)
|
|
1048
|
+
});
|
|
1049
|
+
/** A raw freehand sample. Pressure defaults to 0.5 (mouse) when absent. */
|
|
1050
|
+
const DrawPointSchema = z.object({
|
|
1051
|
+
x: z.number().finite(),
|
|
1052
|
+
y: z.number().finite(),
|
|
1053
|
+
p: z.number().finite().min(0).max(1).default(.5)
|
|
1054
|
+
});
|
|
1055
|
+
/** A world-space point. */
|
|
1056
|
+
const BoardPointSchema = z.object({
|
|
1057
|
+
x: z.number().finite(),
|
|
1058
|
+
y: z.number().finite()
|
|
1059
|
+
});
|
|
1060
|
+
const BoardDrawItemSchema = BoardItemBaseSchema.extend({
|
|
1061
|
+
type: z.literal("draw"),
|
|
1062
|
+
points: z.array(DrawPointSchema).default([]),
|
|
1063
|
+
color: z.string().min(1).default("brand"),
|
|
1064
|
+
size: z.number().finite().positive().default(4)
|
|
1065
|
+
});
|
|
1066
|
+
/**
|
|
1067
|
+
* A free arrow — a standalone annotation stroke between two world points.
|
|
1068
|
+
*
|
|
1069
|
+
* Arrows do not relate nodes. A relation between two nodes is a
|
|
1070
|
+
* `BoardConnection`, which is stored separately and resolves its geometry from
|
|
1071
|
+
* the live node frames. Keeping the two apart is what lets an arrow be a plain
|
|
1072
|
+
* shape (its own frame, freely movable) while a connection stays purely
|
|
1073
|
+
* semantic — neither has to pretend to be the other.
|
|
1074
|
+
*/
|
|
1075
|
+
const BoardArrowItemSchema = BoardItemBaseSchema.extend({
|
|
1076
|
+
type: z.literal("arrow"),
|
|
1077
|
+
start: BoardPointSchema,
|
|
1078
|
+
end: BoardPointSchema,
|
|
1079
|
+
bend: z.number().finite().min(-.85).max(.85).default(0),
|
|
1080
|
+
color: z.string().min(1).default("brand"),
|
|
1081
|
+
size: z.number().finite().positive().default(BOARD_ARROW_STROKE_SIZE),
|
|
1082
|
+
arrowStart: z.boolean().default(false),
|
|
1083
|
+
arrowEnd: z.boolean().default(true),
|
|
1084
|
+
label: z.string().default("")
|
|
1085
|
+
});
|
|
1086
|
+
/** A frame container for organising shapes. */
|
|
1087
|
+
const BoardFrameItemSchema = BoardItemBaseSchema.extend({
|
|
1088
|
+
type: z.literal("frame"),
|
|
1089
|
+
label: z.string().default("Frame"),
|
|
1090
|
+
color: z.string().min(1).default("neutral")
|
|
1091
|
+
});
|
|
1092
|
+
/** Image node — space file only, natural aspect, no chrome. */
|
|
1093
|
+
const BoardImageItemSchema = BoardItemBaseSchema.extend({
|
|
1094
|
+
type: z.literal("image"),
|
|
1095
|
+
ref: SpaceFileRefSchema,
|
|
1096
|
+
snapshot: BoardMediaSnapshotSchema.optional(),
|
|
1097
|
+
/** Optional normalized crop in source image space (0..1). */
|
|
1098
|
+
crop: z.object({
|
|
1099
|
+
x: z.number().finite().min(0).max(1).default(0),
|
|
1100
|
+
y: z.number().finite().min(0).max(1).default(0),
|
|
1101
|
+
w: z.number().finite().min(0).max(1).default(1),
|
|
1102
|
+
h: z.number().finite().min(0).max(1).default(1)
|
|
1103
|
+
}).optional()
|
|
1104
|
+
});
|
|
1105
|
+
/** Video node — space file only. Playback state is local UI, never synced. */
|
|
1106
|
+
const BoardVideoItemSchema = BoardItemBaseSchema.extend({
|
|
1107
|
+
type: z.literal("video"),
|
|
1108
|
+
ref: SpaceFileRefSchema,
|
|
1109
|
+
snapshot: BoardMediaSnapshotSchema.optional()
|
|
1110
|
+
});
|
|
1111
|
+
/** Audio node — space file only. Playback state is local UI, never synced. */
|
|
1112
|
+
const BoardAudioItemSchema = BoardItemBaseSchema.extend({
|
|
1113
|
+
type: z.literal("audio"),
|
|
1114
|
+
ref: SpaceFileRefSchema,
|
|
1115
|
+
snapshot: BoardMediaSnapshotSchema.extend({ durationMs: z.number().finite().nonnegative().optional() }).optional()
|
|
1116
|
+
});
|
|
1117
|
+
/**
|
|
1118
|
+
* Cached display facts for a file card.
|
|
1119
|
+
*
|
|
1120
|
+
* Purely derived from the referenced file and versioned by `mtimeMs`, so it is a
|
|
1121
|
+
* cache and never a second source of truth: the workspace file remains
|
|
1122
|
+
* authoritative, and a stale snapshot is detectable rather than silently wrong.
|
|
1123
|
+
* It is stored so that opening a board renders complete cards immediately, with
|
|
1124
|
+
* no per-node file read on the first paint.
|
|
1125
|
+
*/
|
|
1126
|
+
const BoardFileSnapshotSchema = z.object({
|
|
1127
|
+
title: z.string().optional(),
|
|
1128
|
+
mimeType: z.string().optional(),
|
|
1129
|
+
size: z.number().finite().nonnegative().optional(),
|
|
1130
|
+
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
1131
|
+
/** Cleaned leading prose. Capped when written (see FILE_EXCERPT_MAX_CHARS). */
|
|
1132
|
+
excerpt: z.string().optional(),
|
|
1133
|
+
/** Cover image inside the space, already resolved to a workspace path. */
|
|
1134
|
+
coverPath: z.string().optional(),
|
|
1135
|
+
/** Cover image at an absolute https URL, as declared by the file itself. */
|
|
1136
|
+
coverUrl: z.string().optional()
|
|
1137
|
+
});
|
|
1138
|
+
/**
|
|
1139
|
+
* File node — a thumbnail entry point to any workspace file.
|
|
1140
|
+
*
|
|
1141
|
+
* This is the fallback for every dropped file that is not natively an image or
|
|
1142
|
+
* video, including binaries and unknown extensions: a board should never refuse
|
|
1143
|
+
* a file, only present it with less detail. Presentation tiers are derived from
|
|
1144
|
+
* the snapshot (see filePreviewKind), not stored, so there is no display state
|
|
1145
|
+
* to drift from the facts.
|
|
1146
|
+
*/
|
|
1147
|
+
const BoardFileItemSchema = BoardItemBaseSchema.extend({
|
|
1148
|
+
type: z.literal("file"),
|
|
1149
|
+
ref: SpaceFileRefSchema,
|
|
1150
|
+
snapshot: BoardFileSnapshotSchema.optional()
|
|
1151
|
+
});
|
|
1152
|
+
const BoardTaskMediaArtifactFields = {
|
|
1153
|
+
id: z.string().min(1).max(240),
|
|
1154
|
+
title: z.string().max(240).optional(),
|
|
1155
|
+
url: BoardRemoteUrlSchema,
|
|
1156
|
+
mimeType: z.string().max(160).optional()
|
|
1157
|
+
};
|
|
1158
|
+
const BoardTaskArtifactSchema = z.discriminatedUnion("type", [
|
|
1159
|
+
z.object({
|
|
1160
|
+
...BoardTaskMediaArtifactFields,
|
|
1161
|
+
type: z.literal("image"),
|
|
1162
|
+
naturalWidth: z.number().positive().optional(),
|
|
1163
|
+
naturalHeight: z.number().positive().optional()
|
|
1164
|
+
}).strict(),
|
|
1165
|
+
z.object({
|
|
1166
|
+
...BoardTaskMediaArtifactFields,
|
|
1167
|
+
type: z.literal("video"),
|
|
1168
|
+
previewUrl: BoardRemoteUrlSchema.optional(),
|
|
1169
|
+
durationMs: z.number().int().positive().optional(),
|
|
1170
|
+
naturalWidth: z.number().positive().optional(),
|
|
1171
|
+
naturalHeight: z.number().positive().optional()
|
|
1172
|
+
}).strict(),
|
|
1173
|
+
z.object({
|
|
1174
|
+
...BoardTaskMediaArtifactFields,
|
|
1175
|
+
type: z.literal("audio"),
|
|
1176
|
+
previewUrl: BoardRemoteUrlSchema.optional(),
|
|
1177
|
+
durationMs: z.number().int().positive().optional()
|
|
1178
|
+
}).strict(),
|
|
1179
|
+
z.object({
|
|
1180
|
+
id: z.string().min(1).max(240),
|
|
1181
|
+
type: z.literal("text"),
|
|
1182
|
+
title: z.string().max(240).optional(),
|
|
1183
|
+
textExcerpt: z.string().min(1).max(480)
|
|
1184
|
+
}).strict()
|
|
1185
|
+
]);
|
|
1186
|
+
/**
|
|
1187
|
+
* Cached task facts used for an immediate first paint. The task run remains the
|
|
1188
|
+
* source of truth and live clients refresh this projection by `taskRunId`.
|
|
1189
|
+
*/
|
|
1190
|
+
const BoardTaskSnapshotSchema = z.object({
|
|
1191
|
+
taskType: z.string().min(1).max(120),
|
|
1192
|
+
status: z.enum([
|
|
1193
|
+
"pending",
|
|
1194
|
+
"running",
|
|
1195
|
+
"completed",
|
|
1196
|
+
"failed"
|
|
1197
|
+
]),
|
|
1198
|
+
title: z.string().min(1).max(240),
|
|
1199
|
+
model: z.string().max(160).optional(),
|
|
1200
|
+
promptExcerpt: z.string().max(480).optional(),
|
|
1201
|
+
artifactCount: z.number().int().nonnegative(),
|
|
1202
|
+
artifacts: z.array(BoardTaskArtifactSchema).max(6).default([]),
|
|
1203
|
+
updatedAt: z.string().optional()
|
|
1204
|
+
}).strict();
|
|
1205
|
+
/** A stable reference to a task run with a small, replaceable display cache. */
|
|
1206
|
+
const BoardTaskItemSchema = BoardItemBaseSchema.extend({
|
|
1207
|
+
type: z.literal("task"),
|
|
1208
|
+
taskRunId: z.string().min(1),
|
|
1209
|
+
snapshot: BoardTaskSnapshotSchema
|
|
1210
|
+
});
|
|
1211
|
+
/**
|
|
1212
|
+
* A forward-compatible carrier for shape types this client does not recognise.
|
|
1213
|
+
* Its discriminant is the literal `"unknown"` so the item union still narrows
|
|
1214
|
+
* cleanly on `type`. The real type string and every original field are preserved
|
|
1215
|
+
* verbatim in `raw`.
|
|
1216
|
+
*/
|
|
1217
|
+
const UNKNOWN_BOARD_ITEM_TYPE = "unknown";
|
|
1218
|
+
const BoardItemSchema = z.any().transform((raw) => parseBoardItemLoose(raw));
|
|
1219
|
+
/**
|
|
1220
|
+
* Parse a single item leniently: known types are validated and normalised;
|
|
1221
|
+
* anything else becomes a lossless unknown item. Never throws on shape data —
|
|
1222
|
+
* a malformed known item degrades to unknown rather than failing the document.
|
|
1223
|
+
*/
|
|
1224
|
+
function parseBoardItemLoose(raw) {
|
|
1225
|
+
if (!raw || typeof raw !== "object") return makeUnknownItem(raw);
|
|
1226
|
+
switch (raw.type) {
|
|
1227
|
+
case "image": {
|
|
1228
|
+
const parsed = BoardImageItemSchema.safeParse(raw);
|
|
1229
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1230
|
+
}
|
|
1231
|
+
case "video": {
|
|
1232
|
+
const parsed = BoardVideoItemSchema.safeParse(raw);
|
|
1233
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1234
|
+
}
|
|
1235
|
+
case "audio": {
|
|
1236
|
+
const parsed = BoardAudioItemSchema.safeParse(raw);
|
|
1237
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1238
|
+
}
|
|
1239
|
+
case "file": {
|
|
1240
|
+
const parsed = BoardFileItemSchema.safeParse(raw);
|
|
1241
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1242
|
+
}
|
|
1243
|
+
case "task": {
|
|
1244
|
+
const parsed = BoardTaskItemSchema.safeParse(raw);
|
|
1245
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1246
|
+
}
|
|
1247
|
+
case "text": {
|
|
1248
|
+
const parsed = BoardTextItemSchema.safeParse(raw);
|
|
1249
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1250
|
+
}
|
|
1251
|
+
case "geo": {
|
|
1252
|
+
const parsed = BoardGeoItemSchema.safeParse(raw);
|
|
1253
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1254
|
+
}
|
|
1255
|
+
case "draw": {
|
|
1256
|
+
const parsed = BoardDrawItemSchema.safeParse(raw);
|
|
1257
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1258
|
+
}
|
|
1259
|
+
case "arrow": {
|
|
1260
|
+
const parsed = BoardArrowItemSchema.safeParse(raw);
|
|
1261
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1262
|
+
}
|
|
1263
|
+
case "frame": {
|
|
1264
|
+
const parsed = BoardFrameItemSchema.safeParse(raw);
|
|
1265
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
1266
|
+
}
|
|
1267
|
+
default: return makeUnknownItem(raw);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
function makeUnknownItem(raw) {
|
|
1271
|
+
const record = raw && typeof raw === "object" ? raw : {};
|
|
1272
|
+
const frameParsed = BoardFrameSchema.safeParse(record.frame);
|
|
1273
|
+
const styleParsed = BoardItemStyleSchema.safeParse(record.style);
|
|
1274
|
+
return {
|
|
1275
|
+
id: typeof record.id === "string" && record.id ? record.id : "unknown",
|
|
1276
|
+
type: UNKNOWN_BOARD_ITEM_TYPE,
|
|
1277
|
+
frame: frameParsed.success ? frameParsed.data : {
|
|
1278
|
+
x: 0,
|
|
1279
|
+
y: 0,
|
|
1280
|
+
width: 120,
|
|
1281
|
+
height: 80,
|
|
1282
|
+
rotation: 0
|
|
1283
|
+
},
|
|
1284
|
+
...record.locked === true ? { locked: true } : {},
|
|
1285
|
+
...styleParsed.success && record.style ? { style: styleParsed.data } : {},
|
|
1286
|
+
...record.metadata && typeof record.metadata === "object" ? { metadata: record.metadata } : {},
|
|
1287
|
+
raw: record
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
z.object({
|
|
1291
|
+
kind: z.literal(BOARD_DOCUMENT_KIND),
|
|
1292
|
+
version: z.literal(1),
|
|
1293
|
+
appearance: BoardAppearanceSchema.default({
|
|
1294
|
+
theme: "clean",
|
|
1295
|
+
background: { kind: "solid" },
|
|
1296
|
+
grid: {
|
|
1297
|
+
visible: false,
|
|
1298
|
+
size: 24,
|
|
1299
|
+
opacity: .12
|
|
1300
|
+
},
|
|
1301
|
+
mood: "clean"
|
|
1302
|
+
}),
|
|
1303
|
+
viewport: BoardViewportSchema,
|
|
1304
|
+
items: z.array(BoardItemSchema),
|
|
1305
|
+
/**
|
|
1306
|
+
* Node relations. Separate from `items` because a connection has no frame of
|
|
1307
|
+
* its own — its geometry is derived from the nodes it joins, so it is a
|
|
1308
|
+
* relation over the item set rather than a member of it.
|
|
1309
|
+
*
|
|
1310
|
+
* Connections referencing a missing node are dropped on parse: a relation to
|
|
1311
|
+
* nothing is not a relation, and keeping one would let an invisible dangling
|
|
1312
|
+
* edge accumulate silently. Callers that need to know write through the
|
|
1313
|
+
* transaction API, which reports the reference error instead.
|
|
1314
|
+
*/
|
|
1315
|
+
connections: z.array(BoardConnectionSchema).default([])
|
|
1316
|
+
});
|
|
1317
|
+
//#endregion
|
|
1318
|
+
//#region ../protocol/dist/board-node.js
|
|
1319
|
+
const BOARD_COLOR_IDS = [
|
|
1320
|
+
"brand",
|
|
1321
|
+
"neutral",
|
|
1322
|
+
"black",
|
|
1323
|
+
"white",
|
|
1324
|
+
"blue",
|
|
1325
|
+
"green",
|
|
1326
|
+
"amber",
|
|
1327
|
+
"violet",
|
|
1328
|
+
"rose"
|
|
1329
|
+
];
|
|
1330
|
+
const BoardColorIdSchema = z.enum(BOARD_COLOR_IDS);
|
|
1331
|
+
const BOARD_GEO_KINDS = [
|
|
1332
|
+
"rectangle",
|
|
1333
|
+
"rounded",
|
|
1334
|
+
"ellipse",
|
|
1335
|
+
"diamond",
|
|
1336
|
+
"triangle"
|
|
1337
|
+
];
|
|
1338
|
+
const BoardGeoKindSchema = z.enum(BOARD_GEO_KINDS);
|
|
1339
|
+
const BOARD_NATIVE_NODE_TYPES = [
|
|
1340
|
+
"image",
|
|
1341
|
+
"video",
|
|
1342
|
+
"audio",
|
|
1343
|
+
"file",
|
|
1344
|
+
"task",
|
|
1345
|
+
"text",
|
|
1346
|
+
"geo",
|
|
1347
|
+
"draw",
|
|
1348
|
+
"arrow",
|
|
1349
|
+
"frame"
|
|
1350
|
+
];
|
|
1351
|
+
const metadataSchema = z.record(z.string(), z.unknown());
|
|
1352
|
+
const commonData = {
|
|
1353
|
+
locked: z.boolean().optional(),
|
|
1354
|
+
metadata: metadataSchema.optional()
|
|
1355
|
+
};
|
|
1356
|
+
const pointSchema = z.object({
|
|
1357
|
+
x: z.number().finite(),
|
|
1358
|
+
y: z.number().finite(),
|
|
1359
|
+
p: z.number().finite().min(0).max(1).default(.5)
|
|
1360
|
+
}).strict();
|
|
1361
|
+
const worldPointSchema = z.object({
|
|
1362
|
+
x: z.number().finite(),
|
|
1363
|
+
y: z.number().finite()
|
|
1364
|
+
}).strict();
|
|
1365
|
+
const strokeSizeSchema = z.number().finite().min(1).max(64);
|
|
1366
|
+
const mediaViewSchema = z.object({
|
|
1367
|
+
title: z.string().optional(),
|
|
1368
|
+
mimeType: z.string().optional(),
|
|
1369
|
+
size: z.number().finite().nonnegative().optional(),
|
|
1370
|
+
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
1371
|
+
naturalWidth: z.number().finite().positive().optional(),
|
|
1372
|
+
naturalHeight: z.number().finite().positive().optional()
|
|
1373
|
+
}).strict();
|
|
1374
|
+
const audioViewSchema = mediaViewSchema.extend({ durationMs: z.number().finite().nonnegative().optional() });
|
|
1375
|
+
const dataSchemas = {
|
|
1376
|
+
text: z.object({
|
|
1377
|
+
...commonData,
|
|
1378
|
+
text: z.string().default(""),
|
|
1379
|
+
color: BoardColorIdSchema.default("neutral"),
|
|
1380
|
+
fontSize: z.number().finite().min(2).max(512).default(24)
|
|
1381
|
+
}).strict(),
|
|
1382
|
+
geo: z.object({
|
|
1383
|
+
...commonData,
|
|
1384
|
+
geo: BoardGeoKindSchema.default("rectangle"),
|
|
1385
|
+
text: z.string().default(""),
|
|
1386
|
+
color: BoardColorIdSchema.default("brand"),
|
|
1387
|
+
fillOpacity: z.number().finite().min(0).max(1).default(0)
|
|
1388
|
+
}).strict(),
|
|
1389
|
+
draw: z.object({
|
|
1390
|
+
...commonData,
|
|
1391
|
+
points: z.array(pointSchema).min(1),
|
|
1392
|
+
color: BoardColorIdSchema.default("brand"),
|
|
1393
|
+
size: strokeSizeSchema.default(4)
|
|
1394
|
+
}).strict(),
|
|
1395
|
+
arrow: z.object({
|
|
1396
|
+
...commonData,
|
|
1397
|
+
start: worldPointSchema,
|
|
1398
|
+
end: worldPointSchema,
|
|
1399
|
+
bend: z.number().finite().min(-.85).max(.85).default(0),
|
|
1400
|
+
color: BoardColorIdSchema.default("brand"),
|
|
1401
|
+
size: strokeSizeSchema.default(BOARD_ARROW_STROKE_SIZE),
|
|
1402
|
+
arrowStart: z.boolean().default(false),
|
|
1403
|
+
arrowEnd: z.boolean().default(true),
|
|
1404
|
+
label: z.string().default("")
|
|
1405
|
+
}).strict(),
|
|
1406
|
+
frame: z.object({
|
|
1407
|
+
...commonData,
|
|
1408
|
+
label: z.string().default("Frame"),
|
|
1409
|
+
color: BoardColorIdSchema.default("neutral")
|
|
1410
|
+
}).strict(),
|
|
1411
|
+
image: z.object({
|
|
1412
|
+
...commonData,
|
|
1413
|
+
crop: z.object({
|
|
1414
|
+
x: z.number().finite().min(0).max(1),
|
|
1415
|
+
y: z.number().finite().min(0).max(1),
|
|
1416
|
+
w: z.number().finite().min(0).max(1),
|
|
1417
|
+
h: z.number().finite().min(0).max(1)
|
|
1418
|
+
}).strict().optional()
|
|
1419
|
+
}).strict(),
|
|
1420
|
+
video: z.object(commonData).strict(),
|
|
1421
|
+
audio: z.object(commonData).strict(),
|
|
1422
|
+
file: z.object(commonData).strict(),
|
|
1423
|
+
task: z.object({
|
|
1424
|
+
...commonData,
|
|
1425
|
+
taskRunId: z.string().min(1)
|
|
1426
|
+
}).strict()
|
|
1427
|
+
};
|
|
1428
|
+
const fileViewSchema = z.object({
|
|
1429
|
+
title: z.string().optional(),
|
|
1430
|
+
mimeType: z.string().optional(),
|
|
1431
|
+
size: z.number().finite().nonnegative().optional(),
|
|
1432
|
+
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
1433
|
+
excerpt: z.string().optional(),
|
|
1434
|
+
coverPath: z.string().optional(),
|
|
1435
|
+
coverUrl: z.string().url().optional()
|
|
1436
|
+
}).strict();
|
|
1437
|
+
const taskViewSchema = BoardTaskSnapshotSchema;
|
|
1438
|
+
const emptyViewSchema = z.object({}).strict();
|
|
1439
|
+
const viewSchemas = {
|
|
1440
|
+
image: mediaViewSchema,
|
|
1441
|
+
video: mediaViewSchema,
|
|
1442
|
+
audio: audioViewSchema,
|
|
1443
|
+
file: fileViewSchema,
|
|
1444
|
+
task: taskViewSchema,
|
|
1445
|
+
text: emptyViewSchema,
|
|
1446
|
+
geo: emptyViewSchema,
|
|
1447
|
+
draw: emptyViewSchema,
|
|
1448
|
+
arrow: emptyViewSchema,
|
|
1449
|
+
frame: emptyViewSchema
|
|
1450
|
+
};
|
|
1451
|
+
const nodeEnvelopeSchema = z.object({
|
|
1452
|
+
nodeId: z.string().min(1).max(160),
|
|
1453
|
+
type: z.string().min(1).max(40),
|
|
1454
|
+
parentId: z.string().min(1).max(160).nullable(),
|
|
1455
|
+
orderKey: z.string().max(4096).nullable(),
|
|
1456
|
+
x: z.number().finite(),
|
|
1457
|
+
y: z.number().finite(),
|
|
1458
|
+
width: z.number().finite().positive(),
|
|
1459
|
+
height: z.number().finite().positive(),
|
|
1460
|
+
rotation: z.number().finite(),
|
|
1461
|
+
refKind: z.string().max(40).nullable(),
|
|
1462
|
+
refPath: z.string().max(4096).nullable(),
|
|
1463
|
+
refUrl: z.string().max(4096).nullable(),
|
|
1464
|
+
view: z.record(z.string(), z.unknown()),
|
|
1465
|
+
style: z.record(z.string(), z.unknown()),
|
|
1466
|
+
data: z.record(z.string(), z.unknown())
|
|
1467
|
+
}).strict();
|
|
1468
|
+
function jsonSchema(schema) {
|
|
1469
|
+
return z.toJSONSchema(schema);
|
|
1470
|
+
}
|
|
1471
|
+
const BOARD_NODE_CONTRACT = {
|
|
1472
|
+
types: BOARD_NATIVE_NODE_TYPES,
|
|
1473
|
+
colors: BOARD_COLOR_IDS,
|
|
1474
|
+
geos: BOARD_GEO_KINDS,
|
|
1475
|
+
coordinates: {
|
|
1476
|
+
frame: "world",
|
|
1477
|
+
drawPoints: "frame-local",
|
|
1478
|
+
arrowEndpoints: "world"
|
|
1479
|
+
},
|
|
1480
|
+
references: {
|
|
1481
|
+
nodeTypes: [
|
|
1482
|
+
"image",
|
|
1483
|
+
"video",
|
|
1484
|
+
"audio",
|
|
1485
|
+
"file"
|
|
1486
|
+
],
|
|
1487
|
+
kind: "space_file",
|
|
1488
|
+
pathField: "refPath"
|
|
1489
|
+
},
|
|
1490
|
+
schemas: {
|
|
1491
|
+
envelope: jsonSchema(nodeEnvelopeSchema),
|
|
1492
|
+
data: Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(dataSchemas[type])])),
|
|
1493
|
+
view: Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(viewSchemas[type])]))
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
function issueDiagnostic(issue, path) {
|
|
1497
|
+
const fullPath = [path, ...issue.path].join(".");
|
|
1498
|
+
const values = "values" in issue && Array.isArray(issue.values) ? issue.values.filter((value) => typeof value === "string") : void 0;
|
|
1499
|
+
return {
|
|
1500
|
+
severity: "error",
|
|
1501
|
+
code: "INVALID_BOARD_NODE",
|
|
1502
|
+
message: `${fullPath}: ${issue.message}`,
|
|
1503
|
+
path: fullPath,
|
|
1504
|
+
...values?.length ? { allowedValues: values } : {}
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
function drawGeometryDiagnostic(node, data, path) {
|
|
1508
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
1509
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
1510
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
1511
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
1512
|
+
for (const point of data.points) {
|
|
1513
|
+
const radius = Math.max(.5, data.size / 2 * (.5 + point.p));
|
|
1514
|
+
minX = Math.min(minX, point.x - radius);
|
|
1515
|
+
minY = Math.min(minY, point.y - radius);
|
|
1516
|
+
maxX = Math.max(maxX, point.x + radius);
|
|
1517
|
+
maxY = Math.max(maxY, point.y + radius);
|
|
1518
|
+
}
|
|
1519
|
+
const width = Math.max(1, maxX - minX);
|
|
1520
|
+
const height = Math.max(1, maxY - minY);
|
|
1521
|
+
const tolerance = Math.max(.01, node.width * 1e-6, node.height * 1e-6);
|
|
1522
|
+
if (Math.abs(minX) <= tolerance && Math.abs(minY) <= tolerance && Math.abs(width - node.width) <= tolerance && Math.abs(height - node.height) <= tolerance) return null;
|
|
1523
|
+
return {
|
|
1524
|
+
severity: "error",
|
|
1525
|
+
code: "INVALID_BOARD_GEOMETRY",
|
|
1526
|
+
message: `${path}.data.points must use frame-local coordinates and match the node frame`,
|
|
1527
|
+
path: `${path}.data.points`,
|
|
1528
|
+
expected: "frame-local points with bounds matching width and height",
|
|
1529
|
+
coordinateSpace: "frame-local"
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
function validateBoardNodeInput(node, path = "node") {
|
|
1533
|
+
const envelopeResult = nodeEnvelopeSchema.safeParse(node);
|
|
1534
|
+
if (!envelopeResult.success) return envelopeResult.error.issues.map((issue) => issueDiagnostic(issue, path));
|
|
1535
|
+
if (typeof node.type !== "string" || !BOARD_NATIVE_NODE_TYPES.includes(node.type)) return [{
|
|
1536
|
+
severity: "error",
|
|
1537
|
+
code: "INVALID_BOARD_NODE",
|
|
1538
|
+
message: `${path}.type is not supported`,
|
|
1539
|
+
path: `${path}.type`,
|
|
1540
|
+
expected: "BoardNativeNodeType",
|
|
1541
|
+
received: node.type,
|
|
1542
|
+
allowedValues: BOARD_NATIVE_NODE_TYPES
|
|
1543
|
+
}];
|
|
1544
|
+
const type = node.type;
|
|
1545
|
+
const dataResult = dataSchemas[type].safeParse(node.data ?? {});
|
|
1546
|
+
if (!dataResult.success) return dataResult.error.issues.map((issue) => issueDiagnostic(issue, `${path}.data`));
|
|
1547
|
+
const viewResult = viewSchemas[type].safeParse(node.view ?? {});
|
|
1548
|
+
if (!viewResult.success) return viewResult.error.issues.map((issue) => issueDiagnostic(issue, `${path}.view`));
|
|
1549
|
+
if (type === "image" || type === "video" || type === "audio" || type === "file") {
|
|
1550
|
+
if (node.refKind !== "space_file" || typeof node.refPath !== "string" || !node.refPath) return [{
|
|
1551
|
+
severity: "error",
|
|
1552
|
+
code: "INVALID_BOARD_NODE",
|
|
1553
|
+
message: `${path} requires a space file reference`,
|
|
1554
|
+
path: `${path}.refPath`,
|
|
1555
|
+
expected: "non-empty refPath with refKind space_file"
|
|
1556
|
+
}];
|
|
1557
|
+
}
|
|
1558
|
+
if (type === "draw") {
|
|
1559
|
+
const diagnostic = drawGeometryDiagnostic(node, dataResult.data, path);
|
|
1560
|
+
return diagnostic ? [diagnostic] : [];
|
|
1561
|
+
}
|
|
1562
|
+
if (type === "arrow") {
|
|
1563
|
+
const data = dataResult.data;
|
|
1564
|
+
const inside = (point) => point.x >= node.x && point.x <= node.x + node.width && point.y >= node.y && point.y <= node.y + node.height;
|
|
1565
|
+
if (!inside(data.start) || !inside(data.end)) return [{
|
|
1566
|
+
severity: "error",
|
|
1567
|
+
code: "INVALID_BOARD_GEOMETRY",
|
|
1568
|
+
message: `${path}.data endpoints must be covered by the node frame`,
|
|
1569
|
+
path: `${path}.data`,
|
|
1570
|
+
expected: "world-space endpoints inside the node frame",
|
|
1571
|
+
coordinateSpace: "world"
|
|
1572
|
+
}];
|
|
1573
|
+
}
|
|
1574
|
+
return [];
|
|
1575
|
+
}
|
|
1576
|
+
//#endregion
|
|
1577
|
+
//#region ../protocol/dist/realtime/board-awareness.js
|
|
1578
|
+
const idSchema = z.string().min(1).max(160);
|
|
1579
|
+
const finiteSchema = z.number().finite();
|
|
1580
|
+
const BoardAwarenessPointSchema = z.object({
|
|
1581
|
+
x: finiteSchema,
|
|
1582
|
+
y: finiteSchema
|
|
1583
|
+
});
|
|
1584
|
+
const BoardAwarenessDrawPointSchema = BoardAwarenessPointSchema.extend({ p: finiteSchema.min(0).max(1) });
|
|
1585
|
+
const BoardAwarenessFrameSchema = z.object({
|
|
1586
|
+
x: finiteSchema,
|
|
1587
|
+
y: finiteSchema,
|
|
1588
|
+
width: finiteSchema.positive(),
|
|
1589
|
+
height: finiteSchema.positive(),
|
|
1590
|
+
rotation: finiteSchema
|
|
1591
|
+
});
|
|
1592
|
+
const BoardAwarenessNodePreviewSchema = z.object({
|
|
1593
|
+
nodeId: idSchema,
|
|
1594
|
+
frame: BoardAwarenessFrameSchema,
|
|
1595
|
+
/** Live endpoints of a free arrow being dragged. */
|
|
1596
|
+
arrow: z.object({
|
|
1597
|
+
start: BoardAwarenessPointSchema,
|
|
1598
|
+
end: BoardAwarenessPointSchema,
|
|
1599
|
+
bend: finiteSchema
|
|
1600
|
+
}).optional()
|
|
1601
|
+
});
|
|
1602
|
+
const BoardAwarenessStateUpdateSchema = z.object({
|
|
1603
|
+
type: z.literal("state"),
|
|
1604
|
+
client: z.object({ formFactor: z.enum(["desktop", "mobile"]) }).optional(),
|
|
1605
|
+
cursor: BoardAwarenessPointSchema.extend({ pointerType: z.enum([
|
|
1606
|
+
"mouse",
|
|
1607
|
+
"pen",
|
|
1608
|
+
"touch"
|
|
1609
|
+
]) }).nullable(),
|
|
1610
|
+
tool: z.string().min(1).max(40),
|
|
1611
|
+
selection: z.object({
|
|
1612
|
+
ids: z.array(idSchema).max(64),
|
|
1613
|
+
count: z.number().int().nonnegative().max(5e4),
|
|
1614
|
+
bounds: BoardAwarenessFrameSchema.nullable()
|
|
1615
|
+
}),
|
|
1616
|
+
editingId: idSchema.nullable()
|
|
1617
|
+
});
|
|
1618
|
+
const BoardAwarenessGestureSchema = z.discriminatedUnion("kind", [
|
|
1619
|
+
z.object({
|
|
1620
|
+
kind: z.literal("draw"),
|
|
1621
|
+
id: idSchema,
|
|
1622
|
+
nodeId: idSchema,
|
|
1623
|
+
color: z.string().min(1).max(64),
|
|
1624
|
+
size: finiteSchema.positive().max(256),
|
|
1625
|
+
from: z.number().int().nonnegative().max(1e5),
|
|
1626
|
+
points: z.array(BoardAwarenessDrawPointSchema).min(1).max(64)
|
|
1627
|
+
}),
|
|
1628
|
+
z.object({
|
|
1629
|
+
kind: z.literal("arrow"),
|
|
1630
|
+
id: idSchema,
|
|
1631
|
+
nodeId: idSchema,
|
|
1632
|
+
start: BoardAwarenessPointSchema,
|
|
1633
|
+
current: BoardAwarenessPointSchema,
|
|
1634
|
+
color: z.string().min(1).max(64),
|
|
1635
|
+
size: finiteSchema.positive().max(256).default(BOARD_ARROW_STROKE_SIZE)
|
|
1636
|
+
}),
|
|
1637
|
+
z.object({
|
|
1638
|
+
kind: z.literal("box"),
|
|
1639
|
+
id: idSchema,
|
|
1640
|
+
nodeId: idSchema,
|
|
1641
|
+
shape: z.enum(["geo", "frame"]),
|
|
1642
|
+
start: BoardAwarenessPointSchema,
|
|
1643
|
+
current: BoardAwarenessPointSchema,
|
|
1644
|
+
color: z.string().min(1).max(64),
|
|
1645
|
+
geo: z.string().min(1).max(40)
|
|
1646
|
+
}),
|
|
1647
|
+
z.object({
|
|
1648
|
+
kind: z.literal("connection"),
|
|
1649
|
+
id: idSchema,
|
|
1650
|
+
sourceNodeId: idSchema,
|
|
1651
|
+
targetNodeId: idSchema.nullable(),
|
|
1652
|
+
current: BoardAwarenessPointSchema,
|
|
1653
|
+
color: z.string().min(1).max(64),
|
|
1654
|
+
size: finiteSchema.positive().max(256).default(BOARD_CONNECTION_STROKE_SIZE)
|
|
1655
|
+
}),
|
|
1656
|
+
z.object({
|
|
1657
|
+
kind: z.literal("transform"),
|
|
1658
|
+
id: idSchema,
|
|
1659
|
+
mode: z.enum([
|
|
1660
|
+
"translate",
|
|
1661
|
+
"resize",
|
|
1662
|
+
"rotate",
|
|
1663
|
+
"arrow",
|
|
1664
|
+
"connection"
|
|
1665
|
+
]),
|
|
1666
|
+
nodes: z.array(BoardAwarenessNodePreviewSchema).max(64),
|
|
1667
|
+
bounds: BoardAwarenessFrameSchema.nullable()
|
|
1668
|
+
})
|
|
1669
|
+
]);
|
|
1670
|
+
const BoardAwarenessUpdateSchema = z.discriminatedUnion("type", [
|
|
1671
|
+
BoardAwarenessStateUpdateSchema,
|
|
1672
|
+
z.object({
|
|
1673
|
+
type: z.literal("gesture"),
|
|
1674
|
+
gesture: BoardAwarenessGestureSchema
|
|
1675
|
+
}),
|
|
1676
|
+
z.object({
|
|
1677
|
+
type: z.literal("gesture.end"),
|
|
1678
|
+
gestureId: idSchema,
|
|
1679
|
+
resultingNodeIds: z.array(idSchema).max(64)
|
|
1680
|
+
}),
|
|
1681
|
+
z.object({
|
|
1682
|
+
type: z.literal("gesture.cancel"),
|
|
1683
|
+
gestureId: idSchema
|
|
1684
|
+
})
|
|
1685
|
+
]);
|
|
1686
|
+
const BoardAwarenessClientPayloadSchema = z.object({
|
|
1687
|
+
spaceId: z.string().uuid(),
|
|
1688
|
+
boardId: z.string().uuid(),
|
|
1689
|
+
seq: z.number().int().nonnegative(),
|
|
1690
|
+
update: BoardAwarenessUpdateSchema
|
|
1691
|
+
});
|
|
1692
|
+
//#endregion
|
|
1693
|
+
//#region ../protocol/dist/public-identifiers.js
|
|
1694
|
+
const USERNAME_PATTERN = /^(?!-)(?!.*--)[a-z0-9-]{1,39}(?<!-)$/;
|
|
1695
|
+
const SPACE_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9_-]{0,78}[a-z0-9])?$/;
|
|
1696
|
+
/**
|
|
1697
|
+
* Platform-owned path segments that must not be newly assigned to public
|
|
1698
|
+
* identities. Existing stored values remain readable through parse helpers.
|
|
1699
|
+
*/
|
|
1700
|
+
const RESERVED_PLATFORM_PATH_SEGMENTS = Object.freeze([
|
|
1701
|
+
"admin",
|
|
1702
|
+
"api",
|
|
1703
|
+
"assets",
|
|
1704
|
+
"auth",
|
|
1705
|
+
"callback",
|
|
1706
|
+
"changelog",
|
|
1707
|
+
"docs",
|
|
1708
|
+
"explore",
|
|
1709
|
+
"invite",
|
|
1710
|
+
"landing",
|
|
1711
|
+
"login",
|
|
1712
|
+
"logout",
|
|
1713
|
+
"new",
|
|
1714
|
+
"org",
|
|
1715
|
+
"pricing",
|
|
1716
|
+
"pwa",
|
|
1717
|
+
"referrals",
|
|
1718
|
+
"sessions",
|
|
1719
|
+
"settings",
|
|
1720
|
+
"spaces",
|
|
1721
|
+
"static",
|
|
1722
|
+
"teams",
|
|
1723
|
+
"trending",
|
|
1724
|
+
"u",
|
|
1725
|
+
"user",
|
|
1726
|
+
"users",
|
|
1727
|
+
"work-auth"
|
|
1728
|
+
]);
|
|
1729
|
+
new Set(RESERVED_PLATFORM_PATH_SEGMENTS);
|
|
1730
|
+
[...RESERVED_PLATFORM_PATH_SEGMENTS];
|
|
1731
|
+
function parseUsername(value) {
|
|
1732
|
+
if (value === null || value === void 0) return null;
|
|
1733
|
+
const normalized = value.trim().toLowerCase();
|
|
1734
|
+
return USERNAME_PATTERN.test(normalized) ? normalized : null;
|
|
1735
|
+
}
|
|
1736
|
+
function parseSpaceSlug(value) {
|
|
1737
|
+
if (value === null || value === void 0) return null;
|
|
1738
|
+
const normalized = value.trim();
|
|
1739
|
+
return SPACE_SLUG_PATTERN.test(normalized) ? normalized : null;
|
|
1740
|
+
}
|
|
1741
|
+
//#endregion
|
|
1742
|
+
//#region ../protocol/dist/ui-command.js
|
|
1743
|
+
/**
|
|
1744
|
+
* Lets an agent drive the Cohub frontend that originated the work. Routing comes
|
|
1745
|
+
* from request provenance, never a caller-supplied target, so a command only
|
|
1746
|
+
* reaches the actor's own instances.
|
|
1747
|
+
*/
|
|
1748
|
+
const UI_COMMAND_VERSION = 1;
|
|
1749
|
+
/** Persisted and broadcast, so every field is capped; MAX_BYTES bounds the whole. */
|
|
1750
|
+
const UI_COMMAND_PAYLOAD_MAX_BYTES = 32 * 1024;
|
|
1751
|
+
const UI_COMMAND_MAX_BYTES = 40 * 1024;
|
|
1752
|
+
const UI_COMMAND_LAUNCH_MAX_LENGTH = 2048;
|
|
1753
|
+
const UI_COMMAND_DEFAULT_TIMEOUT_MS = 600 * 1e3;
|
|
1754
|
+
const UI_COMMAND_MAX_TIMEOUT_MS = 720 * 60 * 1e3;
|
|
1755
|
+
const UI_COMMAND_SETTLEMENT_GRACE_SECONDS = 600;
|
|
1756
|
+
/** Keeps pending commands reportable for the full wait window plus settlement grace. */
|
|
1757
|
+
const UI_COMMAND_PENDING_TTL_SECONDS = 43800;
|
|
1758
|
+
const UI_COMMAND_TERMINAL_TTL_SECONDS = 1800;
|
|
1759
|
+
const UI_COMMAND_TERMINAL_STATUSES = [
|
|
1760
|
+
"applied",
|
|
1761
|
+
"no_active_client",
|
|
1762
|
+
"ui_host_unavailable",
|
|
1763
|
+
"rejected",
|
|
1764
|
+
"unsupported",
|
|
1765
|
+
"timeout"
|
|
1766
|
+
];
|
|
1767
|
+
const isTerminalUiCommandStatus = (status) => UI_COMMAND_TERMINAL_STATUSES.includes(status);
|
|
1768
|
+
const METHOD_RE = /^[A-Za-z][A-Za-z0-9_.:-]{0,63}$/;
|
|
1769
|
+
const isUiSurfaceMethod = (value) => typeof value === "string" && METHOD_RE.test(value);
|
|
1770
|
+
const UI_COMMAND_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
1771
|
+
const parseUiCommandId = (value) => {
|
|
1772
|
+
if (typeof value !== "string") return null;
|
|
1773
|
+
const trimmed = value.trim();
|
|
1774
|
+
return UI_COMMAND_ID_RE.test(trimmed) ? trimmed : null;
|
|
1775
|
+
};
|
|
1776
|
+
const isRecord$2 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1777
|
+
const asTrimmed = (value) => {
|
|
1778
|
+
if (typeof value !== "string") return null;
|
|
1779
|
+
const trimmed = value.trim();
|
|
1780
|
+
return trimmed ? trimmed : null;
|
|
1781
|
+
};
|
|
1782
|
+
const parseLaunch = (value) => {
|
|
1783
|
+
if (!isRecord$2(value)) return void 0;
|
|
1784
|
+
const search = asTrimmed(value.search);
|
|
1785
|
+
const hash = asTrimmed(value.hash);
|
|
1786
|
+
if (!search && !hash) return void 0;
|
|
1787
|
+
return {
|
|
1788
|
+
...search ? { search: search.startsWith("?") ? search : `?${search}` } : {},
|
|
1789
|
+
...hash ? { hash: hash.startsWith("#") ? hash : `#${hash}` } : {}
|
|
1790
|
+
};
|
|
1791
|
+
};
|
|
1792
|
+
const measureUiCommandPayload = (value) => {
|
|
1793
|
+
if (value === void 0) return 0;
|
|
1794
|
+
try {
|
|
1795
|
+
return new TextEncoder().encode(JSON.stringify(value) ?? "").length;
|
|
1796
|
+
} catch {
|
|
1797
|
+
return null;
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
const parseUiCommand = (input) => {
|
|
1801
|
+
if (!isRecord$2(input)) return {
|
|
1802
|
+
command: null,
|
|
1803
|
+
error: "command must be an object"
|
|
1804
|
+
};
|
|
1805
|
+
if (input.type !== "preview.show") return {
|
|
1806
|
+
command: null,
|
|
1807
|
+
error: "command.type must be one of: preview.show"
|
|
1808
|
+
};
|
|
1809
|
+
const preview = input.preview;
|
|
1810
|
+
if (!isRecord$2(preview)) return {
|
|
1811
|
+
command: null,
|
|
1812
|
+
error: "command.preview is required"
|
|
1813
|
+
};
|
|
1814
|
+
if (preview.kind !== "work") return {
|
|
1815
|
+
command: null,
|
|
1816
|
+
error: "command.preview.kind must be one of: work"
|
|
1817
|
+
};
|
|
1818
|
+
const workId = asTrimmed(preview.workId);
|
|
1819
|
+
if (!workId) return {
|
|
1820
|
+
command: null,
|
|
1821
|
+
error: "command.preview.workId is required"
|
|
1822
|
+
};
|
|
1823
|
+
if (!isUuid(workId)) return {
|
|
1824
|
+
command: null,
|
|
1825
|
+
error: "command.preview.workId must be a Work id"
|
|
1826
|
+
};
|
|
1827
|
+
const label = asTrimmed(preview.label);
|
|
1828
|
+
if (label && label.length > 200) return {
|
|
1829
|
+
command: null,
|
|
1830
|
+
error: `command.preview.label exceeds 200 characters`
|
|
1831
|
+
};
|
|
1832
|
+
const launch = parseLaunch(preview.launch);
|
|
1833
|
+
if (launch) {
|
|
1834
|
+
for (const [field, value] of [["search", launch.search], ["hash", launch.hash]]) if (value && value.length > 2048) return {
|
|
1835
|
+
command: null,
|
|
1836
|
+
error: `command.preview.launch.${field} exceeds ${UI_COMMAND_LAUNCH_MAX_LENGTH} characters`
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
let request;
|
|
1840
|
+
if (input.request !== void 0 && input.request !== null) {
|
|
1841
|
+
if (!isRecord$2(input.request)) return {
|
|
1842
|
+
command: null,
|
|
1843
|
+
error: "command.request must be an object"
|
|
1844
|
+
};
|
|
1845
|
+
const method = asTrimmed(input.request.method);
|
|
1846
|
+
if (!method) return {
|
|
1847
|
+
command: null,
|
|
1848
|
+
error: "command.request.method is required"
|
|
1849
|
+
};
|
|
1850
|
+
if (!isUiSurfaceMethod(method)) return {
|
|
1851
|
+
command: null,
|
|
1852
|
+
error: "command.request.method has an unsupported format"
|
|
1853
|
+
};
|
|
1854
|
+
const size = measureUiCommandPayload(input.request.input);
|
|
1855
|
+
if (size === null) return {
|
|
1856
|
+
command: null,
|
|
1857
|
+
error: "command.request.input must be JSON-serializable"
|
|
1858
|
+
};
|
|
1859
|
+
if (size > 32768) return {
|
|
1860
|
+
command: null,
|
|
1861
|
+
error: `command.request.input exceeds ${UI_COMMAND_PAYLOAD_MAX_BYTES} bytes`
|
|
1862
|
+
};
|
|
1863
|
+
request = {
|
|
1864
|
+
method,
|
|
1865
|
+
...input.request.input === void 0 ? {} : { input: input.request.input }
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
const command = {
|
|
1869
|
+
type: "preview.show",
|
|
1870
|
+
preview: {
|
|
1871
|
+
kind: "work",
|
|
1872
|
+
workId,
|
|
1873
|
+
...label ? { label } : {},
|
|
1874
|
+
...launch ? { launch } : {}
|
|
1875
|
+
},
|
|
1876
|
+
...request ? { request } : {}
|
|
1877
|
+
};
|
|
1878
|
+
const totalSize = measureUiCommandPayload(command);
|
|
1879
|
+
if (totalSize === null) return {
|
|
1880
|
+
command: null,
|
|
1881
|
+
error: "command must be JSON-serializable"
|
|
1882
|
+
};
|
|
1883
|
+
if (totalSize > 40960) return {
|
|
1884
|
+
command: null,
|
|
1885
|
+
error: `command exceeds ${UI_COMMAND_MAX_BYTES} bytes`
|
|
1886
|
+
};
|
|
1887
|
+
return {
|
|
1888
|
+
command,
|
|
1889
|
+
error: null
|
|
1890
|
+
};
|
|
1891
|
+
};
|
|
1892
|
+
//#endregion
|
|
1893
|
+
//#region ../protocol/dist/work-surface.js
|
|
1894
|
+
const WORK_SURFACE_PROTOCOL = "cohub.surface";
|
|
1895
|
+
const WORK_SURFACE_READY_TIMEOUT_MS = 1e4;
|
|
1896
|
+
const WORK_SURFACE_REQUEST_TIMEOUT_MS = 15e3;
|
|
1897
|
+
const WORK_COMPOSER_CHIP_KEY_MAX_LENGTH = 80;
|
|
1898
|
+
const WORK_COMPOSER_CHIP_LABEL_MAX_LENGTH = 120;
|
|
1899
|
+
const WORK_COMPOSER_CHIP_CONTENT_MAX_BYTES = 32 * 1024;
|
|
1900
|
+
const isRecord$1 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1901
|
+
const isSurfaceEnvelope = (value) => isRecord$1(value) && value.protocol === "cohub.surface" && value.version === 1;
|
|
1902
|
+
const parseWorkSurfaceReady = (value) => {
|
|
1903
|
+
if (!isSurfaceEnvelope(value) || value.type !== "ready") return null;
|
|
1904
|
+
const methods = Array.isArray(value.methods) ? value.methods.filter((method) => typeof method === "string" && Boolean(method)) : [];
|
|
1905
|
+
return {
|
|
1906
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1907
|
+
version: 1,
|
|
1908
|
+
type: "ready",
|
|
1909
|
+
methods
|
|
1910
|
+
};
|
|
1911
|
+
};
|
|
1912
|
+
const parseWorkSurfaceResponse = (value) => {
|
|
1913
|
+
if (!isSurfaceEnvelope(value) || value.type !== "response") return null;
|
|
1914
|
+
if (typeof value.requestId !== "string" || !value.requestId) return null;
|
|
1915
|
+
const error = isRecord$1(value.error) ? {
|
|
1916
|
+
code: typeof value.error.code === "string" && value.error.code ? value.error.code : "surface_error",
|
|
1917
|
+
message: typeof value.error.message === "string" ? value.error.message : "Work surface call failed"
|
|
1918
|
+
} : void 0;
|
|
1919
|
+
return {
|
|
1920
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1921
|
+
version: 1,
|
|
1922
|
+
type: "response",
|
|
1923
|
+
requestId: value.requestId,
|
|
1924
|
+
ok: value.ok === true,
|
|
1925
|
+
...error ? { error } : {}
|
|
1926
|
+
};
|
|
1927
|
+
};
|
|
1928
|
+
const parseComposerChipKey = (value) => {
|
|
1929
|
+
if (typeof value !== "string") return null;
|
|
1930
|
+
const key = value.trim();
|
|
1931
|
+
if (!key || key.length > 80) return null;
|
|
1932
|
+
return key;
|
|
1933
|
+
};
|
|
1934
|
+
const parseWorkComposerChipSet = (value) => {
|
|
1935
|
+
if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.set" || !isRecord$1(value.chip)) return null;
|
|
1936
|
+
const key = parseComposerChipKey(value.chip.key);
|
|
1937
|
+
if (!key || typeof value.chip.label !== "string" || typeof value.chip.content !== "string") return null;
|
|
1938
|
+
const label = value.chip.label.trim();
|
|
1939
|
+
if (!label || label.length > 120) return null;
|
|
1940
|
+
if (!value.chip.content.trim()) return null;
|
|
1941
|
+
if (new TextEncoder().encode(value.chip.content).length > 32768) return null;
|
|
1942
|
+
return {
|
|
1943
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1944
|
+
version: 1,
|
|
1945
|
+
type: "composer.chip.set",
|
|
1946
|
+
chip: {
|
|
1947
|
+
key,
|
|
1948
|
+
label,
|
|
1949
|
+
content: value.chip.content
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
};
|
|
1953
|
+
const parseWorkComposerChipClear = (value) => {
|
|
1954
|
+
if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.clear") return null;
|
|
1955
|
+
const key = parseComposerChipKey(value.key);
|
|
1956
|
+
return key ? {
|
|
1957
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1958
|
+
version: 1,
|
|
1959
|
+
type: "composer.chip.clear",
|
|
1960
|
+
key
|
|
1961
|
+
} : null;
|
|
1962
|
+
};
|
|
1963
|
+
const parseWorkSurfaceRequest = (value) => {
|
|
1964
|
+
if (!isSurfaceEnvelope(value) || value.type !== "request") return null;
|
|
1965
|
+
if (typeof value.requestId !== "string" || !value.requestId) return null;
|
|
1966
|
+
if (typeof value.method !== "string" || !value.method) return null;
|
|
1967
|
+
const commandId = parseUiCommandId(value.commandId);
|
|
1968
|
+
if (!commandId) return null;
|
|
1969
|
+
return {
|
|
1970
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1971
|
+
version: 1,
|
|
1972
|
+
type: "request",
|
|
1973
|
+
requestId: value.requestId,
|
|
1974
|
+
method: value.method,
|
|
1975
|
+
...value.input === void 0 ? {} : { input: value.input },
|
|
1976
|
+
commandId
|
|
1977
|
+
};
|
|
1978
|
+
};
|
|
1979
|
+
const buildWorkSurfaceReady = (methods) => ({
|
|
1980
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1981
|
+
version: 1,
|
|
1982
|
+
type: "ready",
|
|
1983
|
+
methods: [...methods]
|
|
1984
|
+
});
|
|
1985
|
+
const buildWorkSurfaceRequest = (input) => ({
|
|
1986
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1987
|
+
version: 1,
|
|
1988
|
+
type: "request",
|
|
1989
|
+
...input
|
|
1990
|
+
});
|
|
1991
|
+
const buildWorkSurfaceResponse = (input) => ({
|
|
1992
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1993
|
+
version: 1,
|
|
1994
|
+
type: "response",
|
|
1995
|
+
...input
|
|
1996
|
+
});
|
|
1997
|
+
const buildWorkComposerChipSet = (chip) => ({
|
|
1998
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1999
|
+
version: 1,
|
|
2000
|
+
type: "composer.chip.set",
|
|
2001
|
+
chip
|
|
2002
|
+
});
|
|
2003
|
+
const buildWorkComposerChipClear = (key) => ({
|
|
2004
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
2005
|
+
version: 1,
|
|
2006
|
+
type: "composer.chip.clear",
|
|
2007
|
+
key
|
|
2008
|
+
});
|
|
2009
|
+
//#endregion
|
|
2010
|
+
//#region src/board/core/draw-geometry.ts
|
|
2011
|
+
/** Radius of a sample in world units given the stroke size and pressure. */
|
|
2012
|
+
function sampleRadius(size, pressure) {
|
|
2013
|
+
const clamped = Math.min(1, Math.max(0, pressure));
|
|
2014
|
+
return Math.max(.5, size / 2 * (.5 + clamped));
|
|
2015
|
+
}
|
|
2016
|
+
/** Axis-aligned bounds of a stroke in its local space, padded by stroke width. */
|
|
2017
|
+
function computeDrawBounds(points, size) {
|
|
2018
|
+
if (points.length === 0) return {
|
|
2019
|
+
x: 0,
|
|
2020
|
+
y: 0,
|
|
2021
|
+
width: 1,
|
|
2022
|
+
height: 1
|
|
2023
|
+
};
|
|
2024
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
2025
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
2026
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
2027
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
2028
|
+
for (const point of points) {
|
|
2029
|
+
const r = sampleRadius(size, point.p);
|
|
2030
|
+
minX = Math.min(minX, point.x - r);
|
|
2031
|
+
minY = Math.min(minY, point.y - r);
|
|
2032
|
+
maxX = Math.max(maxX, point.x + r);
|
|
2033
|
+
maxY = Math.max(maxY, point.y + r);
|
|
2034
|
+
}
|
|
2035
|
+
return {
|
|
2036
|
+
x: minX,
|
|
2037
|
+
y: minY,
|
|
2038
|
+
width: Math.max(1, maxX - minX),
|
|
2039
|
+
height: Math.max(1, maxY - minY)
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
//#endregion
|
|
2043
|
+
//#region src/board/nodes.ts
|
|
2044
|
+
var BoardInputError = class extends Error {
|
|
2045
|
+
code = "INVALID_BOARD_NODE";
|
|
2046
|
+
diagnostics;
|
|
2047
|
+
body;
|
|
2048
|
+
constructor(diagnostics) {
|
|
2049
|
+
const message = diagnostics[0]?.message ?? "Invalid Board node";
|
|
2050
|
+
super(message);
|
|
2051
|
+
this.name = "BoardInputError";
|
|
2052
|
+
this.diagnostics = diagnostics;
|
|
2053
|
+
this.body = {
|
|
2054
|
+
code: this.code,
|
|
2055
|
+
message,
|
|
2056
|
+
diagnostics
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
function baseNode(spec, frame) {
|
|
2061
|
+
return {
|
|
2062
|
+
nodeId: spec.id,
|
|
2063
|
+
type: spec.type,
|
|
2064
|
+
parentId: spec.parentId ?? null,
|
|
2065
|
+
orderKey: spec.orderKey ?? null,
|
|
2066
|
+
x: frame.x,
|
|
2067
|
+
y: frame.y,
|
|
2068
|
+
width: frame.width,
|
|
2069
|
+
height: frame.height,
|
|
2070
|
+
rotation: frame.rotation ?? 0,
|
|
2071
|
+
refKind: null,
|
|
2072
|
+
refPath: null,
|
|
2073
|
+
refUrl: null,
|
|
2074
|
+
view: {},
|
|
2075
|
+
style: spec.style ?? {},
|
|
2076
|
+
data: {}
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
function arrowFrame(start, end) {
|
|
2080
|
+
const padding = 16;
|
|
2081
|
+
return {
|
|
2082
|
+
x: Math.min(start.x, end.x) - padding,
|
|
2083
|
+
y: Math.min(start.y, end.y) - padding,
|
|
2084
|
+
width: Math.max(1, Math.abs(end.x - start.x) + padding * 2),
|
|
2085
|
+
height: Math.max(1, Math.abs(end.y - start.y) + padding * 2)
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
/**
|
|
2089
|
+
* Create one validated wire node from semantic Board input.
|
|
2090
|
+
*
|
|
2091
|
+
* Box nodes take an explicit world-space frame. Draw samples and arrow endpoints
|
|
2092
|
+
* take world coordinates; the builder derives their frame and local storage form.
|
|
2093
|
+
*/
|
|
2094
|
+
function createBoardNode(spec) {
|
|
2095
|
+
let node;
|
|
2096
|
+
if (spec.type === "draw") {
|
|
2097
|
+
const size = spec.size ?? 4;
|
|
2098
|
+
const worldPoints = spec.points.map((point) => ({
|
|
2099
|
+
x: point.x,
|
|
2100
|
+
y: point.y,
|
|
2101
|
+
p: point.p ?? .5
|
|
2102
|
+
}));
|
|
2103
|
+
const bounds = computeDrawBounds(worldPoints, size);
|
|
2104
|
+
node = baseNode(spec, bounds);
|
|
2105
|
+
node.data = {
|
|
2106
|
+
points: worldPoints.map((point) => ({
|
|
2107
|
+
x: point.x - bounds.x,
|
|
2108
|
+
y: point.y - bounds.y,
|
|
2109
|
+
p: point.p
|
|
2110
|
+
})),
|
|
2111
|
+
color: spec.color ?? "brand",
|
|
2112
|
+
size
|
|
2113
|
+
};
|
|
2114
|
+
} else if (spec.type === "arrow") {
|
|
2115
|
+
node = baseNode(spec, arrowFrame(spec.start, spec.end));
|
|
2116
|
+
node.data = {
|
|
2117
|
+
start: spec.start,
|
|
2118
|
+
end: spec.end,
|
|
2119
|
+
bend: spec.bend ?? 0,
|
|
2120
|
+
color: spec.color ?? "brand",
|
|
2121
|
+
size: spec.size ?? 2.5,
|
|
2122
|
+
arrowStart: spec.arrowStart ?? false,
|
|
2123
|
+
arrowEnd: spec.arrowEnd ?? true,
|
|
2124
|
+
label: spec.label ?? ""
|
|
2125
|
+
};
|
|
2126
|
+
} else {
|
|
2127
|
+
node = baseNode(spec, spec.frame);
|
|
2128
|
+
switch (spec.type) {
|
|
2129
|
+
case "text":
|
|
2130
|
+
node.data = {
|
|
2131
|
+
text: spec.text ?? "",
|
|
2132
|
+
color: spec.color ?? "neutral",
|
|
2133
|
+
fontSize: spec.fontSize ?? 24
|
|
2134
|
+
};
|
|
2135
|
+
break;
|
|
2136
|
+
case "geo":
|
|
2137
|
+
node.data = {
|
|
2138
|
+
geo: spec.geo ?? "rectangle",
|
|
2139
|
+
text: spec.text ?? "",
|
|
2140
|
+
color: spec.color ?? "brand",
|
|
2141
|
+
fillOpacity: spec.fillOpacity ?? 0
|
|
2142
|
+
};
|
|
2143
|
+
break;
|
|
2144
|
+
case "frame":
|
|
2145
|
+
node.data = {
|
|
2146
|
+
label: spec.label ?? "Frame",
|
|
2147
|
+
color: spec.color ?? "neutral"
|
|
2148
|
+
};
|
|
2149
|
+
break;
|
|
2150
|
+
case "image":
|
|
2151
|
+
node.refKind = "space_file";
|
|
2152
|
+
node.refPath = spec.path;
|
|
2153
|
+
node.view = spec.snapshot ?? {};
|
|
2154
|
+
node.data = spec.crop ? { crop: spec.crop } : {};
|
|
2155
|
+
break;
|
|
2156
|
+
case "video":
|
|
2157
|
+
case "audio":
|
|
2158
|
+
node.refKind = "space_file";
|
|
2159
|
+
node.refPath = spec.path;
|
|
2160
|
+
node.view = spec.snapshot ?? {};
|
|
2161
|
+
break;
|
|
2162
|
+
case "file":
|
|
2163
|
+
node.refKind = "space_file";
|
|
2164
|
+
node.refPath = spec.path;
|
|
2165
|
+
node.view = spec.snapshot ?? {};
|
|
2166
|
+
break;
|
|
2167
|
+
case "task":
|
|
2168
|
+
node.view = spec.snapshot;
|
|
2169
|
+
node.data = { taskRunId: spec.taskRunId };
|
|
2170
|
+
break;
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
assertBoardNodes([node]);
|
|
2174
|
+
return node;
|
|
2175
|
+
}
|
|
2176
|
+
function validateBoardNodes(nodes, path = "nodes") {
|
|
2177
|
+
return nodes.flatMap((node, index) => validateBoardNodeInput(node, `${path}.${index}`));
|
|
2178
|
+
}
|
|
2179
|
+
function assertBoardNodes(nodes, path = "nodes") {
|
|
2180
|
+
const diagnostics = validateBoardNodes(nodes, path);
|
|
2181
|
+
if (diagnostics.length > 0) throw new BoardInputError(diagnostics);
|
|
2182
|
+
}
|
|
2183
|
+
function assertBoardTransactionNodeCreates(operations) {
|
|
2184
|
+
const diagnostics = operations.flatMap((operation, index) => {
|
|
2185
|
+
if (operation.type !== "node.create") return [];
|
|
2186
|
+
const payload = operation.payload;
|
|
2187
|
+
return payload.node ? validateBoardNodeInput(payload.node, `operations.${index}.payload.node`) : [];
|
|
2188
|
+
});
|
|
2189
|
+
if (diagnostics.length > 0) throw new BoardInputError(diagnostics);
|
|
2190
|
+
}
|
|
2191
|
+
//#endregion
|
|
644
2192
|
//#region src/session-patch-reducer.ts
|
|
645
2193
|
const blockSubPathPattern = /^\/message\/content\/blocks\/(\d+)\/(.+)$/;
|
|
646
2194
|
const blockPathPattern = /^\/message\/content\/blocks\/(\d+)$/;
|
|
@@ -1041,8 +2589,8 @@ const createSessionPatchReducer = () => new SessionPatchReducer();
|
|
|
1041
2589
|
//#region src/session-generation-stream.ts
|
|
1042
2590
|
const SNAPSHOT_RECOVERY_TIMEOUT_MS = 2500;
|
|
1043
2591
|
const SNAPSHOT_RECOVERY_MAX_BUFFERED_EVENTS = 256;
|
|
1044
|
-
const isRecord
|
|
1045
|
-
const isContentBlockArray = (value) => Array.isArray(value) && value.every((item) => isRecord
|
|
2592
|
+
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2593
|
+
const isContentBlockArray = (value) => Array.isArray(value) && value.every((item) => isRecord(item) && typeof item.type === "string");
|
|
1046
2594
|
const getMessageKind = (message) => {
|
|
1047
2595
|
const kind = message.meta?.messageKind;
|
|
1048
2596
|
return typeof kind === "string" ? kind : null;
|
|
@@ -1082,7 +2630,7 @@ function parseAssistantMessageCommit(message) {
|
|
|
1082
2630
|
}
|
|
1083
2631
|
function messageRecordToIntermediate(message) {
|
|
1084
2632
|
if (!isContentBlockArray(message.content) || message.content.length === 0) return null;
|
|
1085
|
-
const meta = isRecord
|
|
2633
|
+
const meta = isRecord(message.meta) ? message.meta : {};
|
|
1086
2634
|
return {
|
|
1087
2635
|
id: message.id,
|
|
1088
2636
|
sessionId: message.sessionId,
|
|
@@ -1200,9 +2748,9 @@ function mergeMessagesSharingToolUseId(messages) {
|
|
|
1200
2748
|
return merged;
|
|
1201
2749
|
}
|
|
1202
2750
|
function getCompactionPlacement(message) {
|
|
1203
|
-
const meta = isRecord
|
|
1204
|
-
const compaction = isRecord
|
|
1205
|
-
const placement = isRecord
|
|
2751
|
+
const meta = isRecord(message.meta) ? message.meta : null;
|
|
2752
|
+
const compaction = isRecord(meta?.compaction) ? meta.compaction : null;
|
|
2753
|
+
const placement = isRecord(compaction?.placement) ? compaction.placement : null;
|
|
1206
2754
|
return meta?.messageKind === "compacted" ? {
|
|
1207
2755
|
beforeMessageId: typeof placement?.beforeMessageId === "string" ? placement.beforeMessageId : null,
|
|
1208
2756
|
sequence: typeof message.sequence === "number" ? message.sequence : null
|
|
@@ -1476,7 +3024,7 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
|
|
|
1476
3024
|
}
|
|
1477
3025
|
handlePersisted(event, handlers) {
|
|
1478
3026
|
const message = event.payload.message;
|
|
1479
|
-
if (!isRecord
|
|
3027
|
+
if (!isRecord(message)) return;
|
|
1480
3028
|
const commit = parseAssistantMessageCommit(message);
|
|
1481
3029
|
if (commit.kind === "intermediate") {
|
|
1482
3030
|
const intermediate = messageRecordToIntermediate(commit.message);
|
|
@@ -1511,7 +3059,7 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
|
|
|
1511
3059
|
}
|
|
1512
3060
|
handleFinalized(event, handlers) {
|
|
1513
3061
|
const turn = event.payload.turn;
|
|
1514
|
-
if (!isRecord
|
|
3062
|
+
if (!isRecord(turn)) return;
|
|
1515
3063
|
const typedTurn = turn;
|
|
1516
3064
|
if (typedTurn.status === "interrupted") this.reducer.interrupt({
|
|
1517
3065
|
spaceId: this.spaceId,
|
|
@@ -1543,7 +3091,7 @@ var SessionGenerationStreamClient = class SessionGenerationStreamClient {
|
|
|
1543
3091
|
return;
|
|
1544
3092
|
case "session.turn.updated": {
|
|
1545
3093
|
const turn = event.payload.turn;
|
|
1546
|
-
if (!isRecord
|
|
3094
|
+
if (!isRecord(turn)) return;
|
|
1547
3095
|
this.emit(handlers, {
|
|
1548
3096
|
type: "turn_updated",
|
|
1549
3097
|
turn,
|
|
@@ -1756,6 +3304,80 @@ var SpacesApi = class {
|
|
|
1756
3304
|
});
|
|
1757
3305
|
}
|
|
1758
3306
|
};
|
|
3307
|
+
function inlineFileDataUrl(file) {
|
|
3308
|
+
const mimeType = file.mimeType ?? "application/octet-stream";
|
|
3309
|
+
return file.encoding === "base64" ? `data:${mimeType};base64,${file.content}` : `data:${mimeType};charset=utf-8,${encodeURIComponent(file.content)}`;
|
|
3310
|
+
}
|
|
3311
|
+
function spaceFileAbortReason(signal) {
|
|
3312
|
+
return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
3313
|
+
}
|
|
3314
|
+
function waitForSpaceFileRetry(ms, signal) {
|
|
3315
|
+
if (signal?.aborted) return Promise.reject(spaceFileAbortReason(signal));
|
|
3316
|
+
return new Promise((resolve, reject) => {
|
|
3317
|
+
const timeout = setTimeout(finish, ms);
|
|
3318
|
+
const onAbort = () => {
|
|
3319
|
+
if (signal) finish(spaceFileAbortReason(signal));
|
|
3320
|
+
};
|
|
3321
|
+
function finish(error) {
|
|
3322
|
+
clearTimeout(timeout);
|
|
3323
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3324
|
+
if (error !== void 0) reject(error);
|
|
3325
|
+
else resolve();
|
|
3326
|
+
}
|
|
3327
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
3328
|
+
});
|
|
3329
|
+
}
|
|
3330
|
+
function createSpaceFileDeadlineSignal(signal, timeoutMs) {
|
|
3331
|
+
const controller = new AbortController();
|
|
3332
|
+
let timedOut = false;
|
|
3333
|
+
const onAbort = () => {
|
|
3334
|
+
if (signal) controller.abort(spaceFileAbortReason(signal));
|
|
3335
|
+
};
|
|
3336
|
+
if (signal?.aborted) onAbort();
|
|
3337
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
3338
|
+
const timeout = setTimeout(() => {
|
|
3339
|
+
if (controller.signal.aborted) return;
|
|
3340
|
+
timedOut = true;
|
|
3341
|
+
controller.abort(new DOMException("Space file URL resolution timed out", "TimeoutError"));
|
|
3342
|
+
}, timeoutMs);
|
|
3343
|
+
return {
|
|
3344
|
+
signal: controller.signal,
|
|
3345
|
+
timedOut: () => timedOut,
|
|
3346
|
+
dispose: () => {
|
|
3347
|
+
clearTimeout(timeout);
|
|
3348
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3349
|
+
}
|
|
3350
|
+
};
|
|
3351
|
+
}
|
|
3352
|
+
var SpacePublicFilesApi = class {
|
|
3353
|
+
transport;
|
|
3354
|
+
spaceId;
|
|
3355
|
+
constructor(transport, spaceId) {
|
|
3356
|
+
this.transport = transport;
|
|
3357
|
+
this.spaceId = spaceId;
|
|
3358
|
+
}
|
|
3359
|
+
createUpload(input, options = {}) {
|
|
3360
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/public/uploads`, {
|
|
3361
|
+
method: "POST",
|
|
3362
|
+
headers: { "Content-Type": "application/json" },
|
|
3363
|
+
body: JSON.stringify(input),
|
|
3364
|
+
signal: options.signal
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
list(path = "", options = {}) {
|
|
3368
|
+
const params = new URLSearchParams();
|
|
3369
|
+
if (path) params.set("path", path);
|
|
3370
|
+
if (options.recursive) params.set("recursive", "true");
|
|
3371
|
+
if (options.limit != null) params.set("limit", String(options.limit));
|
|
3372
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
3373
|
+
const query = params.toString();
|
|
3374
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/public${query ? `?${query}` : ""}`, { fetch: options.fetch });
|
|
3375
|
+
}
|
|
3376
|
+
url(path, customFetch) {
|
|
3377
|
+
const params = new URLSearchParams({ path });
|
|
3378
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/public/url?${params.toString()}`, { fetch: customFetch });
|
|
3379
|
+
}
|
|
3380
|
+
};
|
|
1759
3381
|
var SpaceFilesApi = class {
|
|
1760
3382
|
transport;
|
|
1761
3383
|
spaceId;
|
|
@@ -1769,9 +3391,39 @@ var SpaceFilesApi = class {
|
|
|
1769
3391
|
const query = params.toString();
|
|
1770
3392
|
return this.transport.request(`/api/spaces/${this.spaceId}/fs/tree${query ? `?${query}` : ""}`, { fetch: customFetch });
|
|
1771
3393
|
}
|
|
1772
|
-
read(path, customFetch) {
|
|
3394
|
+
read(path, customFetch, signal) {
|
|
1773
3395
|
const params = new URLSearchParams({ path });
|
|
1774
|
-
return this.transport.request(`/api/spaces/${this.spaceId}/fs/file?${params.toString()}`, {
|
|
3396
|
+
return this.transport.request(`/api/spaces/${this.spaceId}/fs/file?${params.toString()}`, {
|
|
3397
|
+
fetch: customFetch,
|
|
3398
|
+
signal
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3401
|
+
/** Resolve a browser-ready file URL, waiting for CDN delivery when necessary. */
|
|
3402
|
+
async resolveUrl(path, options = {}) {
|
|
3403
|
+
const purpose = options.purpose ?? "preview";
|
|
3404
|
+
const requestedTimeoutMs = options.timeoutMs ?? 15e3;
|
|
3405
|
+
const timeoutMs = Number.isFinite(requestedTimeoutMs) ? Math.max(0, requestedTimeoutMs) : 15e3;
|
|
3406
|
+
const deadlineAt = Date.now() + timeoutMs;
|
|
3407
|
+
const deadline = createSpaceFileDeadlineSignal(options.signal, timeoutMs);
|
|
3408
|
+
try {
|
|
3409
|
+
while (true) {
|
|
3410
|
+
const file = await this.read(path, options.fetch, deadline.signal);
|
|
3411
|
+
if (deadline.timedOut()) return null;
|
|
3412
|
+
if ("content" in file) {
|
|
3413
|
+
if (file.delivery === "url" && file.url) return file.url;
|
|
3414
|
+
return purpose === "preview" ? inlineFileDataUrl(file) : null;
|
|
3415
|
+
}
|
|
3416
|
+
const remainingMs = deadlineAt - Date.now();
|
|
3417
|
+
if (remainingMs <= 0) return null;
|
|
3418
|
+
const retryAfterMs = Math.max(250, Math.min(file.retryAfterMs, 2e3));
|
|
3419
|
+
await waitForSpaceFileRetry(Math.min(retryAfterMs, remainingMs), deadline.signal);
|
|
3420
|
+
}
|
|
3421
|
+
} catch (error) {
|
|
3422
|
+
if (deadline.timedOut()) return null;
|
|
3423
|
+
throw error;
|
|
3424
|
+
} finally {
|
|
3425
|
+
deadline.dispose();
|
|
3426
|
+
}
|
|
1775
3427
|
}
|
|
1776
3428
|
/** Pending workspace changes vs the space head checkpoint. */
|
|
1777
3429
|
diff(customFetch) {
|
|
@@ -2644,7 +4296,9 @@ var BoardClient = class {
|
|
|
2644
4296
|
targetNodeId: input.targetNodeId,
|
|
2645
4297
|
...input.relation === void 0 ? {} : { relation: input.relation },
|
|
2646
4298
|
...input.direction === void 0 ? {} : { direction: input.direction },
|
|
2647
|
-
...input.label === void 0 ? {} : { label: input.label }
|
|
4299
|
+
...input.label === void 0 ? {} : { label: input.label },
|
|
4300
|
+
...input.sourcePortId === void 0 ? {} : { sourcePortId: input.sourcePortId },
|
|
4301
|
+
...input.targetPortId === void 0 ? {} : { targetPortId: input.targetPortId }
|
|
2648
4302
|
});
|
|
2649
4303
|
return this.apply({
|
|
2650
4304
|
txId: input.txId ?? randomBoardId(),
|
|
@@ -2725,6 +4379,7 @@ var SpaceBoardsApi = class {
|
|
|
2725
4379
|
return new BoardClient(this.spaceId, boardId, this.transport, this.websocketClient);
|
|
2726
4380
|
}
|
|
2727
4381
|
create(input) {
|
|
4382
|
+
assertBoardNodes(input.nodes ?? []);
|
|
2728
4383
|
return this.transport.request(`/api/spaces/${this.spaceId}/boards`, {
|
|
2729
4384
|
method: "POST",
|
|
2730
4385
|
headers: { "Content-Type": "application/json" },
|
|
@@ -2749,6 +4404,7 @@ var SpaceBoardsApi = class {
|
|
|
2749
4404
|
});
|
|
2750
4405
|
}
|
|
2751
4406
|
async apply(transaction) {
|
|
4407
|
+
assertBoardTransactionNodeCreates(transaction.operations);
|
|
2752
4408
|
try {
|
|
2753
4409
|
return await this.transport.request(`/api/spaces/${this.spaceId}/boards/${transaction.boardId}/transactions`, {
|
|
2754
4410
|
method: "POST",
|
|
@@ -2904,6 +4560,7 @@ var SpaceClient = class {
|
|
|
2904
4560
|
transport;
|
|
2905
4561
|
websocketClient;
|
|
2906
4562
|
files;
|
|
4563
|
+
publicFiles;
|
|
2907
4564
|
sessions;
|
|
2908
4565
|
turns;
|
|
2909
4566
|
members;
|
|
@@ -2924,6 +4581,7 @@ var SpaceClient = class {
|
|
|
2924
4581
|
this.transport = transport;
|
|
2925
4582
|
this.websocketClient = websocketClient;
|
|
2926
4583
|
this.files = new SpaceFilesApi(transport, id);
|
|
4584
|
+
this.publicFiles = new SpacePublicFilesApi(transport, id);
|
|
2927
4585
|
this.sessions = new SpaceSessionsApi(transport, id, websocketClient);
|
|
2928
4586
|
this.turns = new SpaceTurnsApi(transport, id);
|
|
2929
4587
|
this.members = new SpaceMembersApi(transport, id);
|
|
@@ -3160,158 +4818,6 @@ var TasksApi = class {
|
|
|
3160
4818
|
}
|
|
3161
4819
|
};
|
|
3162
4820
|
//#endregion
|
|
3163
|
-
//#region ../protocol/dist/ui-command.js
|
|
3164
|
-
/**
|
|
3165
|
-
* Lets an agent drive the Cohub frontend that originated the work. Routing comes
|
|
3166
|
-
* from request provenance, never a caller-supplied target, so a command only
|
|
3167
|
-
* reaches the actor's own instances.
|
|
3168
|
-
*/
|
|
3169
|
-
const UI_COMMAND_VERSION = 1;
|
|
3170
|
-
/** Persisted and broadcast, so every field is capped; MAX_BYTES bounds the whole. */
|
|
3171
|
-
const UI_COMMAND_PAYLOAD_MAX_BYTES = 32 * 1024;
|
|
3172
|
-
const UI_COMMAND_MAX_BYTES = 40 * 1024;
|
|
3173
|
-
const UI_COMMAND_LAUNCH_MAX_LENGTH = 2048;
|
|
3174
|
-
const UI_COMMAND_DEFAULT_TIMEOUT_MS = 600 * 1e3;
|
|
3175
|
-
const UI_COMMAND_MAX_TIMEOUT_MS = 720 * 60 * 1e3;
|
|
3176
|
-
const UI_COMMAND_SETTLEMENT_GRACE_SECONDS = 600;
|
|
3177
|
-
/** Keeps pending commands reportable for the full wait window plus settlement grace. */
|
|
3178
|
-
const UI_COMMAND_PENDING_TTL_SECONDS = 43800;
|
|
3179
|
-
const UI_COMMAND_TERMINAL_TTL_SECONDS = 1800;
|
|
3180
|
-
const UI_COMMAND_TERMINAL_STATUSES = [
|
|
3181
|
-
"applied",
|
|
3182
|
-
"no_active_client",
|
|
3183
|
-
"ui_host_unavailable",
|
|
3184
|
-
"rejected",
|
|
3185
|
-
"unsupported",
|
|
3186
|
-
"timeout"
|
|
3187
|
-
];
|
|
3188
|
-
const isTerminalUiCommandStatus = (status) => UI_COMMAND_TERMINAL_STATUSES.includes(status);
|
|
3189
|
-
const METHOD_RE = /^[A-Za-z][A-Za-z0-9_.:-]{0,63}$/;
|
|
3190
|
-
const isUiSurfaceMethod = (value) => typeof value === "string" && METHOD_RE.test(value);
|
|
3191
|
-
const WORK_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
3192
|
-
const UI_COMMAND_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
3193
|
-
const parseUiCommandId = (value) => {
|
|
3194
|
-
if (typeof value !== "string") return null;
|
|
3195
|
-
const trimmed = value.trim();
|
|
3196
|
-
return UI_COMMAND_ID_RE.test(trimmed) ? trimmed : null;
|
|
3197
|
-
};
|
|
3198
|
-
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3199
|
-
const asTrimmed = (value) => {
|
|
3200
|
-
if (typeof value !== "string") return null;
|
|
3201
|
-
const trimmed = value.trim();
|
|
3202
|
-
return trimmed ? trimmed : null;
|
|
3203
|
-
};
|
|
3204
|
-
const parseLaunch = (value) => {
|
|
3205
|
-
if (!isRecord(value)) return void 0;
|
|
3206
|
-
const search = asTrimmed(value.search);
|
|
3207
|
-
const hash = asTrimmed(value.hash);
|
|
3208
|
-
if (!search && !hash) return void 0;
|
|
3209
|
-
return {
|
|
3210
|
-
...search ? { search: search.startsWith("?") ? search : `?${search}` } : {},
|
|
3211
|
-
...hash ? { hash: hash.startsWith("#") ? hash : `#${hash}` } : {}
|
|
3212
|
-
};
|
|
3213
|
-
};
|
|
3214
|
-
const measureUiCommandPayload = (value) => {
|
|
3215
|
-
if (value === void 0) return 0;
|
|
3216
|
-
try {
|
|
3217
|
-
return new TextEncoder().encode(JSON.stringify(value) ?? "").length;
|
|
3218
|
-
} catch {
|
|
3219
|
-
return null;
|
|
3220
|
-
}
|
|
3221
|
-
};
|
|
3222
|
-
const parseUiCommand = (input) => {
|
|
3223
|
-
if (!isRecord(input)) return {
|
|
3224
|
-
command: null,
|
|
3225
|
-
error: "command must be an object"
|
|
3226
|
-
};
|
|
3227
|
-
if (input.type !== "preview.show") return {
|
|
3228
|
-
command: null,
|
|
3229
|
-
error: "command.type must be one of: preview.show"
|
|
3230
|
-
};
|
|
3231
|
-
const preview = input.preview;
|
|
3232
|
-
if (!isRecord(preview)) return {
|
|
3233
|
-
command: null,
|
|
3234
|
-
error: "command.preview is required"
|
|
3235
|
-
};
|
|
3236
|
-
if (preview.kind !== "work") return {
|
|
3237
|
-
command: null,
|
|
3238
|
-
error: "command.preview.kind must be one of: work"
|
|
3239
|
-
};
|
|
3240
|
-
const workId = asTrimmed(preview.workId);
|
|
3241
|
-
if (!workId) return {
|
|
3242
|
-
command: null,
|
|
3243
|
-
error: "command.preview.workId is required"
|
|
3244
|
-
};
|
|
3245
|
-
if (!WORK_ID_RE.test(workId)) return {
|
|
3246
|
-
command: null,
|
|
3247
|
-
error: "command.preview.workId must be a Work id"
|
|
3248
|
-
};
|
|
3249
|
-
const label = asTrimmed(preview.label);
|
|
3250
|
-
if (label && label.length > 200) return {
|
|
3251
|
-
command: null,
|
|
3252
|
-
error: `command.preview.label exceeds 200 characters`
|
|
3253
|
-
};
|
|
3254
|
-
const launch = parseLaunch(preview.launch);
|
|
3255
|
-
if (launch) {
|
|
3256
|
-
for (const [field, value] of [["search", launch.search], ["hash", launch.hash]]) if (value && value.length > 2048) return {
|
|
3257
|
-
command: null,
|
|
3258
|
-
error: `command.preview.launch.${field} exceeds ${UI_COMMAND_LAUNCH_MAX_LENGTH} characters`
|
|
3259
|
-
};
|
|
3260
|
-
}
|
|
3261
|
-
let request;
|
|
3262
|
-
if (input.request !== void 0 && input.request !== null) {
|
|
3263
|
-
if (!isRecord(input.request)) return {
|
|
3264
|
-
command: null,
|
|
3265
|
-
error: "command.request must be an object"
|
|
3266
|
-
};
|
|
3267
|
-
const method = asTrimmed(input.request.method);
|
|
3268
|
-
if (!method) return {
|
|
3269
|
-
command: null,
|
|
3270
|
-
error: "command.request.method is required"
|
|
3271
|
-
};
|
|
3272
|
-
if (!isUiSurfaceMethod(method)) return {
|
|
3273
|
-
command: null,
|
|
3274
|
-
error: "command.request.method has an unsupported format"
|
|
3275
|
-
};
|
|
3276
|
-
const size = measureUiCommandPayload(input.request.input);
|
|
3277
|
-
if (size === null) return {
|
|
3278
|
-
command: null,
|
|
3279
|
-
error: "command.request.input must be JSON-serializable"
|
|
3280
|
-
};
|
|
3281
|
-
if (size > 32768) return {
|
|
3282
|
-
command: null,
|
|
3283
|
-
error: `command.request.input exceeds ${UI_COMMAND_PAYLOAD_MAX_BYTES} bytes`
|
|
3284
|
-
};
|
|
3285
|
-
request = {
|
|
3286
|
-
method,
|
|
3287
|
-
...input.request.input === void 0 ? {} : { input: input.request.input }
|
|
3288
|
-
};
|
|
3289
|
-
}
|
|
3290
|
-
const command = {
|
|
3291
|
-
type: "preview.show",
|
|
3292
|
-
preview: {
|
|
3293
|
-
kind: "work",
|
|
3294
|
-
workId,
|
|
3295
|
-
...label ? { label } : {},
|
|
3296
|
-
...launch ? { launch } : {}
|
|
3297
|
-
},
|
|
3298
|
-
...request ? { request } : {}
|
|
3299
|
-
};
|
|
3300
|
-
const totalSize = measureUiCommandPayload(command);
|
|
3301
|
-
if (totalSize === null) return {
|
|
3302
|
-
command: null,
|
|
3303
|
-
error: "command must be JSON-serializable"
|
|
3304
|
-
};
|
|
3305
|
-
if (totalSize > 40960) return {
|
|
3306
|
-
command: null,
|
|
3307
|
-
error: `command exceeds ${UI_COMMAND_MAX_BYTES} bytes`
|
|
3308
|
-
};
|
|
3309
|
-
return {
|
|
3310
|
-
command,
|
|
3311
|
-
error: null
|
|
3312
|
-
};
|
|
3313
|
-
};
|
|
3314
|
-
//#endregion
|
|
3315
4821
|
//#region src/apis/ui-commands.ts
|
|
3316
4822
|
const DEFAULT_POLL_INTERVAL_MS = 300;
|
|
3317
4823
|
const resolveTimeoutMs = (timeoutMs) => {
|
|
@@ -3386,6 +4892,7 @@ var UiCommandsApi = class {
|
|
|
3386
4892
|
};
|
|
3387
4893
|
//#endregion
|
|
3388
4894
|
//#region src/apis/user.ts
|
|
4895
|
+
const usageDate = (value) => value instanceof Date ? value.toISOString() : value;
|
|
3389
4896
|
var UserApi = class {
|
|
3390
4897
|
transport;
|
|
3391
4898
|
transportBaseUrl;
|
|
@@ -3428,9 +4935,13 @@ var UserApi = class {
|
|
|
3428
4935
|
getSession(sessionId, customFetch) {
|
|
3429
4936
|
return this.transport.request(`/api/sessions/${sessionId}`, { fetch: customFetch });
|
|
3430
4937
|
}
|
|
3431
|
-
|
|
3432
|
-
const params = new URLSearchParams(
|
|
3433
|
-
|
|
4938
|
+
getActivity(options = {}, customFetch) {
|
|
4939
|
+
const params = new URLSearchParams();
|
|
4940
|
+
if (options.days !== void 0) params.set("days", String(options.days));
|
|
4941
|
+
if (options.from !== void 0) params.set("from", usageDate(options.from));
|
|
4942
|
+
if (options.to !== void 0) params.set("to", usageDate(options.to));
|
|
4943
|
+
const query = params.toString();
|
|
4944
|
+
return this.transport.request(`/api/me/activity${query ? `?${query}` : ""}`, { fetch: customFetch });
|
|
3434
4945
|
}
|
|
3435
4946
|
async setAuthToken(token) {
|
|
3436
4947
|
const trimmedToken = token.trim();
|
|
@@ -3549,6 +5060,33 @@ var WorksApi = class {
|
|
|
3549
5060
|
getStats(workId) {
|
|
3550
5061
|
return this.transport.request(`/api/works/${workId}/stats`);
|
|
3551
5062
|
}
|
|
5063
|
+
listPromotions(workId) {
|
|
5064
|
+
return this.transport.request(`/api/works/${workId}/promotions`);
|
|
5065
|
+
}
|
|
5066
|
+
createPromotion(workId, input) {
|
|
5067
|
+
return this.transport.request(`/api/works/${workId}/promotions`, {
|
|
5068
|
+
method: "POST",
|
|
5069
|
+
headers: { "Content-Type": "application/json" },
|
|
5070
|
+
body: JSON.stringify(input)
|
|
5071
|
+
});
|
|
5072
|
+
}
|
|
5073
|
+
getPromotionStats(workId, promotionId) {
|
|
5074
|
+
return this.transport.request(`/api/works/${workId}/promotions/${promotionId}/stats`);
|
|
5075
|
+
}
|
|
5076
|
+
recordPromotionEvent(workId, promotionId, input) {
|
|
5077
|
+
return this.transport.request(`/api/works/${workId}/promotions/${promotionId}/events`, {
|
|
5078
|
+
method: "POST",
|
|
5079
|
+
headers: { "Content-Type": "application/json" },
|
|
5080
|
+
body: JSON.stringify(input)
|
|
5081
|
+
});
|
|
5082
|
+
}
|
|
5083
|
+
recordPromotionRegistration(workId, promotionId, input) {
|
|
5084
|
+
return this.transport.request(`/api/works/${workId}/promotions/${promotionId}/registration`, {
|
|
5085
|
+
method: "POST",
|
|
5086
|
+
headers: input ? { "Content-Type": "application/json" } : void 0,
|
|
5087
|
+
body: input ? JSON.stringify(input) : void 0
|
|
5088
|
+
});
|
|
5089
|
+
}
|
|
3552
5090
|
listVersions(workId) {
|
|
3553
5091
|
return this.transport.request(`/api/works/${workId}/versions`);
|
|
3554
5092
|
}
|
|
@@ -3657,4 +5195,4 @@ var CohubHttpClient = class {
|
|
|
3657
5195
|
};
|
|
3658
5196
|
const createHttpClient = (options) => new CohubHttpClient(options);
|
|
3659
5197
|
//#endregion
|
|
3660
|
-
export {
|
|
5198
|
+
export { parseUsername as $, WORK_SURFACE_READY_TIMEOUT_MS as A, parseWorkSurfaceRequest as B, BoardInputError as C, WORK_COMPOSER_CHIP_CONTENT_MAX_BYTES as D, validateBoardNodes as E, buildWorkSurfaceRequest as F, UI_COMMAND_PENDING_TTL_SECONDS as G, UI_COMMAND_DEFAULT_TIMEOUT_MS as H, buildWorkSurfaceResponse as I, UI_COMMAND_VERSION as J, UI_COMMAND_SETTLEMENT_GRACE_SECONDS as K, parseWorkComposerChipClear as L, buildWorkComposerChipClear as M, buildWorkComposerChipSet as N, WORK_COMPOSER_CHIP_KEY_MAX_LENGTH as O, buildWorkSurfaceReady as P, parseSpaceSlug as Q, parseWorkComposerChipSet as R, createSessionPatchReducer as S, createBoardNode as T, UI_COMMAND_MAX_TIMEOUT_MS as U, parseWorkSurfaceResponse as V, UI_COMMAND_PAYLOAD_MAX_BYTES as W, isUiSurfaceMethod as X, isTerminalUiCommandStatus as Y, parseUiCommand as Z, buildSpacePath as _, ModelsApi as _t, ReferralsApi as a, validateBoardNodeInput as at, parseAssistantMessageCommit as b, ChannelsApi as bt, UiCommandsApi as c, ensureRealtimeConnected as ct, BoardTransactionError as d, SessionAccessApi as dt, BoardAwarenessClientPayloadSchema as et, SpaceClient as f, ReferencesApi as ft, buildSpaceInvitePath as g, PromptsApi as gt, PublicInviteApi as h, SkillsApi as ht, WorksApi as i, BOARD_NODE_CONTRACT as it, WORK_SURFACE_REQUEST_TIMEOUT_MS as j, WORK_COMPOSER_CHIP_LABEL_MAX_LENGTH as k, TasksApi as l, BOARD_BUILTIN_CAPABILITIES as lt, SpacesApi as m, PublicAssetsApi as mt, createHttpClient as n, BOARD_GEO_KINDS as nt, UsersApi as o, BoardPlaybackPolicySchema as ot, SpacePublicFilesApi as p, SearchApi as pt, UI_COMMAND_TERMINAL_TTL_SECONDS as q, WorkCommerceApi as r, BOARD_NATIVE_NODE_TYPES as rt, UserApi as s, parseBoardPlaybackPolicy as st, CohubHttpClient as t, BOARD_COLOR_IDS as tt, BoardClient as u, DEFAULT_BOARD_RENDER_LIMITS as ut, SessionGenerationStreamClient as v, GenerationsApi as vt, assertBoardNodes as w, SessionPatchReducer as x, createSessionGenerationStreamClient as y, CronJobsApi as yt, parseWorkSurfaceReady as z };
|