@dp-bohrium/widget-sdk 0.0.6 → 0.1.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/CHANGELOG.md +100 -0
- package/README.md +178 -151
- package/dist/compatibility/testing.js +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +182 -2
- package/dist/manifest.d.ts +13 -0
- package/dist/{protocol-endpoint-ZmZWaN92.js → protocol-endpoint-I78q3jVT.js} +1 -1
- package/dist/runtime/bootstrap.d.ts +10 -8
- package/dist/runtime/client.d.ts +3 -3
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +3 -3
- package/dist/runtime/version.d.ts +2 -0
- package/dist/{runtime-Bva0ricl.js → runtime-B5kilTcQ.js} +18 -11
- package/dist/widget.d.ts +137 -0
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# `@dp-bohrium/widget-sdk` 变更记录
|
|
2
|
+
|
|
3
|
+
本文件只记录影响使用者的变更:运行时 API、事件订阅、Manifest 要求。
|
|
4
|
+
|
|
5
|
+
## 0.1.0
|
|
6
|
+
|
|
7
|
+
### 新增
|
|
8
|
+
|
|
9
|
+
**1. 暴露受控文件下载、长任务观察和 UI 状态 API**
|
|
10
|
+
|
|
11
|
+
- 新增 `context.files.download`、`context.tasks.watch` 和 `context.ui.state.read/write`,均受 Host grant、预算和作用域约束。
|
|
12
|
+
|
|
13
|
+
**2. 暴露结构化 Tool Result envelope 类型**
|
|
14
|
+
|
|
15
|
+
- SDK 类型和运行时辅助函数支持带状态、指标、产物引用、provenance、warning 与 missing 的结构化 Tool Result。
|
|
16
|
+
|
|
17
|
+
说明:0.0.9 曾在 Git 中准备,但未创建发布 Tag 或进入 npm;本节变更作为新的兼容性边界随 0.1.0 发布。
|
|
18
|
+
|
|
19
|
+
## 0.0.8
|
|
20
|
+
|
|
21
|
+
### 修复
|
|
22
|
+
|
|
23
|
+
- 与 `@dp-bohrium/widget-contracts` 0.0.8 保持版本一致。运行时 API、事件订阅与 Manifest 要求均无变化,从 0.0.7 升级无需改代码。
|
|
24
|
+
|
|
25
|
+
说明:0.0.8 未单独发布;本节变更最终随 0.1.0 一并发布。
|
|
26
|
+
|
|
27
|
+
## 0.0.7
|
|
28
|
+
|
|
29
|
+
### 破坏性变更
|
|
30
|
+
|
|
31
|
+
**1. onEvent 的事件载荷从 PublicWidgetEvent 变为 WidgetLiveEvent**
|
|
32
|
+
|
|
33
|
+
新载荷只有 `type` 和 `payload` 两个字段。`schemaVersion`、`id`、`occurredAt`、`sessionId`、`workflowId`、`topic` 都不再下发,读这些字段的代码会失效。
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// 0.0.6 及以前
|
|
37
|
+
client.onEvent((event) => {
|
|
38
|
+
if (event.topic === "tools.lifecycle" && event.type === "tool-call") {
|
|
39
|
+
render(event.payload);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// 0.0.7 —— 按 type 分派,不再有 topic
|
|
44
|
+
client.onEvent((event) => {
|
|
45
|
+
if (event.type === "tool-call") {
|
|
46
|
+
render(event.payload);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
需要 `sessionId` / `workflowId` 的,从 `init` 阶段拿到的上下文取,不要再从事件对象上读。
|
|
52
|
+
|
|
53
|
+
**2. Manifest 的 widgetConfig.behaviors 移除,改为 widgetConfig.activationEvents**
|
|
54
|
+
|
|
55
|
+
从对象数组改成字符串数组,保留旧字段的 Manifest 上传会被拒。完整迁移写法见 `@dp-bohrium/widget-contracts` 的变更记录。原先靠 `behaviors` 自动执行 `panel.open` 等 Host 动作的能力没有等价替代。
|
|
56
|
+
|
|
57
|
+
### 新增
|
|
58
|
+
|
|
59
|
+
- onEvent 新增按事件名订阅的重载,回调直接收到该类型的事件,不必再判断 event.type。
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
client.onEvent("tool-call", (event) => render(event.payload));
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
无参数形式仍然接收全部事件,行为不变。
|
|
66
|
+
|
|
67
|
+
## 0.0.6
|
|
68
|
+
|
|
69
|
+
### 新增
|
|
70
|
+
|
|
71
|
+
- 支持接收 Host 通过 MessageChannel 主动推送的实时事件(`onEvent`),无需 HTTP 轮询。
|
|
72
|
+
|
|
73
|
+
## 0.0.5
|
|
74
|
+
|
|
75
|
+
### 新增
|
|
76
|
+
|
|
77
|
+
- 支持消费事件驱动的 Widget 行为。
|
|
78
|
+
|
|
79
|
+
## 0.0.4
|
|
80
|
+
|
|
81
|
+
### 新增
|
|
82
|
+
|
|
83
|
+
- Manifest 支持声明式地在 Session 启动时打开 Panel。
|
|
84
|
+
|
|
85
|
+
## 0.0.3
|
|
86
|
+
|
|
87
|
+
- 仅发布流程调整,无对外变更。
|
|
88
|
+
|
|
89
|
+
## 0.0.2
|
|
90
|
+
|
|
91
|
+
### 破坏性变更
|
|
92
|
+
|
|
93
|
+
- 包迁移至 `@dp-bohrium` scope,旧 scope 不再发布新版本。
|
|
94
|
+
|
|
95
|
+
## 0.0.1
|
|
96
|
+
|
|
97
|
+
首个公开版本。
|
|
98
|
+
|
|
99
|
+
- MessagePort Transport、Widget 生命周期、Host Bridge 能力。
|
|
100
|
+
- Host 代理的网络请求能力。
|
package/README.md
CHANGED
|
@@ -1,191 +1,218 @@
|
|
|
1
1
|
# @dp-bohrium/widget-sdk
|
|
2
2
|
|
|
3
|
-
Widget 开发者 SDK
|
|
4
|
-
宣告 Bundle、接收转移过来的 `MessagePort
|
|
5
|
-
|
|
3
|
+
Widget 开发者 SDK。普通作者只实现 `activate(context)` 和可选的 `deactivate()`;CLI
|
|
4
|
+
会生成 Widget Host 入口,负责向 Host 宣告 Bundle、接收转移过来的 `MessagePort`,并暴露共享的
|
|
5
|
+
生命周期与能力协议。作者无需直接处理 `window.postMessage`,也无需手动启动插件。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
激活事件目录、`context.events` 与低层 `topic` 的边界见
|
|
8
|
+
[Widget 激活事件与事件查询](../../docs/design/widget-activation-events-zh.md)。
|
|
9
|
+
|
|
10
|
+
## 环境要求
|
|
11
|
+
|
|
12
|
+
| 项 | 要求 |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| Node.js | ≥ 20(仅构建期;产物运行在浏览器 iframe 内) |
|
|
15
|
+
| 源码格式 | 纯 ESM,必须经打包器处理后交付 |
|
|
16
|
+
| 运行环境 | 必须由 Bohrium Host 加载,脱离 Host 无法完成握手 |
|
|
17
|
+
| 产物格式 | Host iframe 为 `sandbox="allow-scripts"` 且无 `allow-same-origin` 时,打成 classic script / IIFE(隔离源下 `type="module"` 需要 CORS) |
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
20
|
+
|
|
21
|
+
写 Widget 装本包。Host 与控制面只消费协议类型,装
|
|
22
|
+
[`@dp-bohrium/widget-contracts`](https://www.npmjs.com/package/@dp-bohrium/widget-contracts)
|
|
23
|
+
即可。
|
|
8
24
|
|
|
9
25
|
```bash
|
|
26
|
+
npm install @dp-bohrium/widget-sdk
|
|
10
27
|
pnpm add @dp-bohrium/widget-sdk
|
|
28
|
+
yarn add @dp-bohrium/widget-sdk
|
|
11
29
|
```
|
|
12
30
|
|
|
13
|
-
|
|
31
|
+
需要在 TypeScript 中直接声明或在编辑器中解析 Manifest Schema 时,再把
|
|
32
|
+
`@dp-bohrium/widget-contracts` 作为开发依赖安装;它是独立的类型/Schema 包,不需要通过
|
|
33
|
+
`defineManifest()` 工厂获得提示。
|
|
14
34
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const widget = createWidgetClient({
|
|
19
|
-
// 填入已安装的 @dp-bohrium/widget-sdk 精确版本(npm view / package.json)
|
|
20
|
-
widgetSdkVersion: "REPLACE_WITH_INSTALLED_SDK_VERSION",
|
|
21
|
-
async onInit(payload) {
|
|
22
|
-
// 使用 Host 下发的 locale、theme 与 capabilities 初始化。
|
|
23
|
-
console.log(payload.locale);
|
|
24
|
-
await Promise.resolve(); // 在此加载应用状态。
|
|
25
|
-
},
|
|
26
|
-
onRender(request) {
|
|
27
|
-
// 按所选 Slot 渲染完整快照。
|
|
28
|
-
console.log(request.slot);
|
|
29
|
-
},
|
|
30
|
-
onDispose({ reason, deadlineMs }) {
|
|
31
|
-
// 释放本地定时器、订阅与 worker。
|
|
32
|
-
console.log(reason, deadlineMs);
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
+
在自己的 `package.json` 里钉死精确 SemVer,不要用 `^` 或裸 `latest`。SDK 运行时会从已安装
|
|
36
|
+
包自身读取版本,插件源码和 Manifest 不需要再次填写 SDK 版本。
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
widget.resize({ width: 600, height: 400 });
|
|
38
|
-
```
|
|
38
|
+
## 快速开始
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
```ts
|
|
41
|
+
import type { WidgetContext } from "@dp-bohrium/widget-sdk";
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
export function activate(context: WidgetContext) {
|
|
44
|
+
context.render((request, renderContext) => {
|
|
45
|
+
renderSlot(request.slot, request.content, renderContext.phase);
|
|
46
|
+
});
|
|
47
|
+
context.events.on("tool-call-result", (event) => showToolResult(event.payload));
|
|
48
|
+
}
|
|
49
|
+
```
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
CLI 生成的 `src/widget-host.ts` 会调用平台入口 `runWidgetHost(widget)`。
|
|
52
|
+
这与 VS Code 的激活入口一致:开发者实现 `activate(context)`,可选实现 `deactivate()`;
|
|
53
|
+
Widget Host/SDK 负责加载、等待初始化、报告启动失败和执行清理。普通插件代码不需要调用
|
|
54
|
+
`ready()`,也不应该写 `plugin.ready().catch(() => {})`。`whenReady` 只保留给测试和高级宿主编排。
|
|
55
|
+
|
|
56
|
+
`context.signal` 会在 Host 停用插件或 Bridge 断开时 `abort`。`context.render` 和 `context.events`
|
|
57
|
+
会自动登记到当前插件生命周期;`subscriptions` 只用于
|
|
58
|
+
第三方计时器、观察器或其它需要在停用时释放的资源。
|
|
59
|
+
|
|
60
|
+
三条时序约定:
|
|
61
|
+
|
|
62
|
+
- `ready()` 只在 `activate(context)` 返回(含其 Promise settle)之后才发给 Host,
|
|
63
|
+
`handshakeTimeoutMs` 同时约束这一步;
|
|
64
|
+
- 初始化失败时,SDK 先报结构化错误,再调用插件资源清理;
|
|
65
|
+
- `context.render` 首帧和更新都会收到完整快照,通过 `renderContext.phase` 区分。
|
|
66
|
+
|
|
67
|
+
## 从 Manifest 到插件运行
|
|
68
|
+
|
|
69
|
+
Manifest 是插件的静态声明,SDK 代码是隔离 iframe 内的运行时实现,Host 负责二者之间的生命周期编排。一个插件可以这样声明:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"$schema": "@dp-bohrium/widget-contracts/schemas/widget-plugin-manifest.schema.json",
|
|
74
|
+
"name": "research",
|
|
75
|
+
"version": "1.0.0",
|
|
76
|
+
"entry": "src/widget.ts",
|
|
77
|
+
"displayName": "研究面板",
|
|
78
|
+
"description": "展示研究工具的结构化结果",
|
|
79
|
+
"activationEvents": ["session.start", "tool-call-result:ScholarSearch", "ui-tool-opened"],
|
|
80
|
+
"contributes": {
|
|
81
|
+
"widget": { "type": "panel", "id": "research", "surfaces": ["workspace", "floating"] }
|
|
54
82
|
},
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
consume(result.data.status, result.data.headers, result.data.body);
|
|
60
|
-
} else {
|
|
61
|
-
showNetworkFailure(result.reason, result.retryable);
|
|
83
|
+
"capabilities": {
|
|
84
|
+
"files": { "read": true },
|
|
85
|
+
"events": { "topics": ["tool-call-result", "workspace-file-opened"] }
|
|
86
|
+
}
|
|
62
87
|
}
|
|
63
88
|
```
|
|
64
89
|
|
|
65
|
-
|
|
66
|
-
Host 决定。在未就绪、已 dispose,或当前没有 `network.fetch` grant 时,调用会在
|
|
67
|
-
本地拒绝。中止与超时复用既有 MessageChannel 能力生命周期。
|
|
68
|
-
`requestCapability` 仍可用于协议级集成,但 Widget 应用代码应优先使用
|
|
69
|
-
`widget.network.fetch`。
|
|
90
|
+
`activationEvents` 只决定 Host 何时创建插件运行时;它不授予事件读取权限,也不负责打开思考过程或某个面板。插件激活后通过 `context.events` 和 `context.render` 注册监听与渲染器。Host 会对每个事件和能力请求做白名单投影与授权,插件收到的事件可能是有限回放,也可能是实时事件。
|
|
70
91
|
|
|
71
|
-
|
|
92
|
+
实现建议:事件处理器保持幂等,按 `toolCallId` 或 `requestId` 去重;不要从 iframe URL、DOM 或宿主绝对路径推断内部数据;当能力未授予时显示结构化空态并继续保留可渲染的主界面。声明但没有实际使用的能力会被 Host 记录为诊断信息,未声明能力的调用会被拒绝并结束当前 Bridge 会话。
|
|
72
93
|
|
|
73
|
-
|
|
74
|
-
普通 `createWidgetClient()` 结果与低层 `WidgetClient` 上均可使用相同方法:
|
|
94
|
+
### 统一插件入口
|
|
75
95
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const result = await widget.present("floating", {
|
|
81
|
-
timeoutMs: PRESENTATION_CONFIRMATION_TIMEOUT_MS,
|
|
82
|
-
signal: presentationAbort.signal,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
if (!result.ok) {
|
|
86
|
-
showPresentationDenial(result.reason, result.message);
|
|
87
|
-
} else if ("surface" in result.data) {
|
|
88
|
-
// Host 可能回报不同的实际表面,例如紧凑模式下从 floating 降级到 overlay
|
|
89
|
-
// 或 workspace。
|
|
90
|
-
showCurrentSurface(result.data.surface);
|
|
91
|
-
}
|
|
96
|
+
所有插件类型都使用同一个 `activate(context)` 约定。面板、工具结果和文件预览由
|
|
97
|
+
Manifest 的 `contributes.widget` 决定;运行时代码只认识统一的 `context.events`、
|
|
98
|
+
`context.render`、`context.capabilities` 和 `context.ui`。首帧和后续快照通过
|
|
99
|
+
`renderContext.phase` 区分,不需要维护两套回调。
|
|
92
100
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
Manifest 的编辑器提示来自 `@dp-bohrium/widget-contracts` 发布的 JSON Schema 和
|
|
102
|
+
TypeScript 类型,不依赖某个 `defineManifest` 工厂。构建工具调用
|
|
103
|
+
`compileWidgetPluginManifest`,自动补齐 `schemaVersion`、`hostApiVersion`、SDK 版本和
|
|
104
|
+
宿主加载字段;它会直接读取 Manifest 内的 `samples`,只有构建器从别处生成样例时
|
|
105
|
+
才需要传入第二个参数。`createWidgetClient` 是协议级基础实现,不属于 Widget 作者 API;当前根入口只提供统一 Widget API,协议级代码应从
|
|
106
|
+
`./runtime` 显式引入。
|
|
97
107
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
`
|
|
101
|
-
`
|
|
102
|
-
有自定义焦点管理器的框架可直接调用类型化 helper。
|
|
108
|
+
SDK 发布包会生成 `dist/index.d.ts` 与对应的 declaration map,并通过 `package.json` 的
|
|
109
|
+
`types`/`exports` 暴露。插件安装 `@dp-bohrium/widget-sdk` 后,编辑器会直接获得
|
|
110
|
+
`WidgetContext`、事件名、能力请求、渲染请求和生命周期返回值的类型提示;Manifest
|
|
111
|
+
编辑器提示由 `@dp-bohrium/widget-contracts` 的 JSON Schema 和声明文件提供。
|
|
103
112
|
|
|
104
|
-
|
|
105
|
-
策略拒绝或用户拒绝会以 `{ ok: false, reason, message, retryable }` 正常兑现;
|
|
106
|
-
客户端超时、中止、dispose 或畸形成功响应会 reject Promise。浏览器 resize、
|
|
107
|
-
可见性、焦点与指针事件都不会被当成确认。
|
|
113
|
+
## 包入口
|
|
108
114
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
| 入口 | 内容 | 用途 |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| `.` | `runWidgetHost`、`compileWidgetPluginManifest` 和全部公开类型 | 生成的生产 Bundle |
|
|
118
|
+
| `./testing` | `MemoryTransport`、`MockWidgetHost` | 单元测试 |
|
|
119
|
+
| `./runtime` | 低层 `WidgetClient`、transport 与 bootstrap 常量 | 协议级集成 |
|
|
120
|
+
| `./package.json` | 已安装包的元数据 | SDK 和构建工具自动读取版本,作者无需复制版本字符串 |
|
|
114
121
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
`handshakeTimeoutMs` 也会在 Host 连接建立后约束该初始化步骤。
|
|
122
|
+
根入口只导出插件运行时和 Manifest 编译所需的少量值,其余均为类型。低层
|
|
123
|
+
`MessagePortTransport` 与 bootstrap 消息不属于普通 Widget API。
|
|
118
124
|
|
|
119
|
-
|
|
125
|
+
`runWidgetHost` 是生成的 `widget-host.ts` 使用的 Widget Host 启动边界;
|
|
126
|
+
插件作者只维护 `widget.ts` 中的 `activate/deactivate`。
|
|
120
127
|
|
|
121
|
-
|
|
122
|
-
`{ timeoutMs, signal }`,应用生命周期钩子可取消在途读取、避免迟到结果。高级集成与
|
|
123
|
-
协议级测试可从 `@dp-bohrium/widget-sdk/runtime` 使用。
|
|
128
|
+
## 能力
|
|
124
129
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
普通 Widget API。
|
|
130
|
+
每项能力都要在 Manifest 的 `capabilities` 里声明,并由 Host 实际 grant;
|
|
131
|
+
缺任一侧,调用在 Widget 本地即被拒绝。所有能力调用都必须在 `ready()` 之后。
|
|
128
132
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
生产 iframe、CSP、授权或资源隔离保证。Schema 与类型校验以
|
|
133
|
-
`@dp-bohrium/widget-contracts` 为准。
|
|
133
|
+
下面七项是当前的全部能力。grant 名与 Manifest 申请键不一定同名 —— `ui.presentation`
|
|
134
|
+
对应的作者侧申请键是 `capabilities.presentation`(编译后的 宿主格式字段才叫
|
|
135
|
+
`requestedCapabilities.presentation`)。
|
|
134
136
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
| grant 名 | Manifest 申请键 | SDK 调用 |
|
|
138
|
+
| --- | --- | --- |
|
|
139
|
+
| `files.list` / `files.metadata` / `files.read` | `files` | `context.capabilities.request({ operation, input })` |
|
|
140
|
+
| `files.download` | `files.download` | `context.files.download({ file, suggestedName })` |
|
|
141
|
+
| `events.read` | `events`(需列出 `topics`) | 低层分页订阅;`context.events.on(…)` 是 Host 主动推送,不需要该 grant |
|
|
142
|
+
| `tasks.watch` | `tasks.watch` | `context.tasks.watch({ taskId, cursor, limit })` |
|
|
143
|
+
| `ui.state` | `state`(`read` / `write`) | `context.ui.state.read(key)` / `write(key, value)` |
|
|
144
|
+
| `actions.invoke` | `actions` | `context.capabilities.request({ operation: "actions.invoke", input })` |
|
|
145
|
+
| `network.fetch` | `network` | `context.network.fetch(…)` |
|
|
146
|
+
| `ui.presentation` | `presentation` | `context.ui.present(…)` / `changeSurface` / `close` |
|
|
138
147
|
|
|
139
|
-
|
|
140
|
-
源为 `type="module"` 提供 CORS,否则请将打包后的 Widget 以 classic script / IIFE
|
|
141
|
-
加载。
|
|
148
|
+
除 Host 主动推送事件外,其余能力共用同一套调用范式,掌握下面这一个就会调其余能力:
|
|
142
149
|
|
|
143
|
-
|
|
144
|
-
|
|
150
|
+
```ts
|
|
151
|
+
const controller = new AbortController();
|
|
152
|
+
const result = await context.network.fetch(
|
|
153
|
+
{ url: "https://api.example.org/papers", method: "GET", auth: "optional" },
|
|
154
|
+
{ signal: controller.signal, timeoutMs: 15_000 },
|
|
155
|
+
);
|
|
145
156
|
|
|
146
|
-
|
|
157
|
+
if (result.ok) {
|
|
158
|
+
consume(result.data.status, result.data.headers, result.data.body);
|
|
159
|
+
} else {
|
|
160
|
+
// 策略拒绝、用户拒绝、未授权都走这里,不是 throw。
|
|
161
|
+
showNetworkFailure(result.reason, result.retryable);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
147
164
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
165
|
+
跨能力约定:
|
|
166
|
+
|
|
167
|
+
- 失败以 `{ ok: false, reason, message, retryable }` 正常兑现;只有客户端超时、中止、
|
|
168
|
+
dispose 和畸形响应才 reject。
|
|
169
|
+
- 中止与超时统一用 `{ signal, timeoutMs }`,复用既有 MessageChannel 能力生命周期。
|
|
170
|
+
- Widget 永远拿不到 Host 凭据,也不应直接调用浏览器 `fetch`:是否弹窗、记住 Origin
|
|
171
|
+
授权、跟随重定向还是拒绝,全部由 Host 决定。
|
|
172
|
+
- `context.files.download` 只提交文件引用和建议文件名;Host 在用户确认后经宿主
|
|
173
|
+
Download Adapter 惰性打开内容源。Widget 不会收到源 URL、保存路径或凭据。
|
|
174
|
+
- `context.tasks.watch` 只读取一次受限任务快照和事件页;调用方用返回的 cursor 继续轮询。
|
|
175
|
+
它不创建、调度或取消任务,服务端负责任务授权、脱敏和终态保留。
|
|
176
|
+
- `context.ui.state` 只保存按 Widget + Session 隔离的 JSON UI 状态;Host 按 key、单值大小、总字节数和调用
|
|
177
|
+
频率限流。它不能保存任务执行、凭据、文件内容或 Workspace 权限状态。
|
|
178
|
+
- Tool Result 的 `content.envelope` 是可选的结构化投影,包含受约束的状态、数据、指标、产物引用、
|
|
179
|
+
provenance、warning 和 missing;产物只携带 opaque id 与展示元数据,不携带下载 URL 或本机路径。
|
|
180
|
+
- 呈现只是**请求**。Host 可能降级到别的表面,成功结果里的 `surface` 才是真实表面;
|
|
181
|
+
Widget 不能指定目标 Panel、身份、运行时 generation、坐标或层级。
|
|
182
|
+
- 高层插件 API 使用 `context.events.on(handler)` 接收 Host 从当前 Runtime **主动推送**的
|
|
183
|
+
聚合事件,只按 `type` 分发,不依赖 Manifest Topic 或 `events.read` grant。需要直接使用有界
|
|
184
|
+
分页读取时,从 `@dp-bohrium/widget-sdk/runtime` 使用 `subscribeEvents` / `onTool`。
|
|
185
|
+
- 低层 `subscribeEvents` / `onTool` 的所有订阅共享同一实例的 events 读预算,并各自遵守
|
|
186
|
+
topic 最小轮询间隔 —— 订阅越多,单个 topic 刷新越慢。游标只在 `onBatch` 成功后
|
|
187
|
+
提交,重新订阅需显式传回 `events.cursor`。工具 Hook 观察的是**已经发生的**服务端
|
|
188
|
+
事实,不是执行前拦截器。
|
|
189
|
+
|
|
190
|
+
## 测试
|
|
151
191
|
|
|
152
192
|
```ts
|
|
153
|
-
|
|
154
|
-
topic: "jobs.progress",
|
|
155
|
-
onBatch(batch, { signal }) {
|
|
156
|
-
if (signal.aborted) return;
|
|
157
|
-
// 只应用本批真实事件;batch.reset 时替换旧状态。
|
|
158
|
-
renderProgress(batch.events);
|
|
159
|
-
},
|
|
160
|
-
onError: showSubscriptionError,
|
|
161
|
-
});
|
|
162
|
-
const hooks = client.onTool({
|
|
163
|
-
onToolCall: showStarted,
|
|
164
|
-
onToolResult: showCompleted,
|
|
165
|
-
onToolError: showFailed,
|
|
166
|
-
onToolCancelled: showCancelled,
|
|
167
|
-
}, { onError: showSubscriptionError });
|
|
168
|
-
const result = await client.invokeAction({
|
|
169
|
-
action: "tools.inspect",
|
|
170
|
-
idempotencyKey: crypto.randomUUID(),
|
|
171
|
-
arguments: { toolCallId },
|
|
172
|
-
});
|
|
173
|
-
const resumeCursor = events.cursor;
|
|
174
|
-
events.unsubscribe();
|
|
175
|
-
hooks.unsubscribe();
|
|
193
|
+
import { MockWidgetHost } from "@dp-bohrium/widget-sdk/testing";
|
|
176
194
|
```
|
|
177
195
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
196
|
+
`MockWidgetHost` 负责 init/ready 时序、首帧 render 与 update 选择、resize 事件、
|
|
197
|
+
能力 adapter 响应与终态 dispose;`capabilityResult` 给出确定性的默认允许/拒绝,
|
|
198
|
+
同时提供 `handleCapability` 时后者优先。`host.capabilityRequests` 返回有序的防御性
|
|
199
|
+
拷贝,断言不会改动 Mock 状态。
|
|
200
|
+
|
|
201
|
+
它使用与生产 Host 相同的公开 contracts,但**不等同于**生产 iframe、CSP、授权或资源
|
|
202
|
+
隔离保证。
|
|
203
|
+
|
|
204
|
+
## 限制
|
|
205
|
+
|
|
206
|
+
- SDK 不做授权决策,能力是否可用最终由 Host 判定。
|
|
207
|
+
- `tasks.watch` 是有界观察协议,不是调度器、执行器或取消 API;任务状态和事件由服务端投影。
|
|
208
|
+
- `invokeAction` 不自动重试,回调完成也不代表工具执行完成。
|
|
209
|
+
- `subscribeEvents` 的 `events.read` 是有界分页读取,不是 websocket;实时推送走
|
|
210
|
+
`onEvent`。当前版本没有单请求取消的独立协议消息:`unsubscribe` 会停止后续轮询并取消
|
|
211
|
+
本地等待,已发出的服务端读取可能完成,但结果被忽略。
|
|
212
|
+
- Host 发来畸形 Bridge 信封或载荷时,transport 报结构化 `MESSAGE_INVALID` 并关闭
|
|
213
|
+
连接,不会让 Widget 悬等。
|
|
214
|
+
- Schema 与类型校验以 `@dp-bohrium/widget-contracts` 为准。
|
|
182
215
|
|
|
183
|
-
|
|
184
|
-
`onBatch` 成功后提交;消费者异常、权限撤销和不可恢复的历史错误会关闭订阅并调用
|
|
185
|
-
`onError`。`unsubscribe` 和 Host dispose 释放订阅、排队请求和本地在途等待,忽略
|
|
186
|
-
迟到结果;异步回调需检查传入 signal 后再更新自己的状态。`closed` 表示订阅已关闭,
|
|
187
|
-
并不等待用户未完成的回调结束。重新订阅时显式传入已保存 cursor。
|
|
216
|
+
## 许可证
|
|
188
217
|
|
|
189
|
-
|
|
190
|
-
unsubscribe 会停止后续轮询并取消本地等待,已经发出的服务端读取可能完成但其结果被
|
|
191
|
-
忽略。Host 实例卸载另通过既有 lease/AbortSignal 释放在途宿主请求。
|
|
218
|
+
MIT。
|
|
@@ -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-I78q3jVT.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,2 +1,6 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
export {
|
|
1
|
+
export type { WidgetActivationEvent, WidgetActivationEvents, WidgetContribution, WidgetEventName, WidgetFilePreviewContribution, WidgetPanelContribution, WidgetPluginContributions, WidgetPluginManifest, WidgetToolPhase, WidgetToolResultContribution, } from "@dp-bohrium/widget-contracts";
|
|
2
|
+
export { isWidgetActivationEvent, isWidgetEventName, WIDGET_ACTIVATION_EVENTS, WIDGET_EVENT_NAMES, } from "@dp-bohrium/widget-contracts";
|
|
3
|
+
export type { CompileWidgetPluginManifestOptions } from "./manifest.js";
|
|
4
|
+
export { compileWidgetPluginManifest } from "./manifest.js";
|
|
5
|
+
export type { FileDownloadCapabilityResult, TaskWatchCapabilityResult, UiStateReadCapabilityResult, UiStateWriteCapabilityResult, WidgetActivation, WidgetCapabilityApi, WidgetContext, WidgetDisposable, WidgetEventApi, WidgetFileApi, WidgetHandle, WidgetHostOptions, WidgetModule, WidgetRenderContext, WidgetRenderHandler, WidgetTaskApi, WidgetUiApi, WidgetUiStateApi, } from "./widget.js";
|
|
6
|
+
export { runWidgetHost } from "./widget.js";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,182 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { c as WIDGET_SDK_VERSION, i as createWidgetClient } from "./runtime-B5kilTcQ.js";
|
|
2
|
+
import { WIDGET_ACTIVATION_EVENTS, WIDGET_EVENT_NAMES, isWidgetActivationEvent, isWidgetEventName } from "@dp-bohrium/widget-contracts";
|
|
3
|
+
//#region src/manifest.ts
|
|
4
|
+
function compiledSlot(contribution) {
|
|
5
|
+
switch (contribution.type) {
|
|
6
|
+
case "panel": return {
|
|
7
|
+
type: "panel",
|
|
8
|
+
panelType: contribution.id
|
|
9
|
+
};
|
|
10
|
+
case "tool-result": return {
|
|
11
|
+
type: "tool-result",
|
|
12
|
+
toolName: contribution.toolName
|
|
13
|
+
};
|
|
14
|
+
case "file-preview": return {
|
|
15
|
+
type: "file-preview",
|
|
16
|
+
extensions: [...contribution.extensions]
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 将开发者维护的插件清单编译为宿主可加载的宿主清单。
|
|
22
|
+
*
|
|
23
|
+
* 这是构建工具的边界函数,不是类型提示入口:作者直接编辑带 `$schema` 的 JSON,
|
|
24
|
+
* SDK 版本从当前安装包读取,宿主入口默认落到 dist/widget.js,内部 slot 标识只在这里生成。
|
|
25
|
+
*/
|
|
26
|
+
function compileWidgetPluginManifest(manifest, options = {}) {
|
|
27
|
+
const samples = options.samples ?? manifest.samples;
|
|
28
|
+
if (!samples || samples.length === 0) throw new Error("Widget Plugin Manifest must provide at least one sample before packaging");
|
|
29
|
+
const activationEvents = manifest.activationEvents ? [...manifest.activationEvents] : void 0;
|
|
30
|
+
return {
|
|
31
|
+
name: manifest.name,
|
|
32
|
+
...manifest.description === void 0 ? {} : { description: manifest.description },
|
|
33
|
+
widgetConfig: {
|
|
34
|
+
schemaVersion: 1,
|
|
35
|
+
hostApiVersion: 1,
|
|
36
|
+
widgetSdkVersion: WIDGET_SDK_VERSION,
|
|
37
|
+
entry: options.hostEntry ?? "dist/widget.js",
|
|
38
|
+
slot: compiledSlot(manifest.contributes.widget),
|
|
39
|
+
...activationEvents === void 0 ? {} : { activationEvents },
|
|
40
|
+
...manifest.capabilities === void 0 ? {} : { requestedCapabilities: manifest.capabilities },
|
|
41
|
+
...manifest.runtime === void 0 ? {} : { runtime: manifest.runtime },
|
|
42
|
+
samples
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/widget.ts
|
|
48
|
+
/**
|
|
49
|
+
* Generated Widget Host 的唯一运行入口。Widget 作者只导出 activate/deactivate,
|
|
50
|
+
* 不直接调用此函数;CLI 生成的 widget-host.ts 负责调用它。
|
|
51
|
+
*/
|
|
52
|
+
function runWidgetHost(widget, options = {}) {
|
|
53
|
+
const client = createWidgetRuntime((typeof widget === "function" ? { activate: widget } : widget).activate, options);
|
|
54
|
+
const whenReady = client.ready();
|
|
55
|
+
whenReady.catch((reason) => {
|
|
56
|
+
const error = toStartupError(reason);
|
|
57
|
+
if (options.onStartupError) {
|
|
58
|
+
options.onStartupError(error);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
console.error(`[Widget 启动失败] ${error.message}`, error);
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
whenReady,
|
|
65
|
+
state: client.state,
|
|
66
|
+
dispose: client.dispose
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function disposable(registration) {
|
|
70
|
+
return typeof registration === "function" ? { dispose: registration } : registration;
|
|
71
|
+
}
|
|
72
|
+
function createContext(client, payload, renderHandlers, registrations, signal) {
|
|
73
|
+
return {
|
|
74
|
+
instanceId: payload.instanceId,
|
|
75
|
+
locale: payload.locale,
|
|
76
|
+
theme: payload.theme,
|
|
77
|
+
signal,
|
|
78
|
+
capabilities: {
|
|
79
|
+
granted: payload.capabilities,
|
|
80
|
+
request: (request, options) => client.requestCapability(request, options)
|
|
81
|
+
},
|
|
82
|
+
files: { download: (input, options) => client.requestCapability({
|
|
83
|
+
operation: "files.download",
|
|
84
|
+
input
|
|
85
|
+
}, options) },
|
|
86
|
+
tasks: { watch: (input, options) => client.requestCapability({
|
|
87
|
+
operation: "tasks.watch",
|
|
88
|
+
input
|
|
89
|
+
}, options) },
|
|
90
|
+
network: client.network,
|
|
91
|
+
ui: {
|
|
92
|
+
state: {
|
|
93
|
+
read: (key, options) => client.requestCapability({
|
|
94
|
+
operation: "ui.state",
|
|
95
|
+
input: {
|
|
96
|
+
action: "read",
|
|
97
|
+
key
|
|
98
|
+
}
|
|
99
|
+
}, options),
|
|
100
|
+
write: (key, value, options) => client.requestCapability({
|
|
101
|
+
operation: "ui.state",
|
|
102
|
+
input: {
|
|
103
|
+
action: "write",
|
|
104
|
+
key,
|
|
105
|
+
value
|
|
106
|
+
}
|
|
107
|
+
}, options)
|
|
108
|
+
},
|
|
109
|
+
present: (surface, options) => client.present(surface, options),
|
|
110
|
+
close: (options) => client.closePresentation(options),
|
|
111
|
+
changeSurface: (surface, options) => client.changePresentationSurface(surface, options)
|
|
112
|
+
},
|
|
113
|
+
events: { on(handlerOrName, maybeHandler) {
|
|
114
|
+
const result = { dispose: typeof handlerOrName === "string" ? client.onEvent(handlerOrName, maybeHandler) : client.onEvent(handlerOrName) };
|
|
115
|
+
registrations.add(result);
|
|
116
|
+
return result;
|
|
117
|
+
} },
|
|
118
|
+
subscriptions: { add(registration) {
|
|
119
|
+
registrations.add(disposable(registration));
|
|
120
|
+
} },
|
|
121
|
+
render(handler) {
|
|
122
|
+
renderHandlers.add(handler);
|
|
123
|
+
const result = { dispose: () => {
|
|
124
|
+
renderHandlers.delete(handler);
|
|
125
|
+
} };
|
|
126
|
+
registrations.add(result);
|
|
127
|
+
return result;
|
|
128
|
+
},
|
|
129
|
+
resize: (width, height) => client.resize(width, height)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function createWidgetRuntime(activate, options = {}) {
|
|
133
|
+
let client;
|
|
134
|
+
let widgetDispose;
|
|
135
|
+
let context;
|
|
136
|
+
const renderHandlers = /* @__PURE__ */ new Set();
|
|
137
|
+
const registrations = /* @__PURE__ */ new Set();
|
|
138
|
+
const lifetime = new AbortController();
|
|
139
|
+
client = createWidgetClient({
|
|
140
|
+
...options,
|
|
141
|
+
events: {
|
|
142
|
+
...options.events,
|
|
143
|
+
async onInit(payload) {
|
|
144
|
+
context = createContext(client, payload, renderHandlers, registrations, lifetime.signal);
|
|
145
|
+
const registration = await activate(context);
|
|
146
|
+
if (registration !== void 0) {
|
|
147
|
+
widgetDispose = disposable(registration);
|
|
148
|
+
context.subscriptions.add(widgetDispose);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
onRender(request, update) {
|
|
152
|
+
const renderContext = {
|
|
153
|
+
phase: update ? "update" : "initial",
|
|
154
|
+
signal: lifetime.signal
|
|
155
|
+
};
|
|
156
|
+
for (const handler of [...renderHandlers]) try {
|
|
157
|
+
Promise.resolve(handler(request, renderContext)).catch(() => {});
|
|
158
|
+
} catch {}
|
|
159
|
+
},
|
|
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
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
ready: () => client.ready(),
|
|
174
|
+
state: () => client.currentState,
|
|
175
|
+
dispose: () => client.dispose()
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function toStartupError(reason) {
|
|
179
|
+
return reason instanceof Error ? reason : new Error("Widget 启动失败", { cause: reason });
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
export { WIDGET_ACTIVATION_EVENTS, WIDGET_EVENT_NAMES, compileWidgetPluginManifest, isWidgetActivationEvent, isWidgetEventName, runWidgetHost };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WidgetManifest, WidgetPluginManifest } from "@dp-bohrium/widget-contracts";
|
|
2
|
+
export interface CompileWidgetPluginManifestOptions {
|
|
3
|
+
readonly samples?: NonNullable<WidgetPluginManifest["samples"]>;
|
|
4
|
+
/** Host bundle entry. CLI supplies this after the project is built. */
|
|
5
|
+
readonly hostEntry?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 将开发者维护的插件清单编译为宿主可加载的宿主清单。
|
|
9
|
+
*
|
|
10
|
+
* 这是构建工具的边界函数,不是类型提示入口:作者直接编辑带 `$schema` 的 JSON,
|
|
11
|
+
* SDK 版本从当前安装包读取,宿主入口默认落到 dist/widget.js,内部 slot 标识只在这里生成。
|
|
12
|
+
*/
|
|
13
|
+
export declare function compileWidgetPluginManifest(manifest: WidgetPluginManifest, options?: CompileWidgetPluginManifestOptions): WidgetManifest;
|
|
@@ -39,7 +39,7 @@ function isProtocolPayload(type, payload) {
|
|
|
39
39
|
const content = property(payload, "content");
|
|
40
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, "slot") === "string" && typeof property(payload, "state") === "string" && isRecord(context) && isRecord(content);
|
|
41
41
|
}
|
|
42
|
-
case "event": return
|
|
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"));
|
|
44
44
|
case "error": return typeof property(payload, "code") === "string" && typeof property(payload, "phase") === "string" && typeof property(payload, "message") === "string" && typeof property(payload, "retryable") === "boolean" && typeof property(payload, "correlationId") === "string";
|
|
45
45
|
case "dispose": return typeof property(payload, "reason") === "string" && isFiniteNonNegative(property(payload, "deadlineMs"));
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { CapabilityRequest, CapabilityResult, InitPayload, PresentationCapabilityResult,
|
|
1
|
+
import type { CapabilityRequest, CapabilityResult, InitPayload, PresentationCapabilityResult, RenderRequest, WidgetError, WidgetLiveEvent } from "@dp-bohrium/widget-contracts";
|
|
2
2
|
import { type PresentationFocusDirection, type PresentationSurface, 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 { WIDGET_SDK_VERSION } from "./version.js";
|
|
5
6
|
export declare const WIDGET_BOOTSTRAP_READY: "widget-bootstrap-ready";
|
|
6
7
|
export declare const WIDGET_HOST_CONNECT: "widget-host-connect";
|
|
8
|
+
export { WIDGET_SDK_VERSION };
|
|
7
9
|
export interface WidgetHostConnectMessage {
|
|
8
10
|
type: typeof WIDGET_HOST_CONNECT;
|
|
9
11
|
protocolVersion: 1;
|
|
@@ -26,9 +28,11 @@ export interface WidgetLiveEventContext {
|
|
|
26
28
|
readonly signal: AbortSignal;
|
|
27
29
|
}
|
|
28
30
|
/** 由 Host 通过当前 MessageChannel 推送的实时 Workflow 事件处理器。 */
|
|
29
|
-
export type WidgetLiveEventHandler = (event:
|
|
31
|
+
export type WidgetLiveEventHandler = (event: WidgetLiveEvent, context: WidgetLiveEventContext) => void | Promise<void>;
|
|
32
|
+
export type WidgetLiveEventName = WidgetLiveEvent["type"];
|
|
30
33
|
export interface CreateWidgetClientOptions {
|
|
31
|
-
|
|
34
|
+
/** 低层协议字段;Widget Host 会从已安装 SDK 自动读取 SDK 版本。 */
|
|
35
|
+
widgetSdkVersion?: string;
|
|
32
36
|
hostApiVersion?: 1;
|
|
33
37
|
handshakeTimeoutMs?: number;
|
|
34
38
|
capabilityTimeoutMs?: number;
|
|
@@ -87,12 +91,10 @@ export declare class WidgetRuntimeClient {
|
|
|
87
91
|
changePresentationSurface(surface: PresentationSurface, options?: WidgetCapabilityRequestOptions): Promise<PresentationCapabilityResult>;
|
|
88
92
|
focusPresentationBoundary(direction: PresentationFocusDirection, options?: WidgetCapabilityRequestOptions): Promise<PresentationCapabilityResult>;
|
|
89
93
|
subscribeEvents(options: SubscribeWidgetEventsOptions): WidgetEventSubscription;
|
|
90
|
-
/**
|
|
91
|
-
* 订阅 Host 从当前 Workflow 流实时推送的事件。
|
|
92
|
-
*
|
|
93
|
-
* Topic 授权由 Host 在发送前按 Manifest 与 Grant 过滤;这里不发起 HTTP 轮询。
|
|
94
|
-
*/
|
|
94
|
+
/** 订阅 Host 从当前 Workflow 流实时推送的全部聚合事件。 */
|
|
95
95
|
onEvent(handler: WidgetLiveEventHandler): () => void;
|
|
96
|
+
/** 订阅 Host 从当前 Workflow 流实时推送的指定聚合事件。 */
|
|
97
|
+
onEvent(eventName: WidgetLiveEventName, handler: WidgetLiveEventHandler): () => void;
|
|
96
98
|
onTool(hooks: WidgetToolHooks, options: Pick<SubscribeWidgetEventsOptions, "cursor" | "onError">): WidgetEventSubscription;
|
|
97
99
|
invokeAction(input: Extract<CapabilityRequest, {
|
|
98
100
|
operation: "actions.invoke";
|
package/dist/runtime/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CapabilityRequest, CapabilityResult, GrantedCapability, InitPayload, PresentationCapabilityRequest, PresentationCapabilityResult,
|
|
1
|
+
import type { CapabilityRequest, CapabilityResult, GrantedCapability, InitPayload, PresentationCapabilityRequest, PresentationCapabilityResult, 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";
|
|
@@ -16,8 +16,8 @@ export interface WidgetClientEvents {
|
|
|
16
16
|
onInit?: (payload: InitPayload) => void | PromiseLike<void>;
|
|
17
17
|
onRender?: (request: RenderRequest, update: boolean) => void;
|
|
18
18
|
onUpdate?: (request: RenderRequest) => void;
|
|
19
|
-
/** 接收 Host 从当前
|
|
20
|
-
onEvent?: (event:
|
|
19
|
+
/** 接收 Host 从当前 Runtime 主动推送的聚合事件。 */
|
|
20
|
+
onEvent?: (event: WidgetLiveEvent) => void;
|
|
21
21
|
onError?: (error: WidgetError) => void;
|
|
22
22
|
onDispose?: (reason: string, deadlineMs?: number) => void;
|
|
23
23
|
}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { CreateWidgetClientOptions, WidgetClientFactoryOptions, WidgetDisposeEvent, WidgetHostConnectMessage, WidgetLiveEventContext, WidgetLiveEventHandler, WidgetRuntimeWindow, WidgetSize, } from "./bootstrap.js";
|
|
2
|
-
export { createWidgetClient, isWidgetHostConnectMessage, WIDGET_BOOTSTRAP_READY, WIDGET_HOST_CONNECT, WidgetRuntimeClient, } from "./bootstrap.js";
|
|
1
|
+
export type { CreateWidgetClientOptions, WidgetClientFactoryOptions, WidgetDisposeEvent, WidgetHostConnectMessage, WidgetLiveEventContext, WidgetLiveEventHandler, WidgetLiveEventName, WidgetRuntimeWindow, WidgetSize, } from "./bootstrap.js";
|
|
2
|
+
export { createWidgetClient, isWidgetHostConnectMessage, WIDGET_BOOTSTRAP_READY, WIDGET_HOST_CONNECT, WIDGET_SDK_VERSION, WidgetRuntimeClient, } from "./bootstrap.js";
|
|
3
3
|
export type { PresentationFocusDirection, PresentationSurface, WidgetCapabilityRequestOptions, WidgetClientEvents, WidgetClientOptions, WidgetClientState, } from "./client.js";
|
|
4
4
|
export { PRESENTATION_CONFIRMATION_TIMEOUT_MS, WidgetClient, } from "./client.js";
|
|
5
5
|
export type { SubscribeWidgetEventsOptions, WidgetEventBatchContext, WidgetEventSubscription, WidgetToolHooks, } from "./events.js";
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as isWidgetHostConnectMessage, i as createWidgetClient, n as WIDGET_HOST_CONNECT, o as PRESENTATION_CONFIRMATION_TIMEOUT_MS, r as WidgetRuntimeClient, s as WidgetClient, t as WIDGET_BOOTSTRAP_READY } from "../runtime-
|
|
2
|
-
import { i as isWidgetProtocolMessage, r as MessagePortTransport } from "../protocol-endpoint-
|
|
3
|
-
export { MessagePortTransport, PRESENTATION_CONFIRMATION_TIMEOUT_MS, WIDGET_BOOTSTRAP_READY, WIDGET_HOST_CONNECT, WidgetClient, WidgetRuntimeClient, createWidgetClient, isWidgetHostConnectMessage, isWidgetProtocolMessage };
|
|
1
|
+
import { a as isWidgetHostConnectMessage, c as WIDGET_SDK_VERSION, i as createWidgetClient, n as WIDGET_HOST_CONNECT, o as PRESENTATION_CONFIRMATION_TIMEOUT_MS, r as WidgetRuntimeClient, s as WidgetClient, t as WIDGET_BOOTSTRAP_READY } from "../runtime-B5kilTcQ.js";
|
|
2
|
+
import { i as isWidgetProtocolMessage, r as MessagePortTransport } from "../protocol-endpoint-I78q3jVT.js";
|
|
3
|
+
export { MessagePortTransport, PRESENTATION_CONFIRMATION_TIMEOUT_MS, WIDGET_BOOTSTRAP_READY, WIDGET_HOST_CONNECT, WIDGET_SDK_VERSION, WidgetClient, WidgetRuntimeClient, createWidgetClient, isWidgetHostConnectMessage, isWidgetProtocolMessage };
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { r as MessagePortTransport, t as ProtocolEndpoint } from "./protocol-endpoint-
|
|
1
|
+
import { r as MessagePortTransport, t as ProtocolEndpoint } from "./protocol-endpoint-I78q3jVT.js";
|
|
2
|
+
//#endregion
|
|
3
|
+
//#region src/runtime/version.ts
|
|
4
|
+
/** 从当前安装的 SDK package.json 读取版本,插件作者不需要重复填写。 */
|
|
5
|
+
const WIDGET_SDK_VERSION = "0.1.0";
|
|
6
|
+
//#endregion
|
|
2
7
|
//#region src/runtime/network.ts
|
|
3
8
|
const failureReasons = /* @__PURE__ */ new Set([
|
|
4
9
|
"unsupported",
|
|
@@ -1023,16 +1028,18 @@ var WidgetRuntimeClient = class {
|
|
|
1023
1028
|
if (this.disposed || this.currentState === "disposed") subscription.unsubscribe();
|
|
1024
1029
|
return subscription;
|
|
1025
1030
|
}
|
|
1026
|
-
|
|
1027
|
-
* 订阅 Host 从当前 Workflow 流实时推送的事件。
|
|
1028
|
-
*
|
|
1029
|
-
* Topic 授权由 Host 在发送前按 Manifest 与 Grant 过滤;这里不发起 HTTP 轮询。
|
|
1030
|
-
*/
|
|
1031
|
-
onEvent(handler) {
|
|
1031
|
+
onEvent(eventNameOrHandler, maybeHandler) {
|
|
1032
1032
|
if (this.disposed) throw new Error("client is disposed");
|
|
1033
|
-
|
|
1033
|
+
const handler = typeof eventNameOrHandler === "string" ? maybeHandler : eventNameOrHandler;
|
|
1034
|
+
if (!handler) throw new TypeError("event handler is required");
|
|
1035
|
+
const eventName = typeof eventNameOrHandler === "string" ? eventNameOrHandler : void 0;
|
|
1036
|
+
const subscribedHandler = eventName === void 0 ? handler : (event, context) => {
|
|
1037
|
+
if (event.type !== eventName) return;
|
|
1038
|
+
return handler(event, context);
|
|
1039
|
+
};
|
|
1040
|
+
this.liveEventHandlers.add(subscribedHandler);
|
|
1034
1041
|
return () => {
|
|
1035
|
-
this.liveEventHandlers.delete(
|
|
1042
|
+
this.liveEventHandlers.delete(subscribedHandler);
|
|
1036
1043
|
};
|
|
1037
1044
|
}
|
|
1038
1045
|
onTool(hooks, options) {
|
|
@@ -1099,7 +1106,7 @@ var WidgetRuntimeClient = class {
|
|
|
1099
1106
|
const clientOptions = {
|
|
1100
1107
|
transport: new MessagePortTransport(port),
|
|
1101
1108
|
channelId: event.data.channelId,
|
|
1102
|
-
widgetSdkVersion: this.options.widgetSdkVersion,
|
|
1109
|
+
widgetSdkVersion: this.options.widgetSdkVersion ?? WIDGET_SDK_VERSION,
|
|
1103
1110
|
initialPayload: event.data.initPayload,
|
|
1104
1111
|
...this.options.hostApiVersion === void 0 ? {} : { hostApiVersion: this.options.hostApiVersion },
|
|
1105
1112
|
...this.options.capabilityTimeoutMs === void 0 ? {} : { capabilityTimeoutMs: this.options.capabilityTimeoutMs },
|
|
@@ -1155,4 +1162,4 @@ function createWidgetClient(options) {
|
|
|
1155
1162
|
return new WidgetRuntimeClient(options);
|
|
1156
1163
|
}
|
|
1157
1164
|
//#endregion
|
|
1158
|
-
export { isWidgetHostConnectMessage as a, createWidgetClient as i, WIDGET_HOST_CONNECT as n, PRESENTATION_CONFIRMATION_TIMEOUT_MS as o, WidgetRuntimeClient as r, WidgetClient as s, WIDGET_BOOTSTRAP_READY as t };
|
|
1165
|
+
export { isWidgetHostConnectMessage as a, WIDGET_SDK_VERSION as c, createWidgetClient as i, WIDGET_HOST_CONNECT as n, PRESENTATION_CONFIRMATION_TIMEOUT_MS as o, WidgetRuntimeClient as r, WidgetClient as s, WIDGET_BOOTSTRAP_READY as t };
|
package/dist/widget.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
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";
|
|
3
|
+
import type { WidgetNetworkClient } from "./runtime/network.js";
|
|
4
|
+
export interface WidgetDisposable {
|
|
5
|
+
dispose(): void | Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export type WidgetDisposeRegistration = WidgetDisposable | (() => void | Promise<void>);
|
|
8
|
+
export interface WidgetRenderContext {
|
|
9
|
+
readonly phase: "initial" | "update";
|
|
10
|
+
readonly signal: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
export type WidgetRenderHandler = (request: RenderRequest, context: WidgetRenderContext) => void | PromiseLike<void>;
|
|
13
|
+
export interface WidgetEventApi {
|
|
14
|
+
on(handler: WidgetLiveEventHandler): WidgetDisposable;
|
|
15
|
+
on<T extends WidgetEventName>(eventName: T, handler: (event: WidgetLiveEvent & {
|
|
16
|
+
readonly type: T;
|
|
17
|
+
}, context: WidgetLiveEventContext) => void | Promise<void>): WidgetDisposable;
|
|
18
|
+
}
|
|
19
|
+
export interface WidgetCapabilityApi {
|
|
20
|
+
readonly granted: readonly GrantedCapability[];
|
|
21
|
+
request(request: CapabilityRequest, options?: {
|
|
22
|
+
readonly timeoutMs?: number;
|
|
23
|
+
readonly signal?: AbortSignal;
|
|
24
|
+
}): Promise<CapabilityResult>;
|
|
25
|
+
}
|
|
26
|
+
export interface WidgetFileApi {
|
|
27
|
+
/** 请求 Host 在用户确认后通过宿主 Download Adapter 保存文件。 */
|
|
28
|
+
download(input: {
|
|
29
|
+
readonly file: FileReference;
|
|
30
|
+
readonly suggestedName: string;
|
|
31
|
+
}, options?: {
|
|
32
|
+
readonly timeoutMs?: number;
|
|
33
|
+
readonly signal?: AbortSignal;
|
|
34
|
+
}): Promise<FileDownloadCapabilityResult>;
|
|
35
|
+
}
|
|
36
|
+
export type FileDownloadCapabilityResult = {
|
|
37
|
+
readonly ok: true;
|
|
38
|
+
readonly data: FileDownloadResult;
|
|
39
|
+
} | Extract<CapabilityResult, {
|
|
40
|
+
readonly ok: false;
|
|
41
|
+
}>;
|
|
42
|
+
export interface WidgetTaskApi {
|
|
43
|
+
/**
|
|
44
|
+
* 读取一次受 Host 限制的任务快照和事件页。调用方使用返回的 cursor
|
|
45
|
+
* 继续轮询;此 API 不创建、调度或取消任务。
|
|
46
|
+
*/
|
|
47
|
+
watch(input: {
|
|
48
|
+
readonly taskId: string;
|
|
49
|
+
readonly cursor?: string;
|
|
50
|
+
readonly limit?: number;
|
|
51
|
+
}, options?: {
|
|
52
|
+
readonly timeoutMs?: number;
|
|
53
|
+
readonly signal?: AbortSignal;
|
|
54
|
+
}): Promise<TaskWatchCapabilityResult>;
|
|
55
|
+
}
|
|
56
|
+
export type TaskWatchCapabilityResult = {
|
|
57
|
+
readonly ok: true;
|
|
58
|
+
readonly data: TaskObservationBatch;
|
|
59
|
+
} | Extract<CapabilityResult, {
|
|
60
|
+
readonly ok: false;
|
|
61
|
+
}>;
|
|
62
|
+
export interface WidgetUiStateApi {
|
|
63
|
+
read(key: string, options?: {
|
|
64
|
+
readonly timeoutMs?: number;
|
|
65
|
+
readonly signal?: AbortSignal;
|
|
66
|
+
}): Promise<UiStateReadCapabilityResult>;
|
|
67
|
+
write(key: string, value: import("@dp-bohrium/widget-contracts").JsonValue, options?: {
|
|
68
|
+
readonly timeoutMs?: number;
|
|
69
|
+
readonly signal?: AbortSignal;
|
|
70
|
+
}): Promise<UiStateWriteCapabilityResult>;
|
|
71
|
+
}
|
|
72
|
+
export type UiStateReadCapabilityResult = {
|
|
73
|
+
readonly ok: true;
|
|
74
|
+
readonly data: UiStateResult;
|
|
75
|
+
} | Extract<CapabilityResult, {
|
|
76
|
+
readonly ok: false;
|
|
77
|
+
}>;
|
|
78
|
+
export type UiStateWriteCapabilityResult = {
|
|
79
|
+
readonly ok: true;
|
|
80
|
+
readonly data: UiStateWriteResult;
|
|
81
|
+
} | Extract<CapabilityResult, {
|
|
82
|
+
readonly ok: false;
|
|
83
|
+
}>;
|
|
84
|
+
export interface WidgetUiApi {
|
|
85
|
+
readonly state: WidgetUiStateApi;
|
|
86
|
+
present(surface: "workspace" | "floating" | "overlay", options?: {
|
|
87
|
+
readonly timeoutMs?: number;
|
|
88
|
+
readonly signal?: AbortSignal;
|
|
89
|
+
}): Promise<CapabilityResult>;
|
|
90
|
+
close(options?: {
|
|
91
|
+
readonly timeoutMs?: number;
|
|
92
|
+
readonly signal?: AbortSignal;
|
|
93
|
+
}): Promise<CapabilityResult>;
|
|
94
|
+
changeSurface(surface: "workspace" | "floating" | "overlay", options?: {
|
|
95
|
+
readonly timeoutMs?: number;
|
|
96
|
+
readonly signal?: AbortSignal;
|
|
97
|
+
}): Promise<CapabilityResult>;
|
|
98
|
+
}
|
|
99
|
+
export interface WidgetContext {
|
|
100
|
+
readonly instanceId: string;
|
|
101
|
+
readonly locale: string;
|
|
102
|
+
readonly theme: Theme;
|
|
103
|
+
/** Widget Host 的生命周期信号;Host 停用或 Bridge 断开时会 abort。 */
|
|
104
|
+
readonly signal: AbortSignal;
|
|
105
|
+
readonly capabilities: WidgetCapabilityApi;
|
|
106
|
+
readonly files: WidgetFileApi;
|
|
107
|
+
readonly tasks: WidgetTaskApi;
|
|
108
|
+
readonly network: WidgetNetworkClient;
|
|
109
|
+
readonly ui: WidgetUiApi;
|
|
110
|
+
readonly events: WidgetEventApi;
|
|
111
|
+
readonly subscriptions: {
|
|
112
|
+
add(registration: WidgetDisposeRegistration): void;
|
|
113
|
+
};
|
|
114
|
+
render(handler: WidgetRenderHandler): WidgetDisposable;
|
|
115
|
+
resize(width: number, height: number): void;
|
|
116
|
+
}
|
|
117
|
+
export type WidgetActivation = (context: WidgetContext) => void | WidgetDisposeRegistration | PromiseLike<WidgetDisposeRegistration | void>;
|
|
118
|
+
export interface WidgetHostOptions extends Omit<CreateWidgetClientOptions, "widgetSdkVersion" | "events" | "onInit" | "onRender" | "onUpdate" | "onEvent" | "onDispose"> {
|
|
119
|
+
readonly events?: Pick<NonNullable<WidgetClientFactoryOptions["events"]>, "onError">;
|
|
120
|
+
/** Widget Host 启动失败时的统一处理入口。 */
|
|
121
|
+
readonly onStartupError?: (error: Error) => void;
|
|
122
|
+
}
|
|
123
|
+
export interface WidgetModule {
|
|
124
|
+
activate: WidgetActivation;
|
|
125
|
+
deactivate?: () => void | PromiseLike<void>;
|
|
126
|
+
}
|
|
127
|
+
export interface WidgetHandle {
|
|
128
|
+
/** 供测试、开发工具和 Host 编排等待激活完成。 */
|
|
129
|
+
readonly whenReady: Promise<void>;
|
|
130
|
+
readonly state: () => WidgetClientState;
|
|
131
|
+
dispose(): void;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Generated Widget Host 的唯一运行入口。Widget 作者只导出 activate/deactivate,
|
|
135
|
+
* 不直接调用此函数;CLI 生成的 widget-host.ts 负责调用它。
|
|
136
|
+
*/
|
|
137
|
+
export declare function runWidgetHost(widget: WidgetModule | WidgetActivation, options?: WidgetHostOptions): WidgetHandle;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dp-bohrium/widget-sdk",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Bohrium Widget 开发者 SDK:在 Widget Bundle 内与 Host 建立 Bridge,并暴露生命周期与能力协议。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"files": [
|
|
39
39
|
"dist",
|
|
40
40
|
"README.md",
|
|
41
|
+
"CHANGELOG.md",
|
|
41
42
|
"LICENSE"
|
|
42
43
|
],
|
|
43
44
|
"sideEffects": false,
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
"access": "public"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
48
|
-
"@dp-bohrium/widget-contracts": "0.0
|
|
49
|
+
"@dp-bohrium/widget-contracts": "0.1.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@biomejs/biome": "^2.2.0",
|