page-agent-sdk 2.4.1 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,30 +5,33 @@ From the smallest working setup to a full-featured integration. Read top-down; s
5
5
  ## Stage 0 — Prerequisites
6
6
 
7
7
  - An OpenAI-compatible LLM endpoint (DeepSeek works out of the box). Get an API key.
8
- - Page data you want the AI to edit, placed on `window` (e.g. `window.app = { ... }`).
8
+ - A main data object you want the AI to edit (any plain or reactive object).
9
9
 
10
10
  ## Stage 1 — Minimal (5 lines, CDN, no build)
11
11
 
12
- Drop into any HTML page. The built-in dialog mounts itself. (`systemPrompt` is optional — a built-in default is used if omitted: a generic page-operation assistant + `systemPromptHelpers.reliableWriteRules`. Shown here explicitly for clarity.)
12
+ Drop into any HTML page. The built-in dialog mounts itself. (`systemPrompt` is optional — a built-in default is used if omitted: a generic JSON-operation assistant + `systemPromptHelpers.reliableWriteRules`. Shown here explicitly for clarity.)
13
13
 
14
14
  ```html
15
15
  <div id="root"></div>
16
16
  <script src="https://unpkg.com/page-agent-sdk"></script>
17
17
  <script>
18
- window.app = { title: 'Hello', theme: 'light' }
18
+ const app = { title: 'Hello', theme: 'light' } // plain object (no window needed)
19
19
  ChatSdk.createChatSdk({
20
20
  container: '#root',
21
21
  llm: { apiKey: 'sk-...', baseUrl: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
22
- systemPrompt: 'You are a page assistant. Read/write window.app via tools.',
23
- dataSlots: [
24
- { path: 'app.title', description: '标题', schema: ChatSdk.z.string() },
25
- { path: 'app.theme', description: '主题', schema: ChatSdk.z.enum(['light','dark']) },
26
- ],
22
+ systemPrompt: 'You are a JSON operation assistant. Read/write the main data via tools.',
23
+ data: {
24
+ schema: ChatSdk.z.object({
25
+ title: ChatSdk.z.string().describe('标题'),
26
+ theme: ChatSdk.z.enum(['light', 'dark']).describe('主题'),
27
+ }),
28
+ bind: app,
29
+ },
27
30
  }).mount()
28
31
  </script>
29
32
  ```
30
33
 
31
- Talk to it: "change theme to dark" → AI calls `write({ path:'app.theme', value:'dark' })` → `window.app.theme === 'dark'`.
34
+ Talk to it: "change theme to dark" → AI calls `write({ value: 'dark', patch: { op: 'set', jsonPath: 'theme' } })` → `app.theme === 'dark'`.
32
35
 
33
36
  ## Stage 2 — npm + module project
34
37
 
@@ -40,17 +43,20 @@ npm i page-agent-sdk zod @langchain/openai @langchain/core
40
43
  import { createChatSdk, z } from 'page-agent-sdk'
41
44
  import 'page-agent-sdk/style.css'
42
45
 
43
- window.app = { title: 'Hello', theme: 'light', items: [] }
46
+ const app = { title: 'Hello', theme: 'light', items: [] }
44
47
 
