@dp-bohrium/widget-cli 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { createHash } from "node:crypto";
3
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
+ import { existsSync, lstatSync, readFileSync, readdirSync, statSync } from "node:fs";
4
4
  import { lstat, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
5
5
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
@@ -95,52 +95,78 @@ function collectReferencedFileIds(request) {
95
95
  return referenced;
96
96
  }
97
97
  //#endregion
98
- //#region ../widget-project/src/manifest.mjs
99
- const require$2 = createRequire(import.meta.url);
98
+ //#region ../widget-sdk/dist/runtime-DVFTv55x.js
99
+ /** 从当前安装的 SDK package.json 读取版本,插件作者不需要重复填写。 */
100
+ const WIDGET_SDK_VERSION = "0.5.0";
101
+ //#endregion
102
+ //#region ../widget-sdk/dist/index.js
103
+ function compiledContribution(contribution) {
104
+ switch (contribution.type) {
105
+ case "panel": return {
106
+ type: "panel",
107
+ id: contribution.id,
108
+ ...contribution.placement === void 0 ? {} : { placement: contribution.placement }
109
+ };
110
+ case "file-preview": return {
111
+ type: "file-preview",
112
+ extensions: [...contribution.extensions]
113
+ };
114
+ }
115
+ }
100
116
  /**
101
- * Convert the author-facing VS Code-style plugin manifest to the Host
102
- * manifest consumed by Host Runtime Config. This is deliberately a tooling
103
- * boundary: source code and editor tooling use `contributes`, while only the
104
- * upload/preview pipeline knows about `widgetConfig.contribution` and protocol fields.
117
+ * 将开发者维护的插件清单编译为宿主可加载的宿主清单。
118
+ *
119
+ * 这是构建工具的边界函数,不是类型提示入口:作者直接编辑带 `$schema` 的 JSON,
120
+ * SDK 版本从当前安装包读取,宿主入口默认落到 dist/widget.js,内部 contribution 标识只在这里生成。
105
121
  */
106
- function compileAuthorManifest(manifest, options = {}) {
107
- if (!isAuthorManifest(manifest)) return manifest;
108
- const sdkVersion = options.sdkVersion ?? installedSdkVersion();
122
+ function compileWidgetPluginManifest(manifest, options = {}) {
123
+ const samples = options.samples ?? manifest.samples;
124
+ if (!samples || samples.length === 0) throw new Error("Widget Plugin Manifest must provide at least one sample before packaging");
109
125
  const contribution = manifest.contributes.widget;
110
- if (!contribution || typeof contribution !== "object" || !["panel", "file-preview"].includes(contribution.type) || contribution.type === "file-preview" && !Array.isArray(contribution.extensions)) return manifest;
111
- const compiledContribution = contribution.type === "panel" ? {
112
- type: "panel",
113
- id: contribution.id
114
- } : {
115
- type: "file-preview",
116
- extensions: [...contribution.extensions]
117
- };
126
+ if (contribution.type === "panel" && contribution.placement?.kind === "region" && (manifest.capabilities?.region?.mode !== contribution.placement.mode || !manifest.capabilities.region.regions.includes(contribution.placement.region))) throw new Error("A region Widget contribution must declare a matching region capability");
127
+ const activationEvents = manifest.activationEvents ? [...manifest.activationEvents] : void 0;
118
128
  return {
119
129
  name: manifest.name,
120
130
  ...manifest.description === void 0 ? {} : { description: manifest.description },
121
131
  widgetConfig: {
122
132
  schemaVersion: 1,
123
133
  hostApiVersion: 1,
124
- widgetSdkVersion: sdkVersion,
134
+ widgetSdkVersion: WIDGET_SDK_VERSION,
125
135
  entry: options.hostEntry ?? "dist/widget.js",
126
- contribution: compiledContribution,
127
- ...manifest.activationEvents === void 0 ? {} : { activationEvents: manifest.activationEvents },
136
+ contribution: compiledContribution(manifest.contributes.widget),
137
+ ...activationEvents === void 0 ? {} : { activationEvents },
128
138
  ...manifest.capabilities === void 0 ? {} : { requestedCapabilities: manifest.capabilities },
129
139
  ...manifest.runtime === void 0 ? {} : { runtime: manifest.runtime },
130
- samples: options.samples ?? manifest.samples ?? []
140
+ samples
131
141
  }
132
142
  };
133
143
  }
144
+ //#endregion
145
+ //#region ../widget-project/src/manifest.mjs
146
+ /**
147
+ * Convert the author-facing VS Code-style plugin manifest to the Host
148
+ * manifest consumed by Host Runtime Config. This is deliberately a tooling
149
+ * boundary: source code and editor tooling use `contributes`, while only the
150
+ * upload/preview pipeline knows about `widgetConfig.contribution` and protocol fields.
151
+ */
152
+ function compileAuthorManifest(manifest, options = {}) {
153
+ if (!isAuthorManifest(manifest)) return manifest;
154
+ const contribution = manifest.contributes?.widget;
155
+ if (!contribution || typeof contribution !== "object" || !["panel", "file-preview"].includes(contribution.type) || contribution.type === "file-preview" && !Array.isArray(contribution.extensions)) return manifest;
156
+ const compiled = (options.compiler ?? compileWidgetPluginManifest)(manifest, {
157
+ hostEntry: options.hostEntry,
158
+ samples: options.samples
159
+ });
160
+ if (options.sdkVersion !== void 0 && compiled.widgetConfig.widgetSdkVersion !== options.sdkVersion) {
161
+ const error = /* @__PURE__ */ new Error(`Manifest compiler SDK version ${compiled.widgetConfig.widgetSdkVersion} does not match requested SDK version ${options.sdkVersion}`);
162
+ error.code = "WIDGET_CLI_PROJECT_SDK_MISMATCH";
163
+ throw error;
164
+ }
165
+ return compiled;
166
+ }
134
167
  function isAuthorManifest(value) {
135
168
  return Boolean(value && typeof value === "object" && !Array.isArray(value) && "contributes" in value && "version" in value);
136
169
  }
137
- function installedSdkVersion() {
138
- try {
139
- return require$2("@dp-bohrium/widget-sdk/package.json").version;
140
- } catch {
141
- return "0.0.0-dev";
142
- }
143
- }
144
170
  //#endregion
145
171
  //#region ../../node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
146
172
  var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
@@ -6798,7 +6824,7 @@ var require_json_schema_2020_12 = /* @__PURE__ */ __commonJSMin(((exports) => {
6798
6824
  exports.default = addMetaSchema2020;
6799
6825
  }));
6800
6826
  //#endregion
6801
- //#region ../contract-testing/dist/manifest-validator-BWdmIEES.js
6827
+ //#region ../contract-testing/dist/manifest-validator-DUzSsw8D.js
6802
6828
  var import__2020 = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
6803
6829
  Object.defineProperty(exports, "__esModule", { value: true });
6804
6830
  exports.MissingRefError = exports.ValidationError = exports.CodeGen = exports.Name = exports.nil = exports.stringify = exports.str = exports._ = exports.KeywordCxt = exports.Ajv2020 = void 0;
@@ -6978,7 +7004,7 @@ const widgetManifestSchema = {
6978
7004
  }
6979
7005
  },
6980
7006
  "FilesCapabilityRequest": {
6981
- "description": "只读文件能力的申请开关;对应 files.list / files.metadata / files.read / files.download。",
7007
+ "description": "文件能力的独立申请开关;保存已有文件必须单独申请 files.write 并获得 Host 授权,可写范围由 Host grant 决定。",
6982
7008
  "title": "FilesCapabilityRequest",
6983
7009
  "type": "object",
6984
7010
  "additionalProperties": false,
@@ -6986,7 +7012,8 @@ const widgetManifestSchema = {
6986
7012
  "list": { "type": "boolean" },
6987
7013
  "metadata": { "type": "boolean" },
6988
7014
  "read": { "type": "boolean" },
6989
- "download": { "type": "boolean" }
7015
+ "download": { "type": "boolean" },
7016
+ "write": { "type": "boolean" }
6990
7017
  }
6991
7018
  },
6992
7019
  "EventsCapabilityRequest": {
@@ -7009,12 +7036,15 @@ const widgetManifestSchema = {
7009
7036
  } }
7010
7037
  },
