@aifabrix/builder 2.52.1 → 2.53.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 (225) hide show
  1. package/lib/app/deploy-config.js +3 -2
  2. package/lib/channels/add-channel.js +11 -41
  3. package/lib/channels/approval-guides/index.js +3 -0
  4. package/lib/channels/approval-guides/microsoft-copilot.js +26 -17
  5. package/lib/channels/channel-add-existing.js +72 -0
  6. package/lib/channels/channel-runtime-resolver.js +5 -1
  7. package/lib/channels/channel-status-display.js +30 -3
  8. package/lib/channels/microsoft-copilot-entra-sso-form.js +73 -2
  9. package/lib/channels/microsoft-copilot-entra-sso.js +26 -5
  10. package/lib/channels/microsoft-copilot-limits.js +22 -3
  11. package/lib/channels/microsoft-copilot-package.js +59 -14
  12. package/lib/channels/microsoft-copilot-validate.js +40 -10
  13. package/lib/cli/attach-default-help-examples.js +102 -0
  14. package/lib/cli/index.js +2 -0
  15. package/lib/cli/setup-credential-deployment.js +7 -5
  16. package/lib/cli/setup-utility-validate.js +1 -0
  17. package/lib/commands/credential-push.js +37 -5
  18. package/lib/commands/package-score.js +72 -0
  19. package/lib/commands/role-assistant.js +46 -8
  20. package/lib/deployment/deployer-poll-run.js +2 -2
  21. package/lib/deployment/deployer.js +1 -1
  22. package/lib/deployment/environment.js +2 -2
  23. package/lib/external-system/deploy.js +11 -21
  24. package/lib/integration-definition/apply.js +12 -14
  25. package/lib/integration-definition/enrich-openapi-operations.js +9 -10
  26. package/lib/lab/package-parity-score.js +417 -0
  27. package/lib/programmatic/capability-registry.js +1 -0
  28. package/lib/programmatic/help-content/channel-onboarding.md +35 -0
  29. package/lib/programmatic/help-content/cip-overview.md +27 -0
  30. package/lib/programmatic/help-content/connected-system-ui.md +70 -0
  31. package/lib/programmatic/help-content/dimensions-guide.md +23 -0
  32. package/lib/programmatic/help-content/enterprise-sync-fabrix.md +123 -0
  33. package/lib/programmatic/help-content/overview.md +25 -0
  34. package/lib/programmatic/help-content/subscription-guide.md +27 -0
  35. package/lib/programmatic/help-content/workflow.md +50 -0
  36. package/lib/programmatic/openapi-component-schemas.js +1 -0
  37. package/lib/programmatic/openapi-descriptions.js +6 -0
  38. package/lib/programmatic/openapi-envelope-examples.js +11 -0
  39. package/lib/programmatic/openapi-operation-bindings.js +5 -0
  40. package/lib/programmatic/openapi-request-schemas.js +39 -13
  41. package/lib/programmatic/openapi-result-schemas.js +30 -0
  42. package/lib/programmatic/openapi-schema-descriptions-core.js +18 -0
  43. package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
  44. package/lib/programmatic/persist-supplied-openapi.js +180 -0
  45. package/lib/programmatic/route-handler-map.js +6 -0
  46. package/lib/programmatic/run-integration-definition.js +13 -5
  47. package/lib/role-assistant/evidence-sync.js +55 -7
  48. package/lib/role-assistant/test-runner-attachments.js +118 -0
  49. package/lib/role-assistant/test-runner-bid-apply.js +100 -21
  50. package/lib/role-assistant/test-runner-bid-document.js +1 -1
  51. package/lib/role-assistant/test-runner-start-payload.js +96 -0
  52. package/lib/role-assistant/test-runner-workhub-answers.js +11 -7
  53. package/lib/role-assistant/test-runner-workhub-helpers.js +100 -0
  54. package/lib/role-assistant/test-runner-workhub-soft.js +86 -0
  55. package/lib/role-assistant/test-runner-workhub.js +68 -105
  56. package/lib/role-assistant/test-runner.js +5 -63
  57. package/lib/role-assistant/test-suite.js +33 -40
  58. package/lib/schema/external-datasource.schema.json +13 -4
  59. package/lib/utils/compose-generator.js +10 -1
  60. package/lib/utils/compose-traefik-extra-routes.js +107 -0
  61. package/lib/utils/docker-build.js +2 -2
  62. package/lib/utils/help-builder.js +2 -1
  63. package/lib/utils/promote-dev-docker-image.js +15 -10
  64. package/package.json +14 -16
  65. package/templates/README.md +12 -1
  66. package/templates/applications/dataplane/application.yaml +8 -1
  67. package/templates/applications/dataplane/env.template +1 -1
  68. package/templates/applications/miso-controller/application.yaml +2 -1
  69. package/templates/applications/miso-controller/rbac.yaml +1 -0
  70. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +6 -2
  71. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +8 -5
  72. package/templates/channels/microsoft-copilot/fi-fi.template.json.hbs +7 -0
  73. package/templates/channels/microsoft-copilot/install.template.md.hbs +74 -22
  74. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +29 -14
  75. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +6 -2
  76. package/templates/python/docker-compose.hbs +36 -0
  77. package/templates/typescript/docker-compose.hbs +36 -0
  78. package/.cursor/plans/Archive/157.1-security-quality-violations-by-type.csv +0 -374
  79. package/.cursor/process/README.md +0 -9
  80. package/.cursor/rules/anchor-docs.mdc +0 -15
  81. package/.cursor/rules/assistant-driven-integration-build.mdc +0 -40
  82. package/.cursor/rules/cli-layout.mdc +0 -81
  83. package/.cursor/rules/cli-product-neutral.mdc +0 -43
  84. package/.cursor/rules/deploy-rules/docker-image-ip-policy.mdc +0 -49
  85. package/.cursor/rules/docs-rules.mdc +0 -32
  86. package/.cursor/rules/plan-rules/application-lifecycle.mdc +0 -30
  87. package/.cursor/rules/plan-rules/plan-frontmatter.mdc +0 -35
  88. package/.cursor/rules/plan-rules/plan-lifecycle.mdc +0 -31
  89. package/.cursor/rules/plan-rules/plan-validation-gate.mdc +0 -34
  90. package/.cursor/rules/project-rules.mdc +0 -913
  91. package/.cursor/skills/README.md +0 -15
  92. package/.dockerignore +0 -52
  93. package/.eslintignore +0 -22
  94. package/.markdownlint.json +0 -14
  95. package/anchor-docs/README.md +0 -10
  96. package/anchor-docs/_TEMPLATE +0 -24
  97. package/babel.config.js +0 -6
  98. package/bin/builder-api-cleanup.js +0 -26
  99. package/bin/builder-api-nest.js +0 -10
  100. package/bin/builder-api-spike.js +0 -19
  101. package/enterprise-knowledge/README.md +0 -50
  102. package/enterprise-knowledge/enterprise-integration-lifecycle/README.md +0 -22
  103. package/enterprise-knowledge/enterprise-integration-lifecycle/application.json +0 -15
  104. package/enterprise-knowledge/enterprise-integration-lifecycle/deploy.js +0 -69
  105. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-deploy.json +0 -52
  106. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-system.json +0 -29
  107. package/enterprise-knowledge/enterprise-integration-lifecycle/env.template +0 -2
  108. package/integration/README.md +0 -81
  109. package/integration/github/README.md +0 -53
  110. package/integration/github/application.json +0 -21
  111. package/integration/github/deploy.js +0 -72
  112. package/integration/github/env.template +0 -11
  113. package/integration/github/github-datasource-issue-comments.json +0 -239
  114. package/integration/github/github-datasource-issues.json +0 -399
  115. package/integration/github/github-datasource-repository.json +0 -354
  116. package/integration/github/github-deploy.json +0 -1202
  117. package/integration/github/github-system.json +0 -82
  118. package/integration/hubspot-test/README.md +0 -159
  119. package/integration/hubspot-test/application.json +0 -54
  120. package/integration/hubspot-test/companies.json +0 -2048
  121. package/integration/hubspot-test/create-hubspot.js +0 -523
  122. package/integration/hubspot-test/env.template +0 -4
  123. package/integration/hubspot-test/hubspot-test-datasource-company.json +0 -138
  124. package/integration/hubspot-test/hubspot-test-datasource-contact.json +0 -146
  125. package/integration/hubspot-test/hubspot-test-datasource-deal.json +0 -146
  126. package/integration/hubspot-test/hubspot-test-datasource-users.json +0 -76
  127. package/integration/hubspot-test/hubspot-test-deploy.json +0 -2160
  128. package/integration/hubspot-test/hubspot-test-system.json +0 -74
  129. package/integration/hubspot-test/rbac.json +0 -166
  130. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +0 -20
  131. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +0 -9
  132. package/integration/hubspot-test/test-artifacts/wizard-invalid-add-datasource.yaml +0 -5
  133. package/integration/hubspot-test/test-artifacts/wizard-invalid-app-name.yaml +0 -5
  134. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-create.yaml +0 -7
  135. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-select.yaml +0 -7
  136. package/integration/hubspot-test/test-artifacts/wizard-invalid-known-platform.yaml +0 -4
  137. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-app.yaml +0 -4
  138. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
  139. package/integration/hubspot-test/test-artifacts/wizard-invalid-mode.yaml +0 -5
  140. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-file.yaml +0 -5
  141. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-url.yaml +0 -4
  142. package/integration/hubspot-test/test-artifacts/wizard-invalid-source.yaml +0 -4
  143. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-array-test.yaml +0 -5
  144. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
  145. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
  146. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-test.yaml +0 -5
  147. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-test.yaml +0 -5
  148. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +0 -5
  149. package/integration/hubspot-test/test-dataplane-down-helpers.js +0 -249
  150. package/integration/hubspot-test/test-dataplane-down-tests.js +0 -386
  151. package/integration/hubspot-test/test-dataplane-down.js +0 -209
  152. package/integration/hubspot-test/test.js +0 -1588
  153. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +0 -16
  154. package/integration/hubspot-test/wizard-hubspot-platform.yaml +0 -8
  155. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +0 -23
  156. package/integration/roundtrip-test-local/README.md +0 -143
  157. package/integration/roundtrip-test-local/application.yaml +0 -13
  158. package/integration/roundtrip-test-local/env.template +0 -15
  159. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +0 -14
  160. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +0 -61
  161. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +0 -25
  162. package/integration/roundtrip-test-local2/README.md +0 -143
  163. package/integration/roundtrip-test-local2/application.yaml +0 -13
  164. package/integration/roundtrip-test-local2/env.template +0 -15
  165. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +0 -31
  166. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +0 -86
  167. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +0 -25
  168. package/integration/test/wizard.yaml +0 -8
  169. package/jest.config.coverage.js +0 -37
  170. package/jest.config.default.js +0 -11
  171. package/jest.config.integration.fixtures.js +0 -22
  172. package/jest.config.integration.js +0 -33
  173. package/jest.config.isolated.js +0 -11
  174. package/jest.config.manual.js +0 -30
  175. package/jest.global-live-fabrix-hooks.js +0 -36
  176. package/jest.isolated-projects.js +0 -283
  177. package/jest.projects.js +0 -79
  178. package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +0 -205
  179. package/packages/builder-api/README.md +0 -120
  180. package/packages/builder-api/package.json +0 -30
  181. package/packages/builder-api/src/app.module.ts +0 -7
  182. package/packages/builder-api/src/auth/auth.module.ts +0 -5
  183. package/packages/builder-api/src/external-system/external-system.module.ts +0 -5
  184. package/packages/builder-api/src/main.ts +0 -16
  185. package/packages/builder-api/tsconfig.json +0 -15
  186. package/scripts/check-builder-api-architecture-grep.js +0 -79
  187. package/scripts/check-datasource-test-run-schema-sync.js +0 -34
  188. package/scripts/ci-fix.sh +0 -19
  189. package/scripts/ci-simulate.sh +0 -19
  190. package/scripts/diagnose-cli.js +0 -150
  191. package/scripts/docker_image_content_gate.js +0 -47
  192. package/scripts/docker_image_content_gate.sh +0 -5
  193. package/scripts/export-builder-api-evidence.js +0 -262
  194. package/scripts/install-local.js +0 -490
  195. package/scripts/lib/builder-api-docker-staging-copy.js +0 -60
  196. package/scripts/lib/builder-api-docker-staging-core.js +0 -183
  197. package/scripts/lib/docker-image-content-gate-core.js +0 -166
  198. package/scripts/lib/plan-actor.js +0 -109
  199. package/scripts/lib/plan-config.js +0 -20
  200. package/scripts/lib/plan-gates.js +0 -154
  201. package/scripts/lib/plan-meta.js +0 -104
  202. package/scripts/lib/plan-parse.js +0 -162
  203. package/scripts/lib/plan-prevalidate-gates.js +0 -337
  204. package/scripts/lib/plan-run-core.js +0 -125
  205. package/scripts/lib/plan-run-report.js +0 -119
  206. package/scripts/plan-approve.js +0 -24
  207. package/scripts/plan-archive.js +0 -49
  208. package/scripts/plan-run.js +0 -13
  209. package/scripts/plan-set-actor.js +0 -21
  210. package/scripts/plan-validate.js +0 -80
  211. package/scripts/pnpm/af.mjs +0 -125
  212. package/scripts/pnpm/check-quiet.mjs +0 -32
  213. package/scripts/pnpm/dev-install.mjs +0 -29
  214. package/scripts/pnpm/dev-reload.mjs +0 -18
  215. package/scripts/pnpm/help.mjs +0 -55
  216. package/scripts/pnpm/lib/dev-reload-core.mjs +0 -189
  217. package/scripts/pnpm/lib/dev-reload-log-capture.mjs +0 -67
  218. package/scripts/pnpm/lib/dev-reload-log-utils.mjs +0 -32
  219. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +0 -124
  220. package/scripts/pnpm/lib/resolve-app-url.mjs +0 -183
  221. package/scripts/pnpm/lib/spinner.mjs +0 -42
  222. package/scripts/pnpm-global-remove.js +0 -48
  223. package/scripts/stage_builder_api_for_docker.js +0 -65
  224. package/scripts/sync-builder-api-template.js +0 -44
  225. package/scripts/test-dataplane-bootstrap.js +0 -331
