@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +17 -0
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/deploy/build.ts +56 -2
- package/corpus/core/src/event-bus/bus.ts +138 -140
- package/corpus/core/src/event-bus/registry.ts +81 -79
- package/corpus/core/src/file-upload/registry.ts +135 -125
- package/corpus/core/src/notifications/registry.ts +218 -216
- package/corpus/core/src/private-blob/registry.ts +246 -242
- package/corpus/core/src/secrets/register.ts +132 -129
- package/corpus/core/src/shared/global-scope.ts +75 -0
- package/corpus/core/src/sharing/registry.ts +193 -194
- package/corpus/core/src/tracking/providers.ts +425 -422
- package/corpus/core/src/tracking/registry.ts +102 -102
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +23 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +36 -2
- package/dist/deploy/build.js.map +1 -1
- package/dist/event-bus/bus.d.ts.map +1 -1
- package/dist/event-bus/bus.js +8 -6
- package/dist/event-bus/bus.js.map +1 -1
- package/dist/event-bus/registry.d.ts.map +1 -1
- package/dist/event-bus/registry.js +10 -6
- package/dist/event-bus/registry.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/file-upload/registry.d.ts.map +1 -1
- package/dist/file-upload/registry.js +28 -8
- package/dist/file-upload/registry.js.map +1 -1
- package/dist/notifications/registry.d.ts.map +1 -1
- package/dist/notifications/registry.js +6 -5
- package/dist/notifications/registry.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/private-blob/registry.d.ts.map +1 -1
- package/dist/private-blob/registry.js +18 -13
- package/dist/private-blob/registry.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/secrets/register.d.ts.map +1 -1
- package/dist/secrets/register.js +11 -7
- package/dist/secrets/register.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/shared/global-scope.d.ts +55 -0
- package/dist/shared/global-scope.d.ts.map +1 -0
- package/dist/shared/global-scope.js +70 -0
- package/dist/shared/global-scope.js.map +1 -0
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js +2 -16
- package/dist/sharing/registry.js.map +1 -1
- package/dist/tracking/providers.d.ts.map +1 -1
- package/dist/tracking/providers.js +11 -9
- package/dist/tracking/providers.js.map +1 -1
- package/dist/tracking/registry.d.ts.map +1 -1
- package/dist/tracking/registry.js +5 -5
- package/dist/tracking/registry.js.map +1 -1
- package/package.json +2 -1
|
@@ -1,216 +1,218 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
import { emit as emitBusEvent } from "../event-bus/bus.js";
|
|
4
|
-
import { registerEvent } from "../event-bus/registry.js";
|
|
5
|
-
import type { EventDefinition } from "../event-bus/types.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type
|
|
12
|
-
type
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { emit as emitBusEvent } from "../event-bus/bus.js";
|
|
4
|
+
import { registerEvent } from "../event-bus/registry.js";
|
|
5
|
+
import type { EventDefinition } from "../event-bus/types.js";
|
|
6
|
+
import { getScopedGlobal } from "../shared/global-scope.js";
|
|
7
|
+
import { truncate } from "../shared/truncate.js";
|
|
8
|
+
import { insertNotification, updateDeliveredChannels } from "./store.js";
|
|
9
|
+
import {
|
|
10
|
+
NOTIFICATION_SEVERITIES,
|
|
11
|
+
type NotificationChannel,
|
|
12
|
+
type NotificationInput,
|
|
13
|
+
type NotificationMeta,
|
|
14
|
+
type Notification,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
|
|
17
|
+
export interface NotificationDeliveryResult {
|
|
18
|
+
notification?: Notification;
|
|
19
|
+
deliveredChannels: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
registerEvent({
|
|
23
|
+
name: "notification.sent",
|
|
24
|
+
description:
|
|
25
|
+
"Fires after notify() delivers to at least one channel. Automations can chain off this — e.g. fan critical notifications to Slack.",
|
|
26
|
+
payloadSchema: z.object({
|
|
27
|
+
notificationId: z.string().optional(),
|
|
28
|
+
severity: z.enum(NOTIFICATION_SEVERITIES),
|
|
29
|
+
title: z.string(),
|
|
30
|
+
body: z.string().optional(),
|
|
31
|
+
deliveredChannels: z.array(z.string()),
|
|
32
|
+
}) as unknown as EventDefinition["payloadSchema"],
|
|
33
|
+
example: {
|
|
34
|
+
notificationId: "ntf_abc",
|
|
35
|
+
severity: "critical",
|
|
36
|
+
title: "Payment failed",
|
|
37
|
+
body: "Card ending 4242 declined",
|
|
38
|
+
deliveredChannels: ["inbox", "webhook"],
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const REGISTRY_BASE_KEY = "agent-native.notifications.registry";
|
|
43
|
+
|
|
44
|
+
// globalThis-pinned so one app's ESM graphs share one channel registry, but
|
|
45
|
+
// scope-aware + lazily resolved so unified workspace deployments (all apps in
|
|
46
|
+
// one isolate) keep per-app channels. See shared/global-scope.
|
|
47
|
+
function getRegistry(): Map<string, NotificationChannel> {
|
|
48
|
+
return getScopedGlobal(
|
|
49
|
+
REGISTRY_BASE_KEY,
|
|
50
|
+
() => new Map<string, NotificationChannel>(),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function registerNotificationChannel(
|
|
55
|
+
channel: NotificationChannel,
|
|
56
|
+
): void {
|
|
57
|
+
if (!channel?.name) {
|
|
58
|
+
throw new Error("registerNotificationChannel: channel.name is required");
|
|
59
|
+
}
|
|
60
|
+
if (typeof channel.deliver !== "function") {
|
|
61
|
+
throw new Error(
|
|
62
|
+
"registerNotificationChannel: channel.deliver must be a function",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
getRegistry().set(channel.name, channel);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function unregisterNotificationChannel(name: string): boolean {
|
|
69
|
+
return getRegistry().delete(name);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function listNotificationChannels(): string[] {
|
|
73
|
+
return Array.from(getRegistry().keys());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Deliver a notification.
|
|
78
|
+
*
|
|
79
|
+
* The `inbox` channel always persists a row that drives the in-app UI
|
|
80
|
+
* (bell + toast). Additional channels (webhook, custom) run in parallel,
|
|
81
|
+
* best-effort. Returns the stored Notification when `inbox` ran, otherwise
|
|
82
|
+
* `undefined`.
|
|
83
|
+
*
|
|
84
|
+
* Also emits `notification.sent` on the event bus so automations can react
|
|
85
|
+
* to notifications (e.g. "when a critical notification fires, also page me").
|
|
86
|
+
*/
|
|
87
|
+
const MAX_TITLE_LEN = 100;
|
|
88
|
+
const MAX_BODY_LEN = 2000;
|
|
89
|
+
|
|
90
|
+
export async function notify(
|
|
91
|
+
input: NotificationInput,
|
|
92
|
+
meta: NotificationMeta,
|
|
93
|
+
): Promise<Notification | undefined> {
|
|
94
|
+
return (await notifyWithDelivery(input, meta)).notification;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function notifyWithDelivery(
|
|
98
|
+
input: NotificationInput,
|
|
99
|
+
meta: NotificationMeta,
|
|
100
|
+
): Promise<NotificationDeliveryResult> {
|
|
101
|
+
if (!meta?.owner) {
|
|
102
|
+
throw new Error("notify: meta.owner is required");
|
|
103
|
+
}
|
|
104
|
+
input = {
|
|
105
|
+
...input,
|
|
106
|
+
title: truncate(input.title, MAX_TITLE_LEN),
|
|
107
|
+
body: truncate(input.body, MAX_BODY_LEN),
|
|
108
|
+
};
|
|
109
|
+
const channels = selectChannels(input.channels);
|
|
110
|
+
const storedMetadata = scrubStoredMetadata(input.metadata);
|
|
111
|
+
|
|
112
|
+
// The inbox channel is always included unless explicitly excluded.
|
|
113
|
+
const runInbox = !input.channels || input.channels.includes("inbox");
|
|
114
|
+
const delivered: string[] = [];
|
|
115
|
+
let stored: Notification | undefined;
|
|
116
|
+
|
|
117
|
+
if (runInbox) {
|
|
118
|
+
try {
|
|
119
|
+
// Stored with just "inbox" first; the real delivered list is written
|
|
120
|
+
// after fan-out so a failing webhook doesn't claim it was delivered.
|
|
121
|
+
stored = await insertNotification({
|
|
122
|
+
owner: meta.owner,
|
|
123
|
+
severity: input.severity,
|
|
124
|
+
title: input.title,
|
|
125
|
+
body: input.body,
|
|
126
|
+
metadata: storedMetadata,
|
|
127
|
+
deliveredChannels: ["inbox"],
|
|
128
|
+
});
|
|
129
|
+
delivered.push("inbox");
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error("[notifications] inbox persist failed:", err);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Await every channel so a 500-ing webhook doesn't end up in `delivered`.
|
|
136
|
+
const results = await Promise.allSettled(
|
|
137
|
+
channels.map(async (channel) => {
|
|
138
|
+
const delivered = await channel.deliver(input, meta);
|
|
139
|
+
// Explicit `false` means the channel skipped (no URL / recipients).
|
|
140
|
+
if (delivered === false) return null;
|
|
141
|
+
return channel.name;
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
144
|
+
results.forEach((r, i) => {
|
|
145
|
+
if (r.status === "fulfilled") {
|
|
146
|
+
if (r.value) delivered.push(r.value);
|
|
147
|
+
} else {
|
|
148
|
+
console.error(
|
|
149
|
+
`[notifications] channel "${channels[i].name}" failed:`,
|
|
150
|
+
r.reason,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const hasExtraChannel = delivered.some((c) => c !== "inbox");
|
|
156
|
+
if (stored && hasExtraChannel) {
|
|
157
|
+
try {
|
|
158
|
+
await updateDeliveredChannels(stored.id, delivered);
|
|
159
|
+
stored = { ...stored, deliveredChannels: delivered };
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.error("[notifications] delivered-channel update failed:", err);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Only emit when at least one channel delivered — an emission with an
|
|
166
|
+
// empty delivery list (and likely a null notificationId) would mislead
|
|
167
|
+
// any automation chaining off this event.
|
|
168
|
+
if (delivered.length > 0) {
|
|
169
|
+
try {
|
|
170
|
+
emitBusEvent(
|
|
171
|
+
"notification.sent",
|
|
172
|
+
{
|
|
173
|
+
notificationId: stored?.id,
|
|
174
|
+
severity: input.severity,
|
|
175
|
+
title: input.title,
|
|
176
|
+
body: input.body,
|
|
177
|
+
deliveredChannels: delivered,
|
|
178
|
+
},
|
|
179
|
+
{ owner: meta.owner },
|
|
180
|
+
);
|
|
181
|
+
} catch {
|
|
182
|
+
// best-effort
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { notification: stored, deliveredChannels: delivered };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function scrubStoredMetadata(
|
|
190
|
+
metadata: Record<string, unknown> | undefined,
|
|
191
|
+
): Record<string, unknown> | undefined {
|
|
192
|
+
if (!metadata) return undefined;
|
|
193
|
+
const entries = Object.entries(metadata).filter(
|
|
194
|
+
([key]) =>
|
|
195
|
+
key !== "delivery" && key !== "webhookUrl" && key !== "slackWebhookUrl",
|
|
196
|
+
);
|
|
197
|
+
return entries.length ? Object.fromEntries(entries) : undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function selectChannels(allowlist?: string[]): NotificationChannel[] {
|
|
201
|
+
const registry = getRegistry();
|
|
202
|
+
const all = Array.from(registry.values());
|
|
203
|
+
if (!allowlist) return all;
|
|
204
|
+
return all.filter((c) => allowlist.includes(c.name));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Test helper — drops all registered channels. */
|
|
208
|
+
export function __resetNotificationChannels(): void {
|
|
209
|
+
getRegistry().clear();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export {
|
|
213
|
+
listNotifications,
|
|
214
|
+
markNotificationRead,
|
|
215
|
+
markAllNotificationsRead,
|
|
216
|
+
deleteNotification,
|
|
217
|
+
countUnread,
|
|
218
|
+
} from "./store.js";
|