@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 +1 @@
1
- {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/secrets/register.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,KAAK,CAAC;AACvD,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,CACE,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,eAAe,GAAG,OAAO,CAAC,GAAG,eAAe,GAAG,OAAO,CAAC;CACnE;AAED,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,KAAK,EAAE,WAAW,CAAC;IACnB,0EAA0E;IAC1E,IAAI,EAAE,UAAU,CAAC;IACjB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAgBD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAqCrE;AAED,2DAA2D;AAC3D,wBAAgB,mBAAmB,IAAI,gBAAgB,EAAE,CAExD;AAED,iDAAiD;AACjD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE3E;AAED,sDAAsD;AACtD,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/secrets/register.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,KAAK,CAAC;AACvD,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,CACE,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,eAAe,GAAG,OAAO,CAAC,GAAG,eAAe,GAAG,OAAO,CAAC;CACnE;AAED,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,KAAK,EAAE,WAAW,CAAC;IACnB,0EAA0E;IAC1E,IAAI,EAAE,UAAU,CAAC;IACjB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAiBD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAqCrE;AAED,2DAA2D;AAC3D,wBAAgB,mBAAmB,IAAI,gBAAgB,EAAE,CAExD;AAED,iDAAiD;AACjD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE3E;AAED,sDAAsD;AACtD,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
@@ -6,14 +6,18 @@
6
6
  * read from this registry on every request so overrides and late-registered
7
7
  * secrets are picked up without a restart.
8
8
  */
9
+ import { getScopedGlobal } from "../shared/global-scope.js";
9
10
  // Pin the registry to globalThis so templates that load `@agent-native/core`
10
11
  // via more than one ESM graph (e.g. dev-mode Vite + Nitro, symlinked
11
12
  // node_modules, dist/ vs src/) share a single registry. Without this, a
12
13
  // template's `register-secrets.ts` side-effect module may populate one
13
14
  // registry instance while the /_agent-native/secrets route reads from
14
15
  // another — net effect: the UI sees an empty list.
15
- const REGISTRY_KEY = Symbol.for("@agent-native/core/secrets.registry");
16
- const registry = (globalThis[REGISTRY_KEY] ??= new Map());
16
+ // Scope-aware + lazily resolved so unified workspace deployments (all apps in
17
+ // one isolate) keep per-app secret registrations. See shared/global-scope.
18
+ function getSecretsRegistry() {
19
+ return getScopedGlobal("agent-native.secrets.registry", () => new Map());
20
+ }
17
21
  /**
18
22
  * Register (or override) a required secret.
19
23
  *
@@ -32,10 +36,10 @@ export function registerRequiredSecret(secret) {
32
36
  if (secret.kind !== "api-key" && secret.kind !== "oauth") {
33
37
  throw new Error(`registerRequiredSecret: secret.kind must be "api-key" or "oauth" (got "${secret.kind}")`);
34
38
  }
35
- if (registry.has(secret.key) && process.env.DEBUG) {
39
+ if (getSecretsRegistry().has(secret.key) && process.env.DEBUG) {
36
40
  console.log(`[agent-native] Overriding registered secret "${secret.key}" with new registration.`);
37
41
  }
38
- registry.set(secret.key, secret);
42
+ getSecretsRegistry().set(secret.key, secret);
39
43
  // Auto-inject an onboarding step for required secrets. Done via dynamic
40
44
  // import to avoid a load-order cycle between register and the onboarding
41
45
  // registry during module bootstrap.
@@ -51,14 +55,14 @@ export function registerRequiredSecret(secret) {
51
55
  }
52
56
  /** Return all registered secrets in registration order. */
53
57
  export function listRequiredSecrets() {
54
- return Array.from(registry.values());
58
+ return Array.from(getSecretsRegistry().values());
55
59
  }
56
60
  /** Look up a single registered secret by key. */
57
61
  export function getRequiredSecret(key) {
58
- return registry.get(key);
62
+ return getSecretsRegistry().get(key);
59
63
  }
60
64
  /** Test helper — clears the registry between runs. */
61
65
  export function __resetSecretsRegistry() {
62
- registry.clear();
66
+ getSecretsRegistry().clear();
63
67
  }
64
68
  //# sourceMappingURL=register.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/secrets/register.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiDH,6EAA6E;AAC7E,qEAAqE;AACrE,wEAAwE;AACxE,uEAAuE;AACvE,sEAAsE;AACtE,mDAAmD;AACnD,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;AAIvE,MAAM,QAAQ,GAAkC,CAC9C,UACD,CAAC,YAAY,CAAC,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC;AAE/B;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAwB;IAC7D,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,IACE,MAAM,CAAC,KAAK,KAAK,MAAM;QACvB,MAAM,CAAC,KAAK,KAAK,WAAW;QAC5B,MAAM,CAAC,KAAK,KAAK,KAAK,EACtB,CAAC;QACD,MAAM,IAAI,KAAK,CACb,oFAAoF,MAAM,CAAC,KAAK,IAAI,CACrG,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CACb,0EAA0E,MAAM,CAAC,IAAI,IAAI,CAC1F,CAAC;IACJ,CAAC;IACD,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QAClD,OAAO,CAAC,GAAG,CACT,gDAAgD,MAAM,CAAC,GAAG,0BAA0B,CACrF,CAAC;IACJ,CAAC;IACD,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAEjC,wEAAwE;IACxE,yEAAyE;IACzE,oCAAoC;IACpC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,yEAAyE;QACzE,kEAAkE;QAClE,MAAM,CAAC,iBAAiB,CAAC;aACtB,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,iCAAiC,CAAC,MAAM,CAAC,CAAC;aAC5D,KAAK,CAAC,GAAG,EAAE;YACV,4DAA4D;QAC9D,CAAC,CAAC,CAAC;IACP,CAAC;AACH,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,mBAAmB;IACjC,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;AACvC,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,sBAAsB;IACpC,QAAQ,CAAC,KAAK,EAAE,CAAC;AACnB,CAAC","sourcesContent":["/**\r\n * In-process registry of required / optional secrets.\r\n *\r\n * Templates call `registerRequiredSecret()` at module load time — typically\r\n * from a server plugin. The secrets HTTP routes and the sidebar settings UI\r\n * read from this registry on every request so overrides and late-registered\r\n * secrets are picked up without a restart.\r\n */\r\n\r\nexport type SecretScope = \"user\" | \"workspace\" | \"org\";\r\nexport type SecretKind = \"api-key\" | \"oauth\";\r\n\r\nexport interface ValidatorResult {\r\n ok: boolean;\r\n error?: string;\r\n}\r\n\r\nexport interface SecretValidator {\r\n (\r\n value: string,\r\n ): Promise<ValidatorResult | boolean> | ValidatorResult | boolean;\r\n}\r\n\r\nexport interface RegisteredSecret {\r\n /** Env var name & settings key — e.g. \"OPENAI_API_KEY\". */\r\n key: string;\r\n /** Human-readable label shown in the sidebar. */\r\n label: string;\r\n /** Short description shown below the label. */\r\n description?: string;\r\n /** URL where the user can obtain the key or connect the account. */\r\n docsUrl?: string;\r\n /** Whether the secret is per-user or shared across a workspace/org. */\r\n scope: SecretScope;\r\n /** UI affordance: \"api-key\" renders an input; \"oauth\" renders Connect. */\r\n kind: SecretKind;\r\n /** When true, an onboarding step is auto-injected for this secret. */\r\n required?: boolean;\r\n /**\r\n * Optional health check. Receives the plain-text value, returns `true` or\r\n * `{ ok: true }` on success. Returning `{ ok: false, error }` surfaces the\r\n * error to the UI. Never log the value from inside the validator.\r\n */\r\n validator?: SecretValidator;\r\n /**\r\n * For `kind: \"oauth\"` — the oauth-tokens provider id (e.g. \"google\") that\r\n * backs this registration. Used to surface OAuth status in the unified UI.\r\n */\r\n oauthProvider?: string;\r\n /**\r\n * For `kind: \"oauth\"` — URL the Connect button should point at. Typically\r\n * the framework's `/_agent-native/google/auth-url` or similar.\r\n */\r\n oauthConnectUrl?: string;\r\n}\r\n\r\n// Pin the registry to globalThis so templates that load `@agent-native/core`\r\n// via more than one ESM graph (e.g. dev-mode Vite + Nitro, symlinked\r\n// node_modules, dist/ vs src/) share a single registry. Without this, a\r\n// template's `register-secrets.ts` side-effect module may populate one\r\n// registry instance while the /_agent-native/secrets route reads from\r\n// another — net effect: the UI sees an empty list.\r\nconst REGISTRY_KEY = Symbol.for(\"@agent-native/core/secrets.registry\");\r\ninterface GlobalWithRegistry {\r\n [REGISTRY_KEY]?: Map<string, RegisteredSecret>;\r\n}\r\nconst registry: Map<string, RegisteredSecret> = ((\r\n globalThis as unknown as GlobalWithRegistry\r\n)[REGISTRY_KEY] ??= new Map());\r\n\r\n/**\r\n * Register (or override) a required secret.\r\n *\r\n * Subsequent registrations with the same `key` replace the previous\r\n * definition — later plugins can override framework defaults.\r\n */\r\nexport function registerRequiredSecret(secret: RegisteredSecret): void {\r\n if (!secret || typeof secret.key !== \"string\" || !secret.key) {\r\n throw new Error(\"registerRequiredSecret: secret.key is required\");\r\n }\r\n if (\r\n secret.scope !== \"user\" &&\r\n secret.scope !== \"workspace\" &&\r\n secret.scope !== \"org\"\r\n ) {\r\n throw new Error(\r\n `registerRequiredSecret: secret.scope must be \"user\", \"workspace\", or \"org\" (got \"${secret.scope}\")`,\r\n );\r\n }\r\n if (secret.kind !== \"api-key\" && secret.kind !== \"oauth\") {\r\n throw new Error(\r\n `registerRequiredSecret: secret.kind must be \"api-key\" or \"oauth\" (got \"${secret.kind}\")`,\r\n );\r\n }\r\n if (registry.has(secret.key) && process.env.DEBUG) {\r\n console.log(\r\n `[agent-native] Overriding registered secret \"${secret.key}\" with new registration.`,\r\n );\r\n }\r\n registry.set(secret.key, secret);\r\n\r\n // Auto-inject an onboarding step for required secrets. Done via dynamic\r\n // import to avoid a load-order cycle between register and the onboarding\r\n // registry during module bootstrap.\r\n if (secret.required) {\r\n // Lazy import — resolved synchronously in practice because the module is\r\n // already loaded once any route handler runs, but tolerate async.\r\n import(\"./onboarding.js\")\r\n .then((mod) => mod.maybeRegisterSecretOnboardingStep(secret))\r\n .catch(() => {\r\n // Onboarding is optional — never let it block registration.\r\n });\r\n }\r\n}\r\n\r\n/** Return all registered secrets in registration order. */\r\nexport function listRequiredSecrets(): RegisteredSecret[] {\r\n return Array.from(registry.values());\r\n}\r\n\r\n/** Look up a single registered secret by key. */\r\nexport function getRequiredSecret(key: string): RegisteredSecret | undefined {\r\n return registry.get(key);\r\n}\r\n\r\n/** Test helper — clears the registry between runs. */\r\nexport function __resetSecretsRegistry(): void {\r\n registry.clear();\r\n}\r\n"]}
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/secrets/register.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAiD5D,6EAA6E;AAC7E,qEAAqE;AACrE,wEAAwE;AACxE,uEAAuE;AACvE,sEAAsE;AACtE,mDAAmD;AACnD,8EAA8E;AAC9E,2EAA2E;AAC3E,SAAS,kBAAkB;IACzB,OAAO,eAAe,CACpB,+BAA+B,EAC/B,GAAG,EAAE,CAAC,IAAI,GAAG,EAA4B,CAC1C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAwB;IAC7D,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,IACE,MAAM,CAAC,KAAK,KAAK,MAAM;QACvB,MAAM,CAAC,KAAK,KAAK,WAAW;QAC5B,MAAM,CAAC,KAAK,KAAK,KAAK,EACtB,CAAC;QACD,MAAM,IAAI,KAAK,CACb,oFAAoF,MAAM,CAAC,KAAK,IAAI,CACrG,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CACb,0EAA0E,MAAM,CAAC,IAAI,IAAI,CAC1F,CAAC;IACJ,CAAC;IACD,IAAI,kBAAkB,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QAC9D,OAAO,CAAC,GAAG,CACT,gDAAgD,MAAM,CAAC,GAAG,0BAA0B,CACrF,CAAC;IACJ,CAAC;IACD,kBAAkB,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAE7C,wEAAwE;IACxE,yEAAyE;IACzE,oCAAoC;IACpC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,yEAAyE;QACzE,kEAAkE;QAClE,MAAM,CAAC,iBAAiB,CAAC;aACtB,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,iCAAiC,CAAC,MAAM,CAAC,CAAC;aAC5D,KAAK,CAAC,GAAG,EAAE;YACV,4DAA4D;QAC9D,CAAC,CAAC,CAAC;IACP,CAAC;AACH,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,mBAAmB;IACjC,OAAO,KAAK,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,OAAO,kBAAkB,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACvC,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,sBAAsB;IACpC,kBAAkB,EAAE,CAAC,KAAK,EAAE,CAAC;AAC/B,CAAC","sourcesContent":["/**\n * In-process registry of required / optional secrets.\n *\n * Templates call `registerRequiredSecret()` at module load time — typically\n * from a server plugin. The secrets HTTP routes and the sidebar settings UI\n * read from this registry on every request so overrides and late-registered\n * secrets are picked up without a restart.\n */\n\nimport { getScopedGlobal } from \"../shared/global-scope.js\";\n\nexport type SecretScope = \"user\" | \"workspace\" | \"org\";\nexport type SecretKind = \"api-key\" | \"oauth\";\n\nexport interface ValidatorResult {\n ok: boolean;\n error?: string;\n}\n\nexport interface SecretValidator {\n (\n value: string,\n ): Promise<ValidatorResult | boolean> | ValidatorResult | boolean;\n}\n\nexport interface RegisteredSecret {\n /** Env var name & settings key — e.g. \"OPENAI_API_KEY\". */\n key: string;\n /** Human-readable label shown in the sidebar. */\n label: string;\n /** Short description shown below the label. */\n description?: string;\n /** URL where the user can obtain the key or connect the account. */\n docsUrl?: string;\n /** Whether the secret is per-user or shared across a workspace/org. */\n scope: SecretScope;\n /** UI affordance: \"api-key\" renders an input; \"oauth\" renders Connect. */\n kind: SecretKind;\n /** When true, an onboarding step is auto-injected for this secret. */\n required?: boolean;\n /**\n * Optional health check. Receives the plain-text value, returns `true` or\n * `{ ok: true }` on success. Returning `{ ok: false, error }` surfaces the\n * error to the UI. Never log the value from inside the validator.\n */\n validator?: SecretValidator;\n /**\n * For `kind: \"oauth\"` — the oauth-tokens provider id (e.g. \"google\") that\n * backs this registration. Used to surface OAuth status in the unified UI.\n */\n oauthProvider?: string;\n /**\n * For `kind: \"oauth\"` — URL the Connect button should point at. Typically\n * the framework's `/_agent-native/google/auth-url` or similar.\n */\n oauthConnectUrl?: string;\n}\n\n// Pin the registry to globalThis so templates that load `@agent-native/core`\n// via more than one ESM graph (e.g. dev-mode Vite + Nitro, symlinked\n// node_modules, dist/ vs src/) share a single registry. Without this, a\n// template's `register-secrets.ts` side-effect module may populate one\n// registry instance while the /_agent-native/secrets route reads from\n// another — net effect: the UI sees an empty list.\n// Scope-aware + lazily resolved so unified workspace deployments (all apps in\n// one isolate) keep per-app secret registrations. See shared/global-scope.\nfunction getSecretsRegistry(): Map<string, RegisteredSecret> {\n return getScopedGlobal(\n \"agent-native.secrets.registry\",\n () => new Map<string, RegisteredSecret>(),\n );\n}\n\n/**\n * Register (or override) a required secret.\n *\n * Subsequent registrations with the same `key` replace the previous\n * definition — later plugins can override framework defaults.\n */\nexport function registerRequiredSecret(secret: RegisteredSecret): void {\n if (!secret || typeof secret.key !== \"string\" || !secret.key) {\n throw new Error(\"registerRequiredSecret: secret.key is required\");\n }\n if (\n secret.scope !== \"user\" &&\n secret.scope !== \"workspace\" &&\n secret.scope !== \"org\"\n ) {\n throw new Error(\n `registerRequiredSecret: secret.scope must be \"user\", \"workspace\", or \"org\" (got \"${secret.scope}\")`,\n );\n }\n if (secret.kind !== \"api-key\" && secret.kind !== \"oauth\") {\n throw new Error(\n `registerRequiredSecret: secret.kind must be \"api-key\" or \"oauth\" (got \"${secret.kind}\")`,\n );\n }\n if (getSecretsRegistry().has(secret.key) && process.env.DEBUG) {\n console.log(\n `[agent-native] Overriding registered secret \"${secret.key}\" with new registration.`,\n );\n }\n getSecretsRegistry().set(secret.key, secret);\n\n // Auto-inject an onboarding step for required secrets. Done via dynamic\n // import to avoid a load-order cycle between register and the onboarding\n // registry during module bootstrap.\n if (secret.required) {\n // Lazy import — resolved synchronously in practice because the module is\n // already loaded once any route handler runs, but tolerate async.\n import(\"./onboarding.js\")\n .then((mod) => mod.maybeRegisterSecretOnboardingStep(secret))\n .catch(() => {\n // Onboarding is optional — never let it block registration.\n });\n }\n}\n\n/** Return all registered secrets in registration order. */\nexport function listRequiredSecrets(): RegisteredSecret[] {\n return Array.from(getSecretsRegistry().values());\n}\n\n/** Look up a single registered secret by key. */\nexport function getRequiredSecret(key: string): RegisteredSecret | undefined {\n return getSecretsRegistry().get(key);\n}\n\n/** Test helper — clears the registry between runs. */\nexport function __resetSecretsRegistry(): void {\n getSecretsRegistry().clear();\n}\n"]}
@@ -34,37 +34,37 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
34
34
  /** POST /_agent-native/secrets/:key — write a secret. */