@@ -78,9 +78,10 @@ async function extractDeploymentConfig(options, _variables) {
78
78
  // Used by guided installers which already show a top-level spinner.
79
79
  silentPoll: options.silentPoll === true,
80
80
  repositoryUrl: options.repositoryUrl,
81
- // Omit default here so deployer can resolve 2s vs 5s from controller deploymentType (GET /api/v1/health)
81
+ // Omit default here so deployer can resolve 1s vs 5s from controller deploymentType (GET /api/v1/health)
82
82
  pollInterval: Number.isFinite(pollIntervalExplicit) ? pollIntervalExplicit : undefined,
83
- pollMaxAttempts: options.pollMaxAttempts || 60,
83
+ // Default 300 attempts: ~5 min at 1s (local) / ~25 min at 5s (azure) — deploy may run tests/gates
84
+ pollMaxAttempts: options.pollMaxAttempts || 300,
84
85
  deploymentAuth: options.deploymentAuth
85
86
  };
86
87
  }
@@ -8,9 +8,9 @@
8
8
 
9
9
  'use strict';
10
10
 
11
+ const path = require('path');
11
12
  const { getChannelTarget, getDefaultChannelDescription } = require('./channel-targets');
12
13
  const { resolveAppDescription } = require('./channel-description');
13
- const path = require('path');
14
14
  const { resolveChannelRuntimeValues } = require('./channel-runtime-resolver');