7011
7038
  "TasksCapabilityRequest": {
7012
- "description": "任务观察能力的申请;watch 固定为 true。",
7039
+ "description": "任务发现与观察能力的申请;list / watch 至少显式申请一项,已申请项固定为 true。",
7013
7040
  "title": "TasksCapabilityRequest",
7014
7041
  "type": "object",
7015
7042
  "additionalProperties": false,
7016
- "required": ["watch"],
7017
- "properties": { "watch": { "const": true } }
7043
+ "minProperties": 1,
7044
+ "properties": {
7045
+ "list": { "const": true },
7046
+ "watch": { "const": true }
7047
+ }
7018
7048
  },
7019
7049
  "StateCapabilityRequest": {
7020
7050
  "description": "受限 UI 状态读写能力的申请;read / write 分别声明。",
@@ -7439,9 +7469,14 @@ const widgetRuntimeContractsSchema = {
7439
7469
  "presentationCapabilityResult": { "$ref": "#/$defs/PresentationCapabilityResult" },
7440
7470
  "regionCapabilityResult": { "$ref": "#/$defs/RegionCapabilityResult" },
7441
7471
  "fileDownloadResult": { "$ref": "#/$defs/FileDownloadResult" },
7472
+ "fileReadResult": { "$ref": "#/$defs/FileReadResult" },
7473
+ "fileWriteResult": { "$ref": "#/$defs/FileWriteResult" },
7442
7474
  "taskObservationSnapshot": { "$ref": "#/$defs/TaskObservationSnapshot" },
7443
7475
  "taskObservationEvent": { "$ref": "#/$defs/TaskObservationEvent" },
7444
7476
  "taskObservationBatch": { "$ref": "#/$defs/TaskObservationBatch" },
7477
+ "taskListField": { "$ref": "#/$defs/TaskListField" },
7478
+ "taskListItem": { "$ref": "#/$defs/TaskListItem" },
7479
+ "taskListPage": { "$ref": "#/$defs/TaskListPage" },
7445
7480
  "toolResultEnvelope": { "$ref": "#/$defs/ToolResultEnvelope" },
7446
7481
  "uiStateResult": { "$ref": "#/$defs/UiStateResult" },
7447
7482
  "uiStateWriteResult": { "$ref": "#/$defs/UiStateWriteResult" },
@@ -7671,7 +7706,7 @@ const widgetRuntimeContractsSchema = {
7671
7706
  }
7672
7707
  },
7673
7708
  "FileReference": {
7674
- "description": "只读文件引用;仅含不透明 id 与展示元数据,不含路径或 URL。",
7709
+ "description": "Host 签发的文件引用;仅含不透明 id、可选内容版本和展示元数据。引用本身不授予写权限,不含路径或 URL。",
7675
7710
  "title": "FileReference",
7676
7711
  "type": "object",
7677
7712
  "additionalProperties": false,
@@ -7703,7 +7738,121 @@ const widgetRuntimeContractsSchema = {
7703
7738
  "size": {
7704
7739
  "type": "integer",
7705
7740
  "minimum": 0
7706
- }
7741
+ },
7742
+ "revision": { "$ref": "#/$defs/FileRevision" }
7743
+ }
7744
+ },
7745
+ "FileRevision": {
7746
+ "description": "由存储执行端签发的文件内容版本,用于条件读取和保存;不是 RenderRequest.revision。",
7747
+ "title": "FileRevision",
7748
+ "type": "string",
7749
+ "minLength": 1,
7750
+ "maxLength": 256
7751
+ },
7752
+ "FileMutationId": {
7753
+ "description": "一次逻辑保存的稳定标识;重试或确认结果时必须复用,相同标识不得提交不同内容。",
7754
+ "title": "FileMutationId",
7755
+ "type": "string",
7756
+ "minLength": 1,
7757
+ "maxLength": 128,
7758
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]*$"
7759
+ },
7760
+ "FileReadInput": {
7761
+ "description": "分块读取已授权文件;后续分块应携带首块返回的 expectedRevision,防止拼接不同版本。",
7762
+ "title": "FileReadInput",
7763
+ "type": "object",
7764
+ "additionalProperties": false,
7765
+ "required": [
7766
+ "file",
7767
+ "offset",
7768
+ "length",
7769
+ "encoding"
7770
+ ],
7771
+ "properties": {
7772
+ "file": { "$ref": "#/$defs/FileReference" },
7773
+ "offset": {
7774
+ "type": "integer",
7775
+ "minimum": 0,
7776
+ "maximum": 9007199254740991
7777
+ },
7778
+ "length": {
7779
+ "type": "integer",
7780
+ "minimum": 1,
7781
+ "maximum": 9007199254740991
7782
+ },
7783
+ "encoding": { "enum": ["utf8", "base64"] },
7784
+ "expectedRevision": { "$ref": "#/$defs/FileRevision" }
7785
+ }
7786
+ },
7787
+ "FileReadResult": {
7788
+ "description": "文件读取结果;普通读取只要求内容与编码,原有游标和截断信息按 Host 能力返回。revision 可选且必须与本次读取的内容属于同一快照;请求 expectedRevision 时必须返回匹配版本。缺少 revision 的结果仅用于只读展示,不能据此条件保存。提供字节游标时 nextOffset 等于请求 offset + bytesRead,不按字符串长度计数。",
7789
+ "title": "FileReadResult",
7790
+ "type": "object",
7791
+ "additionalProperties": false,
7792
+ "required": ["content", "encoding"],
7793
+ "properties": {
7794
+ "content": { "type": "string" },
7795
+ "encoding": { "enum": ["utf8", "base64"] },
7796
+ "offset": {
7797
+ "type": "integer",
7798
+ "minimum": 0,
7799
+ "maximum": 9007199254740991
7800
+ },
7801
+ "bytesRead": {
7802
+ "type": "integer",
7803
+ "minimum": 0,
7804
+ "maximum": 9007199254740991
7805
+ },
7806
+ "nextOffset": {
7807
+ "type": "integer",
7808
+ "minimum": 0,
7809
+ "maximum": 9007199254740991
7810
+ },
7811
+ "eof": { "type": "boolean" },
7812
+ "truncated": { "type": "boolean" },
7813
+ "revision": { "$ref": "#/$defs/FileRevision" }
7814
+ }
7815
+ },
7816
+ "FileWriteInput": {
7817
+ "description": "按 Host 授权范围对已有文件作整文件条件替换;Host 校验引用、作用域、权限和实际字节预算。超时不表示未写入,使用同一 mutationId 核对结果。",
7818
+ "title": "FileWriteInput",
7819
+ "type": "object",
7820
+ "additionalProperties": false,
7821
+ "required": [
7822
+ "file",
7823
+ "content",
7824
+ "encoding",
7825
+ "expectedRevision",
7826
+ "mutationId"
7827
+ ],
7828
+ "properties": {
7829
+ "file": { "$ref": "#/$defs/FileReference" },
7830
+ "content": { "type": "string" },
7831
+ "encoding": { "enum": ["utf8", "base64"] },
7832
+ "expectedRevision": { "$ref": "#/$defs/FileRevision" },
7833
+ "mutationId": { "$ref": "#/$defs/FileMutationId" }
7834
+ }
7835
+ },
7836
+ "FileWriteResult": {
7837
+ "description": "执行端已确认持久化的保存回执;file.id 保持原文件引用身份,revision 是新内容版本。",
7838
+ "title": "FileWriteResult",
7839
+ "type": "object",
7840
+ "additionalProperties": false,
7841
+ "required": [
7842
+ "file",
7843
+ "revision",
7844
+ "bytesWritten",
7845
+ "mutationId"
7846
+ ],
7847
+ "properties": {
7848
+ "file": { "$ref": "#/$defs/FileReference" },
7849
+ "revision": { "$ref": "#/$defs/FileRevision" },
7850
+ "bytesWritten": {
7851
+ "type": "integer",
7852
+ "minimum": 0,
7853
+ "maximum": 9007199254740991
7854
+ },
7855
+ "mutationId": { "$ref": "#/$defs/FileMutationId" }
7707
7856
  }
7708
7857
  },
