@svadmin/surface 0.8.19 → 0.10.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.
Files changed (102) hide show
  1. package/README.md +76 -38
  2. package/STYLING.md +33 -0
  3. package/compatibility.json +40 -8
  4. package/dist/agent-contract.d.ts +37 -0
  5. package/dist/agent-contract.js +206 -0
  6. package/dist/agent.d.ts +38 -7
  7. package/dist/agent.js +98 -59
  8. package/dist/binding.d.ts +2 -0
  9. package/dist/binding.js +7 -1
  10. package/dist/builtin-definitions.d.ts +6 -0
  11. package/dist/builtin-definitions.js +40 -0
  12. package/dist/builtin-schemas.d.ts +25 -0
  13. package/dist/builtin-schemas.js +13 -0
  14. package/dist/business-data.d.ts +16 -0
  15. package/dist/business-data.js +42 -0
  16. package/dist/business-definitions.d.ts +28 -0
  17. package/dist/business-definitions.js +53 -0
  18. package/dist/business.d.ts +3 -0
  19. package/dist/business.js +17 -0
  20. package/dist/catalog.d.ts +4 -61
  21. package/dist/catalog.js +20 -44
  22. package/dist/components/ActivityFeedWidget.svelte +34 -0
  23. package/dist/components/ActivityFeedWidget.svelte.d.ts +4 -0
  24. package/dist/components/MetricWidget.svelte +13 -8
  25. package/dist/components/MetricWidget.svelte.d.ts +1 -0
  26. package/dist/components/ResourceDetailWidget.svelte +56 -0
  27. package/dist/components/ResourceDetailWidget.svelte.d.ts +4 -0
  28. package/dist/components/ResourceFormBody.svelte +97 -0
  29. package/dist/components/ResourceFormBody.svelte.d.ts +13 -0
  30. package/dist/components/ResourceFormWidget.svelte +19 -0
  31. package/dist/components/ResourceFormWidget.svelte.d.ts +4 -0
  32. package/dist/components/ResourceTableWidget.svelte +15 -12
  33. package/dist/components/ResourceTableWidget.svelte.d.ts +1 -0
  34. package/dist/components/SurfaceEditPreview.svelte +107 -0
  35. package/dist/components/SurfaceEditPreview.svelte.d.ts +20 -0
  36. package/dist/components/SurfaceRenderer.svelte +121 -163
  37. package/dist/components/SurfaceRenderer.svelte.d.ts +5 -0
  38. package/dist/components/SurfaceWorkflowProvider.svelte +22 -0
  39. package/dist/components/SurfaceWorkflowProvider.svelte.d.ts +17 -0
  40. package/dist/components/SvarGridWidget.svelte +47 -0
  41. package/dist/components/SvarGridWidget.svelte.d.ts +4 -0
  42. package/dist/components/SvarSurfaceProvider.svelte +12 -0
  43. package/dist/components/SvarSurfaceProvider.svelte.d.ts +11 -0
  44. package/dist/design-contract.d.ts +2 -0
  45. package/dist/design-contract.js +2 -0
  46. package/dist/edits.d.ts +57 -0
  47. package/dist/edits.js +166 -0
  48. package/dist/index.d.ts +8 -2
  49. package/dist/index.js +5 -1
  50. package/dist/interactive.d.ts +6 -0
  51. package/dist/interactive.js +15 -0
  52. package/dist/localization.d.ts +11 -1
  53. package/dist/localization.js +20 -0
  54. package/dist/openui.d.ts +64 -0
  55. package/dist/openui.js +168 -0
  56. package/dist/recipes.d.ts +20 -0
  57. package/dist/recipes.js +20 -0
  58. package/dist/schema.d.ts +108 -0
  59. package/dist/schema.js +73 -0
  60. package/dist/server-sqlite.d.ts +1 -0
  61. package/dist/server-sqlite.js +2 -0
  62. package/dist/server.d.ts +4 -0
  63. package/dist/server.js +4 -0
  64. package/dist/source-cache.d.ts +30 -0
  65. package/dist/source-cache.js +135 -0
  66. package/dist/styles/editor.css +124 -0
  67. package/dist/styles/editor.d.ts +8 -0
  68. package/dist/styles/editor.js +9 -0
  69. package/dist/styles.css +43 -0
  70. package/dist/svar-catalog.d.ts +3 -0
  71. package/dist/svar-catalog.js +7 -0
  72. package/dist/svar-context.d.ts +9 -0
  73. package/dist/svar-context.js +1 -0
  74. package/dist/svar-schema.d.ts +21 -0
  75. package/dist/svar-schema.js +36 -0
  76. package/dist/svar.d.ts +4 -0
  77. package/dist/svar.js +3 -0
  78. package/dist/svelte.d.ts +4 -2
  79. package/dist/svelte.js +2 -1
  80. package/dist/types.d.ts +5 -1
  81. package/dist/validation.js +14 -108
  82. package/dist/wire.d.ts +13 -0
  83. package/dist/wire.js +30 -0
  84. package/dist/workflows/action-contracts.d.ts +7 -0
  85. package/dist/workflows/action-contracts.js +54 -0
  86. package/dist/workflows/catalog.d.ts +20 -0
  87. package/dist/workflows/catalog.js +50 -0
  88. package/dist/workflows/client.d.ts +45 -0
  89. package/dist/workflows/client.js +125 -0
  90. package/dist/workflows/context.d.ts +9 -0
  91. package/dist/workflows/context.js +5 -0
  92. package/dist/workflows/openui-guard.d.ts +19 -0
  93. package/dist/workflows/openui-guard.js +209 -0
  94. package/dist/workflows/service.d.ts +58 -0
  95. package/dist/workflows/service.js +296 -0
  96. package/dist/workflows/sqlite-store.d.ts +20 -0
  97. package/dist/workflows/sqlite-store.js +91 -0
  98. package/dist/workflows/types.d.ts +103 -0
  99. package/dist/workflows/types.js +8 -0
  100. package/dist/workflows.d.ts +5 -0
  101. package/dist/workflows.js +4 -0
  102. package/package.json +59 -8
