@aifabrix/builder 2.51.1 → 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 (187) 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/app/run-container-start.js +4 -0
  10. package/lib/app/run-deploy-job.js +135 -27
  11. package/lib/app/run-env-compose.js +32 -0
  12. package/lib/app/run-env-recovery.js +35 -0
  13. package/lib/app/run-helpers.js +5 -6
  14. package/lib/app/run.js +11 -3
  15. package/lib/channels/add-channel.js +126 -19
  16. package/lib/channels/approval-guides/chatgpt.js +12 -9
  17. package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
  18. package/lib/channels/channel-add-ensure.js +128 -0
  19. package/lib/channels/channel-artifact-generator.js +11 -1
  20. package/lib/channels/channel-branding.js +94 -0
  21. package/lib/channels/channel-credential.js +8 -0
  22. package/lib/channels/channel-discovery.js +330 -0
  23. package/lib/channels/channel-external-system.js +1 -1
  24. package/lib/channels/channel-runtime-resolver.js +103 -14
  25. package/lib/channels/channel-status-display.js +7 -1
  26. package/lib/channels/channel-targets.js +18 -1
  27. package/lib/channels/chatgpt-runtime-contract.js +1 -1
  28. package/lib/channels/microsoft-copilot-channel-add.js +138 -0
  29. package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
  30. package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
  31. package/lib/channels/microsoft-copilot-icons.js +74 -0
  32. package/lib/channels/microsoft-copilot-limits.js +84 -0
  33. package/lib/channels/microsoft-copilot-package.js +130 -49
  34. package/lib/channels/microsoft-copilot-validate.js +197 -0
  35. package/lib/cli/index.js +2 -0
  36. package/lib/cli/infra-guided-footers.js +1 -1
  37. package/lib/cli/infra-guided-public-url-warn.js +55 -0
  38. package/lib/cli/infra-guided.js +78 -62
  39. package/lib/cli/setup-app.help.js +4 -0
  40. package/lib/cli/setup-app.test-commands.js +59 -28
  41. package/lib/cli/setup-external-system.js +50 -12
  42. package/lib/cli/setup-infra.js +31 -4
  43. package/lib/commands/channel-add-setup.js +35 -10
  44. package/lib/commands/channel.js +27 -12
  45. package/lib/commands/deploy-gate-orchestrator.js +3 -2
  46. package/lib/commands/governance-verify-external.js +2 -5
  47. package/lib/commands/integration-definition.js +158 -0
  48. package/lib/commands/lifecycle-external.js +14 -7
  49. package/lib/commands/repair-auth-apply.js +19 -5
  50. package/lib/commands/repair-datasource-sync-operations.js +4 -1
  51. package/lib/commands/role-assistant-test-output.js +200 -0
  52. package/lib/commands/role-assistant.js +131 -114
  53. package/lib/commands/test-governance-external.js +2 -5
  54. package/lib/commands/up-miso.js +61 -24
  55. package/lib/commands/upload-data-sync-finalize.js +20 -3
  56. package/lib/commands/verify-governance-external.js +2 -5
  57. package/lib/commands/verify-operations-external.js +2 -5
  58. package/lib/commands/verify-operations-step-plan.js +31 -0
  59. package/lib/commands/verify-trust-command-action.js +2 -5
  60. package/lib/datasource/capability/capability-resolve.js +15 -5
  61. package/lib/datasource/capability/copy-operations.js +14 -4
  62. package/lib/datasource/datasource-identifier.js +4 -3
  63. package/lib/datasource/integration-context.js +3 -3
  64. package/lib/datasource/resolve-app.js +90 -8
  65. package/lib/datasource/test-e2e.js +2 -2
  66. package/lib/datasource/unified-validation-run-body.js +15 -1
  67. package/lib/datasource/unified-validation-run-resolve.js +2 -2
  68. package/lib/external-system/deploy-helpers.js +9 -5
  69. package/lib/external-system/deploy.js +2 -1
  70. package/lib/external-system/test-system-level.js +21 -3
  71. package/lib/external-system/test.js +4 -4
  72. package/lib/generator/builders.js +9 -15
  73. package/lib/infrastructure/index.js +19 -4
  74. package/lib/integration-definition/apply.js +478 -0
  75. package/lib/integration-definition/canonical-json.js +51 -0
  76. package/lib/integration-definition/emit-suite-bid.js +94 -0
  77. package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
  78. package/lib/integration-definition/enrich-openapi-operations.js +434 -0
  79. package/lib/integration-definition/from-discovery.js +339 -0
  80. package/lib/integration-definition/hash.js +37 -0
  81. package/lib/integration-definition/index.js +37 -0
  82. package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
  83. package/lib/integration-definition/resource-type-catalog.js +59 -0
  84. package/lib/integration-definition/system-kind-enum.js +40 -0
  85. package/lib/integration-definition/validate.js +148 -0
  86. package/lib/programmatic/builder-api-request.js +5 -1
  87. package/lib/programmatic/builder-help-governance.js +3 -2
  88. package/lib/programmatic/capability-registry.js +19 -0
  89. package/lib/programmatic/openapi-component-schemas.js +6 -1
  90. package/lib/programmatic/openapi-descriptions.js +40 -0
  91. package/lib/programmatic/openapi-envelope-examples.js +269 -0
  92. package/lib/programmatic/openapi-operation-bindings.js +45 -0
  93. package/lib/programmatic/openapi-request-schemas.js +14 -0
  94. package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
  95. package/lib/programmatic/openapi-result-schemas.js +46 -99
  96. package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
  97. package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
  98. package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
  99. package/lib/programmatic/openapi-schema-helpers.js +9 -9
  100. package/lib/programmatic/openapi-spec.js +11 -1
  101. package/lib/programmatic/orchestration-openapi-meta.js +10 -0
  102. package/lib/programmatic/route-handler-map.js +29 -1
  103. package/lib/programmatic/run-channel-package.js +236 -0
  104. package/lib/programmatic/run-integration-definition.js +69 -0
  105. package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
  106. package/lib/role-assistant/knowledge-sync-logging.js +58 -0
  107. package/lib/role-assistant/knowledge-sync.js +104 -89
  108. package/lib/role-assistant/test-cases-bid.js +155 -0
  109. package/lib/role-assistant/test-cases-evaluate.js +9 -1
  110. package/lib/role-assistant/test-cases-honesty.js +24 -10
  111. package/lib/role-assistant/test-cases.js +28 -0
  112. package/lib/role-assistant/test-runner-bid-apply.js +122 -0
  113. package/lib/role-assistant/test-runner-bid-document.js +278 -0
  114. package/lib/role-assistant/test-runner-workhub-answers.js +289 -46
  115. package/lib/role-assistant/test-runner-workhub.js +121 -25
  116. package/lib/role-assistant/test-suite.js +119 -0
  117. package/lib/schema/application-schema.json +64 -32
  118. package/lib/schema/external-datasource.schema.json +103 -17
  119. package/lib/schema/external-system.schema.json +54 -3
  120. package/lib/schema/infra.parameter.yaml +0 -20
  121. package/lib/schema/integration-definition.schema.json +195 -0
  122. package/lib/utils/authentication-shape.js +6 -0
  123. package/lib/utils/certify-external-workspace.js +40 -0
  124. package/lib/utils/docker-build.js +3 -3
  125. package/lib/utils/docker-run-ephemeral.js +77 -17
  126. package/lib/utils/health-check-public-warn.js +31 -13
  127. package/lib/utils/health-check.js +11 -1
  128. package/lib/utils/help-builder.js +1 -0
  129. package/lib/utils/paths.js +13 -15
  130. package/lib/utils/promote-dev-docker-image.js +97 -18
  131. package/lib/utils/role-assistant-paths.js +76 -14
  132. package/lib/utils/transient-developer-id.js +28 -0
  133. package/lib/utils/upload-sync-options.js +7 -4
  134. package/lib/validation/reserved-system-key-prefixes.js +56 -0
  135. package/lib/validation/validate.js +54 -19
  136. package/package.json +1 -1
  137. package/scripts/pnpm/af.mjs +24 -9
  138. package/scripts/pnpm/help.mjs +1 -1
  139. package/templates/applications/builder-api/application.yaml +3 -3
  140. package/templates/applications/dataplane/application.yaml +5 -5
  141. package/templates/applications/dataplane/env.template +8 -7
  142. package/templates/applications/dataplane/rbac.yaml +10 -10
  143. package/templates/applications/keycloak/application.yaml +2 -2
  144. package/templates/applications/keycloak/env.template +5 -2
  145. package/templates/applications/miso-controller/application.yaml +25 -11
  146. package/templates/applications/miso-controller/env.template +24 -5
  147. package/templates/channels/README.md +11 -1
  148. package/templates/channels/brand/color-192.png +0 -0
  149. package/templates/channels/brand/dark/128.png +0 -0
  150. package/templates/channels/brand/dark/192.png +0 -0
  151. package/templates/channels/brand/dark/256.png +0 -0
  152. package/templates/channels/brand/dark/32.png +0 -0
  153. package/templates/channels/brand/dark/512.png +0 -0
  154. package/templates/channels/brand/dark/64.png +0 -0
  155. package/templates/channels/brand/light/128.png +0 -0
  156. package/templates/channels/brand/light/192.png +0 -0
  157. package/templates/channels/brand/light/256.png +0 -0
  158. package/templates/channels/brand/light/32.png +0 -0
  159. package/templates/channels/brand/light/512.png +0 -0
  160. package/templates/channels/brand/light/64.png +0 -0
  161. package/templates/channels/brand/mono-white/128.png +0 -0
  162. package/templates/channels/brand/mono-white/192.png +0 -0
  163. package/templates/channels/brand/mono-white/256.png +0 -0
  164. package/templates/channels/brand/mono-white/32.png +0 -0
  165. package/templates/channels/brand/mono-white/512.png +0 -0
  166. package/templates/channels/brand/mono-white/64.png +0 -0
  167. package/templates/channels/brand/outline-32.png +0 -0
  168. package/templates/channels/chatgpt/install.template.md.hbs +76 -36
  169. package/templates/channels/chatgpt/integration/README.md +4 -4
  170. package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
  171. package/templates/channels/chatgpt/manifest.template.json.hbs +3 -3
  172. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
  173. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
  174. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
  175. package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
  176. package/templates/channels/microsoft-copilot/integration/README.md +2 -3
  177. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
  178. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
  179. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
  180. package/templates/channels/slack/install.template.md.hbs +6 -1
  181. package/templates/channels/slack/integration/README.md +2 -4
  182. package/templates/channels/teams-bot/install.template.md.hbs +4 -0
  183. package/templates/channels/teams-bot/integration/README.md +3 -5
  184. package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
  185. package/templates/python/docker-compose.hbs +10 -0
  186. package/templates/typescript/docker-compose.hbs +10 -0
  187. /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