7709
7858
  "FileDownloadResult": {
@@ -7718,6 +7867,18 @@ const widgetRuntimeContractsSchema = {
7718
7867
  "cancelled"
7719
7868
  ] } }
7720
7869
  },
7870
+ "TaskStatus": {
7871
+ "description": "Host 投影给 Widget 的统一任务状态。",
7872
+ "title": "TaskStatus",
7873
+ "enum": [
7874
+ "queued",
7875
+ "running",
7876
+ "succeeded",
7877
+ "failed",
7878
+ "cancelled",
7879
+ "expired"
7880
+ ]
7881
+ },
7721
7882
  "TaskObservationSnapshot": {
7722
7883
  "description": "任务观察的当前快照:taskId、status 与 updatedAt;progress、message 可选。",
7723
7884
  "title": "TaskObservationSnapshot",
@@ -7734,14 +7895,7 @@ const widgetRuntimeContractsSchema = {
7734
7895
  "minLength": 1,
7735
7896
  "maxLength": 256
7736
7897
  },
7737
- "status": { "enum": [
7738
- "queued",
7739
- "running",
7740
- "succeeded",
7741
- "failed",
7742
- "cancelled",
7743
- "expired"
7744
- ] },
7898
+ "status": { "$ref": "#/$defs/TaskStatus" },
7745
7899
  "progress": {
7746
7900
  "type": "number",
7747
7901
  "minimum": 0,
@@ -7825,6 +7979,110 @@ const widgetRuntimeContractsSchema = {
7825
7979
  }
7826
7980
  }
7827
7981
  },
