@jami-studio/core 0.92.27 → 0.92.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +17 -0
  3. package/corpus/core/package.json +2 -1
  4. package/corpus/core/src/deploy/build.ts +56 -2
  5. package/corpus/core/src/event-bus/bus.ts +138 -140
  6. package/corpus/core/src/event-bus/registry.ts +81 -79
  7. package/corpus/core/src/file-upload/registry.ts +135 -125
  8. package/corpus/core/src/notifications/registry.ts +218 -216
  9. package/corpus/core/src/private-blob/registry.ts +246 -242
  10. package/corpus/core/src/secrets/register.ts +132 -129
  11. package/corpus/core/src/shared/global-scope.ts +75 -0
  12. package/corpus/core/src/sharing/registry.ts +193 -194
  13. package/corpus/core/src/tracking/providers.ts +425 -422
  14. package/corpus/core/src/tracking/registry.ts +102 -102
  15. package/dist/collab/routes.d.ts +1 -1
  16. package/dist/deploy/build.d.ts +23 -0
  17. package/dist/deploy/build.d.ts.map +1 -1
  18. package/dist/deploy/build.js +36 -2
  19. package/dist/deploy/build.js.map +1 -1
  20. package/dist/event-bus/bus.d.ts.map +1 -1
  21. package/dist/event-bus/bus.js +8 -6
  22. package/dist/event-bus/bus.js.map +1 -1
  23. package/dist/event-bus/registry.d.ts.map +1 -1
  24. package/dist/event-bus/registry.js +10 -6
  25. package/dist/event-bus/registry.js.map +1 -1
  26. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  27. package/dist/file-upload/registry.d.ts.map +1 -1
  28. package/dist/file-upload/registry.js +28 -8
  29. package/dist/file-upload/registry.js.map +1 -1
  30. package/dist/notifications/registry.d.ts.map +1 -1
  31. package/dist/notifications/registry.js +6 -5
  32. package/dist/notifications/registry.js.map +1 -1
  33. package/dist/observability/routes.d.ts +5 -5
  34. package/dist/private-blob/registry.d.ts.map +1 -1
  35. package/dist/private-blob/registry.js +18 -13
  36. package/dist/private-blob/registry.js.map +1 -1
  37. package/dist/progress/routes.d.ts +1 -1
  38. package/dist/resources/handlers.d.ts +2 -2
  39. package/dist/secrets/register.d.ts.map +1 -1
  40. package/dist/secrets/register.js +11 -7
  41. package/dist/secrets/register.js.map +1 -1
  42. package/dist/secrets/routes.d.ts +9 -9
  43. package/dist/shared/global-scope.d.ts +55 -0
  44. package/dist/shared/global-scope.d.ts.map +1 -0
  45. package/dist/shared/global-scope.js +70 -0
  46. package/dist/shared/global-scope.js.map +1 -0
  47. package/dist/sharing/registry.d.ts.map +1 -1
  48. package/dist/sharing/registry.js +2 -16
  49. package/dist/sharing/registry.js.map +1 -1
  50. package/dist/tracking/providers.d.ts.map +1 -1
  51. package/dist/tracking/providers.js +11 -9
  52. package/dist/tracking/providers.js.map +1 -1
  53. package/dist/tracking/registry.d.ts.map +1 -1
  54. package/dist/tracking/registry.js +5 -5
  55. package/dist/tracking/registry.js.map +1 -1
  56. package/package.json +2 -1
