@dp-bohrium/widget-sdk 0.8.0 → 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 (38) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/README.md +0 -7
  4. package/assets/schemas/widget-manifest.schema.json +0 -12
  5. package/assets/schemas/widget-runtime-contracts.schema.json +906 -272
  6. package/assets/toolchain.json +8 -8
  7. package/conformance/development-diagnostics.json +128 -0
  8. package/dist/cli.mjs +184 -14
  9. package/dist/compatibility/testing.js +1 -1
  10. package/dist/contracts.js +3 -2
  11. package/dist/development-guards-COvBR0G1.js +136 -0
  12. package/dist/{dsh-BlpXm2Bv.mjs → dsh-BJWUB8Nq.mjs} +1308 -516
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.js +53 -17
  15. package/dist/protocol/development-guards.d.ts +9 -0
  16. package/dist/protocol/generated-development-policy.d.ts +22 -0
  17. package/dist/protocol/generated-host-runtime-config.d.ts +0 -22
  18. package/dist/protocol/generated-manifest.d.ts +0 -8
  19. package/dist/protocol/generated-plugin-manifest.d.ts +0 -8
  20. package/dist/protocol/generated-runtime-contracts.d.ts +230 -128
  21. package/dist/protocol/index.d.ts +7 -2
  22. package/dist/protocol/widget-recovery.d.ts +19 -0
  23. package/dist/{protocol-endpoint-CDz-YDGT.js → protocol-endpoint-DbT0OJNu.js} +5 -0
  24. package/dist/runtime/bootstrap.d.ts +3 -1
  25. package/dist/runtime/client.d.ts +5 -1
  26. package/dist/runtime/index.js +2 -2
  27. package/dist/runtime/protocol-endpoint.d.ts +5 -5
  28. package/dist/runtime/transport.d.ts +9 -9
  29. package/dist/{runtime-D2xmZVSb.js → runtime-Bn_ee8xy.js} +39 -5
  30. package/dist/widget-recovery-BF_sSUZX.js +472 -0
  31. package/dist/widget.d.ts +1 -41
  32. package/llms.txt +13 -1
  33. package/package.json +1 -1
  34. package/schemas/widget-manifest.schema.json +0 -12
  35. package/schemas/widget-runtime-contracts.schema.json +906 -272
  36. package/conformance/bridge/tasks-list-request.json +0 -17
  37. package/conformance/bridge/tasks-watch-request.json +0 -18
  38. package/dist/widget-events-gsKJbjqg.js +0 -65
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "formatVersion": 3,
3
3
  "packageName": "@dp-bohrium/widget-sdk",
4
- "cli": "0.8.0",
4
+ "cli": "0.10.0",
5
5
  "profile": {
6
- "id": "widget-sdk-0.8.0",
7
- "sdk": "0.8.0"
6
+ "id": "widget-sdk-0.10.0",
7
+ "sdk": "0.10.0"
8
8
  },
9
9
  "protocol": {
10
10
  "schemaVersion": 1,
11
11
  "hostApiVersion": 1,
12
- "digest": "sha256:ee3023e337559bbf83c096365fe1eb49ce6809a880a5e5d65e2a2f6fd53a7ddb"
12
+ "digest": "sha256:00ae6aba78a5103a2e5318ecafd769a03d02048168cd73e4bea3d87e0f80366d"
13
13
  },
14
14
  "hostAcceptance": "unknown",
15
15
  "documentation": {
@@ -20,9 +20,9 @@
20
20
  "llms": "https://dev.dp.tech/bohrium-widget-sdk/llms.txt"
21
21
  },
22
22
  "digests": {
23
- "compiler": "sha256:92822832e958aa5448312194de3d1d4e8515721be2862d978abaefe2143c9c71",
24
- "validators": "sha256:47f6793e099e0b4c56da28f42e6ea0c79aecd9cc8915e1746fbd24f999bf05d9",
25
- "schemas": "sha256:ee3023e337559bbf83c096365fe1eb49ce6809a880a5e5d65e2a2f6fd53a7ddb",
26
- "templates": "sha256:87cf4085fb2769b1b874e9f331258c024f5826c31d0937a6e046b5511b5567c9"
23
+ "compiler": "sha256:b8ca913544930c6431fcccd04180fd7cf413366f719a89fdf973eb00bfe1495a",
24
+ "validators": "sha256:3229f4c649cd0ef27e0886a8037dc9f9ebf4e4e391c9c010738a915bf2704cec",
25
+ "schemas": "sha256:00ae6aba78a5103a2e5318ecafd769a03d02048168cd73e4bea3d87e0f80366d",
26
+ "templates": "sha256:85be81d7ede0a1631c08a464ce7857ffcce71691bb8d75717d97513c6496a8e5"
27
27
  }