@@ -18,6 +18,7 @@ const {
18
18
  auditCredentialUpdated,
19
19
  auditFailed
20
20
  } = require('./channel-audit');
21
+ const { skipsChannelOauthCredential } = require('./microsoft-copilot-entra-sso');
21
22
 
22
23
  /** @type {Record<string, { method: string, displayName: string, credentialConfig: Object }>} */
23
24
  const CREDENTIAL_SPECS = {
@@ -171,6 +172,13 @@ async function pushIntegrationSecrets(ctx, channelTarget) {
171
172
  */
172
173
  async function ensureChannelCredential(params) {
173
174
  const { ctx, channelTarget, target, force = false } = params;
175
+ if (skipsChannelOauthCredential(channelTarget)) {
176
+ return {
177
+ skipped: true,
178
+ reason: 'entraSsoTrustedIssuer',
179
+ credentialKey: target.credentialKey
180
+ };
181
+ }
174
182
  const existing = await findCredentialByKey(ctx, target.credentialKey);
175
183
  if (existing) {
176
184
  auditCredentialUpdated(channelTarget, {
@@ -0,0 +1,330 @@
1
+ /**
2
+ * External AI Discovery mapping for channel packaging (plan 167.0).
3
+ *
4
+ * @fileoverview Pure discovery → package context; optional Dataplane fetch
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { ApiClient } = require('../api');
12
+ const { RUNTIME_ORCHESTRATION_SYSTEM_KEY } = require('./chatgpt-runtime-contract');
13
+ const { normalizeBaseUrl, joinUrl } = require('./channel-runtime-resolver');
14
+
15
+ const GATEWAY_TOOLS_CATALOG_MARKER = '/api/v1/mcp/servers/tools';
16
+
17
+ /**
18
+ * @param {string} pathOrUrl
19
+ * @returns {boolean}
20
+ */
21
+ function isGatewayToolsCatalogUrl(pathOrUrl) {
22
+ const value = String(pathOrUrl || '');
23
+ return value.includes(GATEWAY_TOOLS_CATALOG_MARKER);
24
+ }
25
+
26
+ /**
27
+ * Normalize discovery paths that include a public `/data` prefix.
28
+ * @param {string} dataplaneBaseUrl
29
+ * @param {string|null|undefined} pathOrUrl
30
+ * @returns {string|null}
31
+ */
32
+ function resolveDiscoveryUrl(dataplaneBaseUrl, pathOrUrl) {
33
+ if (!pathOrUrl || typeof pathOrUrl !== 'string') {
34
+ return null;
35
+ }
36
+ const trimmed = pathOrUrl.trim();
37
+ if (!trimmed) {
38
+ return null;
39
+ }
40
+ if (isGatewayToolsCatalogUrl(trimmed)) {
41
+ throw new Error(
42
+ 'Gateway tools catalog must not be used as MCP server URL for channel packages'
43
+ );
44
+ }
45
+ if (/^https?:\/\//i.test(trimmed)) {
46
+ return trimmed;
47
+ }
48
+ let relative = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
49
+ if (relative.startsWith('/data/api/')) {
50
+ relative = relative.slice('/data'.length);
51
+ }
52
+ return joinUrl(normalizeBaseUrl(dataplaneBaseUrl), relative);
53
+ }
54
+
55
+ /**
56
+ * @param {Object} discovery
57
+ * @param {string} key
58
+ * @returns {Object|null}
59
+ */
60
+ function findEntryPoint(discovery, key) {
61
+ const entries = Array.isArray(discovery?.entryPoints) ? discovery.entryPoints : [];
62
+ return entries.find((ep) => ep && ep.key === key) || null;
63
+ }
64
+
65
+ /**
66
+ * @param {Object} discovery
67
+ * @returns {string[]}
68
+ */
69
+ function extractGovernedToolNames(discovery) {
70
+ const fromRuntime = discovery?.runtimeDiscovery?.governedToolNames;
71
+ if (Array.isArray(fromRuntime) && fromRuntime.length > 0) {
72
+ return fromRuntime.map((name) => String(name)).filter(Boolean);
73
+ }
74
+ const tools = Array.isArray(discovery?.tools) ? discovery.tools : [];
75
+ return tools.map((tool) => (tool && tool.name ? String(tool.name) : '')).filter(Boolean);
76
+ }
77
+
78
+ /**
79
+ * @param {Object} discovery
80
+ * @param {Object|null} adapter
81
+ * @param {string} dataplaneBaseUrl
82
+ * @returns {{ mcpServerUrl: string|null, installReady: boolean, blocker: string|null }}
83
+ */
84
+ function resolveMcpConnection(discovery, adapter, dataplaneBaseUrl) {
85
+ const adapterUrl = adapter?.connection?.mcpServerUrl || null;
86
+ const remote = findEntryPoint(discovery, 'remoteMcp');
87
+ const fromEntry =
88
+ resolveDiscoveryUrl(dataplaneBaseUrl, remote?.absoluteUrl) ||
89
+ resolveDiscoveryUrl(dataplaneBaseUrl, remote?.path);
90
+ const mcpServerUrl = resolveDiscoveryUrl(dataplaneBaseUrl, adapterUrl) || fromEntry;
91
+ if (mcpServerUrl && isGatewayToolsCatalogUrl(mcpServerUrl)) {
92
+ throw new Error(
93
+ 'Gateway tools catalog must not be used as MCP server URL for channel packages'
94
+ );
95
+ }
96
+ const mcpReady = discovery?.publication?.mcpReady === true;
97
+ const connectable = adapter ? adapter.connectable === true : mcpReady;
98
+ const remoteAvailable = discovery?.transport?.remoteMcp?.status === 'available';
99
+ const installReady = Boolean(mcpReady && connectable && remoteAvailable && mcpServerUrl);
100
+ const blocker =
101
+ adapter?.blocker ||
102
+ (!installReady ? 'remoteMcpUnavailable' : null);
103
+ return { mcpServerUrl, installReady, blocker };
104
+ }
105
+
106
+ /**
107
+ * @param {Object} branding
108
+ * @param {Object} identity
109
+ * @param {Object|null} adapter
110
+ * @returns {string}
111
+ */
112
+ function resolvePackageDisplayName(branding, identity, adapter) {
113
+ if (typeof branding.displayName === 'string' && branding.displayName.trim()) {
114
+ return branding.displayName.trim();
115
+ }
116
+ return (
117
+ identity.displayName ||
118
+ adapter?.nameForHuman ||
119
+ adapter?.displayName ||
120
+ identity.systemKey ||
121
+ RUNTIME_ORCHESTRATION_SYSTEM_KEY
122
+ );
123
+ }
124
+
125
+ /**
126
+ * @param {Object} branding
127
+ * @param {Object} identity
128
+ * @param {Object|null} adapter
129
+ * @param {Object} discovery
130
+ * @returns {string}
131
+ */
132
+ function resolvePackageDescription(branding, identity, adapter, discovery) {
133
+ if (typeof branding.description === 'string' && branding.description.trim()) {
134
+ return branding.description.trim();
135
+ }
136
+ return (
137
+ identity.description ||
138
+ adapter?.descriptionForHuman ||
139
+ adapter?.description ||
140
+ discovery.operatingModel?.summary ||
141
+ ''
142
+ );
143
+ }
144
+
145
+ /**
146
+ * @param {Object|null} auth
147
+ * @returns {Object|null}
148
+ */
149
+ function mapAdapterAuthentication(auth) {
150
+ if (!auth) return null;
151
+ return {
152
+ recommendedCapability: auth.recommendedCapability || null,
153
+ issuer: auth.issuer || null,
154
+ openidConfigurationUrl: auth.openidConfigurationUrl || null,
155
+ authorizationServerMetadataUrl: auth.authorizationServerMetadataUrl || null,
156
+ protectedResourceMetadataUrl: auth.protectedResourceMetadataUrl || null,
157
+ resource: auth.resource || auth.apiResource || null,
158
+ audience: auth.audience !== undefined && auth.audience !== null ? auth.audience : null,
159
+ scopes: Array.isArray(auth.scopes)
160
+ ? [...auth.scopes]
161
+ : Array.isArray(auth.allowedScopes)
162
+ ? [...auth.allowedScopes]
163
+ : [],
164
+ operatorNote: auth.operatorNote || null,
165
+ pkceRequired: auth.pkceRequired === true,
166
+ clientRegistrationModes: Array.isArray(auth.clientRegistrationModes)
167
+ ? [...auth.clientRegistrationModes]
168
+ : []
169
+ };
170
+ }
171
+
172
+ /**
173
+ * @param {Object} branding
174
+ * @returns {boolean}
175
+ */
176
+ function isBrandingApplied(branding) {
177
+ return Boolean(
178
+ (branding.displayName && String(branding.displayName).trim()) ||
179
+ (branding.description && String(branding.description).trim())
180
+ );
181
+ }
182
+
183
+ /**
184
+ * Map discovery (+ optional adapter) into packaging fields.
185
+ *
186
+ * @param {Object} params
187
+ * @param {Object} params.discovery
188
+ * @param {Object} [params.adapter]
189
+ * @param {string} params.dataplaneBaseUrl
190
+ * @param {Object} [params.branding]
191
+ * @returns {Object}
192
+ */
193
+ function mapDiscoveryToPackageContext(params) {
194
+ const discovery = params.discovery;
195
+ if (!discovery || typeof discovery !== 'object') {
196
+ throw new Error('discovery document is required');
197
+ }
198
+ const identity = discovery.identity || {};
199
+ const branding = params.branding && typeof params.branding === 'object' ? params.branding : {};
200
+ const adapter = params.adapter || null;
201
+ const connection = resolveMcpConnection(discovery, adapter, params.dataplaneBaseUrl);
202
+ const authCaps = Array.isArray(discovery.authenticationCapabilities)
203
+ ? discovery.authenticationCapabilities.map((c) => c.capability || c).filter(Boolean)
204
+ : [];
205
+ return {
206
+ systemKey: identity.systemKey || RUNTIME_ORCHESTRATION_SYSTEM_KEY,
207
+ displayName: resolvePackageDisplayName(branding, identity, adapter),
208
+ description: resolvePackageDescription(branding, identity, adapter, discovery),
209
+ governedToolNames: extractGovernedToolNames(discovery),
210
+ mcpServerUrl: connection.mcpServerUrl,
211
+ installReady: connection.installReady,
212
+ blocker: connection.blocker,
213
+ authentication: mapAdapterAuthentication(adapter?.authentication || null),
214
+ authenticationCapabilities: authCaps,
215
+ publication: {
216
+ mcpReady: discovery.publication?.mcpReady === true,
217
+ environment: discovery.publication?.environment || null
218
+ },
219
+ brandingApplied: isBrandingApplied(branding)
220
+ };
221
+ }
222
+
223
+ /**
224
+ * @param {Object} response
225
+ * @returns {Object}
226
+ */
227
+ function unwrapApiData(response) {
228
+ if (response && typeof response === 'object' && response.data && typeof response.data === 'object') {
229
+ if (response.data.kind || response.data.identity || response.data.adapter) {
230
+ return response.data;
231
+ }
232
+ }
233
+ return response;
234
+ }
235
+
236
+ /**
237
+ * @param {string} dataplaneBaseUrl
238
+ * @param {Object} authConfig
239
+ * @param {string} [systemKey]
240
+ * @returns {Promise<Object>}
241
+ */
242
+ async function fetchExternalAiDiscovery(
243
+ dataplaneBaseUrl,
244
+ authConfig,
245
+ systemKey = RUNTIME_ORCHESTRATION_SYSTEM_KEY
246
+ ) {
247
+ const client = new ApiClient(normalizeBaseUrl(dataplaneBaseUrl), authConfig || {});
248
+ const response = await client.get(`/api/v1/external/systems/${systemKey}/discovery`);
249
+ const discovery = unwrapApiData(response);
250
+ if (!discovery || !discovery.kind) {
251
+ throw new Error(`Discovery document missing for systemKey=${systemKey}`);
252
+ }
253
+ return discovery;
254
+ }
255
+
256
+ /**
257
+ * @param {string} dataplaneBaseUrl
258
+ * @param {Object} authConfig
259
+ * @param {string} adapterKey microsoft-plugin | chatgpt-setup
260
+ * @param {string} [systemKey]
261
+ * @returns {Promise<Object>}
262
+ */
263
+ async function fetchExternalAiDiscoveryAdapter(
264
+ dataplaneBaseUrl,
265
+ authConfig,
266
+ adapterKey,
267
+ systemKey = RUNTIME_ORCHESTRATION_SYSTEM_KEY
268
+ ) {
269
+ const client = new ApiClient(normalizeBaseUrl(dataplaneBaseUrl), authConfig || {});
270
+ const response = await client.get(
271
+ `/api/v1/external/systems/${systemKey}/discovery/adapters/${adapterKey}`
272
+ );
273
+ return unwrapApiData(response);
274
+ }
275
+
276
+ /**
277
+ * @param {string} channelTarget
278
+ * @returns {string|null}
279
+ */
280
+ function adapterKeyForChannel(channelTarget) {
281
+ if (channelTarget === 'microsoft-copilot') return 'microsoft-plugin';
282
+ if (channelTarget === 'chatgpt') return 'chatgpt-setup';
283
+ return null;
284
+ }
285
+
286
+ /**
287
+ * Fetch discovery (+ adapter when known) and map package context.
288
+ *
289
+ * @param {Object} params
290
+ * @returns {Promise<Object>}
291
+ */
292
+ async function loadChannelPackageContext(params) {
293
+ const systemKey = params.runtimeSystemKey || RUNTIME_ORCHESTRATION_SYSTEM_KEY;
294
+ const discovery = await fetchExternalAiDiscovery(
295
+ params.dataplaneBaseUrl,
296
+ params.authConfig,
297
+ systemKey
298
+ );
299
+ const adapterKey = adapterKeyForChannel(params.channelTarget);
300
+ let adapter = null;
301
+ if (adapterKey) {
302
+ try {
303
+ adapter = await fetchExternalAiDiscoveryAdapter(
304
+ params.dataplaneBaseUrl,
305
+ params.authConfig,
306
+ adapterKey,
307
+ systemKey
308
+ );
309
+ } catch {
310
+ adapter = null;
311
+ }
312
+ }
313
+ return mapDiscoveryToPackageContext({
314
+ discovery,
315
+ adapter,
316
+ dataplaneBaseUrl: params.dataplaneBaseUrl,
317
+ branding: params.branding
318
+ });
319
+ }
320
+
321
+ module.exports = {
322
+ GATEWAY_TOOLS_CATALOG_MARKER,
323
+ isGatewayToolsCatalogUrl,
324
+ resolveDiscoveryUrl,
325
+ mapDiscoveryToPackageContext,
326
+ fetchExternalAiDiscovery,
327
+ fetchExternalAiDiscoveryAdapter,
328
+ adapterKeyForChannel,
329
+ loadChannelPackageContext
330
+ };
@@ -72,7 +72,7 @@ async function fetchCommunicationSystem(ctx) {
72
72
  if (!system || typeof system !== 'object') {
73
73
  throw new Error(
74
74
  `Communication external system "${ctx.channelTarget}" was not found. ` +
75
- `Run: aifabrix deploy ${ctx.channelTarget}`
75
+ `Run: aifabrix channel add ${ctx.channelTarget} --name "…" --force`
76
76
  );
77
77
  }
78
78
  const kind = system.systemKind || system.systemKindKey;
@@ -19,6 +19,12 @@ const {
19
19
  COPILOT_RUNTIME_TRANSPORT,
20
20
  COPILOT_RUNTIME_AUTH_MODE
21
21
  } = require('./chatgpt-runtime-contract');
22
+ const { applyMicrosoftCopilotDisplayLimits } = require('./microsoft-copilot-limits');
23
+ const {
24
+ resolveCopilotEntraScopes,
25
+ evaluateCopilotEntraSsoReadiness
26
+ } = require('./microsoft-copilot-entra-sso');
27
+ const { applyCopilotEntraSsoFormFields } = require('./microsoft-copilot-entra-sso-form');
22
28
 
23
29
  /** Default Slack slash prefix — matches dataplane `templates/slack/command_catalog.yaml`. */
24
30
  const DEFAULT_SLACK_COMMAND_PREFIX = 'af';
@@ -152,15 +158,62 @@ function buildDeterministicGuid(seed) {
152
158
  * @param {Object} resolved
153
159
  * @param {Object} params
154
160
  */
155
- function applyMicrosoftCopilotRuntimeValues(resolved, params) {
161
+ /**
162
+ * Apply discovery-driven package context (plan 167.0).
163
+ * @param {Object} resolved
164
+ * @param {Object} [packageContext]
165
+ */
166
+ function applyDiscoveryPackageContext(resolved, packageContext) {
167
+ if (!packageContext || typeof packageContext !== 'object') {
168
+ resolved.installReady = false;
169
+ resolved.discoveryBlocker = resolved.discoveryBlocker || 'discoveryUnavailable';
170
+ return;
171
+ }
172
+ if (Array.isArray(packageContext.governedToolNames) && packageContext.governedToolNames.length > 0) {
173
+ resolved.layerBToolNames = [...packageContext.governedToolNames];
174
+ }
175
+ if (packageContext.mcpServerUrl) {
176
+ resolved.mcpUrl = packageContext.mcpServerUrl;
177
+ }
178
+ if (packageContext.displayName && !resolved.brandingNameOverride) {
179
+ // Keep CLI --name as appName; discovery fills defaults only when requested upstream.
180
+ }
181
+ if (packageContext.description && !resolved.appDescription) {
182
+ resolved.appDescription = packageContext.description;
183
+ }
184
+ resolved.installReady = packageContext.installReady === true;
185
+ resolved.discoveryBlocker = packageContext.blocker || null;
186
+ resolved.discoveryAuthentication = packageContext.authentication || null;
187
+ resolved.discoveryAuthenticationCapabilities = packageContext.authenticationCapabilities || [];
188
+ }
189
+
190
+ /**
191
+ * @param {Object} resolved
192
+ * @returns {void}
193
+ */
194
+ function applyCopilotRuntimeDomain(resolved) {
195
+ resolved.runtimeDomain = '';
196
+ try {
197
+ resolved.runtimeDomain = new URL(resolved.mcpUrl).hostname;
198
+ } catch {
199
+ try {
200
+ resolved.runtimeDomain = new URL(resolved.dataplaneBaseUrl).hostname;
201
+ } catch {
202
+ resolved.runtimeDomain = '';
203
+ }
204
+ }
205
+ }
206
+
207
+ /**
208
+ * @param {Object} resolved
209
+ * @param {Object} params
210
+ * @returns {void}
211
+ */
212
+ function applyCopilotPackageIdentity(resolved, params) {
156
213
  resolved.runtimeSystemKey = COPILOT_RUNTIME_SYSTEM_KEY;
157
214
  resolved.runtimeTransport = COPILOT_RUNTIME_TRANSPORT;
158
215
  resolved.runtimeAuthMode = COPILOT_RUNTIME_AUTH_MODE;
159
216
  resolved.layerBToolNames = Array.isArray(params.layerBToolNames) ? [...params.layerBToolNames] : [];
160
- resolved.scopes = Array.isArray(params.scopes)
161
- ? [...params.scopes]
162
- : ['openid', 'profile', 'offline_access'];
163
- resolved.scopesJson = JSON.stringify(resolved.scopes);
164
217
  resolved.packageVersion = typeof params.packageVersion === 'string' ? params.packageVersion : '1.0.0';
165
218
  resolved.packageAppId = buildDeterministicGuid(
166
219
  `${resolved.channelTarget}:${resolved.baseName}:${resolved.environment}:app`
@@ -168,16 +221,45 @@ function applyMicrosoftCopilotRuntimeValues(resolved, params) {
168
221
  resolved.pluginId = buildDeterministicGuid(
169
222
  `${resolved.channelTarget}:${resolved.baseName}:${resolved.environment}:plugin`
170
223
  );
171
- resolved.runtimeDomain = '';
172
- try {
173
- resolved.runtimeDomain = new URL(resolved.mcpUrl).hostname;
174
- } catch {
175
- try {
176
- resolved.runtimeDomain = new URL(resolved.dataplaneBaseUrl).hostname;
177
- } catch {
178
- resolved.runtimeDomain = '';
179
- }
224
+ const referenceId =
225
+ typeof params.oauthPluginVaultReferenceId === 'string' &&
226
+ params.oauthPluginVaultReferenceId.trim()
227
+ ? params.oauthPluginVaultReferenceId.trim()
228
+ : '';
229
+ resolved.oauthPluginVaultReferenceId = referenceId || null;
230
+ }
231
+
232
+ /**
233
+ * @param {Object} resolved
234
+ * @param {Object} params
235
+ * @returns {void}
236
+ */
237
+ function applyMicrosoftCopilotRuntimeValues(resolved, params) {
238
+ applyCopilotPackageIdentity(resolved, params);
239
+ applyDiscoveryPackageContext(resolved, params.packageContext);
240
+ resolved.scopes = resolveCopilotEntraScopes({
241
+ explicitScopes: params.scopes,
242
+ packageContext: params.packageContext,
243
+ entraProvider: params.entraProvider
244
+ });
245
+ resolved.scopesJson = JSON.stringify(resolved.scopes);
246
+ const readiness = evaluateCopilotEntraSsoReadiness({
247
+ packageContext: params.packageContext,
248
+ oauthPluginVaultReferenceId: resolved.oauthPluginVaultReferenceId,
249
+ scopes: resolved.scopes,
250
+ entraProvider: params.entraProvider || null
251
+ });
252
+ resolved.installReady = readiness.installReady;
253
+ resolved.discoveryBlocker = readiness.blocker;
254
+ resolved.discoveryAuthentication = readiness.packageAuthentication;
255
+ if (params.entraProvider?.issuer) {
256
+ resolved.entraIssuer = params.entraProvider.issuer;
180
257
  }
258
+ if (params.iconColorPath) resolved.iconColorPath = params.iconColorPath;
259
+ if (params.iconOutlinePath) resolved.iconOutlinePath = params.iconOutlinePath;
260
+ applyCopilotRuntimeDomain(resolved);
261
+ applyCopilotEntraSsoFormFields(resolved, params);
262
+ applyMicrosoftCopilotDisplayLimits(resolved);
181
263
  }
182
264
 
183
265
  /**
@@ -254,6 +336,7 @@ function resolveChannelRuntimeValues(params) {
254
336
 
255
337
  if (params.channelTarget === 'slack') {
256
338
  applySlackRuntimeValues(resolved, params);
339
+ applyDiscoveryPackageContext(resolved, params.packageContext);
257
340
  }
258
341
 
259
342
  if (params.channelTarget === 'microsoft-copilot') {
@@ -262,6 +345,11 @@ function resolveChannelRuntimeValues(params) {
262
345
 
263
346
  if (params.channelTarget === 'chatgpt') {
264
347
  applyChatGptRuntimeValues(resolved, params);
348
+ applyDiscoveryPackageContext(resolved, params.packageContext);
349
+ }
350
+
351
+ if (params.channelTarget === 'teams-bot') {
352
+ applyDiscoveryPackageContext(resolved, params.packageContext);
265
353
  }
266
354
 
267
355
  return resolved;
@@ -277,6 +365,7 @@ module.exports = {
277
365
  applySlackRuntimeValues,
278
366
  applyMicrosoftCopilotRuntimeValues,
279
367
  applyChatGptRuntimeValues,
368
+ applyDiscoveryPackageContext,
280
369
  resolveSlackOAuthManifestFields,
281
370
  resolveChannelRuntimeValues,
282
371
  DEFAULT_SLACK_COMMAND_PREFIX
@@ -125,7 +125,13 @@ function displayChannelCoreFields(result) {
125
125
  logger.log(metadata(`Artifact: ${result.artifactPath}`));
126
126
  }
127
127
  if (result.installGuidePath) {
128
- logger.log(metadata(`Install guide: ${result.installGuidePath}`));
128
+ logger.log(metadata(`Install guide (README): ${result.installGuidePath}`));
129
+ }
130
+ if (result.zipPath) {
131
+ logger.log(metadata(`Package ZIP: ${result.zipPath}`));
132
+ }
133
+ if (result.installReady === false && result.blocker) {
134
+ logger.log(metadata(`Install ready: no (${result.blocker})`));
129
135
  }
130
136
  }
131
137
 
@@ -113,10 +113,27 @@ function getDefaultChannelDescription() {
113
113
  );
114
114
  }
115
115
 
116
+ /**
117
+ * Human-readable supported channel list for CLI help and error hints.
118
+ * @returns {string}
119
+ */
120
+ function formatSupportedChannelTargetsHelp() {
121
+ const stable = CHANNEL_TARGET_KEYS.filter((key) => !CHANNEL_TARGETS[key].experimental);
122
+ const experimental = CHANNEL_TARGET_KEYS.filter((key) => CHANNEL_TARGETS[key].experimental);
123
+ const lines = ['Supported channels:', ` ${stable.join(', ')}`];
124
+ if (experimental.length > 0) {
125
+ lines.push(
126
+ ` ${experimental.join(', ')} (experimental — scaffold with --allow-experimental)`
127
+ );
128
+ }
129
+ return lines.join('\n');
130
+ }
131
+
116
132
  module.exports = {
117
133
  CHANNEL_TARGETS,
118
134
  CHANNEL_TARGET_KEYS,
119
135
  isChannelTargetKey,
120
136
  getChannelTarget,
121
- getDefaultChannelDescription
137
+ getDefaultChannelDescription,
138
+ formatSupportedChannelTargetsHelp
122
139
  };
@@ -12,7 +12,7 @@ const RUNTIME_ORCHESTRATION_SYSTEM_KEY = 'runtime-orchestration';
12
12
 
13
13
  const CHATGPT_RUNTIME_SYSTEM_KEY = RUNTIME_ORCHESTRATION_SYSTEM_KEY;
14
14
  const CHATGPT_RUNTIME_TRANSPORT = 'streamable-http';
15
- const CHATGPT_RUNTIME_AUTH_MODE = 'workspace-managed-oauth-or-api-key';
15
+ const CHATGPT_RUNTIME_AUTH_MODE = 'oauth21-authorization-code-pkce-s256';
16
16
 
17
17
  const COPILOT_RUNTIME_SYSTEM_KEY = RUNTIME_ORCHESTRATION_SYSTEM_KEY;
18
18
  const COPILOT_RUNTIME_TRANSPORT = 'streamable-http';