45
48
  const sdk = createChatSdk({
46
49
  container: '#root',
47
50
  llm: { apiKey: import.meta.env.VITE_AI_API_KEY, baseUrl: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
48
- systemPrompt: 'You are a page assistant. Read/write window.app via tools.',
49
- dataSlots: [
50
- { path: 'app.title', description: '标题', schema: z.string() },
51
- { path: 'app.theme', description: '主题', schema: z.enum(['light','dark']) },
52
- { path: 'app.items', description: '列表项', schema: z.array(z.object({ name: z.string(), price: z.number() })) },
53
- ],
51
+ systemPrompt: 'You are a JSON operation assistant. Read/write the main data via tools.',
52
+ data: {
53
+ schema: z.object({
54
+ title: z.string().describe('标题'),
55
+ theme: z.enum(['light', 'dark']).describe('主题'),
56
+ items: z.array(z.object({ name: z.string(), price: z.number() })).describe('列表项'),
57
+ }),
58
+ bind: app,
59
+ },
54
60
  }).mount()
55
61
  ```
56
62
 
@@ -61,10 +67,10 @@ Subscribe via `onEvent` (constructor) or `sdk.hook` (runtime, multi-listener, ca
61
67
  ```ts
62
68
  const sdk = createChatSdk({
63
69
  onEvent(e) {
64
- if (e.type === 'data_slot_change') renderUI() // host page reactive refresh
70
+ if (e.type === 'data_change') renderUI() // host page refresh (plain-object bind needs this; reactive bind auto-refreshes)
65
71
  if (e.type === 'error') console.error(e.message)
66
72
  },
67
- // ...llm, dataSlots...
73
+ // ...llm, data...
68
74
  }).mount()
69
75
 
70
76
  // runtime listener (e.g. analytics), cancellable
@@ -72,6 +78,8 @@ const off = sdk.hook((e) => { if (e.type === 'tool_call') track(e.name) })
72
78
  // off()
73
79
  ```
74
80
 
81
+ > For Vue + `reactive()` bind, template/watch auto-refresh on write — no manual notify needed. For plain-object bind (React/vanilla/Node), subscribe `data_change` to re-render. Both can coexist (reactive for UI, `onEvent` for audit).
82
+
75
83
  ## Stage 4 — Headless (custom UI, framework-agnostic)
76
84
 
77
85
  No built-in dialog; drive the reactive `messages` array yourself.
@@ -79,7 +87,7 @@ No built-in dialog; drive the reactive `messages` array yourself.
79
87
  ```ts
80
88
  const sdk = createChatSdk({
81
89
  ui: false, // headless
82
- llm: { ... }, systemPrompt: '...', dataSlots: [...],
90
+ llm: { ... }, systemPrompt: '...', data: { schema, bind: appObj },
83
91
  }).mount()
84
92
 
85
93
  // your own UI reads sdk.messages (reactive) and calls sdk.send
@@ -92,10 +100,10 @@ Reusable `ChatDialog` / `MessageContent` / `CodePreview` components + `useChat`
92
100
 
93
101
  ```ts
94
102
  createChatSdk({
95
- // ...llm, dataSlots...
103
+ // ...llm, data...
96
104
  capabilities: { verify: true }, // write-back self-check before agent returns
97
105
  verify: { maxAttempts: 2 }, // auto-correct on failure (default check = write-back read + schema)
98
- approval: { tools: ['write'] }, // human-confirm before writes
106
+ approval: { tools: ['write'] }, // human-confirm before writes
99
107
  checkpoint: true, // session-level rollback on bad edits
100
108
  maxParallelTools: 1, // serial tool calls (safe for stateful middleware)
101
109
  contextPreset: 'conservative', // save cost on long sessions
@@ -107,43 +115,35 @@ createChatSdk({
107
115
  ```ts
108
116
  createChatSdk({
109
117
  id: 'my-page-agent', // STABLE id (multi-agent isolation); omit = random + warn
110
- storage: 'indexed', // persist messages/vfs/todos/memory to IndexedDB
111
- // ...llm, dataSlots...
118
+ storage: 'indexed', // persist messages/vfs/todos/memory to IndexedDB (NOT bind — store & re-inject via sdk.setData)
119
+ // ...llm, data...
112
120
  }).mount()
113
121
 
114
122
  // later, switch session:
115
123
  await sdk.switchSession('session-abc') // load or create
116
124
  ```
117
125
 
118
- ## Stage 7 — Dynamic dataSlots (lazy-loaded components)
126
+ ## Stage 7 — Swap data at runtime (dynamic / lazy-loaded schema)
119
127
 
120
- Components loaded on demand with **different schemas each** register at mount, unregister at unmount. No need to pre-declare every possible component at `createChatSdk`. The agent picks up new registrations immediately (no rebuild); `summarization` also embeds a live registry snapshot in compressed summaries so the agent won't act on stale memory.
128
+ When the page schema changes dynamically (e.g. lazy-loaded components with different structures), swap the whole main data config at runtime tools pick up the new bind/schema immediately, no rebuild.
121
129
 
122
130
  ```ts
123
131
  const sdk = createChatSdk({
124
132
  container: '#root', llm: { ... },
125
- // only the static container is pre-declared; per-component paths are dynamic
126
- dataSlots: [{ path: 'app.components', description: '动态组件容器(按 id 存)', schema: z.record(z.string(), z.any()) }],
133
+ data: { schema: initialSchema, bind: initialObj, description: '初始数据' },
127
134
  }).mount()
128
135
 
129
- // component mounts (lazy) register its schema, immediately operative
130
- function mountComp(comp: { id: string; type: 'banner' | 'card' | 'stat' | 'chart' }) {
131
- window.app.components[comp.id] = reactive(comp)
132
- sdk.addDataSlot({
133
- path: `app.components.${comp.id}`,
134
- description: `${typeDescriptions[comp.type]}`, // ← give the LLM field-level detail (it can't see the zod schema)
135
- schema: compSchemas[comp.type], // validation guardrail
136
- })
137
- }
138
- // component unmounts → unregister (snapshot stack cleaned too)
139
- function unmountComp(id: string) {
140
- delete window.app.components[id]
141
- sdk.removeDataSlot(`app.components.${id}`)
142
- }
143
- sdk.listDataSlots() // live registry (reflects dynamic add/remove)
136
+ // later: swap to a different schema + bind (lazy-loaded / dynamic)
137
+ sdk.setData({
138
+ schema: newSchema, // new zod schema (validation + field hints auto-injected)
139
+ bind: newObj, // new reactive/plain object
140
+ description: '新数据',
141
+ })
142
+ // tools now operate on newObj with newSchema — immediately, no rebuild
143
+ sdk.getData() // read current config
144
144
  ```
145
145
 
146
- > Key points: `description` is the LLM's only source of field structure (write it in detail); `schema` is the validation guardrail (the LLM never sees it). Write operations return the current operable path list; long-session compression keeps a live registry snapshot + preserved `describe`/`list` results so the agent never loses track of dynamic components.
146
+ > `summarization` auto-embeds the current data description in compressed summaries, so the agent won't act on stale memory after a swap. Snapshots & optimistic-lock hash reset on swap (old snapshots cleared).
147
147
 
148
148
  **Full runnable demo**: `examples/dynamic-demo/` (`npm run dev` → `/examples/dynamic-demo/`).
149
149
 
@@ -1,13 +1,13 @@
1
1
  # Use cases — end-to-end scenarios
2
2
 
3
- Concrete integration patterns for common scenarios. Each shows the key `dataSlots` + options that matter. Adapt the LLM config to your provider.
3
+ Concrete integration patterns for common scenarios. Each shows the key `data` + options that matter. Adapt the LLM config to your provider.
4
4
 
5
5
  ## 1. Low-code page builder
6
6
 
7
7
  A visual builder where the page is a component tree; the AI edits the tree via jsonPath patches and the canvas re-renders live.
8
8
 
9
9
  ```ts
10
- window.page = {
10
+ const page = {
11
11
  components: [
12
12
  { id: 'banner', type: 'banner', props: { title: 'Welcome', bg: '#1f4d3a' } },
13
13
  { id: 'card1', type: 'card', props: { title: '新品', price: 99 } },
@@ -18,16 +18,18 @@ createChatSdk({
18
18
  container: '#chat',
19
19
  llm: { apiKey, baseUrl: 'https://api.deepseek.com/v1', model: 'deepseek-chat', temperature: 0.3 },
20
20
  systemPrompt: '你是页面搭建助手。用 write 的 patch 按 jsonPath 增量改 components,不要重传整树。',
21
- dataSlots: [
22
- { path: 'page.components', description: '组件树',
23
- schema: z.array(z.object({
21
+ data: {
22
+ schema: z.object({
23
+ components: z.array(z.object({
24
24
  id: z.string(), type: z.string(),
25
25
  props: z.record(z.any()),
26
- })) },
27
- ],
28
- onEvent(e) { if (e.type === 'data_slot_change') renderCanvas() }, // canvas reactive refresh
26
+ })).describe('组件树'),
27
+ }),
28
+ bind: page,
29
+ },
30
+ onEvent(e) { if (e.type === 'data_change') renderCanvas() }, // canvas refresh (plain-object bind)
29
31
  checkpoint: true, // bad edit → one-click rollback
30
- approval: { tools: ['write'] }, // confirm writes
32
+ approval: { tools: ['write'] }, // confirm writes
31
33
  }).mount()
32
34
  ```
33
35
 
@@ -38,7 +40,7 @@ User: "顶部 Banner 改深色、主标题加粗、加一张新品卡" → AI ca
38
40
  Form schema as data; AI edits field definitions, schema validation prevents malformed forms.
39
41
 
40
42
  ```ts
41
- window.form = {
43
+ const form = {
42
44
  fields: [
43
45
  { name: 'phone', label: '手机号', type: 'text', required: true, validation: 'none' },
44
46
  { name: 'address', label: '地址', type: 'text', required: false, cascade: false },
@@ -47,29 +49,31 @@ window.form = {
47
49
 
48
50
  createChatSdk({
49
51
  container: '#chat', llm: { ... },
50
- systemPrompt: '你是表单设计助手。改 form.fields 的字段定义,保持 schema 合法。',
51
- dataSlots: [
52
- { path: 'form.fields', description: '字段定义数组',
53
- schema: z.array(z.object({
52
+ systemPrompt: '你是表单设计助手。改 fields 的字段定义,保持 schema 合法。',
53
+ data: {
54
+ schema: z.object({
55
+ fields: z.array(z.object({
54
56
  name: z.string(), label: z.string(),
55
57
  type: z.enum(['text','number','select','date']),
56
58
  required: z.boolean(),
57
59
  validation: z.enum(['none','phone','email','idcard']),
58
60
  cascade: z.boolean().optional(),
59
- })) },
60
- ],
61
- onEvent(e) { if (e.type === 'data_slot_change') renderForm() },
61
+ })).describe('字段定义数组'),
62
+ }),
63
+ bind: form,
64
+ },
65
+ onEvent(e) { if (e.type === 'data_change') renderForm() },
62
66
  }).mount()
63
67
  ```
64
68
 
65
- User: "手机号加格式校验、地址改三级联动" → AI patches `form.fields[0].validation='phone'`, `form.fields[1].cascade=true`.
69
+ User: "手机号加格式校验、地址改三级联动" → AI patches `fields.0.validation='phone'`, `fields.1.cascade=true`.
66
70
 
67
71
  ## 3. CMS batch operation
68
72
 
69
- Bulk-edit a product list; use `eval_script` or `search_data_slot` + `write` with `patch` for batch ops.
73
+ Bulk-edit a product list; use `eval_script` or `search_data` + `write` with `patch` for batch ops.
70
74
 
71
75
  ```ts
72
- window.products = [
76
+ const products = [
73
77
  { id: 1, title: '商品A', price: 99, highlight: false },
74
78
  { id: 2, title: '商品B', price: 150, highlight: false },
75
79
  // ...hundreds
@@ -77,25 +81,27 @@ window.products = [
77
81
 
78
82
  createChatSdk({
79
83
  container: '#chat', llm: { ... },
80
- systemPrompt: '你是运营助手。批量改 products;标题加前缀用 eval_script,按条件筛选用 search_data_slot。',
81
- dataSlots: [
82
- { path: 'products', description: '商品列表',
83
- schema: z.array(z.object({
84
+ systemPrompt: '你是运营助手。批量改 products;标题加前缀用 eval_script,按条件筛选用 search_data。',
85
+ data: {
86
+ schema: z.object({
87
+ products: z.array(z.object({
84
88
  id: z.number(), title: z.string(), price: z.number(), highlight: z.boolean(),
85
- })) },
86
- ],
87
- onEvent(e) { if (e.type === 'data_slot_change') renderTable() },
89
+ })).describe('商品列表'),
90
+ }),
91
+ bind: { products },
92
+ },
93
+ onEvent(e) { if (e.type === 'data_change') renderTable() },
88
94
  }).mount()
89
95
  ```
90
96
 
91
- User: "标题加『限时』前缀、低于 100 元的标红" → AI uses `eval_script` for the prefix loop + `search_data_slot` to find `<100` then `write` with `patch` to set `highlight`.
97
+ User: "标题加『限时』前缀、低于 100 元的标红" → AI uses `eval_script` for the prefix loop + `search_data` to find `<100` then `write` with `patch` to set `highlight`.
92
98
 
93
99
  ## 4. Ops config console
94
100
 
95
101
  Edit experiment thresholds / feature flags with human confirmation.
96
102
 
97
103
  ```ts
98
- window.config = {
104
+ const config = {
99
105
  expA: { threshold: 0.5, enabled: true },
100
106
  featureB: { enabled: false },
101
107
  }
@@ -103,12 +109,13 @@ window.config = {
103
109
  createChatSdk({
104
110
  container: '#chat', llm: { ... },
105
111
  systemPrompt: '你是运维助手。改 config 前必须经用户确认。',
106
- dataSlots: [
107
- { path: 'config.expA', description: '实验A',
108
- schema: z.object({ threshold: z.number().min(0).max(1), enabled: z.boolean() }) },
109
- { path: 'config.featureB', description: 'B开关',
110
- schema: z.object({ enabled: z.boolean() }) },
111
- ],
112
+ data: {
113
+ schema: z.object({
114
+ expA: z.object({ threshold: z.number().min(0).max(1), enabled: z.boolean() }).describe('实验A'),
115
+ featureB: z.object({ enabled: z.boolean() }).describe('B开关'),
116
+ }),
117
+ bind: config,
118
+ },
112
119
  approval: { tools: ['write'] }, // human-in-the-loop
113
120
  checkpoint: true,
114
121
  capabilities: { verify: true }, // write-back read + schema check
@@ -119,7 +126,7 @@ User: "A 实验阈值调到 30%、关掉 B 开关" → AI proposes writes → us
119
126
 
120
127
  ## 5. AI-native assistant (no page data, custom tools)
121
128
 
122
- The agent drives your product's own API via custom tools (no dataSlotOps).
129
+ The agent drives your product's own API via custom tools (no dataOps).
123
130
 
124
131
  ```ts
125
132
  const lookupTool = defineTool({
@@ -134,7 +141,7 @@ createChatSdk({
134
141
  llm: { ... },
135
142
  systemPrompt: '你是订单助手。用 lookup_order 查询。',
136
143
  tools: [lookupTool],
137
- capabilities: { dataSlotOps: false, fetch: false }, // pure custom-tool agent
144
+ capabilities: { dataOps: false, fetch: false }, // pure custom-tool agent
138
145
  }).mount()
139
146
  ```
140
147
 
@@ -146,15 +153,15 @@ Pure research: fetch docs, parallel subagents for multi-source investigation.
146
153
  createChatSdk({
147
154
  container: '#chat', llm: { ... },
148
155
  systemPrompt: '你是调研助手。多源对比用 spawn_agents 并行委派。',
149
- capabilities: { dataSlotOps: false }, // read-only, no page edits
156
+ capabilities: { dataOps: false }, // read-only, no data edits
150
157
  subagent: { allowedTools: ['fetch_document'] },
151
- contextPreset: 'conservative', // long research sessions
158
+ contextPreset: 'conservative', // long research sessions
152
159
  }).mount()
153
160
  ```
154
161
 
155
162
  ## 7. Headless server-side (Node.js)
156
163
 
157
- Run the agent in Node (no browser). Provide `globalThis.window` only if you enable dataSlotOps.
164
+ Run the agent in Node (no browser). dataOps body works in Node with any `bind` object; only `eval_script` needs Web Worker (disable via `capabilities:{dataOps:false}` if unused).
158
165
 
159
166
  ```ts
160
167
  // node mjs
@@ -165,7 +172,8 @@ const sdk = createChatSdk({
165
172
  storage: 'memory',
166
173
  llm: { apiKey, baseUrl, model },
167
174
  systemPrompt: '...',
168
- capabilities: { dataSlotOps: false, fetch: false },
175
+ data: { schema: z.object({ result: z.string() }), bind: { result: '' } },
176
+ capabilities: { fetch: false }, // dataOps body works; eval_script needs worker
169
177
  tools: [/* your tools */],
170
178
  })
171
179
  await sdk.mount()
@@ -179,9 +187,9 @@ sdk.unmount()
179
187
  Two dialogs backed by one agent brain.
180
188
 
181
189
  ```ts
182
- const a = createChatSdk({ id: 'shared', container: '#dlg-a', llm: {...}, shareContext: true, dataSlots }).mount()
183
- const b = createChatSdk({ id: 'shared', container: '#dlg-b', llm: {...}, shareContext: true, dataSlots }).mount()
184
- // a & b share messages/agent/vfs/todos/memory — two views of one agent
190
+ const a = createChatSdk({ id: 'shared', container: '#dlg-a', llm: {...}, shareContext: true, data }).mount()
191
+ const b = createChatSdk({ id: 'shared', container: '#dlg-b', llm: {...}, shareContext: true, data }).mount()
192
+ // a & b share messages/agent/vfs/todos/memory/bind — two views of one agent
185
193
  ```
186
194
 
187
195
  ## 9. MCP integration (external tool servers)
@@ -199,33 +207,29 @@ createChatSdk({
199
207
 
200
208
  > Note: `@modelcontextprotocol/sdk` is an optional peerDep — install it only if you use `mcp`. Browser supports only remote transports (http/sse/websocket), not stdio.
201
209
 
202
- ## 10. Lazy-loaded components with dynamic schemas (dynamic dataSlots)
210
+ ## 10. Dynamic / lazy-loaded schema (swap main data at runtime)
203
211
 
204
- Components loaded on demand with **different structures each** register their schema at mount time, unregister at unmount. No need to pre-declare every possible component at `createChatSdk`.
212
+ When the page schema changes dynamically (lazy-loaded components with different structures), swap the whole main data config at runtime tools pick up the new bind/schema immediately, no rebuild.
205
213
 
206
214
  ```ts
207
215
  const sdk = createChatSdk({
208
216
  container: '#chat', llm: { ... },
209
- // only the static container is pre-declared; per-component paths are dynamic
210
- dataSlots: [{ path: 'app.components', description: '动态组件容器', schema: z.record(z.any()) }],
211
- systemPrompt: '用 read() 查看当前可操作的组件 path,再按各自 schema 操作',
217
+ data: { schema: initialSchema, bind: initialObj, description: '初始数据' },
218
+ systemPrompt: ' read() 查看当前可操作字段,按 schema 操作',
212
219
  }).mount()
213
220
 
214
- // 组件挂载(懒加载)→ 动态注册其 schema,立即对 AI 生效
215
- function mountComp(comp: { id: string; type: CompType }) {
216
- window.app.components[comp.id] = reactive(comp)
217
- sdk.addDataSlot({
218
- path: `app.components.${comp.id}`,
219
- description: `${comp.type} 组件`,
220
- schema: compSchemas[comp.type], // 结构各异:banner/card/stat/chart 各自 schema
221
+ // later: swap to a different schema + bind (lazy-loaded / dynamic)
222
+ function onComponentTypeChange(comp: { schema: z.ZodType; bind: any }) {
223
+ sdk.setData({
224
+ schema: comp.schema, // new zod schema (validation + field hints auto-injected)
225
+ bind: comp.bind, // new reactive/plain object
226
+ description: `${comp.type} 组件数据`,
221
227
  })
228
+ // tools now operate on comp.bind with comp.schema — immediately, no rebuild
222
229
  }
223
- // 组件卸载 → 动态移除注册(快照栈一并清理)
224
- function unmountComp(id: string) {
225
- delete window.app.components[id]
226
- sdk.removeDataSlot(`app.components.${id}`)
227
- }
230
+
231
+ sdk.getData() // read current config (reflects runtime swap)
228
232
  ```
229
233
 
230
234
  **完整可运行示例**:`examples/dynamic-demo/`(`npm run dev` → `/examples/dynamic-demo/`)。
231
- **何时用**:可视化编辑器/低代码平台中,组件按需加载且结构各异(图表/表单/卡片 schema 各不同),无法在初始化时穷举所有组件 schema。详见 `advanced.md` §0。
235
+ **何时用**:可视化编辑器/低代码平台中,组件按需加载且结构各异(图表/表单/卡片 schema 各不同),无法在初始化时确定唯一 schema。详见 `advanced.md` §0。
package/types/index.d.ts CHANGED
@@ -231,7 +231,13 @@ export interface VerifyMiddlewareOptions {
231
231
  export interface WriteBackCheckOptions {
232
232
  /** name → zod schema(由 createChatSdk 从 data 构造注入,键 '' 代表主数据);省略则只校验「读回非空」 */
233
233
  schemas?: Record<string, any>;
234
- /** 读 window 的根对象(默认 globalThis.window) */
234
+ /**
235
+ * 读回的根对象。优先于 `window`。
236
+ * - 单对象 data 模式:传 bind 对象(或 getter `() => liveData()?.bind`,适配 sdk.setData 运行时替换)
237
+ * - 旧 windowProps 模式:省略则用 `window`(默认 globalThis.window)
238
+ */
239
+ root?: unknown | (() => unknown);
240
+ /** 读 window 的根对象(旧 windowProps 模式;data 模式应传 root)。默认 globalThis.window */
235
241
  window?: unknown;
236
242
  }
237
243