28
28
  }
@@ -0,0 +1,128 @@
1
+ [
2
+ {
3
+ "name": "valid-render-diagnostic",
4
+ "contract": "WidgetDevelopmentDiagnostic",
5
+ "valid": true,
6
+ "value": {
7
+ "schemaVersion": 1,
8
+ "source": "sdk",
9
+ "code": "RENDER_FAILED",
10
+ "phase": "render",
11
+ "operation": "render",
12
+ "severity": "request-error",
13
+ "message": "Widget render handler failed",
14
+ "retryable": false,
15
+ "correlationId": "render-fixture",
16
+ "diagnostics": [
17
+ {
18
+ "file": "src/widget.ts",
19
+ "line": 8,
20
+ "column": 3
21
+ }
22
+ ]
23
+ }
24
+ },
25
+ {
26
+ "name": "invalid-guest-command",
27
+ "contract": "WidgetDevelopmentDiagnostic",
28
+ "valid": false,
29
+ "value": {
30
+ "schemaVersion": 1,
31
+ "source": "sdk",
32
+ "code": "RENDER_FAILED",
33
+ "phase": "render",
34
+ "operation": "render",
35
+ "severity": "request-error",
36
+ "message": "Widget render handler failed",
37
+ "retryable": false,
38
+ "correlationId": "render-fixture",
39
+ "diagnostics": [
40
+ {
41
+ "file": "src/widget.ts",
42
+ "line": 8,
43
+ "column": 3
44
+ }
45
+ ],
46
+ "command": "unsafe"
47
+ }
48
+ },
49
+ {
50
+ "name": "invalid-absolute-source",
51
+ "contract": "WidgetDevelopmentDiagnostic",
52
+ "valid": false,
53
+ "value": {
54
+ "schemaVersion": 1,
55
+ "source": "sdk",
56
+ "code": "RENDER_FAILED",
57
+ "phase": "render",
58
+ "operation": "render",
59
+ "severity": "request-error",
60
+ "message": "Widget render handler failed",
61
+ "retryable": false,
62
+ "correlationId": "render-fixture",
63
+ "diagnostics": [
64
+ {
65
+ "file": "/private/source.ts",
66
+ "line": 1,
67
+ "column": 1
68
+ }
69
+ ]
70
+ }
71
+ },
72
+ {
73
+ "name": "valid-offer",
74
+ "contract": "WidgetDevelopmentOffer",
75
+ "valid": true,
76
+ "value": {
77
+ "type": "widget-bootstrap-offer",
78
+ "protocolVersion": 1,
79
+ "extensions": [
80
+ "development-diagnostics-v1"
81
+ ]
82
+ }
83
+ },
84
+ {
85
+ "name": "invalid-duplicate-extension",
86
+ "contract": "WidgetDevelopmentOffer",
87
+ "valid": false,
88
+ "value": {
89
+ "type": "widget-bootstrap-offer",
90
+ "protocolVersion": 1,
91
+ "extensions": [
92
+ "development-diagnostics-v1",
93
+ "development-diagnostics-v1"
94
+ ]
95
+ }
96
+ },
97
+ {
98
+ "name": "valid-handled-result",
99
+ "contract": "WidgetRenderResult",
100
+ "valid": true,
101
+ "value": {
102
+ "requestId": "render-fixture",
103
+ "revision": 1,
104
+ "outcome": "handled"
105
+ }
106
+ },
107
+ {
108
+ "name": "invalid-failure-without-diagnostic",
109
+ "contract": "WidgetRenderResult",
110
+ "valid": false,
111
+ "value": {
112
+ "requestId": "render-fixture",
113
+ "revision": 1,
114
+ "outcome": "failed"
115
+ }
116
+ },
117
+ {
118
+ "name": "invalid-visual-claim",
119
+ "contract": "WidgetRenderResult",
120
+ "valid": false,
121
+ "value": {
122
+ "requestId": "render-fixture",
123
+ "revision": 1,
124
+ "outcome": "handled",
125
+ "visualVerified": true
126
+ }
127
+ }
128
+ ]
package/dist/cli.mjs CHANGED
@@ -1,13 +1,123 @@
1
1
  #!/usr/bin/env node