35
35
  export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
36
36
  error: string;
37
- status?: undefined;
38
37
  ok?: undefined;
38
+ status?: undefined;
39
39
  } | {
40
+ error?: undefined;
40
41
  ok: boolean;
41
42
  status: string;
42
- error?: undefined;
43
43
  } | {
44
+ ok?: undefined;
44
45
  error: string;
45
46
  removed?: undefined;
46
- ok?: undefined;
47
47
  } | {
48
+ error?: undefined;
48
49
  ok: boolean;
49
50
  removed: boolean;
50
- error?: undefined;
51
51
  }>>;
52
52
  /**
53
53
  * POST /_agent-native/secrets/:key/test — re-run the validator against the
54
54
  * current stored value without changing anything. Useful for the "Test" button.
55
55
  */
56
56
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
57
+ ok?: undefined;
57
58
  error: string;
58
59
  note?: undefined;
59
- ok?: undefined;
60
60
  } | {
61
+ error?: undefined;
61
62
  ok: boolean;
62
63
  note?: undefined;
63
- error?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  ok: boolean;
66
67
  note: string;
67
- error?: undefined;
68
68
  } | {
69
69
  note?: undefined;
70
70
  ok: boolean;
@@ -95,12 +95,12 @@ export interface AdHocSecretPayload {
95
95
  export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
96
96
  error: string;
97
97
  } | {
98
+ error?: undefined;
98
99
  ok: boolean;
99
100
  key: string;
100
- error?: undefined;
101
101
  } | {
102
+ error?: undefined;
102
103
  ok: boolean;
103
104
  removed: boolean;
104
- error?: undefined;
105
105
  }>>;
106
106
  //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Per-module-graph global scope for globalThis-pinned framework registries.
3
+ *
4
+ * Why this exists: several framework registries (file-upload providers,
5
+ * private-blob providers, shareable resources, event bus, notification
6
+ * channels, tracking providers, secrets registry) pin their state on
7
+ * `globalThis` so that multiple ESM graphs of ONE app (Vite dev + Nitro,
8
+ * symlinked node_modules, dist/ vs src/) still share a single registry.
9
+ *
10
+ * On a unified workspace deployment (Cloudflare Pages `_worker.js` dispatcher
11
+ * importing every app's worker into ONE isolate) that pinning goes too far:
12
+ * each app's bundle keeps its own module graph — so module-scope state is
13
+ * correctly per-app — but the `globalThis` pin collapses all apps' registries
14
+ * into one shared map. Real-world failure: an upload POSTed to /assets was
15
+ * served by the clips app's registered S3 provider (wrong object prefix).
16
+ *
17
+ * The fix: each app's generated worker entry calls `setGlobalScopeId(appId)`
18
+ * from a scope-init module evaluated FIRST in the entry's import graph (ESM
19
+ * evaluates imports depth-first in declaration order, so the scope is set
20
+ * before any registry module initializes or registers built-ins). Registries
21
+ * resolve their `globalThis` key LAZILY through `getScopedGlobal`, which
22
+ * namespaces the key by the module graph's scope id. Result:
23
+ *
24
+ * - Unified worker: each app's core copy has its own scope id → per-app keys
25
+ * → per-app registries. Cross-app state sharing is gone.
26
+ * - Dev / single-app deployments: `setGlobalScopeId` is never called → keys
27
+ * are unscoped → the original multi-graph dedupe behavior is preserved.
28
+ *
29
+ * This module must stay dependency-free: it is imported by the generated
30
+ * scope-init module before anything else in the app bundle evaluates.
31
+ */
32
+ /**
33
+ * Set the global-registry scope for THIS module graph (this app's bundle).
34
+ * Called by the generated worker entry's scope-init module on unified
35
+ * workspace deployments. Pass `null` to clear (tests).
36
+ */
37
+ export declare function setGlobalScopeId(id: string | null): void;
38
+ /** The active scope id for this module graph, or `null` when unscoped. */
39
+ export declare function getGlobalScopeId(): string | null;
40
+ /**
41
+ * The fully-qualified global key name for `base` under the active scope.
42
+ * Unscoped graphs get `base` unchanged, so existing dev-mode behavior
43
+ * (one registry across all of an app's ESM graphs) is preserved.
44
+ */
45
+ export declare function scopedGlobalKeyName(base: string): string;
46
+ /**
47
+ * Lazily resolve (and initialize once) a globalThis-pinned singleton under
48
+ * the scope-aware key for `base`. Registries MUST call this per access —
49
+ * never capture the result in module scope — so a scope id set during
50
+ * entry-module evaluation is honored by every later registration and read.
51
+ */
52
+ export declare function getScopedGlobal<T>(base: string, init: () => T): T;
53
+ /** Test helper — delete the pinned singleton for `base` in the ACTIVE scope. */
54
+ export declare function __deleteScopedGlobal(base: string): void;
55
+ //# sourceMappingURL=global-scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-scope.d.ts","sourceRoot":"","sources":["../../src/shared/global-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAGxD;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAIjE;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAGvD"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Per-module-graph global scope for globalThis-pinned framework registries.
3
+ *
4
+ * Why this exists: several framework registries (file-upload providers,
5
+ * private-blob providers, shareable resources, event bus, notification
6
+ * channels, tracking providers, secrets registry) pin their state on
7
+ * `globalThis` so that multiple ESM graphs of ONE app (Vite dev + Nitro,
8
+ * symlinked node_modules, dist/ vs src/) still share a single registry.
9
+ *
10
+ * On a unified workspace deployment (Cloudflare Pages `_worker.js` dispatcher
11
+ * importing every app's worker into ONE isolate) that pinning goes too far:
12
+ * each app's bundle keeps its own module graph — so module-scope state is
13
+ * correctly per-app — but the `globalThis` pin collapses all apps' registries
14
+ * into one shared map. Real-world failure: an upload POSTed to /assets was
15
+ * served by the clips app's registered S3 provider (wrong object prefix).
16
+ *
17
+ * The fix: each app's generated worker entry calls `setGlobalScopeId(appId)`
18
+ * from a scope-init module evaluated FIRST in the entry's import graph (ESM
19
+ * evaluates imports depth-first in declaration order, so the scope is set
20
+ * before any registry module initializes or registers built-ins). Registries
21
+ * resolve their `globalThis` key LAZILY through `getScopedGlobal`, which
22
+ * namespaces the key by the module graph's scope id. Result:
23
+ *
24
+ * - Unified worker: each app's core copy has its own scope id → per-app keys
25
+ * → per-app registries. Cross-app state sharing is gone.
26
+ * - Dev / single-app deployments: `setGlobalScopeId` is never called → keys
27
+ * are unscoped → the original multi-graph dedupe behavior is preserved.
28
+ *
29
+ * This module must stay dependency-free: it is imported by the generated
30
+ * scope-init module before anything else in the app bundle evaluates.
31
+ */
32
+ let moduleGraphScopeId = null;
33
+ /**
34
+ * Set the global-registry scope for THIS module graph (this app's bundle).
35
+ * Called by the generated worker entry's scope-init module on unified
36
+ * workspace deployments. Pass `null` to clear (tests).
37
+ */
38
+ export function setGlobalScopeId(id) {
39
+ const trimmed = typeof id === "string" ? id.trim() : "";
40
+ moduleGraphScopeId = trimmed ? trimmed : null;
41
+ }
42
+ /** The active scope id for this module graph, or `null` when unscoped. */
43
+ export function getGlobalScopeId() {
44
+ return moduleGraphScopeId;
45
+ }
46
+ /**
47
+ * The fully-qualified global key name for `base` under the active scope.
48
+ * Unscoped graphs get `base` unchanged, so existing dev-mode behavior
49
+ * (one registry across all of an app's ESM graphs) is preserved.
50
+ */
51
+ export function scopedGlobalKeyName(base) {
52
+ return moduleGraphScopeId ? `${base}::app:${moduleGraphScopeId}` : base;
53
+ }
54
+ /**
55
+ * Lazily resolve (and initialize once) a globalThis-pinned singleton under
56
+ * the scope-aware key for `base`. Registries MUST call this per access —
57
+ * never capture the result in module scope — so a scope id set during
58
+ * entry-module evaluation is honored by every later registration and read.
59
+ */
60
+ export function getScopedGlobal(base, init) {
61
+ const key = Symbol.for(scopedGlobalKeyName(base));
62
+ const g = globalThis;
63
+ return (g[key] ??= init());
64
+ }
65
+ /** Test helper — delete the pinned singleton for `base` in the ACTIVE scope. */
66
+ export function __deleteScopedGlobal(base) {
67
+ const key = Symbol.for(scopedGlobalKeyName(base));
68
+ delete globalThis[key];
69
+ }
70
+ //# sourceMappingURL=global-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-scope.js","sourceRoot":"","sources":["../../src/shared/global-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,IAAI,kBAAkB,GAAkB,IAAI,CAAC;AAE7C;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAiB;IAChD,MAAM,OAAO,GAAG,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB;IAC9B,OAAO,kBAAkB,CAAC;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,kBAAkB,CAAC,CAAC,CAAC,GAAG,IAAI,SAAS,kBAAkB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAI,IAAY,EAAE,IAAa;IAC5D,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,MAAM,CAAC,GAAG,UAAsD,CAAC;IACjE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;IAClD,OAAQ,UAAiD,CAAC,GAAG,CAAC,CAAC;AACjE,CAAC","sourcesContent":["/**\n * Per-module-graph global scope for globalThis-pinned framework registries.\n *\n * Why this exists: several framework registries (file-upload providers,\n * private-blob providers, shareable resources, event bus, notification\n * channels, tracking providers, secrets registry) pin their state on\n * `globalThis` so that multiple ESM graphs of ONE app (Vite dev + Nitro,\n * symlinked node_modules, dist/ vs src/) still share a single registry.\n *\n * On a unified workspace deployment (Cloudflare Pages `_worker.js` dispatcher\n * importing every app's worker into ONE isolate) that pinning goes too far:\n * each app's bundle keeps its own module graph — so module-scope state is\n * correctly per-app — but the `globalThis` pin collapses all apps' registries\n * into one shared map. Real-world failure: an upload POSTed to /assets was\n * served by the clips app's registered S3 provider (wrong object prefix).\n *\n * The fix: each app's generated worker entry calls `setGlobalScopeId(appId)`\n * from a scope-init module evaluated FIRST in the entry's import graph (ESM\n * evaluates imports depth-first in declaration order, so the scope is set\n * before any registry module initializes or registers built-ins). Registries\n * resolve their `globalThis` key LAZILY through `getScopedGlobal`, which\n * namespaces the key by the module graph's scope id. Result:\n *\n * - Unified worker: each app's core copy has its own scope id → per-app keys\n * → per-app registries. Cross-app state sharing is gone.\n * - Dev / single-app deployments: `setGlobalScopeId` is never called → keys\n * are unscoped → the original multi-graph dedupe behavior is preserved.\n *\n * This module must stay dependency-free: it is imported by the generated\n * scope-init module before anything else in the app bundle evaluates.\n */\n\nlet moduleGraphScopeId: string | null = null;\n\n/**\n * Set the global-registry scope for THIS module graph (this app's bundle).\n * Called by the generated worker entry's scope-init module on unified\n * workspace deployments. Pass `null` to clear (tests).\n */\nexport function setGlobalScopeId(id: string | null): void {\n const trimmed = typeof id === \"string\" ? id.trim() : \"\";\n moduleGraphScopeId = trimmed ? trimmed : null;\n}\n\n/** The active scope id for this module graph, or `null` when unscoped. */\nexport function getGlobalScopeId(): string | null {\n return moduleGraphScopeId;\n}\n\n/**\n * The fully-qualified global key name for `base` under the active scope.\n * Unscoped graphs get `base` unchanged, so existing dev-mode behavior\n * (one registry across all of an app's ESM graphs) is preserved.\n */\nexport function scopedGlobalKeyName(base: string): string {\n return moduleGraphScopeId ? `${base}::app:${moduleGraphScopeId}` : base;\n}\n\n/**\n * Lazily resolve (and initialize once) a globalThis-pinned singleton under\n * the scope-aware key for `base`. Registries MUST call this per access —\n * never capture the result in module scope — so a scope id set during\n * entry-module evaluation is honored by every later registration and read.\n */\nexport function getScopedGlobal<T>(base: string, init: () => T): T {\n const key = Symbol.for(scopedGlobalKeyName(base));\n const g = globalThis as unknown as Record<symbol, T | undefined>;\n return (g[key] ??= init());\n}\n\n/** Test helper — delete the pinned singleton for `base` in the ACTIVE scope. */\nexport function __deleteScopedGlobal(base: string): void {\n const key = Symbol.for(scopedGlobalKeyName(base));\n delete (globalThis as unknown as Record<symbol, unknown>)[key];\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/sharing/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,6BAA6B;IAC5C,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,aAAa,EAAE,GAAG,CAAC;IACnB,qDAAqD;IACrD,WAAW,EAAE,GAAG,CAAC;IACjB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,CAAC;IACjB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;OAKG;IACH,gBAAgB,CAAC,EACb,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,CAAC,CACC,QAAQ,EAAE,GAAG,EACb,GAAG,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAEzC,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,OAAO,CAAC,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC;IAChD;;;;;;;;;;OAUG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK;QACtE,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF;;;;;;;OAOG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;;;OAMG;IACH,aAAa,CAAC,EACV,KAAK,GACL;QACE,uEAAuE;QACvE,YAAY,EAAE,MAAM,CAAC;QACrB,qEAAqE;QACrE,cAAc,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;QACtD,uEAAuE;QACvE,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;QACpD,iEAAiE;QACjE,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACP;AAuCD,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,6BAA6B,GACnC,IAAI,CAGN;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,GACX,6BAA6B,GAAG,SAAS,CAE3C;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,GACX,6BAA6B,CAS/B;AAED,wBAAgB,sBAAsB,IAAI,6BAA6B,EAAE,CAExE"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/sharing/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,MAAM,WAAW,6BAA6B;IAC5C,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,aAAa,EAAE,GAAG,CAAC;IACnB,qDAAqD;IACrD,WAAW,EAAE,GAAG,CAAC;IACjB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD;;;;OAIG;IACH,KAAK,EAAE,MAAM,GAAG,CAAC;IACjB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;OAKG;IACH,gBAAgB,CAAC,EACb,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,CAAC,CACC,QAAQ,EAAE,GAAG,EACb,GAAG,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAEzC,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,OAAO,CAAC,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC;IAChD;;;;;;;;;;OAUG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK;QACtE,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF;;;;;;;OAOG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;;;OAMG;IACH,aAAa,CAAC,EACV,KAAK,GACL;QACE,uEAAuE;QACvE,YAAY,EAAE,MAAM,CAAC;QACrB,qEAAqE;QACrE,cAAc,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;QACtD,uEAAuE;QACvE,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;QACpD,iEAAiE;QACjE,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACP;AAoCD,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,6BAA6B,GACnC,IAAI,CAGN;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,GACX,6BAA6B,GAAG,SAAS,CAE3C;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,GACX,6BAA6B,CAS/B;AAED,wBAAgB,sBAAsB,IAAI,6BAA6B,EAAE,CAExE"}
@@ -16,16 +16,7 @@
16
16
  * titleColumn: "title",
17
17
  * });
