page-agent-sdk 4.17.0 → 4.18.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": "4.17.0",
3
+ "version": "4.18.0",
4
4
  "type": "module",
5
5
  "description": "AI agent SDK for web pages — embed a chat assistant that edits page data via schema-validated tools. A lighter, framework-agnostic alternative to CopilotKit/LangChain for in-page JSON-editing agents. Vue-bundled; works with DeepSeek, OpenAI, MCP.",
6
6
  "main": "./dist/page-agent-sdk.umd.cjs",
@@ -152,6 +152,16 @@ Project-level docs (in the repo, not bundled in this skill):
152
152
 
153
153
  ## Common pitfalls
154
154
 
155
+ - **`augmentSystem` / custom `augmentPrompt` must be idempotent within a round** (4.18, most likely to bite): the hook is called **multiple times per round** (initial assembly / per-round re-render / wrap-up synthesis / `inspect()` introspection) and only the invocation whose output ships with the request takes effect. A one-shot flag ("inject warning once") gets consumed by a discarded invocation → your content **never reaches the model** even though the callback ran. Correct pattern: advance cross-round state at round end and keep the callback read-only:
156
+ ```js
157
+ let answeredDocId = null // anchor, advanced at round end only
158
+ sdk.hook((e) => { if (e.type === 'done' || e.type === 'message_update') answeredDocId = currentDocId() })
159
+ createChatSdk({ /*...*/ augmentSystem: () => {
160
+ const id = currentDocId()
161
+ if (answeredDocId === null || id === null || answeredDocId === id) return '' // read-only check
162
+ return `⚠️ 用户已切换到《${currentTitle()}》,此前读取结果已失效,回答前必须重读当前文档。`
163
+ } })
164
+ ```
155
165
  - **DeepSeek/OpenAI 400 `missing field tool_call_id`**: `ToolMessage` must use snake_case `tool_call_id` (not camelCase). Already handled internally; only relevant if writing custom tool plumbing.
156
166
  - **ChatOpenAI params**: use `apiKey` (not `openAIApiKey`), `model` (not `modelName`); `baseUrl` goes via `configuration.baseURL`.
157
167
  - **MCP injects 0 tools on first cold visit**: `vite.config.ts` `optimizeDeps.include` pre-declares the SDK sub-paths; if you fork the config, keep those entries or the first MCP page load injects nothing (reload fixes it).
@@ -133,8 +133,29 @@ export interface MessageQuote {
133
133
  text: string
134
134
  /** 来源描述(自动捕获 = 页面 title + 最近的在前标题;宿主 setQuote 可自定义) */
135
135
  source?: string
136
- }
137
- export declare function captureSelectionQuote(doc: { getSelection?(): { isCollapsed?: boolean; anchorNode?: Node | null; toString(): string } | null; title?: string; querySelectorAll?: (selector: string) => ArrayLike<Element> }): MessageQuote | null;
136
+ /** DOM 锚点(S4,4.18):选区起始块级祖先的可定位描述 + 块内偏移;toLC 注入为元信息行(是提示不是保证,失效回退 dom_search) */
137
+ anchor?: QuoteAnchor;
138
+ }
139
+ /** 引用 DOM 锚点(host-integration-contract S4):captureSelectionQuote 自动捕获 / 宿主 setQuote 第三参自定义 */
140
+ export interface QuoteAnchor {
141
+ /** 选区起始块级祖先的 CSS selector(id 优先,否则 tag:nth-of-type 链 ≤4 层;宿主可覆盖生成) */
142
+ selector?: string;
143
+ /** 块级祖先在其父容器元素中的序号(0 起) */
144
+ blockIndex?: number;
145
+ /** 选中文本在块内字符偏移(0 起;Range 可得时精确,否则首块片段 indexOf) */
146
+ offset?: number;
147
+ /** 选中文本首片段在块内的第几次出现(1 起;offset 对不齐时的消歧级) */
148
+ occurrence?: number;
149
+ /** 最近的在前标题文本(≤60 字;元信息行展示「小节」) */
150
+ heading?: string;
151
+ /** 最近的在前标题的 id 属性(存在时;宿主跳转锚点用) */
152
+ headingId?: string;
153
+ /** 宿主文档标识(如门户 URL hash 的 doc 参数;宿主 setQuote 注入,元信息行展示) */
154
+ docId?: string;
155
+ /** 捕获时页面 URL(SDK 侧自动记录;toLC 与当前 location.href 比对,不一致标「锚点属于另一文档」—— A3 防旧锚点) */
156
+ pageUrl?: string;
157
+ }
158
+ export declare function captureSelectionQuote(doc: { getSelection?(): { isCollapsed?: boolean; anchorNode?: Node | null; toString(): string } | null; title?: string; location?: { href?: string }; querySelectorAll?: (selector: string) => ArrayLike<Element> }): MessageQuote | null;
138
159
 