15
15
  const { generateChannelArtifact } = require('./channel-artifact-generator');
16
16
  const { fetchRuntimeLayerBToolNames } = require('./runtime-tool-catalog');
@@ -43,11 +43,9 @@ const {
43
43
  normalizeBaseChannelName,
44
44
  resolveChannelDisplayName
45
45
  } = require('./channel-display-name');
46
- const {
47
- displayExistingChannelStatus,
48
- displayChannelInstallResult
49
- } = require('./channel-status-display');
46
+ const { displayChannelInstallResult } = require('./channel-status-display');
50
47
  const { ensureChannelReadyForAdd } = require('./channel-add-ensure');
48
+ const { showExistingChannelStatus } = require('./channel-add-existing');
51
49
  const {
52
50
  assertMicrosoftCopilotOauthReference,
53
51
  assertMicrosoftCopilotOauthReferenceBeforeForce,
@@ -84,41 +82,6 @@ function toValidationDisplay(validation) {
84
82
  };
85
83
  }
86
84
 
87
- /**
88
- * @param {Object} params
89
- * @returns {Object|null}
90
- */
91
- function showExistingChannelStatus(params) {
92
- const { existing, options, channelTarget, name, baseName, ctx } = params;
93
- if (!existing || options.force || options.dryRun) return null;
94
- const sameInstall =
95
- (existing.baseName && existing.baseName === baseName) ||
96
- (!existing.baseName && existing.name === name);
97
- if (!sameInstall) {
98
- throw new Error(
99
- `Channel already configured as "${existing.name}". ` +
100
- `Use --force to replace with "${name}".`
101
- );
102
- }
103
- const projectRoot = options.projectRoot || process.cwd();
104
- const approvalGuide =
105
- existing.status === 'approvalRequired'
106
- ? buildChannelApprovalGuide(channelTarget, {
107
- projectRoot,
108
- name: existing.name || name,
109
- baseName,
110
- dataplaneBaseUrl: ctx?.dataplaneUrl
111
- })
112
- : null;
113
- displayExistingChannelStatus({
114
- channelTarget,
115
- name: existing.name || name,
116
- channelExt: existing,
117
- approvalGuide
118
- });
119
- return { skipped: true, channelExt: existing };
120
- }
121
-
122
85
  /**
123
86
  * @param {string} channelTarget
124
87
  * @param {Object} artifactParams
@@ -245,7 +208,8 @@ async function completeLiveInstall(params) {
245
208
  name,
246
209
  baseName,
247
210
  artifactPath: artifact.outputPath,
248
- dataplaneBaseUrl: ctx.dataplaneUrl
211
+ dataplaneBaseUrl: ctx.dataplaneUrl,
212
+ environment: ctx.environment || options.environment || 'dev'
249
213
  })
250
214
  : null;
251
215
  if (approvalGuide?.portalUrl) {
@@ -266,6 +230,12 @@ async function completeLiveInstall(params) {
266
230
  zipPath: artifact.zipPath,
267
231
  installReady: artifact.installReady,
268
232
  blocker: artifact.blocker,
233
+ packageReady: artifact.packageReady,
234
+ packageBlocker: artifact.packageBlocker,
235
+ microsoftPublicationPending:
236
+ artifact.microsoftPublicationPending !== undefined
237
+ ? artifact.microsoftPublicationPending
238
+ : status === 'approvalRequired',
269
239
  validation: toValidationDisplay(validation),
270
240
  approvalGuide
271
241
  });
@@ -71,6 +71,9 @@ function buildChannelApprovalGuide(channelTarget, context = {}) {
71
71
  : null;
72
72
  const baseCtx = {
73
73
  channelTarget,
74
+ name,
75
+ baseName: context.baseName || name,
76
+ environment: context.environment || 'dev',
74
77
  artifactPath,
75
78
  resumeCommand,
76
79
  credentialKey: target.credentialKey,
@@ -1,37 +1,46 @@
1
1
  /**
2
2
  * Microsoft Copilot channel install / approval steps.
3
3
  *
4
- * @fileoverview Teams custom-app upload guide aligned with install templates
4
+ * @fileoverview Organisation publish via Developer Portal + Teams admin approval
5
5
  * @author AI Fabrix Team
6
- * @version 1.0.0
6
+ * @version 1.2.0
7
7
  */
