@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
package/corpus/README.md CHANGED
@@ -27,5 +27,5 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
27
27
 
28
28
  ## Generated Counts
29
29
 
30
- - core files: 2156
30
+ - core files: 2157
31
31
  - template files: 5114
@@ -1,5 +1,22 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.92.28
4
+
5
+ ### Patch Changes
6
+
7
+ - 77b77e7: Per-app global-registry scoping on unified workspace deployments. The
8
+ globalThis-pinned framework registries (file-upload providers, private-blob
9
+ providers, shareable resources, event-bus registry + bus, notification
10
+ channels, tracking providers + queue, secrets registry) were shared across
11
+ every app in the single Cloudflare Pages isolate, so one app's registrations
12
+ served another app's requests — an upload POSTed to /assets was handled by
13
+ the clips app's S3 provider (wrong object prefix). Registries now resolve
14
+ their global keys lazily through a per-module-graph scope
15
+ (`@agent-native/core/global-scope`), and each mounted app's generated worker
16
+ entry sets its scope id via a `_scope-init.js` module evaluated first in the
17
+ bundle's import graph. Dev-mode and single-app deployments stay unscoped, so
18
+ the original multi-ESM-graph dedupe behavior is unchanged.
19
+
3
20
  ## 0.92.27
4
21
 
