@ekanos/sdk 0.1.0

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 (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -0
  3. package/dist/components/base-activation-dialog.d.ts +41 -0
  4. package/dist/components/base-activation-dialog.js +41 -0
  5. package/dist/components/base-activation-dialog.js.map +1 -0
  6. package/dist/components/base-activation-form.d.ts +36 -0
  7. package/dist/components/base-activation-form.js +21 -0
  8. package/dist/components/base-activation-form.js.map +1 -0
  9. package/dist/components/base-marketplace-tile.d.ts +34 -0
  10. package/dist/components/base-marketplace-tile.js +40 -0
  11. package/dist/components/base-marketplace-tile.js.map +1 -0
  12. package/dist/components/current-return-path.d.ts +19 -0
  13. package/dist/components/current-return-path.js +26 -0
  14. package/dist/components/current-return-path.js.map +1 -0
  15. package/dist/components/index.d.ts +40 -0
  16. package/dist/components/index.js +38 -0
  17. package/dist/components/index.js.map +1 -0
  18. package/dist/components/oauth-activation-form.d.ts +50 -0
  19. package/dist/components/oauth-activation-form.js +119 -0
  20. package/dist/components/oauth-activation-form.js.map +1 -0
  21. package/dist/components/widgets/ai-prompt-chip.d.ts +31 -0
  22. package/dist/components/widgets/ai-prompt-chip.js +38 -0
  23. package/dist/components/widgets/ai-prompt-chip.js.map +1 -0
  24. package/dist/components/widgets/ask-icon.d.ts +3 -0
  25. package/dist/components/widgets/ask-icon.js +5 -0
  26. package/dist/components/widgets/ask-icon.js.map +1 -0
  27. package/dist/components/widgets/use-animated-height.d.ts +12 -0
  28. package/dist/components/widgets/use-animated-height.js +39 -0
  29. package/dist/components/widgets/use-animated-height.js.map +1 -0
  30. package/dist/components/widgets/widget-ask-bar.d.ts +18 -0
  31. package/dist/components/widgets/widget-ask-bar.js +62 -0
  32. package/dist/components/widgets/widget-ask-bar.js.map +1 -0
  33. package/dist/components/widgets/widget-context.d.ts +38 -0
  34. package/dist/components/widgets/widget-context.js +4 -0
  35. package/dist/components/widgets/widget-context.js.map +1 -0
  36. package/dist/components/widgets/widget-state-components.d.ts +5 -0
  37. package/dist/components/widgets/widget-state-components.js +19 -0
  38. package/dist/components/widgets/widget-state-components.js.map +1 -0
  39. package/dist/components/widgets/widget.d.ts +91 -0
  40. package/dist/components/widgets/widget.js +205 -0
  41. package/dist/components/widgets/widget.js.map +1 -0
  42. package/dist/context/egress.d.ts +30 -0
  43. package/dist/context/egress.js +81 -0
  44. package/dist/context/egress.js.map +1 -0
  45. package/dist/context/errors.d.ts +57 -0
  46. package/dist/context/errors.js +78 -0
  47. package/dist/context/errors.js.map +1 -0
  48. package/dist/context/index.d.ts +19 -0
  49. package/dist/context/index.js +20 -0
  50. package/dist/context/index.js.map +1 -0
  51. package/dist/context/require-context.d.ts +26 -0
  52. package/dist/context/require-context.js +36 -0
  53. package/dist/context/require-context.js.map +1 -0
  54. package/dist/context/storage-validation.d.ts +71 -0
  55. package/dist/context/storage-validation.js +132 -0
  56. package/dist/context/storage-validation.js.map +1 -0
  57. package/dist/context/types.d.ts +11 -0
  58. package/dist/context/types.js +2 -0
  59. package/dist/context/types.js.map +1 -0
  60. package/dist/hooks/activation-actions-context.d.ts +80 -0
  61. package/dist/hooks/activation-actions-context.js +31 -0
  62. package/dist/hooks/activation-actions-context.js.map +1 -0
  63. package/dist/hooks/ask-assistant-context.d.ts +32 -0
  64. package/dist/hooks/ask-assistant-context.js +56 -0
  65. package/dist/hooks/ask-assistant-context.js.map +1 -0
  66. package/dist/hooks/fetch-integration-storage.d.ts +48 -0
  67. package/dist/hooks/fetch-integration-storage.js +68 -0
  68. package/dist/hooks/fetch-integration-storage.js.map +1 -0
  69. package/dist/hooks/index.d.ts +28 -0
  70. package/dist/hooks/index.js +32 -0
  71. package/dist/hooks/index.js.map +1 -0
  72. package/dist/hooks/use-activate-integration.d.ts +15 -0
  73. package/dist/hooks/use-activate-integration.js +61 -0
  74. package/dist/hooks/use-activate-integration.js.map +1 -0
  75. package/dist/hooks/use-oauth-connection-status.d.ts +360 -0
  76. package/dist/hooks/use-oauth-connection-status.js +108 -0
  77. package/dist/hooks/use-oauth-connection-status.js.map +1 -0
  78. package/dist/hooks/use-widget-events.d.ts +20 -0
  79. package/dist/hooks/use-widget-events.js +51 -0
  80. package/dist/hooks/use-widget-events.js.map +1 -0
  81. package/dist/hooks/widget-event-context.d.ts +22 -0
  82. package/dist/hooks/widget-event-context.js +75 -0
  83. package/dist/hooks/widget-event-context.js.map +1 -0
  84. package/dist/hooks/widget-event-types.d.ts +55 -0
  85. package/dist/hooks/widget-event-types.js +9 -0
  86. package/dist/hooks/widget-event-types.js.map +1 -0
  87. package/dist/index.d.ts +19 -0
  88. package/dist/index.js +19 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/integration/cron.d.ts +29 -0
  91. package/dist/integration/cron.js +102 -0
  92. package/dist/integration/cron.js.map +1 -0
  93. package/dist/integration/define-integration.d.ts +28 -0
  94. package/dist/integration/define-integration.js +41 -0
  95. package/dist/integration/define-integration.js.map +1 -0
  96. package/dist/integration/event-surfaces.d.ts +15 -0
  97. package/dist/integration/event-surfaces.js +45 -0
  98. package/dist/integration/event-surfaces.js.map +1 -0
  99. package/dist/integration/index.d.ts +20 -0
  100. package/dist/integration/index.js +23 -0
  101. package/dist/integration/index.js.map +1 -0
  102. package/dist/integration/types.d.ts +7 -0
  103. package/dist/integration/types.js +2 -0
  104. package/dist/integration/types.js.map +1 -0
  105. package/dist/mcp/guards.d.ts +40 -0
  106. package/dist/mcp/guards.js +99 -0
  107. package/dist/mcp/guards.js.map +1 -0
  108. package/dist/mcp/index.d.ts +22 -0
  109. package/dist/mcp/index.js +22 -0
  110. package/dist/mcp/index.js.map +1 -0
  111. package/dist/mcp/ownership.d.ts +74 -0
  112. package/dist/mcp/ownership.js +83 -0
  113. package/dist/mcp/ownership.js.map +1 -0
  114. package/dist/mcp/types.d.ts +145 -0
  115. package/dist/mcp/types.js +2 -0
  116. package/dist/mcp/types.js.map +1 -0
  117. package/dist/testing/index.d.ts +16 -0
  118. package/dist/testing/index.js +19 -0
  119. package/dist/testing/index.js.map +1 -0
  120. package/dist/testing/invoke.d.ts +77 -0
  121. package/dist/testing/invoke.js +83 -0
  122. package/dist/testing/invoke.js.map +1 -0
  123. package/dist/testing/mock-context.d.ts +79 -0
  124. package/dist/testing/mock-context.js +206 -0
  125. package/dist/testing/mock-context.js.map +1 -0
  126. package/dist/types/icon.d.ts +12 -0
  127. package/dist/types/icon.js +2 -0
  128. package/dist/types/icon.js.map +1 -0
  129. package/dist/types/index.d.ts +10 -0
  130. package/dist/types/index.js +8 -0
  131. package/dist/types/index.js.map +1 -0
  132. package/dist/types/integration.d.ts +258 -0
  133. package/dist/types/integration.js +2 -0
  134. package/dist/types/integration.js.map +1 -0
  135. package/dist/types/widget-ask-context.d.ts +36 -0
  136. package/dist/types/widget-ask-context.js +2 -0
  137. package/dist/types/widget-ask-context.js.map +1 -0
  138. package/dist/types/workspace-target.d.ts +33 -0
  139. package/dist/types/workspace-target.js +2 -0
  140. package/dist/types/workspace-target.js.map +1 -0
  141. package/package.json +116 -0
@@ -0,0 +1,36 @@
1
+ function isIntegrationContext(value) {
2
+ if (typeof value !== 'object' || value === null)
3
+ return false;
4
+ const candidate = value;
5
+ const storage = candidate.storage;
6
+ return (typeof candidate.accountId === 'string' &&
7
+ typeof candidate.fetch === 'function' &&
8
+ typeof candidate.storage === 'object' &&
9
+ storage !== undefined &&
10
+ storage !== null &&
11
+ typeof storage.account === 'object' &&
12
+ typeof storage.user === 'object' &&
13
+ typeof candidate.secrets === 'object' &&
14
+ candidate.secrets !== null &&
15
+ typeof candidate.logger === 'object' &&
16
+ candidate.logger !== null);
17
+ }
18
+ /**
19
+ * Narrow the internal tool context's `integrationContext` passthrough to the
20
+ * SDK's `IntegrationContext`. Throws with a remediation message when the host
21
+ * path did not attach one — a migrated tool must only run on wired paths.
22
+ */
23
+ export function requireContext(toolContext) {
24
+ const candidate = toolContext.integrationContext;
25
+ if (!isIntegrationContext(candidate)) {
26
+ throw new Error('No capability context is attached to this tool context. ' +
27
+ 'requireContext() only works on host paths that build one: the ' +
28
+ 'integration MCP route (which attaches ctx.integrationContext after ' +
29
+ 'authorization) or withIntegrationContext(slug, …). Either invoke ' +
30
+ 'this tool through a wired path, or — in tests — pass ' +
31
+ '{ integrationContext: createMockContext({ … }) } from ' +
32
+ '@ekanos/sdk/testing.');
33
+ }
34
+ return candidate;
35
+ }
36
+ //# sourceMappingURL=require-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-context.js","sourceRoot":"","sources":["../../src/context/require-context.ts"],"names":[],"mappings":"AAsBA,SAAS,oBAAoB,CAAC,KAAc;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,SAAS,GAAG,KAAgC,CAAC;IACnD,MAAM,OAAO,GAAG,SAAS,CAAC,OAA8C,CAAC;IAEzE,OAAO,CACL,OAAO,SAAS,CAAC,SAAS,KAAK,QAAQ;QACvC,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU;QACrC,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ;QACrC,OAAO,KAAK,SAAS;QACrB,OAAO,KAAK,IAAI;QAChB,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ;QACnC,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;QAChC,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ;QACrC,SAAS,CAAC,OAAO,KAAK,IAAI;QAC1B,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ;QACpC,SAAS,CAAC,MAAM,KAAK,IAAI,CAC1B,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,WAAsC;IAEtC,MAAM,SAAS,GAAG,WAAW,CAAC,kBAAkB,CAAC;IAEjD,IAAI,CAAC,oBAAoB,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CACb,0DAA0D;YACxD,gEAAgE;YAChE,qEAAqE;YACrE,mEAAmE;YACnE,uDAAuD;YACvD,wDAAwD;YACxD,sBAAsB,CACzB,CAAC;IACJ,CAAC;IAED,OAAO,SAAwC,CAAC;AAClD,CAAC","sourcesContent":["/**\n * `requireContext()` — the typed accessor migrated tools use to read the\n * host-built capability context off the internal `ToolContext`.\n *\n * The host attaches the context ADDITIVELY as an untyped passthrough field\n * (`integrationContext?: unknown` on `@kit/integrations-core`'s ToolContext —\n * core cannot import the SDK without a package cycle). This function is the\n * SDK-side narrowing: structural assertion + remediation error, so first-party\n * tools get full typing from the package they already depend on.\n *\n * Dependency-pure and isomorphic like the rest of `/context`.\n */\nimport type { IntegrationContext, StorageSchemas } from './types';\n\n/**\n * The one field `requireContext` reads. `@kit/integrations-core`'s\n * `ToolContext` satisfies this structurally without core importing the SDK.\n */\nexport interface IntegrationContextCarrier {\n integrationContext?: unknown;\n}\n\nfunction isIntegrationContext(value: unknown): value is IntegrationContext {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as Record<string, unknown>;\n const storage = candidate.storage as Record<string, unknown> | undefined;\n\n return (\n typeof candidate.accountId === 'string' &&\n typeof candidate.fetch === 'function' &&\n typeof candidate.storage === 'object' &&\n storage !== undefined &&\n storage !== null &&\n typeof storage.account === 'object' &&\n typeof storage.user === 'object' &&\n typeof candidate.secrets === 'object' &&\n candidate.secrets !== null &&\n typeof candidate.logger === 'object' &&\n candidate.logger !== null\n );\n}\n\n/**\n * Narrow the internal tool context's `integrationContext` passthrough to the\n * SDK's `IntegrationContext`. Throws with a remediation message when the host\n * path did not attach one — a migrated tool must only run on wired paths.\n */\nexport function requireContext<Schemas extends StorageSchemas = StorageSchemas>(\n toolContext: IntegrationContextCarrier,\n): IntegrationContext<Schemas> {\n const candidate = toolContext.integrationContext;\n\n if (!isIntegrationContext(candidate)) {\n throw new Error(\n 'No capability context is attached to this tool context. ' +\n 'requireContext() only works on host paths that build one: the ' +\n 'integration MCP route (which attaches ctx.integrationContext after ' +\n 'authorization) or withIntegrationContext(slug, …). Either invoke ' +\n 'this tool through a wired path, or — in tests — pass ' +\n '{ integrationContext: createMockContext({ … }) } from ' +\n '@ekanos/sdk/testing.',\n );\n }\n\n return candidate as IntegrationContext<Schemas>;\n}\n"]}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The shared storage validator (ruling 1) — ONE implementation, used by the
3
+ * `@ekanos/sdk/testing` mock today and the production `ctx.storage` wrapper
4
+ * when host wiring lands, so the mock enforces exactly what prod enforces.
5
+ */
6
+ import type { z } from 'zod';
7
+ import type { StorageKeyDeclarationInput, StorageSchemaMap } from './types.js';
8
+ type StorageScope = 'account' | 'user';
9
+ /**
10
+ * Normalizes either declaration form — a bare zod schema, or a
11
+ * `{ schema, clientReadable }` descriptor — into the resolved pair. ONE
12
+ * implementation, so the mock, the production `ctx.storage` wrapper, and the
13
+ * browser-facing storage route all read a declaration the same way.
14
+ *
15
+ * The zod-schema test is duck-typed on `safeParse` rather than `instanceof
16
+ * z.ZodType` on purpose: a partner package may bundle its own zod copy, and a
17
+ * cross-realm schema must not be misread as a descriptor (which would then
18
+ * fail the descriptor branch and report a nonsense error).
19
+ *
20
+ * `clientReadable` FAILS CLOSED: the bare-schema form and an omitted flag
21
+ * both resolve to `false`. Widening exposure is always an explicit act.
22
+ */
23
+ export declare function resolveStorageKeyDeclaration(declaration: StorageKeyDeclarationInput): {
24
+ schema: z.ZodType;
25
+ clientReadable: boolean;
26
+ };
27
+ /**
28
+ * Whether a declared key opted in to the generic browser-readable storage
29
+ * route. An UNDECLARED key and a declared-but-not-opted-in key both answer
30
+ * `false` — the route must not distinguish them, or it becomes an existence
31
+ * oracle for a partner's server-only key names.
32
+ */
33
+ export declare function isStorageKeyClientReadable(args: {
34
+ key: string;
35
+ schemas: StorageSchemaMap | undefined;
36
+ }): boolean;
37
+ /**
38
+ * Resolves the declared schema for a storage key, throwing
39
+ * `StorageValidationError` when the key is undeclared (strict keys). Used
40
+ * directly by `delete`, and by `parseStorageValue` for reads and writes.
41
+ */
42
+ export declare function assertStorageKeyDeclared(args: {
43
+ scope: StorageScope;
44
+ key: string;
45
+ schemas: StorageSchemaMap | undefined;
46
+ }): z.ZodType;
47
+ /**
48
+ * Rejects writes and deletes against host-managed read-only data_types
49
+ * (finding C). ONE implementation for the `@ekanos/sdk/testing` mock and the
50
+ * production `ctx.storage` wrapper, so a write that prod refuses also fails
51
+ * in partner tests. Runs BEFORE the declared-key check so a read-only key
52
+ * reports the real reason rather than "undeclared".
53
+ */
54
+ export declare function assertStorageKeyWritable(args: {
55
+ scope: StorageScope;
56
+ key: string;
57
+ }): void;
58
+ /**
59
+ * Validates one storage value against its declared schema and returns the
60
+ * schema's OUTPUT value (defaults and transforms applied). Throws
61
+ * `StorageValidationError` on an undeclared key or a schema mismatch, with
62
+ * the zod issues formatted into the remediation hint.
63
+ */
64
+ export declare function parseStorageValue(args: {
65
+ scope: StorageScope;
66
+ key: string;
67
+ operation: 'read' | 'write';
68
+ schemas: StorageSchemaMap | undefined;
69
+ value: unknown;
70
+ }): unknown;
71
+ export {};
@@ -0,0 +1,132 @@
1
+ import { StorageValidationError } from './errors.js';
2
+ /**
3
+ * Account data_types a partner may READ through `ctx.storage` but never
4
+ * write or delete (authz-hardening finding C). `activation` rows are host
5
+ * lifecycle state: the activation flow creates them and admin deactivation
6
+ * removes them. A partner that could write `activation` (any subtype) could
7
+ * re-create its own activation row after an admin deactivated it —
8
+ * self-reactivation. Reads stay allowed: the capability-context proposal
9
+ * (§2.1) deliberately exposes the non-secret activation payload through
10
+ * `ctx.storage.account.get('activation')`.
11
+ */
12
+ const READ_ONLY_ACCOUNT_DATA_TYPES = new Set(['activation']);
13
+ function formatIssues(issues) {
14
+ return issues
15
+ .map((issue) => {
16
+ const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
17
+ return ` - ${path}: ${issue.message}`;
18
+ })
19
+ .join('\n');
20
+ }
21
+ /**
22
+ * Normalizes either declaration form — a bare zod schema, or a
23
+ * `{ schema, clientReadable }` descriptor — into the resolved pair. ONE
24
+ * implementation, so the mock, the production `ctx.storage` wrapper, and the
25
+ * browser-facing storage route all read a declaration the same way.
26
+ *
27
+ * The zod-schema test is duck-typed on `safeParse` rather than `instanceof
28
+ * z.ZodType` on purpose: a partner package may bundle its own zod copy, and a
29
+ * cross-realm schema must not be misread as a descriptor (which would then
30
+ * fail the descriptor branch and report a nonsense error).
31
+ *
32
+ * `clientReadable` FAILS CLOSED: the bare-schema form and an omitted flag
33
+ * both resolve to `false`. Widening exposure is always an explicit act.
34
+ */
35
+ export function resolveStorageKeyDeclaration(declaration) {
36
+ if (typeof (declaration === null || declaration === void 0 ? void 0 : declaration.safeParse) ===
37
+ 'function') {
38
+ return { schema: declaration, clientReadable: false };
39
+ }
40
+ const descriptor = declaration;
41
+ return {
42
+ schema: descriptor.schema,
43
+ clientReadable: descriptor.clientReadable === true,
44
+ };
45
+ }
46
+ /**
47
+ * Whether a declared key opted in to the generic browser-readable storage
48
+ * route. An UNDECLARED key and a declared-but-not-opted-in key both answer
49
+ * `false` — the route must not distinguish them, or it becomes an existence
50
+ * oracle for a partner's server-only key names.
51
+ */
52
+ export function isStorageKeyClientReadable(args) {
53
+ var _a;
54
+ const declaration = (_a = args.schemas) === null || _a === void 0 ? void 0 : _a[args.key];
55
+ if (!declaration)
56
+ return false;
57
+ return resolveStorageKeyDeclaration(declaration).clientReadable;
58
+ }
59
+ /**
60
+ * Resolves the declared schema for a storage key, throwing
61
+ * `StorageValidationError` when the key is undeclared (strict keys). Used
62
+ * directly by `delete`, and by `parseStorageValue` for reads and writes.
63
+ */
64
+ export function assertStorageKeyDeclared(args) {
65
+ var _a;
66
+ const declaration = (_a = args.schemas) === null || _a === void 0 ? void 0 : _a[args.key];
67
+ if (!declaration) {
68
+ throw new StorageValidationError({
69
+ scope: args.scope,
70
+ key: args.key,
71
+ hint: `No schema is declared for ${args.scope} storage key "${args.key}". ` +
72
+ `Storage keys are strict: declare the key and its zod schema in ` +
73
+ `defineIntegration({ storage: { ${args.scope}: { '${args.key}': z.object({ … }) } } }) ` +
74
+ `(or in createMockContext({ storageSchemas }) in tests) before reading, ` +
75
+ `writing, or deleting it.`,
76
+ });
77
+ }
78
+ return resolveStorageKeyDeclaration(declaration).schema;
79
+ }
80
+ /**
81
+ * Rejects writes and deletes against host-managed read-only data_types
82
+ * (finding C). ONE implementation for the `@ekanos/sdk/testing` mock and the
83
+ * production `ctx.storage` wrapper, so a write that prod refuses also fails
84
+ * in partner tests. Runs BEFORE the declared-key check so a read-only key
85
+ * reports the real reason rather than "undeclared".
86
+ */
87
+ export function assertStorageKeyWritable(args) {
88
+ if (args.scope !== 'account')
89
+ return;
90
+ const slashIndex = args.key.indexOf('/');
91
+ const dataType = slashIndex === -1 ? args.key : args.key.slice(0, slashIndex);
92
+ if (READ_ONLY_ACCOUNT_DATA_TYPES.has(dataType)) {
93
+ throw new StorageValidationError({
94
+ scope: args.scope,
95
+ key: args.key,
96
+ hint: `Storage key "${args.key}" uses the host-managed "${dataType}" ` +
97
+ `data_type, which ctx.storage can read but never write or delete. ` +
98
+ `Activation rows are created by the host activation flow and ` +
99
+ `removed by deactivation — an integration cannot (re)create or ` +
100
+ `drop its own activation. Persist integration state under a ` +
101
+ `"settings", "sync_state", "metrics_summary", or "cache" key instead.`,
102
+ });
103
+ }
104
+ }
105
+ /**
106
+ * Validates one storage value against its declared schema and returns the
107
+ * schema's OUTPUT value (defaults and transforms applied). Throws
108
+ * `StorageValidationError` on an undeclared key or a schema mismatch, with
109
+ * the zod issues formatted into the remediation hint.
110
+ */
111
+ export function parseStorageValue(args) {
112
+ const schema = assertStorageKeyDeclared(args);
113
+ const result = schema.safeParse(args.value);
114
+ if (!result.success) {
115
+ const issues = formatIssues(result.error.issues);
116
+ const hint = args.operation === 'write'
117
+ ? `The value written to ${args.scope} storage key "${args.key}" fails ` +
118
+ `its declared schema. Fix the write payload to match the schema ` +
119
+ `declared in defineIntegration({ storage }):\n${issues}`
120
+ : `The data stored at ${args.scope} storage key "${args.key}" no longer ` +
121
+ `matches its declared schema. Handle schema evolution explicitly: ` +
122
+ `widen the schema (z.union with the legacy shape) or migrate to a ` +
123
+ `versioned key, then rewrite the row via set(). Issues:\n${issues}`;
124
+ throw new StorageValidationError({
125
+ scope: args.scope,
126
+ key: args.key,
127
+ hint,
128
+ });
129
+ }
130
+ return result.data;
131
+ }
132
+ //# sourceMappingURL=storage-validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-validation.js","sourceRoot":"","sources":["../../src/context/storage-validation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared storage validator (ruling 1) — ONE implementation, used by the\n * `@ekanos/sdk/testing` mock today and the production `ctx.storage` wrapper\n * when host wiring lands, so the mock enforces exactly what prod enforces.\n */\nimport type { z } from 'zod';\n\nimport { StorageValidationError } from './errors';\nimport type {\n StorageKeyDeclaration,\n StorageKeyDeclarationInput,\n StorageSchemaMap,\n} from './types';\n\ntype StorageScope = 'account' | 'user';\n\n/**\n * Account data_types a partner may READ through `ctx.storage` but never\n * write or delete (authz-hardening finding C). `activation` rows are host\n * lifecycle state: the activation flow creates them and admin deactivation\n * removes them. A partner that could write `activation` (any subtype) could\n * re-create its own activation row after an admin deactivated it —\n * self-reactivation. Reads stay allowed: the capability-context proposal\n * (§2.1) deliberately exposes the non-secret activation payload through\n * `ctx.storage.account.get('activation')`.\n */\nconst READ_ONLY_ACCOUNT_DATA_TYPES = new Set(['activation']);\n\nfunction formatIssues(issues: readonly z.ZodIssue[]): string {\n return issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';\n return ` - ${path}: ${issue.message}`;\n })\n .join('\\n');\n}\n\n/**\n * Normalizes either declaration form — a bare zod schema, or a\n * `{ schema, clientReadable }` descriptor — into the resolved pair. ONE\n * implementation, so the mock, the production `ctx.storage` wrapper, and the\n * browser-facing storage route all read a declaration the same way.\n *\n * The zod-schema test is duck-typed on `safeParse` rather than `instanceof\n * z.ZodType` on purpose: a partner package may bundle its own zod copy, and a\n * cross-realm schema must not be misread as a descriptor (which would then\n * fail the descriptor branch and report a nonsense error).\n *\n * `clientReadable` FAILS CLOSED: the bare-schema form and an omitted flag\n * both resolve to `false`. Widening exposure is always an explicit act.\n */\nexport function resolveStorageKeyDeclaration(\n declaration: StorageKeyDeclarationInput,\n): { schema: z.ZodType; clientReadable: boolean } {\n if (\n typeof (declaration as { safeParse?: unknown } | null)?.safeParse ===\n 'function'\n ) {\n return { schema: declaration as z.ZodType, clientReadable: false };\n }\n\n const descriptor = declaration as StorageKeyDeclaration;\n\n return {\n schema: descriptor.schema,\n clientReadable: descriptor.clientReadable === true,\n };\n}\n\n/**\n * Whether a declared key opted in to the generic browser-readable storage\n * route. An UNDECLARED key and a declared-but-not-opted-in key both answer\n * `false` — the route must not distinguish them, or it becomes an existence\n * oracle for a partner's server-only key names.\n */\nexport function isStorageKeyClientReadable(args: {\n key: string;\n schemas: StorageSchemaMap | undefined;\n}): boolean {\n const declaration = args.schemas?.[args.key];\n if (!declaration) return false;\n\n return resolveStorageKeyDeclaration(declaration).clientReadable;\n}\n\n/**\n * Resolves the declared schema for a storage key, throwing\n * `StorageValidationError` when the key is undeclared (strict keys). Used\n * directly by `delete`, and by `parseStorageValue` for reads and writes.\n */\nexport function assertStorageKeyDeclared(args: {\n scope: StorageScope;\n key: string;\n schemas: StorageSchemaMap | undefined;\n}): z.ZodType {\n const declaration = args.schemas?.[args.key];\n if (!declaration) {\n throw new StorageValidationError({\n scope: args.scope,\n key: args.key,\n hint:\n `No schema is declared for ${args.scope} storage key \"${args.key}\". ` +\n `Storage keys are strict: declare the key and its zod schema in ` +\n `defineIntegration({ storage: { ${args.scope}: { '${args.key}': z.object({ … }) } } }) ` +\n `(or in createMockContext({ storageSchemas }) in tests) before reading, ` +\n `writing, or deleting it.`,\n });\n }\n\n return resolveStorageKeyDeclaration(declaration).schema;\n}\n\n/**\n * Rejects writes and deletes against host-managed read-only data_types\n * (finding C). ONE implementation for the `@ekanos/sdk/testing` mock and the\n * production `ctx.storage` wrapper, so a write that prod refuses also fails\n * in partner tests. Runs BEFORE the declared-key check so a read-only key\n * reports the real reason rather than \"undeclared\".\n */\nexport function assertStorageKeyWritable(args: {\n scope: StorageScope;\n key: string;\n}): void {\n if (args.scope !== 'account') return;\n\n const slashIndex = args.key.indexOf('/');\n const dataType = slashIndex === -1 ? args.key : args.key.slice(0, slashIndex);\n\n if (READ_ONLY_ACCOUNT_DATA_TYPES.has(dataType)) {\n throw new StorageValidationError({\n scope: args.scope,\n key: args.key,\n hint:\n `Storage key \"${args.key}\" uses the host-managed \"${dataType}\" ` +\n `data_type, which ctx.storage can read but never write or delete. ` +\n `Activation rows are created by the host activation flow and ` +\n `removed by deactivation — an integration cannot (re)create or ` +\n `drop its own activation. Persist integration state under a ` +\n `\"settings\", \"sync_state\", \"metrics_summary\", or \"cache\" key instead.`,\n });\n }\n}\n\n/**\n * Validates one storage value against its declared schema and returns the\n * schema's OUTPUT value (defaults and transforms applied). Throws\n * `StorageValidationError` on an undeclared key or a schema mismatch, with\n * the zod issues formatted into the remediation hint.\n */\nexport function parseStorageValue(args: {\n scope: StorageScope;\n key: string;\n operation: 'read' | 'write';\n schemas: StorageSchemaMap | undefined;\n value: unknown;\n}): unknown {\n const schema = assertStorageKeyDeclared(args);\n const result = schema.safeParse(args.value);\n\n if (!result.success) {\n const issues = formatIssues(result.error.issues);\n const hint =\n args.operation === 'write'\n ? `The value written to ${args.scope} storage key \"${args.key}\" fails ` +\n `its declared schema. Fix the write payload to match the schema ` +\n `declared in defineIntegration({ storage }):\\n${issues}`\n : `The data stored at ${args.scope} storage key \"${args.key}\" no longer ` +\n `matches its declared schema. Handle schema evolution explicitly: ` +\n `widen the schema (z.union with the legacy shape) or migrate to a ` +\n `versioned key, then rewrite the row via set(). Issues:\\n${issues}`;\n throw new StorageValidationError({\n scope: args.scope,\n key: args.key,\n hint,\n });\n }\n\n return result.data;\n}\n"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The capability-context types now live in `@ekanos/integration-schema` (the
3
+ * dependency-pure package both `@ekanos/sdk` and `@kit/integrations-core`
4
+ * depend on) so the canonical integration-definition schema can reference
5
+ * `IntegrationContext` without a package cycle. This file re-exports them so
6
+ * every existing `@ekanos/sdk` import path (`./types`, `../context/types`,
7
+ * the root barrel) keeps resolving unchanged.
8
+ *
9
+ * Spec: docs/devex/capability-context-proposal.md (all six §7 rulings).
10
+ */
11
+ export type { IntegrationActor, IntegrationContext, IntegrationStorage, ScopedStore, IntegrationSecrets, IntegrationFetch, IntegrationLogger, StorageEntry, StorageWriteOptions, StorageSchemas, StorageSchemaMap, StorageScopeSchemas, StorageKeyDeclaration, StorageKeyDeclarationInput, StorageKeySchema, } from '@ekanos/integration-schema';
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/context/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The capability-context types now live in `@ekanos/integration-schema` (the\n * dependency-pure package both `@ekanos/sdk` and `@kit/integrations-core`\n * depend on) so the canonical integration-definition schema can reference\n * `IntegrationContext` without a package cycle. This file re-exports them so\n * every existing `@ekanos/sdk` import path (`./types`, `../context/types`,\n * the root barrel) keeps resolving unchanged.\n *\n * Spec: docs/devex/capability-context-proposal.md (all six §7 rulings).\n */\nexport type {\n IntegrationActor,\n IntegrationContext,\n IntegrationStorage,\n ScopedStore,\n IntegrationSecrets,\n IntegrationFetch,\n IntegrationLogger,\n StorageEntry,\n StorageWriteOptions,\n StorageSchemas,\n StorageSchemaMap,\n StorageScopeSchemas,\n StorageKeyDeclaration,\n StorageKeyDeclarationInput,\n StorageKeySchema,\n} from '@ekanos/integration-schema';\n"]}
@@ -0,0 +1,80 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * The host-injection seam for integration activation (the SDK inversion,
4
+ * DEVELOPER_EXPERIENCE_PLAN.md §5.1).
5
+ *
6
+ * A published SDK cannot import the host's `'use server'` activation actions
7
+ * — that import is what used to drag `server-only` (via `@kit/next/actions`)
8
+ * into every partner client graph and broke partner tests on day one
9
+ * (sdk-export-map.md, BLOCKER 2). Instead the HOST binds its real server
10
+ * actions once, at the root of its client tree:
11
+ *
12
+ * ```tsx
13
+ * // host chrome (e.g. @kit/integrations-core's bridge component)
14
+ * <IntegrationActivationProvider actions={{ activate, deactivate }}>
15
+ * {children}
16
+ * </IntegrationActivationProvider>
17
+ * ```
18
+ *
19
+ * and `useActivateIntegration()` / `useDeactivateIntegration()` keep their
20
+ * zero-argument call signature everywhere — first-party integrations,
21
+ * examples, and partner code are byte-identical before and after the
22
+ * inversion. Server-action references are serializable client values in
23
+ * React 19, so passing them through context is sanctioned.
24
+ *
25
+ * Outside a provider the hooks reject AT CALL TIME with an actionable
26
+ * error (mirroring `useAskAssistant`'s no-op-outside-provider contract,
27
+ * but loud rather than silent — a swallowed activation would look like a
28
+ * hung form).
29
+ */
30
+ /** Input accepted by the host's activate action (BaseActivationSchema). */
31
+ export interface ActivateIntegrationInput {
32
+ accountId: string;
33
+ sourceId?: string;
34
+ productId?: string;
35
+ integrationSlug: string;
36
+ activationData?: unknown;
37
+ /**
38
+ * The TEAM account context the user activated within — separate from
39
+ * `accountId`, which for `scope: 'user'` integrations is the personal
40
+ * account. Optional; the host falls back to `accountId` when absent.
41
+ */
42
+ teamAccountId?: string;
43
+ /** The host schema is passthrough: extra integration fields ride along. */
44
+ [key: string]: unknown;
45
+ }
46
+ /** Input accepted by the host's deactivate action (DeactivationSchema). */
47
+ export interface DeactivateIntegrationInput {
48
+ accountId: string;
49
+ productId?: string;
50
+ integrationSlug: string;
51
+ [key: string]: unknown;
52
+ }
53
+ /**
54
+ * The host action envelope. Host actions return richer payloads; the SDK
55
+ * only relies on the `success` / `errorMessage` pair.
56
+ */
57
+ export type IntegrationActionResult = {
58
+ success?: boolean;
59
+ errorMessage?: string;
60
+ } & Record<string, unknown>;
61
+ export type ActivateIntegrationAction = (input: ActivateIntegrationInput) => Promise<IntegrationActionResult | undefined>;
62
+ export type DeactivateIntegrationAction = (input: DeactivateIntegrationInput) => Promise<IntegrationActionResult | undefined>;
63
+ export interface IntegrationActivationActions {
64
+ activate: ActivateIntegrationAction;
65
+ deactivate: DeactivateIntegrationAction;
66
+ }
67
+ /**
68
+ * Host-mounted provider that binds the real activation server actions.
69
+ * Mounted once at the root of the host's client tree (apps/web root
70
+ * providers; the harness mounts it with fixture actions).
71
+ */
72
+ export declare function IntegrationActivationProvider({ actions, children, }: {
73
+ actions: IntegrationActivationActions;
74
+ children: ReactNode;
75
+ }): import("react").JSX.Element;
76
+ /**
77
+ * Internal: resolve the host-bound actions, or a pair that rejects loudly.
78
+ * Not exported from the entrypoint — hooks and the OAuth form consume it.
79
+ */
80
+ export declare function useIntegrationActivationActions(): IntegrationActivationActions;
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, use, useMemo } from 'react';
4
+ const IntegrationActivationContext = createContext(null);
5
+ /**
6
+ * Host-mounted provider that binds the real activation server actions.
7
+ * Mounted once at the root of the host's client tree (apps/web root
8
+ * providers; the harness mounts it with fixture actions).
9
+ */
10
+ export function IntegrationActivationProvider({ actions, children, }) {
11
+ const value = useMemo(() => ({ activate: actions.activate, deactivate: actions.deactivate }), [actions.activate, actions.deactivate]);
12
+ return (_jsx(IntegrationActivationContext, { value: value, children: children }));
13
+ }
14
+ const MISSING_PROVIDER_MESSAGE = 'No IntegrationActivationProvider is mounted. The host application must ' +
15
+ 'bind its activation server actions (see @ekanos/sdk/hooks ' +
16
+ 'IntegrationActivationProvider) above any component that activates ' +
17
+ 'integrations.';
18
+ /**
19
+ * Internal: resolve the host-bound actions, or a pair that rejects loudly.
20
+ * Not exported from the entrypoint — hooks and the OAuth form consume it.
21
+ */
22
+ export function useIntegrationActivationActions() {
23
+ const context = use(IntegrationActivationContext);
24
+ return useMemo(() => {
25
+ if (context)
26
+ return context;
27
+ const reject = () => Promise.reject(new Error(MISSING_PROVIDER_MESSAGE));
28
+ return { activate: reject, deactivate: reject };
29
+ }, [context]);
30
+ }
31
+ //# sourceMappingURL=activation-actions-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activation-actions-context.js","sourceRoot":"","sources":["../../src/hooks/activation-actions-context.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAkB,aAAa,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AA8EpE,MAAM,4BAA4B,GAChC,aAAa,CAAsC,IAAI,CAAC,CAAC;AAE3D;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,EAC5C,OAAO,EACP,QAAQ,GAIT;IACC,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,EACtE,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CACvC,CAAC;IAEF,OAAO,CACL,KAAC,4BAA4B,IAAC,KAAK,EAAE,KAAK,YACvC,QAAQ,GACoB,CAChC,CAAC;AACJ,CAAC;AAED,MAAM,wBAAwB,GAC5B,yEAAyE;IACzE,4DAA4D;IAC5D,oEAAoE;IACpE,eAAe,CAAC;AAElB;;;GAGG;AACH,MAAM,UAAU,+BAA+B;IAC7C,MAAM,OAAO,GAAG,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAElD,OAAO,OAAO,CAAC,GAAG,EAAE;QAClB,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;QAE5B,MAAM,MAAM,GAAG,GAAG,EAAE,CAClB,OAAO,CAAC,MAAM,CACZ,IAAI,KAAK,CAAC,wBAAwB,CAAC,CACpC,CAAC;QAEJ,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IAClD,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;AAChB,CAAC","sourcesContent":["'use client';\n\nimport { type ReactNode, createContext, use, useMemo } from 'react';\n\n/**\n * The host-injection seam for integration activation (the SDK inversion,\n * DEVELOPER_EXPERIENCE_PLAN.md §5.1).\n *\n * A published SDK cannot import the host's `'use server'` activation actions\n * — that import is what used to drag `server-only` (via `@kit/next/actions`)\n * into every partner client graph and broke partner tests on day one\n * (sdk-export-map.md, BLOCKER 2). Instead the HOST binds its real server\n * actions once, at the root of its client tree:\n *\n * ```tsx\n * // host chrome (e.g. @kit/integrations-core's bridge component)\n * <IntegrationActivationProvider actions={{ activate, deactivate }}>\n * {children}\n * </IntegrationActivationProvider>\n * ```\n *\n * and `useActivateIntegration()` / `useDeactivateIntegration()` keep their\n * zero-argument call signature everywhere — first-party integrations,\n * examples, and partner code are byte-identical before and after the\n * inversion. Server-action references are serializable client values in\n * React 19, so passing them through context is sanctioned.\n *\n * Outside a provider the hooks reject AT CALL TIME with an actionable\n * error (mirroring `useAskAssistant`'s no-op-outside-provider contract,\n * but loud rather than silent — a swallowed activation would look like a\n * hung form).\n */\n\n/** Input accepted by the host's activate action (BaseActivationSchema). */\nexport interface ActivateIntegrationInput {\n accountId: string;\n sourceId?: string;\n productId?: string;\n integrationSlug: string;\n activationData?: unknown;\n /**\n * The TEAM account context the user activated within — separate from\n * `accountId`, which for `scope: 'user'` integrations is the personal\n * account. Optional; the host falls back to `accountId` when absent.\n */\n teamAccountId?: string;\n /** The host schema is passthrough: extra integration fields ride along. */\n [key: string]: unknown;\n}\n\n/** Input accepted by the host's deactivate action (DeactivationSchema). */\nexport interface DeactivateIntegrationInput {\n accountId: string;\n productId?: string;\n integrationSlug: string;\n [key: string]: unknown;\n}\n\n/**\n * The host action envelope. Host actions return richer payloads; the SDK\n * only relies on the `success` / `errorMessage` pair.\n */\nexport type IntegrationActionResult = {\n success?: boolean;\n errorMessage?: string;\n} & Record<string, unknown>;\n\nexport type ActivateIntegrationAction = (\n input: ActivateIntegrationInput,\n) => Promise<IntegrationActionResult | undefined>;\n\nexport type DeactivateIntegrationAction = (\n input: DeactivateIntegrationInput,\n) => Promise<IntegrationActionResult | undefined>;\n\nexport interface IntegrationActivationActions {\n activate: ActivateIntegrationAction;\n deactivate: DeactivateIntegrationAction;\n}\n\nconst IntegrationActivationContext =\n createContext<IntegrationActivationActions | null>(null);\n\n/**\n * Host-mounted provider that binds the real activation server actions.\n * Mounted once at the root of the host's client tree (apps/web root\n * providers; the harness mounts it with fixture actions).\n */\nexport function IntegrationActivationProvider({\n actions,\n children,\n}: {\n actions: IntegrationActivationActions;\n children: ReactNode;\n}) {\n const value = useMemo(\n () => ({ activate: actions.activate, deactivate: actions.deactivate }),\n [actions.activate, actions.deactivate],\n );\n\n return (\n <IntegrationActivationContext value={value}>\n {children}\n </IntegrationActivationContext>\n );\n}\n\nconst MISSING_PROVIDER_MESSAGE =\n 'No IntegrationActivationProvider is mounted. The host application must ' +\n 'bind its activation server actions (see @ekanos/sdk/hooks ' +\n 'IntegrationActivationProvider) above any component that activates ' +\n 'integrations.';\n\n/**\n * Internal: resolve the host-bound actions, or a pair that rejects loudly.\n * Not exported from the entrypoint — hooks and the OAuth form consume it.\n */\nexport function useIntegrationActivationActions(): IntegrationActivationActions {\n const context = use(IntegrationActivationContext);\n\n return useMemo(() => {\n if (context) return context;\n\n const reject = () =>\n Promise.reject<IntegrationActionResult>(\n new Error(MISSING_PROVIDER_MESSAGE),\n );\n\n return { activate: reject, deactivate: reject };\n }, [context]);\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { WidgetAskContext } from '../types/widget-ask-context.js';
3
+ type AskAssistantHandler = (ctx: WidgetAskContext) => void;
4
+ interface AskAssistantProviderProps {
5
+ children: ReactNode;
6
+ }
7
+ /**
8
+ * Hosts the bridge between widget ask-bars and the AI assistant dock.
9
+ *
10
+ * Widgets call `useAskAssistant()` to get a stable dispatcher. The dock
11
+ * registers its actual handler via `useRegisterAskAssistant()` once it
12
+ * has access to its chat hook. Until a handler is registered (e.g. on
13
+ * routes without a dock), calls are no-ops.
14
+ */
15
+ export declare function AskAssistantProvider({ children }: AskAssistantProviderProps): import("react").JSX.Element;
16
+ /**
17
+ * Returns a stable dispatcher widgets can call to send a question about
18
+ * themselves to the assistant. Outside an `AskAssistantProvider` (e.g.
19
+ * widget preview pages), this is a no-op.
20
+ */
21
+ export declare function useAskAssistant(): AskAssistantHandler;
22
+ /**
23
+ * Installs the dock-side handler that the widget dispatcher will call.
24
+ * Re-registers whenever `handler` changes; clears on unmount.
25
+ *
26
+ * Uses useLayoutEffect so the handler is wired up before the browser
27
+ * paints the dock — otherwise a chip click that fires during the first
28
+ * paint frame (e.g. user opens a drawer fast) finds a null handler and
29
+ * silently no-ops.
30
+ */
31
+ export declare function useRegisterAskAssistant(handler: AskAssistantHandler): void;
32
+ export {};
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, use, useCallback, useLayoutEffect, useMemo, useRef, } from 'react';
4
+ const AskAssistantContext = createContext(null);
5
+ /**
6
+ * Hosts the bridge between widget ask-bars and the AI assistant dock.
7
+ *
8
+ * Widgets call `useAskAssistant()` to get a stable dispatcher. The dock
9
+ * registers its actual handler via `useRegisterAskAssistant()` once it
10
+ * has access to its chat hook. Until a handler is registered (e.g. on
11
+ * routes without a dock), calls are no-ops.
12
+ */
13
+ export function AskAssistantProvider({ children }) {
14
+ const handlerRef = useRef(null);
15
+ const ask = useCallback((ctx) => {
16
+ var _a;
17
+ (_a = handlerRef.current) === null || _a === void 0 ? void 0 : _a.call(handlerRef, ctx);
18
+ }, []);
19
+ const registerHandler = useCallback((handler) => {
20
+ handlerRef.current = handler;
21
+ }, []);
22
+ const value = useMemo(() => ({ ask, registerHandler }), [ask, registerHandler]);
23
+ return _jsx(AskAssistantContext, { value: value, children: children });
24
+ }
25
+ const NOOP_ASK = () => { };
26
+ /**
27
+ * Returns a stable dispatcher widgets can call to send a question about
28
+ * themselves to the assistant. Outside an `AskAssistantProvider` (e.g.
29
+ * widget preview pages), this is a no-op.
30
+ */
31
+ export function useAskAssistant() {
32
+ var _a;
33
+ const context = use(AskAssistantContext);
34
+ return (_a = context === null || context === void 0 ? void 0 : context.ask) !== null && _a !== void 0 ? _a : NOOP_ASK;
35
+ }
36
+ /**
37
+ * Installs the dock-side handler that the widget dispatcher will call.
38
+ * Re-registers whenever `handler` changes; clears on unmount.
39
+ *
40
+ * Uses useLayoutEffect so the handler is wired up before the browser
41
+ * paints the dock — otherwise a chip click that fires during the first
42
+ * paint frame (e.g. user opens a drawer fast) finds a null handler and
43
+ * silently no-ops.
44
+ */
45
+ export function useRegisterAskAssistant(handler) {
46
+ const context = use(AskAssistantContext);
47
+ useLayoutEffect(() => {
48
+ if (!context)
49
+ return;
50
+ context.registerHandler(handler);
51
+ return () => {
52
+ context.registerHandler(null);
53
+ };
54
+ }, [context, handler]);
55
+ }
56
+ //# sourceMappingURL=ask-assistant-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask-assistant-context.js","sourceRoot":"","sources":["../../src/hooks/ask-assistant-context.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAEL,aAAa,EACb,GAAG,EACH,WAAW,EACX,eAAe,EACf,OAAO,EACP,MAAM,GACP,MAAM,OAAO,CAAC;AAaf,MAAM,mBAAmB,GAAG,aAAa,CACvC,IAAI,CACL,CAAC;AAMF;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAE,QAAQ,EAA6B;IAC1E,MAAM,UAAU,GAAG,MAAM,CAA6B,IAAI,CAAC,CAAC;IAE5D,MAAM,GAAG,GAAG,WAAW,CAAsB,CAAC,GAAG,EAAE,EAAE;;QACnD,MAAA,UAAU,CAAC,OAAO,2DAAG,GAAG,CAAC,CAAC;IAC5B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,eAAe,GAAG,WAAW,CAAC,CAAC,OAAmC,EAAE,EAAE;QAC1E,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAC/B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,CAAC,EAChC,CAAC,GAAG,EAAE,eAAe,CAAC,CACvB,CAAC;IAEF,OAAO,KAAC,mBAAmB,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAuB,CAAC;AAC7E,CAAC;AAED,MAAM,QAAQ,GAAwB,GAAG,EAAE,GAAE,CAAC,CAAC;AAE/C;;;;GAIG;AACH,MAAM,UAAU,eAAe;;IAC7B,MAAM,OAAO,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACzC,OAAO,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,GAAG,mCAAI,QAAQ,CAAC;AAClC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAA4B;IAClE,MAAM,OAAO,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAEzC,eAAe,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QACjC,OAAO,GAAG,EAAE;YACV,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AACzB,CAAC","sourcesContent":["'use client';\n\nimport {\n type ReactNode,\n createContext,\n use,\n useCallback,\n useLayoutEffect,\n useMemo,\n useRef,\n} from 'react';\n\nimport type { WidgetAskContext } from '../types/widget-ask-context';\n\ntype AskAssistantHandler = (ctx: WidgetAskContext) => void;\n\ninterface AskAssistantContextValue {\n /** Stable dispatcher widgets call. Routes to the registered handler at invocation time. */\n ask: AskAssistantHandler;\n /** Internal — used by the dock side of the bridge to install its handler. */\n registerHandler: (handler: AskAssistantHandler | null) => void;\n}\n\nconst AskAssistantContext = createContext<AskAssistantContextValue | null>(\n null,\n);\n\ninterface AskAssistantProviderProps {\n children: ReactNode;\n}\n\n/**\n * Hosts the bridge between widget ask-bars and the AI assistant dock.\n *\n * Widgets call `useAskAssistant()` to get a stable dispatcher. The dock\n * registers its actual handler via `useRegisterAskAssistant()` once it\n * has access to its chat hook. Until a handler is registered (e.g. on\n * routes without a dock), calls are no-ops.\n */\nexport function AskAssistantProvider({ children }: AskAssistantProviderProps) {\n const handlerRef = useRef<AskAssistantHandler | null>(null);\n\n const ask = useCallback<AskAssistantHandler>((ctx) => {\n handlerRef.current?.(ctx);\n }, []);\n\n const registerHandler = useCallback((handler: AskAssistantHandler | null) => {\n handlerRef.current = handler;\n }, []);\n\n const value = useMemo<AskAssistantContextValue>(\n () => ({ ask, registerHandler }),\n [ask, registerHandler],\n );\n\n return <AskAssistantContext value={value}>{children}</AskAssistantContext>;\n}\n\nconst NOOP_ASK: AskAssistantHandler = () => {};\n\n/**\n * Returns a stable dispatcher widgets can call to send a question about\n * themselves to the assistant. Outside an `AskAssistantProvider` (e.g.\n * widget preview pages), this is a no-op.\n */\nexport function useAskAssistant(): AskAssistantHandler {\n const context = use(AskAssistantContext);\n return context?.ask ?? NOOP_ASK;\n}\n\n/**\n * Installs the dock-side handler that the widget dispatcher will call.\n * Re-registers whenever `handler` changes; clears on unmount.\n *\n * Uses useLayoutEffect so the handler is wired up before the browser\n * paints the dock — otherwise a chip click that fires during the first\n * paint frame (e.g. user opens a drawer fast) finds a null handler and\n * silently no-ops.\n */\nexport function useRegisterAskAssistant(handler: AskAssistantHandler): void {\n const context = use(AskAssistantContext);\n\n useLayoutEffect(() => {\n if (!context) return;\n context.registerHandler(handler);\n return () => {\n context.registerHandler(null);\n };\n }, [context, handler]);\n}\n"]}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The browser half of the generic storage bridge.
3
+ *
4
+ * `ctx.storage` is a server capability and widgets are client components, so
5
+ * a saved setting has to round-trip through HTTP. The host serves that
6
+ * round-trip once, generically, at
7
+ * `GET /api/integrations/[slug]/storage?accountId=…&keys=a,b`; this is the
8
+ * client that calls it, so an integration never hand-writes the URL, the
9
+ * query string, or the not-ok handling.
10
+ *
11
+ * Only keys whose declaration opted in with `clientReadable: true` come back.
12
+ * A key that did not opt in, was never declared, or simply has no stored row
13
+ * is ABSENT from the result — all three are indistinguishable by design, so
14
+ * callers must treat a missing key as "no value", never as an error.
15
+ *
16
+ * Values are returned raw and unvalidated: the integration owns the shape of
17
+ * its own storage, so it validates with the same zod schemas it declared.
18
+ * See the Open-Meteo example's `fetchWeatherSettings` for the pattern.
19
+ */
20
+ export interface FetchIntegrationStorageOptions {
21
+ /** The account whose storage to read. Must be one the caller belongs to. */
22
+ accountId: string;
23
+ /** Declared, `clientReadable` account-scope keys, e.g. `['settings/units']`. */
24
+ keys: readonly string[];
25
+ signal?: AbortSignal;
26
+ /**
27
+ * Overrides the base path. Exists for harnesses and tests that serve the
28
+ * route from somewhere other than the host's own origin; production code
29
+ * should leave it alone.
30
+ */
31
+ baseUrl?: string;
32
+ }
33
+ /**
34
+ * Builds the host storage-route URL for one integration. Exported because a
35
+ * harness or a test double needs to recognize the exact URL this client will
36
+ * request.
37
+ */
38
+ export declare function integrationStorageUrl(integrationSlug: string, options: Pick<FetchIntegrationStorageOptions, 'accountId' | 'keys'> & {
39
+ baseUrl?: string;
40
+ }): string;
41
+ /**
42
+ * Reads declared, client-readable account-scope storage for one integration.
43
+ *
44
+ * Throws when the request itself fails — a 403 (not signed in, not a member,
45
+ * not activated, or no such integration, all deliberately indistinguishable)
46
+ * or a transport error. It does NOT throw for a key that came back absent.
47
+ */
48
+ export declare function fetchIntegrationStorage(integrationSlug: string, options: FetchIntegrationStorageOptions): Promise<Record<string, unknown>>;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The browser half of the generic storage bridge.
3
+ *
4
+ * `ctx.storage` is a server capability and widgets are client components, so
5
+ * a saved setting has to round-trip through HTTP. The host serves that
6
+ * round-trip once, generically, at
7
+ * `GET /api/integrations/[slug]/storage?accountId=…&keys=a,b`; this is the
8
+ * client that calls it, so an integration never hand-writes the URL, the
9
+ * query string, or the not-ok handling.
10
+ *
11
+ * Only keys whose declaration opted in with `clientReadable: true` come back.
12
+ * A key that did not opt in, was never declared, or simply has no stored row
13
+ * is ABSENT from the result — all three are indistinguishable by design, so
14
+ * callers must treat a missing key as "no value", never as an error.
15
+ *
16
+ * Values are returned raw and unvalidated: the integration owns the shape of
17
+ * its own storage, so it validates with the same zod schemas it declared.
18
+ * See the Open-Meteo example's `fetchWeatherSettings` for the pattern.
19
+ */
20
+ /** Mirrors the host route's own per-request cap. */
21
+ const MAX_KEYS_PER_REQUEST = 20;
22
+ /**
23
+ * Builds the host storage-route URL for one integration. Exported because a
24
+ * harness or a test double needs to recognize the exact URL this client will
25
+ * request.
26
+ */
27
+ export function integrationStorageUrl(integrationSlug, options) {
28
+ var _a;
29
+ const query = new URLSearchParams({
30
+ accountId: options.accountId,
31
+ keys: options.keys.join(','),
32
+ });
33
+ const base = (_a = options.baseUrl) !== null && _a !== void 0 ? _a : '/api/integrations';
34
+ return `${base}/${integrationSlug}/storage?${query}`;
35
+ }
36
+ /**
37
+ * Reads declared, client-readable account-scope storage for one integration.
38
+ *
39
+ * Throws when the request itself fails — a 403 (not signed in, not a member,
40
+ * not activated, or no such integration, all deliberately indistinguishable)
41
+ * or a transport error. It does NOT throw for a key that came back absent.
42
+ */
43
+ export async function fetchIntegrationStorage(integrationSlug, options) {
44
+ var _a;
45
+ if (options.keys.length === 0) {
46
+ throw new Error(`fetchIntegrationStorage('${integrationSlug}') needs at least one storage key.`);
47
+ }
48
+ if (options.keys.length > MAX_KEYS_PER_REQUEST) {
49
+ throw new Error(`fetchIntegrationStorage('${integrationSlug}') was asked for ${options.keys.length} keys; ` +
50
+ `the host route reads at most ${MAX_KEYS_PER_REQUEST} per request. Split the read.`);
51
+ }
52
+ const url = integrationStorageUrl(integrationSlug, options);
53
+ const response = await globalThis.fetch(url, {
54
+ signal: (_a = options.signal) !== null && _a !== void 0 ? _a : null,
55
+ headers: { accept: 'application/json' },
56
+ });
57
+ if (!response.ok) {
58
+ throw new Error(`Could not read '${integrationSlug}' storage (${response.status} ${response.statusText}).`);
59
+ }
60
+ const payload = await response.json();
61
+ if (payload === null ||
62
+ typeof payload !== 'object' ||
63
+ Array.isArray(payload)) {
64
+ throw new Error(`The '${integrationSlug}' storage route returned a non-object payload.`);
65
+ }
66
+ return payload;
67
+ }
68
+ //# sourceMappingURL=fetch-integration-storage.js.map