@@ -1,242 +1,246 @@
1
- import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
-
3
- import { uploadFile } from "../file-upload/index.js";
4
- import {
5
- decryptSecretValue,
6
- encryptSecretValue,
7
- getSecretEncryptionKey,
8
- } from "../secrets/crypto.js";
9
- import type {
10
- PrivateBlobDeleteResult,
11
- PrivateBlobHandle,
12
- PrivateBlobProvider,
13
- PrivateBlobPutInput,
14
- PrivateBlobReadResult,
15
- } from "./types.js";
16
-
17
- interface PrivateBlobGlobals {
18
- __agentNativePrivateBlobProviders?: Map<string, PrivateBlobProvider>;
19
- __agentNativePrivateBlobPublicUploadFallback?: { enabled: boolean };
20
- }
21
-
22
- interface EncryptedPayload {
23
- iv: string;
24
- tag: string;
25
- ciphertext: Uint8Array;
26
- }
27
-
28
- interface EncryptionParams {
29
- iv: string;
30
- tag: string;
31
- }
32
-
33
- interface PublicUploadDescriptor {
34
- kind: "agent-native.private-blob.public-upload";
35
- version: 1;
36
- url: string;
37
- uploadProvider: string;
38
- uploadId?: string;
39
- encryption: EncryptionParams;
40
- mimeType?: string;
41
- metadata?: PrivateBlobHandle["metadata"];
42
- size: number;
43
- createdAt: string;
44
- }
45
-
46
- const PUBLIC_UPLOAD_HANDLE_PREFIX = "public-upload:v1:";
47
- const globals = globalThis as typeof globalThis & PrivateBlobGlobals;
48
- const providers: Map<string, PrivateBlobProvider> =
49
- (globals.__agentNativePrivateBlobProviders ??= new Map());
50
- const publicUploadFallbackRef: { enabled: boolean } =
51
- (globals.__agentNativePrivateBlobPublicUploadFallback ??= {
52
- enabled: true,
53
- });
54
-
55
- function toBytes(data: Uint8Array | Buffer): Uint8Array {
56
- return data instanceof Uint8Array ? data : new Uint8Array(data);
57
- }
58
-
59
- function encryptBytes(data: Uint8Array): EncryptedPayload {
60
- const iv = randomBytes(12);
61
- const cipher = createCipheriv("aes-256-gcm", getSecretEncryptionKey(), iv);
62
- const ciphertext = Buffer.concat([
63
- cipher.update(Buffer.from(data)),
64
- cipher.final(),
65
- ]);
66
- return {
67
- iv: iv.toString("base64url"),
68
- ciphertext: new Uint8Array(ciphertext),
69
- tag: cipher.getAuthTag().toString("base64url"),
70
- };
71
- }
72
-
73
- function decryptBytes(
74
- params: EncryptionParams,
75
- ciphertext: Uint8Array,
76
- ): Uint8Array {
77
- const decipher = createDecipheriv(
78
- "aes-256-gcm",
79
- getSecretEncryptionKey(),
80
- Buffer.from(params.iv, "base64url"),
81
- );
82
- decipher.setAuthTag(Buffer.from(params.tag, "base64url"));
83
- return new Uint8Array(
84
- Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]),
85
- );
86
- }
87
-
88
- function encodePublicUploadDescriptor(
89
- descriptor: PublicUploadDescriptor,
90
- ): string {
91
- return `${PUBLIC_UPLOAD_HANDLE_PREFIX}${encryptSecretValue(
92
- JSON.stringify(descriptor),
93
- )}`;
94
- }
95
-
96
- function decodePublicUploadDescriptor(id: string): PublicUploadDescriptor {
97
- if (!id.startsWith(PUBLIC_UPLOAD_HANDLE_PREFIX)) {
98
- throw new Error(
99
- "Private blob handle is not a public-upload fallback handle",
100
- );
101
- }
102
- const raw = decryptSecretValue(id.slice(PUBLIC_UPLOAD_HANDLE_PREFIX.length));
103
- const descriptor = JSON.parse(raw) as PublicUploadDescriptor;
104
- if (
105
- descriptor?.kind !== "agent-native.private-blob.public-upload" ||
106
- descriptor.version !== 1 ||
107
- typeof descriptor.url !== "string"
108
- ) {
109
- throw new Error("Private blob handle descriptor is invalid");
110
- }
111
- return descriptor;
112
- }
113
-
114
- function isPublicUploadFallbackHandle(handle: PrivateBlobHandle): boolean {
115
- return handle.id.startsWith(PUBLIC_UPLOAD_HANDLE_PREFIX);
116
- }
117
-
118
- async function putViaEncryptedPublicUpload(
119
- input: PrivateBlobPutInput,
120
- ): Promise<PrivateBlobHandle | null> {
121
- const bytes = toBytes(input.data);
122
- const encrypted = encryptBytes(bytes);
123
- const uploaded = await uploadFile({
124
- data: Buffer.from(encrypted.ciphertext),
125
- filename: input.filename ?? input.key ?? "private-blob.bin",
126
- mimeType: "application/octet-stream",
127
- ownerEmail: input.ownerEmail,
128
- recordAsset: false,
129
- });
130
- if (!uploaded) return null;
131
-
132
- const descriptor: PublicUploadDescriptor = {
133
- kind: "agent-native.private-blob.public-upload",
134
- version: 1,
135
- url: uploaded.url,
136
- uploadProvider: uploaded.provider,
137
- uploadId: uploaded.id,
138
- encryption: { iv: encrypted.iv, tag: encrypted.tag },
139
- mimeType: input.mimeType,
140
- metadata: input.metadata,
141
- size: bytes.byteLength,
142
- createdAt: new Date().toISOString(),
143
- };
144
-
145
- return {
146
- id: encodePublicUploadDescriptor(descriptor),
147
- provider: `public-upload:${uploaded.provider}`,
148
- opaque: true,
149
- encrypted: true,
150
- mimeType: input.mimeType,
151
- size: bytes.byteLength,
152
- createdAt: descriptor.createdAt,
153
- metadata: input.metadata,
154
- };
155
- }
156
-
157
- async function readViaEncryptedPublicUpload(
158
- handle: PrivateBlobHandle,
159
- ): Promise<PrivateBlobReadResult> {
160
- const descriptor = decodePublicUploadDescriptor(handle.id);
161
- const response = await fetch(descriptor.url);
162
- if (!response.ok) {
163
- throw new Error(
164
- `Private blob public-upload read failed (${response.status}): ${response.statusText}`,
165
- );
166
- }
167
- // The uploaded ciphertext is intentionally opaque; the descriptor carries
168
- // auth tag + IV separately so the backing public URL is useless by itself.
169
- const ciphertext = new Uint8Array(await response.arrayBuffer());
170
- return {
171
- data: decryptBytes(descriptor.encryption, ciphertext),
172
- mimeType: descriptor.mimeType,
173
- metadata: descriptor.metadata,
174
- handle,
175
- };
176
- }
177
-
178
- export function registerPrivateBlobProvider(
179
- provider: PrivateBlobProvider,
180
- ): void {
181
- providers.set(provider.id, provider);
182
- }
183
-
184
- export function unregisterPrivateBlobProvider(id: string): void {
185
- providers.delete(id);
186
- }
187
-
188
- export function listPrivateBlobProviders(): PrivateBlobProvider[] {
189
- return [...providers.values()];
190
- }
191
-
192
- export function getActivePrivateBlobProvider(): PrivateBlobProvider | null {
193
- for (const provider of providers.values()) {
194
- if (provider.isConfigured()) return provider;
195
- }
196
- return null;
197
- }
198
-
199
- export function setPrivateBlobPublicUploadFallbackEnabled(
200
- enabled: boolean,
201
- ): void {
202
- publicUploadFallbackRef.enabled = enabled;
203
- }
204
-
205
- export async function putPrivateBlob(
206
- input: PrivateBlobPutInput,
207
- ): Promise<PrivateBlobHandle | null> {
208
- const provider = getActivePrivateBlobProvider();
209
- if (provider) return provider.put(input);
210
- if (!publicUploadFallbackRef.enabled) return null;
211
- if (process.env.AGENT_NATIVE_PRIVATE_BLOB_PUBLIC_UPLOAD_FALLBACK === "0") {
212
- return null;
213
- }
214
- return putViaEncryptedPublicUpload(input);
215
- }
216
-
217
- export async function readPrivateBlob(
218
- handle: PrivateBlobHandle,
219
- ): Promise<PrivateBlobReadResult> {
220
- const provider = providers.get(handle.provider);
221
- if (provider) return provider.read(handle);
222
- if (isPublicUploadFallbackHandle(handle)) {
223
- return readViaEncryptedPublicUpload(handle);
224
- }
225
- throw new Error(`No private blob provider registered for ${handle.provider}`);
226
- }
227
-
228
- export async function deletePrivateBlob(
229
- handle: PrivateBlobHandle,
230
- ): Promise<PrivateBlobDeleteResult> {
231
- const provider = providers.get(handle.provider);
232
- if (provider) return provider.delete(handle);
233
- if (isPublicUploadFallbackHandle(handle)) {
234
- return {
235
- deleted: false,
236
- provider: handle.provider,
237
- reason:
238
- "delete is not supported by the encrypted public-upload fallback provider",
239
- };
240
- }
241
- throw new Error(`No private blob provider registered for ${handle.provider}`);
242
- }
1
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
+
3
+ import { uploadFile } from "../file-upload/index.js";
4
+ import {
5
+ decryptSecretValue,
6
+ encryptSecretValue,
7
+ getSecretEncryptionKey,
8
+ } from "../secrets/crypto.js";
9
+ import { getScopedGlobal } from "../shared/global-scope.js";
10
+ import type {
11
+ PrivateBlobDeleteResult,
12
+ PrivateBlobHandle,
13
+ PrivateBlobProvider,
14
+ PrivateBlobPutInput,
15
+ PrivateBlobReadResult,
16
+ } from "./types.js";
17
+
18
+ interface EncryptedPayload {
19
+ iv: string;
20
+ tag: string;
21
+ ciphertext: Uint8Array;
22
+ }
23
+
24
+ interface EncryptionParams {
25
+ iv: string;
26
+ tag: string;
27
+ }
28
+
29
+ interface PublicUploadDescriptor {
30
+ kind: "agent-native.private-blob.public-upload";
31
+ version: 1;
32
+ url: string;
33
+ uploadProvider: string;
34
+ uploadId?: string;
35
+ encryption: EncryptionParams;
36
+ mimeType?: string;
37
+ metadata?: PrivateBlobHandle["metadata"];
38
+ size: number;
39
+ createdAt: string;
40
+ }
41
+
42
+ const PUBLIC_UPLOAD_HANDLE_PREFIX = "public-upload:v1:";
43
+ // globalThis-pinned so an app's multiple ESM graphs share one registry, but
44
+ // scope-aware + lazily resolved so unified workspace deployments (all apps
45
+ // in one isolate) keep per-app provider registries. See shared/global-scope.
46
+ function getProviders(): Map<string, PrivateBlobProvider> {
47
+ return getScopedGlobal(
48
+ "agent-native.private-blob.providers",
49
+ () => new Map<string, PrivateBlobProvider>(),
50
+ );
51
+ }
52
+ function getPublicUploadFallbackRef(): { enabled: boolean } {
53
+ return getScopedGlobal(
54
+ "agent-native.private-blob.public-upload-fallback",
55
+ () => ({ enabled: true }),
56
+ );
57
+ }
58
+
59
+ function toBytes(data: Uint8Array | Buffer): Uint8Array {
60
+ return data instanceof Uint8Array ? data : new Uint8Array(data);
61
+ }
62
+
63
+ function encryptBytes(data: Uint8Array): EncryptedPayload {
64
+ const iv = randomBytes(12);
65
+ const cipher = createCipheriv("aes-256-gcm", getSecretEncryptionKey(), iv);
66
+ const ciphertext = Buffer.concat([
67
+ cipher.update(Buffer.from(data)),
68
+ cipher.final(),
69
+ ]);
70
+ return {
71
+ iv: iv.toString("base64url"),
72
+ ciphertext: new Uint8Array(ciphertext),
73
+ tag: cipher.getAuthTag().toString("base64url"),
74
+ };
75
+ }
76
+
77
+ function decryptBytes(
78
+ params: EncryptionParams,
79
+ ciphertext: Uint8Array,
80
+ ): Uint8Array {
81
+ const decipher = createDecipheriv(
82
+ "aes-256-gcm",
83
+ getSecretEncryptionKey(),
84
+ Buffer.from(params.iv, "base64url"),
85
+ );
86
+ decipher.setAuthTag(Buffer.from(params.tag, "base64url"));
87
+ return new Uint8Array(
88
+ Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]),
89
+ );
90
+ }
91
+
92
+ function encodePublicUploadDescriptor(
93
+ descriptor: PublicUploadDescriptor,
94
+ ): string {
95
+ return `${PUBLIC_UPLOAD_HANDLE_PREFIX}${encryptSecretValue(
96
+ JSON.stringify(descriptor),
97
+ )}`;
98
+ }
99
+
100
+ function decodePublicUploadDescriptor(id: string): PublicUploadDescriptor {
101
+ if (!id.startsWith(PUBLIC_UPLOAD_HANDLE_PREFIX)) {
102
+ throw new Error(
103
+ "Private blob handle is not a public-upload fallback handle",
104
+ );
105
+ }
106
+ const raw = decryptSecretValue(id.slice(PUBLIC_UPLOAD_HANDLE_PREFIX.length));
107
+ const descriptor = JSON.parse(raw) as PublicUploadDescriptor;
108
+ if (
109
+ descriptor?.kind !== "agent-native.private-blob.public-upload" ||
110
+ descriptor.version !== 1 ||
111
+ typeof descriptor.url !== "string"
112
+ ) {
113
+ throw new Error("Private blob handle descriptor is invalid");
114
+ }
115
+ return descriptor;
116
+ }
117
+
118
+ function isPublicUploadFallbackHandle(handle: PrivateBlobHandle): boolean {
119
+ return handle.id.startsWith(PUBLIC_UPLOAD_HANDLE_PREFIX);
120
+ }
121
+
122
+ async function putViaEncryptedPublicUpload(
123
+ input: PrivateBlobPutInput,
124
+ ): Promise<PrivateBlobHandle | null> {
125
+ const bytes = toBytes(input.data);
126
+ const encrypted = encryptBytes(bytes);
127
+ const uploaded = await uploadFile({
128
+ data: Buffer.from(encrypted.ciphertext),
129
+ filename: input.filename ?? input.key ?? "private-blob.bin",
130
+ mimeType: "application/octet-stream",
131
+ ownerEmail: input.ownerEmail,
132
+ recordAsset: false,
133
+ });
134
+ if (!uploaded) return null;
135
+
136
+ const descriptor: PublicUploadDescriptor = {
137
+ kind: "agent-native.private-blob.public-upload",
138
+ version: 1,
139
+ url: uploaded.url,
140
+ uploadProvider: uploaded.provider,
141
+ uploadId: uploaded.id,
142
+ encryption: { iv: encrypted.iv, tag: encrypted.tag },
143
+ mimeType: input.mimeType,
144
+ metadata: input.metadata,
145
+ size: bytes.byteLength,
146
+ createdAt: new Date().toISOString(),
147
+ };
148
+
149
+ return {
150
+ id: encodePublicUploadDescriptor(descriptor),
151
+ provider: `public-upload:${uploaded.provider}`,
152
+ opaque: true,
153
+ encrypted: true,
154
+ mimeType: input.mimeType,
155
+ size: bytes.byteLength,
156
+ createdAt: descriptor.createdAt,
157
+ metadata: input.metadata,
158
+ };
159
+ }
160
+
161
+ async function readViaEncryptedPublicUpload(
162
+ handle: PrivateBlobHandle,
163
+ ): Promise<PrivateBlobReadResult> {
164
+ const descriptor = decodePublicUploadDescriptor(handle.id);
165
+ const response = await fetch(descriptor.url);
166
+ if (!response.ok) {
167
+ throw new Error(
168
+ `Private blob public-upload read failed (${response.status}): ${response.statusText}`,
169
+ );
170
+ }
171
+ // The uploaded ciphertext is intentionally opaque; the descriptor carries
172
+ // auth tag + IV separately so the backing public URL is useless by itself.
173
+ const ciphertext = new Uint8Array(await response.arrayBuffer());
174
+ return {
175
+ data: decryptBytes(descriptor.encryption, ciphertext),
176
+ mimeType: descriptor.mimeType,
177
+ metadata: descriptor.metadata,
178
+ handle,
179
+ };
180
+ }
181
+
182
+ export function registerPrivateBlobProvider(
183
+ provider: PrivateBlobProvider,
184
+ ): void {
185
+ getProviders().set(provider.id, provider);
186
+ }
187
+
188
+ export function unregisterPrivateBlobProvider(id: string): void {
189
+ getProviders().delete(id);
190
+ }
191
+
192
+ export function listPrivateBlobProviders(): PrivateBlobProvider[] {
193
+ return [...getProviders().values()];
194
+ }
195
+
196
+ export function getActivePrivateBlobProvider(): PrivateBlobProvider | null {
197
+ for (const provider of getProviders().values()) {
198
+ if (provider.isConfigured()) return provider;
199
+ }
200
+ return null;
201
+ }
202
+
203
+ export function setPrivateBlobPublicUploadFallbackEnabled(
204
+ enabled: boolean,
205
+ ): void {
206
+ getPublicUploadFallbackRef().enabled = enabled;
207
+ }
208
+
209
+ export async function putPrivateBlob(
210
+ input: PrivateBlobPutInput,
211
+ ): Promise<PrivateBlobHandle | null> {
212
+ const provider = getActivePrivateBlobProvider();
213
+ if (provider) return provider.put(input);
214
+ if (!getPublicUploadFallbackRef().enabled) return null;
215
+ if (process.env.AGENT_NATIVE_PRIVATE_BLOB_PUBLIC_UPLOAD_FALLBACK === "0") {
216
+ return null;
217
+ }
218
+ return putViaEncryptedPublicUpload(input);
219
+ }
220
+
221
+ export async function readPrivateBlob(
222
+ handle: PrivateBlobHandle,
223
+ ): Promise<PrivateBlobReadResult> {
224
+ const provider = getProviders().get(handle.provider);
225
+ if (provider) return provider.read(handle);
226
+ if (isPublicUploadFallbackHandle(handle)) {
227
+ return readViaEncryptedPublicUpload(handle);
228
+ }
229
+ throw new Error(`No private blob provider registered for ${handle.provider}`);
230
+ }
231
+
232
+ export async function deletePrivateBlob(
233
+ handle: PrivateBlobHandle,
234
+ ): Promise<PrivateBlobDeleteResult> {
235
+ const provider = getProviders().get(handle.provider);
236
+ if (provider) return provider.delete(handle);
237
+ if (isPublicUploadFallbackHandle(handle)) {
238
+ return {
239
+ deleted: false,
240
+ provider: handle.provider,
241
+ reason:
242
+ "delete is not supported by the encrypted public-upload fallback provider",
243
+ };
244
+ }
245
+ throw new Error(`No private blob provider registered for ${handle.provider}`);
246
+ }