package/README.md CHANGED
@@ -1,16 +1,22 @@
1
1
  # @svadmin/surface
2
2
 
3
- `@svadmin/surface` renders a small, versioned JSON contract as a trusted Svelte dashboard. It is an optional browser package and does not change the public contract of `@svadmin/core`.
3
+ `@svadmin/surface` renders a versioned JSON contract as a trusted Svelte dashboard. It is an optional package and does not change the public contract of `@svadmin/core`.
4
4
 
5
- The MVP supports Svelte 5 + Vite, a fixed 12-column grid, four built-in widgets, read-only `DataProvider` queries, explicit access checks, field projection, and manual host refresh.
5
+ `surface/v1` supports Svelte 5 + Vite, a fixed 12-column grid, four built-in widgets, read-only `DataProvider` queries, explicit access checks, field projection, and manual host refresh. Optional AI helpers derive generation contracts from the component catalog and support revision-checked edit proposals.
6
6
 
7
7
  ## Install
8
8
 
9
+ The declared minimum combination is Core 0.53.0, UI 0.73.0, and Svelte
10
+ 5.56.10, matching the published UI peer requirements. `compatibility.json`
11
+ lists verification targets, not proof of a successful run. Acceptance requires
12
+ the `minimum-supported` strict install and consumer checks in `pack:check` to
13
+ pass against registry artifacts; workspace tarballs do not replace that check.
14
+
9
15
  ```bash
10
16
  bun add @svadmin/surface @svadmin/core @svadmin/ui @tanstack/svelte-query svelte @sinclair/typebox
11
17
  ```
12
18
 
13
- Import protocol types and validation from the DOM-free root entry. Import rendering code from the Svelte subpath.
19
+ Import protocol types and validation from the DOM-free root entry. Import rendering code from the Svelte subpath. Hosts do not need CSS compiler plugins to consume the package's static component styles.
14
20
 
15
21
  ```ts
16
22
  import { validateSurfaceSpec, type SurfacePolicy, type SurfaceSpec } from '@svadmin/surface';
@@ -27,10 +33,7 @@ import {
27
33
  ```svelte
28
34
  <script lang="ts">
29
35
  import type { SurfacePolicy, SurfaceSpec } from '@svadmin/surface';
30
- import {
31
- DEFAULT_SURFACE_CATALOG_VERSION,
32
- SurfaceRenderer,
33
- } from '@svadmin/surface/svelte';
36
+ import { DEFAULT_SURFACE_CATALOG_VERSION, SurfaceRenderer } from '@svadmin/surface/svelte';
34
37
 
