@zihanw/pi-forge 0.5.0 → 0.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.
Files changed (100) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +2 -1
  3. package/README.zh-CN.md +1 -1
  4. package/dist/context-diff-history.d.ts +61 -0
  5. package/dist/context-diff-history.d.ts.map +1 -0
  6. package/dist/context-diff-history.js +84 -0
  7. package/dist/context-diff-history.js.map +1 -0
  8. package/dist/context-diff-snapshot.d.ts +19 -0
  9. package/dist/context-diff-snapshot.d.ts.map +1 -0
  10. package/dist/context-diff-snapshot.js +146 -0
  11. package/dist/context-diff-snapshot.js.map +1 -0
  12. package/dist/context-diff.d.ts +70 -0
  13. package/dist/context-diff.d.ts.map +1 -0
  14. package/dist/context-diff.js +259 -0
  15. package/dist/context-diff.js.map +1 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +5 -2
  18. package/dist/index.js.map +1 -1
  19. package/dist/lifecycle.d.ts +2 -0
  20. package/dist/lifecycle.d.ts.map +1 -1
  21. package/dist/lifecycle.js +2 -0
  22. package/dist/lifecycle.js.map +1 -1
  23. package/dist/payload-capture.d.ts +10 -0
  24. package/dist/payload-capture.d.ts.map +1 -1
  25. package/dist/payload-capture.js +39 -9
  26. package/dist/payload-capture.js.map +1 -1
  27. package/dist/payload-command.d.ts +2 -0
  28. package/dist/payload-command.d.ts.map +1 -1
  29. package/dist/payload-command.js +36 -6
  30. package/dist/payload-command.js.map +1 -1
  31. package/dist/payload-state.d.ts +3 -0
  32. package/dist/payload-state.d.ts.map +1 -1
  33. package/dist/payload-state.js +5 -0
  34. package/dist/payload-state.js.map +1 -1
  35. package/dist/preview.d.ts.map +1 -1
  36. package/dist/preview.js +17 -3
  37. package/dist/preview.js.map +1 -1
  38. package/dist/runtime/tool-policy-runtime.d.ts.map +1 -1
  39. package/dist/runtime/tool-policy-runtime.js +13 -3
  40. package/dist/runtime/tool-policy-runtime.js.map +1 -1
  41. package/dist/runtime/web-editor-runtime.d.ts +2 -1
  42. package/dist/runtime/web-editor-runtime.d.ts.map +1 -1
  43. package/dist/runtime/web-editor-runtime.js +8 -3
  44. package/dist/runtime/web-editor-runtime.js.map +1 -1
  45. package/dist/ui-contribution/contrib-port.d.ts +170 -0
  46. package/dist/ui-contribution/contrib-port.d.ts.map +1 -0
  47. package/dist/ui-contribution/contrib-port.js +640 -0
  48. package/dist/ui-contribution/contrib-port.js.map +1 -0
  49. package/dist/ui-contribution/index.d.ts +3 -0
  50. package/dist/ui-contribution/index.d.ts.map +1 -0
  51. package/dist/ui-contribution/index.js +2 -0
  52. package/dist/ui-contribution/index.js.map +1 -0
  53. package/dist/web-editor/client-script.generated.d.ts.map +1 -1
  54. package/dist/web-editor/client-script.generated.js +1 -1
  55. package/dist/web-editor/client-script.generated.js.map +1 -1
  56. package/dist/web-editor/client-styles.generated.d.ts.map +1 -1
  57. package/dist/web-editor/client-styles.generated.js +1 -1
  58. package/dist/web-editor/client-styles.generated.js.map +1 -1
  59. package/dist/web-editor/contrib-service.d.ts +41 -0
  60. package/dist/web-editor/contrib-service.d.ts.map +1 -0
  61. package/dist/web-editor/contrib-service.js +174 -0
  62. package/dist/web-editor/contrib-service.js.map +1 -0
  63. package/dist/web-editor/line-diff.d.ts +28 -0
  64. package/dist/web-editor/line-diff.d.ts.map +1 -0
  65. package/dist/web-editor/line-diff.js +216 -0
  66. package/dist/web-editor/line-diff.js.map +1 -0
  67. package/dist/web-editor/schema-form.d.ts +63 -0
  68. package/dist/web-editor/schema-form.d.ts.map +1 -0
  69. package/dist/web-editor/schema-form.js +213 -0
  70. package/dist/web-editor/schema-form.js.map +1 -0
  71. package/dist/web-editor/server.d.ts.map +1 -1
  72. package/dist/web-editor/server.js +71 -6
  73. package/dist/web-editor/server.js.map +1 -1
  74. package/dist/web-editor/styles.d.ts.map +1 -1
  75. package/dist/web-editor/styles.js +188 -21
  76. package/dist/web-editor/styles.js.map +1 -1
  77. package/dist/web-editor/types.d.ts +12 -0
  78. package/dist/web-editor/types.d.ts.map +1 -1
  79. package/dist/web-host.d.ts +2 -0
  80. package/dist/web-host.d.ts.map +1 -1
  81. package/dist/web-host.js +1 -0
  82. package/dist/web-host.js.map +1 -1
  83. package/docs/README.md +1 -0
  84. package/docs/concepts/agent-profiles.md +1 -1
  85. package/docs/design/0.5.1-plan.md +237 -0
  86. package/docs/design/architecture-0.5.md +12 -1
  87. package/docs/design/context-diff-plan.md +8 -2
  88. package/docs/development/release.md +7 -1
  89. package/docs/guides/delegation.md +3 -3
  90. package/docs/guides/web-editor.md +1 -1
  91. package/docs/reference/commands.md +1 -1
  92. package/docs/reference/configuration.md +2 -2
  93. package/docs/reference/public-api.md +14 -2
  94. package/docs/reference/stack-schema.md +1 -1
  95. package/docs/reference/ui-contribution-port.md +51 -0
  96. package/docs/zh-CN/concepts/agent-profiles.md +1 -1
  97. package/docs/zh-CN/concepts/prompt-stacks.md +1 -1
  98. package/docs/zh-CN/guides/delegation.md +2 -2
  99. package/docs/zh-CN/reference/commands.md +1 -1
  100. package/package.json +6 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,44 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
  In 0.x development, breaking changes may occur in minor releases and will be explicitly noted.
