page-agent-sdk 1.1.4 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "page-agent-sdk",
3
- "version": "1.1.4",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "框架无关的页面内 Agent JS SDK —— 以对话框形态挂载到任意网页,通过自定义 tool 读写宿主 window 属性(GET 抓文档),具备 planning/skills/虚拟工作区/快照回退/context 管理能力。Vue 打包进库,使用者无需安装 Vue。",
6
6
  "main": "./dist/page-agent-sdk.umd.cjs",
@@ -27,6 +27,8 @@ See `demo/plain.html` for a framework-agnostic importmap + esm.sh example.
27
27
 
28
28
  Put the page data on `window` (e.g. `window.app = { title, theme }`), then declare each writable path with a zod schema. The agent can ONLY touch declared paths; `set`/`edit` are schema-validated (invalid → structured error, no write).
29
29
 
30
+ > `systemPrompt` is optional — a built-in default is used if omitted (generic page-operation assistant + `systemPromptHelpers.reliableWriteRules`: read-before-write, list in dynamic scenarios, fields per `describe`, retry on validation error, prefer incremental `edit`). Passing your own fully overrides it; append `systemPromptHelpers.reliableWriteRules` yourself if you want the rules.
31
+
30
32
  ```ts
31
33
  import { createChatSdk, z } from 'page-agent-sdk'
32
34
  import 'page-agent-sdk/style.css'
@@ -90,8 +92,9 @@ Event types: `window_prop_change` / `message_update` / `tool_call` / `tool_resul
90
92
  | **Headless / server-side** | `ui:false` + `storage:'memory'` + `capabilities:{windowOps:false,fetch:false}`; drive via `sdk.send` |
91
93
  | **Multi-agent on one page** | same `id` + `shareContext:true` → multiple dialogs share one `AgentCore` |
92
94
  | **MCP integration** | `mcp:[{transport,url}]` remote tool servers; `@modelcontextprotocol/sdk` optional peerDep |
95
+ | **Lazy-loaded components (dynamic schemas)** | `sdk.addWindowProp(spec)` on component mount / `removeWindowProp` on unmount; tools pick up new registrations immediately, no rebuild. See [references/advanced.md §0](references/advanced.md) |
93
96
 
94
- When the user describes a scenario, map it to the row above and load `references/use-cases.md` for the matching numbered case (1→9) with copy-paste code.
97
+ When the user describes a scenario, map it to the row above and load `references/use-cases.md` for the matching numbered case (1→10) with copy-paste code. For dynamic/lazy-loaded component schemas, custom tools/skills/subagents/MCP, load [references/advanced.md](references/advanced.md).
95
98
 
96
99
  ## References (read as needed)
97
100
 
@@ -100,8 +103,8 @@ Detailed docs live in this skill's `references/` folder — load the one matchin
100
103
  - **[references/quickstart.md](references/quickstart.md)** — progressive setup from 5-line CDN to full-featured (Stages 0→6). Read when the user wants a step-by-step "from simple to complete" walkthrough.
101
104
  - **[references/options.md](references/options.md)** — every `createChatSdk` option: type, default, purpose & when to use. Read when the user asks "what does option X do" or needs to tune behavior.
102
105
  - **[references/api.md](references/api.md)** — instance methods (`mount`/`send`/`stream`/`inspect`/`switchSession`/`hook`/checkpoints), `defineTool`/`defineSkill`/`presets`, built-in window tools, and the full `SdkEvent` type table. Read when the user asks about APIs, tools, or events.
103
- - **[references/use-cases.md](references/use-cases.md)** — 9 end-to-end scenarios (low-code builder / form designer / CMS batch / ops console / AI-native / research / server-side / multi-agent / MCP). Read when the user wants a concrete pattern for their use case.
104
- - **[references/advanced.md](references/advanced.md)** — detailed examples for the four extensibility surfaces: custom `defineTool` (with error handling + coexisting with windowOps), `defineSkill` (inline content + remote doc), subagents (ad-hoc `spawn_agent`/`spawn_agents` + pre-declared `subagents` → `use_<id>`), MCP (http/sse/websocket + auth + dev gotcha). Read when the user asks "how to add custom tools / skills / subagents / MCP".
106
+ - **[references/use-cases.md](references/use-cases.md)** — 10 end-to-end scenarios (low-code builder / form designer / CMS batch / ops console / AI-native / research / server-side / multi-agent / MCP / lazy-loaded dynamic components). Read when the user wants a concrete pattern for their use case.
107
+ - **[references/advanced.md](references/advanced.md)** — detailed examples for the extensibility surfaces: **dynamic windowProps (`sdk.addWindowProp`/`removeWindowProp` for lazy-loaded components)**, custom `defineTool` (with error handling + coexisting with windowOps), `defineSkill` (inline content + remote doc), subagents (ad-hoc `spawn_agent`/`spawn_agents` + pre-declared `subagents` → `use_<id>`), MCP (http/sse/websocket + auth + dev gotcha). Read when the user asks "how to add custom tools / skills / subagents / MCP" or "lazy-load components with different schemas".
105
108
 