5
22
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.92.27",
3
+ "version": "0.92.28",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/studio-jami/jami-studio#readme",
6
6
  "bugs": {
@@ -97,6 +97,7 @@
97
97
  "./embedding/react": "./dist/embedding/react.js",
98
98
  "./onboarding": "./dist/onboarding/index.js",
99
99
  "./shared": "./dist/shared/index.js",
100
+ "./global-scope": "./dist/shared/global-scope.js",
100
101
  "./voice": "./dist/voice/index.js",
101
102
  "./scripts": "./dist/scripts/index.js",
102
103
  "./application-state": "./dist/application-state/index.js",
@@ -605,6 +605,46 @@ export const CLOUDFLARE_WORKER_NODE_BUILTIN_STUB_MODULES: Record<
605
605
 
606
606
  export interface GenerateWorkerEntryOptions {
607
607
  includeReactRouterSsr?: boolean;
608
+ /**
609
+ * Per-app global-registry scope id for unified workspace deployments.
610
+ * When set, the generated entry's FIRST import is `./_scope-init.js`
611
+ * (written by the build via `generateScopeInitSource`), which calls
612
+ * core's `setGlobalScopeId(appId)` before any other module in the app
613
+ * bundle evaluates — so every globalThis-pinned registry in this app's
614
+ * module graph resolves per-app keys instead of colliding with sibling
615
+ * apps in the shared isolate (issue: /assets uploads served by clips'
616
+ * provider).
617
+ */
618
+ appScopeId?: string | null;
619
+ }
620
+
621
+ /**
622
+ * Source of the `_scope-init.js` module the worker entry imports first on
623
+ * unified workspace deployments. Must stay dependency-free beyond core's
624
+ * lean `global-scope` subpath so nothing registry-touching evaluates before
625
+ * the scope id is set (ESM evaluates imports depth-first in order).
626
+ */
627
+ export function generateScopeInitSource(appScopeId: string): string {
628
+ return `// AUTO-GENERATED by @agent-native/core deploy build
629
+ // Scope globalThis-pinned framework registries to this app's module graph.
630
+ // Evaluated FIRST in the worker entry's import graph so the scope is set
631
+ // before any registry module initializes. See core shared/global-scope.
632
+ import { setGlobalScopeId } from "@agent-native/core/global-scope";
633
+ setGlobalScopeId(${JSON.stringify(appScopeId)});
634
+ `;
635
+ }
636
+
637
+ /**
638
+ * Derive the workspace app id from a mounted app base path ("/assets" →
639
+ * "assets"). Returns null for unmounted (root) builds.
640
+ */
641
+ export function workspaceAppScopeIdFromBasePath(
642
+ basePath: string | undefined,
643
+ ): string | null {
644
+ const normalized = normalizeAppBasePath(basePath ?? "");
645
+ if (!normalized) return null;
646
+ const [first] = normalized.slice(1).split("/");
647
+ return first || null;
608
648
  }
609
649
 
610
650
  interface ReactRouterAssetManifest {
@@ -734,6 +774,7 @@ export function generateWorkerEntry(
734
774
  options: GenerateWorkerEntryOptions = {},
735
775
  ): string {
736
776
  const includeReactRouterSsr = options.includeReactRouterSsr ?? true;
777
+ const appScopeId = options.appScopeId ?? null;
737
778
  const routeImports: string[] = [];
738
779
  const routeRegistrations: string[] = [];
739
780
 
@@ -854,7 +895,7 @@ export function generateWorkerEntry(
854
895
 
855
896
  return `
856
897
  // Auto-generated worker entry point for ${preset}
857
- import { H3, defineEventHandler, readBody, toResponse } from "h3";
898
+ ${appScopeId ? 'import "./_scope-init.js";\n' : ""}import { H3, defineEventHandler, readBody, toResponse } from "h3";
858
899
  ${includeReactRouterSsr ? 'import { createRequestHandler } from "react-router";' : ""}
859
900
  ${includeReactRouterSsr ? 'import * as serverBuild from "./server-build.js";' : ""}
860
901
 
@@ -1668,6 +1709,12 @@ async function buildCloudflarePages() {
1668
1709
 
1669
1710
  // Generate the worker entry
1670
1711
  const immutableAssetPaths = collectImmutableAssetPaths(clientDir);
1712
+ // Per-app global-registry scoping for unified workspace deploys: mounted
1713
+ // builds (APP_BASE_PATH=/<app>) get a scope-init module evaluated first so
1714
+ // globalThis-pinned registries stay per-app in the shared isolate.
1715
+ const appScopeId = workspaceAppScopeIdFromBasePath(
1716
+ normalizeConfiguredAppBasePath(),
1717
+ );
1671
1718
  const entrySource = generateWorkerEntry(
1672
1719
  routes,
1673
1720
  plugins,
@@ -1676,7 +1723,7 @@ async function buildCloudflarePages() {
1676
1723
  workspaceCore,
1677
1724
  immutableAssetPaths,
1678
1725
  normalizeConfiguredAppBasePath(),
1679
- { includeReactRouterSsr },
1726
+ { includeReactRouterSsr, appScopeId },
1680
1727
  );
1681
1728
 
1682
1729
  // Create _worker.js output directory
@@ -1704,6 +1751,13 @@ async function buildCloudflarePages() {
1704
1751
  const tmpEntry = path.join(tmpDir, "index.js");
1705
1752
  fs.writeFileSync(tmpEntry, adjustedEntry);
1706
1753
 
1754
+ if (appScopeId) {
1755
+ fs.writeFileSync(
1756
+ path.join(tmpDir, "_scope-init.js"),
1757
+ generateScopeInitSource(appScopeId),
1758
+ );
1759
+ }
1760
+
1707
1761
  if (includeReactRouterSsr) {
1708
1762
  copyDir(serverDir, path.join(tmpDir, "server"));
1709
1763
  }
@@ -1,140 +1,138 @@
1
- /**
2
- * Typed pub/sub bus for framework events.
3
- *
4
- * Wraps Node's EventEmitter with payload validation against the registered
5
- * Standard Schema for each event. Handler errors are caught and logged so a
6
- * misbehaving subscriber can never crash the emitter.
7
- */
8
-
9
- import { randomUUID } from "node:crypto";
10
- import { EventEmitter } from "node:events";
11
-
12
- import { getEvent } from "./registry.js";
13
- import type { EventMeta } from "./types.js";
14
-
15
- type Handler = (payload: unknown, meta: EventMeta) => void | Promise<void>;
16
-
17
- interface BusState {
18
- emitter: EventEmitter;
19
- subscriptions: Map<string, { event: string; handler: Handler }>;
20
- }
21
-
22
- const BUS_KEY = Symbol.for("@agent-native/core/event-bus.bus");
23
- interface GlobalWithBus {
24
- [BUS_KEY]?: BusState;
25
- }
26
-
27
- function getBus(): BusState {
28
- const g = globalThis as unknown as GlobalWithBus;
29
- if (!g[BUS_KEY]) {
30
- const emitter = new EventEmitter();
31
- // Many integrations may subscribe to the same event; lift the warning
32
- // ceiling rather than printing MaxListenersExceededWarning at runtime.
33
- emitter.setMaxListeners(0);
34
- g[BUS_KEY] = { emitter, subscriptions: new Map() };
35
- }
36
- return g[BUS_KEY]!;
37
- }
38
-
39
- export function subscribe(event: string, handler: Handler): string {
40
- if (typeof event !== "string" || !event) {
41
- throw new Error("subscribe: event name is required");
42
- }
43
- if (typeof handler !== "function") {
44
- throw new Error("subscribe: handler must be a function");
45
- }
46
- const bus = getBus();
47
- const id = randomUUID();
48
- bus.subscriptions.set(id, { event, handler });
49
- bus.emitter.on(event, handler);
50
- return id;
51
- }
52
-
53
- export function unsubscribe(id: string): boolean {
54
- const bus = getBus();
55
- const sub = bus.subscriptions.get(id);
56
- if (!sub) return false;
57
- bus.emitter.off(sub.event, sub.handler);
58
- bus.subscriptions.delete(id);
59
- return true;
60
- }
61
-
62
- export function emit(
63
- event: string,
64
- payload: unknown,
65
- meta?: Partial<EventMeta>,
66
- ): void {
67
- if (typeof event !== "string" || !event) {
68
- throw new Error("emit: event name is required");
69
- }
70
- const bus = getBus();
71
- const def = getEvent(event);
72
-
73
- let validated: unknown = payload;
74
- if (def) {
75
- const result = def.payloadSchema["~standard"].validate(payload);
76
- if (result instanceof Promise) {
77
- console.warn(
78
- `[event-bus] Payload schema for "${event}" returned a Promise — ` +
79
- `async validation is not supported. Dispatching unvalidated payload.`,
80
- );
81
- } else if (result.issues) {
82
- console.warn(
83
- `[event-bus] Payload validation failed for "${event}":`,
84
- result.issues,
85
- );
86
- return;
87
- } else {
88
- validated = (result as { value: unknown }).value;
89
- }
90
- } else {
91
- console.warn(
92
- `[event-bus] Emitting unregistered event "${event}". ` +
93
- `Call registerEvent() to declare it.`,
94
- );
95
- }
96
-
97
- const fullMeta: EventMeta = {
98
- eventId: meta?.eventId ?? randomUUID(),
99
- emittedAt: meta?.emittedAt ?? new Date().toISOString(),
100
- owner: meta?.owner,
101
- };
102
-
103
- // Snapshot listeners so a handler that subscribes/unsubscribes during
104
- // dispatch doesn't perturb this emission.
105
- const listeners = bus.emitter.listeners(event) as Handler[];
106
- for (const listener of listeners) {
107
- try {
108
- const r = listener(validated, fullMeta);
109
- if (r && typeof (r as Promise<void>).catch === "function") {
110
- (r as Promise<void>).catch((err) => {
111
- console.error(
112
- `[event-bus] Async handler for "${event}" rejected:`,
113
- err,
114
- );
115
- });
116
- }
117
- } catch (err) {
118
- console.error(`[event-bus] Handler for "${event}" threw:`, err);
119
- }
120
- }
121
- }
122
-
123
- export function listSubscriptions(
124
- event?: string,
125
- ): { id: string; event: string }[] {
126
- const bus = getBus();
127
- const out: { id: string; event: string }[] = [];
128
- for (const [id, sub] of bus.subscriptions) {
129
- if (event && sub.event !== event) continue;
130
- out.push({ id, event: sub.event });
131
- }
132
- return out;
133
- }
134
-
135
- /** Test helper — drops all subscriptions. */
136
- export function __resetEventBus(): void {
137
- const bus = getBus();
138
- bus.emitter.removeAllListeners();
139
- bus.subscriptions.clear();
140
- }
1
+ /**
2
+ * Typed pub/sub bus for framework events.
3
+ *
4
+ * Wraps Node's EventEmitter with payload validation against the registered
5
+ * Standard Schema for each event. Handler errors are caught and logged so a
6
+ * misbehaving subscriber can never crash the emitter.
7
+ */
8
+
9
+ import { randomUUID } from "node:crypto";
10
+ import { EventEmitter } from "node:events";
11
+
12
+ import { getScopedGlobal } from "../shared/global-scope.js";
13
+ import { getEvent } from "./registry.js";
14
+ import type { EventMeta } from "./types.js";
15
+
16
+ type Handler = (payload: unknown, meta: EventMeta) => void | Promise<void>;
17
+
18
+ interface BusState {
19
+ emitter: EventEmitter;
20
+ subscriptions: Map<string, { event: string; handler: Handler }>;
21
+ }
22
+
23
+ // globalThis-pinned so one app's ESM graphs share one bus, but scope-aware +
24
+ // lazily resolved so unified workspace deployments (all apps in one isolate)
25
+ // keep per-app buses — one app's handlers never fire for a same-named event
26
+ // emitted by a sibling app. See shared/global-scope.
27
+ function getBus(): BusState {
28
+ return getScopedGlobal("agent-native.event-bus.bus", () => {
29
+ const emitter = new EventEmitter();
30
+ // Many integrations may subscribe to the same event; lift the warning
31
+ // ceiling rather than printing MaxListenersExceededWarning at runtime.
32
+ emitter.setMaxListeners(0);
33
+ return { emitter, subscriptions: new Map() };
34
+ });
35
+ }
36
+
37
+ export function subscribe(event: string, handler: Handler): string {
38
+ if (typeof event !== "string" || !event) {
39
+ throw new Error("subscribe: event name is required");
40
+ }
41
+ if (typeof handler !== "function") {
42
+ throw new Error("subscribe: handler must be a function");
43
+ }
44
+ const bus = getBus();
45
+ const id = randomUUID();
46
+ bus.subscriptions.set(id, { event, handler });
47
+ bus.emitter.on(event, handler);
48
+ return id;
49
+ }
50
+
51
+ export function unsubscribe(id: string): boolean {
52
+ const bus = getBus();
53
+ const sub = bus.subscriptions.get(id);
54
+ if (!sub) return false;
55
+ bus.emitter.off(sub.event, sub.handler);
56
+ bus.subscriptions.delete(id);
57
+ return true;
58
+ }
59
+
60
+ export function emit(
61
+ event: string,
62
+ payload: unknown,
63
+ meta?: Partial<EventMeta>,
64
+ ): void {
65
+ if (typeof event !== "string" || !event) {
66
+ throw new Error("emit: event name is required");
67
+ }
68
+ const bus = getBus();
69
+ const def = getEvent(event);
70
+
71
+ let validated: unknown = payload;
72
+ if (def) {
73
+ const result = def.payloadSchema["~standard"].validate(payload);
74
+ if (result instanceof Promise) {
75
+ console.warn(
76
+ `[event-bus] Payload schema for "${event}" returned a Promise — ` +
77
+ `async validation is not supported. Dispatching unvalidated payload.`,
78
+ );
79
+ } else if (result.issues) {
80
+ console.warn(
81
+ `[event-bus] Payload validation failed for "${event}":`,
82
+ result.issues,
83
+ );
84
+ return;
85
+ } else {
86
+ validated = (result as { value: unknown }).value;
87
+ }
88
+ } else {
89
+ console.warn(
90
+ `[event-bus] Emitting unregistered event "${event}". ` +
91
+ `Call registerEvent() to declare it.`,
92
+ );
93
+ }
94
+
95
+ const fullMeta: EventMeta = {
96
+ eventId: meta?.eventId ?? randomUUID(),
97
+ emittedAt: meta?.emittedAt ?? new Date().toISOString(),
98
+ owner: meta?.owner,
99
+ };
100
+
101
+ // Snapshot listeners so a handler that subscribes/unsubscribes during
102
+ // dispatch doesn't perturb this emission.
103
+ const listeners = bus.emitter.listeners(event) as Handler[];
104
+ for (const listener of listeners) {
105
+ try {
106
+ const r = listener(validated, fullMeta);
107
+ if (r && typeof (r as Promise<void>).catch === "function") {
108
+ (r as Promise<void>).catch((err) => {
109
+ console.error(
110
+ `[event-bus] Async handler for "${event}" rejected:`,
111
+ err,
112
+ );
113
+ });
114
+ }
115
+ } catch (err) {
116
+ console.error(`[event-bus] Handler for "${event}" threw:`, err);
117
+ }
118
+ }
119
+ }
120
+
121
+ export function listSubscriptions(
122
+ event?: string,
123
+ ): { id: string; event: string }[] {
124
+ const bus = getBus();
125
+ const out: { id: string; event: string }[] = [];
126
+ for (const [id, sub] of bus.subscriptions) {
127
+ if (event && sub.event !== event) continue;
128
+ out.push({ id, event: sub.event });
129
+ }
130
+ return out;
131
+ }
132
+
133
+ /** Test helper — drops all subscriptions. */
134
+ export function __resetEventBus(): void {
135
+ const bus = getBus();
136
+ bus.emitter.removeAllListeners();
137
+ bus.subscriptions.clear();
138
+ }
@@ -1,79 +1,81 @@
1
- /**
2
- * In-process registry of event definitions.
3
- *
4
- * Integrations and templates call `registerEvent()` at module load to declare
5
- * the event types they emit. The bus uses these definitions to validate
6
- * payloads, and the Automations UI lists them so users can build triggers.
7
- */
8
-
9
- import { z } from "zod";
10
-
11
- import type { EventDefinition } from "./types.js";
12
-
13
- // Pin to globalThis so multiple ESM graphs (dev-mode Vite + Nitro, symlinks,
14
- // dist/ vs src/) share a single registry. Same pattern as secrets/register.ts.
15
- const REGISTRY_KEY = Symbol.for("@agent-native/core/event-bus.registry");
16
- interface GlobalWithRegistry {
17
- [REGISTRY_KEY]?: Map<string, EventDefinition>;
18
- }
19
- const registry: Map<string, EventDefinition> = ((
20
- globalThis as unknown as GlobalWithRegistry
21
- )[REGISTRY_KEY] ??= new Map());
22
-
23
- /**
24
- * Register (or replace) an event definition.
25
- *
26
- * Subsequent registrations with the same `name` replace the previous
27
- * definition — later plugins can override built-in defaults.
28
- */
29
- export function registerEvent(def: EventDefinition): void {
30
- if (!def || typeof def.name !== "string" || !def.name) {
31
- throw new Error("registerEvent: def.name is required");
32
- }
33
- if (typeof def.description !== "string" || !def.description) {
34
- throw new Error("registerEvent: def.description is required");
35
- }
36
- if (!def.payloadSchema) {
37
- throw new Error("registerEvent: def.payloadSchema is required");
38
- }
39
- registry.set(def.name, def);
40
- }
41
-
42
- /** Return all registered events in registration order. */
43
- export function listEvents(): EventDefinition[] {
44
- return Array.from(registry.values());
45
- }
46
-
47
- /** Look up a single registered event by name. */
48
- export function getEvent(name: string): EventDefinition | undefined {
49
- return registry.get(name);
50
- }
51
-
52
- /** Test helper — clears the registry between runs. */
53
- export function __resetEventRegistry(): void {
54
- registry.clear();
55
- registerBuiltInEvents();
56
- }
57
-
58
- function registerBuiltInEvents(): void {
59
- registerEvent({
60
- name: "test.event.fired",
61
- description:
62
- "Developer test eventfired manually from the Automations UI or via the test-event action.",
63
- payloadSchema: z
64
- .object({ data: z.record(z.string(), z.unknown()).optional() })
65
- .optional() as unknown as EventDefinition["payloadSchema"],
66
- });
67
-
68
- registerEvent({
69
- name: "agent.turn.completed",
70
- description: "Fires after the agent completes a conversational turn.",
71
- payloadSchema: z.object({
72
- threadId: z.string().optional(),
73
- turnIndex: z.number().optional(),
74
- model: z.string().optional(),
75
- }) as unknown as EventDefinition["payloadSchema"],
76
- });
77
- }
78
-
79
- registerBuiltInEvents();
1
+ /**
2
+ * In-process registry of event definitions.
3
+ *
4
+ * Integrations and templates call `registerEvent()` at module load to declare
5
+ * the event types they emit. The bus uses these definitions to validate
6
+ * payloads, and the Automations UI lists them so users can build triggers.
7
+ */
8
+
9
+ import { z } from "zod";
10
+
11
+ import { getScopedGlobal } from "../shared/global-scope.js";
12
+ import type { EventDefinition } from "./types.js";
13
+
14
+ // Pin to globalThis so multiple ESM graphs (dev-mode Vite + Nitro, symlinks,
15
+ // dist/ vs src/) share a single registry. Same pattern as secrets/register.ts.
16
+ // Scope-aware + lazily resolved so unified workspace deployments (all apps in
17
+ // one isolate) keep per-app event definitions. See shared/global-scope.
18
+ function getEventRegistry(): Map<string, EventDefinition> {
19
+ return getScopedGlobal(
20
+ "agent-native.event-bus.registry",
21
+ () => new Map<string, EventDefinition>(),
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Register (or replace) an event definition.
27
+ *
28
+ * Subsequent registrations with the same `name` replace the previous
29
+ * definition later plugins can override built-in defaults.
30
+ */
31
+ export function registerEvent(def: EventDefinition): void {
32
+ if (!def || typeof def.name !== "string" || !def.name) {
33
+ throw new Error("registerEvent: def.name is required");
34
+ }
35
+ if (typeof def.description !== "string" || !def.description) {
36
+ throw new Error("registerEvent: def.description is required");
37
+ }
38
+ if (!def.payloadSchema) {
39
+ throw new Error("registerEvent: def.payloadSchema is required");
40
+ }
41
+ getEventRegistry().set(def.name, def);
42
+ }
43
+
44
+ /** Return all registered events in registration order. */
45
+ export function listEvents(): EventDefinition[] {
46
+ return Array.from(getEventRegistry().values());
47
+ }
48
+
49
+ /** Look up a single registered event by name. */
50
+ export function getEvent(name: string): EventDefinition | undefined {
51
+ return getEventRegistry().get(name);
52
+ }
53
+
54
+ /** Test helper — clears the registry between runs. */
55
+ export function __resetEventRegistry(): void {
56
+ getEventRegistry().clear();
57
+ registerBuiltInEvents();
58
+ }
59
+
60
+ function registerBuiltInEvents(): void {
61
+ registerEvent({
62
+ name: "test.event.fired",
63
+ description:
64
+ "Developer test event — fired manually from the Automations UI or via the test-event action.",
65
+ payloadSchema: z
66
+ .object({ data: z.record(z.string(), z.unknown()).optional() })
67
+ .optional() as unknown as EventDefinition["payloadSchema"],
68
+ });
69
+
70
+ registerEvent({
71
+ name: "agent.turn.completed",
72
+ description: "Fires after the agent completes a conversational turn.",
73
+ payloadSchema: z.object({
74
+ threadId: z.string().optional(),
75
+ turnIndex: z.number().optional(),
76
+ model: z.string().optional(),
77
+ }) as unknown as EventDefinition["payloadSchema"],
78
+ });
79
+ }
80
+
81
+ registerBuiltInEvents();