35
38
  const policy = {
36
39
  resources: {
@@ -78,35 +81,69 @@ import {
78
81
 
79
82
  When rendered inside `AdminApp`, the renderer resolves the configured provider for each resource. A trusted host may instead pass `dataProvider`, which is still narrowed to `getList` and `getOne`.
80
83
 
81
- The renderer also follows the active `AdminApp` i18n locale. Built-in loading, empty, error, boolean, number, currency, percent, and date presentation updates automatically. A standalone host can pass `locale`, and can override individual built-in strings with `messages`. Business copy such as the surface title, widget titles, labels, and column headers remains part of the JSON spec; build or select a localized spec when the host locale changes.
84
+ The renderer follows the active `AdminApp` i18n locale. Built-in loading, empty, error, boolean, number, currency, percent, and date presentation updates automatically. A standalone host can pass `locale` and override built-in strings with `messages`. Business copy such as titles, labels, and column headers remains part of the JSON spec.
82
85
 
83
86
  ```svelte
84
- <SurfaceRenderer
85
- {spec}
86
- {policy}
87
- locale="zh-CN"
88
- messages={{ tableNoRecords: '没有符合条件的记录' }}
89
- />
87
+ <SurfaceRenderer {spec} {policy} locale="zh-CN"
88
+ messages={{ tableNoRecords: '没有符合条件的记录' }} />
90
89
  ```
91
90
 
92
- ## AI proposals
91
+ ## Incremental loading and session boundaries
92
+
93
+ Title, layout, widget ordering, equivalent JSON and locale-only changes reuse unchanged source results and in-flight reads. Query, projection-policy or provider changes reload only affected sources. `refresh(sourceId)` forces one source and `refresh()` forces all sources. Stale replies and errors are ignored after replacement, removal or destruction; this is logical cancellation, not a transport `AbortSignal` guarantee.
94
+
95
+ Stable widget IDs and component types preserve local input state during visual edits. Removed/retyped widgets can remount. A changed trusted `scopeKey` or `dataScopeKey` clears source identity and remounts local widget state; observable logout, tenant, authentication and access-control identities are also tracked. Hosts must update a non-secret scope token when opaque credentials/session state changes, and discard pending AI proposals on account or tenant changes. These properties are not model-controlled fields. Presentation changes are no longer implicit data refresh or reauthorization signals.
96
+
97
+ ## AI generation contracts
93
98
 
94
- The DOM-free root entry also exposes an opt-in Agent protocol. `buildSurfaceAgentPrompt()` constrains a model to return a proposal envelope and includes the host's widget/resource/field allowlists. `parseSurfaceAgentProposal()` parses and validates the complete `SurfaceSpec` against the same catalog and policy without querying a provider:
99
+ Catalog definitions can include `description` and schema-validated `examples`. The root exports `createSurfaceCatalogManifest()` and `createSurfaceAgentResponseSchema()` for model adapters. Inline, closed TypeBox object schemas are supported; transforms and schema references fail explicitly rather than silently weakening the generated contract.
95
100
 
96
101
  ```ts
97
102
  import {
98
- buildSurfaceAgentPrompt,
99
- parseSurfaceAgentProposal,
103
+ buildSurfaceAgentMessages,
104
+ parseSurfaceAgentResponse,
105
+ selectSurfaceCatalog,
100
106
  } from '@svadmin/surface';
101
-
102
- const prompt = buildSurfaceAgentPrompt('Generate an inventory dashboard', catalog, policy);
103
- const proposal = parseSurfaceAgentProposal(modelText, catalog, policy);
104
- if (proposal.ok) {
105
- // Preview proposal.value.spec, then require explicit user approval before rendering.
107
+ import { defaultSurfaceCatalog } from '@svadmin/surface/svelte';
108
+
109
+ const catalog = selectSurfaceCatalog(defaultSurfaceCatalog, ['metric', 'resource-table']);
110
+ const messages = buildSurfaceAgentMessages('Generate an inventory dashboard', catalog, policy);
111
+ // Send messages through a trusted model adapter. Never expose model credentials.
112
+ const response = parseSurfaceAgentResponse(modelText, catalog, policy);
113
+ if (response.ok && response.value.action === 'propose') {
114
+ // Preview response.value.spec and require explicit approval before applying it.
115
+ } else if (response.ok) {
116
+ // Render response.value.message as text: this is a cannot-fulfill response.
106
117
  }
107
118
  ```
108
119
 
109
- The adapter is deliberately proposal-only. Persistence, revision history, audit records, and the final apply decision belong to the host application. It never executes generated code or mutation actions.
120
+ Use the same task-scoped catalog for generation, validation and rendering. JSON Schema narrows generation but does not replace cross-source binding checks, field policy or server authorization. The schema is not claimed to match every vendor's restricted structured-output subset.
121
+
122
+ `buildSurfaceAgentPrompt()` and `parseSurfaceAgentProposal()` remain compatible with the legacy proposal-only `surface-agent/v1` envelope. The new message API uses `surface-agent/v2` with a structured `cannot-fulfill` alternative. `createSurfaceAgentStream()` buffers bounded text and validates at `finish()`; it does **not** render partial JSON or implement OpenUI Lang streaming.
123
+
124
+ ## Revisioned edits and explicit preview
125
+
126
+ `createSurfaceRevision()` creates a validated immutable snapshot. `buildSurfaceEditMessages()` describes the current revision, catalog and allowed operations. `applySurfaceEditProposal()` validates a complete `surface-edit/v1` transaction and returns a new candidate revision without mutating the old one. Operations cover title/layout, widget/source upsert/removal and complete widget reordering by stable ID. Stale `baseRevision`, unknown IDs, invalid props, denied fields and broken final references reject the whole transaction.
127
+
128
+ The Svelte `SurfaceEditPreview` component accepts a controlled revision and untrusted proposal:
129
+
130
+ ```svelte
131
+ <SurfaceEditPreview
132
+ {revision}
133
+ {proposal}
134
+ {streaming}
135
+ {policy}
136
+ {catalog}
137
+ {dataProvider}
138
+ scopeKey={trustedSessionRevision}
139
+ density="comfortable"
140
+ onApply={applyApprovedRevision}
141
+ />
142
+ ```
143
+
144
+ It leaves the current surface intact during streaming, offers a separate preview/back action, and revalidates at the explicit apply click. It calls `onApply(candidate)` only for a valid proposal. The host performs any persistence with server authorization and atomic revision compare-and-swap, then updates `revision` and clears `proposal`. Missing handlers, invalid proposals and pending callbacks disable application. A rejected callback reports an error without changing the controlled revision. Changing scope does not authorize an old proposal: the host must discard old proposals as described above.
145
+
146
+ Preview controls use local finite class helpers and maintained static CSS. `@svadmin/surface/editor.css` remains available for explicit CSS collection. Density/button variants and complete-color semantic CSS variable overrides are preserved. Widgets use local semantic recipes to retain compatibility with older UI peers; no UI-generated helpers are copied during build. The separate `@svadmin/surface/styles.css` entry remains available and rendering components also import it automatically; see `STYLING.md`.
110
147
 
111
148
  ## Built-in catalog
112
149
 
@@ -117,18 +154,21 @@ The adapter is deliberately proposal-only. Persistence, revision history, audit
117
154
  | `bar-chart` | `/items` | Zero-dependency SVG bar chart |
118
155
  | `line-chart` | `/items` | Zero-dependency SVG line chart |
119
156
 
120
- The catalog version is `svadmin/v1`. `catalogVersion` must match exactly.
121
-
122
- Custom catalogs are trusted runtime configuration. Every registration must use a strict TypeBox object schema and a trusted Svelte component. Item widgets that read record fields must expose those fields through `getReferencedFields`; validation then checks them against `SurfacePolicy.readFields`.
157
+ The catalog version is `svadmin/v1`. `catalogVersion` must match exactly. Custom registrations use strict TypeBox props schemas and trusted Svelte components. Item widgets reading record fields must expose them through `getReferencedFields` so runtime validation checks `SurfacePolicy.readFields`.
123
158
 
124
159
  ```ts
160
+ import { Type } from '@sinclair/typebox';
161
+ import { Value } from '@sinclair/typebox/value';
162
+
163
+ const statusProps = Type.Object({ statusField: Type.String() }, { additionalProperties: false });
125
164
  const catalog = defineSurfaceCatalog({
126
165
  version: 'acme/v1',
127
166
  widgets: [{
128
167
  type: 'status-list',
168
+ description: 'Display a policy-authorized status field from resource items.',
129
169
  dataKind: 'items',
130
- propsSchema: z.object({ statusField: z.string() }).strict(),
131
- getReferencedFields: (props) => [props.statusField as string],
170
+ propsSchema: statusProps,
171
+ getReferencedFields: (props) => [Value.Decode(statusProps, props).statusField],
132
172
  component: StatusList,
133
173
  }],
134
174
  });
@@ -136,18 +176,16 @@ const catalog = defineSurfaceCatalog({
136
176
 
137
177
  ## Security boundary
138
178
 
139
- Surface specs are untrusted data. The renderer validates the whole document before sending any query. It rejects unknown components and sources, duplicate or overlong IDs, invalid props, dangerous pointers, mismatched versions, policy violations, and configured limits.
140
-
141
- Specs cannot contain HTML, Svelte, JavaScript, event handlers, Tailwind classes, style declarations, colors, URLs, SQL, provider selection, `meta`, arbitrary requests, or mutation actions. Provider records are projected to `readFields`, and selected values containing `Date`, `File`, `BigInt`, functions, cycles, `NaN`, or infinities fail instead of being converted.
179
+ Surface specs are untrusted data. The renderer validates the whole document before sending any query. It rejects unknown components/sources, duplicate or overlong IDs, invalid props, dangerous pointers, mismatched versions, policy violations, and configured limits.
142
180
 
143
- The browser access-control check is display gating only. The backend must independently authorize every request and must not trust the spec, policy, or projected fields received from a browser.
181
+ Specs cannot contain executable HTML, Svelte, JavaScript, event handlers, class names, style declarations, colors, URLs, SQL, provider selection, `meta`, arbitrary requests, or mutation actions. Provider records are projected to `readFields`. Selected values containing `Date`, `File`, `BigInt`, functions, cycles, `NaN`, or infinities fail instead of being converted.
144
182
 
145
- MVP limits are eight data sources, 24 widgets, 100 rows per page, eight filters, three sorters, 64-character IDs, 64 levels of JSON nesting, and 10,000 JSON nodes. A source is loaded once and shared. Generation checks discard stale responses.
183
+ The browser access-control check is display gating only. The backend must independently authorize every request and must not trust a spec, client policy or projected fields as proof of authority. AI and edit helpers neither contact a model nor execute business writes. Persistence, audit records, model transport and approval policy belong to the host.
146
184
 
147
- Supported Core/UI ranges and the minimum packed-consumer matrix are published in `compatibility.json`.
185
+ Limits are eight data sources, 24 widgets, 100 rows per page, eight filters, three sorters, 64-character IDs, 64 levels of JSON nesting, and 10,000 JSON nodes. AI text/request/contract sizes and edit operation counts are separately bounded by their exported limit constants. Supported Core/UI ranges and packed-consumer requirements are published in `compatibility.json`.
148
186
 
149
- ## Out of scope for v1
187
+ ## Current boundaries
150
188
 
151
- SSR/Lite, Agent generation, storage, revisions, JSON Patch, actions, mutations, automatic refresh, arbitrary URLs, client aggregation, Canvas, and iframe rendering are intentionally absent. Aggregated metrics should come from a backend summary resource and bind through `resource-one`.
189
+ Actual OpenUI Lang parsing, nested interactive forms, registered business actions, server persistence, SSR/Lite rendering, arbitrary URLs, client aggregation, Canvas and iframe execution are not implemented. Aggregated metrics should come from a policy-authorized backend summary resource and bind through `resource-one`. The current UI contract remains read-only even though its definition can be edited.
152
190
 
153
- 中文指南见文档站的“声明式 Surface”。
191
+ See `docs/architecture/openui-surface-phase2.md` for the original feature scope and `docs/architecture/surface-integration-provenance.md` for historical integration evidence. 中文指南见文档站的“声明式 Surface”。
package/STYLING.md ADDED
@@ -0,0 +1,33 @@
1
+ # Surface semantic styles
2
+
3
+ 默认 `svadmin/v1` 的校验规则和未指定变体时的组件样式保持兼容。新语义变体必须显式选择 `styledSurfaceCatalog`,不能只把 tone/density 添加到旧版 spec。
4
+
5
+ ```ts
6
+ import '@svadmin/ui/app.css';
7
+ import '@svadmin/surface/styles.css';
8
+ import { styledSurfaceCatalog, STYLED_SURFACE_CATALOG_VERSION } from '@svadmin/surface/svelte';
9
+ ```
10
+
11
+ 将 spec.catalogVersion 设置为 STYLED_SURFACE_CATALOG_VERSION,并把同一 styledSurfaceCatalog 传给提示生成器、校验器和 SurfaceRenderer 的 catalog 属性。这样三个环节使用同一组件契约。`metric` 支持 tone(neutral/success/warning/danger/info)及 density(comfortable/compact),`resource-table` 支持 density。
12
+
13
+ ```json
14
+ {
15
+ "type": "metric",
16
+ "props": {
17
+ "label": "待处理订单",
18
+ "format": "number",
19
+ "tone": "warning",
20
+ "density": "compact"
21
+ }
22
+ }
23
+ ```
24
+
25
+ 以上只是 widget 片段;完整 spec 仍需要 schemaVersion、catalogVersion、数据源、绑定及布局等必要字段。
26
+
27
+ Surface 使用本地有限 recipe helper 和静态 styles.css;design-contract 统一从 @svadmin/ui/design-contract 导入,要求 UI >=0.73.0 <0.74.0。不复制 UI 生成物,也不依赖样式编译器。渲染组件自动导入语义样式;原有 styles.css 子路径仍可显式导入。UI 基础样式仍由宿主通过 @svadmin/ui/app.css 加载。构建仅执行 svelte-package;styles:check 检查全部有限变体及静态 CSS 边界。
28
+
29
+ editor.css 与本地 styles/editor.ts 一起维护,保留 density、button variant、禁用/焦点状态和语义变量覆盖;editor.css 子路径不变。宿主升级时应同时升级 UI 与 Surface,并重新执行消费侧构建。
30
+
31
+ AI 只能选择公开枚举,不能指定 class/style、任意颜色、recipe 定义或可执行代码。字段权限、只读查询、版本匹配和宿主确认要求不因样式变体而改变。
32
+
33
+ 此次仅替换样式基础设施。OpenUI 入口、catalog/schema 校验、workflow 授权与执行、提案预览及宿主确认逻辑保持不变;去除样式编译器不代表增加新的协议能力。
@@ -1,16 +1,16 @@
1
1
  {
2
- "surface": "0.8.x",
2
+ "surface": "0.10.x",
3
3
  "minimumSupported": {
4
- "@svadmin/core": "0.34.2",
5
- "@svadmin/ui": "0.40.6",
6
- "svelte": "5.56.8"
4
+ "@svadmin/core": "0.53.0",
5
+ "@svadmin/ui": "0.73.0",
6
+ "svelte": "5.56.10"
7
7
  },
8
8
  "testedCombinations": [
9
9
  {
10
10
  "name": "minimum-supported",
11
- "core": "0.34.2",
12
- "ui": "0.40.6",
13
- "svelte": "5.56.8"
11
+ "core": "0.53.0",
12
+ "ui": "0.73.0",
13
+ "svelte": "5.56.10"
14
14
  },
15
15
  {
16
16
  "name": "workspace-packed",
@@ -18,5 +18,37 @@
18
18
  "ui": "workspace tarball",
19
19
  "svelte": "workspace override"
20
20
  }
21
- ]
21
+ ],
22
+ "minimumSupportedAppliesTo": [
23
+ ".",
24
+ "./svelte"
25
+ ],
26
+ "entrypointCompatibility": {
27
+ "./interactive": {
28
+ "status": "experimental",
29
+ "requiredUiCapabilities": [
30
+ "JsonSchemaForm.idPrefix",
31
+ "proxy-safe nested field updates",
32
+ "required boolean and decimal input support"
33
+ ],
34
+ "minimumPublishedUi": null,
35
+ "verification": "same-checkout tarballs via scripts/surface-workflows/packed-consumer.mjs; inspect the current run result",
36
+ "releaseGate": "Coordinate a tested UI release and narrow the package peer floor before publishing this entry."
37
+ },
38
+ "./server/sqlite": {
39
+ "runtime": "Node.js",
40
+ "verification": "Current CI Node version with real node:sqlite; no browser or distributed-store support implied."
41
+ },
42
+ "./business": {
43
+ "status": "experimental",
44
+ "requiredUiCapabilities": [
45
+ "ActivityFeed.title",
46
+ "ActivityFeed.emptyLabel",
47
+ "ActivityFeed.countLabel"
48
+ ],
49
+ "minimumPublishedUi": null,
50
+ "verification": "same-checkout business + interactive packed consumer; current Surface workflow reports",
51
+ "releaseGate": "Coordinate a tested UI release and verify the peer floor before publishing this new entry."
52
+ }
53
+ }
22
54
  }
@@ -0,0 +1,37 @@
1
+ import { type TSchema } from '@sinclair/typebox';
2
+ import { type JsonObject, type SurfaceCatalog, type SurfacePolicy } from './types.js';
3
+ export declare const SURFACE_CATALOG_SCHEMA_VERSION: "surface-catalog/v1";
4
+ export declare const SURFACE_AGENT_SCHEMA_VERSION: "surface-agent/v1";
5
+ export declare const SURFACE_AGENT_RESPONSE_SCHEMA_VERSION: "surface-agent/v2";
6
+ export declare const SURFACE_AGENT_LIMITS: {
7
+ readonly maxInputCharacters: 262144;
8
+ readonly maxRequestCharacters: 16384;
9
+ readonly maxContractCharacters: 131072;
10
+ };
11
+ export interface SurfaceCatalogManifest {
12
+ readonly schemaVersion: typeof SURFACE_CATALOG_SCHEMA_VERSION;
13
+ readonly catalogVersion: string;
14
+ readonly widgets: readonly {
15
+ readonly type: string;
16
+ readonly dataKind: 'none' | 'scalar' | 'items' | 'record';
17
+ readonly description?: string;
18
+ readonly propsSchema: JsonObject;
19
+ readonly examples?: readonly JsonObject[];
20
+ }[];
21
+ }
22
+ /** 不把 Transform、函数或引用 schema 静默降级为宽松的 JSON schema。 */
23
+ export declare function surfaceSchemaToJson(schema: TSchema): JsonObject;
24
+ /** 同一目录可用于提示、运行时校验及渲染;任务筛选不会仅在提示词中生效。 */
25
+ export declare function selectSurfaceCatalog<T extends SurfaceCatalog>(catalog: T, widgetTypes: readonly string[]): Omit<T, 'widgets'> & {
26
+ readonly widgets: readonly T['widgets'][number][];
27
+ };
28
+ export declare function createSurfaceCatalogManifest(catalog: SurfaceCatalog): SurfaceCatalogManifest;
29
+ /** 生成约束缩小候选空间,不能代替绑定/字段关联校验,更不能代替后端授权。 */
30
+ export declare function createSurfaceGenerationSpecSchema(catalog: SurfaceCatalog, policy: SurfacePolicy): TSchema;
31
+ export declare function createSurfaceAgentResponseSchema(catalog: SurfaceCatalog, policy: SurfacePolicy): JsonObject;
32
+ export declare const surfaceCannotFulfillSchema: import("@sinclair/typebox").TObject<{
33
+ schemaVersion: import("@sinclair/typebox").TLiteral<"surface-agent/v2">;
34
+ action: import("@sinclair/typebox").TLiteral<"cannot-fulfill">;
35
+ reason: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"unsupported_request">, import("@sinclair/typebox").TLiteral<"insufficient_permissions">, import("@sinclair/typebox").TLiteral<"insufficient_context">]>;
36
+ message: import("@sinclair/typebox").TString;
37
+ }>;
@@ -0,0 +1,206 @@
1
+ import { Kind, OptionalKind, ReadonlyKind, Type } from '@sinclair/typebox';
2
+ import { Value } from '@sinclair/typebox/value';
3
+ import { escapedJsonPointerToken, jsonValueIssue } from './json.js';
4
+ import { surfaceFilterSchema, surfaceIdSchema, surfaceListSourceSchema, surfaceOneSourceSchema, surfaceSpecSchema, surfaceWidgetSchema, } from './schema.js';
5
+ import { SURFACE_LIMITS, SURFACE_SCHEMA_VERSION, } from './types.js';
6
+ export const SURFACE_CATALOG_SCHEMA_VERSION = 'surface-catalog/v1';
7
+ export const SURFACE_AGENT_SCHEMA_VERSION = 'surface-agent/v1';
8
+ export const SURFACE_AGENT_RESPONSE_SCHEMA_VERSION = 'surface-agent/v2';
9
+ export const SURFACE_AGENT_LIMITS = {
10
+ maxInputCharacters: 262_144,
11
+ maxRequestCharacters: 16_384,
12
+ maxContractCharacters: 131_072,
13
+ };
14
+ /** 不把 Transform、函数或引用 schema 静默降级为宽松的 JSON schema。 */
15
+ export function surfaceSchemaToJson(schema) {
16
+ const ancestors = new Set();
17
+ let nodes = 0;
18
+ function visit(value, depth) {
19
+ nodes += 1;
20
+ if (nodes > SURFACE_LIMITS.maxJsonNodes || depth > SURFACE_LIMITS.maxJsonDepth) {
21
+ throw new Error('Surface schema exceeds the supported size');
22
+ }
23
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
24
+ return value;
25
+ if (typeof value === 'number' && Number.isFinite(value))
26
+ return value;
27
+ if (typeof value !== 'object' || ancestors.has(value)) {
28
+ throw new Error('Surface schema must be serializable without transforms or cycles');
29
+ }
30
+ const prototype = Object.getPrototypeOf(value);
31
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
32
+ throw new Error('Surface schema must contain only plain objects');
33
+ }
34
+ ancestors.add(value);
35
+ const result = Object.create(null);
36
+ for (const key of Reflect.ownKeys(value)) {
37
+ if (Array.isArray(value) && key === 'length')
38
+ continue;
39
+ if (typeof key === 'symbol') {
40
+ if (key === Kind || key === OptionalKind || key === ReadonlyKind)
41
+ continue;
42
+ throw new Error('Surface schema contains unsupported symbol metadata or a transform');
43
+ }
44
+ if (['__proto__', 'constructor', 'prototype', '$ref', '$dynamicRef', '$recursiveRef'].includes(key)) {
45
+ throw new Error(`Surface schema key "${key}" is not supported; use an inline schema`);
46
+ }
47
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
48
+ if (!descriptor?.enumerable || !('value' in descriptor)) {
49
+ throw new Error('Surface schema accessors and hidden properties are not supported');
50
+ }
51
+ result[key] = visit(descriptor.value, depth + 1);
52
+ }
53
+ ancestors.delete(value);
54
+ if (Array.isArray(value)) {
55
+ if (Object.keys(result).length !== value.length || Object.keys(result).some((key) => !/^(0|[1-9][0-9]*)$/u.test(key) || Number(key) >= value.length)) {
56
+ throw new Error('Surface schema arrays must be dense JSON arrays');
57
+ }
58
+ return Array.from({ length: value.length }, (_, index) => {
59
+ const item = result[String(index)];
60
+ if (item === undefined)
61
+ throw new Error('Surface schema arrays must be dense JSON arrays');
62
+ return item;
63
+ });
64
+ }
65
+ return result;
66
+ }
67
+ const result = visit(schema, 0);
68
+ if (result === null || typeof result !== 'object' || Array.isArray(result)) {
69
+ throw new Error('Expected an object JSON schema');
70
+ }
71
+ if (JSON.stringify(result).length > SURFACE_AGENT_LIMITS.maxContractCharacters) {
72
+ throw new Error('Surface schema exceeds the supported character count');
73
+ }
74
+ return result;
75
+ }
76
+ function isClosedObject(schema) {
77
+ if (schema['type'] === 'object' && schema['additionalProperties'] === false)
78
+ return true;
79
+ const branches = schema['anyOf'] ?? schema['oneOf'] ?? schema['allOf'];
80
+ return Array.isArray(branches) && branches.length > 0 && branches.every((branch) => (branch !== null && typeof branch === 'object' && !Array.isArray(branch) && isClosedObject(branch)));
81
+ }
82
+ /** 同一目录可用于提示、运行时校验及渲染;任务筛选不会仅在提示词中生效。 */
83
+ export function selectSurfaceCatalog(catalog, widgetTypes) {
84
+ const requested = new Set(widgetTypes);
85
+ if (requested.size !== widgetTypes.length)
86
+ throw new Error('Duplicate requested surface widget type');
87
+ const known = new Set(catalog.widgets.map((widget) => widget.type));
88
+ for (const type of requested) {
89
+ if (!known.has(type))
90
+ throw new Error(`Unknown surface widget type "${type}"`);
91
+ }
92
+ return { ...catalog, widgets: catalog.widgets.filter((widget) => requested.has(widget.type)) };
93
+ }
94
+ export function createSurfaceCatalogManifest(catalog) {
95
+ if (!catalog.version)
96
+ throw new Error('Surface catalog version must not be empty');
97
+ const known = new Set();
98
+ const widgets = catalog.widgets.map((widget) => {
99
+ if (!/^[A-Za-z][A-Za-z0-9_-]*$/u.test(widget.type) || widget.type.length > SURFACE_LIMITS.maxIdLength || known.has(widget.type)) {
100
+ throw new Error(`Invalid or duplicate surface widget type "${widget.type}"`);
101
+ }
102
+ known.add(widget.type);
103
+ if (!['none', 'scalar', 'items', 'record'].includes(widget.dataKind))
104
+ throw new Error('Invalid surface widget data kind');
105
+ if (widget.dataKind === 'record' && !widget.getReferencedFields)
106
+ throw new Error('Record widgets require an explicit field selector');
107
+ const propsSchema = surfaceSchemaToJson(widget.propsSchema);
108
+ if (!isClosedObject(propsSchema))
109
+ throw new Error(`Widget "${widget.type}" must use a closed object props schema`);
110
+ const examples = widget.examples?.map((props) => {
111
+ if (jsonValueIssue(props) || !Value.Check(widget.propsSchema, props)) {
112
+ throw new Error(`Invalid props example for widget "${widget.type}"`);
113
+ }
114
+ return JSON.parse(JSON.stringify(props));
115
+ });
116
+ return {
117
+ type: widget.type,
118
+ dataKind: widget.dataKind,
119
+ ...(widget.description === undefined ? {} : { description: widget.description }),
120
+ propsSchema,
121
+ ...(examples === undefined ? {} : { examples }),
122
+ };
123
+ });
124
+ const manifest = {
125
+ schemaVersion: SURFACE_CATALOG_SCHEMA_VERSION,
126
+ catalogVersion: catalog.version,
127
+ widgets,
128
+ };
129
+ if (jsonValueIssue(manifest) || JSON.stringify(manifest).length > SURFACE_AGENT_LIMITS.maxContractCharacters) {
130
+ throw new Error('Surface catalog manifest exceeds the JSON contract limits');
131
+ }
132
+ return manifest;
133
+ }
134
+ function literals(values) {
135
+ return values.length === 0 ? Type.Never() : Type.Union([...new Set(values)].map((value) => Type.Literal(value)));
136
+ }
137
+ /** 生成约束缩小候选空间,不能代替绑定/字段关联校验,更不能代替后端授权。 */
138
+ export function createSurfaceGenerationSpecSchema(catalog, policy) {
139
+ createSurfaceCatalogManifest(catalog);
140
+ const sources = [];
141
+ const scalarPointers = new Set(['/total']);
142
+ for (const [resource, rule] of Object.entries(policy.resources)) {
143
+ const limit = Math.min(rule.maxPageSize ?? SURFACE_LIMITS.maxPageSize, SURFACE_LIMITS.maxPageSize);
144
+ if (!Number.isSafeInteger(limit) || limit < 1)
145
+ throw new Error(`Invalid page limit for resource "${resource}"`);
146
+ const filters = Type.Union(surfaceFilterSchema.anyOf.map((branch) => Type.Object({
147
+ ...branch.properties,
148
+ field: literals(rule.filterFields ?? []),
149
+ }, { additionalProperties: false })));
150
+ sources.push(Type.Object({
151
+ ...surfaceListSourceSchema.properties,
152
+ resource: Type.Literal(resource),
153
+ // 显式页大小避免宿主默认值 10 超过小于 10 的策略上限。
154
+ pageSize: Type.Integer({ minimum: 1, maximum: limit }),
155
+ sorters: Type.Optional(Type.Array(Type.Object({
156
+ field: literals(rule.sortFields ?? []),
157
+ order: Type.Union([Type.Literal('asc'), Type.Literal('desc')]),
158
+ }, { additionalProperties: false }), { maxItems: (rule.sortFields?.length ?? 0) > 0 ? SURFACE_LIMITS.maxSorters : 0 })),
159
+ filters: Type.Optional(Type.Array(filters, { maxItems: (rule.filterFields?.length ?? 0) > 0 ? SURFACE_LIMITS.maxFilters : 0 })),
160
+ }, { additionalProperties: false }));
161
+ if (rule.allowGetOne === true) {
162
+ sources.push(Type.Object({ ...surfaceOneSourceSchema.properties, resource: Type.Literal(resource) }, { additionalProperties: false }));
163
+ for (const field of rule.readFields)
164
+ scalarPointers.add(`/${escapedJsonPointerToken(field)}`);
165
+ }
166
+ }
167
+ const widgets = catalog.widgets.map((widget) => {
168
+ const { binding: _binding, ...properties } = surfaceWidgetSchema.properties;
169
+ return Type.Object({
170
+ ...properties,
171
+ type: Type.Literal(widget.type),
172
+ props: widget.propsSchema,
173
+ ...(widget.dataKind === 'none' ? {} : {
174
+ binding: Type.Object({
175
+ sourceId: surfaceIdSchema,
176
+ pointer: widget.dataKind === 'items' ? Type.Literal('/items')
177
+ : widget.dataKind === 'record' ? Type.Literal('') : literals([...scalarPointers]),
178
+ }, { additionalProperties: false }),
179
+ }),
180
+ }, { additionalProperties: false, ...(widget.description ? { description: widget.description } : {}) });
181
+ });
182
+ return Type.Object({
183
+ ...surfaceSpecSchema.properties,
184
+ schemaVersion: Type.Literal(SURFACE_SCHEMA_VERSION),
185
+ catalogVersion: Type.Literal(catalog.version),
186
+ dataSources: Type.Array(sources.length ? Type.Union(sources) : Type.Never(), { maxItems: sources.length ? SURFACE_LIMITS.maxDataSources : 0 }),
187
+ widgets: Type.Array(widgets.length ? Type.Union(widgets) : Type.Never(), { maxItems: widgets.length ? SURFACE_LIMITS.maxWidgets : 0 }),
188
+ }, { additionalProperties: false });
189
+ }
190
+ export function createSurfaceAgentResponseSchema(catalog, policy) {
191
+ const proposal = Type.Object({
192
+ schemaVersion: Type.Literal(SURFACE_AGENT_RESPONSE_SCHEMA_VERSION),
193
+ action: Type.Literal('propose'),
194
+ summary: Type.Optional(Type.String({ minLength: 1, maxLength: 240 })),
195
+ spec: createSurfaceGenerationSpecSchema(catalog, policy),
196
+ }, { additionalProperties: false });
197
+ return surfaceSchemaToJson(Type.Union([proposal, surfaceCannotFulfillSchema]));
198
+ }
199
+ export const surfaceCannotFulfillSchema = Type.Object({
200
+ schemaVersion: Type.Literal(SURFACE_AGENT_RESPONSE_SCHEMA_VERSION),
201
+ action: Type.Literal('cannot-fulfill'),
202
+ reason: Type.Union([
203
+ Type.Literal('unsupported_request'), Type.Literal('insufficient_permissions'), Type.Literal('insufficient_context'),
204
+ ]),
205
+ message: Type.String({ minLength: 1, maxLength: 480 }),
206
+ }, { additionalProperties: false });
package/dist/agent.d.ts CHANGED
@@ -1,12 +1,18 @@
1
+ import { type Static } from '@sinclair/typebox';
2
+ import { SURFACE_AGENT_RESPONSE_SCHEMA_VERSION, SURFACE_AGENT_SCHEMA_VERSION, surfaceCannotFulfillSchema } from './agent-contract.js';
1
3
  import type { SurfaceCatalog, SurfacePolicy, SurfaceSpec, SurfaceValidationIssue } from './types.js';
2
- /** Wire version for model-produced, human-reviewable Surface proposals. */
3
- export declare const SURFACE_AGENT_SCHEMA_VERSION: "surface-agent/v1";
4
+ export { SURFACE_AGENT_SCHEMA_VERSION } from './agent-contract.js';
4
5
  export interface SurfaceAgentProposal {
5
6
  readonly schemaVersion: typeof SURFACE_AGENT_SCHEMA_VERSION;
6
7
  readonly action: 'propose';
7
8
  readonly summary?: string;
8
9
  readonly spec: SurfaceSpec;
9
10
  }
11
+ export interface SurfaceAgentProposalV2 extends Omit<SurfaceAgentProposal, 'schemaVersion'> {
12
+ readonly schemaVersion: typeof SURFACE_AGENT_RESPONSE_SCHEMA_VERSION;
13
+ }
14
+ export type SurfaceAgentCannotFulfill = Static<typeof surfaceCannotFulfillSchema>;
15
+ export type SurfaceAgentResponse = SurfaceAgentProposal | SurfaceAgentProposalV2 | SurfaceAgentCannotFulfill;
10
16
  export type SurfaceAgentValidationResult = {
11
17
  readonly ok: true;
12
18
  readonly value: SurfaceAgentProposal;
@@ -14,10 +20,35 @@ export type SurfaceAgentValidationResult = {
14
20
  readonly ok: false;
15
21
  readonly issues: readonly SurfaceValidationIssue[];
16
22
  };
17
- /**
18
- * Parse and fully validate an AI-generated proposal before a host previews it.
19
- * This function has no side effects and never queries a provider.
20
- */
23
+ export type SurfaceAgentResponseResult = {
24
+ readonly ok: true;
25
+ readonly value: SurfaceAgentResponse;
26
+ } | {
27
+ readonly ok: false;
28
+ readonly issues: readonly SurfaceValidationIssue[];
29
+ };
30
+ export interface SurfaceAgentMessage {
31
+ readonly role: 'system' | 'user';
32
+ readonly content: string;
33
+ }
34
+ /** 保留 v1 的函数签名、返回类型以及带说明文字的 fenced JSON 兼容行为。 */
21
35
  export declare function parseSurfaceAgentProposal(input: unknown, catalog: SurfaceCatalog, policy: SurfacePolicy): SurfaceAgentValidationResult;
22
- /** Build a model instruction that keeps generation inside the Surface contract. */
36
+ /** 结构化“无法完成”也是有效响应,但永远不是可渲染/可执行的提案。 */
37
+ export declare function parseSurfaceAgentResponse(input: unknown, catalog: SurfaceCatalog, policy: SurfacePolicy): SurfaceAgentResponseResult;
38
+ /** 兼容旧 v1 输出,同时补齐从组件契约派生的完整参数/结构 schema。 */
23
39
  export declare function buildSurfaceAgentPrompt(request: string, catalog: SurfaceCatalog, policy: SurfacePolicy): string;
40
+ /** 系统契约与用户需求分开传递;不绑定某个模型厂商或 CSS 编译器。 */
41
+ export declare function buildSurfaceAgentMessages(request: string, catalog: SurfaceCatalog, policy: SurfacePolicy): readonly SurfaceAgentMessage[];
42
+ export interface SurfaceAgentStream {
43
+ /** 接收文本,不解析、不渲染、不查询数据。 */
44
+ push(chunk: string): {
45
+ readonly ok: true;
46
+ readonly characters: number;
47
+ } | Extract<SurfaceAgentResponseResult, {
48
+ ok: false;
49
+ }>;
50
+ /** 仅结束后返回整体校验结果。重复调用返回同一终态。 */
51
+ finish(): SurfaceAgentResponseResult;
52
+ }
53
+ /** 有界传输缓冲,不冒充 OpenUI 增量渲染;半成品不进入运行时。 */
54
+ export declare function createSurfaceAgentStream(catalog: SurfaceCatalog, policy: SurfacePolicy): SurfaceAgentStream;