7982
+ "TaskListField": {
7983
+ "description": "任务列表中的受限展示字段;key 是稳定机器键,value 是 Host 裁剪后的展示文本。",
7984
+ "title": "TaskListField",
7985
+ "type": "object",
7986
+ "additionalProperties": false,
7987
+ "required": ["key", "value"],
7988
+ "properties": {
7989
+ "key": {
7990
+ "description": "展示字段的稳定机器键,例如 machine 或 command。",
7991
+ "type": "string",
7992
+ "minLength": 1,
7993
+ "maxLength": 64,
7994
+ "pattern": "^[A-Za-z][A-Za-z0-9_.:-]*$"
7995
+ },
7996
+ "value": {
7997
+ "description": "Host 完成授权与裁剪后的展示文本。",
7998
+ "type": "string",
7999
+ "maxLength": 16384
8000
+ }
8001
+ }
8002
+ },
8003
+ "TaskListItem": {
8004
+ "description": "Host 已授权发现的任务摘要;taskId 是可继续传给 tasks.watch 的不透明引用。",
8005
+ "title": "TaskListItem",
8006
+ "type": "object",
8007
+ "additionalProperties": false,
8008
+ "required": [
8009
+ "taskId",
8010
+ "kind",
8011
+ "name",
8012
+ "status",
8013
+ "updatedAt",
8014
+ "fields"
8015
+ ],
8016
+ "properties": {
8017
+ "taskId": {
8018
+ "description": "可继续传给 tasks.watch 的 Host 不透明任务引用。",
8019
+ "type": "string",
8020
+ "minLength": 1,
8021
+ "maxLength": 256
8022
+ },
8023
+ "kind": {
8024
+ "description": "任务来源类型的稳定机器键。",
8025
+ "type": "string",
8026
+ "minLength": 1,
8027
+ "maxLength": 64,
8028
+ "pattern": "^[A-Za-z][A-Za-z0-9_.:-]*$"
8029
+ },
8030
+ "name": {
8031
+ "description": "任务的安全展示名称。",
8032
+ "type": "string",
8033
+ "minLength": 1,
8034
+ "maxLength": 256
8035
+ },
8036
+ "status": { "$ref": "#/$defs/TaskStatus" },
8037
+ "progress": {
8038
+ "description": "可选执行进度,取值范围为 0 到 1。",
8039
+ "type": "number",
8040
+ "minimum": 0,
8041
+ "maximum": 1
8042
+ },
8043
+ "message": {
8044
+ "description": "Host 裁剪后的可选状态说明。",
8045
+ "type": "string",
8046
+ "maxLength": 1024
8047
+ },
8048
+ "updatedAt": {
8049
+ "description": "任务摘要最后更新时间,使用 UTC ISO 8601 字符串。",
8050
+ "type": "string",
8051
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}T[^ ]+Z$"
8052
+ },
8053
+ "fields": {
8054
+ "description": "任务面板可展示的受限附加字段。",
8055
+ "type": "array",
8056
+ "maxItems": 32,
8057
+ "items": { "$ref": "#/$defs/TaskListField" }
8058
+ }
8059
+ }
8060
+ },
8061
+ "TaskListPage": {
8062
+ "description": "Host 已授权任务摘要的一页结果;nextCursor 缺失表示当前页已到末尾。",
8063
+ "title": "TaskListPage",
8064
+ "type": "object",
8065
+ "additionalProperties": false,
8066
+ "required": ["schemaVersion", "tasks"],
8067
+ "properties": {
8068
+ "schemaVersion": {
8069
+ "description": "任务列表页结构版本,当前固定为 1。",
8070
+ "const": 1
8071
+ },
8072
+ "tasks": {
8073
+ "description": "当前页中 Host 已授权的任务摘要。",
8074
+ "type": "array",
8075
+ "maxItems": 100,
8076
+ "items": { "$ref": "#/$defs/TaskListItem" }
8077
+ },
8078
+ "nextCursor": {
8079
+ "description": "下一页的不透明游标;缺失表示当前列表已到末尾。",
8080
+ "type": "string",
8081
+ "minLength": 1,
8082
+ "maxLength": 512
8083
+ }
8084
+ }
8085
+ },
7828
8086
  "ToolResultMetric": {
7829
8087
  "description": "工具结果中的单个指标。",
7830
8088
  "title": "ToolResultMetric",
@@ -8119,6 +8377,40 @@ const widgetRuntimeContractsSchema = {
8119
8377
  }
8120
8378
  }
