@ekanos/sdk 0.7.0 → 0.8.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.
- package/README.md +42 -10
- package/api-report.md +19 -2
- package/dist/components/base-activation-dialog.js +2 -2
- package/dist/components/base-activation-dialog.js.map +1 -1
- package/dist/components/base-activation-form.js +2 -2
- package/dist/components/base-activation-form.js.map +1 -1
- package/dist/components/base-marketplace-tile.js +4 -2
- package/dist/components/base-marketplace-tile.js.map +1 -1
- package/dist/components/widgets/ai-prompt-chip.js +4 -2
- package/dist/components/widgets/ai-prompt-chip.js.map +1 -1
- package/dist/components/widgets/widget-ask-bar.js +3 -1
- package/dist/components/widgets/widget-ask-bar.js.map +1 -1
- package/dist/components/widgets/widget-state-components.js +4 -4
- package/dist/components/widgets/widget-state-components.js.map +1 -1
- package/dist/components/widgets/widget.d.ts +1 -6
- package/dist/components/widgets/widget.js +8 -15
- package/dist/components/widgets/widget.js.map +1 -1
- package/dist/hooks/partner-oauth.js +4 -1
- package/dist/hooks/partner-oauth.js.map +1 -1
- package/dist/integration/define-agent-event.d.ts +18 -0
- package/dist/integration/define-agent-event.js +20 -0
- package/dist/integration/define-agent-event.js.map +1 -0
- package/dist/integration/event-surfaces.js +12 -7
- package/dist/integration/event-surfaces.js.map +1 -1
- package/dist/integration/index.d.ts +3 -2
- package/dist/integration/index.js +3 -1
- package/dist/integration/index.js.map +1 -1
- package/dist/types/integration.d.ts +3 -3
- package/dist/types/integration.js.map +1 -1
- package/dist/types/workspace-target.d.ts +6 -0
- package/dist/types/workspace-target.js.map +1 -1
- package/eslint.design-preset.mjs +7 -5
- package/eslint.design-rules.mjs +60 -3
- package/package.json +6 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isEgressAllowed } from '../context/egress.js';
|
|
2
2
|
import { parseCronExpression } from './cron.js';
|
|
3
3
|
export function assertEventSurfacesValid(definition) {
|
|
4
|
-
var _a, _b;
|
|
4
|
+
var _a, _b, _c;
|
|
5
5
|
const problems = [];
|
|
6
6
|
for (const schedule of (_a = definition.schedules) !== null && _a !== void 0 ? _a : []) {
|
|
7
7
|
try {
|
|
@@ -11,11 +11,16 @@ export function assertEventSurfacesValid(definition) {
|
|
|
11
11
|
problems.push(`schedules["${schedule.id}"]: ${error instanceof Error ? error.message : String(error)}`);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Every OAuth connection's endpoints, the main one and any named ones.
|
|
15
|
+
const connections = [
|
|
16
|
+
...(definition.oauth ? [['oauth', definition.oauth]] : []),
|
|
17
|
+
...Object.entries((_b = definition.oauthConnections) !== null && _b !== void 0 ? _b : {}).map(([id, connection]) => [`oauthConnections.${id}`, connection]),
|
|
18
|
+
];
|
|
19
|
+
const egress = (_c = definition.egress) !== null && _c !== void 0 ? _c : [];
|
|
20
|
+
for (const [path, connection] of connections) {
|
|
16
21
|
const endpoints = [
|
|
17
|
-
['authorizationUrl',
|
|
18
|
-
['tokenUrl',
|
|
22
|
+
['authorizationUrl', connection.provider.authorizationUrl],
|
|
23
|
+
['tokenUrl', connection.provider.tokenUrl],
|
|
19
24
|
];
|
|
20
25
|
for (const [field, url] of endpoints) {
|
|
21
26
|
let covered = false;
|
|
@@ -23,12 +28,12 @@ export function assertEventSurfacesValid(definition) {
|
|
|
23
28
|
covered = isEgressAllowed(url, egress);
|
|
24
29
|
}
|
|
25
30
|
catch (error) {
|
|
26
|
-
problems.push(
|
|
31
|
+
problems.push(`${path}.provider.${field}: ${error instanceof Error ? error.message : String(error)}`);
|
|
27
32
|
continue;
|
|
28
33
|
}
|
|
29
34
|
if (!covered) {
|
|
30
35
|
const origin = new URL(url).origin;
|
|
31
|
-
problems.push(
|
|
36
|
+
problems.push(`${path}.provider.${field}: its origin ${origin} is not covered by ` +
|
|
32
37
|
`this definition's egress list ${JSON.stringify(egress)}. The ` +
|
|
33
38
|
`OAuth code exchange runs through the integration's declared ` +
|
|
34
39
|
`egress, so add "${origin}" (or a covering "*." wildcard entry) ` +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-surfaces.js","sourceRoot":"","sources":["../../src/integration/event-surfaces.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAE7C,MAAM,UAAU,wBAAwB,CACtC,UAAiC;;IAEjC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,QAAQ,UAAI,UAAU,CAAC,SAAS,mCAAI,EAAE,EAAE,CAAC;QAClD,IAAI,CAAC;YACH,mBAAmB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CACX,cAAc,QAAQ,CAAC,EAAE,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACzF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,
|
|
1
|
+
{"version":3,"file":"event-surfaces.js","sourceRoot":"","sources":["../../src/integration/event-surfaces.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAE7C,MAAM,UAAU,wBAAwB,CACtC,UAAiC;;IAEjC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,QAAQ,UAAI,UAAU,CAAC,SAAS,mCAAI,EAAE,EAAE,CAAC;QAClD,IAAI,CAAC;YACH,mBAAmB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CACX,cAAc,QAAQ,CAAC,EAAE,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACzF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,MAAM,WAAW,GAAG;QAClB,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,UAAU,CAAC,KAAK,CAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,GAAG,MAAM,CAAC,OAAO,OAAC,UAAU,CAAC,gBAAgB,mCAAI,EAAE,CAAC,CAAC,GAAG,CACtD,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,oBAAoB,EAAE,EAAE,EAAE,UAAU,CAAU,CACtE;KACF,CAAC;IACF,MAAM,MAAM,SAAG,UAAU,CAAC,MAAM,mCAAI,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,WAAW,EAAE,CAAC;QAC7C,MAAM,SAAS,GAAG;YAChB,CAAC,kBAAkB,EAAE,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAC1D,CAAC,UAAU,EAAE,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;SAClC,CAAC;QAEX,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,SAAS,EAAE,CAAC;YACrC,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,IAAI,CAAC;gBACH,OAAO,GAAG,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACzC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,QAAQ,CAAC,IAAI,CACX,GAAG,IAAI,aAAa,KAAK,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACvF,CAAC;gBACF,SAAS;YACX,CAAC;YACD,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;gBACnC,QAAQ,CAAC,IAAI,CACX,GAAG,IAAI,aAAa,KAAK,gBAAgB,MAAM,qBAAqB;oBAClE,iCAAiC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ;oBAC/D,8DAA8D;oBAC9D,mBAAmB,MAAM,wCAAwC;oBACjE,+DAA+D;oBAC/D,wBAAwB,CAC3B,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,uCAAuC,UAAU,CAAC,IAAI,MAAM;YAC1D,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACnD,CAAC;IACJ,CAAC;AACH,CAAC","sourcesContent":["/**\n * SDK-layer validation of the three event surfaces (webhooks, schedules,\n * OAuth) — the cross-field rules the dependency-pure schema package cannot\n * own, layered on top of the canonical parse the same way\n * `parsePartnerIntegration` layers host workspace rules:\n *\n * - every `schedules[].cron` must be valid 5-field cron syntax\n * (`parseCronExpression`);\n * - the OAuth endpoints' origins must be covered by the definition's own\n * `egress` list, because the code exchange runs through the integration's\n * declared egress — an origin the reviewer never saw is an origin the\n * exchange must not reach.\n */\nimport type { IntegrationDefinition } from '@ekanos/integration-schema';\n\nimport { isEgressAllowed } from '../context/egress';\nimport { parseCronExpression } from './cron';\n\nexport function assertEventSurfacesValid(\n definition: IntegrationDefinition,\n): void {\n const problems: string[] = [];\n\n for (const schedule of definition.schedules ?? []) {\n try {\n parseCronExpression(schedule.cron);\n } catch (error) {\n problems.push(\n `schedules[\"${schedule.id}\"]: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n // Every OAuth connection's endpoints, the main one and any named ones.\n const connections = [\n ...(definition.oauth ? [['oauth', definition.oauth] as const] : []),\n ...Object.entries(definition.oauthConnections ?? {}).map(\n ([id, connection]) => [`oauthConnections.${id}`, connection] as const,\n ),\n ];\n const egress = definition.egress ?? [];\n for (const [path, connection] of connections) {\n const endpoints = [\n ['authorizationUrl', connection.provider.authorizationUrl],\n ['tokenUrl', connection.provider.tokenUrl],\n ] as const;\n\n for (const [field, url] of endpoints) {\n let covered = false;\n try {\n covered = isEgressAllowed(url, egress);\n } catch (error) {\n problems.push(\n `${path}.provider.${field}: ${error instanceof Error ? error.message : String(error)}`,\n );\n continue;\n }\n if (!covered) {\n const origin = new URL(url).origin;\n problems.push(\n `${path}.provider.${field}: its origin ${origin} is not covered by ` +\n `this definition's egress list ${JSON.stringify(egress)}. The ` +\n `OAuth code exchange runs through the integration's declared ` +\n `egress, so add \"${origin}\" (or a covering \"*.\" wildcard entry) ` +\n `to \\`egress\\` — widening egress is a security-posture change ` +\n `reviewed at promotion.`,\n );\n }\n }\n }\n\n if (problems.length > 0) {\n throw new Error(\n `Invalid integration definition for \"${definition.slug}\":\\n` +\n problems.map((line) => ` - ${line}`).join('\\n'),\n );\n }\n}\n"]}
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
* api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.
|
|
16
16
|
*/
|
|
17
17
|
export { defineIntegration } from './define-integration.js';
|
|
18
|
+
export { defineAgentEvent } from './define-agent-event.js';
|
|
18
19
|
export { parseCronExpression } from './cron.js';
|
|
19
20
|
export { PublicActionError } from '../errors/public-action-error.js';
|
|
20
21
|
export { ADMIN_HIERARCHY_LEVEL, OWNER_HIERARCHY_LEVEL, actorIsAtLeast, } from './actor-role.js';
|
|
21
22
|
export { DEFAULT_UNTRUSTED_TEXT_MAX_CHARS, untrustedText, untrustedTextNote, } from './untrusted-text.js';
|
|
22
|
-
export { DEFAULT_SIGNATURE_TOLERANCE_SECONDS, IntegrationDefinitionSchema, PARTNER_STORAGE_LIMITS, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
|
|
23
|
-
export type { IntegrationDefinition, IntegrationComponentDeclarations, IntegrationProposals, IntegrationCapabilityDeclaration, IntegrationPermissionDeclaration, PartnerWidgetDeclaration, PartnerToolModule, PartnerToolParameters, PartnerActionDeclaration, ToolClassificationProposal, DefinitionCollisionInput, FirstPartyInventory, PartnerWebhookDeclaration, WebhookSignatureDeclaration, WebhookSignatureScheme, WebhookEvent, WebhookResult, PartnerScheduleDeclaration, ScheduleInvocation, ScheduleResult, PartnerOAuthDeclaration, OAuthProviderDeclaration, OAuthTokens, OnActivateHandler, AttentionHandler, AttentionSignal, WidgetVerdict, WidgetVerdictTone, } from '@ekanos/integration-schema';
|
|
23
|
+
export { DEFAULT_OAUTH_CONNECTION_ID, DEFAULT_SIGNATURE_TOLERANCE_SECONDS, IntegrationDefinitionSchema, MAX_AGENT_EVENT_DEDUPE_KEY_LENGTH, MAX_RAISED_AGENT_EVENTS, PARTNER_STORAGE_LIMITS, RESERVED_OAUTH_AUTHORIZATION_PARAMS, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
|
|
24
|
+
export type { IntegrationDefinition, IntegrationComponentDeclarations, IntegrationProposals, IntegrationCapabilityDeclaration, IntegrationPermissionDeclaration, PartnerWidgetDeclaration, PartnerToolModule, PartnerToolParameters, PartnerActionDeclaration, ToolClassificationProposal, DefinitionCollisionInput, FirstPartyInventory, PartnerWebhookDeclaration, WebhookSignatureDeclaration, WebhookSignatureScheme, WebhookEvent, WebhookResult, RaisedAgentEvent, PartnerAgentChannelDeclaration, AgentChannelRun, AgentEventDeclaration, AgentConversation, AgentTriggerFilter, AgentTriggerFilterField, AgentChannelReply, AgentChannelDeliveryResult, AgentChannelRunOutcome, PartnerScheduleDeclaration, ScheduleInvocation, ScheduleResult, PartnerOAuthDeclaration, PartnerOAuthConnectionDeclaration, TeamAppDeclaration, OAuthProviderDeclaration, OAuthTokens, OnActivateHandler, AttentionHandler, AttentionSignal, WidgetVerdict, WidgetVerdictTone, } from '@ekanos/integration-schema';
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.
|
|
16
16
|
*/
|
|
17
17
|
export { defineIntegration } from './define-integration.js';
|
|
18
|
+
// Agent events (plan 222): types an event's functions by its payload schema.
|
|
19
|
+
export { defineAgentEvent } from './define-agent-event.js';
|
|
18
20
|
// Event-surface validation values: the 5-field cron parser backing
|
|
19
21
|
// `defineIntegration()`'s schedule validation, exported so validators and
|
|
20
22
|
// the harness can check/describe an expression the same way the SDK does.
|
|
@@ -27,5 +29,5 @@ export { PublicActionError } from '../errors/public-action-error.js';
|
|
|
27
29
|
export { ADMIN_HIERARCHY_LEVEL, OWNER_HIERARCHY_LEVEL, actorIsAtLeast, } from './actor-role.js';
|
|
28
30
|
// Vendor/user-authored text in tool output: one name (`untrusted*`), one clip.
|
|
29
31
|
export { DEFAULT_UNTRUSTED_TEXT_MAX_CHARS, untrustedText, untrustedTextNote, } from './untrusted-text.js';
|
|
30
|
-
export { DEFAULT_SIGNATURE_TOLERANCE_SECONDS, IntegrationDefinitionSchema, PARTNER_STORAGE_LIMITS, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
|
|
32
|
+
export { DEFAULT_OAUTH_CONNECTION_ID, DEFAULT_SIGNATURE_TOLERANCE_SECONDS, IntegrationDefinitionSchema, MAX_AGENT_EVENT_DEDUPE_KEY_LENGTH, MAX_RAISED_AGENT_EVENTS, PARTNER_STORAGE_LIMITS, RESERVED_OAUTH_AUTHORIZATION_PARAMS, validateIntegrationDefinitions, } from '@ekanos/integration-schema';
|
|
31
33
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integration/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,mEAAmE;AACnE,0EAA0E;AAC1E,0EAA0E;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAE7C,oEAAoE;AACpE,+CAA+C;AAC/C,8CAA8C;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAElE,8EAA8E;AAC9E,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,+EAA+E;AAC/E,OAAO,EACL,gCAAgC,EAChC,aAAa,EACb,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,mCAAmC,EACnC,2BAA2B,EAC3B,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,4BAA4B,CAAC","sourcesContent":["/**\n * @ekanos/sdk/integration — the partner authoring contract.\n *\n * `defineIntegration()` is THE way a partner declares an integration\n * Partners never extend a base class.\n * The host adapts the returned definition internally via\n * `registerPartnerIntegration` in `@kit/integrations-core`, which re-parses\n * against the SAME canonical schema (`@ekanos/integration-schema`) at the\n * trust boundary — one contract, no structural twin (F3/F9).\n *\n * Dependency-pure and isomorphic: zod plus the schema package, no `@kit/*`,\n * no `server-only`.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport { defineIntegration } from './define-integration';\n\n// Event-surface validation values: the 5-field cron parser backing\n// `defineIntegration()`'s schedule validation, exported so validators and\n// the harness can check/describe an expression the same way the SDK does.\nexport { parseCronExpression } from './cron';\n\n// The one escape hatch from the actions host route's opaque-failure\n// discipline — see the class's own doc comment\n// (the widget-actions opaque-failure ruling).\nexport { PublicActionError } from '../errors/public-action-error';\n\n// Gate a write action on the caller's role (the host fills `ctx.actor.role`).\nexport {\n ADMIN_HIERARCHY_LEVEL,\n OWNER_HIERARCHY_LEVEL,\n actorIsAtLeast,\n} from './actor-role';\n\n// Vendor/user-authored text in tool output: one name (`untrusted*`), one clip.\nexport {\n DEFAULT_UNTRUSTED_TEXT_MAX_CHARS,\n untrustedText,\n untrustedTextNote,\n} from './untrusted-text';\n\nexport {\n DEFAULT_SIGNATURE_TOLERANCE_SECONDS,\n IntegrationDefinitionSchema,\n PARTNER_STORAGE_LIMITS,\n validateIntegrationDefinitions,\n} from '@ekanos/integration-schema';\n\nexport type {\n IntegrationDefinition,\n IntegrationComponentDeclarations,\n IntegrationProposals,\n IntegrationCapabilityDeclaration,\n IntegrationPermissionDeclaration,\n PartnerWidgetDeclaration,\n PartnerToolModule,\n PartnerToolParameters,\n // Widget-invoked server handlers (`actions`): the declaration type a\n // partner needs to export a handler shared between an action and a tool.\n PartnerActionDeclaration,\n ToolClassificationProposal,\n DefinitionCollisionInput,\n FirstPartyInventory,\n // Event surfaces (webhooks, schedules, OAuth): declared in the definition,\n // executed locally by the harness/testing helpers today; the host's real\n // transports (public ingress, scheduler, hosted OAuth callback) bind to\n // these exact declarations later with no partner code change.\n PartnerWebhookDeclaration,\n WebhookSignatureDeclaration,\n WebhookSignatureScheme,\n WebhookEvent,\n WebhookResult,\n PartnerScheduleDeclaration,\n ScheduleInvocation,\n ScheduleResult,\n PartnerOAuthDeclaration,\n OAuthProviderDeclaration,\n OAuthTokens,\n // Activation lifecycle hook: cache seeding / eager validation at connect\n // time. Runs after first persist and after every activationData update;\n // v1 errors are non-fatal (logged as a warning, activation stays active).\n OnActivateHandler,\n // Each widget's verdict, computed on the server for home's Needs you.\n AttentionHandler,\n AttentionSignal,\n WidgetVerdict,\n WidgetVerdictTone,\n} from '@ekanos/integration-schema';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integration/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,6EAA6E;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,mEAAmE;AACnE,0EAA0E;AAC1E,0EAA0E;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAE7C,oEAAoE;AACpE,+CAA+C;AAC/C,8CAA8C;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAElE,8EAA8E;AAC9E,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,GACf,MAAM,cAAc,CAAC;AAEtB,+EAA+E;AAC/E,OAAO,EACL,gCAAgC,EAChC,aAAa,EACb,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,2BAA2B,EAC3B,mCAAmC,EACnC,2BAA2B,EAC3B,iCAAiC,EACjC,uBAAuB,EACvB,sBAAsB,EACtB,mCAAmC,EACnC,8BAA8B,GAC/B,MAAM,4BAA4B,CAAC","sourcesContent":["/**\n * @ekanos/sdk/integration — the partner authoring contract.\n *\n * `defineIntegration()` is THE way a partner declares an integration\n * Partners never extend a base class.\n * The host adapts the returned definition internally via\n * `registerPartnerIntegration` in `@kit/integrations-core`, which re-parses\n * against the SAME canonical schema (`@ekanos/integration-schema`) at the\n * trust boundary — one contract, no structural twin (F3/F9).\n *\n * Dependency-pure and isomorphic: zod plus the schema package, no `@kit/*`,\n * no `server-only`.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport { defineIntegration } from './define-integration';\n\n// Agent events (plan 222): types an event's functions by its payload schema.\nexport { defineAgentEvent } from './define-agent-event';\n\n// Event-surface validation values: the 5-field cron parser backing\n// `defineIntegration()`'s schedule validation, exported so validators and\n// the harness can check/describe an expression the same way the SDK does.\nexport { parseCronExpression } from './cron';\n\n// The one escape hatch from the actions host route's opaque-failure\n// discipline — see the class's own doc comment\n// (the widget-actions opaque-failure ruling).\nexport { PublicActionError } from '../errors/public-action-error';\n\n// Gate a write action on the caller's role (the host fills `ctx.actor.role`).\nexport {\n ADMIN_HIERARCHY_LEVEL,\n OWNER_HIERARCHY_LEVEL,\n actorIsAtLeast,\n} from './actor-role';\n\n// Vendor/user-authored text in tool output: one name (`untrusted*`), one clip.\nexport {\n DEFAULT_UNTRUSTED_TEXT_MAX_CHARS,\n untrustedText,\n untrustedTextNote,\n} from './untrusted-text';\n\nexport {\n DEFAULT_OAUTH_CONNECTION_ID,\n DEFAULT_SIGNATURE_TOLERANCE_SECONDS,\n IntegrationDefinitionSchema,\n MAX_AGENT_EVENT_DEDUPE_KEY_LENGTH,\n MAX_RAISED_AGENT_EVENTS,\n PARTNER_STORAGE_LIMITS,\n RESERVED_OAUTH_AUTHORIZATION_PARAMS,\n validateIntegrationDefinitions,\n} from '@ekanos/integration-schema';\n\nexport type {\n IntegrationDefinition,\n IntegrationComponentDeclarations,\n IntegrationProposals,\n IntegrationCapabilityDeclaration,\n IntegrationPermissionDeclaration,\n PartnerWidgetDeclaration,\n PartnerToolModule,\n PartnerToolParameters,\n // Widget-invoked server handlers (`actions`): the declaration type a\n // partner needs to export a handler shared between an action and a tool.\n PartnerActionDeclaration,\n ToolClassificationProposal,\n DefinitionCollisionInput,\n FirstPartyInventory,\n // Event surfaces (webhooks, schedules, OAuth): declared in the definition,\n // executed locally by the harness/testing helpers today; the host's real\n // transports (public ingress, scheduler, hosted OAuth callback) bind to\n // these exact declarations later with no partner code change.\n PartnerWebhookDeclaration,\n WebhookSignatureDeclaration,\n WebhookSignatureScheme,\n WebhookEvent,\n WebhookResult,\n // Agent events and channel (plan 222): events a webhook raises to start\n // agents, and the channel a run answers in.\n RaisedAgentEvent,\n PartnerAgentChannelDeclaration,\n AgentChannelRun,\n AgentEventDeclaration,\n AgentConversation,\n AgentTriggerFilter,\n AgentTriggerFilterField,\n AgentChannelReply,\n AgentChannelDeliveryResult,\n AgentChannelRunOutcome,\n PartnerScheduleDeclaration,\n ScheduleInvocation,\n ScheduleResult,\n PartnerOAuthDeclaration,\n PartnerOAuthConnectionDeclaration,\n TeamAppDeclaration,\n OAuthProviderDeclaration,\n OAuthTokens,\n // Activation lifecycle hook: cache seeding / eager validation at connect\n // time. Runs after first persist and after every activationData update;\n // v1 errors are non-fatal (logged as a warning, activation stays active).\n OnActivateHandler,\n // Each widget's verdict, computed on the server for home's Needs you.\n AttentionHandler,\n AttentionSignal,\n WidgetVerdict,\n WidgetVerdictTone,\n} from '@ekanos/integration-schema';\n"]}
|
|
@@ -135,7 +135,7 @@ export interface IntegrationMetadata {
|
|
|
135
135
|
* omission. An Integration is any package extending `BaseIntegration`; its
|
|
136
136
|
* kind says who turns it on and whether it needs credentials:
|
|
137
137
|
*
|
|
138
|
-
* '
|
|
138
|
+
* 'experience' — an Ekanos-native capability. Enabled for a Source by a
|
|
139
139
|
* super-admin; no third-party credentials, no activation flow, no
|
|
140
140
|
* marketplace tile. Always declares its own top-level sidebar Section
|
|
141
141
|
* via `getSidebarNav()` and NEVER declares `getWorkspaceTargets()` —
|
|
@@ -151,7 +151,7 @@ export interface IntegrationMetadata {
|
|
|
151
151
|
* a Feature MUST declare a Section and MUST NOT declare a Workspace; a
|
|
152
152
|
* connector MAY declare either or both.
|
|
153
153
|
*/
|
|
154
|
-
kind: '
|
|
154
|
+
kind: 'experience' | 'integration';
|
|
155
155
|
/**
|
|
156
156
|
* Product slugs that MUST be attached to the same Source for this product to
|
|
157
157
|
* function — a hard dependency, not a suggestion.
|
|
@@ -161,7 +161,7 @@ export interface IntegrationMetadata {
|
|
|
161
161
|
* the dependency in both directions ("Requires X" / "Enables Y").
|
|
162
162
|
*
|
|
163
163
|
* Host CI enforces: every slug is registered, nothing requires itself, no
|
|
164
|
-
* cycles, and a `kind: '
|
|
164
|
+
* cycles, and a `kind: 'experience'` may require a `'integration'` but never
|
|
165
165
|
* the reverse.
|
|
166
166
|
*/
|
|
167
167
|
requires?: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.js","sourceRoot":"","sources":["../../src/types/integration.ts"],"names":[],"mappings":"","sourcesContent":["import type { ComponentType } from 'react';\n\nimport type { IconRenderer } from './icon';\n\n// Capability shown on the integration detail page (numbered grid)\nexport interface IntegrationCapability {\n label: string;\n description: string;\n icon?: IconRenderer;\n}\n\n// Permission scope shown on the integration detail page\nexport interface IntegrationPermission {\n label: string;\n detail: string;\n type: 'read' | 'write';\n}\n\n// ---- Integration Health Monitoring (issue #474) -----------------------------\n\n// Higher = healthier. Used internally for \"least healthy wins\" merging.\nexport type HealthStatus = 'healthy' | 'degraded' | 'unhealthy' | 'unknown';\n\n/**\n * Result returned by `BaseIntegration.healthCheck(accountId)`. Routed through\n * `classifyHealth()` (server-side) before writing to\n * `integration_health_status`.\n */\nexport interface HealthCheckResult {\n status: HealthStatus;\n errorCode?: string;\n errorMessage?: string;\n /** Rolling 24h error rate from `account_product_usage_events`, 0..100. */\n errorRatePct?: number;\n /** Sample size used to compute errorRatePct; if below threshold, treated as unknown. */\n sampleSize?: number;\n /** Whether the most recent observed call succeeded. */\n lastObservationSucceeded?: boolean;\n /** Token/credential expiry, if known. Drives token-expiry warnings. */\n tokenExpiresAt?: string;\n /** Free-form per-integration data carried to the dashboard. */\n metadata?: Record<string, unknown>;\n}\n\n// Activation result from integration\nexport interface ActivationResult {\n success: boolean;\n configData?: Record<string, unknown>;\n externalId?: string;\n errorMessage?: string;\n metadata?: Record<string, unknown>;\n}\n\n// Base props that all integration components need\ninterface BaseIntegrationComponentProps {\n accountId: string;\n sourceId?: string;\n integrationConfig?: Record<string, unknown>;\n}\n\n// Discriminated union for product reference - only one allowed\ntype ProductReference =\n | { productId: string; productSlug?: never }\n | { productSlug: string; productId?: never };\n\n// New base type using discriminated union\nexport type IntegrationComponentProps = BaseIntegrationComponentProps &\n ProductReference;\n\n// Marketplace tile props\nexport type MarketplaceTileProps = IntegrationComponentProps & {\n onActivate?: () => void;\n isActivated?: boolean;\n /** Navigation URL for the integration detail page */\n href?: string;\n};\n\n// Activation form props\nexport type ActivationFormProps = IntegrationComponentProps & {\n onSuccess?: (result: ActivationResult) => void;\n onCancel?: () => void;\n isLoading?: boolean;\n /** Render inline instead of in a dialog */\n inline?: boolean;\n /**\n * The integration is already connected and the person chose to connect it\n * again — after a token expired, a password changed, or a health check\n * failed. An OAuth form sends them through sign-in even when a token is\n * stored; a credential form simply accepts new values. Activating again\n * resets the integration's health to unknown until the next check.\n */\n reconnect?: boolean;\n /**\n * The TEAM account context the user is activating within — separate from\n * `accountId`, which for `scope: 'user'` integrations is collapsed to the\n * personal account for activation storage/token purposes. Only consumed by\n * workspace materialization (`materializeWorkspaceOnActivation`), never for\n * activation/token storage itself (plan 009 Part A2). Optional: undefined\n * for team-scoped integrations (their `accountId` already IS the team) and\n * for any caller that hasn't threaded it yet — the server action falls back\n * to `accountId` when absent.\n */\n teamAccountId?: string;\n};\n\n// Integration metadata\nexport interface IntegrationMetadata {\n productId: string;\n productSlug: string;\n integrationName: string;\n description: string;\n category: string;\n version: string;\n icon?: string;\n /** Bundled logo URL — overrides product.logo_path on the marketplace */\n logoPath?: string;\n /** When 'user', activation data is stored under the user's personal account (user.id) instead of the team account */\n scope?: 'user' | 'team';\n /**\n * Set FALSE to assert this integration has NO self-serve activation surface —\n * the marketplace detail page then renders no activation form at all.\n *\n * This is deliberately distinct from \"has no CUSTOM activation form\": an\n * integration that omits `getActivationForm()` still gets the generic one, and\n * six integrations depend on that. Use this flag only when submitting ANY\n * activation form would be a dead end — where `activate()` cannot succeed, so\n * showing a form hands the user a button that cannot work.\n * Undefined/true = normal self-serve activation.\n */\n selfServeActivation?: boolean;\n /**\n * How this integration's MCP tools resolve credentials — the integration's\n * SELF-DECLARATION of machine-executability, consumed dynamically by the\n * workflow engine's fail-closed tool allowlist (isGatewayExposableTool).\n *\n * 'account' — credentials/identity resolve from team- or source-level\n * config via the ToolContext (`getAccountId(ctx)` + `ctx.supabase`).\n * Safe for durable machine-principal execution (workflows): eligible\n * tools (read + public/internal sensitivity) become workflow-callable.\n * 'user' — credentials or identity are personal (ctx.user.id, personal\n * OAuth, session reads like getSupabaseServerClient()/requireUser()).\n * NEVER machine-executable: a workflow would silently ride one\n * employee's personal grant.\n * 'source' — platform-level credentials (diagnostics-class integrations).\n * Not exposed to account workflows.\n *\n * UNDECLARED = fail closed (treated as not machine-executable). Declaring\n * 'account' is a security assertion: the integration's MCP run() handlers\n * must use ctx-based resolution ONLY — no session clients, no personal\n * grants. A guard test in @kit/integrations-workflows enforces the obvious\n * violations (session-client reads in mcp/ code of 'account' declarers).\n */\n credentialModel?: 'account' | 'user' | 'source';\n /**\n * Which KIND of integration this is. **Required** — nothing is inferred from\n * omission. An Integration is any package extending `BaseIntegration`; its\n * kind says who turns it on and whether it needs credentials:\n *\n * 'feature' — an Ekanos-native capability. Enabled for a Source by a\n * super-admin; no third-party credentials, no activation flow, no\n * marketplace tile. Always declares its own top-level sidebar Section\n * via `getSidebarNav()` and NEVER declares `getWorkspaceTargets()` —\n * its widgets have no Workspace of their own, so an admin places them\n * on one in the composer (`/admin/sources/[id]/layout`).\n * 'integration' — a connection to an outside system. Made available to a\n * Source by a super-admin, then connected by a team or user (usually\n * OAuth/SSO); has credentials, an activation flow and a marketplace\n * tile. Declares a Workspace via `getWorkspaceTargets()`, a top-level\n * Section via `getSidebarNav()`, or both (Centrex does both).\n *\n * The asymmetry is one-directional and enforced by the host's CI:\n * a Feature MUST declare a Section and MUST NOT declare a Workspace; a\n * connector MAY declare either or both.\n */\n kind: 'feature' | 'integration';\n /**\n * Product slugs that MUST be attached to the same Source for this product to\n * function — a hard dependency, not a suggestion.\n *\n * The host REFUSES to attach this product to a Source missing any of them,\n * and refuses a detach that would strand a dependent. Admin surfaces render\n * the dependency in both directions (\"Requires X\" / \"Enables Y\").\n *\n * Host CI enforces: every slug is registered, nothing requires itself, no\n * cycles, and a `kind: 'feature'` may require a `'integration'` but never\n * the reverse.\n */\n requires?: string[];\n}\n\n// Widget configuration\nexport interface WidgetConfig {\n id: string;\n name: string;\n component: ComponentType<IntegrationComponentProps>;\n gridSize?: { cols: number; rows: number } | { cols: number; rows: number }[];\n gridPosition?: { col: number; row: number };\n widgetState: 'active' | 'inactive' | 'disabled';\n /**\n * `compact` (a figure tile, 1 → 2 → 4 across its row), `half` or `full`\n * (the design system's widget rules). Unset, it is derived from `layouts.lg.w`.\n */\n size?: 'compact' | 'half' | 'full';\n // React-grid-layout format\n layouts?: {\n lg?: { x: number; y: number; w: number; h: number; maxHeight?: number };\n md?: { x: number; y: number; w: number; h: number; maxHeight?: number };\n sm?: { x: number; y: number; w: number; h: number; maxHeight?: number };\n };\n // Integration metadata for widgets\n integrationMetadata?: IntegrationMetadata;\n category?: { id: string; name: string; slug: string; icon: string | null };\n // Database product ID (UUID) from widget_config table\n productId?: string;\n isCollapsible?: boolean;\n // For collapsed state persistence\n collapsed?: boolean;\n widgetConfigId?: string;\n // Resolved workspace placement (explicit widget_config.workspace_id\n // override, else the product's inheriting attachment). Populated by the\n // dashboard widgets loader; null = no workspace (unscoped dashboard).\n workspaceId?: string | null;\n isPinnable?: boolean;\n isPinned?: boolean;\n // When true, the dashboard renders the \"Ask about this\" AI footer bar for\n // this widget. The widget may still pass `askContext` to the wrapper for a\n // rich snapshot; otherwise a minimal default is used.\n aiFooterEnabled?: boolean;\n // Integration health for this widget's product. `unhealthy` downgrades the\n // widget to `disabled`; `degraded` surfaces a stale-data footer.\n health?: {\n status: HealthStatus;\n lastSuccessAt?: string | null;\n errorMessage?: string | null;\n };\n}\n\n// Database types for integration tables.\n//\n// PARTNER-VISIBLE SUBSET: these row types deliberately OMIT `vault_secret_id`.\n// A vault id is a host handle to a decryptable secret; the partner surface\n// never exposes one — `ctx.secrets` fences it off. Host code keeps the full\n// row because it needs the handle; a parity test pins the SDK type as the\n// host's row minus exactly the host-only fields, so a new column cannot drift\n// in here unnoticed.\nexport interface IntegrationConfig {\n id: string;\n account_id: string | null;\n product_id: string;\n config_type: 'api_credentials' | 'activation_data' | 'settings';\n config_data: Record<string, unknown>;\n status: 'active' | 'inactive' | 'pending' | 'error' | 'suspended';\n is_active: boolean;\n created_at: string;\n updated_at: string;\n created_by: string | null;\n updated_by: string | null;\n}\n\n/**\n * The PARTNER-VISIBLE `data_type` values on `account_product_data` — a strict\n * SUBSET of `@kit/integrations-core`'s union.\n *\n * `'secret'` is deliberately absent. Secret rows are host-managed (one row per\n * secret name, value in Vault, `vault_secret_id` on the row), written only by\n * the capability context's `persistSecret` and read only through\n * `ctx.secrets`. Naming the kind here would advertise a row shape partner code\n * must never address: `ctx.storage` rejects it, and core's generic accessors\n * reject it too (`config-utils.ts`, `assertNotHostOnlyDataType`) precisely\n * because a write there would land the plaintext in the `data` column.\n *\n * When you add an integration-specific kind, add it in BOTH places (core owns\n * the full union) and keep this one partner-appropriate. Prefer `dataSubtype`\n * for orthogonal slicing within a kind (e.g. `data_type='cache'`,\n * `data_subtype='profile'`); reserve a new top-level kind for data that has a\n * distinct lifecycle or is queried independently.\n */\nexport type AccountProductDataType =\n | 'activation'\n | 'settings'\n | 'metrics_summary'\n | 'sync_state'\n | 'cache'\n // Integration-specific kinds — extend here when needed.\n | 'cash_forecast' // quickbooks — user-supplied manual cash-forecast estimates\n | 'tax_config'; // quickbooks — per-user federal/state tax rate config\n\nexport interface AccountProductData {\n id: string;\n account_id: string;\n product_id: string;\n data_type: AccountProductDataType;\n data_subtype?: string;\n data: Record<string, unknown>;\n expires_at?: string;\n integration_config_id?: string;\n external_id?: string;\n created_at: string;\n updated_at: string;\n created_by: string | null;\n updated_by: string | null;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"integration.js","sourceRoot":"","sources":["../../src/types/integration.ts"],"names":[],"mappings":"","sourcesContent":["import type { ComponentType } from 'react';\n\nimport type { IconRenderer } from './icon';\n\n// Capability shown on the integration detail page (numbered grid)\nexport interface IntegrationCapability {\n label: string;\n description: string;\n icon?: IconRenderer;\n}\n\n// Permission scope shown on the integration detail page\nexport interface IntegrationPermission {\n label: string;\n detail: string;\n type: 'read' | 'write';\n}\n\n// ---- Integration Health Monitoring (issue #474) -----------------------------\n\n// Higher = healthier. Used internally for \"least healthy wins\" merging.\nexport type HealthStatus = 'healthy' | 'degraded' | 'unhealthy' | 'unknown';\n\n/**\n * Result returned by `BaseIntegration.healthCheck(accountId)`. Routed through\n * `classifyHealth()` (server-side) before writing to\n * `integration_health_status`.\n */\nexport interface HealthCheckResult {\n status: HealthStatus;\n errorCode?: string;\n errorMessage?: string;\n /** Rolling 24h error rate from `account_product_usage_events`, 0..100. */\n errorRatePct?: number;\n /** Sample size used to compute errorRatePct; if below threshold, treated as unknown. */\n sampleSize?: number;\n /** Whether the most recent observed call succeeded. */\n lastObservationSucceeded?: boolean;\n /** Token/credential expiry, if known. Drives token-expiry warnings. */\n tokenExpiresAt?: string;\n /** Free-form per-integration data carried to the dashboard. */\n metadata?: Record<string, unknown>;\n}\n\n// Activation result from integration\nexport interface ActivationResult {\n success: boolean;\n configData?: Record<string, unknown>;\n externalId?: string;\n errorMessage?: string;\n metadata?: Record<string, unknown>;\n}\n\n// Base props that all integration components need\ninterface BaseIntegrationComponentProps {\n accountId: string;\n sourceId?: string;\n integrationConfig?: Record<string, unknown>;\n}\n\n// Discriminated union for product reference - only one allowed\ntype ProductReference =\n | { productId: string; productSlug?: never }\n | { productSlug: string; productId?: never };\n\n// New base type using discriminated union\nexport type IntegrationComponentProps = BaseIntegrationComponentProps &\n ProductReference;\n\n// Marketplace tile props\nexport type MarketplaceTileProps = IntegrationComponentProps & {\n onActivate?: () => void;\n isActivated?: boolean;\n /** Navigation URL for the integration detail page */\n href?: string;\n};\n\n// Activation form props\nexport type ActivationFormProps = IntegrationComponentProps & {\n onSuccess?: (result: ActivationResult) => void;\n onCancel?: () => void;\n isLoading?: boolean;\n /** Render inline instead of in a dialog */\n inline?: boolean;\n /**\n * The integration is already connected and the person chose to connect it\n * again — after a token expired, a password changed, or a health check\n * failed. An OAuth form sends them through sign-in even when a token is\n * stored; a credential form simply accepts new values. Activating again\n * resets the integration's health to unknown until the next check.\n */\n reconnect?: boolean;\n /**\n * The TEAM account context the user is activating within — separate from\n * `accountId`, which for `scope: 'user'` integrations is collapsed to the\n * personal account for activation storage/token purposes. Only consumed by\n * workspace materialization (`materializeWorkspaceOnActivation`), never for\n * activation/token storage itself (plan 009 Part A2). Optional: undefined\n * for team-scoped integrations (their `accountId` already IS the team) and\n * for any caller that hasn't threaded it yet — the server action falls back\n * to `accountId` when absent.\n */\n teamAccountId?: string;\n};\n\n// Integration metadata\nexport interface IntegrationMetadata {\n productId: string;\n productSlug: string;\n integrationName: string;\n description: string;\n category: string;\n version: string;\n icon?: string;\n /** Bundled logo URL — overrides product.logo_path on the marketplace */\n logoPath?: string;\n /** When 'user', activation data is stored under the user's personal account (user.id) instead of the team account */\n scope?: 'user' | 'team';\n /**\n * Set FALSE to assert this integration has NO self-serve activation surface —\n * the marketplace detail page then renders no activation form at all.\n *\n * This is deliberately distinct from \"has no CUSTOM activation form\": an\n * integration that omits `getActivationForm()` still gets the generic one, and\n * six integrations depend on that. Use this flag only when submitting ANY\n * activation form would be a dead end — where `activate()` cannot succeed, so\n * showing a form hands the user a button that cannot work.\n * Undefined/true = normal self-serve activation.\n */\n selfServeActivation?: boolean;\n /**\n * How this integration's MCP tools resolve credentials — the integration's\n * SELF-DECLARATION of machine-executability, consumed dynamically by the\n * workflow engine's fail-closed tool allowlist (isGatewayExposableTool).\n *\n * 'account' — credentials/identity resolve from team- or source-level\n * config via the ToolContext (`getAccountId(ctx)` + `ctx.supabase`).\n * Safe for durable machine-principal execution (workflows): eligible\n * tools (read + public/internal sensitivity) become workflow-callable.\n * 'user' — credentials or identity are personal (ctx.user.id, personal\n * OAuth, session reads like getSupabaseServerClient()/requireUser()).\n * NEVER machine-executable: a workflow would silently ride one\n * employee's personal grant.\n * 'source' — platform-level credentials (diagnostics-class integrations).\n * Not exposed to account workflows.\n *\n * UNDECLARED = fail closed (treated as not machine-executable). Declaring\n * 'account' is a security assertion: the integration's MCP run() handlers\n * must use ctx-based resolution ONLY — no session clients, no personal\n * grants. A guard test in @kit/integrations-workflows enforces the obvious\n * violations (session-client reads in mcp/ code of 'account' declarers).\n */\n credentialModel?: 'account' | 'user' | 'source';\n /**\n * Which KIND of integration this is. **Required** — nothing is inferred from\n * omission. An Integration is any package extending `BaseIntegration`; its\n * kind says who turns it on and whether it needs credentials:\n *\n * 'experience' — an Ekanos-native capability. Enabled for a Source by a\n * super-admin; no third-party credentials, no activation flow, no\n * marketplace tile. Always declares its own top-level sidebar Section\n * via `getSidebarNav()` and NEVER declares `getWorkspaceTargets()` —\n * its widgets have no Workspace of their own, so an admin places them\n * on one in the composer (`/admin/sources/[id]/layout`).\n * 'integration' — a connection to an outside system. Made available to a\n * Source by a super-admin, then connected by a team or user (usually\n * OAuth/SSO); has credentials, an activation flow and a marketplace\n * tile. Declares a Workspace via `getWorkspaceTargets()`, a top-level\n * Section via `getSidebarNav()`, or both (Centrex does both).\n *\n * The asymmetry is one-directional and enforced by the host's CI:\n * a Feature MUST declare a Section and MUST NOT declare a Workspace; a\n * connector MAY declare either or both.\n */\n kind: 'experience' | 'integration';\n /**\n * Product slugs that MUST be attached to the same Source for this product to\n * function — a hard dependency, not a suggestion.\n *\n * The host REFUSES to attach this product to a Source missing any of them,\n * and refuses a detach that would strand a dependent. Admin surfaces render\n * the dependency in both directions (\"Requires X\" / \"Enables Y\").\n *\n * Host CI enforces: every slug is registered, nothing requires itself, no\n * cycles, and a `kind: 'experience'` may require a `'integration'` but never\n * the reverse.\n */\n requires?: string[];\n}\n\n// Widget configuration\nexport interface WidgetConfig {\n id: string;\n name: string;\n component: ComponentType<IntegrationComponentProps>;\n gridSize?: { cols: number; rows: number } | { cols: number; rows: number }[];\n gridPosition?: { col: number; row: number };\n widgetState: 'active' | 'inactive' | 'disabled';\n /**\n * `compact` (a figure tile, 1 → 2 → 4 across its row), `half` or `full`\n * (the design system's widget rules). Unset, it is derived from `layouts.lg.w`.\n */\n size?: 'compact' | 'half' | 'full';\n // React-grid-layout format\n layouts?: {\n lg?: { x: number; y: number; w: number; h: number; maxHeight?: number };\n md?: { x: number; y: number; w: number; h: number; maxHeight?: number };\n sm?: { x: number; y: number; w: number; h: number; maxHeight?: number };\n };\n // Integration metadata for widgets\n integrationMetadata?: IntegrationMetadata;\n category?: { id: string; name: string; slug: string; icon: string | null };\n // Database product ID (UUID) from widget_config table\n productId?: string;\n isCollapsible?: boolean;\n // For collapsed state persistence\n collapsed?: boolean;\n widgetConfigId?: string;\n // Resolved workspace placement (explicit widget_config.workspace_id\n // override, else the product's inheriting attachment). Populated by the\n // dashboard widgets loader; null = no workspace (unscoped dashboard).\n workspaceId?: string | null;\n isPinnable?: boolean;\n isPinned?: boolean;\n // When true, the dashboard renders the \"Ask about this\" AI footer bar for\n // this widget. The widget may still pass `askContext` to the wrapper for a\n // rich snapshot; otherwise a minimal default is used.\n aiFooterEnabled?: boolean;\n // Integration health for this widget's product. `unhealthy` downgrades the\n // widget to `disabled`; `degraded` surfaces a stale-data footer.\n health?: {\n status: HealthStatus;\n lastSuccessAt?: string | null;\n errorMessage?: string | null;\n };\n}\n\n// Database types for integration tables.\n//\n// PARTNER-VISIBLE SUBSET: these row types deliberately OMIT `vault_secret_id`.\n// A vault id is a host handle to a decryptable secret; the partner surface\n// never exposes one — `ctx.secrets` fences it off. Host code keeps the full\n// row because it needs the handle; a parity test pins the SDK type as the\n// host's row minus exactly the host-only fields, so a new column cannot drift\n// in here unnoticed.\nexport interface IntegrationConfig {\n id: string;\n account_id: string | null;\n product_id: string;\n config_type: 'api_credentials' | 'activation_data' | 'settings';\n config_data: Record<string, unknown>;\n status: 'active' | 'inactive' | 'pending' | 'error' | 'suspended';\n is_active: boolean;\n created_at: string;\n updated_at: string;\n created_by: string | null;\n updated_by: string | null;\n}\n\n/**\n * The PARTNER-VISIBLE `data_type` values on `account_product_data` — a strict\n * SUBSET of `@kit/integrations-core`'s union.\n *\n * `'secret'` is deliberately absent. Secret rows are host-managed (one row per\n * secret name, value in Vault, `vault_secret_id` on the row), written only by\n * the capability context's `persistSecret` and read only through\n * `ctx.secrets`. Naming the kind here would advertise a row shape partner code\n * must never address: `ctx.storage` rejects it, and core's generic accessors\n * reject it too (`config-utils.ts`, `assertNotHostOnlyDataType`) precisely\n * because a write there would land the plaintext in the `data` column.\n *\n * When you add an integration-specific kind, add it in BOTH places (core owns\n * the full union) and keep this one partner-appropriate. Prefer `dataSubtype`\n * for orthogonal slicing within a kind (e.g. `data_type='cache'`,\n * `data_subtype='profile'`); reserve a new top-level kind for data that has a\n * distinct lifecycle or is queried independently.\n */\nexport type AccountProductDataType =\n | 'activation'\n | 'settings'\n | 'metrics_summary'\n | 'sync_state'\n | 'cache'\n // Integration-specific kinds — extend here when needed.\n | 'cash_forecast' // quickbooks — user-supplied manual cash-forecast estimates\n | 'tax_config'; // quickbooks — per-user federal/state tax rate config\n\nexport interface AccountProductData {\n id: string;\n account_id: string;\n product_id: string;\n data_type: AccountProductDataType;\n data_subtype?: string;\n data: Record<string, unknown>;\n expires_at?: string;\n integration_config_id?: string;\n external_id?: string;\n created_at: string;\n updated_at: string;\n created_by: string | null;\n updated_by: string | null;\n}\n"]}
|
|
@@ -31,4 +31,10 @@ export interface WorkspaceTargetDefinition {
|
|
|
31
31
|
inheritsWidgets?: boolean;
|
|
32
32
|
/** Limit this workspace to specific widget ids. */
|
|
33
33
|
includedWidgetIds?: string[];
|
|
34
|
+
/**
|
|
35
|
+
* Create this workspace on a Source as soon as the product is made
|
|
36
|
+
* available there, instead of waiting for an admin to add it. Only for a
|
|
37
|
+
* product that works as shipped (`activation: { required: false }`).
|
|
38
|
+
*/
|
|
39
|
+
materializeOnEntitlement?: boolean;
|
|
34
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-target.js","sourceRoot":"","sources":["../../src/types/workspace-target.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The contract an integration uses to declare the workspaces it ships with\n * (`BaseIntegration.getWorkspaceTargets()`).\n *\n * The host's canonical definition is `z.input<typeof WorkspaceTargetSchema>`\n * (`@kit/integrations-core`, `schemas/workspace-target.schema.ts`) — the\n * schema validates declarations in CI and at materialization time, including\n * cross-field rules a type cannot carry (icon-name validity, slug format,\n * `inheritsWidgets`/`includedWidgetIds` exclusivity, one inheriting target\n * per set). This structural twin exists so the SDK's root entrypoint owns its\n * types without importing the host schema; the parity test beside these types\n * keeps the two shapes mutually assignable.\n */\nexport interface WorkspaceTargetDefinition {\n /** Stable URL segment — `/workspace/<slug>` routes reference it. Lowercase\n * alphanumeric with single hyphens between segments. */\n slug: string;\n name: string;\n /** A Font Awesome class string, e.g. `fa-light fa-inbox`. Legacy Lucide\n * names are still translated at render time during the migration, but must\n * not be written. Validated by the host at gate time. */\n icon?: string;\n description?: string;\n /** Defaults to `'masonry'` when omitted. */\n layout?: 'masonry' | 'two-column';\n /**\n * Whether this workspace receives the product's otherwise-unplaced\n * widgets. Exactly one target per product must set it; mutually exclusive\n * with `includedWidgetIds`.\n */\n inheritsWidgets?: boolean;\n /** Limit this workspace to specific widget ids. */\n includedWidgetIds?: string[];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"workspace-target.js","sourceRoot":"","sources":["../../src/types/workspace-target.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The contract an integration uses to declare the workspaces it ships with\n * (`BaseIntegration.getWorkspaceTargets()`).\n *\n * The host's canonical definition is `z.input<typeof WorkspaceTargetSchema>`\n * (`@kit/integrations-core`, `schemas/workspace-target.schema.ts`) — the\n * schema validates declarations in CI and at materialization time, including\n * cross-field rules a type cannot carry (icon-name validity, slug format,\n * `inheritsWidgets`/`includedWidgetIds` exclusivity, one inheriting target\n * per set). This structural twin exists so the SDK's root entrypoint owns its\n * types without importing the host schema; the parity test beside these types\n * keeps the two shapes mutually assignable.\n */\nexport interface WorkspaceTargetDefinition {\n /** Stable URL segment — `/workspace/<slug>` routes reference it. Lowercase\n * alphanumeric with single hyphens between segments. */\n slug: string;\n name: string;\n /** A Font Awesome class string, e.g. `fa-light fa-inbox`. Legacy Lucide\n * names are still translated at render time during the migration, but must\n * not be written. Validated by the host at gate time. */\n icon?: string;\n description?: string;\n /** Defaults to `'masonry'` when omitted. */\n layout?: 'masonry' | 'two-column';\n /**\n * Whether this workspace receives the product's otherwise-unplaced\n * widgets. Exactly one target per product must set it; mutually exclusive\n * with `includedWidgetIds`.\n */\n inheritsWidgets?: boolean;\n /** Limit this workspace to specific widget ids. */\n includedWidgetIds?: string[];\n /**\n * Create this workspace on a Source as soon as the product is made\n * available there, instead of waiting for an admin to add it. Only for a\n * product that works as shipped (`activation: { required: false }`).\n */\n materializeOnEntitlement?: boolean;\n}\n"]}
|
package/eslint.design-preset.mjs
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* submission).
|
|
15
15
|
*
|
|
16
16
|
* ── What it enforces ─────────────────────────────────────────────────────
|
|
17
|
-
*
|
|
17
|
+
* Ten rules. One is local to this file: no raw interactive HTML element
|
|
18
18
|
* where `@ekanos/ui` publishes the host's component. A hand-rolled `<button>`
|
|
19
19
|
* is not a styling nit — it misses the kit's focus ring, its disabled and
|
|
20
20
|
* loading states, its size scale, and every per-Source retint that reaches a
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
* inputs, selects, textareas and labels, each of which carries accessibility
|
|
23
23
|
* wiring a bare element does not.
|
|
24
24
|
*
|
|
25
|
-
* The other
|
|
25
|
+
* The other nine are the host's own `design/*` rules — palette-adjacent
|
|
26
26
|
* white/black utilities, Badge tone variants, overlay widths, rounded row
|
|
27
27
|
* hovers, faded muted text, viewport breakpoints in widgets, tinted chips,
|
|
28
|
-
* toolbar tabs — imported from `./eslint.design-rules.mjs`, the single
|
|
28
|
+
* toolbar tabs, raw type steps — imported from `./eslint.design-rules.mjs`, the single
|
|
29
29
|
* implementation the host repo's oxlint plugin also loads (through the
|
|
30
30
|
* two-line re-export the host's lint config keeps). There is
|
|
31
31
|
* exactly one copy of each rule, so a partner is held to what the host holds
|
|
@@ -114,8 +114,10 @@ export const FORBIDDEN_ELEMENTS = [
|
|
|
114
114
|
{
|
|
115
115
|
element: 'table',
|
|
116
116
|
message:
|
|
117
|
-
'Use <
|
|
118
|
-
'
|
|
117
|
+
'Use <DataTable> from @ekanos/ui/data-table for a list of records ' +
|
|
118
|
+
'(sorting, paging, empty and loading states); use <Table> from ' +
|
|
119
|
+
'@ekanos/ui/table only for a simple static table. ' +
|
|
120
|
+
'For a plain list, prefer edge-to-edge rows: a container with no horizontal ' +
|
|
119
121
|
'padding, each row `px-5 py-3` with a flat `hover:bg-accent/50`, ' +
|
|
120
122
|
'separated by <Separator> from @ekanos/ui/separator. See ' +
|
|
121
123
|
"the design system's widget rules.",
|
package/eslint.design-rules.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The design-system rules the shadcn linter does not cover —
|
|
2
|
+
* The design-system rules the shadcn linter does not cover — nine of them,
|
|
3
3
|
* as one lint plugin object. The rules and their reasoning live in the design system's
|
|
4
4
|
* rules; each message names the rule.
|
|
5
5
|
*
|
|
6
6
|
* ── Why this file sits in a published package ────────────────────────────
|
|
7
|
-
* Two consumers need the same
|
|
7
|
+
* Two consumers need the same nine rules and neither may fork them:
|
|
8
8
|
*
|
|
9
9
|
* - the host repo's own lint, as an oxlint JS plugin. `.oxlintrc.json`
|
|
10
10
|
* still loads this plugin through a
|
|
@@ -204,7 +204,17 @@ function isRoundedRowHover(value) {
|
|
|
204
204
|
|
|
205
205
|
const CHIP_TONES = ['success', 'primary', 'warning', 'destructive', 'info'];
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
// The two raw steps PLUS the three tiers that replaced them (the type scale:
|
|
208
|
+
// `text-fine` is 12px, `text-meta` 14px/400, `text-label` 14px/500). Without
|
|
209
|
+
// the tiers this rule went silently blind the moment the type-scale sweep
|
|
210
|
+
// landed — five weak tinted chips stopped matching while looking identical.
|
|
211
|
+
// Any rule keyed on a raw step has the same failure mode; check for it.
|
|
212
|
+
//
|
|
213
|
+
// `subhead-lg` (20px) and `headline-lg` (30px) are deliberately NOT here:
|
|
214
|
+
// this is the SMALL-text set, and a chip at 20px is not a weak chip, it is a
|
|
215
|
+
// mis-sized one. Re-check this list whenever a tier is added at 12 or 14px.
|
|
216
|
+
const CHIP_SMALL_TEXT =
|
|
217
|
+
/(?:^|\s)(?:[\w-]+:)*text-(?:xs|sm|fine|meta|label)(?=$|\s)/;
|
|
208
218
|
|
|
209
219
|
/** Base-tone small text on a tint of the same tone. */
|
|
210
220
|
/** @param {string} value */
|
|
@@ -249,6 +259,43 @@ function predicateVisitors(predicate, message) {
|
|
|
249
259
|
const VIEWPORT_BREAKPOINT =
|
|
250
260
|
/(?:^|\s)(?:[\w-]+:)*(?:max-)?(?:sm|md|lg|xl|2xl):[^\s:]/;
|
|
251
261
|
|
|
262
|
+
/**
|
|
263
|
+
* A raw Tailwind type step in a class string, when the Ekanos type scale names
|
|
264
|
+
* a tier for it.
|
|
265
|
+
*
|
|
266
|
+
* Three things the pattern has to get right, because a rule that cries wolf
|
|
267
|
+
* on `text-muted-foreground` is turned off the same week:
|
|
268
|
+
*
|
|
269
|
+
* - only the thirteen SIZE steps, listed out. Every other `text-*` utility
|
|
270
|
+
* — a colour (`text-primary`, `text-muted-foreground`), an alignment
|
|
271
|
+
* (`text-center`), a wrap (`text-balance`), an overflow
|
|
272
|
+
* (`text-ellipsis`), and the TIER names themselves — shares the prefix
|
|
273
|
+
* and must pass. A tier is not in the list, so `text-subhead` never
|
|
274
|
+
* matches, and neither do the `-lg` ranks: `text-subhead-lg` continues
|
|
275
|
+
* past `text-` into a name the alternation does not hold, so there is
|
|
276
|
+
* nothing for the trailing `(?=$|\s)` to anchor;
|
|
277
|
+
* - an arbitrary value only when it is a SIZE: `text-[18px]`,
|
|
278
|
+
* `text-[0.8rem]`, `text-[length:var(--x)]`. `text-[#fff]` and
|
|
279
|
+
* `text-[color:var(--brand)]` are colours and pass;
|
|
280
|
+
* - a variant chain in front of it (`sm:text-lg`, `2xl:text-2xl`,
|
|
281
|
+
* `data-[state=open]:text-xs`). A chain always ends in `:` immediately
|
|
282
|
+
* before `text-`, so the whole of it is covered by allowing `:` as the
|
|
283
|
+
* left boundary — deliberately, rather than by matching the chain the way
|
|
284
|
+
* the overlay-width rule does. That rule's nested `(?:…|…)+:)*` prefix
|
|
285
|
+
* group backtracks exponentially on a long non-matching string; it gets
|
|
286
|
+
* away with it because it only ever sees an overlay's `className`,
|
|
287
|
+
* whereas this rule is handed EVERY string literal in the repo and took
|
|
288
|
+
* oxlint from one second to over thirty minutes. The `-` excluded from
|
|
289
|
+
* the boundary is what keeps it off `--text-lg` in a stylesheet string.
|
|
290
|
+
*
|
|
291
|
+
* Tailwind's `/leading` modifier (`text-sm/6`) rides along on the end.
|
|
292
|
+
*/
|
|
293
|
+
const RAW_TEXT_SIZE =
|
|
294
|
+
/(?:^|[\s:])text-(?:xs|sm|base|lg|xl|[2-9]xl|\[(?:length:[^\]]*|[\d.]+(?:rem|px|em|ex|ch|pt|pc|in|cm|mm|vw|vh|vmin|vmax|%))\])(?:\/[\w.[\]-]+)?(?=$|\s)/;
|
|
295
|
+
|
|
296
|
+
const RAW_TEXT_SIZE_MESSAGE =
|
|
297
|
+
'A raw type step is not the scale. Use the Ekanos type-scale tier: text-display-lg (48px) | text-display (36px) | text-headline-lg (30px, 700) | text-headline (24px, 600) | text-subhead-lg (20px, 600) | text-subhead (18px, 600) | text-title (16px, 600) | text-body (16px, 400) | text-label (14px, 500) | text-meta (14px, 400) | text-fine (12px). Each tier carries its leading and weight, so the font-* class beside the old step usually goes with it (design-system type-scale rule).';
|
|
298
|
+
|
|
252
299
|
const TOOLBAR_TAB_ELEMENTS = new Set(['TabsList', 'ScopePills', 'ScopeLinks']);
|
|
253
300
|
const TOOLBARS = new Set(['PageToolbar', 'PageToolbarActions']);
|
|
254
301
|
|
|
@@ -433,5 +480,15 @@ export default {
|
|
|
433
480
|
};
|
|
434
481
|
},
|
|
435
482
|
},
|
|
483
|
+
'no-raw-text-size': {
|
|
484
|
+
meta: {
|
|
485
|
+
type: 'problem',
|
|
486
|
+
docs: {
|
|
487
|
+
description:
|
|
488
|
+
'Disallow a raw Tailwind type step; one scale, nine tiers, each carrying its leading and weight.',
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
create: stringVisitors(RAW_TEXT_SIZE, RAW_TEXT_SIZE_MESSAGE),
|
|
492
|
+
},
|
|
436
493
|
},
|
|
437
494
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekanos/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"private": false,
|
|
4
5
|
"description": "The official SDK for building Ekanos integrations.",
|
|
6
|
+
"homepage": "https://sdk.ekanos.dev/sdk/overview",
|
|
5
7
|
"bugs": {
|
|
6
8
|
"email": "npm@govastly.com"
|
|
7
9
|
},
|
|
@@ -75,8 +77,8 @@
|
|
|
75
77
|
"access": "public"
|
|
76
78
|
},
|
|
77
79
|
"dependencies": {
|
|
78
|
-
"@ekanos/integration-schema": "0.
|
|
79
|
-
"@ekanos/ui": "0.
|
|
80
|
+
"@ekanos/integration-schema": "0.8.0",
|
|
81
|
+
"@ekanos/ui": "0.8.0",
|
|
80
82
|
"@supabase/supabase-js": "2.87.1",
|
|
81
83
|
"server-only": "^0.0.1"
|
|
82
84
|
},
|
|
@@ -87,7 +89,7 @@
|
|
|
87
89
|
"@types/react": "19.2.18",
|
|
88
90
|
"@types/react-dom": "19.2.5",
|
|
89
91
|
"eslint": "^10.11.0",
|
|
90
|
-
"next": "16.4.
|
|
92
|
+
"next": "16.4.2",
|
|
91
93
|
"react": "19.2.8",
|
|
92
94
|
"react-dom": "19.2.8",
|
|
93
95
|
"react-hook-form": "^7.88.0",
|