139
160
  export interface AgentConfig {
140
161
  model: string;
@@ -395,6 +416,12 @@ export interface AgentInfo {
395
416
  workingMemory?: WorkingMemory;
396
417
  /** 写驱动过期读失效会话累计(stale-read-invalidation;写后旧 read/query/search 结果被替换为占位的次数) */
397
418
  staleReadsInvalidated?: number;
419
+ /** S2 宿主变更失效会话累计(host-integration-contract;notifyHostChange 触发的页面读占位替换次数,与写驱动分列) */
420
+ hostReadsInvalidated?: number;
421
+ /** A9 收口门禁会话累计(stage → { retries 回灌, exhausted 耗尽放行 };page_assertion_gate 键存在性 = domInspect 装配反射) */
422
+ gates?: Record<string, { retries: number; exhausted: number }>;
423
+ /** A9 最近一次 system 段构成(段名/字节/超预算 drop 标记;集成方 augmentSystem/pageContext 段被 drop 时可观察) */
424
+ systemSegments?: Array<{ name: string; tokens: number; dropped: boolean }>;
398
425
  /** 模型调用重试会话累计(retry-visibility;启动/body 阶段自动重试次数 —— 环境故障 vs SDK 回归的第一判据) */
399
426
  llmRetries?: number;
400
427
  /** 模型调用最终失败会话累计(retry-visibility;重试耗尽/不可重试类终败次数) */
@@ -922,6 +949,11 @@ export interface ChatSdkOptions {
922
949
  * - ctx.data 每轮从 liveData() 取最新(setData 后自动同步),可据此动态算组件说明 / 部分 schema 描述
923
950
  * - 回调异常降级为跳过该段 + debug 日志(不崩 agent)
924
951
  * - 段排在内置段之后、用户 middleware 之前;不配 = 完全现状行为
952
+ * - ⚠️ 幂等契约(host-integration-contract S1):同一轮内该回调可能被调用多次(toLC / replaceSystem / 收口
953
+ * 综合 / inspect() 内省),只有随请求发出的那次生效 —— 回调必须**幂等**(同轮多次调用返回同一结果),
954
+ * 禁止在回调里推进状态/消费一次性标志(会被输出被丢弃的调用吞掉,警示永不送达模型)。跨轮状态(如
955
+ * 「用户已切换文档」锚点)请在整轮结束推进:`sdk.hook` 监听 `done`/`message_update` 事件推进,回调只读判断。
956
+ * 实测踩坑:一次性标志形态 → 警示逻辑正确执行但从未进入任何请求。
925
957
  */
926
958
  augmentSystem?: (ctx: SystemAugmentContext) => string | undefined;
927
959
  tools?: any[];
@@ -1134,9 +1166,11 @@ export interface ChatSdk {
1134
1166
  /** 清除全部聚焦焦点(退出精修模式,恢复全量可操作范围) */
1135
1167
  clearFocus(): void;
1136
1168
  /** 挂「待发引用」(page-quote):下一条 send 附带并消费(空文本=清除;文本归一+截2000;与内置 UI autoQuote 划词捕获共用状态) */
1137
- setQuote(text: string, source?: string): void;
1169
+ setQuote(text: string, source?: string, anchor?: MessageQuote['anchor']): void;
1138
1170
  /** 清除待发引用 */
1139
1171
  clearQuote(): void;
1172
+ /** S2 宿主变更通知:SPA 换文/路由切换/tab 切换后调用 —— 流内页面读结果(read_page/dom_search/dom_info/get_dom/take_screenshot)置过期占位,并注入一次性「重读当前页面」提示段(下一 invoke 的 system,pin 段跨压缩,轮末清除);幂等可重复调 */
1173
+ notifyHostChange(opts?: { reason?: string }): void;
1140
1174
  /** 回退到最近一次正常 checkpoint(整体还原对话历史 + 主数据 + vfs + todos);需开启 checkpoint,无可用返回 false */
1141
1175
  restoreLastCheckpoint(): boolean;
1142
1176
  /** 列出可用 checkpoint(回退点);需开启 checkpoint,未开启返回空数组 */
@@ -1558,6 +1592,11 @@ export interface CreateAgentOptions {
1558
1592
  onLog?: (entry: DebugLog) => void;
1559
1593
  /** 子 agent 标记(子栈门禁据此豁免:子纯文本收口是正常形态) */
1560
1594
  __pgIsSubagent?: boolean;
1595
+ /**
1596
+ * S3 页面断言门禁装配开关(host-integration-contract 17b):createChatSdk 按 `capabilities.domInspect`
1597
+ * 传入(仅页面问答形态装配 —— 数据槽场景「页面上已改成…」误伤路径从结构上切断);false/缺省 = 门禁层不进判定。
1598
+ */
1599
+ pageAssertionGate?: boolean;
1561
1600
  /** LLM 运行时切换回调(setLlm 后触发,供重解析模型能力 contextWindow/maxOutputTokens) */
1562
1601
  onLlmChange?: (newLlm: import('@langchain/core/language_models/chat_models').BaseChatModel) => void;
1563
1602
  /** 显式声明主模型是否多模态识图(声明 > 查表 > 缺省 false) */
@@ -1583,7 +1622,11 @@ export interface AgentInstance {
1583
1622
  /** 模型调用重试会话累计(环境故障 vs SDK 回归的第一判据) */
1584
1623
  getLlmRetries(): number;
1585
1624
  getLlmCallFailures(): number;
1586
- /** 会话切换/重置时清零(stale-read + 重试/终败三计数) */
1625
+ /** A9 收口门禁会话累计(page_assertion_gate 键存在性 = S3 domInspect 装配反射) */
1626
+ getGateStats(): Record<string, { retries: number; exhausted: number }>;
1627
+ /** A9 最近一次 system 段构成(dropped = 超预算被 drop) */
1628
+ getLastSystemSegments(): Array<{ name: string; tokens: number; dropped: boolean }>;
1629
+ /** 会话切换/重置时清零(stale-read + 重试/终败 + 门禁计数) */
1587
1630
  resetSessionCounters(): void;
1588
1631
  /** 运行时重设用户工具(与中间件贡献工具合并) */
1589
1632
  setTools(userTools: import('@langchain/core/tools').StructuredToolInterface[]): void;
@@ -1880,8 +1923,11 @@ export interface LoopProgress {
1880
1923
  invokeUsage: { prompt_tokens: number; completion_tokens: number; total_tokens: number };
1881
1924
  /** 写工具同路径连续失败计数(path → 次数;写成功清零) */
1882
1925
  writeFailures: Record<string, number>;
1883
- /** 预算提示是否已注入(每任务一次,防每轮复读刷存在感) */
1884
- budgetHinted: boolean;
1926
+ /**
1927
+ * @deprecated 4.18 起恒不写入:token 预算提示已改纯函数持续注入(一次性标志会被输出被丢弃的
1928
+ * augmentPrompt 调用消费,提示从未稳定送达)。保留至下个 major 物理移除,请勿读写。
1929
+ */
1930
+ budgetHinted?: boolean;
1885
1931
  }
1886
1932
 
1887
1933
  /** Harness 运行态(中间件维护 state 字段,last-writer 合并;Deep Agents 的 agent state 同位物) */
@@ -1973,7 +2019,11 @@ export interface Middleware {
1973
2019
  name: string;
1974
2020
  /** 该中间件贡献的工具,合并进工具集 */
1975
2021
  tools?: import('@langchain/core/tools').StructuredToolInterface[];
1976
- /** 追加到 system prompt 的段(每轮模型调用前收集渲染) */
2022
+ /** 追加到 system prompt 的段(每轮模型调用前收集渲染)。
2023
+ * ⚠️ 幂等契约(host-integration-contract S1):同一轮内可能被调用多次(toLC / replaceSystem / 收口综合 /
2024
+ * inspect() 内省),只有随请求发出的那次生效 —— 必须**幂等**(同轮多次调用返回同一结果),禁止在其中
2025
+ * 推进状态或消费一次性标志(会被输出被丢弃的调用吞掉,内容永不送达模型)。跨轮状态请在轮边界推进
2026
+ * (beforeModel / afterAgent / sdk.hook 事件)。 */
1977
2027
  augmentPrompt?: (state: HarnessState) => string | undefined;
1978
2028
  /** 构建上下文前压缩历史消息(summarization 中间件用,链式) */
1979
2029
  compressInput?: (messages: AgentMessage[]) => Promise<{ messages: AgentMessage[]; stats?: unknown }> | AgentMessage[];
package/types/index.d.ts CHANGED
@@ -141,8 +141,29 @@ export interface MessageQuote {
141
141
  text: string
142
142
  /** 来源描述(自动捕获 = 页面 title + 最近的在前标题;宿主 setQuote 可自定义) */
143
143
  source?: string
144
- }
145
- export declare function captureSelectionQuote(doc: { getSelection?(): { isCollapsed?: boolean; anchorNode?: Node | null; toString(): string } | null; title?: string; querySelectorAll?: (selector: string) => ArrayLike<Element> }): MessageQuote | null;
144
+ /** DOM 锚点(S4,4.18):选区起始块级祖先的可定位描述 + 块内偏移;toLC 注入为元信息行(是提示不是保证,失效回退 dom_search) */
145
+ anchor?: QuoteAnchor;
146
+ }
147
+ /** 引用 DOM 锚点(host-integration-contract S4):captureSelectionQuote 自动捕获 / 宿主 setQuote 第三参自定义 */
148
+ export interface QuoteAnchor {
149
+ /** 选区起始块级祖先的 CSS selector(id 优先,否则 tag:nth-of-type 链 ≤4 层;宿主可覆盖生成) */
150
+ selector?: string;
151
+ /** 块级祖先在其父容器元素中的序号(0 起) */
152
+ blockIndex?: number;
153
+ /** 选中文本在块内字符偏移(0 起;Range 可得时精确,否则首块片段 indexOf) */
154
+ offset?: number;
155
+ /** 选中文本首片段在块内的第几次出现(1 起;offset 对不齐时的消歧级) */
156
+ occurrence?: number;
157
+ /** 最近的在前标题文本(≤60 字;元信息行展示「小节」) */
158
+ heading?: string;
159
+ /** 最近的在前标题的 id 属性(存在时;宿主跳转锚点用) */
160
+ headingId?: string;
161
+ /** 宿主文档标识(如门户 URL hash 的 doc 参数;宿主 setQuote 注入,元信息行展示) */
162
+ docId?: string;
163
+ /** 捕获时页面 URL(SDK 侧自动记录;toLC 与当前 location.href 比对,不一致标「锚点属于另一文档」—— A3 防旧锚点) */
164
+ pageUrl?: string;
165
+ }
166
+ export declare function captureSelectionQuote(doc: { getSelection?(): { isCollapsed?: boolean; anchorNode?: Node | null; toString(): string } | null; title?: string; location?: { href?: string }; querySelectorAll?: (selector: string) => ArrayLike<Element> }): MessageQuote | null;
146
167
 
147
168
  export interface AgentMessage {
148
169
  role: 'user' | 'assistant' | 'system';
@@ -704,6 +725,12 @@ export interface AgentInfo {
704
725
  workingMemory?: WorkingMemory;
705
726
  /** 写驱动过期读失效会话累计(stale-read-invalidation;写后旧 read/query/search 结果被替换为占位的次数) */
706
727
  staleReadsInvalidated?: number;
728
+ /** S2 宿主变更失效会话累计(host-integration-contract;notifyHostChange 触发的页面读占位替换次数,与写驱动分列) */
729
+ hostReadsInvalidated?: number;
730
+ /** A9 收口门禁会话累计(stage → { retries 回灌, exhausted 耗尽放行 };page_assertion_gate 键存在性 = domInspect 装配反射) */
731
+ gates?: Record<string, { retries: number; exhausted: number }>;
732
+ /** A9 最近一次 system 段构成(段名/字节/超预算 drop 标记;集成方 augmentSystem/pageContext 段被 drop 时可观察) */
733
+ systemSegments?: Array<{ name: string; tokens: number; dropped: boolean }>;
707
734
  /** 模型调用重试会话累计(retry-visibility;启动/body 阶段自动重试次数 —— 环境故障 vs SDK 回归的第一判据) */
708
735
  llmRetries?: number;
709
736
  /** 模型调用最终失败会话累计(retry-visibility;重试耗尽/不可重试类终败次数) */
@@ -1282,6 +1309,11 @@ export interface ChatSdkOptions {
1282
1309
  * - ctx.data 每轮从 liveData() 取最新(setData 后自动同步),可据此动态算组件说明 / 部分 schema 描述
1283
1310
  * - 回调异常降级为跳过该段 + debug 日志(不崩 agent)
1284
1311
  * - 段排在内置段之后、用户 middleware 之前;不配 = 完全现状行为
1312
+ * - ⚠️ 幂等契约(host-integration-contract S1):同一轮内该回调可能被调用多次(toLC / replaceSystem / 收口
1313
+ * 综合 / inspect() 内省),只有随请求发出的那次生效 —— 回调必须**幂等**(同轮多次调用返回同一结果),
1314
+ * 禁止在回调里推进状态/消费一次性标志(会被输出被丢弃的调用吞掉,警示永不送达模型)。跨轮状态(如
1315
+ * 「用户已切换文档」锚点)请在整轮结束推进:`sdk.hook` 监听 `done`/`message_update` 事件推进,回调只读判断。
1316
+ * 实测踩坑:一次性标志形态 → 警示逻辑正确执行但从未进入任何请求。
1285
1317
  */
1286
1318
  augmentSystem?: (ctx: SystemAugmentContext) => string | undefined;
1287
1319
  tools?: any[];
@@ -1550,9 +1582,11 @@ export interface ChatSdk {
1550
1582
  /** 清除全部聚焦焦点(退出精修模式,恢复全量可操作范围) */
1551
1583
  clearFocus(): void;
1552
1584
  /** 挂「待发引用」(page-quote):下一条 send 附带并消费(空文本=清除;文本归一+截2000;内置 UI autoQuote 划词捕获与此共用状态) */
1553
- setQuote(text: string, source?: string): void;
1585
+ setQuote(text: string, source?: string, anchor?: MessageQuote['anchor']): void;
1554
1586
  /** 清除待发引用 */
1555
1587
  clearQuote(): void;
1588
+ /** S2 宿主变更通知:SPA 换文/路由切换/tab 切换后调用 —— 流内页面读结果(read_page/dom_search/dom_info/get_dom/take_screenshot)置过期占位,并注入一次性「重读当前页面」提示段(下一 invoke 的 system,pin 段跨压缩,轮末清除);幂等可重复调 */
1589
+ notifyHostChange(opts?: { reason?: string }): void;
1556
1590
  /** 回退到最近一次正常 checkpoint(整体还原对话历史 + 主数据 + vfs + todos);需开启 checkpoint,无可用返回 false */
1557
1591
  restoreLastCheckpoint(): boolean;
1558
1592
  /** 列出可用 checkpoint(回退点);需开启 checkpoint,未开启返回空数组 */
@@ -1987,6 +2021,11 @@ export interface CreateAgentOptions {
1987
2021
  onLog?: (entry: DebugLog) => void;
1988
2022
  /** 子 agent 标记(子栈门禁据此豁免:子纯文本收口是正常形态) */
1989
2023
  __pgIsSubagent?: boolean;
2024
+ /**
2025
+ * S3 页面断言门禁装配开关(host-integration-contract 17b):createChatSdk 按 `capabilities.domInspect`
2026
+ * 传入(仅页面问答形态装配 —— 数据槽场景「页面上已改成…」误伤路径从结构上切断);false/缺省 = 门禁层不进判定。
2027
+ */
2028
+ pageAssertionGate?: boolean;
1990
2029
  /** LLM 运行时切换回调(setLlm 后触发,供重解析模型能力 contextWindow/maxOutputTokens) */
1991
2030
  onLlmChange?: (newLlm: import('@langchain/core/language_models/chat_models').BaseChatModel) => void;
1992
2031
  /** 显式声明主模型是否多模态识图(声明 > 查表 > 缺省 false) */
@@ -2012,7 +2051,11 @@ export interface AgentInstance {
2012
2051
  /** 模型调用重试会话累计(环境故障 vs SDK 回归的第一判据) */
2013
2052
  getLlmRetries(): number;
2014
2053
  getLlmCallFailures(): number;
2015
- /** 会话切换/重置时清零(stale-read + 重试/终败三计数) */
2054
+ /** A9 收口门禁会话累计(page_assertion_gate 键存在性 = S3 domInspect 装配反射) */
2055
+ getGateStats(): Record<string, { retries: number; exhausted: number }>;
2056
+ /** A9 最近一次 system 段构成(dropped = 超预算被 drop) */
2057
+ getLastSystemSegments(): Array<{ name: string; tokens: number; dropped: boolean }>;
2058
+ /** 会话切换/重置时清零(stale-read + 重试/终败 + 门禁计数) */
2016
2059
  resetSessionCounters(): void;
2017
2060
  /** 运行时重设用户工具(与中间件贡献工具合并) */
2018
2061
  setTools(userTools: import('@langchain/core/tools').StructuredToolInterface[]): void;
@@ -2306,8 +2349,11 @@ export interface LoopProgress {
2306
2349
  invokeUsage: { prompt_tokens: number; completion_tokens: number; total_tokens: number };
2307
2350
  /** 写工具同路径连续失败计数(path → 次数;写成功清零) */
2308
2351
  writeFailures: Record<string, number>;
2309
- /** 预算提示是否已注入(每任务一次,防每轮复读刷存在感) */
2310
- budgetHinted: boolean;
2352
+ /**
2353
+ * @deprecated 4.18 起恒不写入:token 预算提示已改纯函数持续注入(一次性标志会被输出被丢弃的
2354
+ * augmentPrompt 调用消费,提示从未稳定送达)。保留至下个 major 物理移除,请勿读写。
2355
+ */
2356
+ budgetHinted?: boolean;
2311
2357
  }
2312
2358
 
2313
2359
  /** Harness 运行态(中间件维护 state 字段,last-writer 合并;Deep Agents 的 agent state 同位物) */
@@ -2403,7 +2449,11 @@ export interface Middleware {
2403
2449
  name: string;
2404
2450
  /** 该中间件贡献的工具,合并进工具集 */
2405
2451
  tools?: import('@langchain/core/tools').StructuredToolInterface[];
2406
- /** 追加到 system prompt 的段(每轮模型调用前收集渲染) */
2452
+ /** 追加到 system prompt 的段(每轮模型调用前收集渲染)。
2453
+ * ⚠️ 幂等契约(host-integration-contract S1):同一轮内可能被调用多次(toLC / replaceSystem / 收口综合 /
2454
+ * inspect() 内省),只有随请求发出的那次生效 —— 必须**幂等**(同轮多次调用返回同一结果),禁止在其中
2455
+ * 推进状态或消费一次性标志(会被输出被丢弃的调用吞掉,内容永不送达模型)。跨轮状态请在轮边界推进
2456
+ * (beforeModel / afterAgent / sdk.hook 事件)。 */
2407
2457
  augmentPrompt?: (state: HarnessState) => string | undefined;
2408
2458
  /** 构建上下文前压缩历史消息(summarization 中间件用,链式) */
2409
2459
  compressInput?: (messages: AgentMessage[]) => Promise<{ messages: AgentMessage[]; stats?: unknown }> | AgentMessage[];