@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
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Microsoft Copilot Entra SSO / trustedIssuer packaging helpers (plan 169.0).
3
+ *
4
+ * @fileoverview Readiness, scopes, and auth selection for microsoft-copilot
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const TRUSTED_ISSUER = 'trustedIssuer';
12
+ const CUSTOMER_OAUTH = 'customerOAuth';
13
+ const OPENID_ONLY = new Set(['openid', 'profile', 'offline_access']);
14
+
15
+ /**
16
+ * @param {*} value
17
+ * @returns {boolean}
18
+ */
19
+ function isPresent(value) {
20
+ return value !== undefined && value !== null && value !== '';
21
+ }
22
+
23
+ /**
24
+ * @param {string} channelTarget
25
+ * @returns {boolean}
26
+ */
27
+ function skipsChannelOauthCredential(channelTarget) {
28
+ return channelTarget === 'microsoft-copilot';
29
+ }
30
+
31
+ /**
32
+ * @param {unknown} scopes
33
+ * @returns {string[]}
34
+ */
35
+ function normalizeScopeList(scopes) {
36
+ if (!Array.isArray(scopes)) {
37
+ return [];
38
+ }
39
+ return scopes.map((s) => String(s).trim()).filter(Boolean);
40
+ }
41
+
42
+ /**
43
+ * True when scopes are empty or only OIDC login scopes (insufficient for Runtime MCP).
44
+ * @param {string[]} scopes
45
+ * @returns {boolean}
46
+ */
47
+ function isOpenIdOnlyOrEmptyScopes(scopes) {
48
+ const list = normalizeScopeList(scopes);
49
+ if (list.length === 0) {
50
+ return true;
51
+ }
52
+ return list.every((scope) => OPENID_ONLY.has(scope));
53
+ }
54
+
55
+ /**
56
+ * @param {Object|null|undefined} auth
57
+ * @returns {string|null}
58
+ */
59
+ function resolveRecommendedCapability(auth) {
60
+ const value = auth && typeof auth.recommendedCapability === 'string'
61
+ ? auth.recommendedCapability.trim()
62
+ : '';
63
+ return value || null;
64
+ }
65
+
66
+ /**
67
+ * Map adapter auth for Copilot packaging: keep trustedIssuer public fields;
68
+ * drop Keycloak/customerOAuth AS metadata from the package surface.
69
+ * @param {Object|null} auth
70
+ * @returns {Object|null}
71
+ */
72
+ function mapCopilotPackageAuthentication(auth) {
73
+ if (!auth || typeof auth !== 'object') {
74
+ return null;
75
+ }
76
+ const recommended = resolveRecommendedCapability(auth);
77
+ const base = {
78
+ recommendedCapability: recommended,
79
+ issuer: auth.issuer || null,
80
+ openidConfigurationUrl: auth.openidConfigurationUrl || null,
81
+ protectedResourceMetadataUrl: auth.protectedResourceMetadataUrl || null,
82
+ resource: auth.resource || auth.apiResource || null,
83
+ operatorNote: auth.operatorNote || null,
84
+ pkceRequired: auth.pkceRequired === true,
85
+ audience: isPresent(auth.audience) ? auth.audience : null,
86
+ scopes: normalizeScopeList(auth.scopes || auth.allowedScopes)
87
+ };
88
+ if (recommended === TRUSTED_ISSUER) {
89
+ return {
90
+ ...base,
91
+ authorizationServerMetadataUrl: null,
92
+ clientRegistrationModes: []
93
+ };
94
+ }
95
+ return {
96
+ ...base,
97
+ authorizationServerMetadataUrl: auth.authorizationServerMetadataUrl || null,
98
+ clientRegistrationModes: Array.isArray(auth.clientRegistrationModes)
99
+ ? [...auth.clientRegistrationModes]
100
+ : []
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Resolve Runtime MCP Entra scopes — never default to openid/profile/offline_access.
106
+ * @param {Object} params
107
+ * @param {string[]} [params.explicitScopes]
108
+ * @param {Object|null} [params.packageContext]
109
+ * @param {Object|null} [params.entraProvider]
110
+ * @returns {string[]}
111
+ */
112
+ function resolveCopilotEntraScopes(params) {
113
+ const explicit = normalizeScopeList(params.explicitScopes);
114
+ if (explicit.length > 0 && !isOpenIdOnlyOrEmptyScopes(explicit)) {
115
+ return explicit;
116
+ }
117
+ const fromProvider = normalizeScopeList(params.entraProvider?.allowedScopes);
118
+ if (fromProvider.length > 0 && !isOpenIdOnlyOrEmptyScopes(fromProvider)) {
119
+ return fromProvider;
120
+ }
121
+ const auth = params.packageContext?.authentication || null;
122
+ const fromAuth = normalizeScopeList(auth?.scopes || auth?.allowedScopes);
123
+ if (fromAuth.length > 0 && !isOpenIdOnlyOrEmptyScopes(fromAuth)) {
124
+ return fromAuth;
125
+ }
126
+ const resource = typeof auth?.resource === 'string' ? auth.resource.trim() : '';
127
+ if (resource.startsWith('api://')) {
128
+ return [resource];
129
+ }
130
+ return [];
131
+ }
132
+
133
+ /**
134
+ * @param {Object} params
135
+ * @returns {string|null}
136
+ */
137
+ function firstCopilotEntraBlocker(params) {
138
+ const {
139
+ discoveryReady,
140
+ discoveryBlocker,
141
+ recommended,
142
+ referenceId,
143
+ issuer,
144
+ audience,
145
+ scopes
146
+ } = params;
147
+ if (!discoveryReady) {
148
+ return discoveryBlocker || 'remoteMcpUnavailable';
149
+ }
150
+ if (recommended !== TRUSTED_ISSUER) {
151
+ return recommended === CUSTOMER_OAUTH
152
+ ? 'customerOAuthNotAllowed'
153
+ : 'trustedIssuerRequired';
154
+ }
155
+ if (!referenceId) {
156
+ return 'oauthPluginVaultReferenceRequired';
157
+ }
158
+ if (!issuer) {
159
+ return 'entraIssuerRequired';
160
+ }
161
+ if (!isPresent(audience)) {
162
+ return 'runtimeMcpAudienceRequired';
163
+ }
164
+ if (isOpenIdOnlyOrEmptyScopes(scopes)) {
165
+ return 'runtimeMcpScopeRequired';
166
+ }
167
+ return null;
168
+ }
169
+
170
+ /**
171
+ * @param {Object|null} auth
172
+ * @param {Object|null} entraProvider
173
+ * @returns {{ audience: * , issuer: string|null }}
174
+ */
175
+ function resolveAudienceAndIssuer(auth, entraProvider) {
176
+ const audience = isPresent(entraProvider?.audience)
177
+ ? entraProvider.audience
178
+ : isPresent(auth?.audience)
179
+ ? auth.audience
180
+ : null;
181
+ const issuer =
182
+ (entraProvider && entraProvider.issuer) || (auth && auth.issuer) || null;
183
+ return { audience, issuer };
184
+ }
185
+
186
+ /**
187
+ * @param {Object} params
188
+ * @returns {{ installReady: boolean, blocker: string|null, packageAuthentication: Object|null }}
189
+ */
190
+ function evaluateCopilotEntraSsoReadiness(params) {
191
+ const packageContext = params.packageContext || null;
192
+ const auth = mapCopilotPackageAuthentication(packageContext?.authentication || null);
193
+ const recommended = resolveRecommendedCapability(auth);
194
+ const referenceId =
195
+ typeof params.oauthPluginVaultReferenceId === 'string'
196
+ ? params.oauthPluginVaultReferenceId.trim()
197
+ : '';
198
+ const scopes = normalizeScopeList(params.scopes);
199
+ const { audience, issuer } = resolveAudienceAndIssuer(auth, params.entraProvider || null);
200
+
201
+ if (auth) {
202
+ auth.issuer = issuer;
203
+ auth.audience = audience;
204
+ auth.scopes = scopes;
205
+ }
206
+
207
+ const blocker = firstCopilotEntraBlocker({
208
+ discoveryReady: packageContext?.installReady === true,
209
+ discoveryBlocker: packageContext?.blocker || null,
210
+ recommended,
211
+ referenceId,
212
+ issuer,
213
+ audience,
214
+ scopes
215
+ });
216
+ return {
217
+ installReady: blocker === null,
218
+ blocker,
219
+ packageAuthentication: auth
220
+ };
221
+ }
222
+
223
+ const {
224
+ formatOauthPluginVaultReferenceRequiredError
225
+ } = require('./microsoft-copilot-entra-sso-form');
226
+
227
+ /**
228
+ * @param {Object} [options]
229
+ * @throws {Error}
230
+ */
231
+ function assertOauthPluginVaultReferenceId(options = {}) {
232
+ const ref = options.oauthPluginVaultReferenceId;
233
+ if (typeof ref === 'string' && ref.trim()) {
234
+ return;
235
+ }
236
+ throw new Error(formatOauthPluginVaultReferenceRequiredError(options.formContext || options));
237
+ }
238
+
239
+ /**
240
+ * @param {Object} [runtimeValues]
241
+ * @throws {Error}
242
+ */
243
+ function assertCopilotPackageOauthPluginVaultReference(runtimeValues = {}) {
244
+ const ref = runtimeValues.oauthPluginVaultReferenceId;
245
+ if (typeof ref === 'string' && ref.trim()) {
246
+ return;
247
+ }
248
+ throw new Error(formatOauthPluginVaultReferenceRequiredError(runtimeValues));
249
+ }
250
+
251
+ module.exports = {
252
+ TRUSTED_ISSUER,
253
+ CUSTOMER_OAUTH,
254
+ skipsChannelOauthCredential,
255
+ normalizeScopeList,
256
+ isOpenIdOnlyOrEmptyScopes,
257
+ resolveRecommendedCapability,
258
+ mapCopilotPackageAuthentication,
259
+ resolveCopilotEntraScopes,
260
+ evaluateCopilotEntraSsoReadiness,
261
+ formatOauthPluginVaultReferenceRequiredError,
262
+ assertOauthPluginVaultReferenceId,
263
+ assertCopilotPackageOauthPluginVaultReference
264
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Valid Microsoft Teams / Copilot package icons (192×192 color, 32×32 outline).
3
+ *
4
+ * Defaults from templates/channels/brand (plan 167.0):
5
+ * - color-192.png ← light/192 (color app icon)
6
+ * - outline-32.png ← mono-white/32 (Teams outline)
7
+ * Variant packs: light/, dark/, mono-white/ at 32–512.
8
+ *
9
+ * @fileoverview Loads AI Fabrix brand PNGs from templates/channels/brand
10
+ * @author AI Fabrix Team
11
+ * @version 2.1.0
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const path = require('path');
17
+ const fs = require('../internal/fs-real-sync');
18
+ const { BUILDER_ROOT } = require('./channel-integration-template');
19
+
20
+ const BRAND_DIR = path.join(BUILDER_ROOT, 'templates', 'channels', 'brand');
21
+ const DEFAULT_COLOR_PATH = path.join(BRAND_DIR, 'color-192.png');
22
+ const DEFAULT_OUTLINE_PATH = path.join(BRAND_DIR, 'outline-32.png');
23
+
24
+ /**
25
+ * @param {string} filePath
26
+ * @returns {Buffer}
27
+ */
28
+ function readPngFile(filePath) {
29
+ if (!fs.existsSync(filePath)) {
30
+ throw new Error(`Brand icon not found: ${filePath}`);
31
+ }
32
+ return fs.readFileSync(filePath);
33
+ }
34
+
35
+ /**
36
+ * @param {{ colorPath?: string, outlinePath?: string }} [options]
37
+ * @returns {{ color: Buffer, outline: Buffer }}
38
+ */
39
+ function getMicrosoftCopilotIcons(options = {}) {
40
+ const colorPath =
41
+ typeof options.colorPath === 'string' && options.colorPath.trim()
42
+ ? options.colorPath.trim()
43
+ : DEFAULT_COLOR_PATH;
44
+ const outlinePath =
45
+ typeof options.outlinePath === 'string' && options.outlinePath.trim()
46
+ ? options.outlinePath.trim()
47
+ : DEFAULT_OUTLINE_PATH;
48
+ return {
49
+ color: readPngFile(colorPath),
50
+ outline: readPngFile(outlinePath)
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Read PNG IHDR width/height without external deps.
56
+ * @param {Buffer} buf
57
+ * @returns {{ width: number, height: number } | null}
58
+ */
59
+ function readPngDimensions(buf) {
60
+ if (!Buffer.isBuffer(buf) || buf.length < 24) return null;
61
+ if (buf.toString('hex', 0, 8) !== '89504e470d0a1a0a') return null;
62
+ return {
63
+ width: buf.readUInt32BE(16),
64
+ height: buf.readUInt32BE(20)
65
+ };
66
+ }
67
+
68
+ module.exports = {
69
+ getMicrosoftCopilotIcons,
70
+ readPngDimensions,
71
+ BRAND_DIR,
72
+ DEFAULT_COLOR_PATH,
73
+ DEFAULT_OUTLINE_PATH
74
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Microsoft Copilot / Teams package string limits.
3
+ *
4
+ * @fileoverview Truncate and normalize Copilot package display fields to schema limits
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const TEAMS_NAME_SHORT_MAX = 30;
12
+ const TEAMS_NAME_FULL_MAX = 100;
13
+ const TEAMS_DESCRIPTION_SHORT_MAX = 80;
14
+ const TEAMS_DESCRIPTION_FULL_MAX = 4000;
15
+ const PLUGIN_NAME_FOR_HUMAN_MAX = 20;
16
+ const PLUGIN_DESCRIPTION_FOR_HUMAN_MAX = 100;
17
+
18
+ /**
19
+ * @param {string} text
20
+ * @param {number} max
21
+ * @returns {string}
22
+ */
23
+ function truncateForSchema(text, max) {
24
+ const value = String(text ?? '').trim();
25
+ if (!value) return '';
26
+ if (value.length <= max) return value;
27
+ if (max <= 1) return value.slice(0, max);
28
+ return `${value.slice(0, max - 1)}…`;
29
+ }
30
+
31
+ /**
32
+ * Plugin namespace: lowercase alphanumeric/hyphen only (plugin schema ^[A-Za-z0-9-]+$).
33
+ * @param {string} baseName
34
+ * @returns {string}
35
+ */
36
+ function buildPluginNamespace(baseName) {
37
+ const raw = String(baseName ?? 'aifabrix')
38
+ .trim()
39
+ .toLowerCase()
40
+ .replace(/[^a-z0-9-]+/g, '-')
41
+ .replace(/-+/g, '-')
42
+ .replace(/^-|-$/g, '');
43
+ const ns = raw || 'aifabrix';
44
+ return truncateForSchema(ns, 32).replace(/[^a-z0-9-]/g, '') || 'aifabrix';
45
+ }
46
+
47
+ /**
48
+ * @param {Object} runtimeValues
49
+ * @returns {Object}
50
+ */
51
+ function applyMicrosoftCopilotDisplayLimits(runtimeValues) {
52
+ const appDescription = String(runtimeValues.appDescription || '').trim();
53
+ const baseName = String(runtimeValues.baseName || runtimeValues.appName || 'Assistant').trim();
54
+ const appName = String(runtimeValues.appName || baseName).trim();
55
+ runtimeValues.shortName = truncateForSchema(baseName, TEAMS_NAME_SHORT_MAX);
56
+ runtimeValues.fullName = truncateForSchema(appName, TEAMS_NAME_FULL_MAX);
57
+ runtimeValues.shortDescription = truncateForSchema(
58
+ appDescription || 'Enterprise AI assistant via AI Fabrix Runtime.',
59
+ TEAMS_DESCRIPTION_SHORT_MAX
60
+ );
61
+ runtimeValues.fullDescription = truncateForSchema(
62
+ appDescription || runtimeValues.shortDescription,
63
+ TEAMS_DESCRIPTION_FULL_MAX
64
+ );
65
+ runtimeValues.pluginNameForHuman = truncateForSchema(baseName, PLUGIN_NAME_FOR_HUMAN_MAX);
66
+ runtimeValues.pluginDescriptionForHuman = truncateForSchema(
67
+ appDescription || runtimeValues.shortDescription,
68
+ PLUGIN_DESCRIPTION_FOR_HUMAN_MAX
69
+ );
70
+ runtimeValues.pluginNamespace = buildPluginNamespace(baseName);
71
+ return runtimeValues;
72
+ }
73
+
74
+ module.exports = {
75
+ TEAMS_NAME_SHORT_MAX,
76
+ TEAMS_NAME_FULL_MAX,
77
+ TEAMS_DESCRIPTION_SHORT_MAX,
78
+ TEAMS_DESCRIPTION_FULL_MAX,
79
+ PLUGIN_NAME_FOR_HUMAN_MAX,
80
+ PLUGIN_DESCRIPTION_FOR_HUMAN_MAX,
81
+ truncateForSchema,
82
+ buildPluginNamespace,
83
+ applyMicrosoftCopilotDisplayLimits
84
+ };
@@ -9,12 +9,16 @@
9
9
  'use strict';
10
10
 
11
11
  const crypto = require('crypto');
12
- const fs = require('fs');
12
+ const fs = require('../internal/fs-real-sync');
13
13
  const path = require('path');
14
14
  const AdmZip = require('adm-zip');
15
-
16
- const PLACEHOLDER_ICON_BASE64 =
17
- 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO2s9WQAAAAASUVORK5CYII=';
15
+ const { getMicrosoftCopilotIcons } = require('./microsoft-copilot-icons');
16
+ const { assertValidMicrosoftCopilotPackage } = require('./microsoft-copilot-validate');
17
+ const { applyMicrosoftCopilotDisplayLimits } = require('./microsoft-copilot-limits');
18
+ const {
19
+ assertCopilotPackageOauthPluginVaultReference
20
+ } = require('./microsoft-copilot-entra-sso');
21
+ const { renderCopilotChannelTemplate } = require('./microsoft-copilot-entra-sso-form');
18
22
 
19
23
  /**
20
24
  * @param {string} outputPath
@@ -37,16 +41,20 @@ function buildCopilotOutputPaths(outputPath, runtimeValues) {
37
41
  zipFileName,
38
42
  zipPath: path.join(buildDir, zipFileName),
39
43
  validationPath: path.join(installDir, 'validation.json'),
40
- installPath: path.join(installDir, 'README.md')
44
+ installPath: path.join(rootDir, 'INSTALL.md'),
45
+ installReadmePath: path.join(installDir, 'README.md')
41
46
  };
42
47
  }
43
48
 
44
49
  /**
50
+ * @param {Object} [runtimeValues]
45
51
  * @returns {{ color: Buffer, outline: Buffer }}
46
52
  */
47
- function getPlaceholderIcons() {
48
- const icon = Buffer.from(PLACEHOLDER_ICON_BASE64, 'base64');
49
- return { color: icon, outline: icon };
53
+ function resolvePackageIcons(runtimeValues) {
54
+ return getMicrosoftCopilotIcons({
55
+ colorPath: runtimeValues?.iconColorPath,
56
+ outlinePath: runtimeValues?.iconOutlinePath
57
+ });
50
58
  }
51
59
 
52
60
  /**
@@ -67,7 +75,8 @@ function renderCopilotTemplates(runtimeValues, renderTemplateFile, templatesDir)
67
75
  runtimeValues
68
76
  )
69
77
  };
70
- const installText = renderTemplateFile(path.join(templatesDir, 'install.template.md.hbs'), runtimeValues);
78
+ // INSTALL prose + Entra SSO form table live in templates (partial entraSsoRegistration).
79
+ const installText = renderCopilotChannelTemplate('install.template.md.hbs', runtimeValues);
71
80
  return { sourceFiles, installText };
72
81
  }
73
82
 
@@ -112,6 +121,9 @@ function buildCopilotValidation(runtimeValues, paths, sourceFileNames) {
112
121
  runtimeMcpUrl: runtimeValues.mcpUrl,
113
122
  runtimeTransport: runtimeValues.runtimeTransport,
114
123
  expectedLayerBToolNames: runtimeValues.layerBToolNames || [],
124
+ governedToolNames: runtimeValues.layerBToolNames || [],
125
+ installReady: runtimeValues.installReady === true,
126
+ blocker: runtimeValues.discoveryBlocker || null,
115
127
  boundaries: {
116
128
  onePackagePerEnvironment: true,
117
129
  topLevelVendorToolsExposed: false,
@@ -142,6 +154,65 @@ function computeCopilotArtifactHash(parts) {
142
154
  return hash.digest('hex');
143
155
  }
144
156
 
157
+ /**
158
+ * @param {Object} runtimeValues
159
+ * @returns {Object}
160
+ */
161
+ function buildCopilotSummaryRuntime(runtimeValues) {
162
+ return {
163
+ runtimeSystemKey: runtimeValues.runtimeSystemKey,
164
+ remoteMcpUrl: runtimeValues.mcpUrl,
165
+ transport: runtimeValues.runtimeTransport,
166
+ assistantCatalogUrl: runtimeValues.assistantCatalogUrl,
167
+ roleAssistantResolution: 'dynamic-runtime',
168
+ layerBToolNames: runtimeValues.layerBToolNames || [],
169
+ governedToolNames: runtimeValues.layerBToolNames || []
170
+ };
171
+ }
172
+
173
+ /**
174
+ * @param {Object} runtimeValues
175
+ * @returns {Object}
176
+ */
177
+ function buildCopilotSummaryAuthentication(runtimeValues) {
178
+ const discovery = runtimeValues.discoveryAuthentication || null;
179
+ const serialized = discovery ? JSON.stringify(discovery) : '';
180
+ if (/authorizationServerMetadataUrl":"[^"]+"/i.test(serialized)) {
181
+ throw new Error('Keycloak authorizationServerMetadata must not appear in Copilot package summary');
182
+ }
183
+ return {
184
+ mode: runtimeValues.runtimeAuthMode,
185
+ pluginAuthType: 'OAuthPluginVault',
186
+ oauthPluginVaultReferenceId: runtimeValues.oauthPluginVaultReferenceId || null,
187
+ recommendedCapability: discovery?.recommendedCapability || null,
188
+ entraIssuer: runtimeValues.entraIssuer || discovery?.issuer || null,
189
+ entraEnterpriseAppId: runtimeValues.entraEnterpriseAppId || null,
190
+ entraTenantId: runtimeValues.entraTenantId || null,
191
+ entraSsoClientId: runtimeValues.entraSsoClientId || null,
192
+ scopes: runtimeValues.scopes || [],
193
+ discovery,
194
+ note:
195
+ 'Microsoft Entra SSO via OAuthPluginVault (Teams auth-config reference_id); ' +
196
+ 'miso-controller performs exchange — package carries public specs only; no Fabrix OAuth secrets'
197
+ };
198
+ }
199
+
200
+ /**
201
+ * @param {Object} runtimeValues
202
+ * @param {Object} paths
203
+ * @returns {Object}
204
+ */
205
+ function buildCopilotSummaryPackage(runtimeValues, paths) {
206
+ return {
207
+ appId: runtimeValues.packageAppId,
208
+ version: runtimeValues.packageVersion,
209
+ sourceFiles: ['manifest.json', 'declarativeAgent.json', 'ai-plugin.json', 'color.png', 'outline.png'],
210
+ zipFileName: paths.zipFileName,
211
+ installGuide: 'INSTALL.md',
212
+ validationFile: path.relative(paths.rootDir, paths.validationPath)
213
+ };
214
+ }
215
+
145
216
  /**
146
217
  * @param {Object} runtimeValues
147
218
  * @param {Object} paths
@@ -156,27 +227,16 @@ function buildCopilotSummary(runtimeValues, paths, validation, artifactHash) {
156
227
  baseName: runtimeValues.baseName,
157
228
  description: runtimeValues.appDescription,
158
229
  environment: runtimeValues.environment,
159
- runtime: {
160
- runtimeSystemKey: runtimeValues.runtimeSystemKey,
161
- remoteMcpUrl: runtimeValues.mcpUrl,
162
- transport: runtimeValues.runtimeTransport,
163
- assistantCatalogUrl: runtimeValues.assistantCatalogUrl,
164
- roleAssistantResolution: 'dynamic-runtime',
165
- layerBToolNames: runtimeValues.layerBToolNames || []
166
- },
167
- authentication: {
168
- mode: runtimeValues.runtimeAuthMode,
169
- credentialKey: runtimeValues.credentialKey,
170
- credentialPortalUrl: runtimeValues.credentialPortalUrl,
171
- scopes: runtimeValues.scopes || []
172
- },
173
- package: {
174
- appId: runtimeValues.packageAppId,
175
- version: runtimeValues.packageVersion,
176
- sourceFiles: ['manifest.json', 'declarativeAgent.json', 'ai-plugin.json', 'color.png', 'outline.png'],
177
- zipFileName: paths.zipFileName,
178
- installGuide: path.relative(paths.rootDir, paths.installPath),
179
- validationFile: path.relative(paths.rootDir, paths.validationPath)
230
+ runtime: buildCopilotSummaryRuntime(runtimeValues),
231
+ authentication: buildCopilotSummaryAuthentication(runtimeValues),
232
+ package: buildCopilotSummaryPackage(runtimeValues, paths),
233
+ installReady: runtimeValues.installReady === true,
234
+ blocker: runtimeValues.discoveryBlocker || null,
235
+ links: {
236
+ readme: paths.installPath,
237
+ zip: paths.zipPath,
238
+ filesRoot: paths.sourceDir,
239
+ validation: paths.validationPath
180
240
  },
181
241
  probeTiers: {
182
242
  builderValidated: true,
@@ -211,24 +271,21 @@ function writeCopilotArtifacts(params) {
211
271
  fs.writeFileSync(path.join(paths.sourceDir, 'outline.png'), icons.outline);
212
272
  fs.writeFileSync(paths.zipPath, zipBuffer);
213
273
  fs.writeFileSync(paths.installPath, installText, 'utf8');
274
+ if (paths.installReadmePath) {
275
+ fs.writeFileSync(paths.installReadmePath, installText, 'utf8');
276
+ }
214
277
  fs.writeFileSync(paths.validationPath, validationText, 'utf8');
215
278
  fs.writeFileSync(paths.summaryPath, summaryText, 'utf8');
216
279
  }
217
280
 
218
281
  /**
219
282
  * @param {Object} params
220
- * @param {Object} params.runtimeValues
221
- * @param {string} params.templatesDir
222
- * @param {string} params.outputPath
223
- * @param {boolean} [params.dryRun]
224
- * @param {(content: string) => void} params.assertNoSecretsInArtifact
225
- * @param {(templatePath: string, context: Object) => string} params.renderTemplateFile
226
- * @returns {{ outputPath: string, artifactHash: string, content: string, installPath: string }}
283
+ * @param {Object} runtimeValues
284
+ * @param {Object} paths
285
+ * @param {{ color: Buffer, outline: Buffer }} icons
286
+ * @returns {{ summary: Object, summaryText: string, zipBuffer: Buffer, sourceFiles: Object, installText: string, validationText: string }}
227
287
  */
228
- function generateMicrosoftCopilotPackage(params) {
229
- const runtimeValues = params.runtimeValues || {};
230
- const paths = buildCopilotOutputPaths(params.outputPath, runtimeValues);
231
- const icons = getPlaceholderIcons();
288
+ function buildCopilotPackagePayload(params, runtimeValues, paths, icons) {
232
289
  const { sourceFiles, installText } = renderCopilotTemplates(
233
290
  runtimeValues,
234
291
  params.renderTemplateFile,
@@ -236,6 +293,7 @@ function generateMicrosoftCopilotPackage(params) {
236
293
  );
237
294
  assertNoSecretsInSourceFiles(sourceFiles, params.assertNoSecretsInArtifact);
238
295
  params.assertNoSecretsInArtifact(installText);
296
+ assertValidMicrosoftCopilotPackage(sourceFiles, icons);
239
297
  const zipBuffer = buildCopilotZipBuffer(sourceFiles, icons);
240
298
  const validation = buildCopilotValidation(runtimeValues, paths, Object.keys(sourceFiles));
241
299
  const validationText = JSON.stringify(validation, null, 2);
@@ -250,22 +308,45 @@ function generateMicrosoftCopilotPackage(params) {
250
308
  const summary = buildCopilotSummary(runtimeValues, paths, validation, artifactHash);
251
309
  const summaryText = JSON.stringify(summary, null, 2);
252
310
  params.assertNoSecretsInArtifact(summaryText);
311
+ return { summary, summaryText, zipBuffer, sourceFiles, installText, validationText };
312
+ }
313
+
314
+ /**
315
+ * @param {Object} params
316
+ * @param {Object} params.runtimeValues
317
+ * @param {string} params.templatesDir
318
+ * @param {string} params.outputPath
319
+ * @param {boolean} [params.dryRun]
320
+ * @param {(content: string) => void} params.assertNoSecretsInArtifact
321
+ * @param {(templatePath: string, context: Object) => string} params.renderTemplateFile
322
+ * @returns {{ outputPath: string, artifactHash: string, content: string, installPath: string }}
323
+ */
324
+ function generateMicrosoftCopilotPackage(params) {
325
+ const runtimeValues = applyMicrosoftCopilotDisplayLimits({ ...(params.runtimeValues || {}) });
326
+ assertCopilotPackageOauthPluginVaultReference(runtimeValues);
327
+ const paths = buildCopilotOutputPaths(params.outputPath, runtimeValues);
328
+ const icons = resolvePackageIcons(runtimeValues);
329
+ const payload = buildCopilotPackagePayload(params, runtimeValues, paths, icons);
253
330
  if (!params.dryRun) {
254
331
  writeCopilotArtifacts({
255
332
  paths,
256
- sourceFiles,
333
+ sourceFiles: payload.sourceFiles,
257
334
  icons,
258
- zipBuffer,
259
- installText,
260
- validationText,
261
- summaryText
335
+ zipBuffer: payload.zipBuffer,
336
+ installText: payload.installText,
337
+ validationText: payload.validationText,
338
+ summaryText: payload.summaryText
262
339
  });
263
340
  }
264
341
  return {
265
342
  outputPath: paths.summaryPath,
266
- artifactHash,
267
- content: summaryText,
268
- installPath: paths.installPath
343
+ artifactHash: payload.summary.artifact.hash,
344
+ content: payload.summaryText,
345
+ installPath: paths.installPath,
346
+ zipPath: paths.zipPath,
347
+ links: payload.summary.links,
348
+ installReady: payload.summary.installReady === true,
349
+ blocker: payload.summary.blocker || null
269
350
  };
270
351
  }
271
352