18
18
  */
19
- // Stash the registry on globalThis so it survives SSR bundle duplication.
20
- // Vite SSR's `noExternal: /^(?!node:)/` policy means @agent-native/core gets
21
- // inlined into every server bundle that imports it — and each bundle gets its
22
- // own module-level state. A plain `new Map()` here would create one Map per
23
- // bundle, so the template's `registerShareableResource()` (called from the
24
- // Nitro plugin graph) wouldn't be visible to the framework's auto-mounted
25
- // share-resource action (loaded via `import("../sharing/actions/...js")` in a
26
- // different module instance). Using globalThis collapses them back to one Map.
27
- const REGISTRY_KEY = "__agentNativeShareableResources__";
28
- const globalRegistry = globalThis;
19
+ import { getScopedGlobal } from "../shared/global-scope.js";
29
20
  function isTestRuntime() {
30
21
  return (process.env.NODE_ENV === "test" ||
31
22
  process.env.VITEST === "true" ||
@@ -36,12 +27,7 @@ function registrationCameFromTestFile() {
36
27
  return /[./\\](?:[^/\\]+[.-])(?:test|spec)\.[cm]?[jt]sx?(?::\d+)?(?::\d+)?/.test(stack);
37
28
  }
38
29
  function getRegistry() {
39
- let r = globalRegistry[REGISTRY_KEY];
40
- if (!r) {
41
- r = new Map();
42
- globalRegistry[REGISTRY_KEY] = r;
43
- }
44
- return r;
30
+ return getScopedGlobal("agent-native.sharing.shareable-resources", () => new Map());
45
31
  }
46
32
  export function registerShareableResource(entry) {
47
33
  if (!isTestRuntime() && registrationCameFromTestFile())
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/sharing/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA+GH,0EAA0E;AAC1E,6EAA6E;AAC7E,8EAA8E;AAC9E,4EAA4E;AAC5E,2EAA2E;AAC3E,0EAA0E;AAC1E,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,YAAY,GAAG,mCAAmC,CAAC;AAEzD,MAAM,cAAc,GAClB,UAAiB,CAAC;AAEpB,SAAS,aAAa;IACpB,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM;QAC/B,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM;QAC7B,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,CAC3B,CAAC;AACJ,CAAC;AAED,SAAS,4BAA4B;IACnC,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;IACtC,OAAO,oEAAoE,CAAC,IAAI,CAC9E,KAAK,CACN,CAAC;AACJ,CAAC;AAED,SAAS,WAAW;IAClB,IAAI,CAAC,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;IACrC,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,CAAC,GAAG,IAAI,GAAG,EAAyC,CAAC;QACrD,cAAc,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,KAAoC;IAEpC,IAAI,CAAC,aAAa,EAAE,IAAI,4BAA4B,EAAE;QAAE,OAAO;IAC/D,WAAW,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,IAAY;IAEZ,OAAO,WAAW,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,IAAY;IAEZ,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,qCAAqC,IAAI,gDAAgD,CAC1F,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["/**\r\n * Registry of shareable resources.\r\n *\r\n * Each template registers its ownable resource(s) once on module load so the\r\n * framework-level share actions (`share-resource`, `list-resource-shares`,\r\n * etc.) can dispatch to the correct tables.\r\n *\r\n * import { registerShareableResource } from \"@agent-native/core/sharing\";\r\n * import * as schema from \"./schema.js\";\r\n *\r\n * registerShareableResource({\r\n * type: \"document\",\r\n * resourceTable: schema.documents,\r\n * sharesTable: schema.documentShares,\r\n * displayName: \"Document\",\r\n * titleColumn: \"title\",\r\n * });\r\n */\r\n\r\nexport interface ShareableResourceRegistration {\r\n /** Stable identifier used across actions, UI, and analytics. e.g. \"document\". */\r\n type: string;\r\n /** Drizzle table for the parent resource (must have ownableColumns()). */\r\n resourceTable: any;\r\n /** Drizzle table produced by createSharesTable(). */\r\n sharesTable: any;\r\n /** Human-readable singular label shown in the share dialog. */\r\n displayName: string;\r\n /**\r\n * Column on the resource table that holds a human-readable title for\r\n * display in the share UI. Default: \"title\".\r\n */\r\n titleColumn?: string;\r\n /**\r\n * Optional app-relative path to this resource. Used by share notifications\r\n * when the caller does not pass a more specific resourceUrl.\r\n */\r\n getResourcePath?: (resource: any) => string | undefined;\r\n /**\r\n * Drizzle DB accessor from the template's server/db/index.ts. Required —\r\n * the framework-level share actions and access helpers call this to reach\r\n * the right DB instance (schema is template-specific).\r\n */\r\n getDb: () => any;\r\n /**\r\n * When `false`, `visibility: \"public\"` is rejected by `set-resource-visibility`,\r\n * and `accessFilter` / `resolveAccess` treat any stored public row as private\r\n * (defense in depth — only owner + explicit shares grant access).\r\n *\r\n * Use this for resources that execute code or expose privileged data and must\r\n * never be reachable by a random authenticated user. Extensions set this:\r\n * extension HTML runs inside an iframe that calls actions / DB / proxied APIs\r\n * as the *viewer*, so a public extension would be arbitrary code with the\r\n * viewer's credentials.\r\n *\r\n * Default: `true` (matches the historical behavior — most resources can be public).\r\n */\r\n allowPublic?: boolean;\r\n /**\r\n * Optional role granted by a public-by-link read. Most resources should omit\r\n * this so public visibility remains viewer-only. Use narrowly for local or\r\n * otherwise constrained resources where the resource owner intentionally wants\r\n * unauthenticated link holders to do more than view.\r\n */\r\n publicAccessRole?:\r\n | \"viewer\"\r\n | \"editor\"\r\n | \"admin\"\r\n | ((\r\n resource: any,\r\n ctx: { userEmail?: string; orgId?: string },\r\n ) =>\r\n | \"viewer\"\r\n | \"editor\"\r\n | \"admin\"\r\n | Promise<\"viewer\" | \"editor\" | \"admin\">);\r\n /**\r\n * When `true`, individual user shares (`principalType: \"user\"`) must target\r\n * an email that is already a member of the same org as the resource, OR has\r\n * a pending invitation to that org. Cross-org user shares are rejected.\r\n *\r\n * Pair with `allowPublic: false` for resources that need a hard \"this org\r\n * only\" trust boundary. Extensions set this so a malicious caller can't\r\n * widen reach by sharing a code-executing extension to an outsider email.\r\n *\r\n * Default: `false` (matches the historical behavior — any email can be granted).\r\n */\r\n requireOrgMemberForUserShares?: boolean;\r\n /**\r\n * Optional per-resource access-context adapter. Most resources should use the\r\n * request user/org unchanged. Templates with an intentional alternate local\r\n * identity can normalize here so the generic framework sharing actions and\r\n * access helpers stay in sync with template-owned actions.\r\n */\r\n resolveAccessContext?: (ctx: { userEmail?: string; orgId?: string }) => {\r\n userEmail?: string;\r\n orgId?: string;\r\n };\r\n /**\r\n * When true, direct ownership is recognized by owner_email regardless of the\r\n * caller's active org. Use this only for resource types where the template's\r\n * own actions already treat owner_email as the cross-org authority and list\r\n * views add their own org filters.\r\n *\r\n * Default: `false`.\r\n */\r\n ownerAccessIgnoresOrg?: boolean;\r\n /**\r\n * Optional external-agent read handoff. When set, the framework-level\r\n * `create-agent-resource-link` action can mint a short-lived, read-only\r\n * `agent_access` URL for this resource. The context endpoint is owned by the\r\n * template so it can expose the same intentionally shareable shape as the\r\n * public page, not a generic raw database row.\r\n */\r\n agentReadable?:\r\n | false\r\n | {\r\n /** Token scope. Include the app name to avoid cross-app collisions. */\r\n resourceKind: string;\r\n /** App-relative JSON endpoint that accepts `id` + `agent_access`. */\r\n getContextPath: (resource: any) => string | undefined;\r\n /** Optional override for the page URL. Defaults to getResourcePath. */\r\n getPagePath?: (resource: any) => string | undefined;\r\n /** Optional override for the default two-hour token lifetime. */\r\n ttlSeconds?: number;\r\n };\r\n}\r\n\r\n// Stash the registry on globalThis so it survives SSR bundle duplication.\r\n// Vite SSR's `noExternal: /^(?!node:)/` policy means @agent-native/core gets\r\n// inlined into every server bundle that imports it — and each bundle gets its\r\n// own module-level state. A plain `new Map()` here would create one Map per\r\n// bundle, so the template's `registerShareableResource()` (called from the\r\n// Nitro plugin graph) wouldn't be visible to the framework's auto-mounted\r\n// share-resource action (loaded via `import(\"../sharing/actions/...js\")` in a\r\n// different module instance). Using globalThis collapses them back to one Map.\r\nconst REGISTRY_KEY = \"__agentNativeShareableResources__\";\r\ntype RegistryStore = Map<string, ShareableResourceRegistration>;\r\nconst globalRegistry: { [K in typeof REGISTRY_KEY]?: RegistryStore } =\r\n globalThis as any;\r\n\r\nfunction isTestRuntime(): boolean {\r\n return (\r\n process.env.NODE_ENV === \"test\" ||\r\n process.env.VITEST === \"true\" ||\r\n process.env.VITEST === \"1\"\r\n );\r\n}\r\n\r\nfunction registrationCameFromTestFile(): boolean {\r\n const stack = new Error().stack ?? \"\";\r\n return /[./\\\\](?:[^/\\\\]+[.-])(?:test|spec)\\.[cm]?[jt]sx?(?::\\d+)?(?::\\d+)?/.test(\r\n stack,\r\n );\r\n}\r\n\r\nfunction getRegistry(): RegistryStore {\r\n let r = globalRegistry[REGISTRY_KEY];\r\n if (!r) {\r\n r = new Map<string, ShareableResourceRegistration>();\r\n globalRegistry[REGISTRY_KEY] = r;\r\n }\r\n return r;\r\n}\r\n\r\nexport function registerShareableResource(\r\n entry: ShareableResourceRegistration,\r\n): void {\r\n if (!isTestRuntime() && registrationCameFromTestFile()) return;\r\n getRegistry().set(entry.type, entry);\r\n}\r\n\r\nexport function getShareableResource(\r\n type: string,\r\n): ShareableResourceRegistration | undefined {\r\n return getRegistry().get(type);\r\n}\r\n\r\nexport function requireShareableResource(\r\n type: string,\r\n): ShareableResourceRegistration {\r\n const reg = getRegistry();\r\n const entry = reg.get(type);\r\n if (!entry) {\r\n throw new Error(\r\n `Unknown shareable resource type: \"${type}\". Did you forget registerShareableResource()?`,\r\n );\r\n }\r\n return entry;\r\n}\r\n\r\nexport function listShareableResources(): ShareableResourceRegistration[] {\r\n return Array.from(getRegistry().values());\r\n}\r\n"]}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/sharing/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AA2H5D,SAAS,aAAa;IACpB,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM;QAC/B,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM;QAC7B,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,CAC3B,CAAC;AACJ,CAAC;AAED,SAAS,4BAA4B;IACnC,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;IACtC,OAAO,oEAAoE,CAAC,IAAI,CAC9E,KAAK,CACN,CAAC;AACJ,CAAC;AAED,SAAS,WAAW;IAClB,OAAO,eAAe,CACpB,0CAA0C,EAC1C,GAAG,EAAE,CAAC,IAAI,GAAG,EAAyC,CACvD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,KAAoC;IAEpC,IAAI,CAAC,aAAa,EAAE,IAAI,4BAA4B,EAAE;QAAE,OAAO;IAC/D,WAAW,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,IAAY;IAEZ,OAAO,WAAW,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,IAAY;IAEZ,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,qCAAqC,IAAI,gDAAgD,CAC1F,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["/**\n * Registry of shareable resources.\n *\n * Each template registers its ownable resource(s) once on module load so the\n * framework-level share actions (`share-resource`, `list-resource-shares`,\n * etc.) can dispatch to the correct tables.\n *\n * import { registerShareableResource } from \"@agent-native/core/sharing\";\n * import * as schema from \"./schema.js\";\n *\n * registerShareableResource({\n * type: \"document\",\n * resourceTable: schema.documents,\n * sharesTable: schema.documentShares,\n * displayName: \"Document\",\n * titleColumn: \"title\",\n * });\n */\n\nimport { getScopedGlobal } from \"../shared/global-scope.js\";\n\nexport interface ShareableResourceRegistration {\n /** Stable identifier used across actions, UI, and analytics. e.g. \"document\". */\n type: string;\n /** Drizzle table for the parent resource (must have ownableColumns()). */\n resourceTable: any;\n /** Drizzle table produced by createSharesTable(). */\n sharesTable: any;\n /** Human-readable singular label shown in the share dialog. */\n displayName: string;\n /**\n * Column on the resource table that holds a human-readable title for\n * display in the share UI. Default: \"title\".\n */\n titleColumn?: string;\n /**\n * Optional app-relative path to this resource. Used by share notifications\n * when the caller does not pass a more specific resourceUrl.\n */\n getResourcePath?: (resource: any) => string | undefined;\n /**\n * Drizzle DB accessor from the template's server/db/index.ts. Required —\n * the framework-level share actions and access helpers call this to reach\n * the right DB instance (schema is template-specific).\n */\n getDb: () => any;\n /**\n * When `false`, `visibility: \"public\"` is rejected by `set-resource-visibility`,\n * and `accessFilter` / `resolveAccess` treat any stored public row as private\n * (defense in depth — only owner + explicit shares grant access).\n *\n * Use this for resources that execute code or expose privileged data and must\n * never be reachable by a random authenticated user. Extensions set this:\n * extension HTML runs inside an iframe that calls actions / DB / proxied APIs\n * as the *viewer*, so a public extension would be arbitrary code with the\n * viewer's credentials.\n *\n * Default: `true` (matches the historical behavior — most resources can be public).\n */\n allowPublic?: boolean;\n /**\n * Optional role granted by a public-by-link read. Most resources should omit\n * this so public visibility remains viewer-only. Use narrowly for local or\n * otherwise constrained resources where the resource owner intentionally wants\n * unauthenticated link holders to do more than view.\n */\n publicAccessRole?:\n | \"viewer\"\n | \"editor\"\n | \"admin\"\n | ((\n resource: any,\n ctx: { userEmail?: string; orgId?: string },\n ) =>\n | \"viewer\"\n | \"editor\"\n | \"admin\"\n | Promise<\"viewer\" | \"editor\" | \"admin\">);\n /**\n * When `true`, individual user shares (`principalType: \"user\"`) must target\n * an email that is already a member of the same org as the resource, OR has\n * a pending invitation to that org. Cross-org user shares are rejected.\n *\n * Pair with `allowPublic: false` for resources that need a hard \"this org\n * only\" trust boundary. Extensions set this so a malicious caller can't\n * widen reach by sharing a code-executing extension to an outsider email.\n *\n * Default: `false` (matches the historical behavior — any email can be granted).\n */\n requireOrgMemberForUserShares?: boolean;\n /**\n * Optional per-resource access-context adapter. Most resources should use the\n * request user/org unchanged. Templates with an intentional alternate local\n * identity can normalize here so the generic framework sharing actions and\n * access helpers stay in sync with template-owned actions.\n */\n resolveAccessContext?: (ctx: { userEmail?: string; orgId?: string }) => {\n userEmail?: string;\n orgId?: string;\n };\n /**\n * When true, direct ownership is recognized by owner_email regardless of the\n * caller's active org. Use this only for resource types where the template's\n * own actions already treat owner_email as the cross-org authority and list\n * views add their own org filters.\n *\n * Default: `false`.\n */\n ownerAccessIgnoresOrg?: boolean;\n /**\n * Optional external-agent read handoff. When set, the framework-level\n * `create-agent-resource-link` action can mint a short-lived, read-only\n * `agent_access` URL for this resource. The context endpoint is owned by the\n * template so it can expose the same intentionally shareable shape as the\n * public page, not a generic raw database row.\n */\n agentReadable?:\n | false\n | {\n /** Token scope. Include the app name to avoid cross-app collisions. */\n resourceKind: string;\n /** App-relative JSON endpoint that accepts `id` + `agent_access`. */\n getContextPath: (resource: any) => string | undefined;\n /** Optional override for the page URL. Defaults to getResourcePath. */\n getPagePath?: (resource: any) => string | undefined;\n /** Optional override for the default two-hour token lifetime. */\n ttlSeconds?: number;\n };\n}\n\n// Stash the registry on globalThis so it survives SSR bundle duplication.\n// Vite SSR's `noExternal: /^(?!node:)/` policy means @agent-native/core gets\n// inlined into every server bundle that imports it — and each bundle gets its\n// own module-level state. A plain `new Map()` here would create one Map per\n// bundle, so the template's `registerShareableResource()` (called from the\n// Nitro plugin graph) wouldn't be visible to the framework's auto-mounted\n// share-resource action (loaded via `import(\"../sharing/actions/...js\")` in a\n// different module instance). Using globalThis collapses them back to one Map.\n// Scope-aware + lazily resolved so unified workspace deployments (all apps in\n// one isolate) keep per-app registrations. See shared/global-scope.\ntype RegistryStore = Map<string, ShareableResourceRegistration>;\n\nfunction isTestRuntime(): boolean {\n return (\n process.env.NODE_ENV === \"test\" ||\n process.env.VITEST === \"true\" ||\n process.env.VITEST === \"1\"\n );\n}\n\nfunction registrationCameFromTestFile(): boolean {\n const stack = new Error().stack ?? \"\";\n return /[./\\\\](?:[^/\\\\]+[.-])(?:test|spec)\\.[cm]?[jt]sx?(?::\\d+)?(?::\\d+)?/.test(\n stack,\n );\n}\n\nfunction getRegistry(): RegistryStore {\n return getScopedGlobal(\n \"agent-native.sharing.shareable-resources\",\n () => new Map<string, ShareableResourceRegistration>(),\n );\n}\n\nexport function registerShareableResource(\n entry: ShareableResourceRegistration,\n): void {\n if (!isTestRuntime() && registrationCameFromTestFile()) return;\n getRegistry().set(entry.type, entry);\n}\n\nexport function getShareableResource(\n type: string,\n): ShareableResourceRegistration | undefined {\n return getRegistry().get(type);\n}\n\nexport function requireShareableResource(\n type: string,\n): ShareableResourceRegistration {\n const reg = getRegistry();\n const entry = reg.get(type);\n if (!entry) {\n throw new Error(\n `Unknown shareable resource type: \"${type}\". Did you forget registerShareableResource()?`,\n );\n }\n return entry;\n}\n\nexport function listShareableResources(): ShareableResourceRegistration[] {\n return Array.from(getRegistry().values());\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/tracking/providers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAyWH,wBAAgB,wBAAwB,IAAI,IAAI,CA+C/C"}
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/tracking/providers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA4WH,wBAAgB,wBAAwB,IAAI,IAAI,CA+C/C"}
@@ -12,6 +12,7 @@
12
12
  * Call `registerBuiltinProviders()` at server startup (done
13
13
  * automatically by the core-routes plugin).
14
14
  */
15
+ import { getScopedGlobal } from "../shared/global-scope.js";
15
16
  import { registerTrackingProvider } from "./registry.js";
16
17
  const POSTHOG_DEFAULT_HOST = "https://us.i.posthog.com";
17
18
  const AGENT_NATIVE_ANALYTICS_DEFAULT_ENDPOINT = "https://analytics.jami.studio/track";
@@ -19,20 +20,21 @@ const BATCH_INTERVAL_MS = 10_000;
19
20
  const MAX_BATCH_SIZE = 50;
20
21
  // Use globalThis so multiple ESM graph instances (Vite dev + Nitro symlinks)
21
22
  // share one queue, matching the same pattern as the tracking registry.
22
- const QUEUE_KEY = Symbol.for("@agent-native/core/tracking.queue");
23
- const TIMER_KEY = Symbol.for("@agent-native/core/tracking.timer");
23
+ // Scope-aware + lazily resolved so unified workspace deployments keep
24
+ // per-app queues/timers. See shared/global-scope.
25
+ const QUEUE_BASE_KEY = "agent-native.tracking.queue";
26
+ const TIMER_BASE_KEY = "agent-native.tracking.timer";
24
27
  function getQueue() {
25
- const g = globalThis;
26
- if (!g[QUEUE_KEY])
27
- g[QUEUE_KEY] = [];
28
- return g[QUEUE_KEY];
28
+ return getScopedGlobal(QUEUE_BASE_KEY, () => []);
29
+ }
30
+ function getTimerBox() {
31
+ return getScopedGlobal(TIMER_BASE_KEY, () => ({ timer: null }));
29
32
  }
30
33
  function getTimer() {
31
- const g = globalThis;
32
- return g[TIMER_KEY] ?? null;
34
+ return getTimerBox().timer;
33
35
  }
34
36
  function setTimer(t) {
35
- globalThis[TIMER_KEY] = t;
37
+ getTimerBox().timer = t;
36
38
  }
37
39
  function enqueue(url, body, headers, options) {
38
40
  const queue = getQueue();
@@ -1 +1 @@
1
- {"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/tracking/providers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Built-in tracking providers that auto-register from env vars.\r\n *\r\n * No SDK dependencies — uses raw HTTP to keep core lightweight.\r\n * Set the env var and tracking starts automatically.\r\n *\r\n * POSTHOG_API_KEY + POSTHOG_HOST → PostHog\r\n * MIXPANEL_TOKEN → Mixpanel\r\n * AMPLITUDE_API_KEY → Amplitude\r\n * AGENT_NATIVE_ANALYTICS_PUBLIC_KEY → Agent Native Analytics\r\n *\r\n * Call `registerBuiltinProviders()` at server startup (done\r\n * automatically by the core-routes plugin).\r\n */\r\n\r\nimport { registerTrackingProvider } from \"./registry.js\";\r\nimport type { TrackingProvider, TrackingEvent } from \"./types.js\";\r\n\r\nconst POSTHOG_DEFAULT_HOST = \"https://us.i.posthog.com\";\r\nconst AGENT_NATIVE_ANALYTICS_DEFAULT_ENDPOINT =\r\n \"https://analytics.jami.studio/track\";\r\nconst BATCH_INTERVAL_MS = 10_000;\r\nconst MAX_BATCH_SIZE = 50;\r\n\r\n// ─── Batched sender ────────────────────────────────────────────────────────\r\n\r\ninterface QueuedEvent {\r\n url: string;\r\n body: string;\r\n headers?: Record<string, string>;\r\n}\r\n\r\ninterface EnqueueOptions {\r\n flushImmediately?: boolean;\r\n}\r\n\r\n// Use globalThis so multiple ESM graph instances (Vite dev + Nitro symlinks)\r\n// share one queue, matching the same pattern as the tracking registry.\r\nconst QUEUE_KEY = Symbol.for(\"@agent-native/core/tracking.queue\");\r\nconst TIMER_KEY = Symbol.for(\"@agent-native/core/tracking.timer\");\r\n\r\ninterface GlobalWithQueue {\r\n [QUEUE_KEY]?: QueuedEvent[];\r\n [TIMER_KEY]?: ReturnType<typeof setTimeout> | null;\r\n}\r\n\r\nfunction getQueue(): QueuedEvent[] {\r\n const g = globalThis as unknown as GlobalWithQueue;\r\n if (!g[QUEUE_KEY]) g[QUEUE_KEY] = [];\r\n return g[QUEUE_KEY]!;\r\n}\r\n\r\nfunction getTimer(): ReturnType<typeof setTimeout> | null {\r\n const g = globalThis as unknown as GlobalWithQueue;\r\n return g[TIMER_KEY] ?? null;\r\n}\r\n\r\nfunction setTimer(t: ReturnType<typeof setTimeout> | null): void {\r\n (globalThis as unknown as GlobalWithQueue)[TIMER_KEY] = t;\r\n}\r\n\r\nfunction enqueue(\r\n url: string,\r\n body: string,\r\n headers?: Record<string, string>,\r\n options?: EnqueueOptions,\r\n): void {\r\n const queue = getQueue();\r\n queue.push({ url, body, headers });\r\n if (options?.flushImmediately || queue.length >= MAX_BATCH_SIZE) {\r\n void drainQueue();\r\n } else if (!getTimer()) {\r\n const timer = setTimeout(() => {\r\n void drainQueue();\r\n }, BATCH_INTERVAL_MS);\r\n if (timer.unref) timer.unref();\r\n setTimer(timer);\r\n }\r\n}\r\n\r\nfunction drainQueue(): Promise<void[]> {\r\n const t = getTimer();\r\n if (t) {\r\n clearTimeout(t);\r\n setTimer(null);\r\n }\r\n const queue = getQueue();\r\n const batch = queue.splice(0, queue.length);\r\n return Promise.all(\r\n batch.map((item) =>\r\n fetch(item.url, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\", ...item.headers },\r\n body: item.body,\r\n }).then(\r\n () => undefined,\r\n () => undefined,\r\n ),\r\n ),\r\n );\r\n}\r\n\r\nfunction isLocalhostUrl(value: string | undefined): boolean {\r\n if (!value || !value.trim()) return false;\r\n const raw = value.trim();\r\n const withProtocol = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(raw)\r\n ? raw\r\n : `https://${raw}`;\r\n try {\r\n const { hostname } = new URL(withProtocol);\r\n const h = hostname.toLowerCase();\r\n return (\r\n h === \"localhost\" ||\r\n h === \"127.0.0.1\" ||\r\n h === \"::1\" ||\r\n h === \"[::1]\" ||\r\n h.endsWith(\".localhost\") ||\r\n h.endsWith(\".local\")\r\n );\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\nfunction shouldSkipAgentNativeAnalyticsForLocalhost(): boolean {\r\n if (process.env.AGENT_NATIVE_ANALYTICS_ALLOW_LOCALHOST === \"true\") {\r\n return false;\r\n }\r\n if (process.env.NODE_ENV === \"development\") return true;\r\n return [\r\n process.env.APP_URL,\r\n process.env.BETTER_AUTH_URL,\r\n process.env.URL,\r\n process.env.DEPLOY_URL,\r\n process.env.VERCEL_PROJECT_PRODUCTION_URL,\r\n process.env.VERCEL_URL,\r\n ].some(isLocalhostUrl);\r\n}\r\n\r\nfunction isServerlessRuntime(): boolean {\r\n return Boolean(\r\n process.env.NETLIFY ||\r\n process.env.VERCEL ||\r\n process.env.AWS_LAMBDA_FUNCTION_NAME ||\r\n process.env.AWS_EXECUTION_ENV ||\r\n process.env.LAMBDA_TASK_ROOT ||\r\n process.env.FUNCTION_NAME,\r\n );\r\n}\r\n\r\nfunction agentNativeAnalyticsFlushesImmediately(): boolean {\r\n const mode =\r\n process.env.AGENT_NATIVE_ANALYTICS_FLUSH_MODE?.trim().toLowerCase();\r\n if (mode === \"batch\") return false;\r\n if (mode === \"immediate\") return true;\r\n return isServerlessRuntime();\r\n}\r\n\r\n// ─── PostHog ───────────────────────────────────────────────────────────────\r\n\r\nfunction isPostHogAiObservabilityEvent(eventName: string): boolean {\r\n return eventName.startsWith(\"$ai_\");\r\n}\r\n\r\nfunction createPostHogProvider(apiKey: string, host: string): TrackingProvider {\r\n return {\r\n name: \"posthog\",\r\n track(event: TrackingEvent) {\r\n const distinctId = event.userId || \"anonymous\";\r\n if (isPostHogAiObservabilityEvent(event.name)) {\r\n enqueue(\r\n `${host}/i/v0/e/`,\r\n JSON.stringify({\r\n api_key: apiKey,\r\n event: event.name,\r\n properties: {\r\n distinct_id: distinctId,\r\n ...event.properties,\r\n timestamp: event.timestamp,\r\n },\r\n }),\r\n );\r\n return;\r\n }\r\n\r\n enqueue(\r\n `${host}/capture/`,\r\n JSON.stringify({\r\n api_key: apiKey,\r\n event: event.name,\r\n distinct_id: distinctId,\r\n properties: {\r\n ...event.properties,\r\n timestamp: event.timestamp,\r\n },\r\n }),\r\n );\r\n },\r\n identify(userId, traits) {\r\n enqueue(\r\n `${host}/capture/`,\r\n JSON.stringify({\r\n api_key: apiKey,\r\n event: \"$identify\",\r\n distinct_id: userId,\r\n properties: { $set: traits },\r\n }),\r\n );\r\n },\r\n flush: () => {\r\n return drainQueue().then(() => undefined);\r\n },\r\n };\r\n}\r\n\r\n// ─── Mixpanel ──────────────────────────────────────────────────────────────\r\n\r\nfunction createMixpanelProvider(token: string): TrackingProvider {\r\n return {\r\n name: \"mixpanel\",\r\n track(event: TrackingEvent) {\r\n const data = {\r\n event: event.name,\r\n properties: {\r\n token,\r\n distinct_id: event.userId || \"anonymous\",\r\n time: event.timestamp\r\n ? new Date(event.timestamp).getTime() / 1000\r\n : undefined,\r\n ...event.properties,\r\n },\r\n };\r\n enqueue(\"https://api.mixpanel.com/track\", JSON.stringify([data]));\r\n },\r\n identify(userId, traits) {\r\n const data = {\r\n $token: token,\r\n $distinct_id: userId,\r\n $set: traits,\r\n };\r\n enqueue(\"https://api.mixpanel.com/engage\", JSON.stringify([data]));\r\n },\r\n flush: () => {\r\n return drainQueue().then(() => undefined);\r\n },\r\n };\r\n}\r\n\r\n// ─── Amplitude ─────────────────────────────────────────────────────────────\r\n\r\nfunction createAmplitudeProvider(apiKey: string): TrackingProvider {\r\n return {\r\n name: \"amplitude\",\r\n track(event: TrackingEvent) {\r\n const data = {\r\n api_key: apiKey,\r\n events: [\r\n {\r\n event_type: event.name,\r\n user_id: event.userId || \"anonymous\",\r\n event_properties: event.properties,\r\n time: event.timestamp\r\n ? new Date(event.timestamp).getTime()\r\n : undefined,\r\n },\r\n ],\r\n };\r\n enqueue(\"https://api2.amplitude.com/2/httpapi\", JSON.stringify(data));\r\n },\r\n identify(userId, traits) {\r\n const data = {\r\n api_key: apiKey,\r\n events: [\r\n {\r\n event_type: \"$identify\",\r\n user_id: userId,\r\n user_properties: { $set: traits },\r\n },\r\n ],\r\n };\r\n enqueue(\"https://api2.amplitude.com/2/httpapi\", JSON.stringify(data));\r\n },\r\n flush: () => {\r\n return drainQueue().then(() => undefined);\r\n },\r\n };\r\n}\r\n\r\n// ─── Webhook (custom HTTP endpoint) ───────────────────────────────────────\r\n\r\nfunction createWebhookProvider(\r\n url: string,\r\n authHeader?: string,\r\n): TrackingProvider {\r\n const extra = authHeader ? { Authorization: authHeader } : undefined;\r\n return {\r\n name: \"webhook\",\r\n track(event: TrackingEvent) {\r\n enqueue(\r\n url,\r\n JSON.stringify({\r\n event: event.name,\r\n properties: event.properties,\r\n userId: event.userId,\r\n timestamp: event.timestamp,\r\n }),\r\n extra,\r\n );\r\n },\r\n identify(userId, traits) {\r\n enqueue(\r\n url,\r\n JSON.stringify({\r\n event: \"$identify\",\r\n userId,\r\n traits,\r\n timestamp: new Date().toISOString(),\r\n }),\r\n extra,\r\n );\r\n },\r\n flush: () => {\r\n return drainQueue().then(() => undefined);\r\n },\r\n };\r\n}\r\n\r\n// ─── Agent Native Analytics ───────────────────────────────────────────────\r\n\r\nfunction createAgentNativeAnalyticsProvider(\r\n publicKey: string,\r\n endpoint: string,\r\n): TrackingProvider {\r\n const flushImmediately = agentNativeAnalyticsFlushesImmediately();\r\n return {\r\n name: \"agent-native-analytics\",\r\n track(event: TrackingEvent) {\r\n enqueue(\r\n endpoint,\r\n JSON.stringify({\r\n publicKey,\r\n event: event.name,\r\n properties: event.properties ?? {},\r\n userId: event.userId,\r\n timestamp: event.timestamp,\r\n }),\r\n undefined,\r\n { flushImmediately },\r\n );\r\n },\r\n identify(userId, traits) {\r\n enqueue(\r\n endpoint,\r\n JSON.stringify({\r\n publicKey,\r\n event: \"$identify\",\r\n userId,\r\n properties: traits ?? {},\r\n timestamp: new Date().toISOString(),\r\n }),\r\n undefined,\r\n { flushImmediately },\r\n );\r\n },\r\n flush: () => {\r\n return drainQueue().then(() => undefined);\r\n },\r\n };\r\n}\r\n\r\n// ─── Auto-registration ────────────────────────────────────────────────────\r\n\r\nlet _registered = false;\r\n\r\nexport function registerBuiltinProviders(): void {\r\n if (_registered) return;\r\n _registered = true;\r\n\r\n const posthogKey = process.env.POSTHOG_API_KEY;\r\n if (posthogKey) {\r\n const host = (process.env.POSTHOG_HOST || POSTHOG_DEFAULT_HOST).replace(\r\n /\\/+$/,\r\n \"\",\r\n );\r\n registerTrackingProvider(createPostHogProvider(posthogKey, host));\r\n }\r\n\r\n const mixpanelToken = process.env.MIXPANEL_TOKEN;\r\n if (mixpanelToken) {\r\n registerTrackingProvider(createMixpanelProvider(mixpanelToken));\r\n }\r\n\r\n const amplitudeKey = process.env.AMPLITUDE_API_KEY;\r\n if (amplitudeKey) {\r\n registerTrackingProvider(createAmplitudeProvider(amplitudeKey));\r\n }\r\n\r\n const agentNativeAnalyticsKey =\r\n process.env.AGENT_NATIVE_ANALYTICS_PUBLIC_KEY ||\r\n process.env.VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY;\r\n if (\r\n agentNativeAnalyticsKey &&\r\n !shouldSkipAgentNativeAnalyticsForLocalhost()\r\n ) {\r\n registerTrackingProvider(\r\n createAgentNativeAnalyticsProvider(\r\n agentNativeAnalyticsKey,\r\n (\r\n process.env.AGENT_NATIVE_ANALYTICS_ENDPOINT ||\r\n AGENT_NATIVE_ANALYTICS_DEFAULT_ENDPOINT\r\n ).replace(/\\/+$/, \"\"),\r\n ),\r\n );\r\n }\r\n\r\n const webhookUrl = process.env.TRACKING_WEBHOOK_URL;\r\n if (webhookUrl) {\r\n registerTrackingProvider(\r\n createWebhookProvider(webhookUrl, process.env.TRACKING_WEBHOOK_AUTH),\r\n );\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/tracking/providers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Built-in tracking providers that auto-register from env vars.\n *\n * No SDK dependencies — uses raw HTTP to keep core lightweight.\n * Set the env var and tracking starts automatically.\n *\n * POSTHOG_API_KEY + POSTHOG_HOST → PostHog\n * MIXPANEL_TOKEN → Mixpanel\n * AMPLITUDE_API_KEY → Amplitude\n * AGENT_NATIVE_ANALYTICS_PUBLIC_KEY → Agent Native Analytics\n *\n * Call `registerBuiltinProviders()` at server startup (done\n * automatically by the core-routes plugin).\n */\n\nimport { getScopedGlobal } from \"../shared/global-scope.js\";\nimport { registerTrackingProvider } from \"./registry.js\";\nimport type { TrackingProvider, TrackingEvent } from \"./types.js\";\n\nconst POSTHOG_DEFAULT_HOST = \"https://us.i.posthog.com\";\nconst AGENT_NATIVE_ANALYTICS_DEFAULT_ENDPOINT =\n \"https://analytics.jami.studio/track\";\nconst BATCH_INTERVAL_MS = 10_000;\nconst MAX_BATCH_SIZE = 50;\n\n// ─── Batched sender ────────────────────────────────────────────────────────\n\ninterface QueuedEvent {\n url: string;\n body: string;\n headers?: Record<string, string>;\n}\n\ninterface EnqueueOptions {\n flushImmediately?: boolean;\n}\n\n// Use globalThis so multiple ESM graph instances (Vite dev + Nitro symlinks)\n// share one queue, matching the same pattern as the tracking registry.\n// Scope-aware + lazily resolved so unified workspace deployments keep\n// per-app queues/timers. See shared/global-scope.\nconst QUEUE_BASE_KEY = \"agent-native.tracking.queue\";\nconst TIMER_BASE_KEY = \"agent-native.tracking.timer\";\n\ninterface TimerBox {\n timer: ReturnType<typeof setTimeout> | null;\n}\n\nfunction getQueue(): QueuedEvent[] {\n return getScopedGlobal(QUEUE_BASE_KEY, () => [] as QueuedEvent[]);\n}\n\nfunction getTimerBox(): TimerBox {\n return getScopedGlobal(TIMER_BASE_KEY, () => ({ timer: null }) as TimerBox);\n}\n\nfunction getTimer(): ReturnType<typeof setTimeout> | null {\n return getTimerBox().timer;\n}\n\nfunction setTimer(t: ReturnType<typeof setTimeout> | null): void {\n getTimerBox().timer = t;\n}\n\nfunction enqueue(\n url: string,\n body: string,\n headers?: Record<string, string>,\n options?: EnqueueOptions,\n): void {\n const queue = getQueue();\n queue.push({ url, body, headers });\n if (options?.flushImmediately || queue.length >= MAX_BATCH_SIZE) {\n void drainQueue();\n } else if (!getTimer()) {\n const timer = setTimeout(() => {\n void drainQueue();\n }, BATCH_INTERVAL_MS);\n if (timer.unref) timer.unref();\n setTimer(timer);\n }\n}\n\nfunction drainQueue(): Promise<void[]> {\n const t = getTimer();\n if (t) {\n clearTimeout(t);\n setTimer(null);\n }\n const queue = getQueue();\n const batch = queue.splice(0, queue.length);\n return Promise.all(\n batch.map((item) =>\n fetch(item.url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...item.headers },\n body: item.body,\n }).then(\n () => undefined,\n () => undefined,\n ),\n ),\n );\n}\n\nfunction isLocalhostUrl(value: string | undefined): boolean {\n if (!value || !value.trim()) return false;\n const raw = value.trim();\n const withProtocol = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(raw)\n ? raw\n : `https://${raw}`;\n try {\n const { hostname } = new URL(withProtocol);\n const h = hostname.toLowerCase();\n return (\n h === \"localhost\" ||\n h === \"127.0.0.1\" ||\n h === \"::1\" ||\n h === \"[::1]\" ||\n h.endsWith(\".localhost\") ||\n h.endsWith(\".local\")\n );\n } catch {\n return false;\n }\n}\n\nfunction shouldSkipAgentNativeAnalyticsForLocalhost(): boolean {\n if (process.env.AGENT_NATIVE_ANALYTICS_ALLOW_LOCALHOST === \"true\") {\n return false;\n }\n if (process.env.NODE_ENV === \"development\") return true;\n return [\n process.env.APP_URL,\n process.env.BETTER_AUTH_URL,\n process.env.URL,\n process.env.DEPLOY_URL,\n process.env.VERCEL_PROJECT_PRODUCTION_URL,\n process.env.VERCEL_URL,\n ].some(isLocalhostUrl);\n}\n\nfunction isServerlessRuntime(): boolean {\n return Boolean(\n process.env.NETLIFY ||\n process.env.VERCEL ||\n process.env.AWS_LAMBDA_FUNCTION_NAME ||\n process.env.AWS_EXECUTION_ENV ||\n process.env.LAMBDA_TASK_ROOT ||\n process.env.FUNCTION_NAME,\n );\n}\n\nfunction agentNativeAnalyticsFlushesImmediately(): boolean {\n const mode =\n process.env.AGENT_NATIVE_ANALYTICS_FLUSH_MODE?.trim().toLowerCase();\n if (mode === \"batch\") return false;\n if (mode === \"immediate\") return true;\n return isServerlessRuntime();\n}\n\n// ─── PostHog ───────────────────────────────────────────────────────────────\n\nfunction isPostHogAiObservabilityEvent(eventName: string): boolean {\n return eventName.startsWith(\"$ai_\");\n}\n\nfunction createPostHogProvider(apiKey: string, host: string): TrackingProvider {\n return {\n name: \"posthog\",\n track(event: TrackingEvent) {\n const distinctId = event.userId || \"anonymous\";\n if (isPostHogAiObservabilityEvent(event.name)) {\n enqueue(\n `${host}/i/v0/e/`,\n JSON.stringify({\n api_key: apiKey,\n event: event.name,\n properties: {\n distinct_id: distinctId,\n ...event.properties,\n timestamp: event.timestamp,\n },\n }),\n );\n return;\n }\n\n enqueue(\n `${host}/capture/`,\n JSON.stringify({\n api_key: apiKey,\n event: event.name,\n distinct_id: distinctId,\n properties: {\n ...event.properties,\n timestamp: event.timestamp,\n },\n }),\n );\n },\n identify(userId, traits) {\n enqueue(\n `${host}/capture/`,\n JSON.stringify({\n api_key: apiKey,\n event: \"$identify\",\n distinct_id: userId,\n properties: { $set: traits },\n }),\n );\n },\n flush: () => {\n return drainQueue().then(() => undefined);\n },\n };\n}\n\n// ─── Mixpanel ──────────────────────────────────────────────────────────────\n\nfunction createMixpanelProvider(token: string): TrackingProvider {\n return {\n name: \"mixpanel\",\n track(event: TrackingEvent) {\n const data = {\n event: event.name,\n properties: {\n token,\n distinct_id: event.userId || \"anonymous\",\n time: event.timestamp\n ? new Date(event.timestamp).getTime() / 1000\n : undefined,\n ...event.properties,\n },\n };\n enqueue(\"https://api.mixpanel.com/track\", JSON.stringify([data]));\n },\n identify(userId, traits) {\n const data = {\n $token: token,\n $distinct_id: userId,\n $set: traits,\n };\n enqueue(\"https://api.mixpanel.com/engage\", JSON.stringify([data]));\n },\n flush: () => {\n return drainQueue().then(() => undefined);\n },\n };\n}\n\n// ─── Amplitude ─────────────────────────────────────────────────────────────\n\nfunction createAmplitudeProvider(apiKey: string): TrackingProvider {\n return {\n name: \"amplitude\",\n track(event: TrackingEvent) {\n const data = {\n api_key: apiKey,\n events: [\n {\n event_type: event.name,\n user_id: event.userId || \"anonymous\",\n event_properties: event.properties,\n time: event.timestamp\n ? new Date(event.timestamp).getTime()\n : undefined,\n },\n ],\n };\n enqueue(\"https://api2.amplitude.com/2/httpapi\", JSON.stringify(data));\n },\n identify(userId, traits) {\n const data = {\n api_key: apiKey,\n events: [\n {\n event_type: \"$identify\",\n user_id: userId,\n user_properties: { $set: traits },\n },\n ],\n };\n enqueue(\"https://api2.amplitude.com/2/httpapi\", JSON.stringify(data));\n },\n flush: () => {\n return drainQueue().then(() => undefined);\n },\n };\n}\n\n// ─── Webhook (custom HTTP endpoint) ───────────────────────────────────────\n\nfunction createWebhookProvider(\n url: string,\n authHeader?: string,\n): TrackingProvider {\n const extra = authHeader ? { Authorization: authHeader } : undefined;\n return {\n name: \"webhook\",\n track(event: TrackingEvent) {\n enqueue(\n url,\n JSON.stringify({\n event: event.name,\n properties: event.properties,\n userId: event.userId,\n timestamp: event.timestamp,\n }),\n extra,\n );\n },\n identify(userId, traits) {\n enqueue(\n url,\n JSON.stringify({\n event: \"$identify\",\n userId,\n traits,\n timestamp: new Date().toISOString(),\n }),\n extra,\n );\n },\n flush: () => {\n return drainQueue().then(() => undefined);\n },\n };\n}\n\n// ─── Agent Native Analytics ───────────────────────────────────────────────\n\nfunction createAgentNativeAnalyticsProvider(\n publicKey: string,\n endpoint: string,\n): TrackingProvider {\n const flushImmediately = agentNativeAnalyticsFlushesImmediately();\n return {\n name: \"agent-native-analytics\",\n track(event: TrackingEvent) {\n enqueue(\n endpoint,\n JSON.stringify({\n publicKey,\n event: event.name,\n properties: event.properties ?? {},\n userId: event.userId,\n timestamp: event.timestamp,\n }),\n undefined,\n { flushImmediately },\n );\n },\n identify(userId, traits) {\n enqueue(\n endpoint,\n JSON.stringify({\n publicKey,\n event: \"$identify\",\n userId,\n properties: traits ?? {},\n timestamp: new Date().toISOString(),\n }),\n undefined,\n { flushImmediately },\n );\n },\n flush: () => {\n return drainQueue().then(() => undefined);\n },\n };\n}\n\n// ─── Auto-registration ────────────────────────────────────────────────────\n\nlet _registered = false;\n\nexport function registerBuiltinProviders(): void {\n if (_registered) return;\n _registered = true;\n\n const posthogKey = process.env.POSTHOG_API_KEY;\n if (posthogKey) {\n const host = (process.env.POSTHOG_HOST || POSTHOG_DEFAULT_HOST).replace(\n /\\/+$/,\n \"\",\n );\n registerTrackingProvider(createPostHogProvider(posthogKey, host));\n }\n\n const mixpanelToken = process.env.MIXPANEL_TOKEN;\n if (mixpanelToken) {\n registerTrackingProvider(createMixpanelProvider(mixpanelToken));\n }\n\n const amplitudeKey = process.env.AMPLITUDE_API_KEY;\n if (amplitudeKey) {\n registerTrackingProvider(createAmplitudeProvider(amplitudeKey));\n }\n\n const agentNativeAnalyticsKey =\n process.env.AGENT_NATIVE_ANALYTICS_PUBLIC_KEY ||\n process.env.VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY;\n if (\n agentNativeAnalyticsKey &&\n !shouldSkipAgentNativeAnalyticsForLocalhost()\n ) {\n registerTrackingProvider(\n createAgentNativeAnalyticsProvider(\n agentNativeAnalyticsKey,\n (\n process.env.AGENT_NATIVE_ANALYTICS_ENDPOINT ||\n AGENT_NATIVE_ANALYTICS_DEFAULT_ENDPOINT\n ).replace(/\\/+$/, \"\"),\n ),\n );\n }\n\n const webhookUrl = process.env.TRACKING_WEBHOOK_URL;\n if (webhookUrl) {\n registerTrackingProvider(\n createWebhookProvider(webhookUrl, process.env.TRACKING_WEBHOOK_AUTH),\n );\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/tracking/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAiB,MAAM,YAAY,CAAC;AAalE,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAUzE;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED,wBAAgB,KAAK,CACnB,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACzB,IAAI,CAuBN;AAED,wBAAgB,QAAQ,CACtB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,IAAI,CAYN;AAED,wBAAgB,aAAa,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAsB/C"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/tracking/registry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAiB,MAAM,YAAY,CAAC;AAYlE,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAUzE;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED,wBAAgB,KAAK,CACnB,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACzB,IAAI,CAuBN;AAED,wBAAgB,QAAQ,CACtB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,IAAI,CAYN;AAED,wBAAgB,aAAa,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAsB/C"}
@@ -1,9 +1,9 @@
1
- const REGISTRY_KEY = Symbol.for("@agent-native/core/tracking.registry");
1
+ import { getScopedGlobal } from "../shared/global-scope.js";
2
+ // globalThis-pinned so one app's ESM graphs share one provider registry, but
3
+ // scope-aware + lazily resolved so unified workspace deployments (all apps in
4
+ // one isolate) keep per-app tracking providers. See shared/global-scope.
2
5
  function getRegistry() {
3
- const g = globalThis;
4
- if (!g[REGISTRY_KEY])
5
- g[REGISTRY_KEY] = new Map();
6
- return g[REGISTRY_KEY];
6
+ return getScopedGlobal("agent-native.tracking.registry", () => new Map());
7
7
  }
8
8
  export function registerTrackingProvider(provider) {
9
9
  if (!provider?.name) {