8
8
 
9
+ ## [0.5.1] - 2026-08-23
10
+
11
+ ### Added
12
+
13
+ - **Cross-platform release CI.** The complete verification surface, including
14
+ the real-browser editor suite and packed-install smoke, now runs on GitHub's
15
+ macOS and Windows hosted runners in addition to Linux. Generated TypeScript
16
+ output and repository text files are pinned to LF so tracked `dist/` checks
17
+ remain deterministic across operating systems.
18
+ - **UI contribution framework (Lane 1).** Optional packages can now contribute schema-driven configuration pages to the web editor without shipping any UI code into the main package:
19
+ - New versioned `@zihanw/pi-forge/ui-contribution` entry point: a generic RPC contract over the Pi event bus (`UI_CONTRIBUTION_PORT_VERSION = 1`, with its own version counter separate from the `/subagent` host port) exposing exactly two operations — `listContributions` and `writeValues` — over discover/available/request/reply/unavailable channels. Wire messages carry `hostId` + `generation` binding, version negotiation (`minVersion`/`maxVersion`), bounded discovery timeouts, explicit duplicate-provider failure, and recursive JSON-compatibility validators. All payloads are plain data: no functions, components, or live contexts cross the bus. See the [UI contribution port contract](docs/reference/ui-contribution-port.md).
20
+ - Web server routes: `GET /api/contrib` returns discovered tab descriptors at page load, and `PUT /api/contrib/<tabId>` proxies submitted values to the contributing package over the bus. The forge side is fully generic and knows nothing about subagents; it never owns the schema, validation, or persistence of contributed tabs (the provider writes its own config files), and with no provider discovered there is no contributed tab and zero cost.
21
+ - Contribution discovery tolerates provider churn across sessions: providers that appear after startup surface their tabs, departed providers are cleared, and service stop cancels pending discovery.
22
+ - Data-driven stack-tab registry replaces the stack editor's hardcoded `"policy" | "regex" | "stack"` tab union. Built-in tabs keep their existing hosts (including the legacy imperative items workspace as an ordinary tab), while contributed configuration renders through a self-contained host on a separate top-level **Settings** surface.
23
+ - Generic schema-form renderer: a self-contained Vue component bridged via the vue-host mechanism, covering deliberately restricted v1 field types — boolean, number, enum, string, plus a record/table shape for keyed per-entry lists. Record identities may use live catalog-backed `keyOptions`, while validation and persistence remain provider-owned. No imperative code was added to `legacy-editor.ts`.
24
+ - **Context diff — per-turn prompt observability (Lane 2).** Consecutive real provider requests are diffed automatically so KV-cache reuse is observable:
25
+ - Host-neutral pure engine (`src/context-diff.ts`): `TurnSnapshot`/`TurnDiff` shapes, block classification (`same`/`added`/`removed`/`modified`) aligned by stable content-derived keys with positional fallback, and a prefix-walk cache-boundary algorithm that walks matching block hashes and trims the char-level common prefix inside the first mismatched block — no Myers diff. Token figures are chars/4 approximations: relative deltas are accurate, and absolute boundary claims are labeled approx in the UI.
26
+ - Automatic per-turn capture: every provider request enters a rolling history of the last 20 turns (not just manually armed captures), and each new snapshot is auto-diffed against the previous turn. Block hashes cover role and serialized wire-relevant fields, and request-level sections (tool definitions and similar) participate as blocks so wire changes break the cache boundary faithfully.
27
+ - `GET /api/context-diff` serves the latest snapshot, the previous-turn diff, and summary rollups (added/removed/net tokens, prefix tokens and ratio).
28
+ - Preview/diff dock: the preview modal is promoted into a wider right-side panel with three focused views — **Preview** (the live compiled draft), **Draft diff** (the unsaved draft against the saved stack), and **Run diff** (the latest captured provider turn against its predecessor). Diff views hide unchanged blocks by default, show modified blocks in Before/After columns, keep run/cache metadata collapsed until requested, and offer **Focus/Split** controls to use the full editor width without permanently crowding stack editing. The panel is a self-contained Vue component riding Lane 1's data-driven registry; no imperative code was added to `legacy-editor.ts`.
29
+
30
+ ### Fixed
31
+
32
+ - Preview/Draft/Run diffs now render git-style unified or split line views with old/new line numbers, inline changed spans, and changes-only/three-line/all-line context controls. Chars/4 figures are labeled strictly as estimates. Each captured provider request is correlated with its completed assistant message, so Run metadata shows Pi's actual input/output/cache-read/cache-write usage and a provider-reported cache-hit rate when cache reporting has been observed; otherwise it says the rate is not reported instead of presenting a synthetic zero.
33
+
34
+ - Schema-driven record settings can now provide catalog-backed `keyOptions`, rendered as a duplicate-safe selector instead of a free-text identity field. UI-contribution handlers may resolve asynchronously, allowing optional packages to refresh host-owned catalogs before returning a schema or validating a write; stale replies are suppressed if that provider generation stops while the handler is pending.
35
+
36
+ - Selective stack tool allow lists now select from Pi's complete registered tool catalog, so built-in inactive tools such as `grep`, `find`, and `ls` can be activated by a stack and are restored to their prior inactive state when the policy is removed. Allow lists containing `*` remain on the active baseline and do not expand to every inactive tool. The web editor now states the active-vs-registered distinction explicitly.
37
+ - The docked Compiled preview now consumes the editor's current in-memory stack draft, refreshes from actual draft-change notifications, aborts superseded requests, and no longer silently previews the saved disk version while unsaved edits are visible.
38
+ - Plugin configuration no longer mounts inside the stack Preview dock. Contributed pages now live on an independent top-level **Settings** surface, so switching between Stack Preview and Subagent Settings cannot unmount or constrain the settings form.
39
+ - Preview now owns a dedicated DOM mount instead of sharing the legacy stack-tab panel, so saving or selecting another stack cannot leave an active-but-empty dock. Draft changes immediately invalidate visible compiled output, Preview and Run requests both reject superseded responses, and browser tests cover save/selection plus controlled delayed-response races.
40
+ - Settings autosaves are serialized per contribution host and replay the newest full form snapshot after an in-flight write, preventing a slow older PUT from overwriting a newer edit while the UI reports `Saved`. Drafts and save status are retained independently per Settings page during rapid tab switches, while a forge-owned monotonic provider-session key forces clean forms to remount with fresh schema and values after fast provider churn. PUT results are bound to the provider session that received them, so a delayed old-session success retries the preserved draft instead of overwriting the new session. Duplicate contribution IDs are ignored after the first descriptor and Settings button IDs cannot collide with built-in stack tabs.
41
+ - Compiled draft diffs align messages with stable source keys rather than positional `message-<index>` IDs, avoiding cascades of false modifications after insertions. Split Preview and top-level Settings collapse to usable single-column layouts on narrow screens without document-level horizontal overflow.
42
+ - The stack editor now keeps the Project/Global selector attached to **New stack** instead of stretching it across a full row, leaves only primary actions in the toolbar, moves infrequent capture/import/export/delete actions into **More**, and compacts the status, metadata, tab, sidebar, and item-list chrome so editing and Preview/Diff receive substantially more viewport space.
43
+ - The profile editor now keeps the canonical `global:<id>` selector visible in list, summary, edit heading, and save status. Profile JSON continues to store a bare schema ID and same-scope prompt-stack references relatively; their global identity is carried by storage scope and is no longer visually lost in the editor.
44
+ - Context-diff fidelity (architecture review F1–F3/F6 and re-review R1–R4): block hashes are computed from the faithful redacted serialization rather than the lossy capture projection, preserving provider field order for request-level blocks; blocks align by stable unique keys so insertions before existing messages classify correctly; token rounding is conservative and consistent so per-block chips cannot contradict summary totals; and the empty Diff view reflects automatic per-turn capture instead of instructing manual arming.
45
+ - UI-contribution hardening (F4/F5/F7): a partial-patch `writeValues` success no longer clobbers cached descriptor values with stale data; contribution lifecycle handles late-surfacing and departed providers and cancels pending discovery on stop; malformed or oversized `/api/contrib` request bodies return 400/413 instead of surfacing raw validator failures.
46
+
9
47
  ## [0.5.0] - 2026-08-21
