# 入口类型与应用清单

配置环境托管前端、平台内路由与普通外链，并迁移不再支持平台授权的历史外部入口。

入口类型决定应用在哪里运行以及如何取得应用身份。需要平台授权的前端使用环境托管；普通外链不签发平台应用 token。所有平台应用仍必须由 Bundle 安装到工作区。

当前项目配置版本为 `3.0.0`。方法目标使用 `{type: "module", bundleId, moduleId}`，可替换接口使用 `{type: "interface", name}`；`manifest.interfaceBindings` 的实现值为 `{bundleId, moduleId}`。模块身份直接来自 Bundle 领域目录，不从模块显示名称推导。

迁移旧项目时，在 Git 源文件中把配置版本改为 `3.0.0`，从目录重新选择每个模块，替换方法目标和接口绑定，然后预检、提交并创建新应用版本。已有不可变版本保持原样。CLI 创建版本时自动固定应用源码和 Bundle 绑定项目各自的提交；服务端只按当前 Bundle 源清单及显式依赖闭包解析引用。

## 当前入口类型与历史迁移

| `entryType`                  | 适用场景                   | 入口要求                                   | 授权方式                                  |
| ---------------------------- | ---------------------- | -------------------------------------- | ------------------------------------- |
| `ENVIRONMENT_HOSTED_APP`     | 由当前环境托管、需要平台身份和能力的前端应用 | 版本内相对根路径，例如 `/index.html`              | 标准 SDK 读取同源环境配置，通过网页授权和 PKCE 取得 token |
| `PLATFORM_ROUTE`             | 平台管理端已经内置的页面           | 以 `/` 开头的平台相对路径                        | 继承平台管理端登录态                            |
| `EXTERNAL_URL`               | 不调用平台能力的普通链接           | `http://` 或 `https://` 地址，不含安装态或运行态占位符 | 不签发平台应用 token；外部系统自行授权                |
| `TRUSTED_EXTERNAL_APP`（历史类型） | 仅用于识别待迁移应用             | 旧外部地址不再作为平台授权前端入口                      | 当前启动和平台授权链路拒绝，须迁移为环境托管应用              |

新开发的 React/Vite 平台应用使用 `ENVIRONMENT_HOSTED_APP`。普通外链仍可使用 `EXTERNAL_URL`；
需要平台身份和 Runtime 能力时，应把前端迁入专属平台项目，创建平台应用版本并随 Bundle 安装。
定义服务仍能识别历史 `TRUSTED_EXTERNAL_APP`，不代表当前环境允许启动或授权。

`PLATFORM_ROUTE` 的“已经内置”必须由平台管理端源码和对应平台发布事实证明。创建了
`PLATFORM_APPLICATION` 项目、提交了 React 源码或安装了 Bundle，都不会自动注册 Manager
内置路由。通用平台应用宿主地址 `/workspace/{workspaceId}/platform-app/{platformAppId}` 也不是可复用的内置页面声明。

## 环境托管入口

在项目根目录的 `baijimu.platform-application.json` 中声明：

```json
{
  "schemaVersion": "3.0.0",
  "entry": {
    "type": "ENVIRONMENT_HOSTED_APP",
    "urlTemplate": "/index.html",
    "openMode": "WORKSPACE_TAB"
  },
  "methodDefinitions": [],
  "configSchema": {
    "type": "object"
  },
  "manifest": {},
  "cardDefinitions": [],
  "workflowDefinitions": []
}
```

`/index.html` 相对于该版本构建产物的根目录，不是 Manager 路由或站点域名根目录。
入口必须对应构建产物中的实际文件，不能填写外部 URL、协议相对地址、查询串、hash、占位符或 `..` 路径段。
不要在清单中写入域名、发布目录或 `{installId}`；平台在安装、托管后解析实际入口，并在启动 `accessUrl` 中追加真实 `installId`。

新应用使用 SDK 7，部署在独立 origin 的根路径。Vite 使用默认根路径或 `base: "/"`；
新建项目模板默认使用 `BrowserRouter`，无需版本目录 `basename`，由站点提供根 `index.html` 回退。
开发者可以在应用源码中改用 `HashRouter`，同时更新对外提供的页面链接；终端用户无需选择路由方式。
应用不可变版本和 Bundle 升级仍然保留。已有部署保持原入口；SDK 6 历史归档首次安装到新环境前必须升级 SDK 并创建新应用版本。
安装后应通过实际 `accessUrl` 检查 HTML、JavaScript、CSS 和公共资源，以及业务首页是否渲染；
资源返回 `200` 但应用进入 NotFound 页面仍属于部署失败。