2
- import { a as normalizeArchivePath, c as compileAuthorManifest, i as prepareWidgetPackage, n as scaffoldDshWidget, o as checkWidgetProject, s as resolveWidgetProjects, t as auditDshPlugin } from "./dsh-BlpXm2Bv.mjs";
2
+ import { a as normalizeArchivePath, c as loadContractValidators, i as prepareWidgetPackage, l as compileAuthorManifest, n as scaffoldDshWidget, o as checkWidgetProject, s as resolveWidgetProjects, t as auditDshPlugin } from "./dsh-BJWUB8Nq.mjs";
3
3
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
4
- import { constants, existsSync, lstatSync, readFileSync, readdirSync, realpathSync, statSync, watch } from "node:fs";
4
+ import { constants, existsSync, fstatSync, lstatSync, readFileSync, readdirSync, realpathSync, statSync, watch } from "node:fs";
5
5
  import { chmod, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, rmdir, stat, writeFile } from "node:fs/promises";
6
6
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { spawn, spawnSync } from "node:child_process";
9
9
  import { createServer } from "node:http";
10
10
  import { isDeepStrictEqual } from "node:util";
11
+ //#region ../../scripts/widget/widget-development-observations.mjs
12
+ const JOURNAL_CAPACITY = 32;
13
+ const ATTEMPT_CAPACITY = 32;
14
+ const ID_PATTERN = /^[a-zA-Z0-9_-]{1,128}$/u;
15
+ const exact = (value, keys) => value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
16
+ /** 每个 SDK 开发会话独占;只有受保护的 Host control 能写入。 */
17
+ async function createDevelopmentObservations() {
18
+ const { validateWidgetDevelopmentDiagnostic, createWidgetRecoveryPlan, isWidgetRenderResult } = await loadContractValidators();
19
+ let cursor = 0;
20
+ let current = {
21
+ state: "unknown",
22
+ cursor
23
+ };
24
+ const attempts = /* @__PURE__ */ new Map();
25
+ const entries = [];
26
+ let previousFailure;
27
+ return {
28
+ snapshot(includeEntries = true) {
29
+ return structuredClone({
30
+ ...current,
31
+ cursor,
32
+ oldestCursor: entries[0]?.cursor ?? cursor,
33
+ ...includeEntries ? { entries } : {}
34
+ });
35
+ },
36
+ reset(generation) {
37
+ attempts.clear();
38
+ current = {
39
+ state: "unknown",
40
+ generation,
41
+ cursor
42
+ };
43
+ },
44
+ accept(input, generation) {
45
+ if (!exact(input, [
46
+ "generation",
47
+ "attemptId",
48
+ "instanceId",
49
+ "sequence",
50
+ "event"
51
+ ]) || input.generation !== generation || typeof input.attemptId !== "string" || !ID_PATTERN.test(input.attemptId) || !(typeof input.instanceId === "string" && /^[a-zA-Z0-9_.:%-]{1,1024}$/u.test(input.instanceId)) || !Number.isSafeInteger(input.sequence) || input.sequence < 1) throw new Error("invalid observation identity");
52
+ const event = input.event;
53
+ const connected = exact(event, ["type", "supported"]) && event.type === "connected" && typeof event.supported === "boolean";
54
+ const diagnostic = exact(event, ["type", "diagnostic"]) && event.type === "diagnostic" && validateWidgetDevelopmentDiagnostic(event.diagnostic).ok;
55
+ const result = exact(event, ["type", "result"]) && event.type === "render-result" && isWidgetRenderResult(event.result);
56
+ if (!connected && !diagnostic && !result) throw new Error("invalid observation contract");
57
+ const known = attempts.get(input.attemptId);
58
+ if (known && (known.instanceId !== input.instanceId || input.sequence <= known.sequence)) throw new Error("stale observation");
59
+ if (!connected && (current.attemptId !== input.attemptId || !known)) throw new Error("inactive observation attempt");
60
+ if (!connected && !known.supported && (result || event.diagnostic.source === "sdk")) throw new Error("development extension was not negotiated");
61
+ if (connected && (known || attempts.size >= ATTEMPT_CAPACITY)) throw new Error("reused or excessive observation attempt");
62
+ attempts.set(input.attemptId, {
63
+ instanceId: input.instanceId,
64
+ sequence: input.sequence,
65
+ supported: connected ? event.supported : known.supported
66
+ });
67
+ if (connected) current = {
68
+ state: event.supported ? "connected" : "unsupported",
69
+ generation,
70
+ attemptId: input.attemptId,
71
+ instanceId: input.instanceId
72
+ };
73
+ const failure = diagnostic ? event.diagnostic : result && event.result.outcome === "failed" ? event.result.diagnostic : void 0;
74
+ if (failure) {
75
+ const fingerprint = JSON.stringify([
76
+ failure.source,
77
+ failure.code,
78
+ failure.phase,
79
+ failure.operation,
80
+ failure.diagnostics
81
+ ]);
82
+ const repeated = previousFailure?.fingerprint === fingerprint && previousFailure.generation === generation;
83
+ const recovery = createWidgetRecoveryPlan(failure, {
84
+ executionState: "finished",
85
+ mayHaveExecuted: true,
86
+ progress: {
87
+ repeated,
88
+ hasNewEvidence: !repeated
89
+ }
90
+ });
91
+ previousFailure = {
92
+ fingerprint,
93
+ generation
94
+ };
95
+ current = {
96
+ ...current,
97
+ state: "failed",
98
+ diagnostic: failure,
99
+ recovery,
100
+ renderResult: result ? event.result : void 0
101
+ };
102
+ } else if (result && event.result.outcome === "handled") {
103
+ const { diagnostic: _diagnostic, recovery: _recovery, ...identity } = current;
104
+ current = {
105
+ ...identity,
106
+ state: "handled",
107
+ renderResult: event.result
108
+ };
109
+ }
110
+ const entry = {
111
+ cursor: ++cursor,
112
+ ...input
113
+ };
114
+ entries.push(entry);
115
+ if (entries.length > JOURNAL_CAPACITY) entries.shift();
116
+ return this.snapshot(false);
117
+ }
118
+ };
119
+ }
120
+ //#endregion
11
121
  //#region ../../scripts/widget/widget-dev.mjs
