@aifabrix/builder 2.52.0 → 2.52.1

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 (134) hide show
  1. package/jest.isolated-projects.js +25 -2
  2. package/jest.projects.js +4 -1
  3. package/lib/api/datasources-extended.api.js +8 -8
  4. package/lib/api/keycloak.api.js +92 -0
  5. package/lib/api/token-providers.api.js +77 -0
  6. package/lib/api/types/keycloak.types.js +23 -0
  7. package/lib/api/types/token-providers.types.js +65 -0
  8. package/lib/app/down.js +16 -1
  9. package/lib/channels/add-channel.js +126 -19
  10. package/lib/channels/approval-guides/chatgpt.js +1 -1
  11. package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
  12. package/lib/channels/channel-add-ensure.js +128 -0
  13. package/lib/channels/channel-artifact-generator.js +11 -1
  14. package/lib/channels/channel-branding.js +94 -0
  15. package/lib/channels/channel-credential.js +8 -0
  16. package/lib/channels/channel-discovery.js +330 -0
  17. package/lib/channels/channel-external-system.js +1 -1
  18. package/lib/channels/channel-runtime-resolver.js +103 -14
  19. package/lib/channels/channel-status-display.js +7 -1
  20. package/lib/channels/microsoft-copilot-channel-add.js +138 -0
  21. package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
  22. package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
  23. package/lib/channels/microsoft-copilot-icons.js +74 -0
  24. package/lib/channels/microsoft-copilot-limits.js +84 -0
  25. package/lib/channels/microsoft-copilot-package.js +130 -49
  26. package/lib/channels/microsoft-copilot-validate.js +197 -0
  27. package/lib/cli/index.js +2 -0
  28. package/lib/cli/infra-guided.js +13 -6
  29. package/lib/cli/setup-infra.js +31 -4
  30. package/lib/commands/channel-add-setup.js +28 -9
  31. package/lib/commands/channel.js +14 -10
  32. package/lib/commands/integration-definition.js +158 -0
  33. package/lib/commands/repair-auth-apply.js +19 -5
  34. package/lib/commands/repair-datasource-sync-operations.js +4 -1
  35. package/lib/commands/role-assistant-test-output.js +200 -0
  36. package/lib/commands/role-assistant.js +125 -166
  37. package/lib/datasource/capability/capability-resolve.js +15 -5
  38. package/lib/datasource/capability/copy-operations.js +14 -4
  39. package/lib/infrastructure/index.js +19 -4
  40. package/lib/integration-definition/apply.js +478 -0
  41. package/lib/integration-definition/canonical-json.js +51 -0
  42. package/lib/integration-definition/emit-suite-bid.js +94 -0
  43. package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
  44. package/lib/integration-definition/enrich-openapi-operations.js +434 -0
  45. package/lib/integration-definition/from-discovery.js +339 -0
  46. package/lib/integration-definition/hash.js +37 -0
  47. package/lib/integration-definition/index.js +37 -0
  48. package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
  49. package/lib/integration-definition/resource-type-catalog.js +59 -0
  50. package/lib/integration-definition/system-kind-enum.js +40 -0
  51. package/lib/integration-definition/validate.js +148 -0
  52. package/lib/programmatic/builder-api-request.js +5 -1
  53. package/lib/programmatic/builder-help-governance.js +3 -2
  54. package/lib/programmatic/capability-registry.js +19 -0
  55. package/lib/programmatic/openapi-component-schemas.js +6 -1
  56. package/lib/programmatic/openapi-descriptions.js +40 -0
  57. package/lib/programmatic/openapi-envelope-examples.js +269 -0
  58. package/lib/programmatic/openapi-operation-bindings.js +45 -0
  59. package/lib/programmatic/openapi-request-schemas.js +14 -0
  60. package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
  61. package/lib/programmatic/openapi-result-schemas.js +46 -99
  62. package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
  63. package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
  64. package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
  65. package/lib/programmatic/openapi-schema-helpers.js +9 -9
  66. package/lib/programmatic/openapi-spec.js +11 -1
  67. package/lib/programmatic/orchestration-openapi-meta.js +10 -0
  68. package/lib/programmatic/route-handler-map.js +29 -1
  69. package/lib/programmatic/run-channel-package.js +236 -0
  70. package/lib/programmatic/run-integration-definition.js +69 -0
  71. package/lib/role-assistant/knowledge-sync-logging.js +58 -0
  72. package/lib/role-assistant/knowledge-sync.js +88 -93
  73. package/lib/role-assistant/test-cases-bid.js +155 -0
  74. package/lib/role-assistant/test-cases-evaluate.js +9 -1
  75. package/lib/role-assistant/test-cases-honesty.js +24 -10
  76. package/lib/role-assistant/test-cases.js +28 -0
  77. package/lib/role-assistant/test-runner-bid-apply.js +122 -0
  78. package/lib/role-assistant/test-runner-bid-document.js +278 -0
  79. package/lib/role-assistant/test-runner-workhub-answers.js +245 -20
  80. package/lib/role-assistant/test-runner-workhub.js +100 -18
  81. package/lib/role-assistant/test-suite.js +119 -0
  82. package/lib/schema/application-schema.json +56 -6
  83. package/lib/schema/external-datasource.schema.json +103 -17
  84. package/lib/schema/external-system.schema.json +54 -3
  85. package/lib/schema/integration-definition.schema.json +195 -0
  86. package/lib/utils/authentication-shape.js +6 -0
  87. package/lib/utils/docker-build.js +3 -3
  88. package/lib/utils/help-builder.js +1 -0
  89. package/lib/utils/promote-dev-docker-image.js +97 -18
  90. package/lib/utils/transient-developer-id.js +28 -0
  91. package/package.json +1 -1
  92. package/scripts/pnpm/af.mjs +24 -9
  93. package/scripts/pnpm/help.mjs +1 -1
  94. package/templates/applications/dataplane/application.yaml +3 -2
  95. package/templates/applications/dataplane/env.template +7 -6
  96. package/templates/applications/miso-controller/application.yaml +23 -9
  97. package/templates/applications/miso-controller/env.template +8 -2
  98. package/templates/channels/README.md +11 -1
  99. package/templates/channels/brand/color-192.png +0 -0
  100. package/templates/channels/brand/dark/128.png +0 -0
  101. package/templates/channels/brand/dark/192.png +0 -0
  102. package/templates/channels/brand/dark/256.png +0 -0
  103. package/templates/channels/brand/dark/32.png +0 -0
  104. package/templates/channels/brand/dark/512.png +0 -0
  105. package/templates/channels/brand/dark/64.png +0 -0
  106. package/templates/channels/brand/light/128.png +0 -0
  107. package/templates/channels/brand/light/192.png +0 -0
  108. package/templates/channels/brand/light/256.png +0 -0
  109. package/templates/channels/brand/light/32.png +0 -0
  110. package/templates/channels/brand/light/512.png +0 -0
  111. package/templates/channels/brand/light/64.png +0 -0
  112. package/templates/channels/brand/mono-white/128.png +0 -0
  113. package/templates/channels/brand/mono-white/192.png +0 -0
  114. package/templates/channels/brand/mono-white/256.png +0 -0
  115. package/templates/channels/brand/mono-white/32.png +0 -0
  116. package/templates/channels/brand/mono-white/512.png +0 -0
  117. package/templates/channels/brand/mono-white/64.png +0 -0
  118. package/templates/channels/brand/outline-32.png +0 -0
  119. package/templates/channels/chatgpt/install.template.md.hbs +6 -4
  120. package/templates/channels/chatgpt/integration/README.md +4 -5
  121. package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
  122. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
  123. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
  124. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
  125. package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
  126. package/templates/channels/microsoft-copilot/integration/README.md +2 -3
  127. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
  128. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
  129. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
  130. package/templates/channels/slack/integration/README.md +2 -4
  131. package/templates/channels/teams-bot/install.template.md.hbs +4 -0
  132. package/templates/channels/teams-bot/integration/README.md +3 -5
  133. package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
  134. /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