106
109
  Project-level docs (in the repo, not bundled in this skill):
107
110
  - `doc/usage-guide.md` (zh) / `doc/usage-guide.en.md` — full options reference
@@ -1,6 +1,73 @@
1
- # Advanced examples — custom tools, skills, subagents, MCP
1
+ # Advanced examples — custom tools, skills, subagents, MCP, dynamic windowProps
2
2
 
3
- Detailed, copy-paste examples for the four extensibility surfaces. Read the section matching the user's need.
3
+ Detailed, copy-paste examples for the extensibility surfaces. Read the section matching the user's need.
4
+
5
+ ## 0. Dynamic windowProps (lazy-loaded components) — `sdk.addWindowProp` / `removeWindowProp`
6
+
7
+ When components are lazy-loaded with **different schemas each**, don't declare all `windowProps` upfront. Register them at runtime as components mount/unmount. The agent's window tools pick up new registrations immediately (no agent rebuild).
8
+
9
+ ```ts
10
+ const sdk = createChatSdk({
11
+ container: '#chat', llm: { ... },
12
+ systemPrompt: '你是页面助手,按组件类型操作 window.app.components.<id>。',
13
+ windowProps: [
14
+ // statically-declared ones (always present)
15
+ { path: 'app.config', description: '全局配置', schema: z.record(z.any()) },
16
+ ],
17
+ }).mount()
18
+
19
+ // 组件懒加载时动态注册其 schema(结构各异)
20
+ function onComponentMount(comp: { id: string; type: string; schema: z.ZodType }) {
21
+ sdk.addWindowProp({ path: `app.components.${comp.id}`, description: `${comp.type} 组件`, schema: comp.schema })
22
+ // 立即生效:AI 现在能 set/edit_window_prop 这个 path,按其 schema 校验
23
+ }
24
+
25
+ // 组件卸载时移除(快照栈一并清理)
26
+ function onComponentUnmount(id: string) {
27
+ sdk.removeWindowProp(`app.components.${id}`)
28
+ }
29
+
30
+ // 查看当前所有注册项(反映动态增删)
31
+ const current: WindowPropSpec[] = sdk.listWindowProps()
32
+ ```
33
+
34
+ Notes:
35
+ - `addWindowProp` 覆盖同名 path 时保留旧快照栈;按新 schema 校验。
36
+ - 动态注册的属性**不自动纳入 checkpoint 快照**(checkpoint 的 windowPaths 在构造时固定);如需回滚动态组件,自行管理或重建。
37
+ - `inspect().windowProps` 与 `verify`(默认 `createWriteBackCheck`)均反映动态注册的最新 schemas(verify 每次 check 实时取 `listWindowProps()`)。
38
+ - `capabilities.windowOps:false` 时 `addWindowProp`/`removeWindowProp` 为 no-op(并 warn)。
39
+
40
+ **完整可运行示例**:`examples/dynamic-demo/`(dev 启动后访问 `/examples/dynamic-demo/`)—— 演示加载/卸载结构各异的组件(banner/card/stat/chart),挂载即 `addWindowProp` 注册其 schema,AI 立即可按各自 schema 操作,卸载即 `removeWindowProp`;右侧实时显示 `sdk.listWindowProps()` 反映动态增删。
41
+
42
+ ### 动态场景下「压缩后不丢信息」的保障(内置,无需额外配置)
43
+
44
+ 动态组件随时增删,长会话压缩后 LLM 可能基于过时记忆操作已卸载的组件、或不知道新组件已注册。SDK 内置两道保障:
45
+
46
+ - **A. 压缩时注入注册表快照**:`summarization` 中间件压缩 older 轮次时,自动把当前 `listWindowProps()` 的 `path + description` 作为一段附进摘要 system 消息(不进压缩)。LLM 即便忘了历史 `describe`,每轮仍看得到「当前有哪些可操作 path」,不会再去操作已卸载的组件。`windowOps` 关闭时返回空,无影响。
47
+ - **C. preserveLastToolResults**:`contextOptions.preserveLastToolResults`(默认 `['describe_window_prop','list_window_props']`)指定这些工具的步骤 `result` 在跨轮摘要时额外保留摘要片段进 summaryMsg。即便 older 轮被摘要,关键字段说明仍在摘要里,LLM 不必反复 `describe`。设为 `[]` 关闭。
48
+
49
+ ```ts
50
+ // 默认即开启 A + C;如需关闭或自定义:
51
+ createChatSdk({
52
+ contextOptions: {
53
+ preserveLastToolResults: [], // 关闭 C(不保留工具结果摘要)
54
+ // getRegisteredProps 由 SDK 内部注入(来自 sdk.listWindowProps),无需手动传
55
+ },
56
+ // ...
57
+ })
58
+ ```
59
+
60
+ - **B. 写操作返回附当前可操作 path 列表**:`set`/`edit`/`delete` 成功返回末尾自动附 `(当前可操作 path: a, b, c)`,LLM 写完即知全貌,多组件批量场景减少 `list` 调用。超过 8 项或过长时只报数量,避免提示过长。
61
+ - **D. `systemPromptHelpers.reliableWriteRules`**:导出的标准化「可靠写入规则」片段,建议拼进 `systemPrompt`:
62
+
63
+ ```ts
64
+ import { systemPromptHelpers } from 'page-agent-sdk'
65
+ createChatSdk({
66
+ systemPrompt: `你是页面助手。\n${systemPromptHelpers.reliableWriteRules}`,
67
+ // ...
68
+ })
69
+ ```
70
+ 内容:改前先 `get` 读真实值、动态场景先 `list`、字段以 `describe` 为准、写错看校验错误重试、优先 `edit` 增量 patch。避免集成方忘了写这些元规则导致 LLM 凭记忆瞎改。
4
71
 