12
122
  const repoRoot = resolve(fileURLToPath(new URL("../..", import.meta.url)));
13
123
  const DEFAULT_PORT = 4300;
@@ -105,6 +215,7 @@ function usage$1() {
105
215
  --watch 监听 src、manifest.json 和 package.json,变化后重新 build
106
216
  --no-build 使用现有的 manifest.widgetConfig.entry,不执行 build
107
217
  --port <port> 固定 Profile 服务端口(默认 ${DEFAULT_PORT})
218
+ --listen-fd <fd> 接管宿主已监听的 IPv4 loopback TCP socket;仅与 --no-build 一起使用
108
219
  --help 显示帮助
109
220
 
110
221
  开发流程:
@@ -116,6 +227,7 @@ function usage$1() {
116
227
  }
117
228
  function parseArgs(argv) {
118
229
  const positional = [];
230
+ let portSpecified = false;
119
231
  const options = {
120
232
  open: false,
121
233
  watch: false,
@@ -160,10 +272,17 @@ function parseArgs(argv) {
160
272
  const value = Number(argv[++index]);
161
273
  if (!Number.isInteger(value) || value < 1024 || value > 65535) throw new Error("--port 必须是 1024 到 65535 之间的整数");
162
274
  options.port = value;
275
+ portSpecified = true;
276
+ } else if (argument === "--listen-fd") {
277
+ const value = argv[++index];
278
+ const fd = Number(value);
279
+ if (!/^\d+$/u.test(value) || fd < 3 || fd > 2 ** 31 - 1) throw new Error("--listen-fd 需要宿主传入的文件描述符(fd ≥ 3)");
280
+ options.listenFd = fd;
163
281
  } else if (argument.startsWith("-")) throw new Error(`未知选项:${argument}`);
164
282
  else positional.push(argument);
165
283
  }
166
284
  if (positional.length !== 1) throw new Error("需要一个 Widget 工程目录或 manifest.json");
285
+ if (options.listenFd !== void 0 && (portSpecified || options.build || options.watch)) throw new Error("--listen-fd 必须与 --no-build 一起使用,不能与 --port 或 --watch 同用");
167
286
  return {
168
287
  help: false,
169
288
  project: positional[0],
@@ -721,6 +840,28 @@ function runBuild(projectRoot, { capture = false } = {}) {
721
840
  });
722
841
  });
723
842
  }
843
+ async function listenForDevelopment(server, { port, listenFd }) {
844
+ if (listenFd !== void 0 && !fstatSync(listenFd).isSocket()) throw new Error("--listen-fd 需要宿主传入已监听的 127.0.0.1 TCP socket");
845
+ await new Promise((resolveListen, rejectListen) => {
846
+ server.once("error", rejectListen);
847
+ const onListen = () => {
848
+ server.off("error", rejectListen);
849
+ resolveListen();
850
+ };
851
+ if (listenFd === void 0) server.listen(port, "127.0.0.1", onListen);
852
+ else server.listen({
853
+ fd: listenFd,
854
+ exclusive: true
855
+ }, onListen);
856
+ });
857
+ const address = server.address();
858
+ if (address?.family !== "IPv4" || address.address !== "127.0.0.1" || !address.port) {
859
+ server.close();
860
+ server.closeAllConnections();
861
+ throw new Error("--listen-fd 需要宿主传入已监听的 127.0.0.1 TCP socket");
862
+ }
863
+ return `http://${address.address}:${address.port}`;
864
+ }
724
865
  async function startWidgetDevelopment(projectRoot, options) {
725
866
  const buildProject = options.buildProject ?? runBuild;
726
867
  const emit = options.emit ?? ((line, error = false) => error ? console.error(line) : console.log(line));
@@ -761,6 +902,8 @@ async function startWidgetDevelopment(projectRoot, options) {
761
902
  },
762
903
  lastBuildError: null
763
904
  };