8121
8379
  },
8380
+ {
8381
+ "type": "object",
8382
+ "additionalProperties": false,
8383
+ "required": ["name", "limits"],
8384
+ "properties": {
8385
+ "name": { "const": "files.write" },
8386
+ "limits": {
8387
+ "type": "object",
8388
+ "additionalProperties": false,
8389
+ "required": [
8390
+ "bytesPerRequest",
8391
+ "bytesPerRun",
8392
+ "callsPerMinute"
8393
+ ],
8394
+ "properties": {
8395
+ "bytesPerRequest": {
8396
+ "type": "integer",
8397
+ "minimum": 1,
8398
+ "maximum": 9007199254740991
8399
+ },
8400
+ "bytesPerRun": {
8401
+ "type": "integer",
8402
+ "minimum": 1,
8403
+ "maximum": 9007199254740991
8404
+ },
8405
+ "callsPerMinute": {
8406
+ "type": "integer",
8407
+ "minimum": 1,
8408
+ "maximum": 9007199254740991
8409
+ }
8410
+ }
8411
+ }
8412
+ }
8413
+ },
8122
8414
  {
8123
8415
  "type": "object",
8124
8416
  "additionalProperties": false,
@@ -8169,6 +8461,40 @@ const widgetRuntimeContractsSchema = {
8169
8461
  }
8170
8462
  }
8171
8463
  },
