@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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +25 -0
- package/README.md +0 -7
- package/assets/schemas/widget-manifest.schema.json +0 -12
- package/assets/schemas/widget-runtime-contracts.schema.json +906 -272
- package/assets/toolchain.json +8 -8
- package/conformance/development-diagnostics.json +128 -0
- package/dist/cli.mjs +184 -14
- package/dist/compatibility/testing.js +1 -1
- package/dist/contracts.js +3 -2
- package/dist/development-guards-COvBR0G1.js +136 -0
- package/dist/{dsh-BlpXm2Bv.mjs → dsh-BJWUB8Nq.mjs} +1308 -516
- package/dist/index.d.ts +2 -2
- package/dist/index.js +53 -17
- package/dist/protocol/development-guards.d.ts +9 -0
- package/dist/protocol/generated-development-policy.d.ts +22 -0
- package/dist/protocol/generated-host-runtime-config.d.ts +0 -22
- package/dist/protocol/generated-manifest.d.ts +0 -8
- package/dist/protocol/generated-plugin-manifest.d.ts +0 -8
- package/dist/protocol/generated-runtime-contracts.d.ts +230 -128
- package/dist/protocol/index.d.ts +7 -2
- package/dist/protocol/widget-recovery.d.ts +19 -0
- package/dist/{protocol-endpoint-CDz-YDGT.js → protocol-endpoint-DbT0OJNu.js} +5 -0
- package/dist/runtime/bootstrap.d.ts +3 -1
- package/dist/runtime/client.d.ts +5 -1
- package/dist/runtime/index.js +2 -2
- package/dist/runtime/protocol-endpoint.d.ts +5 -5
- package/dist/runtime/transport.d.ts +9 -9
- package/dist/{runtime-D2xmZVSb.js → runtime-Bn_ee8xy.js} +39 -5
- package/dist/widget-recovery-BF_sSUZX.js +472 -0
- package/dist/widget.d.ts +1 -41
- package/llms.txt +13 -1
- package/package.json +1 -1
- package/schemas/widget-manifest.schema.json +0 -12
- package/schemas/widget-runtime-contracts.schema.json +906 -272
- package/conformance/bridge/tasks-list-request.json +0 -17
- package/conformance/bridge/tasks-watch-request.json +0 -18
- package/dist/widget-events-gsKJbjqg.js +0 -65
package/assets/toolchain.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"formatVersion": 3,
|
|
3
3
|
"packageName": "@dp-bohrium/widget-sdk",
|
|
4
|
-
"cli": "0.
|
|
4
|
+
"cli": "0.10.0",
|
|
5
5
|
"profile": {
|
|
6
|
-
"id": "widget-sdk-0.
|
|
7
|
-
"sdk": "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:
|
|
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:
|
|
24
|
-
"validators": "sha256:
|
|
25
|
-
"schemas": "sha256:
|
|
26
|
-
"templates": "sha256:
|
|
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
|
|
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 =
|
|
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 || ![
|
|
881
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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-
|
|
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 {
|
|
2
|
-
|
|
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 };
|