10
48
 
11
49
  ### Planning
package/README.md CHANGED
@@ -17,6 +17,7 @@ Think of it as a character sheet and workbench for your AI agent.
17
17
  - Use immutable stack `parameters` with the deterministic forge-v1 template engine.
18
18
  - Apply deterministic regex transforms to outgoing prompts or finalized assistant messages.
19
19
  - Edit stacks and profiles in a local browser UI and inspect the exact provider payload.
20
+ - Inspect prompt changes in the Preview dock: **Preview** compiles the live draft, **Draft diff** compares unsaved edits with disk, and **Run diff** compares recent provider turns. Git-style unified/split views include old/new line numbers, inline highlights, and changes-only/three-line/all-line context. Run metadata keeps chars/4 estimates separate from Pi's provider-reported prompt/cache usage and real cache-hit rate.
20
21
  - Run an explicitly enabled profile as an experimental, approval-gated foreground subagent.
21
22
 
22
23
  ## Install
@@ -57,7 +58,7 @@ Restart Pi or run:
57
58
  /preset ui
58
59
  ```
59
60
 
60
- The local editor can create, fork, validate, preview, import, export, and delete prompt stacks. Its **Agent profiles** view manages one-shot model/thinking/stack presets. Writes require a trusted project. Delegation settings live in the optional `@zihanw/pi-forge-subagents` package's `subagents.json` files, not in the editor.
61
+ The local editor can create, fork, validate, preview, import, export, and delete prompt stacks. Its **Agent profiles** view manages one-shot model/thinking/stack presets. Writes require a trusted project. When `@zihanw/pi-forge-subagents` is installed, its schema-driven editor appears on the separate top-level **Settings** surface and persists to the optional package's `subagents.json` files.
61
62
 
62
63
  ### 3. Save a profile
63
64
 
package/README.zh-CN.md CHANGED
@@ -57,7 +57,7 @@ cp examples/default-prompt-stack.json .pi/forge/prompt-stacks/default.json
57
57
  /preset ui
58
58
  ```