5
72
  ## 1. Custom tools (`defineTool`)
6
73
 
@@ -12,6 +12,9 @@
12
12
  | `inspect()` | `() => AgentInfo` | Inspect agent: tools/skills/windowProps/middleware/todos/mcp.servers (each tool's `source`: `builtin`/`mcp:<name>`/`user`). DebugDrawer uses this. |
13
13
  | `switchSession(id?)` | `(id?: string) => Promise<string>` | Switch session context (load or create by id). Requires `storage` enabled. |
14
14
  | `hook(handler)` | `(h: SdkEventHandler) => () => void` | Runtime event subscription (multi-listener, returns unsubscribe). Complements `onEvent`. |
15
+ | `addWindowProp(spec)` | `(spec: WindowPropSpec) => void` | Runtime register/override a window prop (lazy-loaded components). Takes effect immediately, no rebuild. Needs `windowOps` enabled. |
16
+ | `removeWindowProp(path)` | `(path: string) => boolean` | Remove a registered window prop (component unmount); returns whether it existed. Clears its snapshot stack. |
17
+ | `listWindowProps()` | `() => WindowPropSpec[]` | List currently-registered window props (reflects dynamic add/remove). |
15
18
  | `restoreLastCheckpoint()` | `() => boolean` | Restore last good checkpoint (needs `checkpoint` enabled). |
16
19
  | `listCheckpoints()` | `() => CheckpointMeta[]` | List available checkpoints. |
17
20
 
@@ -59,6 +62,19 @@ createChatSdk({ ...presets.pageBuilder, llm, container }).mount()
59
62
 
60
63
  Spread into options for common scenarios.
61
64
 
65
+ ## systemPromptHelpers (best-practice prompt snippets)
66
+
67
+ ```ts
68
+ import { createChatSdk, systemPromptHelpers } from 'page-agent-sdk'
69
+
70
+ createChatSdk({
71
+ systemPrompt: `你是页面助手。\n${systemPromptHelpers.reliableWriteRules}`,
72
+ llm, container,
73
+ }).mount()
74
+ ```
75
+
76
+ `reliableWriteRules` — standardized "reliable write rules": read before write (`get_window_prop`), list in dynamic scenarios, fields per `describe_window_prop`, retry on schema-validation errors, prefer `edit_window_prop` incremental patches. Recommended for any scenario involving window writes.
77
+
62
78
  ## Built-in window tools (auto-injected when `capabilities.windowOps`)
63
79
 
64
80
  | Tool | Purpose |
@@ -7,7 +7,7 @@ Full reference for `createChatSdk(options)`. Grouped by purpose. Required: `llm`
7
7
  | Option | Type | Default | Purpose / when |
8
8
  |---|---|---|---|
9
9
  | `llm` | `LLMConfig \| BaseChatModel` | — (required) | The model. `LLMConfig = { apiKey, baseUrl, model, temperature?, maxTokens? }` (OpenAI-compatible; DeepSeek default). Or pass any LangChain `BaseChatModel` (e.g. `ChatAnthropic`, install its peerDep). |
10
- | `systemPrompt` | `string` | generic page assistant | Agent identity/instructions. Inject here, not hardcoded. Keep single-line in `.env` (`VITE_AI_SYSTEM_PROMPT`). |
10
+ | `systemPrompt` | `string` | built-in default (generic page assistant + reliable write rules) | Agent identity/instructions. Inject here, not hardcoded. Keep single-line in `.env` (`VITE_AI_SYSTEM_PROMPT`). If omitted, a built-in default is used (page-operation assistant + `systemPromptHelpers.reliableWriteRules`); passing your own fully overrides it (append `systemPromptHelpers.reliableWriteRules` yourself if needed). |
11
11
  | `id` | `string` | random + warn | Stable agent id for multi-agent isolation & persistence. **Must pass a stable value** if you use `storage` or run multiple agents on one page. |
12
12
  | `title` / `placeholder` | `string` | — | Dialog title / input placeholder (cosmetic). |
13
13
 
@@ -70,7 +70,7 @@ Full reference for `createChatSdk(options)`. Grouped by purpose. Required: `llm`
70
70
  | Option | Type | Default | Purpose / when |
71
71
  |---|---|---|---|
72
72
  | `contextPreset` | `'auto'\|'conservative'\|'aggressive'` | `auto` | `conservative` = save cost; `aggressive` = save context. `contextOptions` fine-tunes further. |
73
- | `contextOptions` | `object` | — | Detailed compression params (overrides preset). `false` disables compression. |
73
+ | `contextOptions` | `object` | — | Detailed compression params (overrides preset). `false` disables compression. Key fields: `windowRounds`, `summaryThresholdRounds`, `contextWindow`, `summaryThresholdRatio`, `windowRatio`, `enableRecall`, `recallTopK`, `enableLLMSummary`, `preserveLastToolResults` (default `['describe_window_prop','list_window_props']` — keep these tools' result summaries in the compressed summary so field descriptions survive compression; set `[]` to disable). `getRegisteredProps` is injected internally by the SDK (from `sdk.listWindowProps`) to embed a live registry snapshot in the summary — no need to set it manually. |
74
74
  | `summaryLlm` | `BaseChatModel \| LLMConfig` | main `llm` | Use a cheaper/faster model for summarization. |
75
75
  | `summaryTemperature` | `number` | 0.3 | Summary model temperature. |
76
76
  | `summaryMaxTokens` | `number` | 1024 | Summary output cap. |
@@ -9,7 +9,7 @@ From the smallest working setup to a full-featured integration. Read top-down; s
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.
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.)
13
13
 
14
14
  ```html
15
15
  <div id="root"></div>
@@ -115,6 +115,38 @@ createChatSdk({
115
115
  await sdk.switchSession('session-abc') // load or create
116
116
  ```
117
117
 
118
+ ## Stage 7 — Dynamic windowProps (lazy-loaded components)
119
+
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.
121
+
122
+ ```ts
123
+ const sdk = createChatSdk({
124
+ container: '#root', llm: { ... },
125
+ // only the static container is pre-declared; per-component paths are dynamic
126
+ windowProps: [{ path: 'app.components', description: '动态组件容器(按 id 存)', schema: z.record(z.string(), z.any()) }],
127
+ }).mount()
128
+
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.addWindowProp({
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.removeWindowProp(`app.components.${id}`)
142
+ }
143
+ sdk.listWindowProps() // live registry (reflects dynamic add/remove)
144
+ ```
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.
147
+
148
+ **Full runnable demo**: `examples/dynamic-demo/` (`npm run dev` → `/examples/dynamic-demo/`).
149
+
118
150
  ## Next
119
151
 
120
152
  - All options: see [options.md](options.md)
@@ -198,3 +198,34 @@ createChatSdk({
198
198
  ```
199
199
 
200
200
  > 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
+
202
+ ## 10. Lazy-loaded components with dynamic schemas (dynamic windowProps)
203
+
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`.
205
+
206
+ ```ts
207
+ const sdk = createChatSdk({
208
+ container: '#chat', llm: { ... },
209
+ // only the static container is pre-declared; per-component paths are dynamic
210
+ windowProps: [{ path: 'app.components', description: '动态组件容器', schema: z.record(z.any()) }],
211
+ systemPrompt: '用 list_window_props 查看当前可操作的组件 path,再按各自 schema 操作',
212
+ }).mount()
213
+
214
+ // 组件挂载(懒加载)→ 动态注册其 schema,立即对 AI 生效
215
+ function mountComp(comp: { id: string; type: CompType }) {
216
+ window.app.components[comp.id] = reactive(comp)
217
+ sdk.addWindowProp({
218
+ path: `app.components.${comp.id}`,
219
+ description: `${comp.type} 组件`,
220
+ schema: compSchemas[comp.type], // 结构各异:banner/card/stat/chart 各自 schema
221
+ })
222
+ }
223
+ // 组件卸载 → 动态移除注册(快照栈一并清理)
224
+ function unmountComp(id: string) {
225
+ delete window.app.components[id]
226
+ sdk.removeWindowProp(`app.components.${id}`)
227
+ }
228
+ ```
229
+
230
+ **完整可运行示例**:`examples/dynamic-demo/`(`npm run dev` → `/examples/dynamic-demo/`)。
231
+ **何时用**:可视化编辑器/低代码平台中,组件按需加载且结构各异(图表/表单/卡片 schema 各不同),无法在初始化时穷举所有组件 schema。详见 `advanced.md` §0。
package/types/index.d.ts CHANGED
@@ -168,6 +168,18 @@ export interface WindowOpsOptions {
168
168
  whitelist?: boolean;
169
169
  }
170
170
 
171
+ /** window 属性注册表控制器(运行时动态增删;createWindowOps 返回的工具数组上以不可枚举属性 `controller` 挂载) */
172
+ export interface WindowOpsController {
173
+ /** 新增/覆盖一个属性注册项(运行时懒加载组件场景);覆盖时旧快照栈保留 */
174
+ add(spec: WindowPropSpec): void;
175
+ /** 移除一个属性注册项;返回是否确实存在并移除。快照栈一并清理 */
176
+ remove(path: string): boolean;
177
+ /** 列出当前所有注册项(反映动态增删后的最新状态) */
178
+ list(): WindowPropSpec[];
179
+ /** 是否已注册某 path */
180
+ has(path: string): boolean;
181
+ }
182
+
171
183
  export interface PermissionRule {
172
184
  operations: ('read' | 'write')[];
173
185
  scopes: string[];
@@ -383,6 +395,12 @@ export interface ChatSdk {
383
395
  listCheckpoints(): CheckpointMeta[];
384
396
  /** 运行时订阅 SDK 事件(可多个监听器,返回取消函数);与构造时 onEvent 互补 */
385
397
  hook(handler: SdkEventHandler): () => void;
398
+ /** 运行时动态新增/覆盖一个 window 属性注册项(懒加载组件:组件挂载时注册其 schema);立即对 window 工具生效,无需重建 agent。需开启 windowOps */
399
+ addWindowProp(spec: WindowPropSpec): void;
400
+ /** 运行时移除一个 window 属性注册项(组件卸载);返回是否确实存在并移除。快照栈一并清理 */
401
+ removeWindowProp(path: string): boolean;
402
+ /** 列出当前所有已注册 window 属性(反映动态增删后的最新状态) */
403
+ listWindowProps(): WindowPropSpec[];
386
404
  }
387
405
 
388
406
  export declare function createChatSdk(options: ChatSdkOptions): ChatSdk;
@@ -404,6 +422,11 @@ export declare function createSubagentMiddleware(opts: any): any;
404
422
  export declare function createVerifyMiddleware(opts: VerifyMiddlewareOptions): any;
405
423
  export declare function createWriteBackCheck(opts?: WriteBackCheckOptions): VerifyCheck;
406
424
  export declare const presets: Record<string, any>;
425
+ /** systemPrompt 辅助片段(标准化最佳实践,拼进 systemPrompt 降低写错门槛) */
426
+ export declare const systemPromptHelpers: {
427
+ /** 可靠写入规则:改前先读、动态先 list、字段以 describe 为准、写错看校验错误重试、优先增量 patch */
428
+ readonly reliableWriteRules: string;
429
+ };
407
430
  export declare function createSessionStore(config?: StorageConfig): SessionStore;
408
431
  export declare function createMemoryBackend(): StorageBackend;
409
432
  export declare function createWebStorageBackend(storage: Storage): StorageBackend;