@dp-bohrium/widget-sdk 0.7.0 → 0.9.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,8 +1,8 @@
1
- import { r as MessagePortTransport, t as ProtocolEndpoint } from "./protocol-endpoint-CDz-YDGT.js";
1
+ import { r as MessagePortTransport, t as ProtocolEndpoint } from "./protocol-endpoint-DbT0OJNu.js";
2
2
  //#endregion
3
3
  //#region src/runtime/version.ts
4
4
  /** 从当前安装的 SDK package.json 读取版本,插件作者不需要重复填写。 */
5
- const WIDGET_SDK_VERSION = "0.7.0";
5
+ const WIDGET_SDK_VERSION = "0.9.0";
6
6
  //#endregion
7
7
  //#region src/runtime/network.ts
8
8
  const failureReasons = /* @__PURE__ */ new Set([
@@ -308,6 +308,7 @@ var WidgetClient = class {
308
308
  endpoint;
309
309
  widgetSdkVersion;
310
310
  channelId;
311
+ developmentDiagnostics;
311
312
  handshakeTimeoutMs;
312
313
  capabilityTimeoutMs;
313
314
  createMessageId;
@@ -327,6 +328,7 @@ var WidgetClient = class {
327
328
  constructor(options, events = {}) {
328
329
  this.widgetSdkVersion = options.widgetSdkVersion;
329
330
  this.channelId = options.channelId;
331
+ this.developmentDiagnostics = options.developmentDiagnostics === true;
330
332
  this.hostApiVersion = options.hostApiVersion ?? 1;
331
333
  this.handshakeTimeoutMs = options.handshakeTimeoutMs ?? 5e3;
332
334
  this.capabilityTimeoutMs = options.capabilityTimeoutMs ?? 5e3;
@@ -538,6 +540,18 @@ var WidgetClient = class {
538
540
  this.failHandshake(toError$1(reason, "widget initialization failed"), "initialization-failed", "WIDGET_CRASHED");
539
541
  }
540
542
  }
543
+ reportDevelopmentDiagnostic(diagnostic) {
544
+ if (this.developmentDiagnostics && this.state === "ready") this.endpoint.send({
545
+ type: "diagnostic",
546
+ payload: diagnostic
547
+ });
548
+ }
549
+ reportRenderResult(result) {
550
+ if (this.developmentDiagnostics && this.state === "ready") this.endpoint.send({
551
+ type: "render-result",
552
+ payload: result
553
+ });
554
+ }
541
555
  send(message) {
542
556
  this.endpoint.send(message);
543
557
  }
@@ -1071,6 +1085,11 @@ var WidgetRuntimeClient = class {
1071
1085
  this.runtimeWindow.addEventListener("message", this.messageListener);
1072
1086
  this.connectionTimer = setTimeout(() => this.failConnection(/* @__PURE__ */ new Error("widget connection timed out")), options.connectionTimeoutMs ?? options.handshakeTimeoutMs ?? 5e3);
1073
1087
  try {
1088
+ this.runtimeWindow.parent.postMessage({
1089
+ type: "widget-bootstrap-offer",
1090
+ protocolVersion: 1,
1091
+ extensions: ["development-diagnostics-v1"]
1092
+ }, "*");
1074
1093
  this.runtimeWindow.parent.postMessage({
1075
1094
  type: WIDGET_BOOTSTRAP_READY,
1076
1095
  protocolVersion: 1
@@ -1232,16 +1251,25 @@ var WidgetRuntimeClient = class {
1232
1251
  if (this.disposed || this.client) return;
1233
1252
  if (event.source !== this.runtimeWindow.parent) return;
1234
1253
  if (this.options.expectedHostOrigin !== void 0 && event.origin !== this.options.expectedHostOrigin) return;
1235
- if (!isWidgetHostConnectMessage(event.data)) return;
1254
+ const extended = isRecord(event.data) && Array.isArray(event.data["extensions"]) && event.data["extensions"].length === 1 && event.data["extensions"][0] === "development-diagnostics-v1" && hasExactKeys(event.data, [
1255
+ "type",
1256
+ "protocolVersion",
1257
+ "channelId",
1258
+ "initPayload",
1259
+ "extensions"
1260
+ ]);
1261
+ const candidate = extended ? Object.fromEntries(Object.entries(event.data).filter(([key]) => key !== "extensions")) : event.data;
1262
+ if (!isWidgetHostConnectMessage(candidate)) return;
1236
1263
  const port = event.ports?.[0];
1237
1264
  if (!port) return;
1238
1265
  this.removeConnectionListener();
1239
1266
  try {
1240
1267
  const clientOptions = {
1241
1268
  transport: new MessagePortTransport(port),
1242
- channelId: event.data.channelId,
1269
+ channelId: candidate.channelId,
1243
1270
  widgetSdkVersion: this.options.widgetSdkVersion ?? WIDGET_SDK_VERSION,
1244
- initialPayload: event.data.initPayload,
1271
+ initialPayload: candidate.initPayload,
1272
+ developmentDiagnostics: extended,
1245
1273
  ...this.options.hostApiVersion === void 0 ? {} : { hostApiVersion: this.options.hostApiVersion },
1246
1274
  ...this.options.capabilityTimeoutMs === void 0 ? {} : { capabilityTimeoutMs: this.options.capabilityTimeoutMs },
1247
1275
  ...this.options.handshakeTimeoutMs === void 0 ? {} : { handshakeTimeoutMs: this.options.handshakeTimeoutMs },
@@ -1259,6 +1287,12 @@ var WidgetRuntimeClient = class {
1259
1287
  this.failConnection(toError(reason));
1260
1288
  }
1261
1289
  }
1290
+ reportDevelopmentDiagnostic(diagnostic) {
1291
+ this.client?.reportDevelopmentDiagnostic(diagnostic);
1292
+ }
1293
+ reportRenderResult(result) {
1294
+ this.client?.reportRenderResult(result);
1295
+ }
1262
1296
  requireClient() {
1263
1297
  if (this.disposed) throw new Error("client is disposed");
1264
1298
  if (!this.client) throw new Error("widget host connection is not ready");
@@ -0,0 +1,472 @@
1
+ //#region src/protocol/plugin-manifest.ts
2
+ /**
3
+ * Widget Host 公开的固定激活事件。数组同时作为清单编辑器文档和 Host
4
+ * 运行时校验的单一事实源;带工具名的事件使用下方的动态形式。
5
+ */
6
+ const WIDGET_ACTIVATION_EVENTS = [
7
+ "session.start",
8
+ "turn-start",
9
+ "turn-end",
10
+ "step-start",
11
+ "step-end",
12
+ "tool-call-generating",
13
+ "tool-call-waiting",
14
+ "tool-call-running",
15
+ "tool-call-result",
16
+ "tool-call-error",
17
+ "workspace-file-changed",
18
+ "workspace-tree-refresh",
19
+ "workspace-file-opened",
20
+ "ui-tool-opened",
21
+ "ui-panel-focused",
22
+ "host-surface-changed"
23
+ ];
24
+ const TOOL_EVENT_PATTERN = /^(?:tool-call-(?:generating|waiting|running|result|error)):[A-Za-z0-9_.:-]+$/u;
25
+ /**
26
+ * 判断一个字符串是否为合法激活事件(固定事件或带工具名的动态形式)。
27
+ * @param value 待检查的字符串。
28
+ * @returns 若命中固定目录或工具事件模式则收窄为 `WidgetActivationEvent`。
29
+ */
30
+ function isWidgetActivationEvent(value) {
31
+ return typeof value === "string" && (WIDGET_ACTIVATION_EVENTS.includes(value) || TOOL_EVENT_PATTERN.test(value));
32
+ }
33
+ //#endregion
34
+ //#region src/protocol/widget-events.ts
35
+ /**
36
+ * Host 主动推送给已激活 Widget 的实时事件。`session.start` 只用于激活索引,
37
+ * 不属于这组运行期事件;激活后首个可用快照通过 `context.render` 交付。
38
+ */
39
+ const WIDGET_EVENT_NAMES = [
40
+ "turn-start",
41
+ "turn-end",
42
+ "step-start",
43
+ "step-end",
44
+ "tool-call-generating",
45
+ "tool-call-waiting",
46
+ "tool-call-running",
47
+ "tool-call-result",
48
+ "tool-call-error",
49
+ "workspace-file-changed",
50
+ "workspace-tree-refresh",
51
+ "workspace-file-opened",
52
+ "ui-tool-opened",
53
+ "ui-panel-focused",
54
+ "host-surface-changed"
55
+ ];
56
+ /**
57
+ * 判断一个字符串是否为合法的实时事件名。
58
+ * @param value 待检查的字符串。
59
+ * @returns 若 `value` 命中 `WIDGET_EVENT_NAMES` 则收窄为 `WidgetEventName`。
60
+ */
61
+ function isWidgetEventName(value) {
62
+ return typeof value === "string" && WIDGET_EVENT_NAMES.includes(value);
63
+ }
64
+ //#endregion
65
+ //#region src/protocol/widget-recovery.ts
66
+ function rule(input) {
67
+ const followUpActions = input.disposition === "repair" ? [
68
+ {
69
+ id: "build-project",
70
+ params: { target: "current-project" }
71
+ },
72
+ {
73
+ id: "refresh-preview",
74
+ params: { target: "current-project" }
75
+ },
76
+ {
77
+ id: "verify-scenario",
78
+ params: { target: "next-built-generation" }
79
+ }
80
+ ] : [];
81
+ const value = {
82
+ id: input.id,
83
+ version: 1,
84
+ sources: [input.source],
85
+ codes: input.codes,
86
+ disposition: input.disposition ?? "inspect",
87
+ nextAction: input.nextAction ?? {
88
+ id: "inspect",
89
+ params: { area: input.area }
90
+ },
91
+ verification: input.verification ?? {
92
+ target: "current-attempt",
93
+ milestone: "diagnostics-updated"
94
+ },
95
+ guidance: input.guidance,
96
+ followUpActions
97
+ };
98
+ Object.freeze(value.sources);
99
+ Object.freeze(value.codes);
100
+ Object.freeze(value.nextAction.params);
101
+ Object.freeze(value.nextAction);
102
+ Object.freeze(value.verification);
103
+ for (const action of followUpActions) {
104
+ Object.freeze(action.params);
105
+ Object.freeze(action);
106
+ }
107
+ Object.freeze(followUpActions);
108
+ return Object.freeze(value);
109
+ }
110
+ /** 通用恢复目录;宿主绑定已有工具及权限,Guest 不指定这些规则。 */
111
+ const STANDARD_RULES = Object.freeze([
112
+ rule({
113
+ id: "runtime-render",
114
+ source: "sdk",
115
+ codes: ["RENDER_FAILED", "UPDATE_FAILED"],
116
+ area: "render",
117
+ disposition: "repair",
118
+ nextAction: {
119
+ id: "edit-project",
120
+ params: { evidence: "diagnostics" }
121
+ },
122
+ verification: {
123
+ target: "next-built-generation",
124
+ milestone: "scenario-passed"
125
+ },
126
+ guidance: "读取当前请求的源码诊断,修复处理器后构建并刷新预览,再重跑原失败场景;ready 不代表恢复。"
127
+ }),
128
+ rule({
129
+ id: "bundle-load",
130
+ source: "sdk",
131
+ codes: ["BUNDLE_LOAD_FAILED", "DIGEST_MISMATCH"],
132
+ area: "bundle",
133
+ guidance: "检查入口资源、响应类型、实际摘要、模块依赖及 CSP 证据,再选择修复;不能从超时猜测根因。"
134
+ }),
135
+ rule({
136
+ id: "runtime-lease",
137
+ source: "sdk",
138
+ codes: ["RUNTIME_CONFIG_EXPIRED", "FILE_REFERENCE_INVALID"],
139
+ area: "lease",
140
+ guidance: "核对当前 scope、配置和引用;通过已授权的 acquire/续期取得新凭据,不重用一次性票据。"
141
+ }),
142
+ rule({
143
+ id: "runtime-authorization",
144
+ source: "sdk",
145
+ codes: ["WIDGET_VERSION_REVOKED", "FILE_PERMISSION_DENIED"],
146
+ area: "capability",
147
+ disposition: "external-action",
148
+ nextAction: {
149
+ id: "request-external-action",
150
+ params: { reason: "authorization" }
151
+ },
152
+ guidance: "保留现场并请求目标授权恢复;不要重试被撤销资源或绕过权限。继续不受影响的工作。"
153
+ }),
154
+ rule({
155
+ id: "runtime-profile",
156
+ source: "sdk",
157
+ codes: ["HOST_API_UNSUPPORTED"],
158
+ area: "configuration",
159
+ guidance: "核对实际 Host 兼容声明与工具链;按声明选择迁移或宿主升级,不修改版本字段伪装支持。"
160
+ }),
161
+ rule({
162
+ id: "runtime-handshake",
163
+ source: "sdk",
164
+ codes: ["HANDSHAKE_TIMEOUT"],
165
+ area: "handshake",
166
+ guidance: "先确认资源加载,再检查 profile、frame 来源与连接阶段;补充证据后再建立新 attempt。"
167
+ }),
168
+ rule({
169
+ id: "runtime-protocol",
170
+ source: "sdk",
171
+ codes: [
172
+ "MESSAGE_INVALID",
173
+ "CHANNEL_MISMATCH",
174
+ "MESSAGE_TOO_LARGE",
175
+ "MESSAGE_RATE_LIMITED",
176
+ "TOO_MANY_PENDING_REQUESTS"
177
+ ],
178
+ area: "runtime",
179
+ guidance: "检查协议、消息大小、频率及 pending 诊断;保留既有保护,修复生产方后再连接,不能提高预算掩盖错误。"
180
+ }),
181
+ rule({
182
+ id: "runtime-capability",
183
+ source: "sdk",
184
+ codes: [
185
+ "CAPABILITY_UNDECLARED",
186
+ "CAPABILITY_DENIED",
187
+ "CAPABILITY_UNSUPPORTED",
188
+ "CAPABILITY_TIMEOUT",
189
+ "RESOURCE_LIMIT"
190
+ ],
191
+ area: "capability",
192
+ guidance: "区分未声明、未授权、未接线、预算与超时;检查操作是否可能已执行,再选择声明修复、可用替代或授权流程。"
193
+ }),
194
+ rule({
195
+ id: "runtime-lifetime",
196
+ source: "sdk",
197
+ codes: ["WIDGET_CRASHED", "INSTANCE_DISPOSED"],
198
+ area: "runtime",
199
+ guidance: "查询当前挂载尝试和末次诊断;区分正常释放与崩溃,修复后创建新 attempt,不复活旧实例。"
200
+ }),
201
+ rule({
202
+ id: "capability-authorization",
203
+ source: "host",
204
+ codes: [
205
+ "permission-denied",
206
+ "network-consent-denied",
207
+ "network-credential-unavailable"
208
+ ],
209
+ area: "capability",
210
+ disposition: "external-action",
211
+ nextAction: {
212
+ id: "request-external-action",
213
+ params: { reason: "authorization" }
214
+ },
215
+ guidance: "按具体 operation 核对授权或凭据可用性;使用既有授权入口,不从聊天索取凭据或绕过拒绝。"
216
+ }),
217
+ rule({
218
+ id: "capability-policy",
219
+ source: "host",
220
+ codes: [
221
+ "unsupported",
222
+ "network-policy-denied",
223
+ "network-redirect-denied",
224
+ "network-scope-invalid",
225
+ "network-dns-denied"
226
+ ],
227
+ area: "capability",
228
+ guidance: "核对宿主接线、当前作用域和网络策略;选择允许的操作或替代路径,不反复原样调用。"
229
+ }),
230
+ rule({
231
+ id: "capability-budget",
232
+ source: "host",
233
+ codes: [
234
+ "resource-limit",
235
+ "network-request-too-large",
236
+ "network-response-too-large"
237
+ ],
238
+ area: "capability",
239
+ guidance: "读取已知预算维度,缩小请求或分块;未知维度继续排查,不销毁健康 Bridge 或自动提高额度。"
240
+ }),
241
+ rule({
242
+ id: "capability-rate",
243
+ source: "host",
244
+ codes: ["network-rate-limited"],
245
+ area: "capability",
246
+ disposition: "retry",
247
+ nextAction: {
248
+ id: "wait",
249
+ params: { milestone: "budget-reset" }
250
+ },
251
+ guidance: "仅在确认原操作未执行且允许重试时,按宿主预算等待;预算未知时先 inspect,不能忙轮询。"
252
+ }),
253
+ rule({
254
+ id: "capability-reference",
255
+ source: "host",
256
+ codes: ["invalid-reference"],
257
+ area: "lease",
258
+ guidance: "重新校验当前作用域与文件引用,通过已有授权入口取得有效引用。"
259
+ }),
260
+ rule({
261
+ id: "capability-conflict",
262
+ source: "host",
263
+ codes: [
264
+ "revision-conflict",
265
+ "mutation-conflict",
266
+ "write-outcome-unknown"
267
+ ],
268
+ area: "operation-outcome",
269
+ verification: {
270
+ target: "original-operation",
271
+ milestone: "operation-confirmed"
272
+ },
273
+ guidance: "按原 revision 和 mutation 身份核对资源及操作结果,再决定是否提交新修改;不自动重放写入。"
274
+ }),
275
+ rule({
276
+ id: "capability-transport",
277
+ source: "host",
278
+ codes: [
279
+ "timeout",
280
+ "internal-error",
281
+ "network-transport-unavailable",
282
+ "network-error"
283
+ ],
284
+ area: "operation-outcome",
285
+ guidance: "保留 transport/HTTP/格式分类,先确认原操作是否执行;无响应不等于未产生副作用。"
286
+ }),
287
+ rule({
288
+ id: "toolchain-build",
289
+ source: "toolchain",
290
+ codes: [
291
+ "WIDGET_CLI_BUILD_FAILED",
292
+ "WIDGET_CLI_BUILD_UNRESOLVED_IMPORT",
293
+ "WIDGET_DEV_BUILD_FAILED"
294
+ ],
295
+ area: "toolchain",
296
+ disposition: "repair",
297
+ nextAction: {
298
+ id: "edit-project",
299
+ params: { evidence: "diagnostics" }
300
+ },
301
+ verification: {
302
+ target: "next-built-generation",
303
+ milestone: "scenario-passed"
304
+ },
305
+ guidance: "读取编译器与相对源码诊断,修复后用原执行工具构建,核对回执与新产物,再刷新并复验。"
306
+ }),
307
+ rule({
308
+ id: "toolchain-validation",
309
+ source: "toolchain",
310
+ codes: [
311
+ "WIDGET_CLI_MANIFEST_INVALID",
312
+ "WIDGET_CLI_VALIDATION_FAILED",
313
+ "WIDGET_CLI_PACK_VALIDATION_FAILED",
314
+ "WIDGET_DEV_PROJECT_INVALID"
315
+ ],
316
+ area: "configuration",
317
+ disposition: "repair",
318
+ nextAction: {
319
+ id: "edit-project",
320
+ params: { evidence: "diagnostics" }
321
+ },
322
+ verification: {
323
+ target: "next-built-generation",
324
+ milestone: "scenario-passed"
325
+ },
326
+ guidance: "依据字段与预期值修复工程清单、入口或样例,再检查并构建;不要打包不完整产物。"
327
+ }),
328
+ rule({
329
+ id: "toolchain-profile",
330
+ source: "toolchain",
331
+ codes: [
332
+ "WIDGET_CLI_PROJECT_TOOLCHAIN_REQUIRED",
333
+ "WIDGET_CLI_PROJECT_PROFILE_MISMATCH",
334
+ "WIDGET_CLI_PROJECT_SDK_MISMATCH",
335
+ "WIDGET_CLI_BUILD_RECEIPT_MISMATCH",
336
+ "WIDGET_CLI_HOST_PROFILE_UNSUPPORTED",
337
+ "WIDGET_CLI_HOST_COMPAT_INVALID",
338
+ "WIDGET_CLI_NO_WIDGET_PROJECT"
339
+ ],
340
+ area: "toolchain",
341
+ guidance: "运行 doctor/integration 并核对当前工程与目标宿主;显式采用或迁移工具链,再构建,不伪造 receipt。"
342
+ }),
343
+ rule({
344
+ id: "development-host",
345
+ source: "toolchain",
346
+ codes: [
347
+ "WIDGET_DEV_FAILED",
348
+ "WIDGET_DEV_UNAUTHORIZED",
349
+ "WIDGET_DEV_CLAIM_REJECTED",
350
+ "WIDGET_DEV_PROFILE_NOT_FOUND"
351
+ ],
352
+ area: "host-bridge",
353
+ guidance: "读取失败阶段、端点状态和具体响应;区分未监听、403、超时与不兼容,再检查目标宿主。"
354
+ })
355
+ ]);
356
+ const UNKNOWN_RULE = rule({
357
+ id: "unknown",
358
+ source: "host",
359
+ codes: [],
360
+ area: "runtime",
361
+ guidance: "查询当前状态、实际产物和最后失败阶段,补充证据后选择恢复路径;不要猜测命令或反复执行。"
362
+ });
363
+ const OUTCOME_RULE = rule({
364
+ id: "operation-outcome",
365
+ source: "host",
366
+ codes: [],
367
+ area: "operation-outcome",
368
+ verification: {
369
+ target: "original-operation",
370
+ milestone: "operation-confirmed"
371
+ },
372
+ guidance: "原操作可能已执行或部分完成。先按原操作身份核对资源,再决定后续动作,不自动重放。"
373
+ });
374
+ const REPEATED_RULE = rule({
375
+ id: "repeated-without-progress",
376
+ source: "host",
377
+ codes: [],
378
+ area: "runtime",
379
+ guidance: "相同故障没有新证据或产物,先补充诊断或改变排查策略,不重复同一编辑、构建或重试。"
380
+ });
381
+ /** 包含 unknown 与操作核对规则,Agent 可按 plan.ruleId 定位每个实际计划。 */
382
+ const WIDGET_RECOVERY_RULES = Object.freeze([
383
+ ...STANDARD_RULES,
384
+ UNKNOWN_RULE,
385
+ OUTCOME_RULE,
386
+ REPEATED_RULE
387
+ ]);
388
+ /** 根据稳定码选择通用规则;未知码明确走排查,不匹配自由文本。 */
389
+ function findWidgetRecoveryRule(diagnostic) {
390
+ return WIDGET_RECOVERY_RULES.find((value) => value.sources.includes(diagnostic.source) && value.codes.includes(diagnostic.code)) ?? UNKNOWN_RULE;
391
+ }
392
+ /** 纯规则选择,不运行工具;宿主仍须校验实际能力、权限、scope 与恢复预算。 */
393
+ function createWidgetRecoveryPlan(diagnostic, context = {}) {
394
+ let selected = findWidgetRecoveryRule(diagnostic);
395
+ if (context.executionState === "unknown" || context.executionState === "partial" || context.mayHaveExecuted === true && context.executionState !== "finished") selected = OUTCOME_RULE;
396
+ else if (context.progress?.repeated && !context.progress.hasNewEvidence) selected = REPEATED_RULE;
397
+ else if (selected.disposition === "retry" && (diagnostic.retryable !== true || context.executionState !== "not_started" || context.mayHaveExecuted !== false)) selected = OUTCOME_RULE;
398
+ if (selected.disposition === "repair" && !diagnostic.diagnostics?.length) return {
399
+ schemaVersion: 1,
400
+ ruleId: selected.id,
401
+ ruleVersion: selected.version,
402
+ ...context.progress ? { progress: { ...context.progress } } : {},
403
+ disposition: "inspect",
404
+ nextAction: {
405
+ id: "inspect",
406
+ params: { area: diagnostic.source === "toolchain" ? "toolchain" : "render" }
407
+ },
408
+ followUpActions: [],
409
+ verification: {
410
+ target: "current-attempt",
411
+ milestone: "diagnostics-updated"
412
+ },
413
+ guidance: "缺少可定位的诊断,先查询编译或源码证据,再修复;不能仅凭错误码猜测补丁。"
414
+ };
415
+ return {
416
+ schemaVersion: 1,
417
+ ruleId: selected.id,
418
+ ruleVersion: selected.version,
419
+ disposition: selected.disposition,
420
+ ...context.progress ? { progress: { ...context.progress } } : {},
421
+ nextAction: structuredClone(selected.nextAction),
422
+ followUpActions: structuredClone(selected.followUpActions),
423
+ verification: { ...selected.verification },
424
+ guidance: selected.guidance
425
+ };
426
+ }
427
+ /** 只投影已校验的稳定字段;Guest message、context 和恢复文字不进入可信诊断。 */
428
+ function projectWidgetRuntimeDiagnostic(error, operation, diagnostics) {
429
+ return {
430
+ schemaVersion: 1,
431
+ source: "sdk",
432
+ code: error.code,
433
+ phase: error.phase,
434
+ operation,
435
+ severity: error.code === "RENDER_FAILED" || error.code === "UPDATE_FAILED" ? "request-error" : "instance-failure",
436
+ message: "Widget runtime operation failed",
437
+ retryable: error.retryable,
438
+ correlationId: error.correlationId,
439
+ ...diagnostics ? { diagnostics: structuredClone(diagnostics) } : {}
440
+ };
441
+ }
442
+ /** 能力拒绝只影响当前请求;不把它投影为实例终态。 */
443
+ function projectWidgetCapabilityDiagnostic(failure, operation) {
444
+ return {
445
+ schemaVersion: 1,
446
+ source: "host",
447
+ code: failure.reason,
448
+ phase: "capability",
449
+ operation,
450
+ severity: "request-error",
451
+ message: "Host capability request failed",
452
+ retryable: failure.retryable
453
+ };
454
+ }
455
+ /** 工程错误仅提取稳定 code;原始 stdout/stderr 由可信工具单独脱敏投影。 */
456
+ function projectWidgetToolchainDiagnostic(error, operation, phase) {
457
+ return {
458
+ schemaVersion: 1,
459
+ source: "toolchain",
460
+ code: error.code,
461
+ phase,
462
+ operation,
463
+ severity: "request-error",
464
+ message: "Widget project operation failed"
465
+ };
466
+ }
467
+ /** 仅选择双方声明的扩展;实际 frame 来源和当前 attempt 仍由 Host 校验。 */
468
+ function selectWidgetDevelopmentExtensions(offer, supported) {
469
+ return offer?.extensions.includes("development-diagnostics-v1") && supported.includes("development-diagnostics-v1") ? ["development-diagnostics-v1"] : [];
470
+ }
471
+ //#endregion
472
+ export { projectWidgetRuntimeDiagnostic as a, WIDGET_EVENT_NAMES as c, isWidgetActivationEvent as d, projectWidgetCapabilityDiagnostic as i, isWidgetEventName as l, createWidgetRecoveryPlan as n, projectWidgetToolchainDiagnostic as o, findWidgetRecoveryRule as r, selectWidgetDevelopmentExtensions as s, WIDGET_RECOVERY_RULES as t, WIDGET_ACTIVATION_EVENTS as u };
package/llms.txt CHANGED
@@ -28,7 +28,10 @@
28
28
  - 先在 Manifest 声明 capability,再等待 Host grant。
29
29
  - 用 `WidgetError` 的 `code/phase/retryable/correlationId` 诊断启动、渲染和能力失败。
30
30
  - `context.render` 异常通过 `runWidgetHost` 的既有 `onError` / `events.onError` 和 Guest console
31
- 本地报告,不自动终止或重试;不要改成向旧 Host 发送致命协议 `error`。
31
+ 本地报告,不自动终止或重试。Host 协商 `development-diagnostics-v1` 后还会收到与请求关联的
32
+ `render-result`;使用 Host 的查询入口取得标准诊断与 SDK 恢复规则。旧 Host 不接收新增消息。
33
+ - 修复后构建并刷新当前预览,再查新代际回执。`handled` 证明当前处理器完成,仍需验收实际 DOM
34
+ 和业务场景;`ready`、`unknown`、`unsupported` 和等待超时都不是恢复成功。
32
35
  - `ok=false` 的能力结果是可处理状态,不要直接重试或绕过 Host。
33
36
  - 文件读写使用 `context.files.read(input, options)` / `write(input, options)`;Manifest
34
37
  分别申请 `files.read` / `files.write`。file-preview 的引用来自当前文件;Panel 只有获得
@@ -43,3 +46,12 @@
43
46
  write-outcome-unknown,不自动重试;保留内容和同一 mutationId 核对结果,成功回执后才标记已保存。
44
47
  - revision-conflict 需重读并合并;mutation-conflict 表示同一 mutationId 被用于不同内容。
45
48
  - SDK 本地通过不等于 iframe 已真实渲染、Bundle 已发布或 Agent 已绑定。
49
+
50
+ ## Widget 尺寸与滚动
51
+
52
+ - `panel` 和使用填充布局的 `file-preview` 由 Host 分配 iframe 高度。为 `html`、`body` 和挂载根设置 `height: 100%`,让滚动区或画布容器使用 `flex: 1; min-height: 0`。
53
+ - 按内容定高的嵌入式 `file-preview` 可以在第一次 `context.render` 中用 `context.resize(width, height)` 请求合适的初始尺寸。`request.context.viewport` 表示 Host 报告的当前视口,避免把视口或 iframe 测量值回报给 Host。
54
+ - 观察实际画布容器的大小并重绘。用真实 Host 改变窗口高度,核对内容区、iframe 和滚动区或画布的高度。
55
+
56
+ 生成工程的 `src/widget.ts` 或 `src/widget.tsx` 带有布局示例。完整说明见
57
+ [样式指南](https://dev.dp.tech/bohrium-widget-sdk/guides/styling#面板高度与滚动)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dp-bohrium/widget-sdk",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Bohrium Widget 开发者 SDK:在 Widget Bundle 内与 Host 建立 Bridge,并暴露生命周期与能力协议。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -46,6 +46,7 @@
46
46
  },
47
47
  "files": [
48
48
  "dist",
49
+ "!dist/validation",
49
50
  "assets",
50
51
  "schemas",
51
52
  "conformance",
@@ -66,26 +67,26 @@
66
67
  "tsdown": "0.22.14",
67
68
  "typescript": "^5.8.3",
68
69
  "vitest": "^3.2.4",
69
- "@dp-bohrium/widget-contracts": "0.7.0",
70
- "@dp-bohrium/widget-contract-testing": "0.1.0-alpha.0"
70
+ "ajv": "^8.17.1"
71
71
  },
72
72
  "scripts": {
73
- "build:runtime": "pnpm --filter @dp-bohrium/widget-contracts run build && tsdown && tsc -b tsconfig.build.json --force --pretty false && node ../../scripts/schema/build-sdk-assets.mjs",
74
- "build:cli": "node ../../scripts/schema/build-sdk-cli-assets.mjs && tsdown --config widget-cli.tsdown.config.ts && node --check dist/cli.mjs",
75
- "build": "pnpm build:runtime && pnpm --filter @dp-bohrium/widget-contract-testing build && pnpm build:cli",
76
- "typecheck": "tsc --noEmit",
73
+ "build:runtime": "node ../../scripts/build/widget-build.mjs runtime",
74
+ "build": "node ../../scripts/build/widget-build.mjs sdk",
75
+ "typecheck": "pnpm schema:check && tsc --noEmit",
77
76
  "test": "vitest run",
78
77
  "test:coverage": "vitest run --coverage --coverage.thresholds.lines=80 --coverage.thresholds.functions=80 --coverage.thresholds.statements=80 --coverage.thresholds.branches=70",
79
78
  "test:watch": "vitest",
80
- "fixture:build": "tsdown --config minimal-widget.tsdown.config.ts",
81
- "fixture:pack": "node ../../scripts/fixtures/pack-minimal-sdk-widget.mjs",
79
+ "fixture:build": "node ../../scripts/build/with-build.mjs sdk -- tsdown --config minimal-widget.tsdown.config.ts",
80
+ "fixture:pack": "node ../../scripts/build/with-build.mjs sdk -- node ../../scripts/fixtures/pack-minimal-sdk-widget.mjs",
82
81
  "fixture:pack:test": "node --test ../../scripts/fixtures/pack-minimal-sdk-widget.test.mjs",
83
- "fixture:check": "node ../../scripts/fixtures/check-minimal-sdk-widget.mjs",
82
+ "fixture:check": "node ../../scripts/build/with-build.mjs sdk -- node ../../scripts/fixtures/check-minimal-sdk-widget.mjs",
84
83
  "lint": "biome check src test",
85
84
  "format:check": "biome check --linter-enabled=false src test",
86
85
  "package:contents": "node ../../scripts/quality/check-sdk-package-contents.mjs ../../.artifacts/dp-bohrium-widget-sdk-*.tgz",
87
86
  "consumer:canary": "node ../../scripts/consumer/consumer-sdk-install.mjs ../../.artifacts/dp-bohrium-widget-sdk-*.tgz",
88
87
  "release:check-version": "WIDGET_PACKAGE_NAME=@dp-bohrium/widget-sdk node ../../scripts/release/check-registry-version.mjs",
89
- "check": "pnpm typecheck && pnpm test && pnpm fixture:build && pnpm fixture:pack:test && pnpm lint && pnpm format:check && pnpm --filter @dp-bohrium/widget-contract-testing build && pnpm build && pnpm fixture:pack && pnpm fixture:check"
88
+ "check": "pnpm typecheck && pnpm test && pnpm fixture:build && pnpm fixture:pack:test && pnpm lint && pnpm format:check && pnpm build && pnpm fixture:pack && pnpm fixture:check",
89
+ "schema:check": "node ../../scripts/schema/generate-types.mjs --check && node ../../scripts/schema/check-drift.mjs",
90
+ "build:tools": "node ../../scripts/build/widget-build.mjs tools"
90
91
  }
91
92
  }