@@ -67,6 +67,11 @@ const ISOLATED_SUITE_ENTRIES = [
67
67
  ['role-assistant-knowledge-sync', ['**/tests/lib/role-assistant/knowledge-sync.test.js']],
68
68
  ['role-assistant-evidence-sync', ['**/tests/lib/role-assistant/evidence-sync.test.js']],
69
69
  ['role-assistant-test-cases', ['**/tests/lib/role-assistant/test-cases.test.js']],
70
+ ['role-assistant-test-suite', ['**/tests/lib/role-assistant/test-suite.test.js']],
71
+ [
72
+ 'role-assistant-workhub-answers',
73
+ ['**/tests/lib/role-assistant/test-runner-workhub-answers.test.js']
74
+ ],
70
75
  ['governance-fixture-subjects', ['**/tests/lib/commands/governance-fixture-subjects.test.js']],
71
76
  ['enterprise-knowledge-generate-manifest', ['**/tests/lib/commands/enterprise-knowledge-generate-manifest.test.js']],
72
77
  ['plan-prevalidate-gates', ['**/tests/lib/plan-prevalidate-gates.test.js']],
@@ -152,7 +157,19 @@ const ISOLATED_SUITE_ENTRIES = [
152
157
  [
153
158
  '**/tests/lib/channels/channel-integration-generator.test.js',
154
159
  '**/tests/lib/channels/channel-artifact-generator.test.js',
155
- '**/tests/lib/channels/channel-integration-template.test.js'
160
+ '**/tests/lib/channels/channel-integration-template.test.js',
161
+ '**/tests/lib/channels/add-channel.test.js',
162
+ '**/tests/lib/channels/microsoft-copilot-package-quality.test.js',
163
+ '**/tests/lib/channels/channel-branding.test.js'
164
+ ]
165
+ ],
166
+ [
167
+ 'integration-definition-real-fs',
168
+ [
169
+ '**/tests/lib/validation/integration-definition-schema.test.js',
170
+ '**/tests/lib/integration-definition/apply.test.js',
171
+ '**/tests/lib/integration-definition/from-discovery.test.js',
172
+ '**/tests/lib/integration-definition/emit-suite-bid.test.js'
156
173
  ]
157
174
  ],
158
175
  [
@@ -175,6 +192,10 @@ const ISOLATED_SUITE_ENTRIES = [
175
192
  '**/tests/lib/scripts/sync-builder-api-template.test.js',
176
193
  '**/tests/lib/evidence/evidence-service.test.js'
177
194
  ]
195
+ ],
196
+ [
197
+ 'enrich-openapi-operations',
198
+ ['**/tests/lib/integration-definition/enrich-openapi-operations.test.js']
178
199
  ]
179
200
  ];