905
+ const observations = await createDevelopmentObservations();
906
+ observations.reset(state.generation);
764
907
  const eventClients = /* @__PURE__ */ new Map();
765
908
  let eventId = 0;
766
909
  let session;
@@ -783,7 +926,7 @@ async function startWidgetDevelopment(projectRoot, options) {
783
926
  }
784
927
  });
785
928
  const developmentSnapshot = () => {
786
- const origin = `http://127.0.0.1:${server.address().port}`;
929
+ const { origin } = session.descriptor;
787
930
  const development = panelDevelopmentDescriptor(successfulGeneration, `${origin}${V1_PROFILE_PATH}`, `${origin}${V1_STATUS_PATH}`);
788
931
  return {
789
932
  protocolVersion: 1,
@@ -798,7 +941,8 @@ async function startWidgetDevelopment(projectRoot, options) {
798
941
  assets: successfulGeneration.assets,
799
942
  watch: state.watch,
800
943
  lastBuild: state.lastBuild,
801
- lastBuildError: state.lastBuildError
944
+ lastBuildError: state.lastBuildError,
945
+ runtime: observations.snapshot()
802
946
  };
803
947
  };
804
948
  const generationReadyData = () => {
@@ -813,6 +957,10 @@ async function startWidgetDevelopment(projectRoot, options) {
813
957
  };
814
958
  };
815
959
  const server = createServer((request, response) => {
960
+ if (!session) {
961
+ response.destroy();
962
+ return;
963
+ }
816
964
  const url = new URL(request.url ?? "/", "http://127.0.0.1");
817
965
  if (request.method === "OPTIONS") {
818
966
  response.writeHead(405, secureHeaders("text/plain; charset=utf-8")).end();
@@ -877,8 +1025,30 @@ async function startWidgetDevelopment(projectRoot, options) {
877
1025
  request.on("end", () => {
878
1026
  try {
879
1027
  const control = JSON.parse(body || "{}");
880
- if (control.protocolVersion !== 1 || !["reload", "disconnect"].includes(control.action)) throw new Error("invalid control action");
881
- if (control.action === "disconnect") {
1028
+ if (control.protocolVersion !== 1 || ![
1029
+ "reload",
1030
+ "disconnect",
1031
+ "report-runtime",
1032
+ "refresh-built-output"
1033
+ ].includes(control.action)) throw new Error("invalid control action");
1034
+ if (control.action === "report-runtime") {
1035
+ json$1(response, 200, {
1036
+ ok: true,
1037
+ protocolVersion: 1,
1038
+ runtime: observations.accept(control.observation, state.generation)
1039
+ });
1040
+ return;
1041
+ }
1042
+ if (control.action === "refresh-built-output") {
1043
+ const candidate = createDevelopmentGeneration(projectRoot, successfulGeneration.number + 1, GENERATION_LIMITS, options.manifestOptions);
1044
+ successfulGeneration = candidate;
1045
+ retainedGenerations.set(candidate.guestCapability, candidate);
1046
+ while (retainedGenerations.size > RETAINED_GENERATIONS) retainedGenerations.delete(retainedGenerations.keys().next().value);
1047
+ state.generation = candidate.number;
1048
+ observations.reset(candidate.number);
1049
+ state.lastBuildError = null;
1050
+ sendEvent("generation-ready", generationReadyData());
1051
+ } else if (control.action === "disconnect") {
882
1052
  sendEvent("disconnected", { reason: "host-request" });
883
1053
  session.disconnect();
884
1054
  } else sendEvent("reload-requested", { generation: state.generation });
@@ -915,7 +1085,7 @@ async function startWidgetDevelopment(projectRoot, options) {
915
1085
  });
916
1086
  return;
917
1087
  }
918
- const origin = `http://127.0.0.1:${server.address().port}`;
1088
+ const { origin } = session.descriptor;
919
1089
  const launchUrl = `${origin}${guestGenerationRoot(successfulGeneration)}/launch.html`;
920
1090
  json$1(response, 200, createDevelopmentConfig({
921
1091
  manifest: successfulGeneration.manifest,
@@ -928,7 +1098,7 @@ async function startWidgetDevelopment(projectRoot, options) {
928
1098
  }
929
1099
  if (url.pathname === PROFILE_PATH) {
930
1100
  const runId = url.searchParams.get("runId") || "widget-dev-run";
931
- const origin = `http://127.0.0.1:${server.address().port}`;
1101
+ const { origin } = session.descriptor;
932
1102
  json$1(response, 200, createDevelopmentConfig({
933
1103
  manifest: successfulGeneration.manifest,
934
1104
  runId,
@@ -987,11 +1157,7 @@ async function startWidgetDevelopment(projectRoot, options) {
987
1157
  }
988
1158
  serveGenerationAsset(successfulGeneration, assetPath, response);
989
1159
  });
990
- await new Promise((resolveListen, rejectListen) => {
991
- server.once("error", rejectListen);
992
- server.listen(options.port, "127.0.0.1", resolveListen);
993
- });
994
- const origin = `http://127.0.0.1:${server.address().port}`;
1160
+ const origin = await listenForDevelopment(server, options);
995
1161
  session = createWidgetDevelopmentSession({ origin });
996
1162
  const profileUrl = `${origin}${PROFILE_PATH}`;
997
1163
  const descriptorUrl = `${origin}${DESCRIPTOR_PATH}`;
@@ -1054,6 +1220,7 @@ async function startWidgetDevelopment(projectRoot, options) {
1054
1220
  retainedGenerations.set(candidate.guestCapability, candidate);
1055
1221
  while (retainedGenerations.size > RETAINED_GENERATIONS) retainedGenerations.delete(retainedGenerations.keys().next().value);
1056
1222
  state.generation = candidate.number;
1223
+ observations.reset(candidate.number);
1057
1224
  state.lastBuild = {
1058
1225
  ok: true,
1059
1226
  digest: candidate.digest,
@@ -1315,6 +1482,7 @@ function commandTopics(context) {
1315
1482
  "--open 连接或唤起 Widget 开发宿主(默认关闭)",
1316
1483
  "--no-build 跳过首次构建,使用现有入口(默认先构建)",
1317
1484
  "--port <端口> 本地服务端口,1024–65535(默认 4300)",
1485
+ "--listen-fd <fd> 接管宿主的 IPv4 回环监听;须与 --no-build 同用,排除 --port 和 --watch",
1318
1486
  "--scheme <scheme> 宿主 deep link scheme(默认 widget-host)",
1319
1487
  "--bridge <url> 指定 loopback HTTP(S) bridge;默认探测标准端口",
1320
1488
  HOST_OPTION,
@@ -1475,7 +1643,8 @@ function documentationLines(documentation, topic = {}) {
1475
1643
  ...topic.agentDocs ? [
1476
1644
  `文档站:${documentation.url}`,
1477
1645
  `Agent 索引:${documentation.llms}`,
1478
- `完整文档:${documentation.llmsFull}`
1646
+ `完整文档:${documentation.llmsFull}`,
1647
+ "工程内的作者指引见 node_modules/@dp-bohrium/widget-sdk/llms.txt。"
1479
1648
  ] : []
1480
1649
  ];
1481
1650
  }
@@ -2459,6 +2628,7 @@ Widget ZIP 使用 \`pnpm run widget:pack\` 或 \`npm run widget:pack\`;\`pnpm
2459
2628
 
2460
2629
  - Widget 界面与业务逻辑在 ${sourceFile} 中;本工程的 ${hostFile} 调用 SDK 的 runWidgetHost 启动 Widget。
2461
2630
  - 高度链、布局与滚动策略直接写在 ${sourceFile} 中,样式随 activate 安装并在释放时清理;JSON 展示元素单独处理换行。
2631
+ - 在 [SDK 本地索引](./node_modules/@dp-bohrium/widget-sdk/llms.txt) 查看「Widget 尺寸与滚动」。
2462
2632
  - manifest.json 是作者清单,打包时由 CLI 编译为宿主清单。
2463
2633
  - 提交 widget-toolchain.json 与依赖 lockfile;dist/widget.js 与生成的 ZIP 不提交。
2464
2634
  - check 只验证结构、声明和样例,不执行 JavaScript;pack 成功不代表首帧渲染、真实授权或业务验收成功。
@@ -1,4 +1,4 @@
1
- import { n as MemoryTransport, t as ProtocolEndpoint } from "../protocol-endpoint-CDz-YDGT.js";
1
+ import { n as MemoryTransport, t as ProtocolEndpoint } from "../protocol-endpoint-DbT0OJNu.js";
2
2
  //#region src/runtime/mock-host.ts
3
3
  const widgetMessageTypes = /* @__PURE__ */ new Set([
4
4
  "ready",
package/dist/contracts.js CHANGED
@@ -1,2 +1,3 @@
1
- import { i as isWidgetActivationEvent, n as isWidgetEventName, r as WIDGET_ACTIVATION_EVENTS, t as WIDGET_EVENT_NAMES } from "./widget-events-gsKJbjqg.js";
2
- export { WIDGET_ACTIVATION_EVENTS, WIDGET_EVENT_NAMES, isWidgetActivationEvent, isWidgetEventName };
1
+ import { a as projectWidgetRuntimeDiagnostic, c as WIDGET_EVENT_NAMES, d as isWidgetActivationEvent, i as projectWidgetCapabilityDiagnostic, l as isWidgetEventName, n as createWidgetRecoveryPlan, o as projectWidgetToolchainDiagnostic, r as findWidgetRecoveryRule, s as selectWidgetDevelopmentExtensions, t as WIDGET_RECOVERY_RULES, u as WIDGET_ACTIVATION_EVENTS } from "./widget-recovery-BF_sSUZX.js";
2
+ import { n as isWidgetGuestDiagnostic, r as isWidgetRenderResult, t as isWidgetDevelopmentOffer } from "./development-guards-COvBR0G1.js";
3
+ export { WIDGET_ACTIVATION_EVENTS, WIDGET_EVENT_NAMES, WIDGET_RECOVERY_RULES, createWidgetRecoveryPlan, findWidgetRecoveryRule, isWidgetActivationEvent, isWidgetDevelopmentOffer, isWidgetEventName, isWidgetGuestDiagnostic, isWidgetRenderResult, projectWidgetCapabilityDiagnostic, projectWidgetRuntimeDiagnostic, projectWidgetToolchainDiagnostic, selectWidgetDevelopmentExtensions };
@@ -0,0 +1,136 @@
1
+ //#region src/protocol/generated-development-policy.ts
2
+ /** GENERATED FILE. Do not edit directly. */
3
+ const widgetDevelopmentPolicy = {
4
+ "required": [
5
+ "schemaVersion",
6
+ "source",
7
+ "code",
8
+ "phase",
9
+ "operation",
10
+ "severity",
11
+ "message"
12
+ ],
13
+ "keys": [
14
+ "schemaVersion",
15
+ "source",
16
+ "code",
17
+ "sourceCode",
18
+ "phase",
19
+ "sourcePhase",
20
+ "operation",
21
+ "severity",
22
+ "message",
23
+ "retryable",
24
+ "correlationId",
25
+ "diagnostics",
26
+ "io"
27
+ ],
28
+ "textMax": {
29
+ "code": 96,
30
+ "sourceCode": 96,
31
+ "sourcePhase": 96,
32
+ "operation": 96,
33
+ "message": 1024,
34
+ "field": 128,
35
+ "expected": 256,
36
+ "observed": 256,
37
+ "file": 256,
38
+ "excerpt": 1024
39
+ },
40
+ "maxDetails": 8,
41
+ "phases": [
42
+ "discovery",
43
+ "build",
44
+ "config",
45
+ "load",
46
+ "handshake",
47
+ "render",
48
+ "update",
49
+ "capability",
50
+ "dispose"
51
+ ],
52
+ "severities": [
53
+ "warning",
54
+ "request-error",
55
+ "instance-failure"
56
+ ],
57
+ "identifierPattern": "^[A-Za-z][A-Za-z0-9_.-]*$",
58
+ "filePattern": "^(?!/)(?!.*\\\\)(?!.*:)(?!.*(?:^|/)\\.{1,2}(?:/|$))[^\\u0000-\\u001f]+$"
59
+ };
60
+ //#endregion
61
+ //#region src/protocol/development-guards.ts
62
+ function record(value) {
63
+ return value !== null && typeof value === "object" && !Array.isArray(value);
64
+ }
65
+ function keys(value, required, allowed = required) {
66
+ return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => allowed.includes(key));
67
+ }
68
+ const identifier = new RegExp(widgetDevelopmentPolicy.identifierPattern, "u");
69
+ const sourceFile = new RegExp(widgetDevelopmentPolicy.filePattern, "u");
70
+ function text(value, max, empty = false) {
71
+ return typeof value === "string" && (empty || value.length > 0) && [...value].length <= max;
72
+ }
73
+ function correlation(value) {
74
+ return typeof value === "string" && value.length > 0;
75
+ }
76
+ function id(value) {
77
+ return text(value, widgetDevelopmentPolicy.textMax.code) && identifier.test(value);
78
+ }
79
+ function detail(value) {
80
+ if (!record(value)) return false;
81
+ if ("file" in value) return keys(value, [
82
+ "file",
83
+ "line",
84
+ "column"
85
+ ], [
86
+ "file",
87
+ "line",
88
+ "column",
89
+ "excerpt"
90
+ ]) && text(value["file"], widgetDevelopmentPolicy.textMax.file) && sourceFile.test(value["file"]) && Number.isSafeInteger(value["line"]) && Number(value["line"]) > 0 && Number.isSafeInteger(value["column"]) && Number(value["column"]) > 0 && (value["excerpt"] === void 0 || text(value["excerpt"], widgetDevelopmentPolicy.textMax.excerpt));
91
+ return keys(value, ["field"], [
92
+ "field",
93
+ "expected",
94
+ "observed"
95
+ ]) && text(value["field"], widgetDevelopmentPolicy.textMax.field) && (value["expected"] === void 0 || text(value["expected"], widgetDevelopmentPolicy.textMax.expected, true)) && (value["observed"] === void 0 || text(value["observed"], widgetDevelopmentPolicy.textMax.observed, true));
96
+ }
97
+ function io(value) {
98
+ if (!record(value) || !keys(value, ["kind"], [
99
+ "kind",
100
+ "status",
101
+ "sourceCode"
102
+ ]) || value["sourceCode"] !== void 0 && !id(value["sourceCode"])) return false;
103
+ if (value["kind"] === "http") return Number.isInteger(value["status"]) && Number(value["status"]) >= 100 && Number(value["status"]) <= 599 && !(Number(value["status"]) >= 200 && Number(value["status"]) < 300);
104
+ return (value["kind"] === "transport" || value["kind"] === "invalid-response") && value["status"] === void 0;
105
+ }
106
+ /** 与源头 Schema 同源限额的轻量边界判定,不加载 AJV 或 Runtime。 */
107
+ function isWidgetGuestDiagnostic(value) {
108
+ if (!record(value) || !keys(value, widgetDevelopmentPolicy.required, widgetDevelopmentPolicy.keys) || value["schemaVersion"] !== 1 || value["source"] !== "sdk" || !id(value["code"]) || !id(value["operation"]) || !widgetDevelopmentPolicy.phases.some((phase) => phase === value["phase"]) || !widgetDevelopmentPolicy.severities.some((severity) => severity === value["severity"]) || !text(value["message"], widgetDevelopmentPolicy.textMax.message)) return false;
109
+ if (value["retryable"] !== void 0 && typeof value["retryable"] !== "boolean" || value["correlationId"] !== void 0 && !correlation(value["correlationId"]) || value["sourceCode"] !== void 0 && !id(value["sourceCode"]) || value["sourcePhase"] !== void 0 && !id(value["sourcePhase"])) return false;
110
+ return (value["diagnostics"] === void 0 || Array.isArray(value["diagnostics"]) && value["diagnostics"].length <= widgetDevelopmentPolicy.maxDetails && value["diagnostics"].every(detail)) && (value["io"] === void 0 || io(value["io"]));
111
+ }
112
+ /** offer 只声明解析能力;实际 iframe 来源与 attempt 由 Host 校验。 */
113
+ function isWidgetDevelopmentOffer(value) {
114
+ return record(value) && keys(value, [
115
+ "type",
116
+ "protocolVersion",
117
+ "extensions"
118
+ ]) && value["type"] === "widget-bootstrap-offer" && value["protocolVersion"] === 1 && Array.isArray(value["extensions"]) && value["extensions"].length === 1 && value["extensions"][0] === "development-diagnostics-v1";
119
+ }
120
+ /** 失败回执仍需 Host 再匹配实际已发送的 request/revision。 */
121
+ function isWidgetRenderResult(value) {
122
+ if (!record(value) || !correlation(value["requestId"]) || !Number.isSafeInteger(value["revision"]) || Number(value["revision"]) < 1) return false;
123
+ if (value["outcome"] === "handled" || value["outcome"] === "superseded") return keys(value, [
124
+ "requestId",
125
+ "revision",
126
+ "outcome"
127
+ ]);
128
+ return value["outcome"] === "failed" && keys(value, [
129
+ "requestId",
130
+ "revision",
131
+ "outcome",
132
+ "diagnostic"
133
+ ]) && isWidgetGuestDiagnostic(value["diagnostic"]) && value["diagnostic"].correlationId === value["requestId"] && (value["diagnostic"].phase === "render" || value["diagnostic"].phase === "update");
134
+ }
135
+ //#endregion
136
+ export { isWidgetGuestDiagnostic as n, isWidgetRenderResult as r, isWidgetDevelopmentOffer as t };