@dp-bohrium/widget-sdk 0.1.0 → 0.3.0
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.
- package/AGENTS.md +33 -0
- package/CHANGELOG.md +26 -0
- package/README.md +57 -14
- package/dist/compatibility/testing.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +111 -45
- package/dist/lifetime.d.ts +12 -0
- package/dist/manifest.d.ts +1 -1
- package/dist/{protocol-endpoint-I78q3jVT.js → protocol-endpoint-CDz-YDGT.js} +1 -1
- package/dist/runtime/bootstrap.d.ts +7 -2
- package/dist/runtime/client.d.ts +9 -1
- package/dist/runtime/index.d.ts +4 -2
- package/dist/runtime/index.js +3 -3
- package/dist/runtime/service.d.ts +47 -0
- package/dist/{runtime-B5kilTcQ.js → runtime-Drr96Awx.js} +134 -3
- package/dist/widget.d.ts +22 -2
- package/llms.txt +24 -0
- package/package.json +4 -2
package/AGENTS.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Widget SDK Agent 使用说明
|
|
2
|
+
|
|
3
|
+
`@dp-bohrium/widget-sdk` 是 iframe 内 Widget 的运行时 SDK。Agent 先读取 `llms.txt`,再
|
|
4
|
+
根据任务选择高层 Widget API、低层 Runtime API 或测试入口;不要自行实现 Bridge 握手。
|
|
5
|
+
|
|
6
|
+
## 常用动作
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm add @dp-bohrium/widget-sdk
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
1. 用 `activate(context)` 实现 Widget;让 `bohr-widget` 生成 Host 入口。
|
|
13
|
+
2. 在 Manifest 中声明所需 capability,再使用 `context.capabilities`、`context.files`、
|
|
14
|
+
`context.tasks`、`context.ui` 或 `context.network`。
|
|
15
|
+
3. 在测试中使用 `@dp-bohrium/widget-sdk/testing` 的 `MockWidgetHost`;生产 Bundle 使用
|
|
16
|
+
根入口,不把 testing 入口打进产物。
|
|
17
|
+
4. 低层协议集成只从 `@dp-bohrium/widget-sdk/runtime` 导入,并保留 contracts 的精确版本。
|
|
18
|
+
|
|
19
|
+
## 错误恢复
|
|
20
|
+
|
|
21
|
+
- 初始化或 Bridge 失败:记录结构化错误的 `code`、`phase`、`message`、`retryable`、
|
|
22
|
+
`correlationId`,先检查 Bundle、Host API、版本、握手和 channel,再决定是否重试。
|
|
23
|
+
- 能力返回 `{ ok: false }`:根据 `reason` 和 `retryable` 分支处理;不要把拒绝当作异常,也
|
|
24
|
+
不要绕过 Host 直接访问凭据、文件路径或网络。
|
|
25
|
+
- `MESSAGE_INVALID`、`CHANNEL_MISMATCH`、`CAPABILITY_UNDECLARED` 和
|
|
26
|
+
`CAPABILITY_DENIED` 先修 Manifest、Host grant 或协议版本,不要无限重试。
|
|
27
|
+
- render/update 失败保留 `correlationId` 和当前 phase,确保界面仍能显示可恢复的空态。
|
|
28
|
+
|
|
29
|
+
## 边界
|
|
30
|
+
|
|
31
|
+
SDK 不授予权限、不上传或发布 Bundle、不决定 iframe/CSP 策略,也不保证脱离 Host 可以运行。
|
|
32
|
+
`ok=true` 只表示当前调用被 Runtime/Host 接受;真实授权、渲染和生产绑定由 Host 与平台负责。
|
|
33
|
+
发布包使用精确 SemVer;升级 SDK 时同步核对 contracts 版本和生成 Bundle 的 digest。
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
本文件只记录影响使用者的变更:运行时 API、事件订阅、Manifest 要求。
|
|
4
4
|
|
|
5
|
+
## 0.3.0
|
|
6
|
+
|
|
7
|
+
### 破坏性变更
|
|
8
|
+
|
|
9
|
+
**1. RenderRequest 与 content 删除旧字段:slot 改为 contribution、panelType 改为 id**
|
|
10
|
+
|
|
11
|
+
- 已发布的 0.2.0 运行时仍读取 `request.slot`。本版改为用 `request.contribution` 收窄 `request.content`;`request.slot` 不再存在。
|
|
12
|
+
- `content.panelType` 改为 `content.id`,其余 `content` 的 `slot` 改为 `contribution`。
|
|
13
|
+
- `compileWidgetPluginManifest` 生成的宿主清单字段由 `widgetConfig.slot` 改为 `widgetConfig.contribution`;作者 Manifest 写法不变。
|
|
14
|
+
|
|
15
|
+
### 修复
|
|
16
|
+
|
|
17
|
+
**2. 同步公开类型文档**
|
|
18
|
+
|
|
19
|
+
- SDK 与 contracts 锁定同一版本,公开类型说明与 schema 一致。widget-cli 单独以 `0.1.0` 发布,不进入这次 SDK Tag。
|
|
20
|
+
|
|
21
|
+
## 0.2.0
|
|
22
|
+
|
|
23
|
+
### 新增
|
|
24
|
+
|
|
25
|
+
**1. Host 用户资料读取 facade**
|
|
26
|
+
|
|
27
|
+
- 新增 `WidgetHostUserClient`(`createWidgetHostUserClient`),提供 `readProfile()` / `readEmail()` typed facade,内部仍走精确的 `host.user/profile.read` / `host.user/email.read` service.invoke。
|
|
28
|
+
- 新增 `HostUserProfile`(`{ userId, displayName }`)、`HostUserEmail`(`{ email }`)及对应 Result 类型。
|
|
29
|
+
- 无 grant 时仍在 iframe 内拒绝,不发送 Bridge request。
|
|
30
|
+
|
|
5
31
|
## 0.1.0
|
|
6
32
|
|
|
7
33
|
### 新增
|
package/README.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# @dp-bohrium/widget-sdk
|
|
2
2
|
|
|
3
|
+
Agent 使用入口:先读 [`llms.txt`](./llms.txt) 获取文档索引,再读 [`AGENTS.md`](./AGENTS.md)
|
|
4
|
+
获取安装、运行时调用和错误恢复规则。
|
|
5
|
+
|
|
3
6
|
Widget 开发者 SDK。普通作者只实现 `activate(context)` 和可选的 `deactivate()`;CLI
|
|
4
7
|
会生成 Widget Host 入口,负责向 Host 宣告 Bundle、接收转移过来的 `MessagePort`,并暴露共享的
|
|
5
8
|
生命周期与能力协议。作者无需直接处理 `window.postMessage`,也无需手动启动插件。
|
|
6
9
|
|
|
7
|
-
激活事件目录、`context.events` 与低层 `topic`
|
|
8
|
-
|
|
10
|
+
激活事件目录、`context.events` 与低层 `topic` 的边界以本包的 `AGENTS.md`、本 README
|
|
11
|
+
和 `@dp-bohrium/widget-contracts` 中的类型与 Schema 为准。
|
|
9
12
|
|
|
10
13
|
## 环境要求
|
|
11
14
|
|
|
@@ -42,16 +45,26 @@ import type { WidgetContext } from "@dp-bohrium/widget-sdk";
|
|
|
42
45
|
|
|
43
46
|
export function activate(context: WidgetContext) {
|
|
44
47
|
context.render((request, renderContext) => {
|
|
45
|
-
|
|
48
|
+
renderContribution(request.contribution, request.content, renderContext.phase);
|
|
49
|
+
});
|
|
50
|
+
context.events.on("tool-call-result", () => {
|
|
51
|
+
void context.ui.present("floating");
|
|
46
52
|
});
|
|
47
|
-
context.events.on("tool-call-result", (event) => showToolResult(event.payload));
|
|
48
53
|
}
|
|
49
54
|
```
|
|
50
55
|
|
|
51
56
|
CLI 生成的 `src/widget-host.ts` 会调用平台入口 `runWidgetHost(widget)`。
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`
|
|
57
|
+
这借鉴 VS Code 的激活入口。最终模型按 Binding 建立一个 Extension Runtime,`activate(context)`
|
|
58
|
+
在 Runtime 内只执行一次;工具、文件或面板目标是挂载到 Runtime 的 Surface Instance,拥有
|
|
59
|
+
独立的快照和释放句柄,不会再次触发 `activate`。开发者实现 `activate(context)`,可选实现
|
|
60
|
+
`deactivate()`;Widget Host/SDK 负责加载、等待初始化、报告启动失败和执行清理。普通插件
|
|
61
|
+
代码不需要调用 `ready()`,也不应该写 `plugin.ready().catch(() => {})`。`whenReady` 只保留
|
|
62
|
+
给测试和高级宿主编排;`whenDisposed` 可用于等待已完成激活插件的异步 `deactivate()`。
|
|
63
|
+
停用不会等待一个永不结束的 `activate()`;若激活在停用后迟到完成,SDK 会在已 abort 的生命周期上补偿释放并调用一次 `deactivate()`,但这不再延长 `whenDisposed`。
|
|
64
|
+
|
|
65
|
+
这是 SDK 的目标生命周期契约;当前 Widget Host 的动态加载仍由 Surface-bound Adapter 接线,
|
|
66
|
+
Binding 级 Runtime Loader 尚未开放。插件可以按 Runtime/Surface 分层设计,但不能把当前版本
|
|
67
|
+
当成“无界面必然已启动”的生产保证。
|
|
55
68
|
|
|
56
69
|
`context.signal` 会在 Host 停用插件或 Bridge 断开时 `abort`。`context.render` 和 `context.events`
|
|
57
70
|
会自动登记到当前插件生命周期;`subscriptions` 只用于
|
|
@@ -61,7 +74,8 @@ Widget Host/SDK 负责加载、等待初始化、报告启动失败和执行清
|
|
|
61
74
|
|
|
62
75
|
- `ready()` 只在 `activate(context)` 返回(含其 Promise settle)之后才发给 Host,
|
|
63
76
|
`handshakeTimeoutMs` 同时约束这一步;
|
|
64
|
-
- 初始化失败时,SDK
|
|
77
|
+
- 初始化失败时,SDK 先报结构化错误,再调用已登记的插件资源清理;成功激活后的释放会
|
|
78
|
+
最多调用一次 `deactivate()`,已完成激活的异步清理可通过 `whenDisposed` 等待;
|
|
65
79
|
- `context.render` 首帧和更新都会收到完整快照,通过 `renderContext.phase` 区分。
|
|
66
80
|
|
|
67
81
|
## 从 Manifest 到插件运行
|
|
@@ -87,13 +101,34 @@ Manifest 是插件的静态声明,SDK 代码是隔离 iframe 内的运行时
|
|
|
87
101
|
}
|
|
88
102
|
```
|
|
89
103
|
|
|
104
|
+
需要接管完整的会话右侧区域时,在同一个 Panel contribution 上声明结构性放置意图,并同时申请匹配的
|
|
105
|
+
`ui.region` capability:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"contributes": {
|
|
110
|
+
"widget": {
|
|
111
|
+
"type": "panel",
|
|
112
|
+
"id": "better-sidebar",
|
|
113
|
+
"placement": { "kind": "region", "region": "session.rightRail", "mode": "replace" }
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"capabilities": {
|
|
117
|
+
"region": { "regions": ["session.rightRail"], "mode": "replace" }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`placement` 只表达贡献点的结构性意图;区域租约、确认、几何、焦点、原生面板恢复和失败回退仍由 Host
|
|
123
|
+
负责。没有匹配 capability 时,SDK 编译阶段直接拒绝清单。
|
|
124
|
+
|
|
90
125
|
`activationEvents` 只决定 Host 何时创建插件运行时;它不授予事件读取权限,也不负责打开思考过程或某个面板。插件激活后通过 `context.events` 和 `context.render` 注册监听与渲染器。Host 会对每个事件和能力请求做白名单投影与授权,插件收到的事件可能是有限回放,也可能是实时事件。
|
|
91
126
|
|
|
92
127
|
实现建议:事件处理器保持幂等,按 `toolCallId` 或 `requestId` 去重;不要从 iframe URL、DOM 或宿主绝对路径推断内部数据;当能力未授予时显示结构化空态并继续保留可渲染的主界面。声明但没有实际使用的能力会被 Host 记录为诊断信息,未声明能力的调用会被拒绝并结束当前 Bridge 会话。
|
|
93
128
|
|
|
94
129
|
### 统一插件入口
|
|
95
130
|
|
|
96
|
-
|
|
131
|
+
所有公开插件类型都使用同一个 `activate(context)` 约定。面板和文件预览由
|
|
97
132
|
Manifest 的 `contributes.widget` 决定;运行时代码只认识统一的 `context.events`、
|
|
98
133
|
`context.render`、`context.capabilities` 和 `context.ui`。首帧和后续快照通过
|
|
99
134
|
`renderContext.phase` 区分,不需要维护两套回调。
|
|
@@ -105,6 +140,8 @@ TypeScript 类型,不依赖某个 `defineManifest` 工厂。构建工具调用
|
|
|
105
140
|
才需要传入第二个参数。`createWidgetClient` 是协议级基础实现,不属于 Widget 作者 API;当前根入口只提供统一 Widget API,协议级代码应从
|
|
106
141
|
`./runtime` 显式引入。
|
|
107
142
|
|
|
143
|
+
受信任的内部 Host/协议测试仍可直接构造低层 Tool Result RenderRequest,但该能力不属于外部作者清单或公共示例。
|
|
144
|
+
|
|
108
145
|
SDK 发布包会生成 `dist/index.d.ts` 与对应的 declaration map,并通过 `package.json` 的
|
|
109
146
|
`types`/`exports` 暴露。插件安装 `@dp-bohrium/widget-sdk` 后,编辑器会直接获得
|
|
110
147
|
`WidgetContext`、事件名、能力请求、渲染请求和生命周期返回值的类型提示;Manifest
|
|
@@ -127,10 +164,11 @@ SDK 发布包会生成 `dist/index.d.ts` 与对应的 declaration map,并通
|
|
|
127
164
|
|
|
128
165
|
## 能力
|
|
129
166
|
|
|
130
|
-
|
|
131
|
-
|
|
167
|
+
公开能力要在 Manifest 的 `capabilities` 里声明,并由 Host 实际 grant;
|
|
168
|
+
`service.invoke` 需要由 Host binding 显式授权。缺任一侧,调用在 Widget 本地即被拒绝。
|
|
169
|
+
所有能力调用都必须在 `ready()` 之后。
|
|
132
170
|
|
|
133
|
-
|
|
171
|
+
下面列出的能力是当前 SDK 支持的能力。grant 名与 Manifest 申请键不一定同名 —— `ui.presentation`
|
|
134
172
|
对应的作者侧申请键是 `capabilities.presentation`(编译后的 宿主格式字段才叫
|
|
135
173
|
`requestedCapabilities.presentation`)。
|
|
136
174
|
|
|
@@ -143,7 +181,9 @@ SDK 发布包会生成 `dist/index.d.ts` 与对应的 declaration map,并通
|
|
|
143
181
|
| `ui.state` | `state`(`read` / `write`) | `context.ui.state.read(key)` / `write(key, value)` |
|
|
144
182
|
| `actions.invoke` | `actions` | `context.capabilities.request({ operation: "actions.invoke", input })` |
|
|
145
183
|
| `network.fetch` | `network` | `context.network.fetch(…)` |
|
|
184
|
+
| `service.invoke` | 由 Host binding 显式授权 | `context.services.invoke({ service, action, payload })` |
|
|
146
185
|
| `ui.presentation` | `presentation` | `context.ui.present(…)` / `changeSurface` / `close` |
|
|
186
|
+
| `ui.region` | `region`(固定申请 `session.rightRail`) | `context.ui.region.acquire()` / `release()` |
|
|
147
187
|
|
|
148
188
|
除 Host 主动推送事件外,其余能力共用同一套调用范式,掌握下面这一个就会调其余能力:
|
|
149
189
|
|
|
@@ -169,14 +209,17 @@ if (result.ok) {
|
|
|
169
209
|
- 中止与超时统一用 `{ signal, timeoutMs }`,复用既有 MessageChannel 能力生命周期。
|
|
170
210
|
- Widget 永远拿不到 Host 凭据,也不应直接调用浏览器 `fetch`:是否弹窗、记住 Origin
|
|
171
211
|
授权、跟随重定向还是拒绝,全部由 Host 决定。
|
|
212
|
+
- `context.services` 仅在 Host binding 授予 `service.invoke` 后可用。可调用的服务、操作、请求/响应
|
|
213
|
+
大小和有效期由 Host 的授权策略决定;未获授权时调用会被拒绝。认证状态由宿主侧管理,SDK 只收发
|
|
214
|
+
业务 payload,不会把 Cookie、Bearer Token 或其他凭据放入 iframe。
|
|
172
215
|
- `context.files.download` 只提交文件引用和建议文件名;Host 在用户确认后经宿主
|
|
173
216
|
Download Adapter 惰性打开内容源。Widget 不会收到源 URL、保存路径或凭据。
|
|
174
217
|
- `context.tasks.watch` 只读取一次受限任务快照和事件页;调用方用返回的 cursor 继续轮询。
|
|
175
218
|
它不创建、调度或取消任务,服务端负责任务授权、脱敏和终态保留。
|
|
176
219
|
- `context.ui.state` 只保存按 Widget + Session 隔离的 JSON UI 状态;Host 按 key、单值大小、总字节数和调用
|
|
177
220
|
频率限流。它不能保存任务执行、凭据、文件内容或 Workspace 权限状态。
|
|
178
|
-
- Tool Result
|
|
179
|
-
|
|
221
|
+
- 外部插件的工具结果场景通过 `tool-call-result` 事件作为可选运行时信号;内部 Host/协议测试仍可使用低层 Tool Result RenderRequest
|
|
222
|
+
或展示槽位。插件应把结果转换成自己的 Panel 数据,再通过 `context.render` 更新面板。
|
|
180
223
|
- 呈现只是**请求**。Host 可能降级到别的表面,成功结果里的 `surface` 才是真实表面;
|
|
181
224
|
Widget 不能指定目标 Panel、身份、运行时 generation、坐标或层级。
|
|
182
225
|
- 高层插件 API 使用 `context.events.on(handler)` 接收 Host 从当前 Runtime **主动推送**的
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as MemoryTransport, t as ProtocolEndpoint } from "../protocol-endpoint-
|
|
1
|
+
import { n as MemoryTransport, t as ProtocolEndpoint } from "../protocol-endpoint-CDz-YDGT.js";
|
|
2
2
|
//#region src/runtime/mock-host.ts
|
|
3
3
|
const widgetMessageTypes = /* @__PURE__ */ new Set([
|
|
4
4
|
"ready",
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export type { WidgetActivationEvent, WidgetActivationEvents, WidgetContribution, WidgetEventName, WidgetFilePreviewContribution, WidgetPanelContribution, WidgetPluginContributions, WidgetPluginManifest, WidgetToolPhase,
|
|
1
|
+
export type { WidgetActivationEvent, WidgetActivationEvents, WidgetContribution, WidgetEventName, WidgetFilePreviewContribution, WidgetPanelContribution, WidgetPluginContributions, WidgetPluginManifest, WidgetToolPhase, } from "@dp-bohrium/widget-contracts";
|
|
2
2
|
export { isWidgetActivationEvent, isWidgetEventName, WIDGET_ACTIVATION_EVENTS, WIDGET_EVENT_NAMES, } from "@dp-bohrium/widget-contracts";
|
|
3
3
|
export type { CompileWidgetPluginManifestOptions } from "./manifest.js";
|
|
4
4
|
export { compileWidgetPluginManifest } from "./manifest.js";
|
|
5
|
+
export type { HostUserEmail, HostUserEmailResult, HostUserProfile, HostUserProfileResult, WidgetHostUserClient, } from "./runtime/service.js";
|
|
5
6
|
export type { FileDownloadCapabilityResult, TaskWatchCapabilityResult, UiStateReadCapabilityResult, UiStateWriteCapabilityResult, WidgetActivation, WidgetCapabilityApi, WidgetContext, WidgetDisposable, WidgetEventApi, WidgetFileApi, WidgetHandle, WidgetHostOptions, WidgetModule, WidgetRenderContext, WidgetRenderHandler, WidgetTaskApi, WidgetUiApi, WidgetUiStateApi, } from "./widget.js";
|
|
6
7
|
export { runWidgetHost } from "./widget.js";
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as createWidgetClient, l as WIDGET_SDK_VERSION } from "./runtime-Drr96Awx.js";
|
|
2
2
|
import { WIDGET_ACTIVATION_EVENTS, WIDGET_EVENT_NAMES, isWidgetActivationEvent, isWidgetEventName } from "@dp-bohrium/widget-contracts";
|
|
3
3
|
//#region src/manifest.ts
|
|
4
|
-
function
|
|
4
|
+
function compiledContribution(contribution) {
|
|
5
5
|
switch (contribution.type) {
|
|
6
6
|
case "panel": return {
|
|
7
7
|
type: "panel",
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
case "tool-result": return {
|
|
11
|
-
type: "tool-result",
|
|
12
|
-
toolName: contribution.toolName
|
|
8
|
+
id: contribution.id,
|
|
9
|
+
...contribution.placement === void 0 ? {} : { placement: contribution.placement }
|
|
13
10
|
};
|
|
14
11
|
case "file-preview": return {
|
|
15
12
|
type: "file-preview",
|
|
@@ -21,11 +18,13 @@ function compiledSlot(contribution) {
|
|
|
21
18
|
* 将开发者维护的插件清单编译为宿主可加载的宿主清单。
|
|
22
19
|
*
|
|
23
20
|
* 这是构建工具的边界函数,不是类型提示入口:作者直接编辑带 `$schema` 的 JSON,
|
|
24
|
-
* SDK 版本从当前安装包读取,宿主入口默认落到 dist/widget.js,内部
|
|
21
|
+
* SDK 版本从当前安装包读取,宿主入口默认落到 dist/widget.js,内部 contribution 标识只在这里生成。
|
|
25
22
|
*/
|
|
26
23
|
function compileWidgetPluginManifest(manifest, options = {}) {
|
|
27
24
|
const samples = options.samples ?? manifest.samples;
|
|
28
25
|
if (!samples || samples.length === 0) throw new Error("Widget Plugin Manifest must provide at least one sample before packaging");
|
|
26
|
+
const contribution = manifest.contributes.widget;
|
|
27
|
+
if (contribution.type === "panel" && contribution.placement?.kind === "region" && (manifest.capabilities?.region?.mode !== contribution.placement.mode || !manifest.capabilities.region.regions.includes(contribution.placement.region))) throw new Error("A region Widget contribution must declare a matching region capability");
|
|
29
28
|
const activationEvents = manifest.activationEvents ? [...manifest.activationEvents] : void 0;
|
|
30
29
|
return {
|
|
31
30
|
name: manifest.name,
|
|
@@ -35,7 +34,7 @@ function compileWidgetPluginManifest(manifest, options = {}) {
|
|
|
35
34
|
hostApiVersion: 1,
|
|
36
35
|
widgetSdkVersion: WIDGET_SDK_VERSION,
|
|
37
36
|
entry: options.hostEntry ?? "dist/widget.js",
|
|
38
|
-
|
|
37
|
+
contribution: compiledContribution(manifest.contributes.widget),
|
|
39
38
|
...activationEvents === void 0 ? {} : { activationEvents },
|
|
40
39
|
...manifest.capabilities === void 0 ? {} : { requestedCapabilities: manifest.capabilities },
|
|
41
40
|
...manifest.runtime === void 0 ? {} : { runtime: manifest.runtime },
|
|
@@ -44,13 +43,57 @@ function compileWidgetPluginManifest(manifest, options = {}) {
|
|
|
44
43
|
};
|
|
45
44
|
}
|
|
46
45
|
//#endregion
|
|
46
|
+
//#region src/lifetime.ts
|
|
47
|
+
function createWidgetLifetime(onError) {
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
const resources = /* @__PURE__ */ new Map();
|
|
50
|
+
const pending = /* @__PURE__ */ new Set();
|
|
51
|
+
let closePromise;
|
|
52
|
+
function run(callback) {
|
|
53
|
+
const task = Promise.resolve().then(callback).catch((error) => {
|
|
54
|
+
try {
|
|
55
|
+
onError(error);
|
|
56
|
+
} catch {}
|
|
57
|
+
});
|
|
58
|
+
pending.add(task);
|
|
59
|
+
task.then(() => pending.delete(task));
|
|
60
|
+
return task;
|
|
61
|
+
}
|
|
62
|
+
function add(resource) {
|
|
63
|
+
const existing = resources.get(resource);
|
|
64
|
+
if (existing) return existing;
|
|
65
|
+
let disposed;
|
|
66
|
+
const handle = { dispose() {
|
|
67
|
+
if (disposed) return disposed;
|
|
68
|
+
disposed = run(() => typeof resource === "function" ? resource() : resource.dispose());
|
|
69
|
+
return disposed;
|
|
70
|
+
} };
|
|
71
|
+
resources.set(resource, handle);
|
|
72
|
+
if (controller.signal.aborted) handle.dispose();
|
|
73
|
+
return handle;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
signal: controller.signal,
|
|
77
|
+
add,
|
|
78
|
+
close() {
|
|
79
|
+
if (closePromise) return closePromise;
|
|
80
|
+
controller.abort();
|
|
81
|
+
for (const resource of [...resources.values()].reverse()) resource.dispose();
|
|
82
|
+
closePromise = (async () => {
|
|
83
|
+
while (pending.size) await Promise.all([...pending]);
|
|
84
|
+
})();
|
|
85
|
+
return closePromise;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
47
90
|
//#region src/widget.ts
|
|
48
91
|
/**
|
|
49
92
|
* Generated Widget Host 的唯一运行入口。Widget 作者只导出 activate/deactivate,
|
|
50
93
|
* 不直接调用此函数;CLI 生成的 widget-host.ts 负责调用它。
|
|
51
94
|
*/
|
|
52
95
|
function runWidgetHost(widget, options = {}) {
|
|
53
|
-
const client = createWidgetRuntime(
|
|
96
|
+
const client = createWidgetRuntime(typeof widget === "function" ? { activate: widget } : widget, options);
|
|
54
97
|
const whenReady = client.ready();
|
|
55
98
|
whenReady.catch((reason) => {
|
|
56
99
|
const error = toStartupError(reason);
|
|
@@ -62,19 +105,17 @@ function runWidgetHost(widget, options = {}) {
|
|
|
62
105
|
});
|
|
63
106
|
return {
|
|
64
107
|
whenReady,
|
|
108
|
+
whenDisposed: client.disposed(),
|
|
65
109
|
state: client.state,
|
|
66
110
|
dispose: client.dispose
|
|
67
111
|
};
|
|
68
112
|
}
|
|
69
|
-
function
|
|
70
|
-
return typeof registration === "function" ? { dispose: registration } : registration;
|
|
71
|
-
}
|
|
72
|
-
function createContext(client, payload, renderHandlers, registrations, signal) {
|
|
113
|
+
function createContext(client, payload, renderHandlers, lifetime) {
|
|
73
114
|
return {
|
|
74
115
|
instanceId: payload.instanceId,
|
|
75
116
|
locale: payload.locale,
|
|
76
117
|
theme: payload.theme,
|
|
77
|
-
signal,
|
|
118
|
+
signal: lifetime.signal,
|
|
78
119
|
capabilities: {
|
|
79
120
|
granted: payload.capabilities,
|
|
80
121
|
request: (request, options) => client.requestCapability(request, options)
|
|
@@ -88,6 +129,8 @@ function createContext(client, payload, renderHandlers, registrations, signal) {
|
|
|
88
129
|
input
|
|
89
130
|
}, options) },
|
|
90
131
|
network: client.network,
|
|
132
|
+
services: client.services,
|
|
133
|
+
hostUser: client.hostUser,
|
|
91
134
|
ui: {
|
|
92
135
|
state: {
|
|
93
136
|
read: (key, options) => client.requestCapability({
|
|
@@ -106,47 +149,78 @@ function createContext(client, payload, renderHandlers, registrations, signal) {
|
|
|
106
149
|
}
|
|
107
150
|
}, options)
|
|
108
151
|
},
|
|
152
|
+
region: {
|
|
153
|
+
acquire: (region, options) => client.acquireRegion(region, options),
|
|
154
|
+
release: (options) => client.releaseRegion(options)
|
|
155
|
+
},
|
|
109
156
|
present: (surface, options) => client.present(surface, options),
|
|
110
157
|
close: (options) => client.closePresentation(options),
|
|
111
158
|
changeSurface: (surface, options) => client.changePresentationSurface(surface, options)
|
|
112
159
|
},
|
|
113
160
|
events: { on(handlerOrName, maybeHandler) {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
return result;
|
|
161
|
+
const unsubscribe = typeof handlerOrName === "string" ? client.onEvent(handlerOrName, maybeHandler) : client.onEvent(handlerOrName);
|
|
162
|
+
return lifetime.add(unsubscribe);
|
|
117
163
|
} },
|
|
118
164
|
subscriptions: { add(registration) {
|
|
119
|
-
|
|
165
|
+
lifetime.add(registration);
|
|
120
166
|
} },
|
|
121
167
|
render(handler) {
|
|
122
168
|
renderHandlers.add(handler);
|
|
123
|
-
|
|
169
|
+
return lifetime.add({ dispose: () => {
|
|
124
170
|
renderHandlers.delete(handler);
|
|
125
|
-
} };
|
|
126
|
-
registrations.add(result);
|
|
127
|
-
return result;
|
|
171
|
+
} });
|
|
128
172
|
},
|
|
129
173
|
resize: (width, height) => client.resize(width, height)
|
|
130
174
|
};
|
|
131
175
|
}
|
|
132
|
-
function createWidgetRuntime(
|
|
176
|
+
function createWidgetRuntime(module, options = {}) {
|
|
133
177
|
let client;
|
|
134
|
-
let widgetDispose;
|
|
135
|
-
let context;
|
|
136
178
|
const renderHandlers = /* @__PURE__ */ new Set();
|
|
137
|
-
const
|
|
138
|
-
const lifetime =
|
|
179
|
+
const reportCleanupError = (error) => options.onCleanupError?.(toStartupError(error));
|
|
180
|
+
const lifetime = createWidgetLifetime(reportCleanupError);
|
|
181
|
+
let activation;
|
|
182
|
+
let activationCompleted = false;
|
|
183
|
+
let disposed = false;
|
|
184
|
+
let deactivation;
|
|
185
|
+
let disposal;
|
|
186
|
+
let resolveDisposed;
|
|
187
|
+
const disposedPromise = new Promise((resolve) => {
|
|
188
|
+
resolveDisposed = resolve;
|
|
189
|
+
});
|
|
190
|
+
function runDeactivation() {
|
|
191
|
+
if (deactivation) return deactivation;
|
|
192
|
+
deactivation = Promise.resolve().then(() => module.deactivate?.()).catch((error) => {
|
|
193
|
+
reportCleanupError(error);
|
|
194
|
+
});
|
|
195
|
+
return deactivation;
|
|
196
|
+
}
|
|
197
|
+
function dispose() {
|
|
198
|
+
if (disposal) return;
|
|
199
|
+
disposed = true;
|
|
200
|
+
const cleanup = lifetime.close();
|
|
201
|
+
renderHandlers.clear();
|
|
202
|
+
disposal = (async () => {
|
|
203
|
+
await cleanup;
|
|
204
|
+
if (activationCompleted) await runDeactivation();
|
|
205
|
+
})().catch(reportCleanupError).finally(resolveDisposed);
|
|
206
|
+
}
|
|
139
207
|
client = createWidgetClient({
|
|
140
208
|
...options,
|
|
141
209
|
events: {
|
|
142
210
|
...options.events,
|
|
143
|
-
|
|
144
|
-
context = createContext(client, payload, renderHandlers,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
211
|
+
onInit(payload) {
|
|
212
|
+
const context = createContext(client, payload, renderHandlers, lifetime);
|
|
213
|
+
activation = Promise.resolve().then(async () => {
|
|
214
|
+
if (lifetime.signal.aborted) return;
|
|
215
|
+
const registration = await module.activate(context);
|
|
216
|
+
if (registration !== void 0) {
|
|
217
|
+
const resource = lifetime.add(registration);
|
|
218
|
+
if (lifetime.signal.aborted) await resource.dispose();
|
|
219
|
+
}
|
|
220
|
+
activationCompleted = true;
|
|
221
|
+
if (disposed) runDeactivation();
|
|
222
|
+
});
|
|
223
|
+
return activation;
|
|
150
224
|
},
|
|
151
225
|
onRender(request, update) {
|
|
152
226
|
const renderContext = {
|
|
@@ -157,20 +231,12 @@ function createWidgetRuntime(activate, options = {}) {
|
|
|
157
231
|
Promise.resolve(handler(request, renderContext)).catch(() => {});
|
|
158
232
|
} catch {}
|
|
159
233
|
},
|
|
160
|
-
onDispose
|
|
161
|
-
lifetime.abort();
|
|
162
|
-
for (const registration of [...registrations]) try {
|
|
163
|
-
Promise.resolve(registration.dispose()).catch(() => {});
|
|
164
|
-
} catch {}
|
|
165
|
-
registrations.clear();
|
|
166
|
-
renderHandlers.clear();
|
|
167
|
-
context = void 0;
|
|
168
|
-
widgetDispose = void 0;
|
|
169
|
-
}
|
|
234
|
+
onDispose: dispose
|
|
170
235
|
}
|
|
171
236
|
});
|
|
172
237
|
return {
|
|
173
238
|
ready: () => client.ready(),
|
|
239
|
+
disposed: () => disposedPromise,
|
|
174
240
|
state: () => client.currentState,
|
|
175
241
|
dispose: () => client.dispose()
|
|
176
242
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Guest 内单次启动的资源所有权;异步启动迟到的资源也必须立即释放。 */
|
|
2
|
+
export interface LifetimeResource {
|
|
3
|
+
dispose(): void | PromiseLike<void>;
|
|
4
|
+
}
|
|
5
|
+
export interface LifetimeRegistration {
|
|
6
|
+
dispose(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare function createWidgetLifetime(onError: (error: unknown) => void): {
|
|
9
|
+
signal: AbortSignal;
|
|
10
|
+
add: (resource: LifetimeResource | (() => void | PromiseLike<void>)) => LifetimeRegistration;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
};
|
package/dist/manifest.d.ts
CHANGED
|
@@ -8,6 +8,6 @@ export interface CompileWidgetPluginManifestOptions {
|
|
|
8
8
|
* 将开发者维护的插件清单编译为宿主可加载的宿主清单。
|
|
9
9
|
*
|
|
10
10
|
* 这是构建工具的边界函数,不是类型提示入口:作者直接编辑带 `$schema` 的 JSON,
|
|
11
|
-
* SDK 版本从当前安装包读取,宿主入口默认落到 dist/widget.js,内部
|
|
11
|
+
* SDK 版本从当前安装包读取,宿主入口默认落到 dist/widget.js,内部 contribution 标识只在这里生成。
|
|
12
12
|
*/
|
|
13
13
|
export declare function compileWidgetPluginManifest(manifest: WidgetPluginManifest, options?: CompileWidgetPluginManifestOptions): WidgetManifest;
|
|
@@ -37,7 +37,7 @@ function isProtocolPayload(type, payload) {
|
|
|
37
37
|
const revision = property(payload, "revision");
|
|
38
38
|
const context = property(payload, "context");
|
|
39
39
|
const content = property(payload, "content");
|
|
40
|
-
return property(payload, "schemaVersion") === 1 && typeof property(payload, "requestId") === "string" && typeof property(payload, "instanceId") === "string" && typeof revision === "number" && Number.isSafeInteger(revision) && revision >= 1 && typeof property(payload, "
|
|
40
|
+
return property(payload, "schemaVersion") === 1 && typeof property(payload, "requestId") === "string" && typeof property(payload, "instanceId") === "string" && typeof revision === "number" && Number.isSafeInteger(revision) && revision >= 1 && typeof property(payload, "contribution") === "string" && typeof property(payload, "state") === "string" && isRecord(context) && isRecord(content);
|
|
41
41
|
}
|
|
42
42
|
case "event": return typeof property(payload, "type") === "string" && isRecord(property(payload, "payload"));
|
|
43
43
|
case "capability-result": return typeof property(payload, "requestId") === "string" && isRecord(property(payload, "result"));
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { CapabilityRequest, CapabilityResult, InitPayload, PresentationCapabilityResult, RenderRequest, WidgetError, WidgetLiveEvent } from "@dp-bohrium/widget-contracts";
|
|
2
|
-
import { type PresentationFocusDirection, type PresentationSurface, type WidgetCapabilityRequestOptions, type WidgetClientEvents, type WidgetClientState } from "./client.js";
|
|
1
|
+
import type { CapabilityRequest, CapabilityResult, InitPayload, PresentationCapabilityResult, RegionCapabilityResult, RenderRequest, WidgetError, WidgetLiveEvent } from "@dp-bohrium/widget-contracts";
|
|
2
|
+
import { type PresentationFocusDirection, type PresentationSurface, type RegionName, type WidgetCapabilityRequestOptions, type WidgetClientEvents, type WidgetClientState } from "./client.js";
|
|
3
3
|
import { type SubscribeWidgetEventsOptions, type WidgetEventSubscription, type WidgetToolHooks } from "./events.js";
|
|
4
4
|
import type { WidgetNetworkClient } from "./network.js";
|
|
5
|
+
import { type WidgetHostUserClient, type WidgetServiceClient } from "./service.js";
|
|
5
6
|
import { WIDGET_SDK_VERSION } from "./version.js";
|
|
6
7
|
export declare const WIDGET_BOOTSTRAP_READY: "widget-bootstrap-ready";
|
|
7
8
|
export declare const WIDGET_HOST_CONNECT: "widget-host-connect";
|
|
@@ -60,6 +61,8 @@ export declare function isWidgetHostConnectMessage(value: unknown): value is Wid
|
|
|
60
61
|
*/
|
|
61
62
|
export declare class WidgetRuntimeClient {
|
|
62
63
|
readonly network: WidgetNetworkClient;
|
|
64
|
+
readonly services: WidgetServiceClient;
|
|
65
|
+
readonly hostUser: WidgetHostUserClient;
|
|
63
66
|
private readonly runtimeWindow;
|
|
64
67
|
private readonly options;
|
|
65
68
|
private readonly events;
|
|
@@ -89,6 +92,8 @@ export declare class WidgetRuntimeClient {
|
|
|
89
92
|
present(surface: PresentationSurface, options?: WidgetCapabilityRequestOptions): Promise<PresentationCapabilityResult>;
|
|
90
93
|
closePresentation(options?: WidgetCapabilityRequestOptions): Promise<PresentationCapabilityResult>;
|
|
91
94
|
changePresentationSurface(surface: PresentationSurface, options?: WidgetCapabilityRequestOptions): Promise<PresentationCapabilityResult>;
|
|
95
|
+
acquireRegion(region: RegionName, options?: WidgetCapabilityRequestOptions): Promise<RegionCapabilityResult>;
|
|
96
|
+
releaseRegion(options?: WidgetCapabilityRequestOptions): Promise<RegionCapabilityResult>;
|
|
92
97
|
focusPresentationBoundary(direction: PresentationFocusDirection, options?: WidgetCapabilityRequestOptions): Promise<PresentationCapabilityResult>;
|
|
93
98
|
subscribeEvents(options: SubscribeWidgetEventsOptions): WidgetEventSubscription;
|
|
94
99
|
/** 订阅 Host 从当前 Workflow 流实时推送的全部聚合事件。 */
|
package/dist/runtime/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CapabilityRequest, CapabilityResult, GrantedCapability, InitPayload, PresentationCapabilityRequest, PresentationCapabilityResult, RenderRequest, WidgetError, WidgetLiveEvent } from "@dp-bohrium/widget-contracts";
|
|
1
|
+
import type { CapabilityRequest, CapabilityResult, GrantedCapability, InitPayload, PresentationCapabilityRequest, PresentationCapabilityResult, RegionCapabilityRequest, RegionCapabilityResult, RenderRequest, WidgetError, WidgetLiveEvent } from "@dp-bohrium/widget-contracts";
|
|
2
2
|
import { type WidgetNetworkClient } from "./network.js";
|
|
3
3
|
import type { WidgetTransport } from "./transport.js";
|
|
4
4
|
export type WidgetClientState = "created" | "waiting-ready" | "ready" | "disposed";
|
|
@@ -26,16 +26,22 @@ export interface WidgetCapabilityRequestOptions {
|
|
|
26
26
|
signal?: AbortSignal;
|
|
27
27
|
}
|
|
28
28
|
export declare const PRESENTATION_CONFIRMATION_TIMEOUT_MS = 60000;
|
|
29
|
+
export declare const REGION_CONFIRMATION_TIMEOUT_MS = 60000;
|
|
29
30
|
type SurfacePresentationInput = Extract<PresentationCapabilityRequest["input"], {
|
|
30
31
|
surface: unknown;
|
|
31
32
|
}>;
|
|
32
33
|
type FocusBoundaryPresentationInput = Extract<PresentationCapabilityRequest["input"], {
|
|
33
34
|
action: "focus-boundary";
|
|
34
35
|
}>;
|
|
36
|
+
export type RegionCapabilityInput = RegionCapabilityRequest["input"];
|
|
35
37
|
export type PresentationSurface = SurfacePresentationInput["surface"];
|
|
36
38
|
export type PresentationFocusDirection = FocusBoundaryPresentationInput["direction"];
|
|
39
|
+
export type RegionName = Extract<RegionCapabilityInput, {
|
|
40
|
+
region: unknown;
|
|
41
|
+
}>["region"];
|
|
37
42
|
type PresentationRequest = (request: CapabilityRequest, options: WidgetCapabilityRequestOptions) => Promise<CapabilityResult>;
|
|
38
43
|
export declare function hasOverlayPresentationGrant(capabilities: readonly GrantedCapability[]): boolean;
|
|
44
|
+
export declare function requestRegionCapability(request: PresentationRequest, capabilityRequest: RegionCapabilityRequest, options?: WidgetCapabilityRequestOptions): Promise<RegionCapabilityResult>;
|
|
39
45
|
export declare function requestPresentationCapability(request: PresentationRequest, capabilityRequest: PresentationCapabilityRequest, options?: WidgetCapabilityRequestOptions): Promise<PresentationCapabilityResult>;
|
|
40
46
|
export declare class WidgetClient {
|
|
41
47
|
readonly network: WidgetNetworkClient;
|
|
@@ -66,6 +72,8 @@ export declare class WidgetClient {
|
|
|
66
72
|
present(surface: PresentationSurface, options?: WidgetCapabilityRequestOptions): Promise<PresentationCapabilityResult>;
|
|
67
73
|
closePresentation(options?: WidgetCapabilityRequestOptions): Promise<PresentationCapabilityResult>;
|
|
68
74
|
changePresentationSurface(surface: PresentationSurface, options?: WidgetCapabilityRequestOptions): Promise<PresentationCapabilityResult>;
|
|
75
|
+
acquireRegion(region: RegionName, options?: WidgetCapabilityRequestOptions): Promise<RegionCapabilityResult>;
|
|
76
|
+
releaseRegion(options?: WidgetCapabilityRequestOptions): Promise<RegionCapabilityResult>;
|
|
69
77
|
focusPresentationBoundary(direction: PresentationFocusDirection, options?: WidgetCapabilityRequestOptions): Promise<PresentationCapabilityResult>;
|
|
70
78
|
dispose(): void;
|
|
71
79
|
private finishDispose;
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
export type { RegionCapabilityResult } from "@dp-bohrium/widget-contracts";
|
|
1
2
|
export type { CreateWidgetClientOptions, WidgetClientFactoryOptions, WidgetDisposeEvent, WidgetHostConnectMessage, WidgetLiveEventContext, WidgetLiveEventHandler, WidgetLiveEventName, WidgetRuntimeWindow, WidgetSize, } from "./bootstrap.js";
|
|
2
3
|
export { createWidgetClient, isWidgetHostConnectMessage, WIDGET_BOOTSTRAP_READY, WIDGET_HOST_CONNECT, WIDGET_SDK_VERSION, WidgetRuntimeClient, } from "./bootstrap.js";
|
|
3
|
-
export type { PresentationFocusDirection, PresentationSurface, WidgetCapabilityRequestOptions, WidgetClientEvents, WidgetClientOptions, WidgetClientState, } from "./client.js";
|
|
4
|
-
export { PRESENTATION_CONFIRMATION_TIMEOUT_MS, WidgetClient, } from "./client.js";
|
|
4
|
+
export type { PresentationFocusDirection, PresentationSurface, RegionName, WidgetCapabilityRequestOptions, WidgetClientEvents, WidgetClientOptions, WidgetClientState, } from "./client.js";
|
|
5
|
+
export { PRESENTATION_CONFIRMATION_TIMEOUT_MS, REGION_CONFIRMATION_TIMEOUT_MS, WidgetClient, } from "./client.js";
|
|
5
6
|
export type { SubscribeWidgetEventsOptions, WidgetEventBatchContext, WidgetEventSubscription, WidgetToolHooks, } from "./events.js";
|
|
6
7
|
export type { WidgetNetworkClient, WidgetNetworkFetchResult, } from "./network.js";
|
|
8
|
+
export type { HostUserEmail, HostUserEmailResult, HostUserProfile, HostUserProfileResult, WidgetHostUserClient, WidgetServiceClient, WidgetServiceInvokeInput, WidgetServiceInvokeResult, } from "./service.js";
|
|
7
9
|
export type { WidgetMessagePort, WidgetTransport } from "./transport.js";
|
|
8
10
|
export { isWidgetProtocolMessage, MessagePortTransport, } from "./transport.js";
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as isWidgetHostConnectMessage, c as
|
|
2
|
-
import { i as isWidgetProtocolMessage, r as MessagePortTransport } from "../protocol-endpoint-
|
|
3
|
-
export { MessagePortTransport, PRESENTATION_CONFIRMATION_TIMEOUT_MS, WIDGET_BOOTSTRAP_READY, WIDGET_HOST_CONNECT, WIDGET_SDK_VERSION, WidgetClient, WidgetRuntimeClient, createWidgetClient, isWidgetHostConnectMessage, isWidgetProtocolMessage };
|
|
1
|
+
import { a as isWidgetHostConnectMessage, c as WidgetClient, i as createWidgetClient, l as WIDGET_SDK_VERSION, n as WIDGET_HOST_CONNECT, o as PRESENTATION_CONFIRMATION_TIMEOUT_MS, r as WidgetRuntimeClient, s as REGION_CONFIRMATION_TIMEOUT_MS, t as WIDGET_BOOTSTRAP_READY } from "../runtime-Drr96Awx.js";
|
|
2
|
+
import { i as isWidgetProtocolMessage, r as MessagePortTransport } from "../protocol-endpoint-CDz-YDGT.js";
|
|
3
|
+
export { MessagePortTransport, PRESENTATION_CONFIRMATION_TIMEOUT_MS, REGION_CONFIRMATION_TIMEOUT_MS, WIDGET_BOOTSTRAP_READY, WIDGET_HOST_CONNECT, WIDGET_SDK_VERSION, WidgetClient, WidgetRuntimeClient, createWidgetClient, isWidgetHostConnectMessage, isWidgetProtocolMessage };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { CapabilityRequest, CapabilityResult, JsonValue } from "@dp-bohrium/widget-contracts";
|
|
2
|
+
import type { WidgetCapabilityRequestOptions, WidgetClientState } from "./client.js";
|
|
3
|
+
export type WidgetServiceInvokeInput = Extract<CapabilityRequest, {
|
|
4
|
+
operation: "service.invoke";
|
|
5
|
+
}>["input"];
|
|
6
|
+
export type WidgetServiceInvokeResult = Readonly<{
|
|
7
|
+
ok: true;
|
|
8
|
+
data: JsonValue;
|
|
9
|
+
}> | Extract<CapabilityResult, {
|
|
10
|
+
ok: false;
|
|
11
|
+
}>;
|
|
12
|
+
export interface WidgetServiceClient {
|
|
13
|
+
invoke(input: WidgetServiceInvokeInput, options?: WidgetCapabilityRequestOptions): Promise<WidgetServiceInvokeResult>;
|
|
14
|
+
}
|
|
15
|
+
export interface HostUserProfile {
|
|
16
|
+
readonly userId: string;
|
|
17
|
+
readonly displayName: string;
|
|
18
|
+
}
|
|
19
|
+
export interface HostUserEmail {
|
|
20
|
+
readonly email: string | null;
|
|
21
|
+
}
|
|
22
|
+
export type HostUserProfileResult = Readonly<{
|
|
23
|
+
ok: true;
|
|
24
|
+
data: HostUserProfile;
|
|
25
|
+
}> | Extract<CapabilityResult, {
|
|
26
|
+
ok: false;
|
|
27
|
+
}>;
|
|
28
|
+
export type HostUserEmailResult = Readonly<{
|
|
29
|
+
ok: true;
|
|
30
|
+
data: HostUserEmail;
|
|
31
|
+
}> | Extract<CapabilityResult, {
|
|
32
|
+
ok: false;
|
|
33
|
+
}>;
|
|
34
|
+
export interface WidgetHostUserClient {
|
|
35
|
+
readProfile(options?: WidgetCapabilityRequestOptions): Promise<HostUserProfileResult>;
|
|
36
|
+
readEmail(options?: WidgetCapabilityRequestOptions): Promise<HostUserEmailResult>;
|
|
37
|
+
}
|
|
38
|
+
interface Dependencies {
|
|
39
|
+
readonly state: () => WidgetClientState;
|
|
40
|
+
readonly grant: () => Extract<import("@dp-bohrium/widget-contracts").GrantedCapability, {
|
|
41
|
+
name: "service.invoke";
|
|
42
|
+
}> | undefined;
|
|
43
|
+
readonly request: (request: CapabilityRequest, options?: WidgetCapabilityRequestOptions) => Promise<CapabilityResult>;
|
|
44
|
+
}
|
|
45
|
+
export declare function createWidgetServiceClient(dependencies: Dependencies): WidgetServiceClient;
|
|
46
|
+
export declare function createWidgetHostUserClient(services: WidgetServiceClient): WidgetHostUserClient;
|
|
47
|
+
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { r as MessagePortTransport, t as ProtocolEndpoint } from "./protocol-endpoint-
|
|
1
|
+
import { r as MessagePortTransport, t as ProtocolEndpoint } from "./protocol-endpoint-CDz-YDGT.js";
|
|
2
2
|
//#endregion
|
|
3
3
|
//#region src/runtime/version.ts
|
|
4
4
|
/** 从当前安装的 SDK package.json 读取版本,插件作者不需要重复填写。 */
|
|
5
|
-
const WIDGET_SDK_VERSION = "0.
|
|
5
|
+
const WIDGET_SDK_VERSION = "0.3.0";
|
|
6
6
|
//#endregion
|
|
7
7
|
//#region src/runtime/network.ts
|
|
8
8
|
const failureReasons = /* @__PURE__ */ new Set([
|
|
@@ -82,6 +82,7 @@ function invalidResult() {
|
|
|
82
82
|
//#endregion
|
|
83
83
|
//#region src/runtime/client.ts
|
|
84
84
|
const PRESENTATION_CONFIRMATION_TIMEOUT_MS = 6e4;
|
|
85
|
+
const REGION_CONFIRMATION_TIMEOUT_MS = 6e4;
|
|
85
86
|
const presentationSurfaces = /* @__PURE__ */ new Set([
|
|
86
87
|
"workspace",
|
|
87
88
|
"floating",
|
|
@@ -231,6 +232,41 @@ function assertPresentationResult(input, result) {
|
|
|
231
232
|
}
|
|
232
233
|
if (!valid) throw new Error(`presentation protocol error: invalid success result for ${action}`);
|
|
233
234
|
}
|
|
235
|
+
function assertRegionResult(input, result) {
|
|
236
|
+
if (!isRecord$1(result) || !Object.hasOwn(result, "ok")) throw new Error(`region protocol error: invalid result for ${input.action}`);
|
|
237
|
+
if (property$1(result, "ok") === false) {
|
|
238
|
+
const message = property$1(result, "message");
|
|
239
|
+
if (hasExactKeys$1(result, [
|
|
240
|
+
"ok",
|
|
241
|
+
"reason",
|
|
242
|
+
"message",
|
|
243
|
+
"retryable"
|
|
244
|
+
]) && isCapabilityFailureReason(property$1(result, "reason")) && typeof message === "string" && message.length > 0 && typeof property$1(result, "retryable") === "boolean") return;
|
|
245
|
+
throw new Error(`region protocol error: invalid result for ${input.action}`);
|
|
246
|
+
}
|
|
247
|
+
if (!hasExactKeys$1(result, ["ok", "data"])) throw new Error(`region protocol error: invalid success result for ${input.action}`);
|
|
248
|
+
const data = property$1(result, "data");
|
|
249
|
+
const leaseId = isRecord$1(data) ? property$1(data, "leaseId") : void 0;
|
|
250
|
+
if (!(isRecord$1(data) && hasExactKeys$1(data, input.action === "acquire" ? [
|
|
251
|
+
"action",
|
|
252
|
+
"region",
|
|
253
|
+
"mode",
|
|
254
|
+
"leaseId"
|
|
255
|
+
] : [
|
|
256
|
+
"action",
|
|
257
|
+
"region",
|
|
258
|
+
"mode"
|
|
259
|
+
]) && property$1(data, "action") === input.action && property$1(data, "region") === "session.rightRail" && property$1(data, "mode") === "replace" && (input.action !== "acquire" || typeof leaseId === "string" && leaseId.length > 0))) throw new Error(`region protocol error: invalid success result for ${input.action}`);
|
|
260
|
+
}
|
|
261
|
+
function requestRegionCapability(request, capabilityRequest, options) {
|
|
262
|
+
return request(capabilityRequest, {
|
|
263
|
+
...options,
|
|
264
|
+
timeoutMs: options?.timeoutMs ?? 6e4
|
|
265
|
+
}).then((result) => {
|
|
266
|
+
assertRegionResult(capabilityRequest.input, result);
|
|
267
|
+
return result;
|
|
268
|
+
});
|
|
269
|
+
}
|
|
234
270
|
function requestPresentationCapability(request, capabilityRequest, options) {
|
|
235
271
|
return request(capabilityRequest, {
|
|
236
272
|
...options,
|
|
@@ -398,6 +434,21 @@ var WidgetClient = class {
|
|
|
398
434
|
}
|
|
399
435
|
}, options);
|
|
400
436
|
}
|
|
437
|
+
acquireRegion(region, options) {
|
|
438
|
+
return requestRegionCapability((request, requestOptions) => this.requestCapability(request, requestOptions), {
|
|
439
|
+
operation: "ui.region",
|
|
440
|
+
input: {
|
|
441
|
+
action: "acquire",
|
|
442
|
+
region
|
|
443
|
+
}
|
|
444
|
+
}, options);
|
|
445
|
+
}
|
|
446
|
+
releaseRegion(options) {
|
|
447
|
+
return requestRegionCapability((request, requestOptions) => this.requestCapability(request, requestOptions), {
|
|
448
|
+
operation: "ui.region",
|
|
449
|
+
input: { action: "release" }
|
|
450
|
+
}, options);
|
|
451
|
+
}
|
|
401
452
|
focusPresentationBoundary(direction, options) {
|
|
402
453
|
if (!this.overlayPresentationGranted) return Promise.reject(/* @__PURE__ */ new Error("Overlay presentation is not granted for focus cooperation"));
|
|
403
454
|
return requestPresentationCapability((request, requestOptions) => this.requestCapability(request, requestOptions), {
|
|
@@ -795,6 +846,63 @@ function createPresentationKeyboardAdapter(options) {
|
|
|
795
846
|
} };
|
|
796
847
|
}
|
|
797
848
|
//#endregion
|
|
849
|
+
//#region src/runtime/service.ts
|
|
850
|
+
function createWidgetServiceClient(dependencies) {
|
|
851
|
+
return Object.freeze({ invoke(input, options) {
|
|
852
|
+
const state = dependencies.state();
|
|
853
|
+
if (state === "disposed") return Promise.reject(/* @__PURE__ */ new Error("client is disposed"));
|
|
854
|
+
if (state !== "ready") return Promise.reject(/* @__PURE__ */ new Error("Widget service is not ready"));
|
|
855
|
+
if (!(dependencies.grant()?.services.find((entry) => entry.service === input.service))?.actions.includes(input.action)) return Promise.reject(/* @__PURE__ */ new Error("Widget service is not granted"));
|
|
856
|
+
return dependencies.request({
|
|
857
|
+
operation: "service.invoke",
|
|
858
|
+
input
|
|
859
|
+
}, options).then(validateServiceResult);
|
|
860
|
+
} });
|
|
861
|
+
}
|
|
862
|
+
function createWidgetHostUserClient(services) {
|
|
863
|
+
return Object.freeze({
|
|
864
|
+
readProfile(options) {
|
|
865
|
+
return services.invoke({
|
|
866
|
+
service: "host.user",
|
|
867
|
+
action: "profile.read",
|
|
868
|
+
payload: null
|
|
869
|
+
}, options).then((result) => validateHostUserResult(result, isHostUserProfile));
|
|
870
|
+
},
|
|
871
|
+
readEmail(options) {
|
|
872
|
+
return services.invoke({
|
|
873
|
+
service: "host.user",
|
|
874
|
+
action: "email.read",
|
|
875
|
+
payload: null
|
|
876
|
+
}, options).then((result) => validateHostUserResult(result, isHostUserEmail));
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
function validateHostUserResult(result, validate) {
|
|
881
|
+
if (result.ok === false) return result;
|
|
882
|
+
if (!validate(result.data)) throw new Error("service protocol error: invalid Host user result");
|
|
883
|
+
return {
|
|
884
|
+
ok: true,
|
|
885
|
+
data: result.data
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
function isHostUserProfile(value) {
|
|
889
|
+
return isExactRecord(value, ["userId", "displayName"]) && typeof value["userId"] === "string" && typeof value["displayName"] === "string";
|
|
890
|
+
}
|
|
891
|
+
function isHostUserEmail(value) {
|
|
892
|
+
return isExactRecord(value, ["email"]) && (typeof value["email"] === "string" || value["email"] === null);
|
|
893
|
+
}
|
|
894
|
+
function isExactRecord(value, keys) {
|
|
895
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
896
|
+
const actualKeys = Object.keys(value);
|
|
897
|
+
return actualKeys.length === keys.length && keys.every((key) => actualKeys.includes(key));
|
|
898
|
+
}
|
|
899
|
+
function validateServiceResult(result) {
|
|
900
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) throw new Error("service protocol error: invalid result");
|
|
901
|
+
if (result.ok === false) return result;
|
|
902
|
+
if (result.ok !== true || !("data" in result)) throw new Error("service protocol error: invalid result");
|
|
903
|
+
return result;
|
|
904
|
+
}
|
|
905
|
+
//#endregion
|
|
798
906
|
//#region src/runtime/bootstrap.ts
|
|
799
907
|
const WIDGET_BOOTSTRAP_READY = "widget-bootstrap-ready";
|
|
800
908
|
const WIDGET_HOST_CONNECT = "widget-host-connect";
|
|
@@ -894,6 +1002,8 @@ function cloneGrantedCapabilities(capabilities) {
|
|
|
894
1002
|
*/
|
|
895
1003
|
var WidgetRuntimeClient = class {
|
|
896
1004
|
network;
|
|
1005
|
+
services;
|
|
1006
|
+
hostUser;
|
|
897
1007
|
runtimeWindow;
|
|
898
1008
|
options;
|
|
899
1009
|
events;
|
|
@@ -944,6 +1054,12 @@ var WidgetRuntimeClient = class {
|
|
|
944
1054
|
return this.requireClient().network.fetch(input, requestOptions);
|
|
945
1055
|
} };
|
|
946
1056
|
this.network = Object.freeze(network);
|
|
1057
|
+
this.services = createWidgetServiceClient({
|
|
1058
|
+
state: () => this.currentState,
|
|
1059
|
+
grant: () => this.capabilities.find((capability) => capability.name === "service.invoke"),
|
|
1060
|
+
request: (request, requestOptions) => this.requestCapability(request, requestOptions)
|
|
1061
|
+
});
|
|
1062
|
+
this.hostUser = createWidgetHostUserClient(this.services);
|
|
947
1063
|
this.readyPromise = new Promise((resolve, reject) => {
|
|
948
1064
|
this.resolveReady = resolve;
|
|
949
1065
|
this.rejectReady = reject;
|
|
@@ -1010,6 +1126,21 @@ var WidgetRuntimeClient = class {
|
|
|
1010
1126
|
}
|
|
1011
1127
|
}, options);
|
|
1012
1128
|
}
|
|
1129
|
+
acquireRegion(region, options) {
|
|
1130
|
+
return requestRegionCapability((request, requestOptions) => this.requestCapability(request, requestOptions), {
|
|
1131
|
+
operation: "ui.region",
|
|
1132
|
+
input: {
|
|
1133
|
+
action: "acquire",
|
|
1134
|
+
region
|
|
1135
|
+
}
|
|
1136
|
+
}, options);
|
|
1137
|
+
}
|
|
1138
|
+
releaseRegion(options) {
|
|
1139
|
+
return requestRegionCapability((request, requestOptions) => this.requestCapability(request, requestOptions), {
|
|
1140
|
+
operation: "ui.region",
|
|
1141
|
+
input: { action: "release" }
|
|
1142
|
+
}, options);
|
|
1143
|
+
}
|
|
1013
1144
|
focusPresentationBoundary(direction, options) {
|
|
1014
1145
|
return this.readyPromise.then(() => this.requireClient().focusPresentationBoundary(direction, options));
|
|
1015
1146
|
}
|
|
@@ -1162,4 +1293,4 @@ function createWidgetClient(options) {
|
|
|
1162
1293
|
return new WidgetRuntimeClient(options);
|
|
1163
1294
|
}
|
|
1164
1295
|
//#endregion
|
|
1165
|
-
export { isWidgetHostConnectMessage as a,
|
|
1296
|
+
export { isWidgetHostConnectMessage as a, WidgetClient as c, createWidgetClient as i, WIDGET_SDK_VERSION as l, WIDGET_HOST_CONNECT as n, PRESENTATION_CONFIRMATION_TIMEOUT_MS as o, WidgetRuntimeClient as r, REGION_CONFIRMATION_TIMEOUT_MS as s, WIDGET_BOOTSTRAP_READY as t };
|
package/dist/widget.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { CapabilityRequest, CapabilityResult, FileDownloadResult, FileReference, GrantedCapability, RenderRequest, TaskObservationBatch, Theme, UiStateResult, UiStateWriteResult, WidgetEventName, WidgetLiveEvent } from "@dp-bohrium/widget-contracts";
|
|
2
|
-
import { type CreateWidgetClientOptions, type WidgetClientFactoryOptions, type WidgetClientState, type WidgetLiveEventContext, type WidgetLiveEventHandler } from "./runtime/index.js";
|
|
1
|
+
import type { CapabilityRequest, CapabilityResult, FileDownloadResult, FileReference, GrantedCapability, RegionCapabilityResult, RenderRequest, TaskObservationBatch, Theme, UiStateResult, UiStateWriteResult, WidgetEventName, WidgetLiveEvent } from "@dp-bohrium/widget-contracts";
|
|
2
|
+
import { type CreateWidgetClientOptions, type RegionName, type WidgetClientFactoryOptions, type WidgetClientState, type WidgetLiveEventContext, type WidgetLiveEventHandler } from "./runtime/index.js";
|
|
3
3
|
import type { WidgetNetworkClient } from "./runtime/network.js";
|
|
4
|
+
import type { WidgetHostUserClient, WidgetServiceClient } from "./runtime/service.js";
|
|
4
5
|
export interface WidgetDisposable {
|
|
5
6
|
dispose(): void | Promise<void>;
|
|
6
7
|
}
|
|
@@ -81,8 +82,19 @@ export type UiStateWriteCapabilityResult = {
|
|
|
81
82
|
} | Extract<CapabilityResult, {
|
|
82
83
|
readonly ok: false;
|
|
83
84
|
}>;
|
|
85
|
+
export interface WidgetUiRegionApi {
|
|
86
|
+
acquire(region: RegionName, options?: {
|
|
87
|
+
readonly timeoutMs?: number;
|
|
88
|
+
readonly signal?: AbortSignal;
|
|
89
|
+
}): Promise<RegionCapabilityResult>;
|
|
90
|
+
release(options?: {
|
|
91
|
+
readonly timeoutMs?: number;
|
|
92
|
+
readonly signal?: AbortSignal;
|
|
93
|
+
}): Promise<RegionCapabilityResult>;
|
|
94
|
+
}
|
|
84
95
|
export interface WidgetUiApi {
|
|
85
96
|
readonly state: WidgetUiStateApi;
|
|
97
|
+
readonly region: WidgetUiRegionApi;
|
|
86
98
|
present(surface: "workspace" | "floating" | "overlay", options?: {
|
|
87
99
|
readonly timeoutMs?: number;
|
|
88
100
|
readonly signal?: AbortSignal;
|
|
@@ -106,6 +118,10 @@ export interface WidgetContext {
|
|
|
106
118
|
readonly files: WidgetFileApi;
|
|
107
119
|
readonly tasks: WidgetTaskApi;
|
|
108
120
|
readonly network: WidgetNetworkClient;
|
|
121
|
+
/** 低层 service.invoke 入口;保留给自定义 Host service。 */
|
|
122
|
+
readonly services: WidgetServiceClient;
|
|
123
|
+
/** 经过 Host 独立授权的公共用户资料入口。 */
|
|
124
|
+
readonly hostUser: WidgetHostUserClient;
|
|
109
125
|
readonly ui: WidgetUiApi;
|
|
110
126
|
readonly events: WidgetEventApi;
|
|
111
127
|
readonly subscriptions: {
|
|
@@ -119,6 +135,8 @@ export interface WidgetHostOptions extends Omit<CreateWidgetClientOptions, "widg
|
|
|
119
135
|
readonly events?: Pick<NonNullable<WidgetClientFactoryOptions["events"]>, "onError">;
|
|
120
136
|
/** Widget Host 启动失败时的统一处理入口。 */
|
|
121
137
|
readonly onStartupError?: (error: Error) => void;
|
|
138
|
+
/** 停用或资源释放错误,诊断失败不会影响其它资源释放。 */
|
|
139
|
+
readonly onCleanupError?: (error: Error) => void;
|
|
122
140
|
}
|
|
123
141
|
export interface WidgetModule {
|
|
124
142
|
activate: WidgetActivation;
|
|
@@ -127,6 +145,8 @@ export interface WidgetModule {
|
|
|
127
145
|
export interface WidgetHandle {
|
|
128
146
|
/** 供测试、开发工具和 Host 编排等待激活完成。 */
|
|
129
147
|
readonly whenReady: Promise<void>;
|
|
148
|
+
/** Host 释放 Guest 后完成;包含已完成激活插件的异步 deactivate 清理。 */
|
|
149
|
+
readonly whenDisposed: Promise<void>;
|
|
130
150
|
readonly state: () => WidgetClientState;
|
|
131
151
|
dispose(): void;
|
|
132
152
|
}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @dp-bohrium/widget-sdk
|
|
2
|
+
|
|
3
|
+
> Agent 读取索引:本包提供 Widget 的 `activate(context)` 运行时、Bridge 生命周期、能力
|
|
4
|
+
> API、测试 Host 和低层 Runtime 入口。
|
|
5
|
+
|
|
6
|
+
## 先读
|
|
7
|
+
|
|
8
|
+
- [AGENTS.md](./AGENTS.md):安装、入口选择、错误恢复和安全边界。
|
|
9
|
+
- [README.md](./README.md):快速开始、Manifest、能力和生命周期说明。
|
|
10
|
+
- [package.json](./package.json):精确版本、exports 和 Node 要求。
|
|
11
|
+
|
|
12
|
+
## 选择入口
|
|
13
|
+
|
|
14
|
+
- 生产 Widget:`@dp-bohrium/widget-sdk`
|
|
15
|
+
- 单元测试:`@dp-bohrium/widget-sdk/testing`
|
|
16
|
+
- 协议级集成:`@dp-bohrium/widget-sdk/runtime`
|
|
17
|
+
- 配套类型和 Schema:`@dp-bohrium/widget-contracts`
|
|
18
|
+
|
|
19
|
+
## 使用原则
|
|
20
|
+
|
|
21
|
+
- 先在 Manifest 声明 capability,再等待 Host grant。
|
|
22
|
+
- 用 `WidgetError` 的 `code/phase/retryable/correlationId` 诊断启动、渲染和能力失败。
|
|
23
|
+
- `ok=false` 的能力结果是可处理状态,不要直接重试或绕过 Host。
|
|
24
|
+
- SDK 本地通过不等于 iframe 已真实渲染、Bundle 已发布或 Agent 已绑定。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dp-bohrium/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Bohrium Widget 开发者 SDK:在 Widget Bundle 内与 Host 建立 Bridge,并暴露生命周期与能力协议。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
"files": [
|
|
39
39
|
"dist",
|
|
40
40
|
"README.md",
|
|
41
|
+
"AGENTS.md",
|
|
42
|
+
"llms.txt",
|
|
41
43
|
"CHANGELOG.md",
|
|
42
44
|
"LICENSE"
|
|
43
45
|
],
|
|
@@ -46,7 +48,7 @@
|
|
|
46
48
|
"access": "public"
|
|
47
49
|
},
|
|
48
50
|
"dependencies": {
|
|
49
|
-
"@dp-bohrium/widget-contracts": "0.
|
|
51
|
+
"@dp-bohrium/widget-contracts": "0.3.0"
|
|
50
52
|
},
|
|
51
53
|
"devDependencies": {
|
|
52
54
|
"@biomejs/biome": "^2.2.0",
|