8
8
 
9
9
  'use strict';
10
10
 
11
- const {
12
- ENTRA_SSO_REGISTRATION_URL
13
- } = require('../microsoft-copilot-entra-sso-form');
11
+ const DEVELOPER_PORTAL_URL = 'https://dev.teams.microsoft.com/';
12
+ const TEAMS_ADMIN_APPS_URL = 'https://admin.teams.microsoft.com/policies/manage-apps';
14
13
 
15
14
  /**
16
15
  * @param {Object} ctx
17
16
  * @returns {{ portalUrl: string, steps: string[] }}
18
17
  */
19
18
  function buildMicrosoftCopilotApprovalGuide(ctx) {
19
+ const env = typeof ctx.environment === 'string' && ctx.environment.trim()
20
+ ? ctx.environment.trim()
21
+ : 'dev';
22
+ const zipName = `appPackage.${env}.zip`;
23
+ const appName =
24
+ (typeof ctx.name === 'string' && ctx.name.trim()) ||
25
+ (typeof ctx.baseName === 'string' && ctx.baseName.trim()) ||
26
+ 'your app';
20
27
  return {
21
- portalUrl: ENTRA_SSO_REGISTRATION_URL,
28
+ portalUrl: DEVELOPER_PORTAL_URL,
22
29
  steps: [
23
- 'Open Microsoft Teams with your target Microsoft 365 account.',
24
- 'Go to Apps → Manage your apps → Upload an app → Upload a custom app.',
25
- 'Upload only appPackage.<env>.zip (do not upload individual manifest files or channel-microsoft-copilot.json).',
26
- 'Review permissions, select Add, and open the installed app. Users stay on the Microsoft 365 Entra session — no separate AI Fabrix sign-in.',
27
- 'If prompted, complete Entra admin consent for the Runtime MCP delegated scope.',
28
- 'If Upload a custom app is unavailable, ask a Teams admin to enable custom app uploads or deploy the package centrally.',
29
- `Register Entra SSO first at ${ENTRA_SSO_REGISTRATION_URL} using the Base URL / Client ID / Scope values printed by the CLI (or INSTALL.md). Pass the registration id as --oauth-plugin-vault-reference-id.`,
30
- 'Do not configure AI Fabrix OAuth client secrets under Connected Systems → Authentication for this Entra SSO flow.',
31
- 'Confirm Runtime MCP and governed tools using INSTALL.md, then re-run with --probe after Teams install.',
32
- `After the app is available in Teams, re-run: ${ctx.resumeCommand}`
30
+ `Open Teams Developer Portal (${DEVELOPER_PORTAL_URL}).`,
31
+ `Click Import an app → import ${zipName}. Preserve the generated stable manifest.json.id. Do not create an unrelated new Teams app.`,
32
+ 'Developer Portal → selected app → Publish to your org → Distribute / submit.',
33
+ `Teams admin approves in ${TEAMS_ADMIN_APPS_URL} (Manage apps → search → allow). Expect Published / Unblocked per organisation policy. Allow several minutes for propagation.`,
34
+ `Open Teams → Apps (Built for your org), click "${appName}", Add the application, then choose Open with Copilot.`,
35
+ 'In Copilot, click "Show my role assistants" and run the command.',
36
+ 'Confirm Runtime MCP and governed tools using INSTALL.md, then re-run with --probe after organisation publish.',
37
+ `After the app is available under Built for your org, re-run: ${ctx.resumeCommand}`
33
38
  ]
34
39
  };
35
40
  }