180
201
 
@@ -224,7 +245,9 @@ function buildDefaultTestPathIgnorePatterns(includeLocalTests) {
224
245
  '/tests/integration/',
225
246
  '\\\\tests\\\\integration\\\\',
226
247
  '/tests/manual/',
227
- '\\\\tests\\\\manual\\\\'
248
+ '\\\\tests\\\\manual\\\\',
249
+ '/builder/',
250
+ '\\\\builder\\\\'
228
251
  ];
229
252
  const globs = [
230
253
  ...ISOLATED_SUITE_ENTRIES.flatMap((entry) => entry[1]),
package/jest.projects.js CHANGED
@@ -52,7 +52,10 @@ const defaultProject = {
52
52
  ],
53
53
  testPathIgnorePatterns: buildDefaultTestPathIgnorePatterns(),
54
54
  modulePathIgnorePatterns: [
55
- '<rootDir>/docker-builder-api-staging/'
55
+ '<rootDir>/docker-builder-api-staging/',
56
+ // Generated app dirs under builder/ (except builder-api) — avoid haste/transform
57
+ // picking up junk written by generator/cli suites (e.g. builder/testapp).
58
+ '<rootDir>/builder/(?!builder-api(?:/|$))'
56
59
  ],
57
60
  setupFiles: ['<rootDir>/tests/capture-real-fs.js'],
58
61
  setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
@@ -69,28 +69,28 @@ async function detachPolicy(dataplaneUrl, sourceIdOrKey, policyIdOrKey, authConf
69
69
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
70
70
  return await client.delete(`/api/v1/external/${sourceIdOrKey}/policies/${policyIdOrKey}`);
71
71
  }
72
- /** @requiresPermission {Dataplane} external-system:read (sync) */
72
+ /** @requiresPermission {Dataplane} external-data-source:read (sync under /data-storage) */
73
73
  async function listSyncJobs(dataplaneUrl, sourceIdOrKey, authConfig, options = {}) {
74
74
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
75
- return await client.get(`/api/v1/external/${sourceIdOrKey}/sync`, { params: options });
75
+ return await client.get(`/api/v1/data-storage/${sourceIdOrKey}/sync`, { params: options });
76
76
  }
77
77
  async function createSyncJob(dataplaneUrl, sourceIdOrKey, authConfig, syncData) {
78
78
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
79
- return await client.post(`/api/v1/external/${sourceIdOrKey}/sync`, { body: syncData });
79
+ return await client.post(`/api/v1/data-storage/${sourceIdOrKey}/sync`, { body: syncData });
80
80
  }
81
- /** @requiresPermission {Dataplane} external-system:read */
81
+ /** @requiresPermission {Dataplane} external-data-source:read */
82
82
  async function getSyncJob(dataplaneUrl, sourceIdOrKey, syncJobId, authConfig) {
83
83
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
84
- return await client.get(`/api/v1/external/${sourceIdOrKey}/sync/${syncJobId}`);
84
+ return await client.get(`/api/v1/data-storage/${sourceIdOrKey}/sync/${syncJobId}`);
85
85
  }
86
- /** @requiresPermission {Dataplane} external-system:update */
86
+ /** @requiresPermission {Dataplane} external-data-source:sync */
87
87
  async function updateSyncJob(dataplaneUrl, sourceIdOrKey, syncJobId, authConfig, updateData) {
88
88
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
89
- return await client.put(`/api/v1/external/${sourceIdOrKey}/sync/${syncJobId}`, { body: updateData });
89
+ return await client.put(`/api/v1/data-storage/${sourceIdOrKey}/sync/${syncJobId}`, { body: updateData });
90
90
  }
91
91
  async function executeSyncJob(dataplaneUrl, sourceIdOrKey, syncJobId, authConfig) {
92
92
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
93
- return await client.post(`/api/v1/external/${sourceIdOrKey}/sync/${syncJobId}/execute`);
93
+ return await client.post(`/api/v1/data-storage/${sourceIdOrKey}/sync/${syncJobId}/execute`);
94
94
  }
95
95
  /** @requiresPermission {Dataplane} external-data-source:sync */
96
96
  async function validateDocuments(dataplaneUrl, sourceIdOrKey, authConfig, validateData) {
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @fileoverview Keycloak status API (Controller) — Entra IdP ids for packaging
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { ApiClient } = require('./index');
10
+
11
+ const KEYCLOAK_STATUS_PATH = '/api/v1/keycloak/status';
12
+
13
+ /**
14
+ * @param {Object} response
15
+ * @returns {import('./types/keycloak.types').KeycloakStatus|null}
16
+ */
17
+ function extractKeycloakStatus(response) {
18
+ if (!response || typeof response !== 'object' || response.success === false) {
19
+ return null;
20
+ }
21
+ const body = response.data ?? response;
22
+ const data = body?.data !== undefined ? body.data : body;
23
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
24
+ return null;
25
+ }
26
+ return data;
27
+ }
28
+
29
+ /**
30
+ * @param {unknown} value
31
+ * @returns {string|null}
32
+ */
33
+ function trimNonEmptyString(value) {
34
+ if (typeof value !== 'string') {
35
+ return null;
36
+ }
37
+ const trimmed = value.trim();
38
+ return trimmed || null;
39
+ }
40
+
41
+ /**
42
+ * @param {import('./types/keycloak.types').KeycloakStatus|null} status
43
+ * @returns {import('./types/keycloak.types').KeycloakEntraIdentityProvider|null}
44
+ */
45
+ function extractEntraIdentityProvider(status) {
46
+ const raw = status?.entraIdentityProvider;
47
+ if (!raw || typeof raw !== 'object') {
48
+ return null;
49
+ }
50
+ const enterpriseAppId = trimNonEmptyString(raw.enterpriseAppId);
51
+ if (!enterpriseAppId) {
52
+ return null;
53
+ }
54
+ const tenantId = trimNonEmptyString(raw.tenantId);
55
+ return tenantId ? { enterpriseAppId, tenantId } : { enterpriseAppId };
56
+ }
57
+
58
+ /**
59
+ * GET /api/v1/keycloak/status
60
+ * @requiresPermission {Controller} admin:keycloak
61
+ * @async
62
+ * @param {string} controllerUrl - Miso Controller base URL
63
+ * @param {Object} authConfig - Authentication configuration (device Bearer)
64
+ * @returns {Promise<import('./types/keycloak.types').KeycloakStatus|null>}
65
+ */
66
+ async function getKeycloakStatus(controllerUrl, authConfig) {
67
+ const client = new ApiClient(controllerUrl, authConfig);
68
+ const response = await client.get(KEYCLOAK_STATUS_PATH);
69
+ return extractKeycloakStatus(response);
70
+ }
71
+
72
+ /**
73
+ * Resolve Entra SSO enterprise app id from Keycloak status, or null when missing.
74
+ * Does not invent ids.
75
+ * @requiresPermission {Controller} admin:keycloak
76
+ * @async
77
+ * @param {string} controllerUrl - Miso Controller base URL
78
+ * @param {Object} authConfig - Authentication configuration
79
+ * @returns {Promise<import('./types/keycloak.types').KeycloakEntraIdentityProvider|null>}
80
+ */
81
+ async function getKeycloakEntraIdentityProvider(controllerUrl, authConfig) {
82
+ const status = await getKeycloakStatus(controllerUrl, authConfig);
83
+ return extractEntraIdentityProvider(status);
84
+ }
85
+
86
+ module.exports = {
87
+ KEYCLOAK_STATUS_PATH,
88
+ getKeycloakStatus,
89
+ getKeycloakEntraIdentityProvider,
90
+ extractKeycloakStatus,
91
+ extractEntraIdentityProvider
92
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @fileoverview Admin token providers API (Controller Trust sources)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { ApiClient } = require('./index');
10
+
11
+ const ADMIN_BASE = '/api/v1/admin/token-providers';
12
+ const MICROSOFT_ENTRA_ID_KEY = 'microsoft-entra-id';
13
+
14
+ /**
15
+ * @param {Object} response
16
+ * @returns {import('./types/token-providers.types').TokenProvider[]}
17
+ */
18
+ function extractTokenProviders(response) {
19
+ const body = response?.data ?? response;
20
+ const data = body?.data ?? body;
21
+ if (Array.isArray(data)) {
22
+ return data;
23
+ }
24
+ if (Array.isArray(data?.items)) {
25
+ return data.items;
26
+ }
27
+ return [];
28
+ }
29
+
30
+ /**
31
+ * List admin token providers (paginated).
32
+ * GET /api/v1/admin/token-providers
33
+ * @requiresPermission {Controller} token-provider:read
34
+ * @async
35
+ * @param {string} controllerUrl - Miso Controller base URL
36
+ * @param {Object} authConfig - Authentication configuration (device Bearer)
37
+ * @param {import('./types/token-providers.types').ListAdminTokenProvidersOptions} options
38
+ * @returns {Promise<Object>} Controller list response
39
+ * @throws {Error} If direction is missing or request fails
40
+ */
41
+ async function listAdminTokenProviders(controllerUrl, authConfig, options = {}) {
42
+ const direction = options?.direction;
43
+ if (direction !== 'inbound' && direction !== 'outbound') {
44
+ throw new Error('direction is required and must be "inbound" or "outbound"');
45
+ }
46
+ const client = new ApiClient(controllerUrl, authConfig);
47
+ const params = { ...options, direction };
48
+ return await client.get(ADMIN_BASE, { params });
49
+ }
50
+
51
+ /**
52
+ * Resolve inbound Microsoft Entra ID Trust source, or null when missing.
53
+ * Does not invent issuer, audience, or scopes.
54
+ * @requiresPermission {Controller} token-provider:read
55
+ * @async
56
+ * @param {string} controllerUrl - Miso Controller base URL
57
+ * @param {Object} authConfig - Authentication configuration
58
+ * @returns {Promise<import('./types/token-providers.types').TokenProvider|null>}
59
+ */
60
+ async function getInboundMicrosoftEntraIdProvider(controllerUrl, authConfig) {
61
+ const response = await listAdminTokenProviders(controllerUrl, authConfig, {
62
+ direction: 'inbound',
63
+ search: MICROSOFT_ENTRA_ID_KEY,
64
+ page: 1,
65
+ pageSize: 50
66
+ });
67
+ const items = extractTokenProviders(response);
68
+ const exact = items.find((item) => item && item.key === MICROSOFT_ENTRA_ID_KEY);
69
+ return exact || null;
70
+ }
71
+
72
+ module.exports = {
73
+ listAdminTokenProviders,
74
+ getInboundMicrosoftEntraIdProvider,
75
+ extractTokenProviders,
76
+ MICROSOFT_ENTRA_ID_KEY
77
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @fileoverview Keycloak status API type definitions (Controller)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ /**
8
+ * Entra identity provider fragment from Keycloak status.
9
+ * @typedef {Object} KeycloakEntraIdentityProvider
10
+ * @property {string} enterpriseAppId - Entra SSO application (client) id
11
+ * @property {string} [tenantId] - Entra tenant id when known
12
+ */
13
+
14
+ /**
15
+ * Keycloak configuration status payload (`data` of GET /api/v1/keycloak/status).
16
+ * @typedef {Object} KeycloakStatus
17
+ * @property {string} [realm]
18
+ * @property {string} [status]
19
+ * @property {string} [controllerClientId]
20
+ * @property {KeycloakEntraIdentityProvider} [entraIdentityProvider]
21
+ */
22
+
23
+ module.exports = {};
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @fileoverview Token provider API type definitions (Controller admin Trust sources)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ /**
8
+ * Token provider direction
9
+ * @typedef {'inbound'|'outbound'} TokenProviderDirection
10
+ */
11
+
12
+ /**
13
+ * Token provider subject type
14
+ * @typedef {'user'|'service'|'both'} TokenProviderSubjectType
15
+ */
16
+
17
+ /**
18
+ * Allowed operation on a token provider
19
+ * @typedef {'mcp.invoke'|'dataplane.read'|'dataplane.write'} TokenProviderAllowedOperation
20
+ */
21
+
22
+ /**
23
+ * Admin token provider list item (Controller TokenProvider schema)
24
+ * @typedef {Object} TokenProvider
25
+ * @property {string} id - Unique identifier (cuid)
26
+ * @property {string} key - Unique slug (e.g. microsoft-entra-id)
27
+ * @property {string} displayName - Human-readable name
28
+ * @property {string} issuer - JWT issuer URL (iss claim)
29
+ * @property {string} jwksUri - JWKS endpoint for key verification
30
+ * @property {string|null} [audience] - Expected audience claim (may be null)
31
+ * @property {TokenProviderSubjectType} subjectType - Subject type
32
+ * @property {TokenProviderDirection} direction - inbound or outbound
33
+ * @property {string|null} [identityClaim] - Claim used for user identity
34
+ * @property {string|null} [emailClaim] - Claim used for email
35
+ * @property {boolean} [autoProvision] - Whether to auto-create users
36
+ * @property {string|null} [serviceUserKey] - Service user key for service flow
37
+ * @property {TokenProviderAllowedOperation[]} [allowedOperations] - Allowed operations
38
+ * @property {string[]} [allowedScopes] - OAuth scopes (may be empty)
39
+ * @property {boolean} enabled - Whether this provider is enabled
40
+ * @property {string} [createdAt] - Created at
41
+ * @property {string} [updatedAt] - Updated at
42
+ */
43
+
44
+ /**
45
+ * List admin token providers query options
46
+ * @typedef {Object} ListAdminTokenProvidersOptions
47
+ * @property {TokenProviderDirection} direction - Required by OpenAPI
48
+ * @property {string} [search] - Search across key, displayName, issuer
49
+ * @property {number} [page] - Page number
50
+ * @property {number} [pageSize] - Page size
51
+ * @property {string} [sort] - Sort expression
52
+ * @property {string} [filter] - Filter expression
53
+ * @property {string} [enabled] - 'true' | 'false'
54
+ * @property {TokenProviderSubjectType} [subjectType] - Subject type filter
55
+ */
56
+
57
+ /**
58
+ * Paginated list response for admin token providers
59
+ * @typedef {Object} ListAdminTokenProvidersResponse
60
+ * @property {TokenProvider[]} data - Provider rows
61
+ * @property {Object} meta - Pagination meta
62
+ * @property {Object} [links] - Pagination links
63
+ */
64
+
65
+ module.exports = {};
package/lib/app/down.js CHANGED
@@ -111,6 +111,20 @@ async function stopAppContainers(appName, companionKeys, developerId) {
111
111
  }
112
112
  }
113
113
 
114
+ /**
115
+ * Resolve developer id for downApp (optional override does not change config).
116
+ * @param {Object} options
117
+ * @returns {Promise<string>}
118
+ */
119
+ async function resolveDownAppDeveloperId(options) {
120
+ const override = options && options.developerId;
121
+ if (override === undefined || override === null || override === '') {
122
+ return config.getDeveloperId();
123
+ }
124
+ const { validateAndNormalizeDeveloperId } = require('../core/config-normalize');
125
+ return validateAndNormalizeDeveloperId(override);
126
+ }
127
+
114
128
  /**
115
129
  * Stops and removes a specific application's container.
116
130
  * If --volumes is passed, attempts to remove the app's named Docker volume.
@@ -122,6 +136,7 @@ async function stopAppContainers(appName, companionKeys, developerId) {
122
136
  * @param {string} appName - Application name
123
137
  * @param {Object} options - Options
124
138
  * @param {boolean} [options.volumes=false] - Remove Docker volume data
139
+ * @param {string|number} [options.developerId] - Target developer for this stop only
125
140
  * @returns {Promise<void>} Resolves when operation completes
126
141
  * @throws {Error} If validation fails or Docker operations error (other than missing volume)
127
142
  *
@@ -136,7 +151,7 @@ async function downApp(appName, options = {}) {
136
151
  validateAppName(appName);
137
152
 
138
153
  const volumes = !!options.volumes;
139
- const developerId = await config.getDeveloperId();
154
+ const developerId = await resolveDownAppDeveloperId(options);
140
155
  const appConfig = loadBuilderApplicationConfig(appName);
141
156
  const companionKeys = getCeleryCompanionAppKeys(appName, appConfig);
142
157
 
@@ -10,9 +10,15 @@
10
10
 
11
11
  const { getChannelTarget, getDefaultChannelDescription } = require('./channel-targets');
12
12
  const { resolveAppDescription } = require('./channel-description');
13
+ const path = require('path');
13
14
  const { resolveChannelRuntimeValues } = require('./channel-runtime-resolver');
14
15
  const { generateChannelArtifact } = require('./channel-artifact-generator');
15
16
  const { fetchRuntimeLayerBToolNames } = require('./runtime-tool-catalog');
17
+ const { loadChannelPackageContext } = require('./channel-discovery');
18
+ const {
19
+ readChannelBranding,
20
+ mergeChannelBranding
21
+ } = require('./channel-branding');
16
22
  const {
17
23
  getExistingChannelExtension,
18
24
  buildChannelExtensionBlock,
@@ -39,9 +45,14 @@ const {
39
45
  } = require('./channel-display-name');
40
46
  const {
41
47
  displayExistingChannelStatus,
42
- displayChannelInstallResult,
43
- displayChannelError
48
+ displayChannelInstallResult
44
49
  } = require('./channel-status-display');
50
+ const { ensureChannelReadyForAdd } = require('./channel-add-ensure');
51
+ const {
52
+ assertMicrosoftCopilotOauthReference,
53
+ assertMicrosoftCopilotOauthReferenceBeforeForce,
54
+ loadMicrosoftCopilotEntraContext
55
+ } = require('./microsoft-copilot-channel-add');
45
56
 
46
57
  /**
47
58
  * @param {typeof import('./channel-targets').CHANNEL_TARGETS[string]} target
@@ -252,6 +263,9 @@ async function completeLiveInstall(params) {
252
263
  capabilities: target.capabilities,
253
264
  artifactPath: artifact.outputPath,
254
265
  installGuidePath: artifact.installPath,
266
+ zipPath: artifact.zipPath,
267
+ installReady: artifact.installReady,
268
+ blocker: artifact.blocker,
255
269
  validation: toValidationDisplay(validation),
256
270
  approvalGuide
257
271
  });
@@ -272,7 +286,10 @@ function finishDryRunInstall(params) {
272
286
  credentialKey: params.target.credentialKey,
273
287
  capabilities: params.target.capabilities,
274
288
  artifactPath: params.artifact.outputPath,
275
- installGuidePath: params.artifact.installPath
289
+ installGuidePath: params.artifact.installPath,
290
+ zipPath: params.artifact.zipPath,
291
+ installReady: params.artifact.installReady,
292
+ blocker: params.artifact.blocker
276
293
  });
277
294
  return { dryRun: true, channelBlock: params.channelBlock, artifact: params.artifact };
278
295
  }
@@ -281,12 +298,78 @@ function finishDryRunInstall(params) {
281
298
  * @param {Object} params
282
299
  * @returns {Promise<{ artifact: Object, channelBlock: Object, status: string }>}
283
300
  */
301
+ /**
302
+ * @param {Object|null} packageContext
303
+ * @param {Object} options
304
+ * @param {'iconColorPath'|'iconOutlinePath'} key
305
+ * @returns {string|undefined}
306
+ */
307
+ function brandingIconOrOption(packageContext, options, key) {
308
+ if (typeof options[key] === 'string' && options[key].trim()) {
309
+ return options[key].trim();
310
+ }
311
+ if (packageContext && typeof packageContext[key] === 'string' && packageContext[key].trim()) {
312
+ return packageContext[key].trim();
313
+ }
314
+ return undefined;
315
+ }
316
+
317
+ async function resolvePackageContextForInstall(params) {
318
+ const { channelTarget, options, name, description, ctx } = params;
319
+ const packagingTargets = new Set(['chatgpt', 'microsoft-copilot', 'slack', 'teams-bot']);
320
+ if (!packagingTargets.has(channelTarget)) {
321
+ return null;
322
+ }
323
+ const projectRoot = options.projectRoot || process.cwd();
324
+ const workspaceDir = path.join(projectRoot, 'integration', channelTarget);
325
+ const branding = mergeChannelBranding(readChannelBranding(workspaceDir), {
326
+ displayName: options.overrideDisplayName || undefined,
327
+ description: options.overrideDescription || undefined,
328
+ iconColorPath: options.iconColorPath || undefined,
329
+ iconOutlinePath: options.iconOutlinePath || undefined
330
+ });
331
+ let packageContext;
332
+ try {
333
+ packageContext = await loadChannelPackageContext({
334
+ dataplaneBaseUrl: ctx.dataplaneUrl,
335
+ authConfig: ctx.authConfig,
336
+ channelTarget,
337
+ branding
338
+ });
339
+ } catch {
340
+ packageContext = {
341
+ displayName: name,
342
+ description,
343
+ governedToolNames: [],
344
+ mcpServerUrl: null,
345
+ installReady: false,
346
+ blocker: 'discoveryUnavailable',
347
+ authentication: null,
348
+ authenticationCapabilities: []
349
+ };
350
+ }
351
+ return {
352
+ ...packageContext,
353
+ iconColorPath: branding.iconColorPath,
354
+ iconOutlinePath: branding.iconOutlinePath
355
+ };
356
+ }
357
+
284
358
  async function buildInstallArtifacts(params) {
285
359
  const { channelTarget, options, target, existing, name, baseName, description, ctx } = params;
286
- const runtimeToolNames =
287
- channelTarget === 'chatgpt' || channelTarget === 'microsoft-copilot'
288
- ? await fetchRuntimeLayerBToolNames(ctx.dataplaneUrl, ctx.authConfig, channelTarget)
289
- : [];
360
+ const packageContext = await resolvePackageContextForInstall(params);
361
+ let runtimeToolNames = [];
362
+ if (channelTarget === 'chatgpt' || channelTarget === 'microsoft-copilot') {
363
+ runtimeToolNames =
364
+ packageContext?.governedToolNames?.length > 0
365
+ ? packageContext.governedToolNames
366
+ : await fetchRuntimeLayerBToolNames(ctx.dataplaneUrl, ctx.authConfig, channelTarget);
367
+ }
368
+ let entraProvider = null;
369
+ let entraIdentityProvider = null;
370
+ if (channelTarget === 'microsoft-copilot') {
371
+ ({ entraProvider, entraIdentityProvider } = await loadMicrosoftCopilotEntraContext(ctx));
372
+ }
290
373
  const runtimeValues = resolveChannelRuntimeValues({
291
374
  dataplaneBaseUrl: ctx.dataplaneUrl,
292
375
  channelTarget,
@@ -296,7 +379,14 @@ async function buildInstallArtifacts(params) {
296
379
  appDescription: description,
297
380
  icon: options.icon,
298
381
  commandPrefix: options.commandPrefix,
299
- layerBToolNames: runtimeToolNames
382
+ layerBToolNames: runtimeToolNames,
383
+ packageContext,
384
+ entraProvider,
385
+ entraIdentityProvider,
386
+ oauthPluginVaultReferenceId: options.oauthPluginVaultReferenceId,
387
+ scopes: options.scopes,
388
+ iconColorPath: brandingIconOrOption(packageContext, options, 'iconColorPath'),
389
+ iconOutlinePath: brandingIconOrOption(packageContext, options, 'iconOutlinePath')
300
390
  });
301
391
  const projectRoot = options.projectRoot || process.cwd();
302
392
  const artifact = generateArtifactWithAudit(channelTarget, {
@@ -313,17 +403,39 @@ async function buildInstallArtifacts(params) {
313
403
  return { artifact, channelBlock, status };
314
404
  }
315
405
 
406
+ /**
407
+ * @param {string} channelTarget
408
+ * @param {Object} options
409
+ * @returns {Object}
410
+ */
411
+ function assertChannelAddOptions(channelTarget, options) {
412
+ const target = getChannelTarget(channelTarget);
413
+ if (target.experimental) {
414
+ throw new Error(
415
+ `Channel target "${channelTarget}" is experimental. Confirm product requirement before use.`
416
+ );
417
+ }
418
+ if (!options.name || typeof options.name !== 'string' || !options.name.trim()) {
419
+ throw new Error('--name is required for channel add');
420
+ }
421
+ return target;
422
+ }
423
+
316
424
  /**
317
425
  * @param {string} channelTarget
318
426
  * @param {Object} options
319
427
  * @returns {Promise<Object>}
320
428
  */
321
429
  async function runAddChannel(channelTarget, options = {}) {
322
- const { target, name, baseName, description, ctx, existing } = await prepareChannelContext(
430
+ const target = assertChannelAddOptions(channelTarget, options);
431
+ if (channelTarget === 'microsoft-copilot' && options.force === true) {
432
+ await assertMicrosoftCopilotOauthReferenceBeforeForce(options);
433
+ }
434
+ await ensureChannelReadyForAdd(channelTarget, options);
435
+ const { name, baseName, description, ctx, existing } = await prepareChannelContext(
323
436
  channelTarget,
324
437
  options
325
438
  );
326
-
327
439
  const skipped = showExistingChannelStatus({
328
440
  existing,
329
441
  options,
@@ -333,7 +445,9 @@ async function runAddChannel(channelTarget, options = {}) {
333
445
  ctx
334
446
  });
335
447
  if (skipped) return skipped;
336
-
448
+ if (channelTarget === 'microsoft-copilot') {
449
+ await assertMicrosoftCopilotOauthReference({ options, ctx, name, baseName, description });
450
+ }
337
451
  const { artifact, channelBlock, status } = await buildInstallArtifacts({
338
452
  channelTarget,
339
453
  options,
@@ -344,11 +458,9 @@ async function runAddChannel(channelTarget, options = {}) {
344
458
  description,
345
459
  ctx
346
460
  });
347
-
348
461
  if (options.dryRun) {
349
462
  return finishDryRunInstall({ channelTarget, name, baseName, target, artifact, channelBlock });
350
463
  }
351
-
352
464
  return completeLiveInstall({
353
465
  ctx,
354
466
  channelTarget,
@@ -367,12 +479,7 @@ async function runAddChannel(channelTarget, options = {}) {
367
479
  * @param {Object} options
368
480
  */
369
481
  async function addChannelCommand(channelTarget, options = {}) {
370
- try {
371
- await runAddChannel(channelTarget, options);
372
- } catch (err) {
373
- displayChannelError(err.message || String(err));
374
- throw err;
375
- }
482
+ await runAddChannel(channelTarget, options);
376
483
  }
377
484
 
378
485
  module.exports = {
@@ -22,7 +22,7 @@ function buildChatGptApprovalGuide(ctx) {
22
22
  'Enable Developer mode for custom MCP apps (Business: Workspace settings → Apps, or Settings → Apps → Advanced settings; Enterprise/Edu: grant Developer mode under Permissions & roles → Connected data first).',
23
23
  'Create a custom MCP app with the Remote MCP URL from INSTALL.md (channel-chatgpt.json is a deployment reference, not a plugin archive).',
24
24
  'Select OAuth (authorization-code + PKCE S256). Do not configure a custom API key for a remote custom MCP app.',
25
- 'Scan tools, complete OAuth if prompted, review discovered Runtime Layer B tools, then save the app as a draft.',
25
+ 'Scan tools, complete OAuth if prompted, review discovered governed tools, then save the app as a draft.',
26
26
  formatCredentialPortalStep(
27
27
  ctx,
28
28
  `${ctx.credentialKey || 'channel'} credential references (not secrets to paste into ChatGPT)`