8464
+ {
8465
+ "type": "object",
8466
+ "additionalProperties": false,
8467
+ "required": ["name", "limits"],
8468
+ "properties": {
8469
+ "name": { "const": "tasks.list" },
8470
+ "limits": {
8471
+ "type": "object",
8472
+ "additionalProperties": false,
8473
+ "required": [
8474
+ "callsPerMinute",
8475
+ "pageSize",
8476
+ "minPollMs"
8477
+ ],
8478
+ "properties": {
8479
+ "callsPerMinute": {
8480
+ "type": "integer",
8481
+ "minimum": 1,
8482
+ "maximum": 9007199254740991
8483
+ },
8484
+ "pageSize": {
8485
+ "type": "integer",
8486
+ "minimum": 1,
8487
+ "maximum": 100
8488
+ },
8489
+ "minPollMs": {
8490
+ "type": "integer",
8491
+ "minimum": 1e3,
8492
+ "maximum": 6e4
8493
+ }
8494
+ }
8495
+ }
8496
+ }
8497
+ },
8172
8498
  {
8173
8499
  "type": "object",
8174
8500
  "additionalProperties": false,
@@ -8620,26 +8946,36 @@ const widgetRuntimeContractsSchema = {
8620
8946
  "required": ["operation", "input"],
8621
8947
  "properties": {
8622
8948
  "operation": { "const": "files.read" },
8949
+ "input": { "$ref": "#/$defs/FileReadInput" }
8950
+ }
8951
+ },
8952
+ {
8953
+ "type": "object",
8954
+ "additionalProperties": false,
8955
+ "required": ["operation", "input"],
8956
+ "properties": {
8957
+ "operation": { "const": "files.write" },
8958
+ "input": { "$ref": "#/$defs/FileWriteInput" }
8959
+ }
8960
+ },
8961
+ {
8962
+ "type": "object",
8963
+ "additionalProperties": false,
8964
+ "required": ["operation", "input"],
8965
+ "properties": {
8966
+ "operation": { "const": "files.download" },
8623
8967
  "input": {
8624
8968
  "type": "object",
8625
8969
  "additionalProperties": false,
8626
- "required": [
8627
- "file",
8628
- "offset",
8629
- "length",
8630
- "encoding"
8631
- ],
8970
+ "required": ["file", "suggestedName"],
8632
8971
  "properties": {
8633
8972
  "file": { "$ref": "#/$defs/FileReference" },
8634
- "offset": {
8635
- "type": "integer",
8636
- "minimum": 0
8637
- },
8638
- "length": {
8639
- "type": "integer",
8640
- "minimum": 1
8641
- },
8642
- "encoding": { "enum": ["utf8", "base64"] }
8973
+ "suggestedName": {
8974
+ "type": "string",
8975
+ "minLength": 1,
8976
+ "maxLength": 255,
8977
+ "pattern": "^[^\\\\/\\u0000-\\u001f]+$"
8978
+ }
8643
8979
  }
8644
8980
  }
8645
8981
  }
@@ -8649,18 +8985,20 @@ const widgetRuntimeContractsSchema = {
8649
8985
  "additionalProperties": false,
8650
8986
  "required": ["operation", "input"],
8651
8987
  "properties": {
8652
- "operation": { "const": "files.download" },
8988
+ "operation": { "const": "tasks.list" },
8653
8989
  "input": {
8654
8990
  "type": "object",
8655
8991
  "additionalProperties": false,
8656
- "required": ["file", "suggestedName"],
8657
8992
  "properties": {
8658
- "file": { "$ref": "#/$defs/FileReference" },
8659
- "suggestedName": {
8993
+ "cursor": {
8660
8994
  "type": "string",
8661
8995
  "minLength": 1,
8662
- "maxLength": 255,
8663
- "pattern": "^[^\\\\/\\u0000-\\u001f]+$"
8996
+ "maxLength": 512
8997
+ },
8998
+ "limit": {
8999
+ "type": "integer",
9000
+ "minimum": 1,
9001
+ "maximum": 100
8664
9002
  }
8665
9003
  }
8666
9004
  }
@@ -9201,7 +9539,10 @@ const widgetRuntimeContractsSchema = {
9201
9539
  "network-credential-unavailable",
9202
9540
  "network-scope-invalid",
9203
9541
  "network-dns-denied",
9204
- "network-error"
9542
+ "network-error",
9543
+ "revision-conflict",
9544
+ "mutation-conflict",
9545
+ "write-outcome-unknown"
9205
9546
  ] },
9206
9547
  "message": {
9207
9548
  "type": "string",
@@ -9360,6 +9701,18 @@ const widgetRuntimeContractsSchema = {
9360
9701
  "listPageSize": {
9361
9702
  "type": "integer",
9362
9703
  "minimum": 1
9704
+ },
9705
+ "writeBytesPerRequest": {
9706
+ "description": "写入单次原始字节上限;未签发时不得授权 files.write。",
9707
+ "type": "integer",
9708
+ "minimum": 1,
9709
+ "maximum": 9007199254740991
9710
+ },
9711
+ "writeBytesPerRun": {
9712
+ "description": "当前运行的累计写入原始字节上限;未签发时不得授权 files.write。",
9713
+ "type": "integer",
9714
+ "minimum": 1,
9715
+ "maximum": 9007199254740991
9363
9716
  }
9364
9717
  }
9365
9718
  },
@@ -10735,14 +11088,51 @@ async function checkWidgetProject(projectRoot, options = {}) {
10735
11088
  projectRoot: root
10736
11089
  };
10737
11090
  }
10738
- const { validateWidgetManifest, validateRenderRequest, validateCapabilityResult } = await loadContractValidators();
11091
+ const toolchain = options.toolchain ?? {};
11092
+ const { validateWidgetManifest, validateRenderRequest, validateCapabilityResult } = options.validators ?? toolchain.validators ?? await loadContractValidators();
10739
11093
  const sourceManifest = manifest;
10740
- manifest = compileAuthorManifest(manifest, { hostEntry: options.hostEntry });
11094
+ const sdkVersion = options.sdkVersion ?? toolchain.sdkVersion;
11095
+ try {
11096
+ manifest = compileAuthorManifest(manifest, {
11097
+ hostEntry: options.hostEntry,
11098
+ samples: options.samples,
11099
+ sdkVersion,
11100
+ compiler: options.compiler ?? toolchain.compiler
11101
+ });
11102
+ } catch (error) {
11103
+ checks.push({
11104
+ name: "manifest 编译",
11105
+ ok: false
11106
+ });
11107
+ errors.push(`manifest compilation failed: ${error instanceof Error ? error.message : String(error)}`);
11108
+ return {
11109
+ ok: false,
11110
+ label,
11111
+ checks,
11112
+ errors,
11113
+ manifest: sourceManifest,
11114
+ sourceManifest,
11115
+ manifestKind: isAuthorManifest(sourceManifest) ? "author" : "host",
11116
+ sampleRequest: null,
11117
+ sampleRequests: [],
11118
+ fixtures: null,
11119
+ fixturesPath: null,
11120
+ entryPath: null,
11121
+ projectRoot: root
11122
+ };
11123
+ }
10741
11124
  const manifestResult = validateWidgetManifest(manifest);
10742
11125
  checks.push({
10743
11126
  name: "manifest 契约校验 (AJV)",
10744
11127
  ok: manifestResult.ok
10745
11128
  });
11129
+ if (manifestResult.ok && sdkVersion !== void 0 && manifest.widgetConfig.widgetSdkVersion !== sdkVersion) {
11130
+ checks.push({
11131
+ name: "manifest 工具链 SDK 版本",
11132
+ ok: false
11133
+ });
11134
+ errors.push(`manifest SDK version ${manifest.widgetConfig.widgetSdkVersion} does not match toolchain SDK version ${sdkVersion}`);
11135
+ }
10746
11136
  if (!manifestResult.ok) {
10747
11137
  errors.push(...formatContractErrors(manifestResult));
10748
11138
  return {
@@ -10883,8 +11273,29 @@ function resolveWidgetProjects(input, root = repoRoot) {
10883
11273
  throw new Error("请传入 Widget 工程目录、manifest.json 或 all");
10884
11274
  }
10885
11275
  //#endregion
10886
- //#region ../widget-project/src/export.mjs
11276
+ //#region ../widget-project/src/zip.mjs
11277
+ /** 固定归档顺序与时间戳,统一路径安全与重复条目处理。 */
11278
+ function assertArchivePath(path) {
11279
+ if (typeof path !== "string" || path.length === 0 || path.startsWith("/") || path.includes("..") || path.includes("\\") || path.includes("://") || /^[A-Za-z]:/u.test(path) || [...path].some((character) => {
11280
+ const code = character.charCodeAt(0);
11281
+ return code < 32 || code === 127;
11282
+ })) throw new Error(`Invalid archive path: ${path}`);
11283
+ }
11284
+ function normalizeArchivePath(path) {
11285
+ assertArchivePath(path);
11286
+ const normalized = path.split("/").filter((part) => part !== "" && part !== ".").join("/");
11287
+ assertArchivePath(normalized);
11288
+ return normalized;
11289
+ }
10887
11290
  function storedZip(files) {
11291
+ const uniqueFiles = /* @__PURE__ */ new Map();
11292
+ for (const [declaredPath, value] of files) {
11293
+ const path = normalizeArchivePath(declaredPath);
11294
+ const bytes = Buffer.from(value);
11295
+ const previous = uniqueFiles.get(path);
11296
+ if (previous && !previous.equals(bytes)) throw new Error(`Conflicting duplicate archive path: ${path}`);
11297
+ uniqueFiles.set(path, bytes);
11298
+ }
10888
11299
  const table = Array.from({ length: 256 }, (_, index) => {
10889
11300
  let value = index;
10890
11301
  for (let bit = 0; bit < 8; bit++) value = value & 1 ? 3988292384 ^ value >>> 1 : value >>> 1;
@@ -10893,10 +11304,8 @@ function storedZip(files) {
10893
11304
  const locals = [];
10894
11305
  const central = [];
10895
11306
  let offset = 0;
10896
- for (const [path, value] of [...files].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
10897
- if (!/^[a-zA-Z0-9._/-]+$/.test(path) || path.startsWith("/") || path.split("/").includes("..")) throw new Error("Invalid archive path");
11307
+ for (const [path, bytes] of [...uniqueFiles].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
10898
11308
  const name = Buffer.from(path);
10899
- const bytes = Buffer.from(value);
10900
11309
  let crc = 4294967295;
10901
11310
  for (const byte of bytes) crc = table[(crc ^ byte) & 255] ^ crc >>> 8;
10902
11311
  crc = (crc ^ 4294967295) >>> 0;
@@ -10927,8 +11336,8 @@ function storedZip(files) {
10927
11336
  const centralBytes = Buffer.concat(central);
10928
11337
  const end = Buffer.alloc(22);
10929
11338
  end.writeUInt32LE(101010256);
10930
- end.writeUInt16LE(files.length, 8);
10931
- end.writeUInt16LE(files.length, 10);
11339
+ end.writeUInt16LE(uniqueFiles.size, 8);
11340
+ end.writeUInt16LE(uniqueFiles.size, 10);
10932
11341
  end.writeUInt32LE(centralBytes.length, 12);
10933
11342
  end.writeUInt32LE(offset, 16);
10934
11343
  return Buffer.concat([
@@ -10937,24 +11346,81 @@ function storedZip(files) {
10937
11346
  end
10938
11347
  ]);
10939
11348
  }
10940
- function safeBundlePath(value) {
10941
- return value.replace(/^\/+/u, "");
11349
+ //#endregion
11350
+ //#region ../widget-project/src/export.mjs
11351
+ const EXCLUDED_BUILD_NAMES = /* @__PURE__ */ new Set([
11352
+ "node_modules",
11353
+ ".git",
11354
+ ".widget-build",
11355
+ ".widget-dev",
11356
+ "widget-toolchain.json",
11357
+ ".widget-build-receipt.json"
11358
+ ]);
11359
+ function bundleFileMetadata(root, path) {
11360
+ const normalized = normalizeArchivePath(path);
11361
+ let file = root;
11362
+ let metadata;
11363
+ for (const part of normalized.split("/")) {
11364
+ file = join(file, part);
11365
+ metadata = lstatSync(file);
11366
+ if (metadata.isSymbolicLink()) throw new Error(`ZIP 不允许 symbolic link(符号链接):${path}`);
11367
+ }
11368
+ return {
11369
+ file,
11370
+ metadata
11371
+ };
11372
+ }
11373
+ function readBundleFile(root, path) {
11374
+ const { file, metadata } = bundleFileMetadata(root, path);
11375
+ if (!metadata.isFile()) throw new Error(`ZIP 资源不是普通文件:${path}`);
11376
+ return readFileSync(file);
11377
+ }
11378
+ function collectBuildAssets(root, entry) {
11379
+ entry = normalizeArchivePath(entry);
11380
+ if (!entry.includes("/")) return [[entry, readBundleFile(root, entry)]];
11381
+ const files = [];
11382
+ const rootName = entry.split("/")[0];
11383
+ if (EXCLUDED_BUILD_NAMES.has(rootName)) throw new Error(`构建资源目录不能是工程内部目录:${rootName}`);
11384
+ const visit = (path) => {
11385
+ const { file, metadata } = bundleFileMetadata(root, path);
11386
+ if (metadata.isDirectory()) for (const name of readdirSync(file)) {
11387
+ if (EXCLUDED_BUILD_NAMES.has(name) || /\.zip$/iu.test(name)) continue;
11388
+ visit(`${path}/${name}`);
11389
+ }
11390
+ else files.push([path, readBundleFile(root, path)]);
11391
+ };
11392
+ visit(rootName);
11393
+ return files;
10942
11394
  }
10943
11395
  function buildWidgetPackage(project, options = {}) {
10944
- const manifest = project.manifest ?? compileAuthorManifest(JSON.parse(readFileSync(join(project.projectRoot, "manifest.json"), "utf8")), { hostEntry: options.hostEntry });
11396
+ return storedZip(collectWidgetPackageFiles(project, options));
11397
+ }
11398
+ /** 返回将写入 ZIP 的 `[路径, 字节]` 列表;同一路径可能重复出现,由 `storedZip` 去重。 */
11399
+ function collectWidgetPackageFiles(project, options = {}) {
11400
+ const root = resolve(project.projectRoot);
11401
+ const sourceManifest = project.manifest ?? compileAuthorManifest(JSON.parse(readBundleFile(root, "manifest.json").toString("utf8")), { hostEntry: options.hostEntry });
11402
+ const manifest = {
11403
+ ...sourceManifest,
11404
+ widgetConfig: {
11405
+ ...sourceManifest.widgetConfig,
11406
+ entry: normalizeArchivePath(sourceManifest.widgetConfig.entry),
11407
+ samples: sourceManifest.widgetConfig.samples?.map((sample) => ({
11408
+ ...sample,
11409
+ request: normalizeArchivePath(sample.request)
11410
+ }))
11411
+ }
11412
+ };
10945
11413
  const files = [["manifest.json", Buffer.from(`${JSON.stringify(manifest)}\n`)]];
10946
- const entryPath = join(project.projectRoot, manifest.widgetConfig.entry);
11414
+ const entryPath = join(root, manifest.widgetConfig.entry);
10947
11415
  if (!existsSync(entryPath)) throw new Error("Widget entry is missing; build the project first");
10948
- files.push([safeBundlePath(manifest.widgetConfig.entry), readFileSync(entryPath)]);
10949
- for (const sample of manifest.widgetConfig.samples ?? []) {
10950
- const samplePath = join(project.projectRoot, sample.request);
10951
- if (existsSync(samplePath)) files.push([safeBundlePath(sample.request), readFileSync(samplePath)]);
10952
- }
11416
+ files.push(...collectBuildAssets(root, manifest.widgetConfig.entry));
11417
+ files.push([manifest.widgetConfig.entry, readBundleFile(root, manifest.widgetConfig.entry)]);
11418
+ for (const sample of manifest.widgetConfig.samples ?? []) files.push([sample.request, readBundleFile(root, sample.request)]);
10953
11419
  if (options.includePreviewFixtures) {
10954
- const fixturesPath = join(project.projectRoot, "preview", "fixtures.json");
10955
- if (existsSync(fixturesPath)) files.push(["preview/fixtures.json", readFileSync(fixturesPath)]);
11420
+ const fixturesPath = join(root, "preview", "fixtures.json");
11421
+ if (existsSync(fixturesPath)) files.push(["preview/fixtures.json", readBundleFile(root, "preview/fixtures.json")]);
10956
11422
  }
10957
- return storedZip(files);
11423
+ return files;
10958
11424
  }
10959
11425
  //#endregion
10960
11426
  //#region src/migration/recipes.mjs
@@ -11392,4 +11858,4 @@ async function scaffoldDshWidget(input, output, options) {
11392
11858
  };
11393
11859
  }
11394
11860
  //#endregion
11395
- export { resolveWidgetProjects as a, checkWidgetProject as i, scaffoldDshWidget as n, compileAuthorManifest as o, buildWidgetPackage as r, auditDshPlugin as t };
11861
+ export { normalizeArchivePath as a, resolveWidgetProjects as c, collectWidgetPackageFiles as i, compileAuthorManifest as l, scaffoldDshWidget as n, storedZip as o, buildWidgetPackage as r, checkWidgetProject as s, auditDshPlugin as t };