36
41
 
37
- module.exports = { buildMicrosoftCopilotApprovalGuide };
42
+ module.exports = {
43
+ buildMicrosoftCopilotApprovalGuide,
44
+ DEVELOPER_PORTAL_URL,
45
+ TEAMS_ADMIN_APPS_URL
46
+ };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Existing-channel skip path for aifabrix channel add.
3
+ *
4
+ * @fileoverview Keep add-channel.js under the 500-line limit
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { getArtifactOutputPath } = require('./channel-artifact-generator');
14
+ const { buildChannelApprovalGuide } = require('./channel-approval-guide');
15
+ const { displayExistingChannelStatus } = require('./channel-status-display');
16
+
17
+ /**
18
+ * True when local channel package files are present under dist/channels/<target>/.
19
+ * @param {string} projectRoot
20
+ * @param {string} channelTarget
21
+ * @returns {boolean}
22
+ */
23
+ function localChannelArtifactsExist(projectRoot, channelTarget) {
24
+ const summaryPath = getArtifactOutputPath(projectRoot, channelTarget);
25
+ const installPath = path.join(path.dirname(summaryPath), 'INSTALL.md');
26
+ return fs.existsSync(summaryPath) && fs.existsSync(installPath);
27
+ }
28
+
29
+ /**
30
+ * @param {Object} params
31
+ * @returns {Object|null}
32
+ */
33
+ function showExistingChannelStatus(params) {
34
+ const { existing, options, channelTarget, name, baseName, ctx } = params;
35
+ if (!existing || options.force || options.dryRun) return null;
36
+ const sameInstall =
37
+ (existing.baseName && existing.baseName === baseName) ||
38
+ (!existing.baseName && existing.name === name);
39
+ if (!sameInstall) {
40
+ throw new Error(
41
+ `Channel already configured as "${existing.name}". ` +
42
+ `Use --force to replace with "${name}".`
43
+ );
44
+ }
45
+ const projectRoot = options.projectRoot || process.cwd();
46
+ // Dataplane may still have the channel while local dist/ was deleted — regenerate.
47
+ if (!localChannelArtifactsExist(projectRoot, channelTarget)) {
48
+ return null;
49
+ }
50
+ const approvalGuide =
51
+ existing.status === 'approvalRequired'
52
+ ? buildChannelApprovalGuide(channelTarget, {
53
+ projectRoot,
54
+ name: existing.name || name,
55
+ baseName,
56
+ dataplaneBaseUrl: ctx?.dataplaneUrl,
57
+ environment: ctx?.environment || 'dev'
58
+ })
59
+ : null;
60
+ displayExistingChannelStatus({
61
+ channelTarget,
62
+ name: existing.name || name,
63
+ channelExt: existing,
64
+ approvalGuide
65
+ });
66
+ return { skipped: true, channelExt: existing };
67
+ }
68
+
69
+ module.exports = {
70
+ localChannelArtifactsExist,
71
+ showExistingChannelStatus
72
+ };
@@ -247,10 +247,14 @@ function applyMicrosoftCopilotRuntimeValues(resolved, params) {
247
247
  packageContext: params.packageContext,
248
248
  oauthPluginVaultReferenceId: resolved.oauthPluginVaultReferenceId,
249
249
  scopes: resolved.scopes,
250
- entraProvider: params.entraProvider || null
250
+ entraProvider: params.entraProvider || null,
251
+ mcpServerUrl: resolved.mcpUrl || null
251
252
  });