SDK 从当前 HTTPS 同源的 `/.well-known/baijimu-platform-application.json` 读取环境配置。
配置由平台托管流程提供，应用归档不能覆盖它；不得从查询参数或某个公共 SaaS 默认地址取得授权服务地址。
完整接入见 [Web 接入与运行态调用](/development/bundle-development/platform-application-development/sdk-and-runtime/)。

启动地址只携带安装选择器，不携带 token、用户身份、工作区身份或密钥。`installId` 本身不是权限证明；
平台仍校验当前用户和精确 Bundle 安装。跨工作区分享使用 Manager 的 `/bundle-app/{bundleId}/{platformAppId}`。

## 用普通 href 打开应用内页面

Agent、通知和网页可以直接提供普通 HTTPS 链接，用户点击后进入指定应用页面。生成链接只需本地 URL
拼接，不需要请求链接生成接口、动态登记页面或取得 token。Manager 的基础地址来自当前环境的管理入口配置，
应用身份和路径来自真实应用资料，不得猜测域名或页面路径。

已知业务所属工作区时，使用固定工作区入口：

```text
{managementUrl}#/workspace/{workspaceId}/bundle-app/{bundleId}/{platformAppId}?target={encodedApplicationTarget}
```

`target` 是应用内的根相对地址，包含原生路径、业务 query 和可选 hash，作为一个查询参数完整编码一次：

| 应用路由             | 编码前的 `target` 示例                |
| ---------------- | ------------------------------- |
| 默认 BrowserRouter | `/orders/123?tab=items`         |
| 自选 HashRouter    | `/#/orders/123?tab=items`       |
| 页面锚点             | `/orders/123?tab=items#details` |

以下函数只在本地拼接 href；`managementUrl` 必须使用当前环境已知的管理入口，不能传入应用托管域名：

```ts
function applicationPageHref(managementUrl, workspaceId, bundleId, platformAppId, target) {
  const href = new URL(managementUrl);
  const segments = [workspaceId, bundleId, platformAppId].map(encodeURIComponent);
  const query = new URLSearchParams({ target });
  href.hash = `/workspace/${segments[0]}/bundle-app/${segments[1]}/${segments[2]}?${query}`;
  return href.toString();
}
```

Agent 将返回值直接写入 Markdown 的 `[查看订单](href)`，网页则将它赋给普通链接的 `href` 属性。
不要预先对 `target` 调用 `encodeURIComponent` 后再交给 `URLSearchParams`，这会造成重复编码。

点击后，平台按指定工作区解析当前安装和实际应用版本，并执行正常权限检查及 SDK 授权。链接不携带
`installId`、版本域名或授权凭据；应用升级后仍解析该工作区当前安装的版本。已指定工作区时不会再要求
用户选择工作区。需要登录或安装 Bundle 时，原页面目标会保留到流程完成；安装仍由有权限的用户确认执行。

`WORKSPACE_TAB` 应用在工作区 iframe 中打开目标。其他打开方式从链接所在的浏览上下文直达应用，
不先停在应用概览页；链接在当前还是新浏览器标签打开，由普通 `<a>` 的 `target` 属性或用户操作决定。
这与查询参数名 `target` 是两个不同概念。

只有确实需要接收者自行选择工作区的通用功能分享，才使用
`/bundle-app/{bundleId}/{platformAppId}?target=...`。具体工作区中的订单、文件或记录链接应固定工作区，
避免相同记录 ID 被解释到另一个工作区。应用身份在当前工作区必须唯一；存在多个来源的同名应用时明确报错，
不自动选择其中一个。

`target` 仅适用于 `ENVIRONMENT_HOSTED_APP`。不提供它时保持默认入口行为；提供它时不能为空或重复。
平台拒绝绝对 URL、协议相对地址、反斜杠、控制字符和路径中的 `.` / `..` 段，且不会改变应用 origin。
目标 query 与 hash query 均不得包含 `installId`、`workspaceId`、`code`、`state`、token 等平台身份、
环境端点或授权参数。平台保留启动服务提供的真实安装选择器，再合入目标的业务参数；普通业务重复参数会保留。
目标格式错误或无权限时明确报错；页面或业务记录不存在由应用展示对应错误，不能静默回到首页。

应用应在自身版本的 `manifest.pages` 中声明供 Agent 引用的页面，具体格式见
[页面声明与链接发现](/development/bundle-development/platform-application-development/pages/)。页面定义随应用版本创建和 Bundle 安装交付，不维护独立页面注册表。
路由调整或应用升级时应验证已有对外链接的行为。工作区宿主保存启动时的目标；iframe 后续自行导航
不会自动同步到外层地址，分享后续页面时应由应用用当前内部地址重新拼接 href。

