@mbws/plugin-sdk 0.2.4 → 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.
- package/dist/caps.d.ts +97 -0
- package/dist/caps.js +24 -0
- package/dist/client.d.ts +45 -2
- package/dist/client.js +107 -16
- package/dist/config.d.ts +24 -5
- package/dist/config.js +74 -7
- package/dist/dev-mock.d.ts +1 -7
- package/dist/dev-mock.js +138 -1
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/manifest.d.ts +51 -1
- package/dist/manifest.js +55 -4
- package/dist/protocol.d.ts +10 -3
- package/dist/protocol.js +12 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/vite.d.ts +11 -0
- package/dist/vite.js +372 -9
- package/package.json +2 -1
package/dist/caps.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* caps 能力面协议类型——附件执行/上传查询的共享契约(SDK 客户端 / 宿主 broker / 桌面桥共用)。
|
|
3
|
+
*
|
|
4
|
+
* 模型(docs/plan §7.6 偏离记录):开放附件声明 + 完整执行权。宿主不关心附件是什么,
|
|
5
|
+
* 职责收缩为四件事——下载校验、执行通道、进度/取消、产物登记。插件开发者 shell 级
|
|
6
|
+
* 驱动附件(任意 args),字节不过协议通道(输入 localize 进沙箱、产物宿主代传)。
|
|
7
|
+
*
|
|
8
|
+
* 命名空间固定两个(附件种类开放,不按附件分命名空间):
|
|
9
|
+
* - caps.exec —— 附件 ensure/状态 + 输入本地化 + spawn 长任务 + 产物登记 + 媒体预览(有 attachments 声明才授予)
|
|
10
|
+
* - caps.uploads —— 上传队列状态查询(桌面宿主恒授)
|
|
11
|
+
*/
|
|
12
|
+
/** 附件状态机(宿主侧 attachment-store 的投影)。 */
|
|
13
|
+
export interface AttachmentState {
|
|
14
|
+
name: string;
|
|
15
|
+
/** missing → downloading → verifying → ready;failed 附 message */
|
|
16
|
+
status: "missing" | "downloading" | "verifying" | "ready" | "failed";
|
|
17
|
+
version?: string;
|
|
18
|
+
sha256?: string;
|
|
19
|
+
/** 下载进度 0..1(downloading 时有值) */
|
|
20
|
+
ratio?: number;
|
|
21
|
+
message?: string;
|
|
22
|
+
}
|
|
23
|
+
/** 子进程行流事件(宿主 100ms 节流合并后下发)。 */
|
|
24
|
+
export interface CapsJobLine {
|
|
25
|
+
stream: "stdout" | "stderr";
|
|
26
|
+
text: string;
|
|
27
|
+
}
|
|
28
|
+
/** `mbws:event { event: "job" }` 的 payload 形状。 */
|
|
29
|
+
export interface CapsJobEvent {
|
|
30
|
+
jobId: string;
|
|
31
|
+
kind: "lines" | "exit";
|
|
32
|
+
/** kind=lines:节流合并的行批 */
|
|
33
|
+
lines?: CapsJobLine[];
|
|
34
|
+
/** kind=exit:退出码(-1 = 被 kill;timedOut 标记超时) */
|
|
35
|
+
exitCode?: number;
|
|
36
|
+
timedOut?: boolean;
|
|
37
|
+
/** 尾部 stderr 摘要(exit 时随行,8KB 截断) */
|
|
38
|
+
stderrTail?: string;
|
|
39
|
+
}
|
|
40
|
+
/** `mbws:event { event: "attachment" }` 的 payload 形状。 */
|
|
41
|
+
export interface CapsAttachmentEvent {
|
|
42
|
+
name: string;
|
|
43
|
+
phase: "downloading" | "verifying" | "ready" | "failed";
|
|
44
|
+
ratio?: number;
|
|
45
|
+
message?: string;
|
|
46
|
+
}
|
|
47
|
+
export type CapsEvent = CapsJobEvent | CapsAttachmentEvent;
|
|
48
|
+
/** caps.uploads.status 返回的占位值上传状态(pending-uploads 状态机投影)。 */
|
|
49
|
+
export interface UploadStatusPayload {
|
|
50
|
+
status: "registered" | "pending" | "uploading" | "done" | "failed" | "missing";
|
|
51
|
+
/** uploading 时 0..1 */
|
|
52
|
+
progress?: number;
|
|
53
|
+
/** done 时的 OSS objectKey */
|
|
54
|
+
objectKey?: string | null;
|
|
55
|
+
}
|
|
56
|
+
/** caps.exec.localize 返回:job 沙箱相对路径(cwd 相对,args 直接引用)。 */
|
|
57
|
+
export interface LocalizedInput {
|
|
58
|
+
path: string;
|
|
59
|
+
size: number;
|
|
60
|
+
}
|
|
61
|
+
/** caps.exec.registerOutput 返回:`local:` 占位值(apply 回写用,宿主代传)。 */
|
|
62
|
+
export interface RegisteredOutput {
|
|
63
|
+
placeholder: string;
|
|
64
|
+
}
|
|
65
|
+
/** caps.exec.spawn 返回的裸句柄(SDK 包装成 ExecJob;jobResult 断线兜底同形)。 */
|
|
66
|
+
export interface SpawnedJob {
|
|
67
|
+
jobId: string;
|
|
68
|
+
}
|
|
69
|
+
export interface JobResultPayload {
|
|
70
|
+
done: boolean;
|
|
71
|
+
exitCode?: number;
|
|
72
|
+
timedOut?: boolean;
|
|
73
|
+
stderrTail?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 客户端侧 job 句柄(SDK 构造,插件消费)。wait() 以 job 事件 exit 结算,
|
|
77
|
+
* 非零退出抛 MbwsInvokeError(E_JOB_FAILED, data: { exitCode, stderrTail })。
|
|
78
|
+
*/
|
|
79
|
+
export interface ExecJob {
|
|
80
|
+
jobId: string;
|
|
81
|
+
/** 等待退出(exit 事件结算;断线时走 caps.exec.jobResult 兜底轮询) */
|
|
82
|
+
wait(): Promise<{
|
|
83
|
+
exitCode: number;
|
|
84
|
+
stderrTail: string;
|
|
85
|
+
}>;
|
|
86
|
+
/** 取消执行(SIGTERM → 3s SIGKILL),幂等 */
|
|
87
|
+
cancel(): Promise<void>;
|
|
88
|
+
/** 行级订阅(stdout/stderr;返回退订函数)。ffmpeg -progress 行从这里来 */
|
|
89
|
+
onLine(cb: (e: CapsJobLine) => void): () => void;
|
|
90
|
+
/** 读沙箱内小文本文件(相对路径,maxBytes 上限默认 1MB;不存在返回 null) */
|
|
91
|
+
read(path: string, maxBytes?: number): Promise<string | null>;
|
|
92
|
+
}
|
|
93
|
+
/** caps.exec 方法白名单(宿主 broker 路由 + 参数校验依据,防调到桥内部方法)。 */
|
|
94
|
+
export declare const CAPS_EXEC_METHODS: readonly ["ensureAttachment", "attachmentStatus", "localize", "spawn", "jobResult", "cancelJob", "readFile", "registerOutput", "mediaUrl"];
|
|
95
|
+
export declare const CAPS_UPLOADS_METHODS: readonly ["status"];
|
|
96
|
+
export type CapsExecMethod = (typeof CAPS_EXEC_METHODS)[number];
|
|
97
|
+
export type CapsUploadsMethod = (typeof CAPS_UPLOADS_METHODS)[number];
|
package/dist/caps.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* caps 能力面协议类型——附件执行/上传查询的共享契约(SDK 客户端 / 宿主 broker / 桌面桥共用)。
|
|
3
|
+
*
|
|
4
|
+
* 模型(docs/plan §7.6 偏离记录):开放附件声明 + 完整执行权。宿主不关心附件是什么,
|
|
5
|
+
* 职责收缩为四件事——下载校验、执行通道、进度/取消、产物登记。插件开发者 shell 级
|
|
6
|
+
* 驱动附件(任意 args),字节不过协议通道(输入 localize 进沙箱、产物宿主代传)。
|
|
7
|
+
*
|
|
8
|
+
* 命名空间固定两个(附件种类开放,不按附件分命名空间):
|
|
9
|
+
* - caps.exec —— 附件 ensure/状态 + 输入本地化 + spawn 长任务 + 产物登记 + 媒体预览(有 attachments 声明才授予)
|
|
10
|
+
* - caps.uploads —— 上传队列状态查询(桌面宿主恒授)
|
|
11
|
+
*/
|
|
12
|
+
/** caps.exec 方法白名单(宿主 broker 路由 + 参数校验依据,防调到桥内部方法)。 */
|
|
13
|
+
export const CAPS_EXEC_METHODS = [
|
|
14
|
+
"ensureAttachment",
|
|
15
|
+
"attachmentStatus",
|
|
16
|
+
"localize",
|
|
17
|
+
"spawn",
|
|
18
|
+
"jobResult",
|
|
19
|
+
"cancelJob",
|
|
20
|
+
"readFile",
|
|
21
|
+
"registerOutput",
|
|
22
|
+
"mediaUrl",
|
|
23
|
+
];
|
|
24
|
+
export const CAPS_UPLOADS_METHODS = ["status"];
|
package/dist/client.d.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* 安全定位:客户端只是包装不是防线——插件绕过客户端手发消息伪造调用,
|
|
14
14
|
* broker 对每条进入的消息仍按 manifest 独立校验(防线在宿主侧)。
|
|
15
15
|
*/
|
|
16
|
+
import type { AttachmentState, CapsAttachmentEvent, ExecJob, LocalizedInput, RegisteredOutput, UploadStatusPayload } from "./caps.js";
|
|
16
17
|
import { type FetchContractOptions, type PluginContract } from "./contract.js";
|
|
17
18
|
import { type MbwsErrorCode } from "./errors.js";
|
|
18
19
|
import type { MbwsErrorPayload } from "./protocol.js";
|
|
@@ -22,8 +23,8 @@ import type { MbwsErrorPayload } from "./protocol.js";
|
|
|
22
23
|
* 不传泛型时退宽类型(Record<string, unknown>),与生成前的用法完全兼容。
|
|
23
24
|
*/
|
|
24
25
|
export interface MbwsContractTypes {
|
|
25
|
-
inputs:
|
|
26
|
-
outputs:
|
|
26
|
+
inputs: object;
|
|
27
|
+
outputs: object;
|
|
27
28
|
}
|
|
28
29
|
/** C 的 inputs 面(C 未声明时退宽 Record) */
|
|
29
30
|
export type MbwsContractInputs<C> = C extends {
|
|
@@ -85,6 +86,48 @@ export interface MbwsClient<C extends MbwsContractTypes = MbwsContractTypes> {
|
|
|
85
86
|
/** 应答宿主的 before-close 询问(dirty=true 宿主先弹确认再关) */
|
|
86
87
|
replyBeforeClose(dirty: boolean): void;
|
|
87
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* caps 能力面(桌面宿主注入能力桥才存在;web 宿主 undefined)。
|
|
91
|
+
* exec 需 manifest.attachments 声明;uploads 桌面恒授。
|
|
92
|
+
*/
|
|
93
|
+
caps?: {
|
|
94
|
+
exec: {
|
|
95
|
+
/** 确保附件就绪:未下载则触发下载(宿主先弹 consent),返回就绪态 */
|
|
96
|
+
ensureAttachment(name: string): Promise<{
|
|
97
|
+
status: "ready";
|
|
98
|
+
version: string;
|
|
99
|
+
sha256: string;
|
|
100
|
+
}>;
|
|
101
|
+
/** 非触发式状态查询(下载进度轮询兜底;主通道是 onAttachment 事件) */
|
|
102
|
+
attachmentStatus(name: string): Promise<AttachmentState>;
|
|
103
|
+
/** 输入本地化:OSS objectKey 或 local: 占位 → job 沙箱相对路径(args 直接引用) */
|
|
104
|
+
localize(ref: string): Promise<LocalizedInput>;
|
|
105
|
+
/** 启动附件执行(args 插件全权组装,cwd = job 沙箱),返回 job 句柄 */
|
|
106
|
+
spawn(req: {
|
|
107
|
+
attachment: string;
|
|
108
|
+
args: string[];
|
|
109
|
+
timeoutMs?: number;
|
|
110
|
+
}): Promise<ExecJob>;
|
|
111
|
+
/** 产物登记:job 沙箱内相对路径 → local: 占位值(宿主 move 进上传队列代传) */
|
|
112
|
+
registerOutput(req: {
|
|
113
|
+
jobId: string;
|
|
114
|
+
path: string;
|
|
115
|
+
filename: string;
|
|
116
|
+
contentType?: string;
|
|
117
|
+
}): Promise<RegisteredOutput>;
|
|
118
|
+
/** 媒体预览 URL:local: 占位 / OSS key → media-local://(桌面)或签名 URL */
|
|
119
|
+
mediaUrl(ref: string): Promise<{
|
|
120
|
+
url: string;
|
|
121
|
+
mime?: string;
|
|
122
|
+
}>;
|
|
123
|
+
/** 附件下载进度/状态事件订阅(返回退订函数) */
|
|
124
|
+
onAttachment(cb: (e: CapsAttachmentEvent) => void): () => void;
|
|
125
|
+
};
|
|
126
|
+
uploads: {
|
|
127
|
+
/** 占位值上传状态查询(面板展示「待上传/上传中 42%/已上传」) */
|
|
128
|
+
status(placeholder: string): Promise<UploadStatusPayload>;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
88
131
|
/**
|
|
89
132
|
* 数据结构(契约)面:拉关联任务的最新契约。不经宿主端口——服务端按插件编号
|
|
90
133
|
* (manifest.id)公开派生,dev 预览/宿主内同一调用;故不随 grants 增减、恒在。
|
package/dist/client.js
CHANGED
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* createMbws() 工厂——SDK 侧协议客户端(设计稿 §6.1 / §6.3 的代码落地)。
|
|
3
|
-
*
|
|
4
|
-
* 时序真话:`mbws` 不是静态导出的对象——端口握手是异步的,插件代码零环境分支:
|
|
5
|
-
* - 生产(宿主 sandbox iframe 内):等宿主经 window.postMessage 递来的 MessagePort
|
|
6
|
-
* (校验 event.source === window.parent,防嵌套 iframe 抢端口)→ init 握手
|
|
7
|
-
* → 按 grants 构造命名空间 → 返回 mbws。
|
|
8
|
-
* - 开发(harness/单测/直接起 dev server):顶窗口或等待端口超时 → dev mock 降级。
|
|
9
|
-
*
|
|
10
|
-
* 传输约定(对 M1 starter 手写引导的收紧):init 经窗口通道递端口是唯一一次
|
|
11
|
-
* window.postMessage;此后所有消息(含 init-ack / panel:* 生命周期)一律走端口。
|
|
12
|
-
*
|
|
13
|
-
* 安全定位:客户端只是包装不是防线——插件绕过客户端手发消息伪造调用,
|
|
14
|
-
* broker 对每条进入的消息仍按 manifest 独立校验(防线在宿主侧)。
|
|
15
|
-
*/
|
|
16
1
|
import { fetchContract } from "./contract.js";
|
|
17
2
|
import { createDevMockMbws } from "./dev-mock.js";
|
|
18
3
|
import { MBWS_ERROR_CODES } from "./errors.js";
|
|
@@ -63,6 +48,85 @@ export function connectMbwsPort(port, init, opts = {}) {
|
|
|
63
48
|
});
|
|
64
49
|
});
|
|
65
50
|
const has = (ns) => init.grants.includes(ns);
|
|
51
|
+
// caps 会话:job 行流/退出事件路由 + 附件事件订阅(事件都在端口上,invoke 只是控制面)
|
|
52
|
+
const jobSessions = new Map();
|
|
53
|
+
const attachmentCbs = new Set();
|
|
54
|
+
const jobSessionOf = (jobId) => {
|
|
55
|
+
let session = jobSessions.get(jobId);
|
|
56
|
+
if (!session) {
|
|
57
|
+
session = { lineCbs: new Set(), waiters: [], settled: false };
|
|
58
|
+
jobSessions.set(jobId, session);
|
|
59
|
+
}
|
|
60
|
+
return session;
|
|
61
|
+
};
|
|
62
|
+
const handleCapsEvent = (payload) => {
|
|
63
|
+
const e = payload;
|
|
64
|
+
if (!e || typeof e !== "object")
|
|
65
|
+
return;
|
|
66
|
+
if ("jobId" in e) {
|
|
67
|
+
// CapsJobEvent
|
|
68
|
+
const session = jobSessions.get(e.jobId);
|
|
69
|
+
if (!session)
|
|
70
|
+
return;
|
|
71
|
+
if (e.kind === "lines" && e.lines) {
|
|
72
|
+
for (const line of e.lines) {
|
|
73
|
+
for (const cb of session.lineCbs)
|
|
74
|
+
cb(line);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (e.kind === "exit") {
|
|
78
|
+
const exitCode = e.exitCode ?? -1;
|
|
79
|
+
const stderrTail = e.stderrTail ?? "";
|
|
80
|
+
session.settled = true;
|
|
81
|
+
session.result = { exitCode, stderrTail };
|
|
82
|
+
if (exitCode === 0) {
|
|
83
|
+
for (const w of session.waiters)
|
|
84
|
+
w.resolve(session.result);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
for (const w of session.waiters) {
|
|
88
|
+
w.reject(new MbwsInvokeError({
|
|
89
|
+
code: MBWS_ERROR_CODES.E_JOB_FAILED,
|
|
90
|
+
message: `附件执行失败(exit ${exitCode})`,
|
|
91
|
+
data: { exitCode, stderrTail, timedOut: e.timedOut },
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
session.waiters = [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else if ("name" in e) {
|
|
99
|
+
for (const cb of attachmentCbs)
|
|
100
|
+
cb(e);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
/** spawn 返回的裸 jobId 包装成 ExecJob(wait 以 exit 事件结算,断线走 jobResult 兜底) */
|
|
104
|
+
const makeExecJob = (jobId) => {
|
|
105
|
+
jobSessionOf(jobId);
|
|
106
|
+
return {
|
|
107
|
+
jobId,
|
|
108
|
+
wait: () => {
|
|
109
|
+
const session = jobSessionOf(jobId);
|
|
110
|
+
if (session.settled && session.result) {
|
|
111
|
+
if (session.result.exitCode === 0)
|
|
112
|
+
return Promise.resolve(session.result);
|
|
113
|
+
return Promise.reject(new MbwsInvokeError({
|
|
114
|
+
code: MBWS_ERROR_CODES.E_JOB_FAILED,
|
|
115
|
+
message: `附件执行失败(exit ${session.result.exitCode})`,
|
|
116
|
+
data: { exitCode: session.result.exitCode, stderrTail: session.result.stderrTail },
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
return new Promise((resolve, reject) => session.waiters.push({ resolve, reject }));
|
|
120
|
+
},
|
|
121
|
+
cancel: () => invoke("caps.exec", "cancelJob", { jobId }),
|
|
122
|
+
onLine: (cb) => {
|
|
123
|
+
const session = jobSessionOf(jobId);
|
|
124
|
+
session.lineCbs.add(cb);
|
|
125
|
+
return () => session.lineCbs.delete(cb);
|
|
126
|
+
},
|
|
127
|
+
read: (path, maxBytes) => invoke("caps.exec", "readFile", { jobId, path, maxBytes }),
|
|
128
|
+
};
|
|
129
|
+
};
|
|
66
130
|
port.addEventListener("message", (event) => {
|
|
67
131
|
const message = parseMbwsMessage(event.data);
|
|
68
132
|
if (!message)
|
|
@@ -87,9 +151,12 @@ export function connectMbwsPort(port, init, opts = {}) {
|
|
|
87
151
|
break;
|
|
88
152
|
}
|
|
89
153
|
case "mbws:event": {
|
|
90
|
-
// 取消事件桥到 AbortSignal;
|
|
154
|
+
// 取消事件桥到 AbortSignal;job/attachment 事件路由进 caps 会话
|
|
91
155
|
if (message.event === "cancel")
|
|
92
156
|
abortController.abort();
|
|
157
|
+
else if (message.event === "job" || message.event === "attachment") {
|
|
158
|
+
handleCapsEvent(message.payload);
|
|
159
|
+
}
|
|
93
160
|
break;
|
|
94
161
|
}
|
|
95
162
|
case "panel:before-close": {
|
|
@@ -140,6 +207,30 @@ export function connectMbwsPort(port, init, opts = {}) {
|
|
|
140
207
|
},
|
|
141
208
|
}
|
|
142
209
|
: {}),
|
|
210
|
+
...(has("caps.exec") && has("caps.uploads")
|
|
211
|
+
? {
|
|
212
|
+
caps: {
|
|
213
|
+
exec: {
|
|
214
|
+
ensureAttachment: (name) => invoke("caps.exec", "ensureAttachment", { name }),
|
|
215
|
+
attachmentStatus: (name) => invoke("caps.exec", "attachmentStatus", { name }),
|
|
216
|
+
localize: (ref) => invoke("caps.exec", "localize", { ref }),
|
|
217
|
+
spawn: async (req) => {
|
|
218
|
+
const spawned = (await invoke("caps.exec", "spawn", req));
|
|
219
|
+
return makeExecJob(spawned.jobId);
|
|
220
|
+
},
|
|
221
|
+
registerOutput: (req) => invoke("caps.exec", "registerOutput", req),
|
|
222
|
+
mediaUrl: (ref) => invoke("caps.exec", "mediaUrl", { ref }),
|
|
223
|
+
onAttachment: (cb) => {
|
|
224
|
+
attachmentCbs.add(cb);
|
|
225
|
+
return () => attachmentCbs.delete(cb);
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
uploads: {
|
|
229
|
+
status: (placeholder) => invoke("caps.uploads", "status", { placeholder }),
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
}
|
|
233
|
+
: {}),
|
|
143
234
|
panel: {
|
|
144
235
|
resize: (height) => post({ v: PROTOCOL_VERSION, type: "panel:resize", height }),
|
|
145
236
|
onBeforeClose(cb) {
|
package/dist/config.d.ts
CHANGED
|
@@ -10,18 +10,20 @@
|
|
|
10
10
|
* 面;I/O 契约两头都不落(服务端按编号实时派生,见 contract.ts)。
|
|
11
11
|
*/
|
|
12
12
|
import { z } from "zod";
|
|
13
|
-
import { type Manifest } from "./manifest.js";
|
|
13
|
+
import { type AttachmentDecl, type AttachmentRef, type Manifest } from "./manifest.js";
|
|
14
14
|
/**
|
|
15
15
|
* 用户可填面 = manifest 静态合同 - 派生字段(artifactVersion/sdk 由构建注入)
|
|
16
|
-
* - 契约快照(inputs/outputs
|
|
16
|
+
* - 契约快照(inputs/outputs,服务端才是权威,不给本地副本留口子)
|
|
17
|
+
* - 附件平台 hash(开发者算不出三平台 sha256/size——只填 name/version 引用,
|
|
18
|
+
* platforms 由 resolveAttachmentRefs 从官方附件库解析,buildManifest 填充)。
|
|
17
19
|
*/
|
|
18
20
|
declare const UserConfigSchema: z.ZodObject<{
|
|
19
|
-
id: z.ZodString;
|
|
20
21
|
version: z.ZodString;
|
|
21
22
|
kind: z.ZodEnum<{
|
|
22
23
|
producer: "producer";
|
|
23
24
|
validator: "validator";
|
|
24
25
|
}>;
|
|
26
|
+
id: z.ZodString;
|
|
25
27
|
paramsSchema: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodRecord<z.ZodString, z.ZodType<string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | /*elided*/ any | {
|
|
26
28
|
[key: string]: string | number | boolean | /*elided*/ any | /*elided*/ any | null;
|
|
27
29
|
} | null)[] | {
|
|
@@ -71,7 +73,6 @@ declare const UserConfigSchema: z.ZodObject<{
|
|
|
71
73
|
} | null)[] | {
|
|
72
74
|
[key: string]: string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | /*elided*/ any | null)[] | /*elided*/ any | null)[] | /*elided*/ any | null)[] | /*elided*/ any | null)[] | /*elided*/ any | null)[] | /*elided*/ any | null)[] | /*elided*/ any | null)[] | /*elided*/ any | null)[] | /*elided*/ any | null)[] | /*elided*/ any | null)[] | /*elided*/ any | null;
|
|
73
75
|
} | null, unknown>>>]>>;
|
|
74
|
-
requiresCapabilities: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
75
76
|
permissions: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
76
77
|
automation: z.ZodEnum<{
|
|
77
78
|
batchable: "batchable";
|
|
@@ -88,6 +89,12 @@ declare const UserConfigSchema: z.ZodObject<{
|
|
|
88
89
|
vendor: z.ZodOptional<z.ZodString>;
|
|
89
90
|
screenshots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
90
91
|
}, z.core.$strip>;
|
|
92
|
+
attachments: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
93
|
+
version: z.ZodString;
|
|
94
|
+
name: z.ZodString;
|
|
95
|
+
entry: z.ZodOptional<z.ZodString>;
|
|
96
|
+
devPath: z.ZodOptional<z.ZodString>;
|
|
97
|
+
}, z.core.$strip>>>;
|
|
91
98
|
devKey: z.ZodOptional<z.ZodString>;
|
|
92
99
|
apiBaseUrl: z.ZodOptional<z.ZodString>;
|
|
93
100
|
plugin: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -107,6 +114,18 @@ export declare function validateUserConfig(input: unknown): MbwsUserConfig;
|
|
|
107
114
|
/**
|
|
108
115
|
* config → 装载合同 manifest:注入 artifactVersion,剥掉全部动态开发字段,
|
|
109
116
|
* 补默认值后产出宿主装载所需的完整合同。build 写 dist、dev 虚拟供给共用此派生。
|
|
117
|
+
*
|
|
118
|
+
* 附件声明需先经 resolveAttachmentRefs 解析(platforms hash 权威在官方附件库),
|
|
119
|
+
* 经 resolvedAttachments 传入;声明了附件但没传解析结果 → 抛错(防产物缺平台 hash)。
|
|
120
|
+
*/
|
|
121
|
+
export declare function buildManifest(config: MbwsUserConfig, opts?: {
|
|
122
|
+
resolvedAttachments?: AttachmentDecl[];
|
|
123
|
+
allowEmptyAttachments?: boolean;
|
|
124
|
+
}): Manifest;
|
|
125
|
+
/**
|
|
126
|
+
* 按引用批量拉官方附件库 index → 完整附件声明(platforms hash 填充)。
|
|
127
|
+
* 附件不在库中 / 平台不全 → 抛错(build 阻断);离线或后端不可达时 dev 虚拟
|
|
128
|
+
* manifest 由调用方决定降级(构建必须成功解析,不允许带占位 hash 出产物)。
|
|
110
129
|
*/
|
|
111
|
-
export declare function
|
|
130
|
+
export declare function resolveAttachmentRefs(refs: AttachmentRef[], apiBase: string): Promise<AttachmentDecl[]>;
|
|
112
131
|
export {};
|
package/dist/config.js
CHANGED
|
@@ -10,18 +10,26 @@
|
|
|
10
10
|
* 面;I/O 契约两头都不落(服务端按编号实时派生,见 contract.ts)。
|
|
11
11
|
*/
|
|
12
12
|
import { z } from "zod";
|
|
13
|
-
import { ManifestSchema } from "./manifest.js";
|
|
14
|
-
import { ARTIFACT_VERSION } from "./version.js";
|
|
13
|
+
import { AttachmentDeclSchema, AttachmentRefSchema, ManifestSchema, } from "./manifest.js";
|
|
14
|
+
import { ARTIFACT_VERSION, SDK_VERSION } from "./version.js";
|
|
15
15
|
/**
|
|
16
16
|
* 用户可填面 = manifest 静态合同 - 派生字段(artifactVersion/sdk 由构建注入)
|
|
17
|
-
* - 契约快照(inputs/outputs
|
|
17
|
+
* - 契约快照(inputs/outputs,服务端才是权威,不给本地副本留口子)
|
|
18
|
+
* - 附件平台 hash(开发者算不出三平台 sha256/size——只填 name/version 引用,
|
|
19
|
+
* platforms 由 resolveAttachmentRefs 从官方附件库解析,buildManifest 填充)。
|
|
18
20
|
*/
|
|
19
21
|
const UserConfigSchema = ManifestSchema.omit({
|
|
20
22
|
artifactVersion: true,
|
|
21
23
|
sdk: true,
|
|
22
24
|
inputs: true,
|
|
23
25
|
outputs: true,
|
|
24
|
-
|
|
26
|
+
attachments: true,
|
|
27
|
+
})
|
|
28
|
+
.extend({
|
|
29
|
+
/** 附件引用(name+version;platforms hash 构建期从官方附件库解析填充) */
|
|
30
|
+
attachments: z.array(AttachmentRefSchema).max(8).default([]),
|
|
31
|
+
})
|
|
32
|
+
.extend({
|
|
25
33
|
/** 开发者 key(预留:认领发放的开发验证凭证,契约拉取 X-Dev-Key 用)——不进产物 */
|
|
26
34
|
devKey: z.string().optional(),
|
|
27
35
|
/**
|
|
@@ -54,8 +62,67 @@ export function validateUserConfig(input) {
|
|
|
54
62
|
/**
|
|
55
63
|
* config → 装载合同 manifest:注入 artifactVersion,剥掉全部动态开发字段,
|
|
56
64
|
* 补默认值后产出宿主装载所需的完整合同。build 写 dist、dev 虚拟供给共用此派生。
|
|
65
|
+
*
|
|
66
|
+
* 附件声明需先经 resolveAttachmentRefs 解析(platforms hash 权威在官方附件库),
|
|
67
|
+
* 经 resolvedAttachments 传入;声明了附件但没传解析结果 → 抛错(防产物缺平台 hash)。
|
|
68
|
+
*/
|
|
69
|
+
export function buildManifest(config, opts = {}) {
|
|
70
|
+
const { devKey: _devKey, apiBaseUrl: _apiBaseUrl, plugin: _plugin, attachments, ...contract } = UserConfigSchema.parse(config);
|
|
71
|
+
let resolved = opts.resolvedAttachments ?? [];
|
|
72
|
+
if (attachments.length > 0 && resolved.length === 0 && !opts.allowEmptyAttachments) {
|
|
73
|
+
throw new Error(`mbws.config 声明了附件(${attachments.map((a) => `${a.name}@${a.version}`).join(", ")})` +
|
|
74
|
+
"但未提供解析结果——先调 resolveAttachmentRefs(vite 插件已自动处理)");
|
|
75
|
+
}
|
|
76
|
+
if (resolved.length > 0) {
|
|
77
|
+
// 解析结果必须与声明一一对齐(名字/版本/入口一致,防解析结果张冠李戴)
|
|
78
|
+
const byKey = new Map(resolved.map((decl) => [`${decl.name}@${decl.version}`, decl]));
|
|
79
|
+
resolved = attachments.map((ref) => {
|
|
80
|
+
const decl = byKey.get(`${ref.name}@${ref.version}`);
|
|
81
|
+
if (!decl)
|
|
82
|
+
throw new Error(`附件解析结果缺 ${ref.name}@${ref.version}`);
|
|
83
|
+
return AttachmentDeclSchema.parse({ ...decl, entry: ref.entry ?? decl.entry });
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// sdk 由构建注入(手写无效):产物用的 SDK 版本留痕,审查页/宿主可读——
|
|
87
|
+
// 「本地 dev 与线上构建版本一致性」排查的第一现场
|
|
88
|
+
return ManifestSchema.parse({
|
|
89
|
+
...contract,
|
|
90
|
+
attachments: resolved,
|
|
91
|
+
artifactVersion: ARTIFACT_VERSION,
|
|
92
|
+
sdk: SDK_VERSION,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 按引用批量拉官方附件库 index → 完整附件声明(platforms hash 填充)。
|
|
97
|
+
* 附件不在库中 / 平台不全 → 抛错(build 阻断);离线或后端不可达时 dev 虚拟
|
|
98
|
+
* manifest 由调用方决定降级(构建必须成功解析,不允许带占位 hash 出产物)。
|
|
57
99
|
*/
|
|
58
|
-
export function
|
|
59
|
-
|
|
60
|
-
|
|
100
|
+
export async function resolveAttachmentRefs(refs, apiBase) {
|
|
101
|
+
return Promise.all(refs.map(async (ref) => {
|
|
102
|
+
const url = `${apiBase.replace(/\/+$/, "")}/api/plugin-attachments` +
|
|
103
|
+
`?name=${encodeURIComponent(ref.name)}&version=${encodeURIComponent(ref.version)}`;
|
|
104
|
+
let index;
|
|
105
|
+
try {
|
|
106
|
+
const response = await fetch(url);
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
throw new Error(`HTTP ${response.status}`);
|
|
109
|
+
}
|
|
110
|
+
index = (await response.json());
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
throw new Error(`附件 ${ref.name}@${ref.version} 解析失败(${error instanceof Error ? error.message : error})` +
|
|
114
|
+
"——官方附件库不可达或该版本未登记(发布附件见 scripts/publish-attachment.mjs)");
|
|
115
|
+
}
|
|
116
|
+
const parsed = AttachmentDeclSchema.safeParse({
|
|
117
|
+
name: index.name,
|
|
118
|
+
version: index.version,
|
|
119
|
+
entry: ref.entry,
|
|
120
|
+
platforms: index.platforms,
|
|
121
|
+
});
|
|
122
|
+
if (!parsed.success) {
|
|
123
|
+
throw new Error(`附件 ${ref.name}@${ref.version} 官方库 index 不合法/平台不全:` +
|
|
124
|
+
parsed.error.issues.map((issue) => issue.message).join(";"));
|
|
125
|
+
}
|
|
126
|
+
return parsed.data;
|
|
127
|
+
}));
|
|
61
128
|
}
|
package/dist/dev-mock.d.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dev mock——开发环境(harness/单测/dev server 直接起)无宿主端口时的降级实现(设计稿 §6.3)。
|
|
3
|
-
*
|
|
4
|
-
* 语义边界:data.read 回 `samples/` 夹具数据;data.apply 只在控制台输出、不入任何库;
|
|
5
|
-
* 其余命名空间 no-op。插件代码零环境分支——同一份代码在 harness 里跑 mock、在宿主里跑真端口。
|
|
6
|
-
*/
|
|
7
1
|
import type { MbwsClient } from "./client.js";
|
|
8
2
|
/**
|
|
9
|
-
* 创建 dev mock
|
|
3
|
+
* 创建 dev mock 客户端。固定命名空间 + caps 全部构造(dev 无授权语义),
|
|
10
4
|
* panel 生命周期面上报/应答均无宿主可收,no-op 即可。
|
|
11
5
|
*/
|
|
12
6
|
export declare function createDevMockMbws(): MbwsClient;
|