252
253
  resolved.installReady = readiness.installReady;
253
254
  resolved.discoveryBlocker = readiness.blocker;
255
+ resolved.packageReady = readiness.packageReady;
256
+ resolved.packageBlocker = readiness.packageBlocker;
257
+ resolved.microsoftPublicationPending = readiness.microsoftPublicationPending !== false;
254
258
  resolved.discoveryAuthentication = readiness.packageAuthentication;
255
259
  if (params.entraProvider?.issuer) {
256
260
  resolved.entraIssuer = params.entraProvider.issuer;
@@ -55,10 +55,10 @@ function displayApprovalGuideSection(approvalGuide) {
55
55
  const heading =
56
56
  approvalGuide.approvalRequired === false
57
57
  ? 'Go-live steps'
58
- : 'Platform admin approval required';
58
+ : 'Organisation publish / Teams admin approval required';
59
59
  logger.log(formatWarningLine(heading));
60
60
  if (approvalGuide.portalUrl) {
61
- logger.log(metadata('Admin console:'));
61
+ logger.log(metadata('Developer Portal:'));
62
62
  logger.log(chalk.cyan(approvalGuide.portalUrl));
63
63
  }
64
64
  logger.log('');
@@ -130,7 +130,34 @@ function displayChannelCoreFields(result) {
130
130
  if (result.zipPath) {
131
131
  logger.log(metadata(`Package ZIP: ${result.zipPath}`));
132
132
  }
133
- if (result.installReady === false && result.blocker) {
133
+ displayReadinessLines(result);
134
+ }
135
+
136
+ /**
137
+ * Split package/Runtime readiness from Microsoft organisation publication.
138
+ * @param {Object} result
139
+ */
140
+ function displayReadinessLines(result) {
141
+ const packageReady =
142
+ result.packageReady === true ||
143
+ (result.packageReady === undefined && result.installReady === true);
144
+ const packageBlocker = result.packageBlocker || result.blocker || null;
145
+ if (packageReady) {
146
+ logger.log(metadata('Package / Runtime ready: yes'));
147
+ } else if (packageBlocker) {
148
+ logger.log(metadata(`Package / Runtime ready: no (${packageBlocker})`));
149
+ } else if (result.installReady === false) {
150
+ logger.log(metadata('Package / Runtime ready: no'));
151
+ }
152
+ if (result.microsoftPublicationPending === true) {
153
+ logger.log(
154
+ metadata(
155
+ 'Microsoft publication: pending (Developer Portal import → Publish to your org → Teams admin approval)'
156
+ )
157
+ );
158
+ } else if (result.microsoftPublicationPending === false) {
159
+ logger.log(metadata('Microsoft publication: cleared'));
160
+ } else if (result.installReady === false && result.blocker && !packageBlocker) {
134
161
  logger.log(metadata(`Install ready: no (${result.blocker})`));
135
162
  }
136
163
  }
@@ -101,8 +101,10 @@ function applyCopilotEntraSsoFormFields(resolved, params = {}) {
101
101
  const entraIdentityProvider = params.entraIdentityProvider || null;
102
102
  const auth = resolved.discoveryAuthentication || null;
103
103
  resolved.entraSsoRegistrationUrl = ENTRA_SSO_REGISTRATION_URL;
104
- resolved.entraSsoBaseUrl =
105
- originFromUrl(resolved.dataplaneBaseUrl) || originFromUrl(resolved.mcpUrl) || null;
104
+ // Teams Entra SSO form Base URL = exact Remote MCP URL from package discovery.
105
+ const mcpUrl =
106
+ (typeof resolved.mcpUrl === 'string' && resolved.mcpUrl.trim()) || null;
107
+ resolved.entraSsoBaseUrl = mcpUrl || originFromUrl(resolved.dataplaneBaseUrl) || null;
106
108
  resolved.entraSsoClientId = resolveEntraSsoClientId(
107
109
  entraIdentityProvider,
108
110
  entraProvider,
@@ -119,6 +121,73 @@ function applyCopilotEntraSsoFormFields(resolved, params = {}) {
119
121
  if (entraIdentityProvider?.enterpriseAppId) {
120
122
  resolved.entraEnterpriseAppId = String(entraIdentityProvider.enterpriseAppId).trim();
121
123
  }
124
+ applyWebApplicationInfoFields(resolved, params);
125
+ }
126
+
127
+ const TEAMS_AUTH_CONFIG_GUID =
128
+ /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
129
+
130
+ /**
131
+ * @param {string|unknown} value
132
+ * @returns {string}
133
+ */
134
+ function trimNonEmptyString(value) {
135
+ return typeof value === 'string' ? value.trim() : '';
136
+ }
137
+
138
+ /**
139
+ * Teams Entra SSO Application ID URI: api://auth-{authConfigId}/{clientId}.
140
+ * Only when oauthPluginVaultReferenceId is a GUID (Teams auth-config id).
141
+ * @param {string|null} authConfigId
142
+ * @param {string|null} clientId
143
+ * @returns {string|null}
144
+ */
145
+ function buildTeamsWebApplicationInfoResource(authConfigId, clientId) {
146
+ const configId = trimNonEmptyString(authConfigId);
147
+ const appId = trimNonEmptyString(clientId);
148
+ if (!configId || !TEAMS_AUTH_CONFIG_GUID.test(configId) || !appId) {
149
+ return null;
150
+ }
151
+ return `api://auth-${configId}/${appId}`;
152
+ }
153
+
154
+ /**
155
+ * @param {Object} resolved
156
+ * @returns {string|null}
157
+ */
158
+ function resolveWebApplicationClientId(resolved) {
159
+ return (
160
+ trimNonEmptyString(resolved.entraSsoClientId) ||
161
+ trimNonEmptyString(resolved.entraEnterpriseAppId) ||
162
+ null
163
+ );
164
+ }
165
+
166
+ /**
167
+ * Preserve / derive Teams webApplicationInfo for Entra SSO packages.
168
+ * Prefer explicit params, then prior package values, then GUID auth-config + SSO client id.
169
+ * Do not invent resource URIs from opaque (non-GUID) vault references.
170
+ * @param {Object} resolved
171
+ * @param {Object} params
172
+ * @returns {void}
173
+ */
174
+ function applyWebApplicationInfoFields(resolved, params = {}) {
175
+ const prior = params.priorWebApplicationInfo || null;
176
+ const explicitId = trimNonEmptyString(params.webApplicationInfoId);
177
+ const explicitResource = trimNonEmptyString(params.webApplicationInfoResource);
178
+ const priorId = trimNonEmptyString(prior && prior.id);
179
+ const priorResource = trimNonEmptyString(prior && prior.resource);
180
+ const clientId = resolveWebApplicationClientId(resolved);
181
+ const derivedResource = buildTeamsWebApplicationInfoResource(
182
+ resolved.oauthPluginVaultReferenceId,
183
+ clientId
184
+ );
185
+ const id = explicitId || priorId || (derivedResource ? clientId : null);
186
+ const resource = explicitResource || priorResource || derivedResource || null;
187
+ if (id && resource) {
188
+ resolved.webApplicationInfoId = id;
189
+ resolved.webApplicationInfoResource = resource;
190
+ }
122
191
  }
123
192
 
124
193
  /**
@@ -175,6 +244,8 @@ module.exports = {
175
244
  resolveEntraSsoClientId,
176
245
  resolveEntraTenantId,
177
246
  applyCopilotEntraSsoFormFields,
247
+ applyWebApplicationInfoFields,
248
+ buildTeamsWebApplicationInfoResource,
178
249
  renderCopilotChannelTemplate,
179
250
  formatOauthPluginVaultReferenceRequiredError
180
251
  };
@@ -131,6 +131,9 @@ function resolveCopilotEntraScopes(params) {
131
131
  }
132
132
 
133
133
  /**
134
+ * Package/Runtime readiness blocker (not Microsoft org-publish state).
135
+ * Having a Remote MCP URL is enough for packaging even when live remote status
136
+ * is temporarily unavailable — Microsoft publication is tracked separately.
134
137
  * @param {Object} params
135
138
  * @returns {string|null}
136
139
  */
@@ -138,13 +141,15 @@ function firstCopilotEntraBlocker(params) {
138
141
  const {
139
142
  discoveryReady,
140
143
  discoveryBlocker,
144
+ mcpServerUrl,
141
145
  recommended,
142
146
  referenceId,
143
147
  issuer,
144
148
  audience,
145
149
  scopes
146
150
  } = params;
147
- if (!discoveryReady) {
151
+ const hasMcpUrl = typeof mcpServerUrl === 'string' && mcpServerUrl.trim();
152
+ if (!hasMcpUrl && !discoveryReady) {
148
153
  return discoveryBlocker || 'remoteMcpUnavailable';
149
154
  }
150
155
  if (recommended !== TRUSTED_ISSUER) {
@@ -185,7 +190,14 @@ function resolveAudienceAndIssuer(auth, entraProvider) {
185
190
 
186
191
  /**
187
192
  * @param {Object} params
188
- * @returns {{ installReady: boolean, blocker: string|null, packageAuthentication: Object|null }}
193
+ * @returns {{
194
+ * installReady: boolean,
195
+ * blocker: string|null,
196
+ * packageReady: boolean,
197
+ * packageBlocker: string|null,
198
+ * microsoftPublicationPending: boolean,
199
+ * packageAuthentication: Object|null
200
+ * }}
189
201
  */
190
202
  function evaluateCopilotEntraSsoReadiness(params) {
191
203
  const packageContext = params.packageContext || null;
@@ -197,6 +209,10 @@ function evaluateCopilotEntraSsoReadiness(params) {
197
209
  : '';
198
210
  const scopes = normalizeScopeList(params.scopes);
199
211
  const { audience, issuer } = resolveAudienceAndIssuer(auth, params.entraProvider || null);
212
+ const mcpServerUrl =
213
+ (typeof packageContext?.mcpServerUrl === 'string' && packageContext.mcpServerUrl.trim()) ||
214
+ (typeof params.mcpServerUrl === 'string' && params.mcpServerUrl.trim()) ||
215
+ null;
200
216
 
201
217
  if (auth) {
202
218
  auth.issuer = issuer;
@@ -204,18 +220,23 @@ function evaluateCopilotEntraSsoReadiness(params) {
204
220
  auth.scopes = scopes;
205
221
  }
206
222
 
207
- const blocker = firstCopilotEntraBlocker({
223
+ const packageBlocker = firstCopilotEntraBlocker({
208
224
  discoveryReady: packageContext?.installReady === true,
209
225
  discoveryBlocker: packageContext?.blocker || null,
226
+ mcpServerUrl,
210
227
  recommended,
211
228
  referenceId,
212
229
  issuer,
213
230
  audience,
214
231
  scopes
215
232
  });
233
+ const packageReady = packageBlocker === null;
216
234
  return {
217
- installReady: blocker === null,
218
- blocker,
235
+ installReady: packageReady,
236
+ blocker: packageBlocker,
237
+ packageReady,
238
+ packageBlocker,
239
+ microsoftPublicationPending: true,
219
240
  packageAuthentication: auth
220
241
  };
221
242
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @fileoverview Truncate and normalize Copilot package display fields to schema limits
5
5
  * @author AI Fabrix Team
6
- * @version 1.0.0
6
+ * @version 1.1.0
7
7
  */
8
8
 
9
9
  'use strict';
@@ -15,6 +15,11 @@ const TEAMS_DESCRIPTION_FULL_MAX = 4000;
15
15
  const PLUGIN_NAME_FOR_HUMAN_MAX = 20;
16
16
  const PLUGIN_DESCRIPTION_FOR_HUMAN_MAX = 100;
17
17
 
18
+ const FI_DEFAULT_SHORT =
19
+ 'Yrityksen tekoälyavustaja työntekijöille AI Fabrix Runtime -ympäristössä.';
20
+ const FI_DEFAULT_FULL =
21
+ 'Yrityksen tekoälyavustaja työntekijöille. Hyväksytyt avustajat, yritystieto, työnkulut ja hallitut toiminnot AI Fabrixin kautta.';
22
+
18
23
  /**
19
24
  * @param {string} text
20
25
  * @param {number} max
@@ -52,7 +57,8 @@ function applyMicrosoftCopilotDisplayLimits(runtimeValues) {
52
57
  const appDescription = String(runtimeValues.appDescription || '').trim();
53
58
  const baseName = String(runtimeValues.baseName || runtimeValues.appName || 'Assistant').trim();
54
59
  const appName = String(runtimeValues.appName || baseName).trim();
55
- runtimeValues.shortName = truncateForSchema(baseName, TEAMS_NAME_SHORT_MAX);
60
+ // Teams short + full both use the environment-scoped display name (e.g. "Onni (dev)").
61
+ runtimeValues.shortName = truncateForSchema(appName, TEAMS_NAME_SHORT_MAX);
56
62
  runtimeValues.fullName = truncateForSchema(appName, TEAMS_NAME_FULL_MAX);
57
63
  runtimeValues.shortDescription = truncateForSchema(
58
64
  appDescription || 'Enterprise AI assistant via AI Fabrix Runtime.',
@@ -62,12 +68,23 @@ function applyMicrosoftCopilotDisplayLimits(runtimeValues) {
62
68
  appDescription || runtimeValues.shortDescription,
63
69
  TEAMS_DESCRIPTION_FULL_MAX
64
70
  );
65
- runtimeValues.pluginNameForHuman = truncateForSchema(baseName, PLUGIN_NAME_FOR_HUMAN_MAX);
71
+ // Plugin name_for_human matches the env-scoped display name (e.g. "Onni (dev)").
72
+ runtimeValues.pluginNameForHuman = truncateForSchema(appName, PLUGIN_NAME_FOR_HUMAN_MAX);
66
73
  runtimeValues.pluginDescriptionForHuman = truncateForSchema(
67
74
  appDescription || runtimeValues.shortDescription,
68
75
  PLUGIN_DESCRIPTION_FOR_HUMAN_MAX
69
76
  );
70
77
  runtimeValues.pluginNamespace = buildPluginNamespace(baseName);
78
+ const fiShort =
79
+ typeof runtimeValues.fiShortDescription === 'string' && runtimeValues.fiShortDescription.trim()
80
+ ? runtimeValues.fiShortDescription.trim()
81
+ : FI_DEFAULT_SHORT;
82
+ const fiFull =
83
+ typeof runtimeValues.fiFullDescription === 'string' && runtimeValues.fiFullDescription.trim()
84
+ ? runtimeValues.fiFullDescription.trim()
85
+ : FI_DEFAULT_FULL;
86
+ runtimeValues.fiShortDescription = truncateForSchema(fiShort, TEAMS_DESCRIPTION_SHORT_MAX);
87
+ runtimeValues.fiFullDescription = truncateForSchema(fiFull, TEAMS_DESCRIPTION_FULL_MAX);
71
88
  return runtimeValues;
72
89
  }
73
90
 
@@ -78,6 +95,8 @@ module.exports = {
78
95
  TEAMS_DESCRIPTION_FULL_MAX,
79
96
  PLUGIN_NAME_FOR_HUMAN_MAX,
80
97
  PLUGIN_DESCRIPTION_FOR_HUMAN_MAX,
98
+ FI_DEFAULT_SHORT,
99
+ FI_DEFAULT_FULL,
81
100
  truncateForSchema,
82
101
  buildPluginNamespace,
83
102
  applyMicrosoftCopilotDisplayLimits