验收至少覆盖两种路由、未登录回跳、Bundle 安装返回、详情页刷新、中文及重复业务参数、同一应用连续打开
不同记录，以及无权限和不存在页面。不要只检查首页或静态资源的 HTTP 状态。

## 查询分享入口、托管地址与启动上下文

CLI `0.66.0` 起提供三种只读查询。先逐级读取本机 `platform-app --help` 与目标子命令帮助。
应用入口和准确版本地址查询需要当前环境支持对应的公开查询能力。

| 查询                                      | 用途                | 返回地址                                 |
| --------------------------------------- | ----------------- | ------------------------------------ |
| `platform-app entry`                    | 分享应用，让接收者登录并选择工作区 | `entryUrl`：不带工作区、安装标识或版本             |
| `platform-app version address`          | 检查准确版本在当前环境的托管部署  | `entryUrl`：该版本的完整页面地址，不带安装标识         |
| `platform-app workspace launch-context` | 取得工作区已安装应用的访问入口   | `accessUrl`：已解析实际版本并携带真实 `installId` |

```bash
baijimu platform-app entry <platformAppId> --bundle-id <bundleId> --json

baijimu platform-app version address <platformAppId> \
  --bundle-id <bundleId> --version <semanticVersion> --json

baijimu platform-app workspace list --workspace-id <workspaceId> --json

baijimu platform-app workspace launch-context <resourceLocator> \
  --workspace-id <workspaceId> --json
```

`entry` 查询应用定义，`version address` 查询来源版本，调用者须具备相应来源工作区的读取权限。
这两个命令的可选 `--workspace-id` 只选择本机已有凭证，不改变返回地址，也不绑定接收者工作区。
`version address` 必须使用准确 Semantic Version，不接受 `latest` 或版本范围。
同一环境中的同一应用版本由各工作区共享托管地址；不同环境或版本的地址可能不同。

`workspace list` 是安装目录。其 `entryUrlTemplate` 为 `/index.html` 时，不能直接用于浏览器访问。
把该列表返回的完整 `resourceVersion.resourceLocator` 交给 `launch-context`，CLI 会读取当前安装的
Runtime 和准确版本，并查询启动上下文。该命令不会启动应用、打开浏览器或签发 token。
查询期间版本变化、应用未安装、未启用、无权限或托管未完成时会明确失败，不猜测或拼接地址。

分享入口来自当前环境的平台管理入口配置；未配置时返回错误。准确版本地址只有在托管部署完成后才返回，
查询本身不会触发部署。检查页面和静态资源可使用版本地址；需要调用平台业务能力时，仍须从指定安装的
`accessUrl` 完成正常授权。跨工作区发送链接时使用 `entry` 返回的分享入口。

## 从外部入口迁移

需要平台授权的旧 `TRUSTED_EXTERNAL_APP` 必须创建新的 `ENVIRONMENT_HOSTED_APP` 版本：
把前端源码纳入专属项目，改用版本内入口和同源配置 SDK 初始化，创建新的平台应用版本，
再由新 Bundle 版本引用并升级目标安装。只替换外部域名、修改入口类型或升级 SDK 不会完成迁移。
不需要平台能力的外部站点可以保留普通链接，并使用自己的后台授权。
完整步骤见 [创建版本并纳入 Bundle](/development/bundle-development/platform-application-development/create-and-install/)。

## 打开方式

`openMode` 用于声明入口的展示位置：

- `CURRENT`：在当前页面打开。
- `NEW_TAB`：在浏览器新标签页打开。
- `WORKSPACE_TAB`：在工作区标签中承载。

跨域应用在 `WORKSPACE_TAB` 中通常由 iframe 承载。应用需要同时验证自身的 frame、安全头、Cookie SameSite 和跨域策略是否允许这种运行方式。

## 版本清单

项目根目录的 `baijimu.platform-application.json` 至少要把以下内容作为同一个不可变契约提交：

- `schemaVersion`
- `entry.urlTemplate`
- `entry.type`
- `entry.openMode`
- `methodDefinitions`
- `configSchema`
- `manifest`
- 可选的 `cardDefinitions` 和 `workflowDefinitions`

这些字段使用原生 JSON 类型，不能把数组或对象序列化成字符串。安装记录引用具体版本。修改入口、权限或配置结构后，应提交项目、从该 commit 创建新版本并升级安装，不能只修改已有不可变版本的预期行为。