59
59
 
60
- 本地编辑器可以新建、fork、校验、预览、导入、导出和删除 prompt stack,并可在新建/fork/导入时明确选择写入项目或用户全局存储。切换到 **Agent profiles** 可以浏览项目与全局 profile、编辑和删除全局 profile(通过显式 `global:<id>` 路由)。写入操作要求项目已被信任。Delegation 配置由可选包 `@zihanw/pi-forge-subagents` 的 `subagents.json` 文件管理,不在编辑器中。
60
+ 本地编辑器可以新建、fork、校验、预览、导入、导出和删除 prompt stack,并可在新建/fork/导入时明确选择写入项目或用户全局存储。Preview dock 提供带旧/新行号和行内高亮的 unified/split diff,可只看变化行或保留三行/全部上下文;Run diff 会把 chars/4 估算与 Pi 返回的真实 prompt/cache usage、cache hit rate 明确分开。切换到 **Agent profiles** 可以浏览项目与全局 profile、编辑和删除全局 profile(通过显式 `global:<id>` 路由)。写入操作要求项目已被信任。Delegation 配置由可选包 `@zihanw/pi-forge-subagents` 的 `subagents.json` 文件管理,不在编辑器中。
61
61
 
62
62
  ### 3. 保存 profile
63
63
 
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Host-neutral rolling context-diff history.
3
+ *
4
+ * Keeps the last N turn snapshots captured from provider payloads and exposes
5
+ * the latest engine-computed diff plus compact summaries for recent turns.
6
+ */
7
+ import { type TurnDiff, type TurnSnapshot } from "./context-diff.ts";
8
+ import { type ContextDiffCapture } from "./context-diff-snapshot.ts";
9
+ export declare const CONTEXT_DIFF_HISTORY_LIMIT = 20;
10
+ export interface ContextDiffHistory {
11
+ turns: TurnSnapshot[];
12
+ latestDiff?: TurnDiff;
13
+ nextTurnNumber?: number;
14
+ usageByTurnId: Map<string, ContextDiffProviderUsage>;
15
+ cacheReportingModels: Set<string>;
16
+ }
17
+ export interface ContextDiffProviderUsageInput {
18
+ provider: string;
19
+ model: string;
20
+ stopReason: string;
21
+ input: number;
22
+ output: number;
23
+ cacheRead: number;
24
+ cacheWrite: number;
25
+ totalTokens: number;
26
+ }
27
+ export interface ContextDiffProviderUsage extends ContextDiffProviderUsageInput {
28
+ promptTokens: number;
29
+ cacheHitRatio: number | null;
30
+ cacheStatus: "reported" | "not-reported";
31
+ }
32
+ export interface ContextDiffTurnSummary {
33
+ turnId: string;
34
+ capturedAt: string;
35
+ stackId: string;
36
+ approxTokens: number;
37
+ blockCount: number;
38
+ chars: number;
39
+ deltaTokens?: number;
40
+ prefixTokens?: number;
41
+ prefixRatio?: number;
42
+ changedBlocks?: number;
43
+ usage?: ContextDiffProviderUsage;
44
+ }
45
+ export interface ContextDiffView {
46
+ turns: ContextDiffTurnSummary[];
47
+ latest: {
48
+ turn: TurnSnapshot;
49
+ diff: TurnDiff;
50
+ usage?: ContextDiffProviderUsage;
51
+ } | null;
52
+ latestDiff: TurnDiff | null;
53
+ }
54
+ export declare function createContextDiffHistory(): ContextDiffHistory;
55
+ /** Append a captured payload, evict old turns past the limit, and compute the latest diff. */
56
+ export declare function appendContextDiffCapture(history: ContextDiffHistory, capture: ContextDiffCapture): TurnDiff | undefined;
57
+ /** Attach the authoritative usage returned on the assistant message for one captured provider request. */
58
+ export declare function attachContextDiffUsage(history: ContextDiffHistory, turnId: string, usage: ContextDiffProviderUsageInput): boolean;
59
+ /** Build the response-shaped view: compact summaries for recent turns + latest full diff. */
60
+ export declare function getContextDiffView(history: ContextDiffHistory): ContextDiffView;
61
+ //# sourceMappingURL=context-diff-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-diff-history.d.ts","sourceRoot":"","sources":["../src/context-diff-history.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAA+B,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAE1F,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,UAAU,CAAC,EAAE,QAAQ,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IACrD,oBAAoB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,6BAA6B;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAyB,SAAQ,6BAA6B;IAC9E,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,UAAU,GAAG,cAAc,CAAC;CACzC;AAED,MAAM,WAAW,sBAAsB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,wBAAwB,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAChC,MAAM,EAAE;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,KAAK,CAAC,EAAE,wBAAwB,CAAA;KAAE,GAAG,IAAI,CAAC;IACxF,UAAU,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAgB,wBAAwB,IAAI,kBAAkB,CAO7D;AAED,8FAA8F;AAC9F,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,kBAAkB,GACzB,QAAQ,GAAG,SAAS,CAiBtB;AAED,0GAA0G;AAC1G,wBAAgB,sBAAsB,CACrC,OAAO,EAAE,kBAAkB,EAC3B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,6BAA6B,GAClC,OAAO,CAcT;AAED,6FAA6F;AAC7F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,kBAAkB,GAAG,eAAe,CA0B/E"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Host-neutral rolling context-diff history.
3
+ *
4
+ * Keeps the last N turn snapshots captured from provider payloads and exposes
5
+ * the latest engine-computed diff plus compact summaries for recent turns.
6
+ */
7
+ import { diffTurns, turnApproxTokens } from "./context-diff.js";
8
+ import { extractTurnSnapshot } from "./context-diff-snapshot.js";
9
+ export const CONTEXT_DIFF_HISTORY_LIMIT = 20;
10
+ export function createContextDiffHistory() {
11
+ return {
12
+ turns: [],
13
+ nextTurnNumber: 1,
14
+ usageByTurnId: new Map(),
15
+ cacheReportingModels: new Set(),
16
+ };
17
+ }
18
+ /** Append a captured payload, evict old turns past the limit, and compute the latest diff. */
19
+ export function appendContextDiffCapture(history, capture) {
20
+ const nextTurnNumber = history.nextTurnNumber ?? 1;
21
+ const turnId = capture.turnId ?? `turn-${nextTurnNumber}`;
22
+ history.nextTurnNumber = nextTurnNumber + 1;
23
+ const snapshot = extractTurnSnapshot({
24
+ ...capture,
25
+ turnId,
26
+ });
27
+ const previous = history.turns.at(-1);
28
+ history.turns.push(snapshot);
29
+ if (history.turns.length > CONTEXT_DIFF_HISTORY_LIMIT) {
30
+ const evicted = history.turns.shift();
31
+ if (evicted)
32
+ history.usageByTurnId.delete(evicted.turnId);
33
+ }
34
+ const diff = diffTurns(previous, snapshot);
35
+ history.latestDiff = diff;
36
+ return diff;
37
+ }
38
+ /** Attach the authoritative usage returned on the assistant message for one captured provider request. */
39
+ export function attachContextDiffUsage(history, turnId, usage) {
40
+ if (!history.turns.some((turn) => turn.turnId === turnId))
41
+ return false;
42
+ const modelKey = `${usage.provider}/${usage.model}`;
43
+ const currentReportsCache = usage.cacheRead > 0 || usage.cacheWrite > 0;
44
+ const cacheReported = currentReportsCache || history.cacheReportingModels.has(modelKey);
45
+ if (currentReportsCache)
46
+ history.cacheReportingModels.add(modelKey);
47
+ const promptTokens = usage.input + usage.cacheRead + usage.cacheWrite;
48
+ history.usageByTurnId.set(turnId, {
49
+ ...usage,
50
+ promptTokens,
51
+ cacheHitRatio: cacheReported && promptTokens > 0 ? usage.cacheRead / promptTokens : null,
52
+ cacheStatus: cacheReported ? "reported" : "not-reported",
53
+ });
54
+ return true;
55
+ }
56
+ /** Build the response-shaped view: compact summaries for recent turns + latest full diff. */
57
+ export function getContextDiffView(history) {
58
+ const turns = history.turns.map((turn, index) => {
59
+ const previous = index > 0 ? history.turns[index - 1] : undefined;
60
+ const diff = diffTurns(previous, turn);
61
+ return {
62
+ turnId: turn.turnId,
63
+ capturedAt: turn.capturedAt,
64
+ stackId: turn.stackId,
65
+ approxTokens: turnApproxTokens(turn),
66
+ blockCount: turn.blocks.length,
67
+ chars: turn.blocks.reduce((sum, block) => sum + block.chars, 0),
68
+ deltaTokens: diff.deltaTokens,
69
+ prefixTokens: diff.prefixTokens,
70
+ prefixRatio: diff.prefixRatio,
71
+ changedBlocks: diff.summary.changedBlocks,
72
+ usage: history.usageByTurnId.get(turn.turnId),
73
+ };
74
+ });
75
+ const latest = history.turns.length > 0 && history.latestDiff
76
+ ? {
77
+ turn: history.turns[history.turns.length - 1],
78
+ diff: history.latestDiff,
79
+ usage: history.usageByTurnId.get(history.turns[history.turns.length - 1].turnId),
80
+ }
81
+ : null;
82
+ return { turns, latest, latestDiff: history.latestDiff ?? null };
83
+ }
84
+ //# sourceMappingURL=context-diff-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-diff-history.js","sourceRoot":"","sources":["../src/context-diff-history.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAoC,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAE,mBAAmB,EAA2B,MAAM,4BAA4B,CAAC;AAE1F,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AA+C7C,MAAM,UAAU,wBAAwB;IACvC,OAAO;QACN,KAAK,EAAE,EAAE;QACT,cAAc,EAAE,CAAC;QACjB,aAAa,EAAE,IAAI,GAAG,EAAE;QACxB,oBAAoB,EAAE,IAAI,GAAG,EAAE;KAC/B,CAAC;AACH,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,wBAAwB,CACvC,OAA2B,EAC3B,OAA2B;IAE3B,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,QAAQ,cAAc,EAAE,CAAC;IAC1D,OAAO,CAAC,cAAc,GAAG,cAAc,GAAG,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,mBAAmB,CAAC;QACpC,GAAG,OAAO;QACV,MAAM;KACN,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7B,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,0BAA0B,EAAE,CAAC;QACvD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACtC,IAAI,OAAO;YAAE,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC3C,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAC1B,OAAO,IAAI,CAAC;AACb,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,sBAAsB,CACrC,OAA2B,EAC3B,MAAc,EACd,KAAoC;IAEpC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACxE,MAAM,QAAQ,GAAG,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;IACpD,MAAM,mBAAmB,GAAG,KAAK,CAAC,SAAS,GAAG,CAAC,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC;IACxE,MAAM,aAAa,GAAG,mBAAmB,IAAI,OAAO,CAAC,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxF,IAAI,mBAAmB;QAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACpE,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,UAAU,CAAC;IACtE,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE;QACjC,GAAG,KAAK;QACR,YAAY;QACZ,aAAa,EAAE,aAAa,IAAI,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI;QACxF,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,cAAc;KACxD,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACb,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,kBAAkB,CAAC,OAA2B;IAC7D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC/C,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAClE,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACvC,OAAO;YACN,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,YAAY,EAAE,gBAAgB,CAAC,IAAI,CAAC;YACpC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;YAC9B,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;YAC/D,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa;YACzC,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC;SAC7C,CAAC;IACH,CAAC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,UAAU;QAC5D,CAAC,CAAC;YACD,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE;YAC9C,IAAI,EAAE,OAAO,CAAC,UAAU;YACxB,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,MAAM,CAAC;SACjF;QACD,CAAC,CAAC,IAAI,CAAC;IACR,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;AAClE,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Host-neutral snapshot extraction from captured provider payloads.
3
+ *
4
+ * This module stays free of Node/DOM/Vue APIs so the same extraction logic can
5
+ * be reused by the web host and future non-web surfaces.
6
+ */
7
+ import { type TurnSnapshot } from "./context-diff.ts";
8
+ export interface ContextDiffCapture {
9
+ turnId?: string;
10
+ capturedAt: string;
11
+ stackId?: string;
12
+ payload?: unknown;
13
+ /** Secret-redacted, complete provider-request JSON used for diff identity. */
14
+ serializedPayload?: string;
15
+ text: string;
16
+ }
17
+ /** Convert a captured provider payload into an ordered TurnSnapshot of Blocks. */
18
+ export declare function extractTurnSnapshot(capture: ContextDiffCapture): TurnSnapshot;
19
+ //# sourceMappingURL=context-diff-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-diff-snapshot.d.ts","sourceRoot":"","sources":["../src/context-diff-snapshot.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAyD,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE7G,MAAM,WAAW,kBAAkB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACb;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,kBAAkB,GAAG,YAAY,CAO7E"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Host-neutral snapshot extraction from captured provider payloads.
3
+ *
4
+ * This module stays free of Node/DOM/Vue APIs so the same extraction logic can
5
+ * be reused by the web host and future non-web surfaces.
6
+ */
7
+ import { createBlock, createTurnSnapshot, hashText } from "./context-diff.js";
8
+ /** Convert a captured provider payload into an ordered TurnSnapshot of Blocks. */
9
+ export function extractTurnSnapshot(capture) {
10
+ return createTurnSnapshot({
11
+ turnId: capture.turnId ?? capture.capturedAt,
12
+ capturedAt: capture.capturedAt,
13
+ stackId: capture.stackId ?? "",
14
+ blocks: extractBlocks(capture),
15
+ });
16
+ }
17
+ function extractBlocks(capture) {
18
+ const serializedPayload = parseSerializedPayload(capture.serializedPayload);
19
+ const payload = serializedPayload === undefined ? capture.payload : serializedPayload;
20
+ if (!isPlainObject(payload)) {
21
+ return [createBlock("payload-text", "payload", capture.text, capture.serializedPayload === undefined ? undefined : {
22
+ section: "payload",
23
+ serializedPayload: capture.serializedPayload,
24
+ })];
25
+ }
26
+ const blocks = [];
27
+ const messageKeyCounts = new Map();
28
+ for (const [key, value] of Object.entries(payload)) {
29
+ if (key === "system") {
30
+ blocks.push(createSystemBlock(value));
31
+ continue;
32
+ }
33
+ if (key === "messages" && Array.isArray(value) && value.length > 0) {
34
+ for (const message of value) {
35
+ const text = messageToText(message);
36
+ const role = messageRole(message);
37
+ const baseKey = `message-${keyPart(role)}-${hashText(`${role}\u0000${text}`).slice(0, 8)}`;
38
+ const occurrence = messageKeyCounts.get(baseKey) ?? 0;
39
+ messageKeyCounts.set(baseKey, occurrence + 1);
40
+ const messageKey = occurrence === 0 ? baseKey : `${baseKey}-${occurrence + 1}`;
41
+ blocks.push(createBlock(messageKey, role, text, {
42
+ section: "messages",
43
+ value: message,
44
+ }));
45
+ }
46
+ continue;
47
+ }
48
+ blocks.push(createRequestFieldBlock(key, value));
49
+ }
50
+ if (blocks.length > 0)
51
+ return blocks;
52
+ return [createBlock("payload", "payload", stringify(payload), {
53
+ section: "payload",
54
+ value: payload,
55
+ })];
56
+ }
57
+ function createRequestFieldBlock(key, value) {
58
+ return createBlock(`request-${key}`, "request", stringify({ [key]: value }, 2), {
59
+ section: "request",
60
+ field: key,
61
+ value,
62
+ });
63
+ }
64
+ function createSystemBlock(value) {
65
+ if (typeof value === "string") {
66
+ return createBlock("system", "system", value, {
67
+ section: "system",
68
+ value,
69
+ });
70
+ }
71
+ if (Array.isArray(value)) {
72
+ const systemText = contentBlocksToText(value);
73
+ return createBlock("system", "system", systemText || stringify(value), {
74
+ section: "system",
75
+ value,
76
+ });
77
+ }
78
+ return createBlock("system", "system", stringify(value), {
79
+ section: "system",
80
+ value,
81
+ });
82
+ }
83
+ function messageRole(message) {
84
+ return isPlainObject(message) && typeof message.role === "string" && message.role.length > 0
85
+ ? message.role
86
+ : "message";
87
+ }
88
+ function keyPart(value) {
89
+ const part = value.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
90
+ return part || "message";
91
+ }
92
+ function parseSerializedPayload(serializedPayload) {
93
+ if (serializedPayload === undefined)
94
+ return undefined;
95
+ try {
96
+ return JSON.parse(serializedPayload);
97
+ }
98
+ catch {
99
+ return undefined;
100
+ }
101
+ }
102
+ function stringify(value, space = 0) {
103
+ try {
104
+ const result = JSON.stringify(value, null, space);
105
+ return result === undefined ? String(value) : result;
106
+ }
107
+ catch {
108
+ return String(value);
109
+ }
110
+ }
111
+ function messageToText(message) {
112
+ if (typeof message === "string")
113
+ return message;
114
+ if (!isPlainObject(message))
115
+ return stringify(message);
116
+ const content = message.content;
117
+ if (typeof content === "string")
118
+ return content;
119
+ if (Array.isArray(content))
120
+ return contentBlocksToText(content);
121
+ return stringify(message);
122
+ }
123
+ function contentBlocksToText(content) {
124
+ if (!Array.isArray(content))
125
+ return typeof content === "string" ? content : "";
126
+ const parts = [];
127
+ for (const part of content) {
128
+ if (isPlainObject(part) && typeof part.text === "string") {
129
+ parts.push(part.text);
130
+ }
131
+ else if (isPlainObject(part) && part.type === "image") {
132
+ parts.push("[image content]");
133
+ }
134
+ else if (typeof part === "string") {
135
+ parts.push(part);
136
+ }
137
+ else if (part !== undefined && part !== null) {
138
+ parts.push(stringify(part));
139
+ }
140
+ }
141
+ return parts.join("\n").trim();
142
+ }
143
+ function isPlainObject(value) {
144
+ return !!value && typeof value === "object" && !Array.isArray(value);
145
+ }
146
+ //# sourceMappingURL=context-diff-snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-diff-snapshot.js","sourceRoot":"","sources":["../src/context-diff-snapshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Host-neutral context diff engine.
3
+ *
4
+ * Pure functions only: no DOM, no Vue, no Node APIs. Consecutive turn
5
+ * snapshots are compared with a simple prefix-walk that models KV-cache
6
+ * reuse without attempting a full Myers diff.
7
+ */
8
+ export interface Block {
9
+ key: string;
10
+ role: string;
11
+ text: string;
12
+ chars: number;
13
+ approxTokens: number;
14
+ hash: string;
15
+ }
16
+ export interface TurnSnapshot {
17
+ turnId: string;
18
+ capturedAt: string;
19
+ stackId: string;
20
+ blocks: Block[];
21
+ }
22
+ export type DiffBlockStatus = "same" | "added" | "removed" | "modified";
23
+ export interface DiffBlock {
24
+ status: DiffBlockStatus;
25
+ before?: Block;
26
+ after?: Block;
27
+ tokenDelta: number;
28
+ }
29
+ export interface TurnDiffSummary {
30
+ sameBlocks: number;
31
+ addedBlocks: number;
32
+ removedBlocks: number;
33
+ modifiedBlocks: number;
34
+ changedBlocks: number;
35
+ /** Net tokens introduced by added or lengthened modified blocks. */
36
+ addedTokens: number;
37
+ /** Net tokens removed by removed or shortened modified blocks. */
38
+ removedTokens: number;
39
+ /** Net total token change (`addedTokens - removedTokens`). */
40
+ netTokens: number;
41
+ }
42
+ export interface TurnDiff {
43
+ blocks: DiffBlock[];
44
+ prefixTokens: number;
45
+ prefixRatio: number;
46
+ deltaTokens: number;
47
+ summary: TurnDiffSummary;
48
+ }
49
+ /** Estimate tokens from characters using the same chars/4 approximation as payload capture. */
50
+ export declare function estimateApproxTokens(text: string): number;
51
+ /** Pure string hash used as the block identity shortcut. */
52
+ export declare function hashText(text: string): string;
53
+ export declare function createBlock(key: string, role: string, text: string, serialized?: unknown): Block;
54
+ export declare function createTurnSnapshot(input: {
55
+ turnId: string;
56
+ capturedAt?: string;
57
+ stackId?: string;
58
+ blocks?: Block[];
59
+ }): TurnSnapshot;
60
+ export declare function turnApproxTokens(turn: TurnSnapshot): number;
61
+ /**
62
+ * Diff two consecutive turn snapshots.
63
+ *
64
+ * The cache-boundary walk compares block arrays positionally while hashes
65
+ * match. This preserves the serialized-prefix meaning even when a later
66
+ * block is moved. Classification uses stable block keys after the boundary so
67
+ * a middle insertion does not make every following block look modified.
68
+ */
69
+ export declare function diffTurns(previous: TurnSnapshot | null | undefined, current: TurnSnapshot): TurnDiff;
70
+ //# sourceMappingURL=context-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-diff.d.ts","sourceRoot":"","sources":["../src/context-diff.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,KAAK;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,KAAK,EAAE,CAAC;CAChB;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;AAExE,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,eAAe,CAAC;IACxB,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACxB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,eAAe,CAAC;CACzB;AAED,+FAA+F;AAC/F,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,4DAA4D;AAC5D,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO7C;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,GAAG,KAAK,CAahG;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;CACjB,GAAG,YAAY,CAOf;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAG3D;AA+KD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACxB,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,EACzC,OAAO,EAAE,YAAY,GACnB,QAAQ,CAwCV"}