webloom-framework 0.1.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.
@@ -0,0 +1,3736 @@
1
+ // src/contracts/lifecycle.ts
2
+ var LifecycleScopeRevokedError = class extends Error {
3
+ code = "lifecycle.scope_revoked";
4
+ constructor(message = "Lifecycle scope has been revoked") {
5
+ super(message);
6
+ this.name = "LifecycleScopeRevokedError";
7
+ }
8
+ };
9
+ var PermissionLeaseRevokedError = class extends Error {
10
+ code = "permission.lease_revoked";
11
+ constructor(message = "Permission lease has been revoked") {
12
+ super(message);
13
+ this.name = "PermissionLeaseRevokedError";
14
+ }
15
+ };
16
+ var PermissionDeniedError = class extends Error {
17
+ code = "permission.denied";
18
+ permission;
19
+ constructor(permission, message = `Permission denied: ${permission}`) {
20
+ super(message);
21
+ this.name = "PermissionDeniedError";
22
+ this.permission = permission;
23
+ }
24
+ };
25
+ function createRemoteServiceMessageCodec(options = {}) {
26
+ const prefix = options.prefix ?? "webloom.remote-service";
27
+ const protocolVersion = options.protocolVersion ?? "1";
28
+ const types = /* @__PURE__ */ new Map([
29
+ ["call", `${prefix}.call`],
30
+ ["result", `${prefix}.result`],
31
+ ["error", `${prefix}.error`],
32
+ ["cancel", `${prefix}.cancel`],
33
+ ["handshake", `${prefix}.handshake`],
34
+ ["snapshot", `${prefix}.snapshot`],
35
+ ["invalidate", `${prefix}.invalidate`],
36
+ ["disconnect", `${prefix}.disconnect`]
37
+ ]);
38
+ return Object.freeze({
39
+ protocolVersion,
40
+ type(kind) {
41
+ const value = types.get(kind);
42
+ if (!value) throw new Error(`Unknown remote service message kind: ${kind}`);
43
+ return value;
44
+ },
45
+ encode(message) {
46
+ return { ...message };
47
+ },
48
+ decode(input) {
49
+ if (!input || typeof input !== "object") return void 0;
50
+ const message = input;
51
+ if (typeof message.type !== "string" || ![...types.values()].includes(message.type)) return void 0;
52
+ return message;
53
+ }
54
+ });
55
+ }
56
+ var RemoteServiceUnavailableError = class extends Error {
57
+ code = "service.unavailable";
58
+ constructor(message = "Remote service is unavailable") {
59
+ super(message);
60
+ this.name = "RemoteServiceUnavailableError";
61
+ }
62
+ };
63
+ var UpgradeGateRejectedError = class extends Error {
64
+ code = "upgrade.gate_rejected";
65
+ reason;
66
+ constructor(reason, message = `Upgrade gate rejected operation: ${reason}`) {
67
+ super(message);
68
+ this.name = "UpgradeGateRejectedError";
69
+ this.reason = reason;
70
+ }
71
+ };
72
+ var SCOPED_TASK_SCHEDULER_CAPABILITY = "runtime.task-scheduler";
73
+ var LIFECYCLE_ERROR_TEXT = Object.freeze({
74
+ "lifecycle.scope_revoked": "\u8FD0\u884C\u5B9E\u4F8B\u5DF2\u505C\u6B62",
75
+ "permission.denied": "\u63D2\u4EF6\u6CA1\u6709\u83B7\u5F97\u8BE5\u64CD\u4F5C\u7684\u6743\u9650",
76
+ "permission.lease_revoked": "\u6388\u6743\u79DF\u7EA6\u5DF2\u64A4\u9500",
77
+ "lifecycle.cleanup_failed": "\u8D44\u6E90\u6E05\u7406\u5931\u8D25\uFF0C\u7B49\u5F85\u91CD\u8BD5",
78
+ "lifecycle.cleanup_timeout": "\u8D44\u6E90\u6E05\u7406\u8D85\u65F6\uFF0C\u4ECD\u5728\u540E\u53F0\u6392\u7A7A",
79
+ "upgrade.gate_rejected": "\u7248\u672C\u63A5\u7BA1\u95E8\u7981\u672A\u901A\u8FC7"
80
+ });
81
+ function lifecycleErrorText(code) {
82
+ return LIFECYCLE_ERROR_TEXT[code] ?? "\u751F\u547D\u5468\u671F\u64CD\u4F5C\u672A\u5B8C\u6210";
83
+ }
84
+
85
+ // src/contracts/messageBus.ts
86
+ var RUNTIME_MESSAGE_BUS = "webloom.messageBus";
87
+
88
+ // src/contracts/resource.ts
89
+ var RESOURCE_OWNER = /* @__PURE__ */ Symbol("webloom.resource.owner");
90
+ var RESOURCE_REGISTRY_CAPABILITY = "webloom.resource.registry";
91
+
92
+ // src/host/capabilityRegistry.ts
93
+ function createCapabilityRegistry() {
94
+ const map = /* @__PURE__ */ new Map();
95
+ return {
96
+ provide(key, value) {
97
+ if (map.has(key)) {
98
+ throw new Error(`Capability "${key}" is already provided`);
99
+ }
100
+ map.set(key, value);
101
+ },
102
+ revoke(key) {
103
+ if (!map.has(key)) return;
104
+ map.delete(key);
105
+ },
106
+ get(key) {
107
+ if (!map.has(key)) {
108
+ throw new Error(`Capability "${key}" is not available`);
109
+ }
110
+ return map.get(key);
111
+ },
112
+ has(key) {
113
+ return map.has(key);
114
+ },
115
+ require(key) {
116
+ if (!map.has(key)) {
117
+ throw new Error(`Required capability "${key}" is missing`);
118
+ }
119
+ },
120
+ keys() {
121
+ return [...map.keys()];
122
+ }
123
+ };
124
+ }
125
+
126
+ // src/host/pluginGraph.ts
127
+ var PluginGraphValidationError = class extends Error {
128
+ code = "plugin.graph_invalid";
129
+ diagnostics;
130
+ constructor(diagnostics) {
131
+ super(diagnostics.map((diagnostic) => diagnostic.message).join("; "));
132
+ this.name = "PluginGraphValidationError";
133
+ this.diagnostics = [...diagnostics];
134
+ }
135
+ };
136
+ function unique(values) {
137
+ return [...new Set(values)];
138
+ }
139
+ function isPluginExecution(value) {
140
+ return typeof value === "string" && value.trim().length > 0;
141
+ }
142
+ function isPluginLifetime(value) {
143
+ return typeof value === "string" && value.trim().length > 0;
144
+ }
145
+ function isRecord(value) {
146
+ return typeof value === "object" && value !== null;
147
+ }
148
+ function validateRuntimeUnitDependencyContracts(manifests) {
149
+ const diagnostics = [];
150
+ for (const manifest of manifests) {
151
+ const units = manifest.units;
152
+ if (units === void 0) continue;
153
+ const productLevelFields = [
154
+ ["dependencies", manifest.dependencies],
155
+ ["permissions", manifest.permissions],
156
+ ["contribution", manifest.contribution],
157
+ ["config", manifest.config]
158
+ ];
159
+ for (const [field, value] of productLevelFields) {
160
+ if (value === void 0) continue;
161
+ diagnostics.push({
162
+ code: "plugin.runtime_declaration_at_product_level",
163
+ ids: [manifest.id, field],
164
+ message: `\u591A\u8FD0\u884C\u5355\u5143\u63D2\u4EF6 "${manifest.id}" \u7684 ${field} \u5FC5\u987B\u58F0\u660E\u5728\u5BF9\u5E94 RuntimeUnitDescriptor \u4E2D\uFF0C\u4E0D\u80FD\u4F7F\u7528\u4EA7\u54C1\u7EA7 fallback`
165
+ });
166
+ }
167
+ if ((manifest.provides?.length ?? 0) > 0) {
168
+ diagnostics.push({
169
+ code: "plugin.runtime_declaration_at_product_level",
170
+ ids: [manifest.id, "provides"],
171
+ message: `\u591A\u8FD0\u884C\u5355\u5143\u63D2\u4EF6 "${manifest.id}" \u7684 provides \u5FC5\u987B\u58F0\u660E\u5728\u5BF9\u5E94 RuntimeUnitDescriptor \u4E2D\uFF0C\u4E0D\u80FD\u4F7F\u7528\u4EA7\u54C1\u7EA7 capability \u6458\u8981`
172
+ });
173
+ }
174
+ if (!Array.isArray(units)) {
175
+ diagnostics.push({
176
+ code: "plugin.dependency_contract_invalid",
177
+ ids: [manifest.id, "units"],
178
+ message: `\u63D2\u4EF6 "${manifest.id}" \u7684 units \u5FC5\u987B\u662F\u8FD0\u884C\u5355\u5143\u6570\u7EC4`
179
+ });
180
+ continue;
181
+ }
182
+ units.forEach((unitValue, unitIndex) => {
183
+ if (!isRecord(unitValue)) {
184
+ diagnostics.push({
185
+ code: "plugin.dependency_contract_invalid",
186
+ ids: [manifest.id, `unit:${unitIndex}`],
187
+ message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u7B2C ${unitIndex + 1} \u4E2A\u8FD0\u884C\u5355\u5143\u63CF\u8FF0\u65E0\u6548`
188
+ });
189
+ return;
190
+ }
191
+ const unitId = typeof unitValue.id === "string" && unitValue.id.length > 0 ? unitValue.id : `unit:${unitIndex}`;
192
+ const providedContracts = unitValue.providedContracts;
193
+ if (providedContracts !== void 0) {
194
+ if (!isRecord(providedContracts)) {
195
+ diagnostics.push({
196
+ code: "plugin.dependency_contract_invalid",
197
+ ids: [manifest.id, unitId],
198
+ message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u8FD0\u884C\u5355\u5143 "${unitId}" \u7684 providedContracts\uFF08\u63D0\u4F9B\u5951\u7EA6\u7248\u672C\uFF09\u5FC5\u987B\u662F\u5BF9\u8C61`
199
+ });
200
+ } else {
201
+ for (const [capability, version] of Object.entries(providedContracts)) {
202
+ if (typeof version !== "string" || version.trim() === "") {
203
+ diagnostics.push({
204
+ code: "plugin.dependency_contract_invalid",
205
+ ids: [manifest.id, unitId, capability],
206
+ message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u8FD0\u884C\u5355\u5143 "${unitId}" \u4E3A\u80FD\u529B "${capability}" \u58F0\u660E\u7684\u5951\u7EA6\u7248\u672C\u4E0D\u80FD\u4E3A\u7A7A`
207
+ });
208
+ }
209
+ }
210
+ }
211
+ }
212
+ const dependencies = unitValue.dependencies;
213
+ if (dependencies === void 0) return;
214
+ if (!Array.isArray(dependencies)) {
215
+ diagnostics.push({
216
+ code: "plugin.dependency_contract_invalid",
217
+ ids: [manifest.id, unitId],
218
+ message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u8FD0\u884C\u5355\u5143 "${unitId}" \u7684 dependencies \u5FC5\u987B\u662F\u6570\u7EC4`
219
+ });
220
+ return;
221
+ }
222
+ dependencies.forEach((dependencyValue, dependencyIndex) => {
223
+ const dependency = isRecord(dependencyValue) ? dependencyValue : {};
224
+ const errors = [];
225
+ if (typeof dependency.capability !== "string" || dependency.capability.trim() === "") {
226
+ errors.push("capability\uFF08\u80FD\u529B\u6807\u8BC6\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
227
+ }
228
+ if (typeof dependency.contractVersion !== "string" || dependency.contractVersion.trim() === "") {
229
+ errors.push("contractVersion\uFF08\u5951\u7EA6\u7248\u672C\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
230
+ }
231
+ if (!isPluginExecution(dependency.sourceExecution)) {
232
+ errors.push("sourceExecution\uFF08\u63D0\u4F9B\u8005\u8FD0\u884C\u73AF\u5883\uFF09\u65E0\u6548");
233
+ }
234
+ if (!isPluginLifetime(dependency.scope)) {
235
+ errors.push("scope\uFF08\u63D0\u4F9B\u8005\u4F5C\u7528\u57DF\uFF09\u65E0\u6548");
236
+ }
237
+ if (dependency.reason !== void 0 && typeof dependency.reason !== "string") {
238
+ errors.push("reason\uFF08\u4F9D\u8D56\u8BF4\u660E\uFF09\u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
239
+ }
240
+ if (dependency.optional !== void 0 && typeof dependency.optional !== "boolean") {
241
+ errors.push("optional\uFF08\u662F\u5426\u53EF\u9009\uFF09\u5FC5\u987B\u662F\u5E03\u5C14\u503C");
242
+ }
243
+ if (errors.length === 0) return;
244
+ const capability = typeof dependency.capability === "string" && dependency.capability.length > 0 ? dependency.capability : `dependency:${dependencyIndex}`;
245
+ diagnostics.push({
246
+ code: "plugin.dependency_contract_invalid",
247
+ ids: [manifest.id, unitId, capability],
248
+ message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u8FD0\u884C\u5355\u5143 "${unitId}" \u7B2C ${dependencyIndex + 1} \u6761\u4F9D\u8D56\u5951\u7EA6\u65E0\u6548\uFF1A${errors.join("\u3001")}`
249
+ });
250
+ });
251
+ });
252
+ }
253
+ return diagnostics;
254
+ }
255
+ function selectedRuntimeUnits(manifest, execution) {
256
+ const units = manifest.units ?? [];
257
+ if (units.length === 0) return [];
258
+ if (execution !== void 0) return units.filter((unit) => unit.execution === execution);
259
+ return units.length === 1 ? [...units] : [];
260
+ }
261
+ function dependenciesOfManifest(manifest, execution) {
262
+ const byCapability = /* @__PURE__ */ new Map();
263
+ const add = (dependency) => {
264
+ const previous = byCapability.get(dependency.capability);
265
+ if (!previous || previous.optional === true && dependency.optional !== true) {
266
+ byCapability.set(dependency.capability, { ...dependency });
267
+ }
268
+ };
269
+ const units = manifest.units ?? [];
270
+ if (units.length > 1 && execution === void 0) return [];
271
+ const selectedUnits = selectedRuntimeUnits(manifest, execution);
272
+ if (units.length > 0 && selectedUnits.length === 0) return [];
273
+ if (units.length === 0) {
274
+ for (const dependency of manifest.dependencies ?? []) add(dependency);
275
+ } else {
276
+ for (const unit of selectedUnits) {
277
+ for (const dependency of unit.dependencies ?? []) add(dependency);
278
+ }
279
+ }
280
+ return [...byCapability.values()];
281
+ }
282
+ function providesOfManifest(manifest, execution) {
283
+ const units = manifest.units ?? [];
284
+ if (units.length > 1 && execution === void 0) return [];
285
+ return unique([
286
+ ...units.length === 0 ? manifest.provides ?? [] : [],
287
+ ...selectedRuntimeUnits(manifest, execution).flatMap((unit) => unit.provides ?? [])
288
+ ]);
289
+ }
290
+ function runtimeUnitProviders(manifests, capability) {
291
+ const providers = [];
292
+ for (const manifest of manifests) {
293
+ for (const unit of manifest.units ?? []) {
294
+ if (!unit.provides?.includes(capability)) continue;
295
+ providers.push({
296
+ pluginId: manifest.id,
297
+ unitId: unit.id,
298
+ execution: unit.execution,
299
+ lifetime: unit.lifetime,
300
+ contractVersion: unit.providedContracts?.[capability]
301
+ });
302
+ }
303
+ }
304
+ return providers;
305
+ }
306
+ function matchingRuntimeUnitProviders(manifests, dependency) {
307
+ return runtimeUnitProviders(manifests, dependency.capability).filter(
308
+ (provider) => provider.execution === dependency.sourceExecution && provider.lifetime === dependency.scope && provider.contractVersion === dependency.contractVersion
309
+ );
310
+ }
311
+ function runtimeDependenciesForValidation(manifest, execution) {
312
+ const units = manifest.units ?? [];
313
+ const selected = execution === void 0 ? units : units.filter((unit) => unit.execution === execution);
314
+ return selected.flatMap((unit) => unit.dependencies ?? []);
315
+ }
316
+ function buildPluginGraph(manifests, options = {}) {
317
+ const provides = {};
318
+ const dependencies = {};
319
+ const optionalDependencies = {};
320
+ const dependencyDetails = {};
321
+ const providers = {};
322
+ const unitGraph = {};
323
+ const enabled = options.enabledPluginIds;
324
+ for (const manifest of manifests) {
325
+ const selectedUnits = selectedRuntimeUnits(manifest, options.execution);
326
+ const provided = providesOfManifest(manifest, options.execution);
327
+ const dependencyEntries = dependenciesOfManifest(manifest, options.execution);
328
+ dependencyDetails[manifest.id] = dependencyEntries.map((dependency) => ({ ...dependency }));
329
+ const deps = unique(dependencyEntries.map((dependency) => dependency.capability));
330
+ optionalDependencies[manifest.id] = unique(
331
+ dependencyEntries.filter((dependency) => dependency.optional).map((dependency) => dependency.capability)
332
+ );
333
+ provides[manifest.id] = provided;
334
+ dependencies[manifest.id] = deps;
335
+ for (const unit of selectedUnits) {
336
+ const unitKey = `${manifest.id}:${unit.id}`;
337
+ unitGraph[unitKey] = {
338
+ pluginId: manifest.id,
339
+ unitId: unit.id,
340
+ execution: unit.execution,
341
+ dependencies: unique((unit.dependencies ?? []).map((dependency) => dependency.capability)),
342
+ dependencyDetails: (unit.dependencies ?? []).map((dependency) => ({ ...dependency })),
343
+ provides: unique(unit.provides ?? []),
344
+ providedContracts: unit.providedContracts ? { ...unit.providedContracts } : void 0
345
+ };
346
+ }
347
+ for (const capability of provided) {
348
+ (providers[capability] ??= []).push(manifest.id);
349
+ }
350
+ }
351
+ const providerByCapability = /* @__PURE__ */ new Map();
352
+ for (const [capability, pluginIds] of Object.entries(providers)) {
353
+ providerByCapability.set(capability, pluginIds);
354
+ }
355
+ const reverse = {};
356
+ for (const manifest of manifests) {
357
+ const dependentEnabled = enabled?.has(manifest.id) ?? manifest.meta.defaultEnabled;
358
+ for (const capability of dependencies[manifest.id] ?? []) {
359
+ if (optionalDependencies[manifest.id]?.includes(capability)) continue;
360
+ for (const providerId of providerByCapability.get(capability) ?? []) {
361
+ if (providerId === manifest.id) continue;
362
+ const entries = reverse[providerId] ??= [];
363
+ let entry = entries.find((item) => item.pluginId === manifest.id);
364
+ if (!entry) {
365
+ entry = { pluginId: manifest.id, enabled: dependentEnabled, capabilities: [] };
366
+ entries.push(entry);
367
+ }
368
+ if (!entry.capabilities.includes(capability)) entry.capabilities.push(capability);
369
+ }
370
+ }
371
+ }
372
+ const cycles = [];
373
+ const firstProvider = /* @__PURE__ */ new Map();
374
+ for (const [capability, pluginIds] of providerByCapability) {
375
+ if (pluginIds[0]) firstProvider.set(capability, pluginIds[0]);
376
+ }
377
+ const visited = /* @__PURE__ */ new Set();
378
+ const path = [];
379
+ const visit = (pluginId) => {
380
+ if (visited.has(pluginId)) return;
381
+ const position = path.indexOf(pluginId);
382
+ if (position >= 0) {
383
+ const cycle = [...path.slice(position), pluginId];
384
+ if (!cycles.some((item) => item.join("\0") === cycle.join("\0"))) cycles.push(cycle);
385
+ return;
386
+ }
387
+ path.push(pluginId);
388
+ const manifest = manifests.find((item) => item.id === pluginId);
389
+ if (!manifest) {
390
+ path.pop();
391
+ visited.add(pluginId);
392
+ return;
393
+ }
394
+ for (const dependency of dependenciesOfManifest(manifest, options.execution)) {
395
+ if (dependency.optional) continue;
396
+ const provider = firstProvider.get(dependency.capability);
397
+ if (provider) visit(provider);
398
+ }
399
+ path.pop();
400
+ visited.add(pluginId);
401
+ };
402
+ for (const manifest of manifests) visit(manifest.id);
403
+ return {
404
+ plugins: manifests.map((manifest) => manifest.id),
405
+ dependencies,
406
+ optionalDependencies,
407
+ provides,
408
+ reverse,
409
+ providers,
410
+ dependencyDetails,
411
+ cycles,
412
+ units: unitGraph
413
+ };
414
+ }
415
+ function validatePluginGraph(manifests, options = {}) {
416
+ const diagnostics = [];
417
+ diagnostics.push(...validateRuntimeUnitDependencyContracts(manifests));
418
+ const ids = /* @__PURE__ */ new Set();
419
+ for (const manifest of manifests) {
420
+ if (ids.has(manifest.id)) {
421
+ diagnostics.push({
422
+ code: "plugin.duplicate_id",
423
+ ids: [manifest.id],
424
+ message: `\u63D2\u4EF6\u6807\u8BC6 "${manifest.id}" \u91CD\u590D\uFF0C\u65E0\u6CD5\u786E\u5B9A\u552F\u4E00\u8FD0\u884C\u5B9E\u4F8B`
425
+ });
426
+ }
427
+ ids.add(manifest.id);
428
+ }
429
+ const graph = buildPluginGraph([...manifests], options);
430
+ const builtins = options.builtinCapabilities ?? /* @__PURE__ */ new Set();
431
+ const multiProvider = options.multiProviderCapabilities ?? /* @__PURE__ */ new Set();
432
+ for (const [capability, pluginIds] of Object.entries(graph.providers ?? {})) {
433
+ if (pluginIds.length > 1 && !multiProvider.has(capability)) {
434
+ diagnostics.push({
435
+ code: "capability.duplicate_provider",
436
+ ids: [capability, ...pluginIds],
437
+ message: `\u80FD\u529B "${capability}" \u6709\u591A\u4E2A\u63D0\u4F9B\u8005\uFF08${pluginIds.join(", ")}\uFF09\uFF0C\u4F46\u672A\u58F0\u660E\u4E13\u7528 Provider Registry`
438
+ });
439
+ }
440
+ }
441
+ for (const manifest of manifests) {
442
+ const strictDependencies = runtimeDependenciesForValidation(manifest, options.execution);
443
+ for (const dependency of strictDependencies) {
444
+ if (dependency.optional) continue;
445
+ const matches = matchingRuntimeUnitProviders(manifests, dependency);
446
+ if (matches.length > 0) {
447
+ if (matches.length > 1 && !multiProvider.has(dependency.capability)) {
448
+ diagnostics.push({
449
+ code: "capability.duplicate_provider",
450
+ ids: [dependency.capability, ...matches.map((provider) => `${provider.pluginId}:${provider.unitId}`)],
451
+ message: `\u80FD\u529B "${dependency.capability}" \u7684\u8FD0\u884C\u5355\u5143\u5951\u7EA6\u6709\u591A\u4E2A\u5339\u914D Provider\uFF08${matches.map((provider) => `${provider.pluginId}:${provider.unitId}`).join(", ")}\uFF09\uFF0C\u4F46\u672A\u58F0\u660E\u4E13\u7528 Provider Registry`
452
+ });
453
+ }
454
+ continue;
455
+ }
456
+ const candidates = runtimeUnitProviders(manifests, dependency.capability);
457
+ if (builtins.has(dependency.capability) && candidates.length === 0) {
458
+ continue;
459
+ }
460
+ if (candidates.length > 0) {
461
+ diagnostics.push({
462
+ code: "plugin.dependency_contract_unavailable",
463
+ ids: [manifest.id, dependency.capability],
464
+ message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u8FD0\u884C\u5355\u5143\u4F9D\u8D56 "${dependency.capability}" \u6CA1\u6709\u5339\u914D\u7684\u5951\u7EA6\u7248\u672C\u3001\u6765\u6E90\u73AF\u5883\u6216\u4F5C\u7528\u57DF`
465
+ });
466
+ } else if (!options.allowMissingDependencies) {
467
+ diagnostics.push({
468
+ code: "plugin.missing_dependency",
469
+ ids: [manifest.id, dependency.capability],
470
+ message: `\u63D2\u4EF6 "${manifest.id}" \u7F3A\u5C11\u786C\u4F9D\u8D56\u80FD\u529B "${dependency.capability}"`
471
+ });
472
+ }
473
+ }
474
+ }
475
+ for (const manifest of manifests) {
476
+ const strictDependencyCapabilities = new Set(
477
+ runtimeDependenciesForValidation(manifest, options.execution).map((dependency) => dependency.capability)
478
+ );
479
+ for (const dependency of dependenciesOfManifest(manifest, options.execution)) {
480
+ if (dependency.optional) continue;
481
+ if (strictDependencyCapabilities.has(dependency.capability)) continue;
482
+ const provided = (graph.providers?.[dependency.capability]?.length ?? 0) > 0 || builtins.has(dependency.capability);
483
+ if (!provided && !options.allowMissingDependencies) {
484
+ diagnostics.push({
485
+ code: "plugin.missing_dependency",
486
+ ids: [manifest.id, dependency.capability],
487
+ message: `\u63D2\u4EF6 "${manifest.id}" \u7F3A\u5C11\u786C\u4F9D\u8D56\u80FD\u529B "${dependency.capability}"`
488
+ });
489
+ }
490
+ }
491
+ }
492
+ for (const cycle of graph.cycles ?? []) {
493
+ diagnostics.push({
494
+ code: "plugin.dependency_cycle",
495
+ ids: cycle,
496
+ message: `\u63D2\u4EF6\u5B58\u5728\u786C\u4F9D\u8D56\u73AF\uFF1A${cycle.join(" -> ")}`
497
+ });
498
+ }
499
+ if (diagnostics.length > 0) throw new PluginGraphValidationError(diagnostics);
500
+ return graph;
501
+ }
502
+ function reverseDependentsOf(graph, pluginId, enabledSet) {
503
+ return (graph.reverse[pluginId] ?? []).filter((dependent) => enabledSet.has(dependent.pluginId));
504
+ }
505
+
506
+ // src/messaging/messageBus.ts
507
+ function makeMessageId() {
508
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
509
+ return crypto.randomUUID();
510
+ }
511
+ return `m_${Date.now().toString(36)}_${Math.floor(Math.random() * 1e9).toString(36)}`;
512
+ }
513
+ function errorMessage(err) {
514
+ if (err instanceof Error) return err.message;
515
+ if (typeof err === "string") return err;
516
+ return String(err);
517
+ }
518
+ var MAX_HANDLER_CONCURRENCY = 128;
519
+ function waitForHandler(result, signal) {
520
+ if (signal.aborted) {
521
+ return Promise.reject(signal.reason ?? new Error("aborted"));
522
+ }
523
+ return new Promise((resolve, reject) => {
524
+ const onAbort = () => {
525
+ reject(signal.reason ?? new Error("aborted"));
526
+ };
527
+ signal.addEventListener("abort", onAbort, { once: true });
528
+ result.then(resolve, reject).finally(() => {
529
+ signal.removeEventListener("abort", onAbort);
530
+ });
531
+ });
532
+ }
533
+ function createMessageBus() {
534
+ const subscriptions = /* @__PURE__ */ new Map();
535
+ const routedHandlers = /* @__PURE__ */ new Map();
536
+ const mailboxes = /* @__PURE__ */ new Map();
537
+ const targetHandlerCount = /* @__PURE__ */ new Map();
538
+ const targetConcurrency = /* @__PURE__ */ new Map();
539
+ const snapshotListeners = /* @__PURE__ */ new Set();
540
+ let total = 0;
541
+ let completed = 0;
542
+ let failed = 0;
543
+ let canceled = 0;
544
+ let inFlight = 0;
545
+ let lastError;
546
+ const pumping = /* @__PURE__ */ new Set();
547
+ function emitSnapshot() {
548
+ const snap = snapshot();
549
+ for (const l of snapshotListeners) l(snap);
550
+ }
551
+ function snapshot() {
552
+ const byTarget = {};
553
+ let queued = 0;
554
+ for (const [target, queue] of mailboxes.entries()) {
555
+ byTarget[target] = queue.length;
556
+ queued += queue.length;
557
+ }
558
+ return {
559
+ total,
560
+ queued,
561
+ inFlight,
562
+ completed,
563
+ failed,
564
+ canceled,
565
+ lastError,
566
+ byTarget
567
+ };
568
+ }
569
+ function makeMessage(type, mode, payload, options) {
570
+ return {
571
+ id: options.messageId ?? makeMessageId(),
572
+ type,
573
+ mode,
574
+ payload,
575
+ target: options.target,
576
+ priority: options.priority,
577
+ timeoutMs: options.timeoutMs,
578
+ causationId: options.causationId,
579
+ createdAt: Date.now()
580
+ };
581
+ }
582
+ function publishInternal(message) {
583
+ total += 1;
584
+ const routed = routedHandlers.get(message.type);
585
+ if (routed && !routed.target) {
586
+ try {
587
+ const result = routed.handler(message);
588
+ if (result && typeof result.then === "function") {
589
+ result.catch((err) => {
590
+ failed += 1;
591
+ lastError = errorMessage(err);
592
+ emitSnapshot();
593
+ });
594
+ }
595
+ } catch (err) {
596
+ failed += 1;
597
+ lastError = errorMessage(err);
598
+ }
599
+ }
600
+ const bucket = subscriptions.get(message.type);
601
+ if (bucket) {
602
+ for (const sub of [...bucket]) {
603
+ try {
604
+ sub.handler(message.payload);
605
+ } catch (err) {
606
+ failed += 1;
607
+ lastError = errorMessage(err);
608
+ }
609
+ }
610
+ }
611
+ emitSnapshot();
612
+ return message.id;
613
+ }
614
+ function settleEntry(entry, state, value) {
615
+ if (entry.settled) return;
616
+ entry.settled = true;
617
+ const wasRunning = entry.state === "running";
618
+ entry.state = state;
619
+ entry.cleanup();
620
+ if (state === "completed") {
621
+ completed += 1;
622
+ } else if (state === "failed") {
623
+ failed += 1;
624
+ lastError = errorMessage(value);
625
+ } else {
626
+ canceled += 1;
627
+ lastError = errorMessage(value ?? entry.signal.reason);
628
+ }
629
+ if (wasRunning) inFlight -= 1;
630
+ emitSnapshot();
631
+ try {
632
+ entry.onSettled?.();
633
+ } catch (err) {
634
+ lastError = errorMessage(err);
635
+ }
636
+ if (entry.mode === "request") {
637
+ if (state === "completed") entry.resolve(value);
638
+ else entry.reject(value);
639
+ }
640
+ }
641
+ function enqueueMessage(message, mode, signal, onSettled) {
642
+ total += 1;
643
+ const target = message.target;
644
+ if (!target) {
645
+ failed += 1;
646
+ lastError = `MessageBus.${mode === "request" ? "request" : "dispatch"} requires a target`;
647
+ emitSnapshot();
648
+ try {
649
+ onSettled?.();
650
+ } catch {
651
+ }
652
+ return mode === "request" ? Promise.reject(new Error(lastError)) : message.id;
653
+ }
654
+ const handler = routedHandlers.get(message.type);
655
+ if (!handler || handler.target !== target) {
656
+ failed += 1;
657
+ lastError = `No handler registered for type "${message.type}" at target "${target}"`;
658
+ emitSnapshot();
659
+ try {
660
+ onSettled?.();
661
+ } catch {
662
+ }
663
+ return mode === "request" ? Promise.reject(new Error(lastError)) : message.id;
664
+ }
665
+ if (signal?.aborted) {
666
+ canceled += 1;
667
+ lastError = errorMessage(signal.reason ?? new Error("aborted"));
668
+ emitSnapshot();
669
+ try {
670
+ onSettled?.();
671
+ } catch {
672
+ }
673
+ return mode === "request" ? Promise.reject(signal.reason ?? new Error("aborted")) : message.id;
674
+ }
675
+ const ctl = new AbortController();
676
+ let timeoutHandle;
677
+ const onUpstreamAbort = () => {
678
+ ctl.abort(signal?.reason ?? new Error("aborted"));
679
+ };
680
+ if (signal) {
681
+ signal.addEventListener("abort", onUpstreamAbort, { once: true });
682
+ }
683
+ if (typeof message.timeoutMs === "number" && message.timeoutMs > 0) {
684
+ timeoutHandle = setTimeout(() => {
685
+ ctl.abort(new Error("MessageBus.request timeout"));
686
+ }, message.timeoutMs);
687
+ }
688
+ const stamped = { ...message, signal: ctl.signal };
689
+ const mailbox = mailboxes.get(target) ?? [];
690
+ mailboxes.set(target, mailbox);
691
+ return new Promise((resolve, reject) => {
692
+ const entry = {
693
+ message: stamped,
694
+ signal: ctl.signal,
695
+ mode,
696
+ state: "queued",
697
+ settled: false,
698
+ resolve: (v) => resolve(v),
699
+ reject: (e) => reject(e),
700
+ onSettled,
701
+ cleanup: () => {
702
+ if (timeoutHandle) clearTimeout(timeoutHandle);
703
+ if (signal) signal.removeEventListener("abort", onUpstreamAbort);
704
+ ctl.signal.removeEventListener("abort", onAbort);
705
+ }
706
+ };
707
+ function onAbort() {
708
+ if (entry.state === "queued") {
709
+ const idx = mailbox.indexOf(entry);
710
+ if (idx >= 0) {
711
+ mailbox.splice(idx, 1);
712
+ }
713
+ }
714
+ settleEntry(entry, "canceled", ctl.signal.reason ?? new Error("aborted"));
715
+ }
716
+ ctl.signal.addEventListener("abort", onAbort, { once: true });
717
+ mailbox.push(entry);
718
+ emitSnapshot();
719
+ schedulePump(target);
720
+ });
721
+ }
722
+ function schedulePump(target) {
723
+ if (pumping.has(target)) return;
724
+ const mailbox = mailboxes.get(target);
725
+ if (!mailbox || mailbox.length === 0) return;
726
+ pumping.add(target);
727
+ if (typeof queueMicrotask === "function") {
728
+ queueMicrotask(() => {
729
+ void runPump(target);
730
+ });
731
+ } else {
732
+ Promise.resolve().then(() => {
733
+ void runPump(target);
734
+ });
735
+ }
736
+ }
737
+ async function runPump(target) {
738
+ const concurrency = targetConcurrency.get(target) ?? 1;
739
+ const workers = [];
740
+ for (let i = 0; i < concurrency; i += 1) {
741
+ workers.push(workerLoop(target));
742
+ }
743
+ try {
744
+ await Promise.all(workers);
745
+ } finally {
746
+ pumping.delete(target);
747
+ const mailbox = mailboxes.get(target);
748
+ if (mailbox && mailbox.length > 0) {
749
+ schedulePump(target);
750
+ }
751
+ emitSnapshot();
752
+ }
753
+ }
754
+ async function workerLoop(target, workerId) {
755
+ while (true) {
756
+ const entry = pickBestEntry(target);
757
+ if (!entry) return;
758
+ await processEntry(target, entry);
759
+ }
760
+ }
761
+ function pickBestEntry(target) {
762
+ const mailbox = mailboxes.get(target);
763
+ if (!mailbox || mailbox.length === 0) return void 0;
764
+ let bestIdx = -1;
765
+ let bestEntry;
766
+ for (let i = 0; i < mailbox.length; i += 1) {
767
+ const cur = mailbox[i];
768
+ if (cur.settled) continue;
769
+ if (!bestEntry) {
770
+ bestEntry = cur;
771
+ bestIdx = i;
772
+ continue;
773
+ }
774
+ const bestPriority = bestEntry.message.priority ?? 0;
775
+ const curPriority = cur.message.priority ?? 0;
776
+ if (curPriority > bestPriority || curPriority === bestPriority && cur.message.createdAt < bestEntry.message.createdAt) {
777
+ bestEntry = cur;
778
+ bestIdx = i;
779
+ }
780
+ }
781
+ if (bestEntry && bestIdx >= 0) {
782
+ mailbox.splice(bestIdx, 1);
783
+ }
784
+ return bestEntry;
785
+ }
786
+ async function processEntry(target, entry) {
787
+ if (entry.settled) return;
788
+ entry.state = "running";
789
+ inFlight += 1;
790
+ emitSnapshot();
791
+ const handler = routedHandlers.get(entry.message.type);
792
+ if (!handler) {
793
+ settleEntry(entry, "failed", new Error(`No handler for type "${entry.message.type}"`));
794
+ return;
795
+ }
796
+ try {
797
+ const result = handler.handler(entry.message);
798
+ if (result && typeof result.then === "function") {
799
+ const v = await waitForHandler(
800
+ result,
801
+ entry.signal
802
+ );
803
+ if (entry.signal.aborted) {
804
+ settleEntry(entry, "canceled", entry.signal.reason);
805
+ } else {
806
+ settleEntry(entry, "completed", v);
807
+ }
808
+ } else {
809
+ if (entry.signal.aborted) {
810
+ settleEntry(entry, "canceled", entry.signal.reason);
811
+ } else {
812
+ settleEntry(entry, "completed", result);
813
+ }
814
+ }
815
+ } catch (err) {
816
+ if (entry.signal.aborted) {
817
+ settleEntry(entry, "canceled", entry.signal.reason ?? err);
818
+ } else {
819
+ settleEntry(entry, "failed", err);
820
+ }
821
+ }
822
+ }
823
+ const bus = {
824
+ publish(type, payload, options) {
825
+ const message = makeMessage(type, "event", payload, {
826
+ causationId: options?.causationId,
827
+ messageId: options?.messageId
828
+ });
829
+ return publishInternal(message);
830
+ },
831
+ subscribe(type, handler) {
832
+ const record = { type, handler };
833
+ let bucket = subscriptions.get(type);
834
+ if (!bucket) {
835
+ bucket = /* @__PURE__ */ new Set();
836
+ subscriptions.set(type, bucket);
837
+ }
838
+ bucket.add(record);
839
+ return () => {
840
+ bucket?.delete(record);
841
+ };
842
+ },
843
+ dispatch(type, payload, options) {
844
+ const message = makeMessage(type, "command", payload, {
845
+ target: options.target,
846
+ priority: options.priority,
847
+ timeoutMs: options.timeoutMs,
848
+ causationId: options.causationId,
849
+ messageId: options.messageId
850
+ });
851
+ const result = enqueueMessage(message, "command", options.signal, options.onSettled);
852
+ if (typeof result === "string") return result;
853
+ return message.id;
854
+ },
855
+ request(type, payload, options) {
856
+ const message = makeMessage(type, "request", payload, {
857
+ target: options.target,
858
+ priority: options.priority,
859
+ timeoutMs: options.timeoutMs,
860
+ causationId: options.causationId,
861
+ messageId: options.messageId
862
+ });
863
+ const result = enqueueMessage(message, "request", options.signal, options.onSettled);
864
+ if (typeof result === "string") {
865
+ return Promise.reject(new Error("MessageBus.request returned a string id unexpectedly"));
866
+ }
867
+ return result;
868
+ },
869
+ handle(type, handler, options) {
870
+ const concurrency = options?.concurrency ?? 1;
871
+ if (!Number.isFinite(concurrency) || !Number.isInteger(concurrency) || concurrency <= 0) {
872
+ throw new Error("Handler concurrency must be a positive integer");
873
+ }
874
+ if (concurrency > MAX_HANDLER_CONCURRENCY) {
875
+ throw new Error(
876
+ `Handler concurrency must not exceed ${MAX_HANDLER_CONCURRENCY}`
877
+ );
878
+ }
879
+ const record = {
880
+ type,
881
+ target: options?.target ?? "",
882
+ priority: options?.priority ?? 0,
883
+ concurrency,
884
+ handler
885
+ };
886
+ if (routedHandlers.has(type)) {
887
+ throw new Error(`Handler for "${type}" is already registered`);
888
+ }
889
+ if (record.target) {
890
+ const existing = targetConcurrency.get(record.target);
891
+ if (existing !== void 0 && existing !== concurrency) {
892
+ throw new Error(`Conflicting concurrency for target "${record.target}"`);
893
+ }
894
+ targetConcurrency.set(record.target, concurrency);
895
+ targetHandlerCount.set(record.target, (targetHandlerCount.get(record.target) ?? 0) + 1);
896
+ if (!mailboxes.has(record.target)) {
897
+ mailboxes.set(record.target, []);
898
+ }
899
+ schedulePump(record.target);
900
+ }
901
+ routedHandlers.set(type, record);
902
+ return () => {
903
+ if (routedHandlers.get(type) === record) {
904
+ routedHandlers.delete(type);
905
+ if (record.target) {
906
+ const count = (targetHandlerCount.get(record.target) ?? 1) - 1;
907
+ if (count <= 0) {
908
+ targetHandlerCount.delete(record.target);
909
+ targetConcurrency.delete(record.target);
910
+ } else {
911
+ targetHandlerCount.set(record.target, count);
912
+ }
913
+ }
914
+ }
915
+ };
916
+ },
917
+ snapshot,
918
+ onSnapshot(handler) {
919
+ snapshotListeners.add(handler);
920
+ handler(snapshot());
921
+ return () => snapshotListeners.delete(handler);
922
+ }
923
+ };
924
+ return bus;
925
+ }
926
+
927
+ // src/lifecycle/resourceScope.ts
928
+ function makeId(prefix) {
929
+ try {
930
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
931
+ return `${prefix}:${crypto.randomUUID()}`;
932
+ }
933
+ } catch {
934
+ }
935
+ return `${prefix}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
936
+ }
937
+ function errorMessage2(error) {
938
+ if (error instanceof Error) return error.message;
939
+ return typeof error === "string" ? error : String(error);
940
+ }
941
+ function isPositiveFiniteNumber(value) {
942
+ return value !== void 0 && Number.isFinite(value) && value >= 0;
943
+ }
944
+ function createLifecycleScope(options) {
945
+ const identity = {
946
+ scopeId: options.scopeId ?? makeId(`scope:${options.kind}`),
947
+ instanceId: options.instanceId ?? makeId("instance"),
948
+ kind: options.kind,
949
+ attributes: {},
950
+ ...options.metadata
951
+ };
952
+ const controller = new AbortController();
953
+ const revokeListeners = /* @__PURE__ */ new Set();
954
+ const entries = /* @__PURE__ */ new Map();
955
+ const usedIds = /* @__PURE__ */ new Set();
956
+ let currentState = "active";
957
+ let revokeReason = "scope revoked";
958
+ let disposalPromise;
959
+ let refreshPublishedDisposeResult;
960
+ const disposeResultNotifier = options.onDisposeResult;
961
+ const notifyChange = (scope) => {
962
+ try {
963
+ options.onChange?.(scope);
964
+ } catch {
965
+ }
966
+ };
967
+ const publicScope = {};
968
+ function uniqueResourceId(resourceId) {
969
+ const base = resourceId && resourceId.length > 0 ? resourceId : makeId("resource");
970
+ if (!usedIds.has(base)) {
971
+ usedIds.add(base);
972
+ return base;
973
+ }
974
+ let index = 2;
975
+ while (usedIds.has(`${base}#${index}`)) index += 1;
976
+ const unique2 = `${base}#${index}`;
977
+ usedIds.add(unique2);
978
+ return unique2;
979
+ }
980
+ function assertActive() {
981
+ if (currentState !== "active") {
982
+ throw new LifecycleScopeRevokedError(
983
+ `Lifecycle scope "${identity.scopeId}" is ${currentState}`
984
+ );
985
+ }
986
+ }
987
+ function addEntry(resourceId, cleanup, state, removeOnFailure = false, phase = "before-teardown") {
988
+ assertActive();
989
+ const entry = {
990
+ resourceId: uniqueResourceId(resourceId),
991
+ state,
992
+ cleanup,
993
+ phase,
994
+ cleanupStarted: false,
995
+ cleanupFinished: false,
996
+ removeOnFailure
997
+ };
998
+ entries.set(entry.resourceId, entry);
999
+ notifyChange(publicScope);
1000
+ return entry;
1001
+ }
1002
+ function releaseEntry(entry, reason) {
1003
+ if (entry.cleanupPromise) return entry.cleanupPromise;
1004
+ entry.cleanupStarted = true;
1005
+ entry.cleanupPromise = Promise.resolve().then(() => entry.cleanup(reason)).then(() => {
1006
+ entry.cleanupFinished = true;
1007
+ if (!entry.lateReleaseFailed) {
1008
+ entry.state = "released";
1009
+ entry.error = void 0;
1010
+ }
1011
+ notifyChange(publicScope);
1012
+ }).catch((error) => {
1013
+ entry.cleanupFinished = true;
1014
+ entry.state = "pending";
1015
+ entry.error = errorMessage2(error);
1016
+ notifyChange(publicScope);
1017
+ throw error;
1018
+ });
1019
+ entry.cleanupPromise.catch(() => void 0);
1020
+ return entry.cleanupPromise;
1021
+ }
1022
+ function track(resource, release, resourceId) {
1023
+ if (currentState !== "active") {
1024
+ void Promise.resolve().then(() => release(resource, revokeReason)).catch(() => void 0);
1025
+ throw new LifecycleScopeRevokedError();
1026
+ }
1027
+ let released = false;
1028
+ addEntry(
1029
+ resourceId,
1030
+ async (reason) => {
1031
+ if (released) return;
1032
+ released = true;
1033
+ await release(resource, reason);
1034
+ },
1035
+ "active"
1036
+ );
1037
+ return resource;
1038
+ }
1039
+ function acquire(resourceId, create, release) {
1040
+ assertActive();
1041
+ let resource;
1042
+ let hasResource = false;
1043
+ let released = false;
1044
+ let resolveAcquisition;
1045
+ let rejectAcquisition;
1046
+ let acquisitionSettled = false;
1047
+ const acquisitionDone = new Promise((resolve, reject) => {
1048
+ resolveAcquisition = () => {
1049
+ if (acquisitionSettled) return;
1050
+ acquisitionSettled = true;
1051
+ resolve();
1052
+ };
1053
+ rejectAcquisition = (error) => {
1054
+ if (acquisitionSettled) return;
1055
+ acquisitionSettled = true;
1056
+ reject(error);
1057
+ };
1058
+ });
1059
+ acquisitionDone.catch(() => void 0);
1060
+ const entry = addEntry(
1061
+ resourceId,
1062
+ async (reason) => {
1063
+ await acquisitionDone;
1064
+ if (!hasResource || released) return;
1065
+ released = true;
1066
+ await release(resource, reason);
1067
+ },
1068
+ "acquiring",
1069
+ true,
1070
+ "before-teardown"
1071
+ );
1072
+ entry.acquisitionDone = acquisitionDone;
1073
+ return Promise.resolve().then(() => create(controller.signal)).then(async (created) => {
1074
+ resource = created;
1075
+ hasResource = true;
1076
+ if (currentState !== "active" || controller.signal.aborted || entry.cleanupStarted) {
1077
+ if (!released) {
1078
+ released = true;
1079
+ try {
1080
+ await release(created, revokeReason);
1081
+ } catch (error) {
1082
+ entry.lateReleaseFailed = true;
1083
+ entry.state = "pending";
1084
+ entry.error = errorMessage2(error);
1085
+ notifyChange(publicScope);
1086
+ rejectAcquisition(error);
1087
+ throw error;
1088
+ }
1089
+ }
1090
+ entry.state = "released";
1091
+ resolveAcquisition();
1092
+ notifyChange(publicScope);
1093
+ throw new LifecycleScopeRevokedError(
1094
+ `Resource "${entry.resourceId}" completed after scope revoke`
1095
+ );
1096
+ }
1097
+ entry.state = "active";
1098
+ entry.removeOnFailure = false;
1099
+ resolveAcquisition();
1100
+ notifyChange(publicScope);
1101
+ return created;
1102
+ }).catch((error) => {
1103
+ if (!hasResource) {
1104
+ resolveAcquisition();
1105
+ entries.delete(entry.resourceId);
1106
+ usedIds.delete(entry.resourceId);
1107
+ }
1108
+ throw error;
1109
+ });
1110
+ }
1111
+ function onDispose(cleanup, resourceId, phase = "before-teardown") {
1112
+ if (currentState !== "active") {
1113
+ void Promise.resolve().then(() => cleanup(revokeReason)).catch(() => void 0);
1114
+ return () => void 0;
1115
+ }
1116
+ const entry = addEntry(resourceId, cleanup, "active", false, phase);
1117
+ return () => {
1118
+ if (entry.cleanupStarted || entry.cleanupFinished) return;
1119
+ entries.delete(entry.resourceId);
1120
+ usedIds.delete(entry.resourceId);
1121
+ notifyChange(publicScope);
1122
+ };
1123
+ }
1124
+ function onRevoke(listener) {
1125
+ if (currentState === "active") {
1126
+ revokeListeners.add(listener);
1127
+ return () => revokeListeners.delete(listener);
1128
+ }
1129
+ try {
1130
+ listener(revokeReason);
1131
+ } catch {
1132
+ }
1133
+ return () => void 0;
1134
+ }
1135
+ function revoke(reason = "scope revoked") {
1136
+ if (currentState !== "active") return;
1137
+ revokeReason = reason;
1138
+ currentState = "stopping";
1139
+ try {
1140
+ controller.abort(new LifecycleScopeRevokedError(reason));
1141
+ } catch {
1142
+ controller.abort();
1143
+ }
1144
+ for (const listener of [...revokeListeners]) {
1145
+ try {
1146
+ listener(reason);
1147
+ } catch {
1148
+ }
1149
+ }
1150
+ notifyChange(publicScope);
1151
+ }
1152
+ async function runEntry(entry, reason, timeoutMs, issues, onLateSuccess, onLateFailure) {
1153
+ if (entry.cleanupFinished && entry.state === "released") return "released";
1154
+ const cleanup = releaseEntry(entry, reason);
1155
+ if (!isPositiveFiniteNumber(timeoutMs)) {
1156
+ try {
1157
+ await cleanup;
1158
+ return entry.state === "released" ? "released" : "pending";
1159
+ } catch (error) {
1160
+ issues.push({
1161
+ resourceId: entry.resourceId,
1162
+ code: "lifecycle.cleanup_failed",
1163
+ message: errorMessage2(error)
1164
+ });
1165
+ return "pending";
1166
+ }
1167
+ }
1168
+ let timeoutHandle;
1169
+ const timeout = new Promise((resolve) => {
1170
+ timeoutHandle = setTimeout(() => resolve("timeout"), timeoutMs);
1171
+ });
1172
+ const result = await Promise.race([
1173
+ cleanup.then(() => "released", (error) => ({ error })),
1174
+ timeout
1175
+ ]);
1176
+ if (timeoutHandle) clearTimeout(timeoutHandle);
1177
+ if (result === "timeout") {
1178
+ entry.state = "pending";
1179
+ issues.push({
1180
+ resourceId: entry.resourceId,
1181
+ code: "lifecycle.cleanup_timeout",
1182
+ message: `Cleanup timed out after ${timeoutMs}ms`
1183
+ });
1184
+ void cleanup.then(
1185
+ () => {
1186
+ try {
1187
+ onLateSuccess?.(entry);
1188
+ } catch {
1189
+ }
1190
+ },
1191
+ (error) => {
1192
+ try {
1193
+ onLateFailure?.(entry, error);
1194
+ } catch {
1195
+ }
1196
+ }
1197
+ );
1198
+ return "pending";
1199
+ }
1200
+ if (result === "released") return "released";
1201
+ entry.state = "pending";
1202
+ issues.push({
1203
+ resourceId: entry.resourceId,
1204
+ code: "lifecycle.cleanup_failed",
1205
+ message: errorMessage2(result.error)
1206
+ });
1207
+ return "pending";
1208
+ }
1209
+ async function dispose(options2 = {}) {
1210
+ if (disposalPromise) return disposalPromise;
1211
+ revoke(options2.reason ?? "scope disposed");
1212
+ const reason = options2.reason ?? revokeReason;
1213
+ const entriesToRelease = [...entries.values()].reverse();
1214
+ disposalPromise = (async () => {
1215
+ const errors = [];
1216
+ const pending = /* @__PURE__ */ new Set();
1217
+ const lateReleased = /* @__PURE__ */ new Set();
1218
+ let released = 0;
1219
+ let attempted = 0;
1220
+ let resultSnapshot;
1221
+ let resultPublished = false;
1222
+ const publishDisposeResult = () => {
1223
+ if (!resultPublished || !resultSnapshot) return;
1224
+ try {
1225
+ disposeResultNotifier?.(resultSnapshot);
1226
+ } catch {
1227
+ }
1228
+ };
1229
+ const rebuildResultSnapshot = () => {
1230
+ if (!resultSnapshot) return;
1231
+ const mergedPending = new Set(pending);
1232
+ const mergedErrors = [...errors];
1233
+ let mergedAttempted = attempted;
1234
+ let mergedReleased = released;
1235
+ for (const childEntry of entries.values()) {
1236
+ const childScope = childEntry.childScope;
1237
+ const childResult = childEntry.childResult;
1238
+ if (!childScope || !childResult) continue;
1239
+ const prefix = `child:${childScope.identity.scopeId}:`;
1240
+ mergedAttempted += childResult.attempted;
1241
+ mergedReleased += childResult.released;
1242
+ for (const resourceId of childResult.pending) {
1243
+ mergedPending.add(`${prefix}${resourceId}`);
1244
+ }
1245
+ for (const issue of childResult.errors) {
1246
+ mergedErrors.push({
1247
+ ...issue,
1248
+ resourceId: `${prefix}${issue.resourceId}`
1249
+ });
1250
+ }
1251
+ }
1252
+ resultSnapshot.attempted = mergedAttempted;
1253
+ resultSnapshot.released = mergedReleased;
1254
+ resultSnapshot.pending = [...mergedPending];
1255
+ resultSnapshot.errors = mergedErrors;
1256
+ resultSnapshot.cleanupIncomplete = mergedPending.size > 0 || mergedErrors.length > 0;
1257
+ };
1258
+ const projectChildResult = (entry, childResult, lateResourceId, lateError) => {
1259
+ if (!entry.childScope) return;
1260
+ const previous = entry.childResult;
1261
+ entry.childResult = childResult;
1262
+ entry.state = childResult.cleanupIncomplete ? "pending" : "released";
1263
+ entry.error = childResult.cleanupIncomplete ? childResult.errors[0]?.message ?? "Child scope cleanup is still pending" : void 0;
1264
+ rebuildResultSnapshot();
1265
+ publishDisposeResult();
1266
+ if (lateResourceId && resultSnapshot) {
1267
+ const resourceId = `child:${entry.childScope.identity.scopeId}:${lateResourceId}`;
1268
+ try {
1269
+ if (lateError !== void 0) options2.onLateFailure?.(resourceId, lateError, resultSnapshot);
1270
+ else options2.onLateSuccess?.(resourceId, resultSnapshot);
1271
+ } catch {
1272
+ }
1273
+ } else if (previous?.cleanupIncomplete && !childResult.cleanupIncomplete && resultSnapshot) {
1274
+ try {
1275
+ options2.onLateSuccess?.(`child:${entry.childScope.identity.scopeId}`, resultSnapshot);
1276
+ } catch {
1277
+ }
1278
+ }
1279
+ notifyChange(publicScope);
1280
+ };
1281
+ refreshPublishedDisposeResult = () => {
1282
+ if (!resultPublished || !resultSnapshot) return;
1283
+ rebuildResultSnapshot();
1284
+ publishDisposeResult();
1285
+ };
1286
+ const onLateFailure = (entry, error) => {
1287
+ entry.state = "pending";
1288
+ entry.error = errorMessage2(error);
1289
+ if (!errors.some((issue) => issue.resourceId === entry.resourceId && issue.code === "lifecycle.cleanup_failed")) {
1290
+ errors.push({
1291
+ resourceId: entry.resourceId,
1292
+ code: "lifecycle.cleanup_failed",
1293
+ message: errorMessage2(error)
1294
+ });
1295
+ }
1296
+ pending.add(entry.resourceId);
1297
+ if (resultSnapshot) {
1298
+ rebuildResultSnapshot();
1299
+ }
1300
+ publishDisposeResult();
1301
+ try {
1302
+ options2.onLateFailure?.(entry.resourceId, error, resultSnapshot);
1303
+ } catch {
1304
+ }
1305
+ notifyChange(publicScope);
1306
+ };
1307
+ const onLateSuccess = (entry) => {
1308
+ if (lateReleased.has(entry.resourceId)) return;
1309
+ lateReleased.add(entry.resourceId);
1310
+ pending.delete(entry.resourceId);
1311
+ released += 1;
1312
+ for (let index = errors.length - 1; index >= 0; index -= 1) {
1313
+ const issue = errors[index];
1314
+ if (issue?.resourceId === entry.resourceId && issue.code === "lifecycle.cleanup_timeout") {
1315
+ errors.splice(index, 1);
1316
+ }
1317
+ }
1318
+ if (resultSnapshot) {
1319
+ rebuildResultSnapshot();
1320
+ }
1321
+ publishDisposeResult();
1322
+ try {
1323
+ options2.onLateSuccess?.(entry.resourceId, resultSnapshot);
1324
+ } catch {
1325
+ }
1326
+ notifyChange(publicScope);
1327
+ };
1328
+ const releasePhase = async (phase) => {
1329
+ for (const entry of entriesToRelease.filter((item) => item.phase === phase)) {
1330
+ if (entry.childScope) {
1331
+ entry.cleanupStarted = true;
1332
+ try {
1333
+ const childResult = await entry.childScope.dispose({
1334
+ reason,
1335
+ timeoutMs: options2.timeoutMs,
1336
+ onLateSuccess: (resourceId, result2) => {
1337
+ if (result2) projectChildResult(entry, result2, resourceId);
1338
+ },
1339
+ onLateFailure: (resourceId, error, result2) => {
1340
+ if (result2) projectChildResult(entry, result2, resourceId, error);
1341
+ }
1342
+ });
1343
+ entry.cleanupFinished = true;
1344
+ projectChildResult(entry, childResult);
1345
+ } catch (error) {
1346
+ entry.cleanupFinished = true;
1347
+ entry.state = "pending";
1348
+ entry.error = errorMessage2(error);
1349
+ pending.add(entry.resourceId);
1350
+ errors.push({
1351
+ resourceId: entry.resourceId,
1352
+ code: "lifecycle.cleanup_failed",
1353
+ message: errorMessage2(error)
1354
+ });
1355
+ notifyChange(publicScope);
1356
+ }
1357
+ continue;
1358
+ }
1359
+ attempted += 1;
1360
+ const result = await runEntry(entry, reason, options2.timeoutMs, errors, onLateSuccess, onLateFailure);
1361
+ if (result === "released") released += 1;
1362
+ else if (!lateReleased.has(entry.resourceId)) pending.add(entry.resourceId);
1363
+ }
1364
+ };
1365
+ await releasePhase("before-teardown");
1366
+ if (options2.teardown) {
1367
+ const teardownEntry = {
1368
+ resourceId: "scope:teardown",
1369
+ state: "active",
1370
+ cleanup: options2.teardown,
1371
+ phase: "before-teardown",
1372
+ cleanupStarted: false,
1373
+ cleanupFinished: false,
1374
+ removeOnFailure: false
1375
+ };
1376
+ attempted += 1;
1377
+ const result = await runEntry(teardownEntry, reason, options2.timeoutMs, errors, onLateSuccess, onLateFailure);
1378
+ if (result === "released") released += 1;
1379
+ else if (!lateReleased.has(teardownEntry.resourceId)) pending.add(teardownEntry.resourceId);
1380
+ }
1381
+ await releasePhase("after-teardown");
1382
+ currentState = "stopped";
1383
+ notifyChange(publicScope);
1384
+ resultSnapshot = {
1385
+ scopeId: identity.scopeId,
1386
+ state: "stopped",
1387
+ attempted,
1388
+ released,
1389
+ pending: [...pending],
1390
+ errors,
1391
+ cleanupIncomplete: pending.size > 0 || errors.length > 0
1392
+ };
1393
+ rebuildResultSnapshot();
1394
+ resultPublished = true;
1395
+ publishDisposeResult();
1396
+ return resultSnapshot;
1397
+ })();
1398
+ return disposalPromise;
1399
+ }
1400
+ function resources() {
1401
+ return [...entries.values()].map((entry) => ({
1402
+ resourceId: entry.resourceId,
1403
+ state: entry.state,
1404
+ ...entry.error ? { error: entry.error } : {}
1405
+ }));
1406
+ }
1407
+ function child(kind, metadata = {}) {
1408
+ assertActive();
1409
+ let childEntry;
1410
+ const childScope = createLifecycleScope({
1411
+ kind,
1412
+ metadata: {
1413
+ ...metadata,
1414
+ parentScopeId: identity.scopeId
1415
+ },
1416
+ // 父作用域的 Host 订阅者也必须看到子作用域资源的状态变化。
1417
+ onChange: () => notifyChange(publicScope),
1418
+ // 只有子作用域已经生成最终结果后,父级才允许更新/删除登记。
1419
+ onDisposeResult: (result) => {
1420
+ const entry2 = childEntry;
1421
+ if (!entry2) return;
1422
+ entry2.childResult = result;
1423
+ entry2.state = result.cleanupIncomplete ? "pending" : "released";
1424
+ entry2.error = result.cleanupIncomplete ? result.errors[0]?.message ?? "Child scope cleanup is still pending" : void 0;
1425
+ refreshPublishedDisposeResult?.();
1426
+ if (!result.cleanupIncomplete && !disposalPromise && !entry2.cleanupStarted) {
1427
+ entries.delete(entry2.resourceId);
1428
+ usedIds.delete(entry2.resourceId);
1429
+ }
1430
+ notifyChange(publicScope);
1431
+ }
1432
+ });
1433
+ const removeRevoke = onRevoke((reason) => childScope.revoke(reason));
1434
+ const entry = addEntry(`child:${childScope.identity.scopeId}`, async () => void 0, "active");
1435
+ childEntry = entry;
1436
+ entry.childScope = childScope;
1437
+ childScope.onDispose(() => {
1438
+ removeRevoke();
1439
+ }, `parent-link:${identity.scopeId}`);
1440
+ return childScope;
1441
+ }
1442
+ Object.assign(publicScope, {
1443
+ identity,
1444
+ signal: controller.signal,
1445
+ onRevoke,
1446
+ onDispose,
1447
+ track,
1448
+ acquire,
1449
+ child,
1450
+ revoke,
1451
+ dispose,
1452
+ assertActive,
1453
+ resources
1454
+ });
1455
+ Object.defineProperty(publicScope, "state", {
1456
+ enumerable: true,
1457
+ configurable: false,
1458
+ get: () => currentState
1459
+ });
1460
+ return publicScope;
1461
+ }
1462
+ var createResourceScope = createLifecycleScope;
1463
+
1464
+ // src/lifecycle/scopedMessageBus.ts
1465
+ function mergeSignals(scopeSignal, requestSignal) {
1466
+ if (!requestSignal) return { signal: scopeSignal, dispose: () => void 0 };
1467
+ if (scopeSignal.aborted) {
1468
+ const controller2 = new AbortController();
1469
+ controller2.abort(scopeSignal.reason);
1470
+ return { signal: controller2.signal, dispose: () => void 0 };
1471
+ }
1472
+ if (requestSignal.aborted) {
1473
+ const controller2 = new AbortController();
1474
+ controller2.abort(requestSignal.reason);
1475
+ return { signal: controller2.signal, dispose: () => void 0 };
1476
+ }
1477
+ const controller = new AbortController();
1478
+ const abortFrom = (source) => {
1479
+ try {
1480
+ controller.abort(source.reason);
1481
+ } catch {
1482
+ controller.abort();
1483
+ }
1484
+ };
1485
+ const onScopeAbort = () => abortFrom(scopeSignal);
1486
+ const onRequestAbort = () => abortFrom(requestSignal);
1487
+ scopeSignal.addEventListener("abort", onScopeAbort, { once: true });
1488
+ requestSignal.addEventListener("abort", onRequestAbort, { once: true });
1489
+ return {
1490
+ signal: controller.signal,
1491
+ dispose: () => {
1492
+ scopeSignal.removeEventListener("abort", onScopeAbort);
1493
+ requestSignal.removeEventListener("abort", onRequestAbort);
1494
+ }
1495
+ };
1496
+ }
1497
+ function withScopeCleanup(scope, cleanup) {
1498
+ let active = true;
1499
+ let removeRevoke = () => void 0;
1500
+ let removeDispose = () => void 0;
1501
+ const runCleanup = () => {
1502
+ if (!active) return;
1503
+ active = false;
1504
+ removeRevoke();
1505
+ removeDispose();
1506
+ cleanup();
1507
+ };
1508
+ removeRevoke = scope.onRevoke(runCleanup);
1509
+ removeDispose = scope.onDispose(runCleanup, "message-bus-registration");
1510
+ return () => {
1511
+ if (!active) return;
1512
+ active = false;
1513
+ removeRevoke();
1514
+ removeDispose();
1515
+ cleanup();
1516
+ };
1517
+ }
1518
+ function createScopedMessageBus(base, scope) {
1519
+ return {
1520
+ publish(type, payload, options) {
1521
+ scope.assertActive();
1522
+ return base.publish(type, payload, options);
1523
+ },
1524
+ subscribe(type, handler) {
1525
+ scope.assertActive();
1526
+ const unsubscribe = base.subscribe(type, (payload) => {
1527
+ if (scope.state !== "active") return;
1528
+ handler(payload);
1529
+ });
1530
+ return withScopeCleanup(scope, unsubscribe);
1531
+ },
1532
+ dispatch(type, payload, options) {
1533
+ scope.assertActive();
1534
+ const merged = mergeSignals(scope.signal, options.signal);
1535
+ const cleanup = withScopeCleanup(scope, merged.dispose);
1536
+ try {
1537
+ return base.dispatch(type, payload, {
1538
+ ...options,
1539
+ signal: merged.signal,
1540
+ onSettled: () => {
1541
+ cleanup();
1542
+ options.onSettled?.();
1543
+ }
1544
+ });
1545
+ } catch (error) {
1546
+ cleanup();
1547
+ throw error;
1548
+ }
1549
+ },
1550
+ request(type, payload, options) {
1551
+ scope.assertActive();
1552
+ const merged = mergeSignals(scope.signal, options.signal);
1553
+ const cleanup = withScopeCleanup(scope, merged.dispose);
1554
+ try {
1555
+ const request = base.request(type, payload, {
1556
+ ...options,
1557
+ signal: merged.signal,
1558
+ onSettled: () => {
1559
+ cleanup();
1560
+ options.onSettled?.();
1561
+ }
1562
+ });
1563
+ return request.finally(cleanup);
1564
+ } catch (error) {
1565
+ cleanup();
1566
+ return Promise.reject(error);
1567
+ }
1568
+ },
1569
+ handle(type, handler, options) {
1570
+ scope.assertActive();
1571
+ const scopedHandler = (message) => {
1572
+ if (scope.state !== "active") {
1573
+ throw new LifecycleScopeRevokedError(
1574
+ `Lifecycle scope "${scope.identity.scopeId}" is ${scope.state}`
1575
+ );
1576
+ }
1577
+ const merged = mergeSignals(scope.signal, message.signal);
1578
+ try {
1579
+ const result = handler({ ...message, signal: merged.signal });
1580
+ if (result && typeof result.then === "function") {
1581
+ return Promise.resolve(result).finally(merged.dispose);
1582
+ }
1583
+ merged.dispose();
1584
+ return result;
1585
+ } catch (error) {
1586
+ merged.dispose();
1587
+ throw error;
1588
+ }
1589
+ };
1590
+ const unregister = base.handle(type, scopedHandler, options);
1591
+ return withScopeCleanup(scope, unregister);
1592
+ },
1593
+ snapshot() {
1594
+ return base.snapshot();
1595
+ },
1596
+ onSnapshot(handler) {
1597
+ scope.assertActive();
1598
+ const unsubscribe = base.onSnapshot(handler);
1599
+ return withScopeCleanup(scope, unsubscribe);
1600
+ }
1601
+ };
1602
+ }
1603
+
1604
+ // src/lifecycle/permissionLease.ts
1605
+ function uniquePermissions(permissions) {
1606
+ return [...new Set(permissions ?? [])];
1607
+ }
1608
+ function sameBindingValue(actual, expected) {
1609
+ return expected === void 0 || actual === expected;
1610
+ }
1611
+ function sameAttributes(actual, expected) {
1612
+ if (expected === void 0) return true;
1613
+ return Object.keys(expected).every((key) => Object.is(actual[key], expected[key]));
1614
+ }
1615
+ function normalizedRevision(value, name) {
1616
+ if (value === void 0) return void 0;
1617
+ if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${name} must be a non-negative safe integer`);
1618
+ return value;
1619
+ }
1620
+ function createPermissionLease(options) {
1621
+ const binding = {
1622
+ ...options.identity,
1623
+ requested: Object.freeze(uniquePermissions(options.requested)),
1624
+ approved: Object.freeze(uniquePermissions(options.approved)),
1625
+ ...options.sessionConstraints ? { sessionConstraints: Object.freeze(uniquePermissions(options.sessionConstraints)) } : {},
1626
+ ...options.policyRevision !== void 0 ? { policyRevision: normalizedRevision(options.policyRevision, "policyRevision") } : {},
1627
+ ...options.grantRevision !== void 0 ? { grantRevision: normalizedRevision(options.grantRevision, "grantRevision") } : {},
1628
+ ...options.grantId !== void 0 ? { grantId: options.grantId } : {}
1629
+ };
1630
+ Object.freeze(binding);
1631
+ const granted = new Set(
1632
+ binding.requested.filter(
1633
+ (permission) => binding.approved.includes(permission) && (binding.sessionConstraints === void 0 || binding.sessionConstraints.includes(permission))
1634
+ )
1635
+ );
1636
+ let revoked = false;
1637
+ let revokeReason = "permission lease revoked";
1638
+ const lease = {
1639
+ binding,
1640
+ get revoked() {
1641
+ return revoked || options.scope !== void 0 && options.scope.state !== "active";
1642
+ },
1643
+ has(permission) {
1644
+ return !lease.revoked && granted.has(permission);
1645
+ },
1646
+ assert(permission) {
1647
+ if (lease.revoked) {
1648
+ throw new PermissionLeaseRevokedError(revokeReason);
1649
+ }
1650
+ if (!granted.has(permission)) {
1651
+ throw new PermissionDeniedError(permission);
1652
+ }
1653
+ },
1654
+ assertBinding(expected) {
1655
+ if (!sameBindingValue(binding.pluginId, expected.pluginId) || !sameBindingValue(binding.instanceId, expected.instanceId) || !sameAttributes(binding.attributes, expected.attributes) || !sameBindingValue(binding.policyRevision, expected.policyRevision) || !sameBindingValue(binding.grantRevision, expected.grantRevision) || !sameBindingValue(binding.grantId, expected.grantId)) {
1656
+ throw new PermissionLeaseRevokedError("Permission lease identity does not match");
1657
+ }
1658
+ if (lease.revoked) throw new PermissionLeaseRevokedError(revokeReason);
1659
+ },
1660
+ revoke(reason = "permission lease revoked") {
1661
+ if (revoked) return;
1662
+ revoked = true;
1663
+ revokeReason = reason;
1664
+ }
1665
+ };
1666
+ if (options.scope) {
1667
+ options.scope.onRevoke((reason) => lease.revoke(reason));
1668
+ options.scope.onDispose((reason) => lease.revoke(reason), "permission-lease");
1669
+ }
1670
+ return lease;
1671
+ }
1672
+
1673
+ // src/lifecycle/taskScheduler.ts
1674
+ function errorMessage3(error) {
1675
+ return error instanceof Error ? error.message : String(error);
1676
+ }
1677
+ function createScopedTaskScheduler(scope, options = {}) {
1678
+ const tasks = /* @__PURE__ */ new Map();
1679
+ const listeners = /* @__PURE__ */ new Set();
1680
+ let disposed = false;
1681
+ const notify = () => {
1682
+ const value = [...tasks.values()].map((task) => ({
1683
+ id: task.definition.id,
1684
+ pluginId: task.definition.pluginId ?? scope.identity.pluginId ?? "unknown",
1685
+ label: task.definition.label,
1686
+ state: task.state,
1687
+ ...task.error ? { error: task.error } : {},
1688
+ ...task.lastCompletedAt ? { lastCompletedAt: task.lastCompletedAt } : {},
1689
+ ...task.nextRunAt ? { nextRunAt: task.nextRunAt } : {}
1690
+ }));
1691
+ for (const listener of [...listeners]) {
1692
+ try {
1693
+ listener(value);
1694
+ } catch {
1695
+ }
1696
+ }
1697
+ };
1698
+ const clearTimer = (task) => {
1699
+ if (task.timer !== void 0) {
1700
+ clearTimeout(task.timer);
1701
+ task.timer = void 0;
1702
+ }
1703
+ task.nextRunAt = void 0;
1704
+ };
1705
+ const schedule = (task) => {
1706
+ clearTimer(task);
1707
+ const intervalMs = task.definition.intervalMs;
1708
+ if (!task.active || disposed || intervalMs === void 0) {
1709
+ notify();
1710
+ return;
1711
+ }
1712
+ if (!Number.isFinite(intervalMs) || intervalMs < 0) {
1713
+ task.error = "\u4EFB\u52A1 intervalMs \u5FC5\u987B\u662F\u975E\u8D1F\u6709\u9650\u6570";
1714
+ task.state = "failed";
1715
+ notify();
1716
+ return;
1717
+ }
1718
+ const dueAt = Date.now() + intervalMs;
1719
+ task.nextRunAt = new Date(dueAt).toISOString();
1720
+ task.timer = setTimeout(() => {
1721
+ task.timer = void 0;
1722
+ task.nextRunAt = void 0;
1723
+ void runTask(task, "interval");
1724
+ }, intervalMs);
1725
+ notify();
1726
+ };
1727
+ const runTask = async (task, reason) => {
1728
+ if (!task.active || disposed || scope.state !== "active") return;
1729
+ if (task.runPromise) {
1730
+ task.rerunRequested = true;
1731
+ return task.runPromise;
1732
+ }
1733
+ task.state = "queued";
1734
+ task.error = void 0;
1735
+ notify();
1736
+ const run = (async () => {
1737
+ let requestScope;
1738
+ let controller;
1739
+ let abortFromScope;
1740
+ try {
1741
+ requestScope = scope.child("request");
1742
+ controller = new AbortController();
1743
+ task.controller = controller;
1744
+ abortFromScope = () => controller?.abort(scope.signal.reason);
1745
+ if (scope.signal.aborted) abortFromScope();
1746
+ else scope.signal.addEventListener("abort", abortFromScope, { once: true });
1747
+ task.state = "running";
1748
+ notify();
1749
+ await task.definition.run({ signal: controller.signal, reason });
1750
+ if (!controller.signal.aborted && !requestScope.signal.aborted) {
1751
+ task.state = "idle";
1752
+ task.error = void 0;
1753
+ task.lastCompletedAt = (/* @__PURE__ */ new Date()).toISOString();
1754
+ } else {
1755
+ task.state = "idle";
1756
+ }
1757
+ } catch (error) {
1758
+ task.state = "failed";
1759
+ task.error = controller?.signal.aborted ? void 0 : errorMessage3(error);
1760
+ } finally {
1761
+ if (abortFromScope) scope.signal.removeEventListener("abort", abortFromScope);
1762
+ task.controller = void 0;
1763
+ if (requestScope) {
1764
+ await requestScope.dispose({ reason: `task ${task.definition.id} finished` });
1765
+ }
1766
+ if (task.active && !disposed && scope.state === "active") schedule(task);
1767
+ else clearTimer(task);
1768
+ notify();
1769
+ }
1770
+ })();
1771
+ task.runPromise = run;
1772
+ try {
1773
+ await run;
1774
+ } finally {
1775
+ task.runPromise = void 0;
1776
+ notify();
1777
+ if (task.rerunRequested && task.active && !disposed && scope.state === "active") {
1778
+ task.rerunRequested = false;
1779
+ queueMicrotask(() => {
1780
+ void runTask(task, "coalesced");
1781
+ });
1782
+ }
1783
+ }
1784
+ };
1785
+ const releaseTask = async (task, reason) => {
1786
+ if (!task.active) return;
1787
+ task.active = false;
1788
+ task.rerunRequested = false;
1789
+ clearTimer(task);
1790
+ task.controller?.abort(reason);
1791
+ if (task.runPromise) await task.runPromise;
1792
+ tasks.delete(task.definition.id);
1793
+ notify();
1794
+ };
1795
+ const scheduler = {
1796
+ register(definition) {
1797
+ scope.assertActive();
1798
+ if (!definition.id || tasks.has(definition.id)) {
1799
+ throw new Error(`Scoped task id "${definition.id}" is already registered or empty`);
1800
+ }
1801
+ const task = {
1802
+ definition: { ...definition },
1803
+ state: "idle",
1804
+ rerunRequested: false,
1805
+ active: true
1806
+ };
1807
+ tasks.set(definition.id, task);
1808
+ const removeScopeCleanup = scope.onDispose(
1809
+ (reason) => releaseTask(task, reason),
1810
+ `task:${definition.id}`
1811
+ );
1812
+ const unregister = () => {
1813
+ if (!task.active) return;
1814
+ removeScopeCleanup();
1815
+ void releaseTask(task, "task unregistered");
1816
+ };
1817
+ schedule(task);
1818
+ if (options.runOnRegister) void runTask(task, "initial");
1819
+ return unregister;
1820
+ },
1821
+ async runNow(id, reason = "manual") {
1822
+ const task = tasks.get(id);
1823
+ if (!task) throw new Error(`Scoped task "${id}" is not registered`);
1824
+ await runTask(task, reason);
1825
+ },
1826
+ async cancel(id) {
1827
+ const task = tasks.get(id);
1828
+ if (!task) return;
1829
+ task.rerunRequested = false;
1830
+ task.controller?.abort("task canceled");
1831
+ if (task.runPromise) await task.runPromise;
1832
+ },
1833
+ snapshot() {
1834
+ return [...tasks.values()].map((task) => ({
1835
+ id: task.definition.id,
1836
+ pluginId: task.definition.pluginId ?? scope.identity.pluginId ?? "unknown",
1837
+ label: task.definition.label,
1838
+ state: task.state,
1839
+ ...task.error ? { error: task.error } : {},
1840
+ ...task.lastCompletedAt ? { lastCompletedAt: task.lastCompletedAt } : {},
1841
+ ...task.nextRunAt ? { nextRunAt: task.nextRunAt } : {}
1842
+ }));
1843
+ },
1844
+ subscribe(listener) {
1845
+ listeners.add(listener);
1846
+ listener(scheduler.snapshot());
1847
+ return () => listeners.delete(listener);
1848
+ },
1849
+ async dispose() {
1850
+ if (disposed) return;
1851
+ disposed = true;
1852
+ for (const task of [...tasks.values()]) {
1853
+ task.controller?.abort("task scheduler disposed");
1854
+ clearTimer(task);
1855
+ }
1856
+ await Promise.all([...tasks.values()].map((task) => releaseTask(task, "task scheduler disposed")));
1857
+ listeners.clear();
1858
+ }
1859
+ };
1860
+ return scheduler;
1861
+ }
1862
+
1863
+ // src/resources/resourceRegistry.ts
1864
+ function createResourceRegistry() {
1865
+ const definitions = /* @__PURE__ */ new Map();
1866
+ function registerOwned(ownerId, definition) {
1867
+ if (definitions.has(definition.id)) {
1868
+ throw new Error(
1869
+ `Resource definition "${definition.id}" is already registered`
1870
+ );
1871
+ }
1872
+ const owned = Object.assign({}, definition);
1873
+ Object.defineProperty(owned, RESOURCE_OWNER, {
1874
+ value: ownerId,
1875
+ enumerable: false,
1876
+ writable: false,
1877
+ configurable: false
1878
+ });
1879
+ definitions.set(definition.id, owned);
1880
+ }
1881
+ return {
1882
+ _registerOwned: registerOwned,
1883
+ register(definition) {
1884
+ registerOwned("", definition);
1885
+ },
1886
+ unregister(id) {
1887
+ definitions.delete(id);
1888
+ },
1889
+ get(id) {
1890
+ return definitions.get(id);
1891
+ },
1892
+ /** 获取所有已注册的资源定义 id(用于 ownership 快照) */
1893
+ _ids() {
1894
+ return Array.from(definitions.keys());
1895
+ }
1896
+ };
1897
+ }
1898
+ function registerOwnedResource(registry, ownerId, definition) {
1899
+ const internal = registry;
1900
+ if (!internal._registerOwned) {
1901
+ registry.register(definition);
1902
+ return;
1903
+ }
1904
+ internal._registerOwned(ownerId, definition);
1905
+ }
1906
+
1907
+ // src/resources/resourceStore.ts
1908
+ function createContext(ownerId, getCapability, getAttributes) {
1909
+ return {
1910
+ getCapability,
1911
+ attributes: getAttributes(ownerId),
1912
+ ownerId
1913
+ };
1914
+ }
1915
+ function recordKey(definitionId, key) {
1916
+ return `${definitionId}::${key.join("::")}`;
1917
+ }
1918
+ function defaultEquals(a, b) {
1919
+ return Object.is(a, b);
1920
+ }
1921
+ function createResourceStore(registry, getCapability, getAttributes = () => ({})) {
1922
+ const records = /* @__PURE__ */ new Map();
1923
+ const microtaskQueue = /* @__PURE__ */ new Map();
1924
+ const contextSubscribers = /* @__PURE__ */ new Set();
1925
+ let microtaskScheduled = false;
1926
+ const notify = (record) => {
1927
+ for (const subscriber of [...record.subscribers]) {
1928
+ try {
1929
+ subscriber();
1930
+ } catch {
1931
+ }
1932
+ }
1933
+ };
1934
+ const notifyContext = () => {
1935
+ for (const subscriber of [...contextSubscribers]) {
1936
+ try {
1937
+ subscriber();
1938
+ } catch {
1939
+ }
1940
+ }
1941
+ };
1942
+ const cleanupRecord = (record) => {
1943
+ record.abortController?.abort();
1944
+ record.providerUnsubscribe?.();
1945
+ record.providerUnsubscribe = null;
1946
+ };
1947
+ const contextFor = (definition) => createContext(
1948
+ definition[RESOURCE_OWNER] ?? "__unowned__",
1949
+ getCapability,
1950
+ getAttributes
1951
+ );
1952
+ const getOrCreateRecord = (definition, args) => {
1953
+ const context = contextFor(definition);
1954
+ const key = definition.key(args, context);
1955
+ const keyString = recordKey(definition.id, key);
1956
+ const current = records.get(keyString);
1957
+ if (current) {
1958
+ if (definition.subscribe && !current.providerUnsubscribe) {
1959
+ current.providerUnsubscribe = definition.subscribe(args, context, () => {
1960
+ scheduleInvalidation(definition.id, args);
1961
+ });
1962
+ }
1963
+ return current;
1964
+ }
1965
+ const record = {
1966
+ snapshot: { key, status: "pending", data: void 0, revision: 0 },
1967
+ inFlight: null,
1968
+ abortController: null,
1969
+ subscribers: /* @__PURE__ */ new Set(),
1970
+ invalidationScheduled: false,
1971
+ loadRevision: 0,
1972
+ owner: definition[RESOURCE_OWNER] ?? "__unowned__",
1973
+ providerUnsubscribe: null
1974
+ };
1975
+ records.set(keyString, record);
1976
+ if (definition.subscribe) {
1977
+ record.providerUnsubscribe = definition.subscribe(args, context, () => {
1978
+ scheduleInvalidation(definition.id, args);
1979
+ });
1980
+ }
1981
+ return record;
1982
+ };
1983
+ const loadResource = (definition, args, record) => {
1984
+ record.abortController?.abort();
1985
+ const context = contextFor(definition);
1986
+ const key = definition.key(args, context);
1987
+ if (record.snapshot.key.join("::") !== key.join("::")) return;
1988
+ const abortController = new AbortController();
1989
+ const loadRevision = ++record.loadRevision;
1990
+ record.abortController = abortController;
1991
+ try {
1992
+ record.inFlight = Promise.resolve(definition.load(args, context, abortController.signal));
1993
+ } catch (error) {
1994
+ record.inFlight = Promise.reject(error);
1995
+ }
1996
+ record.snapshot = { ...record.snapshot, status: "pending", revision: record.snapshot.revision + 1 };
1997
+ notify(record);
1998
+ record.inFlight.then((data) => {
1999
+ if (abortController.signal.aborted || record.loadRevision !== loadRevision) return;
2000
+ if (record.snapshot.key.join("::") !== key.join("::")) return;
2001
+ const equals = definition.equals ?? defaultEquals;
2002
+ const changed = !equals(record.snapshot.data, data);
2003
+ record.snapshot = {
2004
+ key,
2005
+ status: "ready",
2006
+ data,
2007
+ revision: changed ? record.snapshot.revision + 1 : record.snapshot.revision
2008
+ };
2009
+ record.inFlight = null;
2010
+ record.abortController = null;
2011
+ if (changed) notify(record);
2012
+ }).catch((error) => {
2013
+ if (abortController.signal.aborted || record.loadRevision !== loadRevision) return;
2014
+ const blocked = error instanceof Error && error.message === "blocked";
2015
+ const errorValue = error instanceof Error ? error : new Error(String(error));
2016
+ record.snapshot = blocked ? { ...record.snapshot, status: "blocked", revision: record.snapshot.revision + 1 } : {
2017
+ ...record.snapshot,
2018
+ status: record.snapshot.data === void 0 ? "error" : "stale",
2019
+ error: {
2020
+ code: typeof errorValue.code === "string" ? String(errorValue.code) : "resource.load_failed",
2021
+ message: errorValue.message
2022
+ },
2023
+ revision: record.snapshot.revision + 1
2024
+ };
2025
+ record.inFlight = null;
2026
+ record.abortController = null;
2027
+ notify(record);
2028
+ });
2029
+ };
2030
+ const invalidateNow = (definitionId, args) => {
2031
+ const definition = registry.get(definitionId);
2032
+ if (!definition) return;
2033
+ const context = contextFor(definition);
2034
+ const key = definition.key(args, context);
2035
+ const record = records.get(recordKey(definitionId, key));
2036
+ if (!record) return;
2037
+ record.snapshot = { ...record.snapshot, status: "stale", revision: record.snapshot.revision + 1 };
2038
+ notify(record);
2039
+ loadResource(definition, args, record);
2040
+ };
2041
+ const flushInvalidations = () => {
2042
+ const queue = [...microtaskQueue.values()];
2043
+ microtaskQueue.clear();
2044
+ microtaskScheduled = false;
2045
+ for (const item of queue) invalidateNow(item.definitionId, item.args);
2046
+ };
2047
+ const scheduleInvalidation = (definitionId, args) => {
2048
+ const definition = registry.get(definitionId);
2049
+ if (!definition) return;
2050
+ if (definition.invalidation === "immediate") {
2051
+ invalidateNow(definitionId, args);
2052
+ return;
2053
+ }
2054
+ const key = recordKey(definitionId, definition.key(args, contextFor(definition)));
2055
+ if (microtaskQueue.has(key)) return;
2056
+ microtaskQueue.set(key, { definitionId, args });
2057
+ if (!microtaskScheduled) {
2058
+ microtaskScheduled = true;
2059
+ queueMicrotask(flushInvalidations);
2060
+ }
2061
+ };
2062
+ const refreshRuntimeBindings = () => {
2063
+ for (const record of records.values()) cleanupRecord(record);
2064
+ records.clear();
2065
+ notifyContext();
2066
+ };
2067
+ return {
2068
+ ensure(definitionId, args) {
2069
+ const definition = registry.get(definitionId);
2070
+ if (!definition) throw new Error(`Resource definition "${definitionId}" not found`);
2071
+ const record = getOrCreateRecord(definition, args);
2072
+ if (!record.inFlight && record.snapshot.status === "pending") loadResource(definition, args, record);
2073
+ return record.snapshot;
2074
+ },
2075
+ subscribe(definitionId, args, callback) {
2076
+ const definition = registry.get(definitionId);
2077
+ if (!definition) return () => void 0;
2078
+ let record = getOrCreateRecord(definition, args);
2079
+ record.subscribers.add(callback);
2080
+ const removeContext = definition.scope === "context" ? (() => {
2081
+ const listener = () => {
2082
+ record.subscribers.delete(callback);
2083
+ record = getOrCreateRecord(definition, args);
2084
+ record.subscribers.add(callback);
2085
+ callback();
2086
+ };
2087
+ contextSubscribers.add(listener);
2088
+ return () => contextSubscribers.delete(listener);
2089
+ })() : void 0;
2090
+ return () => {
2091
+ removeContext?.();
2092
+ record.subscribers.delete(callback);
2093
+ if (record.subscribers.size === 0 && record.providerUnsubscribe) {
2094
+ record.providerUnsubscribe();
2095
+ record.providerUnsubscribe = null;
2096
+ }
2097
+ if (record.subscribers.size === 0 && record.abortController) {
2098
+ const current = record;
2099
+ setTimeout(() => {
2100
+ if (current.subscribers.size === 0 && current.abortController) {
2101
+ current.abortController.abort();
2102
+ current.abortController = null;
2103
+ current.inFlight = null;
2104
+ }
2105
+ }, 100);
2106
+ }
2107
+ };
2108
+ },
2109
+ read(definitionId, args) {
2110
+ const definition = registry.get(definitionId);
2111
+ if (!definition) return void 0;
2112
+ const key = definition.key(args, contextFor(definition));
2113
+ return records.get(recordKey(definitionId, key))?.snapshot;
2114
+ },
2115
+ invalidate: scheduleInvalidation,
2116
+ disposeOwner(ownerId) {
2117
+ for (const [key, record] of records) {
2118
+ if (record.owner !== ownerId) continue;
2119
+ cleanupRecord(record);
2120
+ records.delete(key);
2121
+ }
2122
+ },
2123
+ refreshRuntimeBindings,
2124
+ subscribeContext(callback) {
2125
+ contextSubscribers.add(callback);
2126
+ return () => contextSubscribers.delete(callback);
2127
+ }
2128
+ };
2129
+ }
2130
+
2131
+ // src/host/createPluginHost.ts
2132
+ function createInMemoryPluginConfigStore(initial = {}, readOnly = false) {
2133
+ const values = /* @__PURE__ */ new Map();
2134
+ for (const [id, value] of Object.entries(initial)) {
2135
+ if (typeof value === "boolean") values.set(id, value);
2136
+ }
2137
+ const listeners = /* @__PURE__ */ new Set();
2138
+ const snapshot = () => Object.freeze(Object.fromEntries(values));
2139
+ return {
2140
+ read: snapshot,
2141
+ setEnabled(pluginId, enabled) {
2142
+ if (readOnly) return;
2143
+ if (values.get(pluginId) === enabled) return;
2144
+ values.set(pluginId, enabled);
2145
+ const next = snapshot();
2146
+ for (const listener of [...listeners]) listener(next);
2147
+ },
2148
+ subscribe(listener) {
2149
+ listeners.add(listener);
2150
+ return () => listeners.delete(listener);
2151
+ }
2152
+ };
2153
+ }
2154
+ function errorMessage4(error) {
2155
+ if (error instanceof Error) return error.message;
2156
+ if (error && typeof error === "object" && "message" in error) {
2157
+ const message = error.message;
2158
+ if (typeof message === "string") return message;
2159
+ }
2160
+ return String(error);
2161
+ }
2162
+ function makeInstanceId(pluginId, unitId) {
2163
+ try {
2164
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
2165
+ return `${pluginId}:${unitId}:${crypto.randomUUID()}`;
2166
+ }
2167
+ } catch {
2168
+ }
2169
+ return `${pluginId}:${unitId}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
2170
+ }
2171
+ function lifecycleStateFor(state, desired) {
2172
+ switch (state) {
2173
+ case "starting":
2174
+ return "starting";
2175
+ case "stopping":
2176
+ return "stopping";
2177
+ case "enabled":
2178
+ return "running";
2179
+ case "blocked":
2180
+ return "waiting";
2181
+ case "error-disabled":
2182
+ case "cleanup-pending":
2183
+ return "failed";
2184
+ case "unknown":
2185
+ return desired ? "waiting" : "disabled";
2186
+ case "disabled":
2187
+ return "disabled";
2188
+ case "registered":
2189
+ return desired ? "waiting" : "disabled";
2190
+ }
2191
+ }
2192
+ function selectedUnit(manifest, execution, defaultLifetime) {
2193
+ const units = manifest.units ?? [];
2194
+ if (units.length > 1) {
2195
+ const matches = execution === void 0 ? [] : units.filter((unit) => unit.execution === execution);
2196
+ return matches.length === 1 ? matches[0] : void 0;
2197
+ }
2198
+ if (units.length === 1) {
2199
+ return execution === void 0 || units[0]?.execution === execution ? units[0] : void 0;
2200
+ }
2201
+ return {
2202
+ id: manifest.id,
2203
+ execution: manifest.meta.execution ?? execution ?? "default",
2204
+ lifetime: manifest.meta.lifetime ?? defaultLifetime,
2205
+ dependencies: manifest.dependencies?.map((dependency) => ({
2206
+ capability: dependency.capability,
2207
+ contractVersion: dependency.contractVersion ?? `${dependency.capability}.v1`,
2208
+ sourceExecution: dependency.sourceExecution ?? "default",
2209
+ scope: dependency.scope ?? defaultLifetime,
2210
+ ...dependency.reason !== void 0 ? { reason: dependency.reason } : {},
2211
+ ...dependency.optional !== void 0 ? { optional: dependency.optional } : {}
2212
+ })),
2213
+ provides: manifest.provides,
2214
+ permissions: manifest.permissions,
2215
+ config: manifest.config,
2216
+ contribution: manifest.contribution
2217
+ };
2218
+ }
2219
+ function dependenciesFor(manifest, execution) {
2220
+ return dependenciesOfManifest(manifest, execution);
2221
+ }
2222
+ function setupFor(manifest, unit, options) {
2223
+ return options.runtimeUnitImplementationRegistry?.get(manifest.id, unit.id);
2224
+ }
2225
+ function stableValue(value) {
2226
+ if (Array.isArray(value)) return `[${value.map(stableValue).join(",")}]`;
2227
+ if (!value || typeof value !== "object") return JSON.stringify(value);
2228
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableValue(item)}`).join(",")}}`;
2229
+ }
2230
+ var StartupCapabilityError = class extends Error {
2231
+ details;
2232
+ constructor(details, phase = "startup") {
2233
+ super(`Startup prerequisite unavailable: ${details.map((item) => item.capability).join(", ")} (${phase})`);
2234
+ this.name = "StartupCapabilityError";
2235
+ this.details = details;
2236
+ }
2237
+ };
2238
+ var StartupPluginError = class extends Error {
2239
+ details;
2240
+ constructor(details) {
2241
+ super(`Startup plugin failed: ${details.pluginId}`);
2242
+ this.name = "StartupPluginError";
2243
+ this.details = details;
2244
+ }
2245
+ };
2246
+ function createPluginHost(options = {}) {
2247
+ const listeners = /* @__PURE__ */ new Set();
2248
+ let versionCounter = 0;
2249
+ let hostDisposed = false;
2250
+ let disposePromise;
2251
+ let runtimeSnapshots = options.runtimeSnapshots;
2252
+ const bumpVersion = () => {
2253
+ versionCounter += 1;
2254
+ for (const listener of [...listeners]) {
2255
+ try {
2256
+ listener({ version: versionCounter });
2257
+ } catch {
2258
+ }
2259
+ }
2260
+ };
2261
+ const rootScope = createLifecycleScope({
2262
+ kind: "root",
2263
+ metadata: { attributes: Object.freeze({ ...options.rootAttributes ?? {} }) },
2264
+ onChange: bumpVersion
2265
+ });
2266
+ const taskScheduler = createScopedTaskScheduler(rootScope);
2267
+ const capabilities = createCapabilityRegistry();
2268
+ const messageBus = options.messageBus ?? createMessageBus();
2269
+ const resourceRegistry = options.resourceRegistry ?? createResourceRegistry();
2270
+ let rootAttributes = Object.freeze({ ...options.rootAttributes ?? {} });
2271
+ const instanceAttributes = /* @__PURE__ */ new Map();
2272
+ const resourceStore = createResourceStore(resourceRegistry, (key) => capabilities.has(key) ? capabilities.get(key) : void 0, (ownerId) => instanceAttributes.get(ownerId ?? "") ?? rootAttributes);
2273
+ const injectCapabilities = (source) => {
2274
+ if (!source) return;
2275
+ const entries = source instanceof Map ? source.entries() : Object.entries(source);
2276
+ for (const [key, value] of entries) capabilities.provide(key, value);
2277
+ };
2278
+ injectCapabilities(options.capabilities);
2279
+ injectCapabilities(options.builtinCapabilities);
2280
+ if (!capabilities.has(RUNTIME_MESSAGE_BUS)) capabilities.provide(RUNTIME_MESSAGE_BUS, messageBus);
2281
+ if (!capabilities.has(RESOURCE_REGISTRY_CAPABILITY)) capabilities.provide(RESOURCE_REGISTRY_CAPABILITY, resourceRegistry);
2282
+ if (!capabilities.has(SCOPED_TASK_SCHEDULER_CAPABILITY)) capabilities.provide(SCOPED_TASK_SCHEDULER_CAPABILITY, taskScheduler);
2283
+ const configStore = options.configStore ?? createInMemoryPluginConfigStore(options.initialPluginConfig, false);
2284
+ const knownManifests = /* @__PURE__ */ new Map();
2285
+ const records = /* @__PURE__ */ new Map();
2286
+ const enabledSet = /* @__PURE__ */ new Set();
2287
+ const starting = /* @__PURE__ */ new Map();
2288
+ const stopping = /* @__PURE__ */ new Map();
2289
+ const internalConfigWrites = /* @__PURE__ */ new Set();
2290
+ let intentSnapshot = options.pluginIntentCoordinator?.snapshot();
2291
+ let removeConfigSubscription = () => void 0;
2292
+ let removeIntentSubscription = () => void 0;
2293
+ let removeScopeSubscription = () => void 0;
2294
+ let reconcilePromise;
2295
+ const desiredEnabledFor = (pluginId, manifest) => {
2296
+ if (manifest?.meta.startup === "required" || manifest?.meta.canDisable === false) return true;
2297
+ if (intentSnapshot && Object.prototype.hasOwnProperty.call(intentSnapshot.desiredEnabled, pluginId)) {
2298
+ return intentSnapshot.desiredEnabled[pluginId] === true;
2299
+ }
2300
+ return configStore.read()[pluginId] ?? manifest?.meta.defaultEnabled ?? false;
2301
+ };
2302
+ const desiredRevisionFor = (pluginId) => intentSnapshot?.desiredRevision[pluginId];
2303
+ const selected = (manifest) => selectedUnit(manifest, options.execution, options.defaultLifetime ?? "plugin-instance");
2304
+ const graph = () => buildPluginGraph([...knownManifests.values()], { execution: options.execution, enabledPluginIds: enabledSet });
2305
+ const validateManifest = (manifest) => {
2306
+ if (!manifest || typeof manifest.id !== "string" || manifest.id.trim() === "") throw new Error("Plugin id must be a non-empty string");
2307
+ if (typeof manifest.name !== "string" || manifest.name.trim() === "") throw new Error(`Plugin "${manifest.id}" name must be a non-empty string`);
2308
+ if (!manifest.meta || typeof manifest.meta.defaultEnabled !== "boolean" || typeof manifest.meta.canDisable !== "boolean") {
2309
+ throw new Error(`Plugin "${manifest.id}" meta must define defaultEnabled and canDisable`);
2310
+ }
2311
+ if (manifest.meta.startup === "required" && (manifest.meta.canDisable || !manifest.meta.defaultEnabled)) {
2312
+ throw new Error(`Plugin "${manifest.id}" required startup metadata is inconsistent`);
2313
+ }
2314
+ if (manifest.units !== void 0 && !Array.isArray(manifest.units)) throw new Error(`Plugin "${manifest.id}" units must be an array`);
2315
+ if (manifest.units && manifest.units.length > 0) {
2316
+ const ids = /* @__PURE__ */ new Set();
2317
+ for (const unit of manifest.units) {
2318
+ if (!unit.id || ids.has(unit.id)) throw new Error(`Plugin "${manifest.id}" has duplicate or empty unit id`);
2319
+ ids.add(unit.id);
2320
+ if (!unit.execution || !unit.lifetime) throw new Error(`Plugin "${manifest.id}" unit "${unit.id}" must define execution and lifetime`);
2321
+ }
2322
+ }
2323
+ options.manifestValidator?.(manifest);
2324
+ };
2325
+ const isRequired = (manifest) => manifest.meta.startup === "required" || manifest.meta.canDisable === false;
2326
+ const missingDependencies = (manifest) => {
2327
+ const missing = [];
2328
+ for (const dependency of dependenciesFor(manifest, options.execution)) {
2329
+ if (dependency.optional) continue;
2330
+ if (!capabilities.has(dependency.capability)) missing.push(dependency.capability);
2331
+ }
2332
+ return [...new Set(missing)];
2333
+ };
2334
+ const runtimeUnavailable = (manifest) => {
2335
+ const unit = selected(manifest);
2336
+ if (!unit) {
2337
+ const units = manifest.units ?? [];
2338
+ if (units.length > 0 && options.execution !== void 0 && !units.some((item) => item.execution === options.execution)) {
2339
+ const snapshots = runtimeSnapshots?.() ?? [];
2340
+ if (snapshots.some((item) => item.pluginId === manifest.id)) return `runtime:${manifest.id}:unknown`;
2341
+ }
2342
+ return units.length > 0 ? `runtime:${manifest.id}:unit-unavailable` : void 0;
2343
+ }
2344
+ if (!setupFor(manifest, unit, options)) return `runtime:${manifest.id}:${unit.id}:implementation-unavailable`;
2345
+ return void 0;
2346
+ };
2347
+ const resolveScope = (record, unit) => {
2348
+ const resolver = options.scopeResolver;
2349
+ if (!resolver) return { available: true, parent: rootScope, attributes: rootAttributes, key: stableValue(rootAttributes) };
2350
+ return resolver.resolve({ pluginId: record.manifest.id, unitId: unit.id, lifetime: unit.lifetime, manifest: record.manifest });
2351
+ };
2352
+ const scopeKeyForResolution = (resolution, unit) => resolution.key ?? `${resolution.parent?.identity.scopeId ?? "root"}:${unit.lifetime}:${stableValue(resolution.attributes ?? rootAttributes)}`;
2353
+ const revokeContributions = (record) => {
2354
+ for (const contribution of record.contributions) {
2355
+ if (!contribution.active) continue;
2356
+ contribution.active = false;
2357
+ try {
2358
+ contribution.revoke();
2359
+ } catch {
2360
+ }
2361
+ }
2362
+ };
2363
+ const releaseContributions = async (record) => {
2364
+ let firstError;
2365
+ for (const contribution of [...record.contributions].reverse()) {
2366
+ try {
2367
+ await contribution.dispose();
2368
+ } catch (error) {
2369
+ firstError ??= error;
2370
+ }
2371
+ }
2372
+ record.contributions = [];
2373
+ if (firstError) throw firstError;
2374
+ };
2375
+ const registerContribution = async (record, unit, instanceId, scope) => {
2376
+ const contribution = unit.contribution;
2377
+ if (contribution === void 0 || !options.contributionAdapters) return;
2378
+ for (const adapter of options.contributionAdapters) {
2379
+ const result = await adapter.register({
2380
+ pluginId: record.manifest.id,
2381
+ unitId: unit.id,
2382
+ instanceId,
2383
+ scope,
2384
+ contribution,
2385
+ manifest: record.manifest
2386
+ });
2387
+ if (!result) continue;
2388
+ if (typeof result === "function") {
2389
+ let active = true;
2390
+ let cleanupPromise;
2391
+ const dispose = async () => {
2392
+ if (cleanupPromise) return cleanupPromise;
2393
+ if (!active) return;
2394
+ active = false;
2395
+ cleanupPromise = Promise.resolve(result());
2396
+ await cleanupPromise;
2397
+ };
2398
+ record.contributions.push({ active: true, revoke: () => {
2399
+ void dispose().catch(() => void 0);
2400
+ }, dispose });
2401
+ } else {
2402
+ const handle = result;
2403
+ let cleanupPromise;
2404
+ record.contributions.push({
2405
+ active: true,
2406
+ revoke: () => {
2407
+ try {
2408
+ handle.revoke?.();
2409
+ } catch {
2410
+ }
2411
+ if (!cleanupPromise) cleanupPromise = Promise.resolve(handle.dispose?.());
2412
+ cleanupPromise.catch(() => void 0);
2413
+ },
2414
+ dispose: async () => {
2415
+ if (cleanupPromise) return cleanupPromise;
2416
+ cleanupPromise = Promise.resolve(handle.dispose?.());
2417
+ return cleanupPromise;
2418
+ }
2419
+ });
2420
+ }
2421
+ }
2422
+ };
2423
+ const buildContext = (record, unit, scope, instanceId) => {
2424
+ const requested = [...new Set(unit.permissions ?? [])];
2425
+ const policy = options.permissionPolicy?.({
2426
+ pluginId: record.manifest.id,
2427
+ unitId: unit.id,
2428
+ // Host 的实例令牌和 Scope 原语的内部 instanceId 是两个生成点;
2429
+ // PermissionLease 必须绑定 Context 对外暴露的实例令牌。
2430
+ identity: { ...scope.identity, instanceId },
2431
+ requested
2432
+ }) ?? { approved: requested };
2433
+ const permissionLease = createPermissionLease({
2434
+ identity: { ...scope.identity, instanceId },
2435
+ requested,
2436
+ approved: policy.approved ?? requested,
2437
+ sessionConstraints: policy.sessionConstraints,
2438
+ ...policy.binding ?? {},
2439
+ scope
2440
+ });
2441
+ const extension = Object.freeze({
2442
+ ...options.contextExtension?.({
2443
+ pluginId: record.manifest.id,
2444
+ unitId: unit.id,
2445
+ instanceId,
2446
+ scope,
2447
+ manifest: record.manifest
2448
+ }) ?? {}
2449
+ });
2450
+ const config = unit.config === void 0 ? void 0 : Object.freeze({ ...unit.config });
2451
+ const contextTaskScheduler = createScopedTaskScheduler(scope);
2452
+ const scopedMessageBus = createScopedMessageBus(messageBus, scope);
2453
+ const ownedResourceIds = /* @__PURE__ */ new Set();
2454
+ let resourceDefinitionsRevoked = false;
2455
+ const revokeResourceDefinitions = () => {
2456
+ if (resourceDefinitionsRevoked) return;
2457
+ resourceDefinitionsRevoked = true;
2458
+ resourceStore.disposeOwner(instanceId);
2459
+ for (const resourceId of ownedResourceIds) {
2460
+ if (resourceRegistry.get(resourceId)) resourceRegistry.unregister(resourceId);
2461
+ }
2462
+ ownedResourceIds.clear();
2463
+ };
2464
+ const scopedResourceRegistry = {
2465
+ register(definition) {
2466
+ scope.assertActive();
2467
+ registerOwnedResource(resourceRegistry, instanceId, definition);
2468
+ ownedResourceIds.add(definition.id);
2469
+ },
2470
+ unregister(resourceId) {
2471
+ scope.assertActive();
2472
+ if (!ownedResourceIds.has(resourceId)) {
2473
+ throw new Error(`Resource definition "${resourceId}" is not owned by plugin instance "${instanceId}"`);
2474
+ }
2475
+ resourceRegistry.unregister(resourceId);
2476
+ ownedResourceIds.delete(resourceId);
2477
+ },
2478
+ get(resourceId) {
2479
+ return resourceRegistry.get(resourceId);
2480
+ },
2481
+ _ids() {
2482
+ return [...ownedResourceIds];
2483
+ }
2484
+ };
2485
+ scope.onRevoke(revokeResourceDefinitions);
2486
+ scope.onDispose(revokeResourceDefinitions, `resource-definitions:${record.manifest.id}`, "after-teardown");
2487
+ scope.onDispose(() => contextTaskScheduler.dispose(), `task-scheduler:${record.manifest.id}`);
2488
+ const context = {
2489
+ pluginId: record.manifest.id,
2490
+ instanceId,
2491
+ unitId: unit.id,
2492
+ scope,
2493
+ signal: scope.signal,
2494
+ permissions: Object.freeze([...permissionLease.binding.requested.filter((permission) => permissionLease.has(permission))]),
2495
+ permissionLease,
2496
+ serviceBridge: options.serviceBridgeForPlugin?.(record.manifest.id, instanceId),
2497
+ taskScheduler: contextTaskScheduler,
2498
+ extension,
2499
+ config,
2500
+ onDispose(cleanup) {
2501
+ scope.onDispose(cleanup, `plugin-dispose:${record.manifest.id}`);
2502
+ record.disposeCallbacks.push(cleanup);
2503
+ },
2504
+ provide(key, value) {
2505
+ scope.assertActive();
2506
+ capabilities.provide(key, value);
2507
+ record.capabilities.add(key);
2508
+ },
2509
+ get(key) {
2510
+ if (key === RUNTIME_MESSAGE_BUS) return scopedMessageBus;
2511
+ if (key === RESOURCE_REGISTRY_CAPABILITY) return scopedResourceRegistry;
2512
+ return capabilities.get(key);
2513
+ },
2514
+ has(key) {
2515
+ return capabilities.has(key);
2516
+ },
2517
+ require(key) {
2518
+ capabilities.require(key);
2519
+ },
2520
+ messageBus: scopedMessageBus
2521
+ };
2522
+ return context;
2523
+ };
2524
+ const beginStop = (record, reason, preserveIntent, blockedBy) => {
2525
+ if (record.state === "stopping" || record.state === "cleanup-pending") return;
2526
+ const wasStarting = record.state === "starting";
2527
+ record.state = "stopping";
2528
+ record.error = void 0;
2529
+ record.blockedBy = void 0;
2530
+ record.stopRequested = reason;
2531
+ record.preserveIntentOnStop = preserveIntent;
2532
+ record.scope?.revoke(reason);
2533
+ revokeContributions(record);
2534
+ for (const capability of record.capabilities) capabilities.revoke(capability);
2535
+ record.capabilities.clear();
2536
+ enabledSet.delete(record.manifest.id);
2537
+ if (wasStarting) record.pendingDesiredEnabled = desiredEnabledFor(record.manifest.id, record.manifest);
2538
+ if (blockedBy) record.blockedBy = [...blockedBy];
2539
+ resourceStore.disposeOwner(record.instanceId ?? record.manifest.id);
2540
+ bumpVersion();
2541
+ };
2542
+ const finishStop = async (record, reason) => {
2543
+ const scope = record.scope;
2544
+ const preserveIntent = preserveIntentOf(record);
2545
+ let teardownError;
2546
+ const projectLateCleanup = (result, error2) => {
2547
+ if (result) record.cleanup = result;
2548
+ if (error2 !== void 0) {
2549
+ record.error = errorMessage4(error2);
2550
+ bumpVersion();
2551
+ return;
2552
+ }
2553
+ if (!result || result.cleanupIncomplete || record.state !== "cleanup-pending") return;
2554
+ const desired2 = record.pendingDesiredEnabled ?? desiredEnabledFor(record.manifest.id, record.manifest);
2555
+ const missing2 = missingDependencies(record.manifest);
2556
+ const unavailable2 = runtimeUnavailable(record.manifest);
2557
+ record.state = desired2 && (missing2.length > 0 || unavailable2) ? "blocked" : "disabled";
2558
+ record.blockedBy = record.state === "blocked" ? [.../* @__PURE__ */ new Set([...missing2, ...unavailable2 ? [unavailable2] : []])] : void 0;
2559
+ record.error = void 0;
2560
+ bumpVersion();
2561
+ if (desired2 && record.state === "disabled" && !hostDisposed) {
2562
+ queueMicrotask(() => {
2563
+ void enable(record.manifest.id).catch(() => void 0);
2564
+ });
2565
+ }
2566
+ };
2567
+ const cleanup = scope ? await scope.dispose({
2568
+ reason,
2569
+ timeoutMs: options.lifecycleCleanupTimeoutMs,
2570
+ teardown: async () => {
2571
+ try {
2572
+ if (record.teardown) await record.teardown();
2573
+ await releaseContributions(record);
2574
+ } catch (error2) {
2575
+ teardownError = error2;
2576
+ throw error2;
2577
+ }
2578
+ },
2579
+ onLateSuccess: (_resourceId, result) => projectLateCleanup(result),
2580
+ onLateFailure: (_resourceId, error2, result) => projectLateCleanup(result, error2)
2581
+ }) : void 0;
2582
+ record.cleanup = cleanup;
2583
+ record.scope = void 0;
2584
+ if (record.instanceId) instanceAttributes.delete(record.instanceId);
2585
+ record.instanceId = void 0;
2586
+ record.unitId = void 0;
2587
+ record.teardown = void 0;
2588
+ record.stopRequested = void 0;
2589
+ record.disposeCallbacks = [];
2590
+ const desired = record.pendingDesiredEnabled ?? desiredEnabledFor(record.manifest.id, record.manifest);
2591
+ record.pendingDesiredEnabled = void 0;
2592
+ const missing = missingDependencies(record.manifest);
2593
+ const unavailable = runtimeUnavailable(record.manifest);
2594
+ const incomplete = Boolean(cleanup?.pending.length && cleanup.errors.some((item) => item.code === "lifecycle.cleanup_timeout"));
2595
+ const error = teardownError ?? cleanup?.errors.find((item) => item.code !== "lifecycle.cleanup_timeout");
2596
+ if (incomplete) {
2597
+ record.state = "cleanup-pending";
2598
+ record.error = errorMessage4(error ?? "Plugin cleanup is still pending");
2599
+ } else if (error || cleanup?.cleanupIncomplete) {
2600
+ record.state = "error-disabled";
2601
+ record.error = errorMessage4(error ?? "Plugin cleanup did not complete");
2602
+ } else if (desired && (missing.length > 0 || unavailable)) {
2603
+ record.state = "blocked";
2604
+ record.blockedBy = [.../* @__PURE__ */ new Set([...missing, ...unavailable ? [unavailable] : []])];
2605
+ } else {
2606
+ record.state = "disabled";
2607
+ record.error = void 0;
2608
+ record.blockedBy = void 0;
2609
+ }
2610
+ if (!preserveIntent && !desired) {
2611
+ internalConfigWrites.add(record.manifest.id);
2612
+ try {
2613
+ configStore.setEnabled(record.manifest.id, false);
2614
+ } finally {
2615
+ internalConfigWrites.delete(record.manifest.id);
2616
+ }
2617
+ }
2618
+ record.preserveIntentOnStop = void 0;
2619
+ bumpVersion();
2620
+ if (desired && record.state === "disabled" && !hostDisposed) {
2621
+ queueMicrotask(() => {
2622
+ void enable(record.manifest.id).catch(() => void 0);
2623
+ });
2624
+ }
2625
+ };
2626
+ const preserveIntentOf = (record) => record.preserveIntentOnStop === true;
2627
+ const stopPlugin = async (record, reason, preserveIntent, blockedBy) => {
2628
+ const existing = stopping.get(record.manifest.id);
2629
+ if (existing) return existing;
2630
+ beginStop(record, reason, preserveIntent, blockedBy);
2631
+ const start = starting.get(record.manifest.id);
2632
+ const promise = (start ? start.catch(() => void 0) : Promise.resolve()).then(() => finishStop(record, reason)).finally(() => stopping.delete(record.manifest.id));
2633
+ stopping.set(record.manifest.id, promise);
2634
+ return promise;
2635
+ };
2636
+ const enable = async (pluginId) => {
2637
+ if (hostDisposed) throw new LifecycleScopeRevokedError("Plugin host is disposed");
2638
+ const record = records.get(pluginId);
2639
+ if (!record) throw new Error(`Plugin "${pluginId}" is not registered`);
2640
+ if (!options.pluginIntentCoordinator) {
2641
+ internalConfigWrites.add(pluginId);
2642
+ try {
2643
+ configStore.setEnabled(pluginId, true);
2644
+ } finally {
2645
+ internalConfigWrites.delete(pluginId);
2646
+ }
2647
+ }
2648
+ record.pendingDesiredEnabled = true;
2649
+ const existingStop = stopping.get(pluginId);
2650
+ if (existingStop) {
2651
+ await existingStop;
2652
+ }
2653
+ if (record.state === "enabled") return;
2654
+ const existing = starting.get(pluginId);
2655
+ if (existing) return existing;
2656
+ record.stopRequested = void 0;
2657
+ if (record.state === "cleanup-pending") {
2658
+ throw new Error(`Plugin "${pluginId}" cannot start while cleanup is pending`);
2659
+ }
2660
+ if (record.state === "error-disabled") {
2661
+ record.error = void 0;
2662
+ record.cleanup = void 0;
2663
+ }
2664
+ const task = (async () => {
2665
+ const missing = missingDependencies(record.manifest);
2666
+ const unavailable = runtimeUnavailable(record.manifest);
2667
+ if (missing.length > 0 || unavailable) {
2668
+ record.state = "blocked";
2669
+ record.blockedBy = [.../* @__PURE__ */ new Set([...missing, ...unavailable ? [unavailable] : []])];
2670
+ bumpVersion();
2671
+ return;
2672
+ }
2673
+ const unit = selected(record.manifest);
2674
+ if (!unit) {
2675
+ record.state = "blocked";
2676
+ record.blockedBy = [`runtime:${record.manifest.id}:unit-unavailable`];
2677
+ bumpVersion();
2678
+ return;
2679
+ }
2680
+ const resolution = resolveScope(record, unit);
2681
+ if (!resolution.available) {
2682
+ record.state = "blocked";
2683
+ record.blockedBy = [resolution.reason ?? `scope:${unit.lifetime}:unavailable`];
2684
+ bumpVersion();
2685
+ return;
2686
+ }
2687
+ const instanceId = makeInstanceId(record.manifest.id, unit.id);
2688
+ let scope;
2689
+ try {
2690
+ const parent = resolution.parent ?? rootScope;
2691
+ const attributes = Object.freeze({ ...resolution.attributes ?? rootAttributes });
2692
+ scope = parent.child(unit.lifetime || "plugin-instance", { pluginId: record.manifest.id, attributes });
2693
+ } catch (error) {
2694
+ record.state = "blocked";
2695
+ record.blockedBy = [errorMessage4(error)];
2696
+ bumpVersion();
2697
+ return;
2698
+ }
2699
+ record.state = "starting";
2700
+ record.scope = scope;
2701
+ record.instanceId = instanceId;
2702
+ record.unitId = unit.id;
2703
+ instanceAttributes.set(instanceId, scope.identity.attributes);
2704
+ record.scopeKey = scopeKeyForResolution(resolution, unit);
2705
+ record.error = void 0;
2706
+ record.blockedBy = void 0;
2707
+ bumpVersion();
2708
+ try {
2709
+ const setup = setupFor(record.manifest, unit, options);
2710
+ if (!setup) throw new Error(`Runtime implementation is unavailable for ${record.manifest.id}/${unit.id}`);
2711
+ const context = buildContext(record, unit, scope, instanceId);
2712
+ const result = await setup(context);
2713
+ record.teardown = typeof result === "function" ? result : void 0;
2714
+ await registerContribution(record, unit, instanceId, scope);
2715
+ const declared = providesOfManifest(record.manifest, options.execution);
2716
+ const missingDeclarations = declared.filter((key) => !record.capabilities.has(key));
2717
+ if (missingDeclarations.length > 0) {
2718
+ throw new Error(`Plugin "${record.manifest.id}" did not provide declared capabilities: ${missingDeclarations.join(", ")}`);
2719
+ }
2720
+ if (record.stopRequested || scope.state !== "active" || !desiredEnabledFor(record.manifest.id, record.manifest)) {
2721
+ return;
2722
+ }
2723
+ enabledSet.add(record.manifest.id);
2724
+ record.state = "enabled";
2725
+ record.pendingDesiredEnabled = void 0;
2726
+ bumpVersion();
2727
+ queueMicrotask(() => {
2728
+ void reconcile().catch(() => void 0);
2729
+ });
2730
+ } catch (error) {
2731
+ if (record.stopRequested || scope.state !== "active") {
2732
+ return;
2733
+ }
2734
+ record.state = "error-disabled";
2735
+ record.error = errorMessage4(error);
2736
+ record.scope?.revoke("plugin setup failed");
2737
+ revokeContributions(record);
2738
+ for (const capability of record.capabilities) capabilities.revoke(capability);
2739
+ record.capabilities.clear();
2740
+ record.cleanup = await scope.dispose({ reason: "plugin setup failed", timeoutMs: options.lifecycleCleanupTimeoutMs });
2741
+ record.scope = void 0;
2742
+ instanceAttributes.delete(instanceId);
2743
+ record.instanceId = void 0;
2744
+ record.unitId = void 0;
2745
+ bumpVersion();
2746
+ throw new StartupPluginError({ pluginId: record.manifest.id, capabilities: providesOfManifest(record.manifest, options.execution), state: record.state, error: record.error });
2747
+ }
2748
+ })();
2749
+ starting.set(pluginId, task);
2750
+ try {
2751
+ await task;
2752
+ } finally {
2753
+ starting.delete(pluginId);
2754
+ }
2755
+ };
2756
+ const collectDisablePlan = (pluginId) => {
2757
+ const plan = [];
2758
+ const visited = /* @__PURE__ */ new Set();
2759
+ const currentGraph = graph();
2760
+ const activePluginIds = /* @__PURE__ */ new Set([
2761
+ ...enabledSet,
2762
+ ...[...records.values()].filter((record) => record.state === "starting" || record.state === "stopping").map((record) => record.manifest.id)
2763
+ ]);
2764
+ const visit = (providerId) => {
2765
+ for (const dependent of reverseDependentsOf(currentGraph, providerId, activePluginIds)) {
2766
+ if (visited.has(dependent.pluginId)) continue;
2767
+ visited.add(dependent.pluginId);
2768
+ visit(dependent.pluginId);
2769
+ const dependentRecord = records.get(dependent.pluginId);
2770
+ if (dependentRecord) plan.push(dependentRecord);
2771
+ }
2772
+ };
2773
+ visit(pluginId);
2774
+ const target = records.get(pluginId);
2775
+ if (target) plan.push(target);
2776
+ return plan;
2777
+ };
2778
+ const reconcile = async () => {
2779
+ if (hostDisposed) return;
2780
+ if (reconcilePromise) return reconcilePromise;
2781
+ reconcilePromise = (async () => {
2782
+ const manifests = [...knownManifests.values()];
2783
+ const ordered = orderManifestsByDependencies(manifests, options.execution);
2784
+ const rotationTargets = manifests.filter((manifest) => {
2785
+ const record = records.get(manifest.id);
2786
+ if (!record || !desiredEnabledFor(manifest.id, manifest) || record.state !== "enabled" && record.state !== "starting") return false;
2787
+ const unit = selected(record.manifest);
2788
+ if (!unit || !options.scopeResolver) return false;
2789
+ const resolution = resolveScope(record, unit);
2790
+ return !resolution.available || record.scopeKey !== scopeKeyForResolution(resolution, unit);
2791
+ });
2792
+ const rotationPlan = [];
2793
+ const planned = /* @__PURE__ */ new Set();
2794
+ for (const target of rotationTargets) {
2795
+ for (const item of collectDisablePlan(target.id)) {
2796
+ if (planned.has(item.manifest.id)) continue;
2797
+ planned.add(item.manifest.id);
2798
+ rotationPlan.push(item);
2799
+ }
2800
+ }
2801
+ for (const item of rotationPlan) {
2802
+ await stopPlugin(item, "scope resolution changed", true);
2803
+ }
2804
+ for (const manifest of ordered) {
2805
+ const record = records.get(manifest.id);
2806
+ if (!record) continue;
2807
+ if (desiredEnabledFor(manifest.id, manifest) && record.state !== "enabled" && record.state !== "starting") {
2808
+ await enable(manifest.id);
2809
+ }
2810
+ }
2811
+ for (const manifest of manifests) {
2812
+ const record = records.get(manifest.id);
2813
+ if (record && !desiredEnabledFor(manifest.id, manifest) && (record.state === "enabled" || record.state === "starting")) {
2814
+ await stopPlugin(record, "desired intent disabled", false);
2815
+ }
2816
+ }
2817
+ })().finally(() => {
2818
+ reconcilePromise = void 0;
2819
+ });
2820
+ return reconcilePromise;
2821
+ };
2822
+ function orderManifestsByDependencies(manifests, execution) {
2823
+ const byId = new Map(manifests.map((manifest) => [manifest.id, manifest]));
2824
+ const providers = /* @__PURE__ */ new Map();
2825
+ for (const manifest of manifests) for (const capability of providesOfManifest(manifest, execution)) if (!providers.has(capability)) providers.set(capability, manifest.id);
2826
+ const visited = /* @__PURE__ */ new Set();
2827
+ const visiting = /* @__PURE__ */ new Set();
2828
+ const ordered = [];
2829
+ const visit = (manifest) => {
2830
+ if (visited.has(manifest.id)) return;
2831
+ if (visiting.has(manifest.id)) return;
2832
+ visiting.add(manifest.id);
2833
+ for (const dependency of dependenciesFor(manifest, execution)) {
2834
+ if (dependency.optional) continue;
2835
+ const provider = providers.get(dependency.capability);
2836
+ const providerManifest = provider ? byId.get(provider) : void 0;
2837
+ if (providerManifest) visit(providerManifest);
2838
+ }
2839
+ visiting.delete(manifest.id);
2840
+ visited.add(manifest.id);
2841
+ ordered.push(manifest);
2842
+ };
2843
+ for (const manifest of manifests) visit(manifest);
2844
+ return ordered;
2845
+ }
2846
+ const host = {
2847
+ capabilities,
2848
+ messageBus,
2849
+ resourceRegistry,
2850
+ resourceStore,
2851
+ rootScope,
2852
+ taskScheduler,
2853
+ installed: () => [...knownManifests.keys()],
2854
+ manifests: () => [...knownManifests.keys()],
2855
+ state(pluginId) {
2856
+ const record = records.get(pluginId);
2857
+ if (!record) return { id: pluginId, kind: "disabled", lifecycleState: "disabled" };
2858
+ const desired = desiredEnabledFor(pluginId, record.manifest);
2859
+ const unit = selected(record.manifest);
2860
+ return {
2861
+ id: pluginId,
2862
+ kind: record.state,
2863
+ lifecycleState: lifecycleStateFor(record.state, desired),
2864
+ ...record.error ? { error: record.error } : {},
2865
+ desiredEnabled: desired,
2866
+ ...desiredRevisionFor(pluginId) !== void 0 ? { desiredRevision: desiredRevisionFor(pluginId) } : {},
2867
+ ...record.instanceId ? { instanceId: record.instanceId } : {},
2868
+ ...record.unitId ? { unitId: record.unitId } : {},
2869
+ ...record.blockedBy ? { blockedBy: [...record.blockedBy] } : {},
2870
+ ...record.cleanup ? { cleanup: record.cleanup } : {},
2871
+ units: unit ? [{ pluginId, unitId: unit.id, execution: unit.execution, kind: record.state, ...record.instanceId ? { instanceId: record.instanceId } : {}, ...record.error ? { error: record.error } : {} }] : []
2872
+ };
2873
+ },
2874
+ scope: (pluginId) => records.get(pluginId)?.scope,
2875
+ refreshRuntimeUnitSnapshots() {
2876
+ runtimeSnapshots = options.runtimeSnapshots;
2877
+ bumpVersion();
2878
+ },
2879
+ reconcile,
2880
+ graph,
2881
+ version: () => versionCounter,
2882
+ subscribe(listener) {
2883
+ listeners.add(listener);
2884
+ return () => listeners.delete(listener);
2885
+ },
2886
+ getManifest: (pluginId) => knownManifests.get(pluginId),
2887
+ reverseDeps(pluginId) {
2888
+ return graph().reverse[pluginId] ?? [];
2889
+ },
2890
+ validateManifestSet(manifests) {
2891
+ for (const manifest of manifests) validateManifest(manifest);
2892
+ validatePluginGraph([...manifests], { execution: options.execution, builtinCapabilities: new Set(capabilities.keys()) });
2893
+ },
2894
+ provide(key, value) {
2895
+ if (hostDisposed) throw new LifecycleScopeRevokedError("Plugin host is disposed");
2896
+ capabilities.provide(key, value);
2897
+ bumpVersion();
2898
+ },
2899
+ register: async (manifest) => {
2900
+ if (hostDisposed) throw new LifecycleScopeRevokedError("Plugin host is disposed");
2901
+ validateManifest(manifest);
2902
+ if (knownManifests.has(manifest.id)) throw new Error(`Plugin "${manifest.id}" is already registered`);
2903
+ knownManifests.set(manifest.id, manifest);
2904
+ records.set(manifest.id, { manifest, state: "registered", disposeCallbacks: [], capabilities: /* @__PURE__ */ new Set(), contributions: [] });
2905
+ bumpVersion();
2906
+ if (desiredEnabledFor(manifest.id, manifest)) await enable(manifest.id);
2907
+ },
2908
+ registerAll: async (manifests) => {
2909
+ if (hostDisposed) throw new LifecycleScopeRevokedError("Plugin host is disposed");
2910
+ const current = [...manifests];
2911
+ for (const manifest of current) {
2912
+ validateManifest(manifest);
2913
+ if (knownManifests.has(manifest.id)) throw new Error(`Plugin "${manifest.id}" is already registered`);
2914
+ }
2915
+ validatePluginGraph(current, { execution: options.execution, builtinCapabilities: new Set(capabilities.keys()), allowMissingDependencies: false });
2916
+ for (const manifest of current) {
2917
+ knownManifests.set(manifest.id, manifest);
2918
+ records.set(manifest.id, { manifest, state: "registered", disposeCallbacks: [], capabilities: /* @__PURE__ */ new Set(), contributions: [] });
2919
+ }
2920
+ bumpVersion();
2921
+ await reconcile();
2922
+ },
2923
+ enable,
2924
+ retry: async (pluginId) => {
2925
+ const record = records.get(pluginId);
2926
+ if (!record) throw new Error(`Plugin "${pluginId}" is not registered`);
2927
+ record.error = void 0;
2928
+ record.cleanup = void 0;
2929
+ record.state = "registered";
2930
+ await enable(pluginId);
2931
+ },
2932
+ submitIntent: async (pluginId, desired) => {
2933
+ if (!knownManifests.has(pluginId)) throw new Error(`Plugin "${pluginId}" is not registered`);
2934
+ if (options.pluginIntentCoordinator) {
2935
+ const current = options.pluginIntentCoordinator.snapshot();
2936
+ const command = {
2937
+ commandId: `plugin-intent:${pluginId}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`,
2938
+ authorityInstanceId: options.pluginIntentCoordinator.authorityInstanceId,
2939
+ expectedRevision: current.revision,
2940
+ pluginId,
2941
+ desiredEnabled: desired
2942
+ };
2943
+ return options.pluginIntentCoordinator.submit(command);
2944
+ }
2945
+ internalConfigWrites.add(pluginId);
2946
+ try {
2947
+ configStore.setEnabled(pluginId, desired);
2948
+ } finally {
2949
+ internalConfigWrites.delete(pluginId);
2950
+ }
2951
+ if (desired) await enable(pluginId);
2952
+ else await host.disable(pluginId);
2953
+ const next = Object.freeze({
2954
+ revision: 0,
2955
+ desiredEnabled: { ...configStore.read() },
2956
+ desiredRevision: {}
2957
+ });
2958
+ return { status: "accepted", commandId: `local:${pluginId}`, snapshot: next, persisted: true };
2959
+ },
2960
+ disable: async (pluginId) => {
2961
+ const record = records.get(pluginId);
2962
+ if (!record) throw new Error(`Plugin "${pluginId}" is not registered`);
2963
+ if (isRequired(record.manifest)) return { ok: false, reason: `Plugin "${pluginId}" cannot be disabled` };
2964
+ if (!options.pluginIntentCoordinator) {
2965
+ internalConfigWrites.add(pluginId);
2966
+ try {
2967
+ configStore.setEnabled(pluginId, false);
2968
+ } finally {
2969
+ internalConfigWrites.delete(pluginId);
2970
+ }
2971
+ }
2972
+ const plan = collectDisablePlan(pluginId);
2973
+ for (const item of plan) beginStop(
2974
+ item,
2975
+ item.manifest.id === pluginId ? "plugin disabled" : `dependency ${pluginId} disabled`,
2976
+ item.manifest.id !== pluginId,
2977
+ item.manifest.id === pluginId ? void 0 : [pluginId]
2978
+ );
2979
+ for (const item of plan) await stopPlugin(
2980
+ item,
2981
+ item.manifest.id === pluginId ? "plugin disabled" : `dependency ${pluginId} disabled`,
2982
+ item.manifest.id !== pluginId,
2983
+ item.manifest.id === pluginId ? void 0 : [pluginId]
2984
+ );
2985
+ return { ok: true };
2986
+ },
2987
+ unregister: async (pluginId) => {
2988
+ const record = records.get(pluginId);
2989
+ if (!record) return;
2990
+ if (isRequired(record.manifest)) throw new Error(`Plugin "${pluginId}" cannot be unregistered`);
2991
+ await host.disable(pluginId);
2992
+ if (record.state === "cleanup-pending") throw new Error(`Plugin "${pluginId}" cleanup is still pending`);
2993
+ records.delete(pluginId);
2994
+ knownManifests.delete(pluginId);
2995
+ bumpVersion();
2996
+ },
2997
+ dispose: (reason = "plugin host disposed") => {
2998
+ if (disposePromise) return disposePromise;
2999
+ hostDisposed = true;
3000
+ removeConfigSubscription();
3001
+ removeIntentSubscription();
3002
+ removeScopeSubscription();
3003
+ disposePromise = (async () => {
3004
+ for (const record of [...records.values()].reverse()) {
3005
+ if (record.state === "enabled" || record.state === "starting") {
3006
+ await stopPlugin(record, reason, false);
3007
+ }
3008
+ }
3009
+ return rootScope.dispose({ reason, timeoutMs: options.lifecycleCleanupTimeoutMs });
3010
+ })();
3011
+ return disposePromise;
3012
+ },
3013
+ assertCapabilities(required, extra = {}) {
3014
+ const details = [];
3015
+ for (const capability of required) {
3016
+ if (capabilities.has(capability)) continue;
3017
+ const provider = graph().providers?.[capability]?.[0];
3018
+ const providerRecord = provider ? records.get(provider) : void 0;
3019
+ details.push({
3020
+ capability,
3021
+ providerPluginId: provider,
3022
+ providerState: providerRecord?.state,
3023
+ providerError: providerRecord?.error,
3024
+ configuredEnabled: providerRecord ? desiredEnabledFor(providerRecord.manifest.id, providerRecord.manifest) : void 0
3025
+ });
3026
+ }
3027
+ if (details.length > 0) throw new StartupCapabilityError(details, extra.phase ?? "startup");
3028
+ }
3029
+ };
3030
+ if (options.pluginIntentCoordinator) {
3031
+ removeIntentSubscription = options.pluginIntentCoordinator.subscribe((snapshot) => {
3032
+ intentSnapshot = snapshot;
3033
+ void reconcile().catch(() => void 0);
3034
+ bumpVersion();
3035
+ });
3036
+ }
3037
+ removeConfigSubscription = configStore.subscribe((snapshot) => {
3038
+ if (internalConfigWrites.size > 0) return;
3039
+ for (const [pluginId, manifest] of knownManifests) {
3040
+ if (manifest.meta.startup !== "required" && manifest.meta.canDisable !== false) continue;
3041
+ if (snapshot[pluginId] === true) continue;
3042
+ internalConfigWrites.add(pluginId);
3043
+ try {
3044
+ configStore.setEnabled(pluginId, true);
3045
+ } finally {
3046
+ internalConfigWrites.delete(pluginId);
3047
+ }
3048
+ }
3049
+ void reconcile().catch(() => void 0);
3050
+ bumpVersion();
3051
+ });
3052
+ if (options.scopeResolver?.subscribe) {
3053
+ removeScopeSubscription = options.scopeResolver.subscribe(() => {
3054
+ void reconcile().catch(() => void 0);
3055
+ resourceStore.refreshRuntimeBindings();
3056
+ bumpVersion();
3057
+ });
3058
+ }
3059
+ return host;
3060
+ }
3061
+
3062
+ // src/host/runtimeUnitImplementationRegistry.ts
3063
+ function implementationKey(pluginId, unitId) {
3064
+ return `${pluginId}\0${unitId}`;
3065
+ }
3066
+ function createRuntimeUnitImplementationRegistry(implementations = []) {
3067
+ const entries = /* @__PURE__ */ new Map();
3068
+ const register = (implementation) => {
3069
+ const key = implementationKey(implementation.pluginId, implementation.unitId);
3070
+ if (entries.has(key)) throw new Error(`\u8FD0\u884C\u5355\u5143\u5B9E\u73B0\u91CD\u590D\u6CE8\u518C: ${implementation.pluginId}/${implementation.unitId}`);
3071
+ entries.set(key, implementation.setup);
3072
+ };
3073
+ for (const implementation of implementations) register(implementation);
3074
+ return {
3075
+ get(pluginId, unitId) {
3076
+ return entries.get(implementationKey(pluginId, unitId));
3077
+ },
3078
+ register,
3079
+ unregister(pluginId, unitId) {
3080
+ entries.delete(implementationKey(pluginId, unitId));
3081
+ }
3082
+ };
3083
+ }
3084
+
3085
+ // src/lifecycle/pluginIntentController.ts
3086
+ function makeAuthorityInstanceId() {
3087
+ try {
3088
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
3089
+ return `authority:${crypto.randomUUID()}`;
3090
+ }
3091
+ } catch {
3092
+ }
3093
+ return `authority:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
3094
+ }
3095
+ function cloneSnapshot(snapshot) {
3096
+ return {
3097
+ revision: snapshot.revision,
3098
+ desiredEnabled: { ...snapshot.desiredEnabled },
3099
+ desiredRevision: { ...snapshot.desiredRevision }
3100
+ };
3101
+ }
3102
+ function normalizeSnapshot(initial) {
3103
+ const revision = Number.isSafeInteger(initial?.revision) && (initial?.revision ?? 0) >= 0 ? initial.revision : 0;
3104
+ const desiredEnabled = {};
3105
+ for (const [pluginId, value] of Object.entries(initial?.desiredEnabled ?? {})) {
3106
+ if (typeof value === "boolean") desiredEnabled[pluginId] = value;
3107
+ }
3108
+ const desiredRevision = {};
3109
+ for (const [pluginId, value] of Object.entries(initial?.desiredRevision ?? {})) {
3110
+ if (Number.isSafeInteger(value) && value >= 0) desiredRevision[pluginId] = value;
3111
+ }
3112
+ return { revision, desiredEnabled, desiredRevision };
3113
+ }
3114
+ function commandFingerprint(command) {
3115
+ return [
3116
+ command.authorityInstanceId,
3117
+ command.expectedRevision,
3118
+ command.pluginId,
3119
+ command.desiredEnabled ? "true" : "false"
3120
+ ].join("\0");
3121
+ }
3122
+ function commandError(message) {
3123
+ return { status: "command-conflict", commandId: "", message };
3124
+ }
3125
+ function createPluginIntentController(options = {}) {
3126
+ const authorityInstanceId = options.authorityInstanceId ?? makeAuthorityInstanceId();
3127
+ const maxCommandRecords = Math.max(1, Math.floor(options.maxCommandRecords ?? 256));
3128
+ let current = normalizeSnapshot(options.initial);
3129
+ const commandRecords = /* @__PURE__ */ new Map();
3130
+ const listeners = /* @__PURE__ */ new Set();
3131
+ let queue = Promise.resolve();
3132
+ const notify = () => {
3133
+ const snapshot = cloneSnapshot(current);
3134
+ for (const listener of [...listeners]) {
3135
+ try {
3136
+ listener(snapshot);
3137
+ } catch {
3138
+ }
3139
+ }
3140
+ };
3141
+ const process = async (command) => {
3142
+ const candidate = command;
3143
+ const commandId = typeof candidate?.commandId === "string" ? candidate.commandId : "";
3144
+ if (!candidate || typeof candidate.authorityInstanceId !== "string" || candidate.authorityInstanceId.length === 0 || typeof candidate.pluginId !== "string" || candidate.pluginId.length === 0 || typeof candidate.desiredEnabled !== "boolean" || !Number.isSafeInteger(candidate.expectedRevision) || (candidate.expectedRevision ?? -1) < 0 || commandId.length === 0) {
3145
+ const result = commandError("commandId\u3001pluginId \u548C expectedRevision \u5FC5\u987B\u662F\u6709\u6548\u503C");
3146
+ return { ...result, commandId };
3147
+ }
3148
+ if (command.authorityInstanceId !== authorityInstanceId) {
3149
+ return {
3150
+ status: "stale-authority",
3151
+ commandId: command.commandId,
3152
+ expectedAuthorityInstanceId: authorityInstanceId
3153
+ };
3154
+ }
3155
+ const fingerprint = commandFingerprint(command);
3156
+ const existing = commandRecords.get(command.commandId);
3157
+ if (existing) {
3158
+ if (existing.fingerprint !== fingerprint) {
3159
+ return {
3160
+ status: "command-conflict",
3161
+ commandId: command.commandId,
3162
+ message: "\u76F8\u540C commandId \u7684\u547D\u4EE4\u5185\u5BB9\u4E0D\u540C\uFF0C\u62D2\u7EDD\u8986\u76D6\u539F\u547D\u4EE4"
3163
+ };
3164
+ }
3165
+ return {
3166
+ status: "duplicate",
3167
+ commandId: command.commandId,
3168
+ snapshot: cloneSnapshot(existing.result.snapshot),
3169
+ persisted: true
3170
+ };
3171
+ }
3172
+ if (command.expectedRevision !== current.revision) {
3173
+ return {
3174
+ status: "revision-conflict",
3175
+ commandId: command.commandId,
3176
+ snapshot: cloneSnapshot(current)
3177
+ };
3178
+ }
3179
+ const next = {
3180
+ revision: current.revision + 1,
3181
+ desiredEnabled: {
3182
+ ...current.desiredEnabled,
3183
+ [command.pluginId]: command.desiredEnabled
3184
+ },
3185
+ desiredRevision: {
3186
+ ...current.desiredRevision,
3187
+ [command.pluginId]: (current.desiredRevision[command.pluginId] ?? 0) + 1
3188
+ }
3189
+ };
3190
+ try {
3191
+ await options.persist?.(cloneSnapshot(next));
3192
+ } catch (error) {
3193
+ return {
3194
+ status: "persistence-failed",
3195
+ commandId: command.commandId,
3196
+ message: error instanceof Error ? error.message : String(error),
3197
+ snapshot: cloneSnapshot(current)
3198
+ };
3199
+ }
3200
+ current = next;
3201
+ const accepted = {
3202
+ status: "accepted",
3203
+ commandId: command.commandId,
3204
+ snapshot: cloneSnapshot(current),
3205
+ persisted: true
3206
+ };
3207
+ commandRecords.set(command.commandId, {
3208
+ fingerprint,
3209
+ result: accepted
3210
+ });
3211
+ while (commandRecords.size > maxCommandRecords) {
3212
+ const oldest = commandRecords.keys().next().value;
3213
+ if (oldest === void 0) break;
3214
+ commandRecords.delete(oldest);
3215
+ }
3216
+ notify();
3217
+ return {
3218
+ status: "accepted",
3219
+ commandId: accepted.commandId,
3220
+ snapshot: cloneSnapshot(accepted.snapshot),
3221
+ persisted: true
3222
+ };
3223
+ };
3224
+ const controller = {
3225
+ authorityInstanceId,
3226
+ snapshot: () => cloneSnapshot(current),
3227
+ submit(command) {
3228
+ const result = queue.then(() => process(command));
3229
+ queue = result.then(() => void 0, () => void 0);
3230
+ return result;
3231
+ },
3232
+ subscribe(listener) {
3233
+ listeners.add(listener);
3234
+ return () => listeners.delete(listener);
3235
+ }
3236
+ };
3237
+ return controller;
3238
+ }
3239
+
3240
+ // src/transport/serviceBridge.ts
3241
+ function makeRequestId() {
3242
+ try {
3243
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
3244
+ return `service-request:${crypto.randomUUID()}`;
3245
+ }
3246
+ } catch {
3247
+ }
3248
+ return `service-request:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
3249
+ }
3250
+ function mergeSignals2(...signals) {
3251
+ const activeSignals = signals.filter((signal) => signal !== void 0);
3252
+ if (activeSignals.length === 0) {
3253
+ return { signal: new AbortController().signal, dispose: () => void 0 };
3254
+ }
3255
+ if (activeSignals.length === 1) {
3256
+ const [signal] = activeSignals;
3257
+ if (signal) return { signal, dispose: () => void 0 };
3258
+ }
3259
+ const alreadyAborted = activeSignals.find((signal) => signal.aborted);
3260
+ if (alreadyAborted) {
3261
+ const controller2 = new AbortController();
3262
+ controller2.abort(alreadyAborted.reason);
3263
+ return { signal: controller2.signal, dispose: () => void 0 };
3264
+ }
3265
+ const controller = new AbortController();
3266
+ const abort = (source) => {
3267
+ try {
3268
+ controller.abort(source.reason);
3269
+ } catch {
3270
+ controller.abort();
3271
+ }
3272
+ };
3273
+ const listeners = activeSignals.map((signal) => {
3274
+ const listener = () => abort(signal);
3275
+ signal.addEventListener("abort", listener, { once: true });
3276
+ return { signal, listener };
3277
+ });
3278
+ return {
3279
+ signal: controller.signal,
3280
+ dispose: () => {
3281
+ for (const item of listeners) item.signal.removeEventListener("abort", item.listener);
3282
+ }
3283
+ };
3284
+ }
3285
+ function stableAttributes(value) {
3286
+ const normalize = (input) => {
3287
+ if (Array.isArray(input)) return input.map(normalize);
3288
+ if (!input || typeof input !== "object") return input;
3289
+ return Object.fromEntries(
3290
+ Object.entries(input).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, normalize(item)])
3291
+ );
3292
+ };
3293
+ return JSON.stringify(normalize(value));
3294
+ }
3295
+ function referenceKey(reference) {
3296
+ return [
3297
+ reference.capabilityId,
3298
+ reference.providerInstanceId,
3299
+ reference.execution,
3300
+ reference.contractVersion,
3301
+ reference.authorityInstanceId,
3302
+ reference.scopeId,
3303
+ reference.handoverGeneration,
3304
+ stableAttributes(reference.attributes),
3305
+ reference.grantId ?? "null",
3306
+ reference.authorizationRevision ?? "null",
3307
+ // 代理绑定的是某一份权威目录快照;目录修订后即使 Provider 实例未变,
3308
+ // 旧代理也不能继续使用旧授权视图,必须重新取得当前引用。
3309
+ reference.snapshotRevision
3310
+ ].join("\0");
3311
+ }
3312
+ function lookupMatches(reference, lookup) {
3313
+ return reference.status === "ready" && reference.capabilityId === lookup.capabilityId && reference.contractVersion === lookup.contractVersion && (lookup.execution === void 0 || reference.execution === lookup.execution) && (lookup.scopeId === void 0 || reference.scopeId === lookup.scopeId);
3314
+ }
3315
+ function createServiceBridge(options) {
3316
+ let currentState = "disconnected";
3317
+ let currentConnectionId;
3318
+ let currentAuthorityInstanceId;
3319
+ let lastRevision;
3320
+ let hasBaseline = false;
3321
+ const references = /* @__PURE__ */ new Map();
3322
+ const proxies = /* @__PURE__ */ new Set();
3323
+ const listeners = /* @__PURE__ */ new Set();
3324
+ const notify = () => {
3325
+ for (const listener of [...listeners]) {
3326
+ try {
3327
+ listener();
3328
+ } catch {
3329
+ }
3330
+ }
3331
+ };
3332
+ const revokeProxy = (proxy, reason) => {
3333
+ if (proxy.revoked) return;
3334
+ proxy.revoked = true;
3335
+ proxy.reason = reason;
3336
+ try {
3337
+ proxy.controller.abort(new RemoteServiceUnavailableError(reason));
3338
+ } catch {
3339
+ proxy.controller.abort();
3340
+ }
3341
+ };
3342
+ const invalidateProxies = (reason, keep) => {
3343
+ for (const proxy of proxies) {
3344
+ if (!keep?.has(referenceKey(proxy.reference))) {
3345
+ revokeProxy(proxy, reason);
3346
+ proxies.delete(proxy);
3347
+ }
3348
+ }
3349
+ };
3350
+ const clearReferences = (reason) => {
3351
+ references.clear();
3352
+ invalidateProxies(reason);
3353
+ };
3354
+ const bridge = {
3355
+ get state() {
3356
+ return currentState;
3357
+ },
3358
+ get connectionId() {
3359
+ return currentConnectionId;
3360
+ },
3361
+ get authorityInstanceId() {
3362
+ return currentAuthorityInstanceId;
3363
+ },
3364
+ handshake(input) {
3365
+ if (input.protocolVersion !== options.protocolVersion) {
3366
+ clearReferences("service bridge protocol mismatch");
3367
+ currentConnectionId = void 0;
3368
+ currentAuthorityInstanceId = void 0;
3369
+ lastRevision = void 0;
3370
+ hasBaseline = false;
3371
+ currentState = "disconnected";
3372
+ notify();
3373
+ return { accepted: false, reason: "protocol-mismatch" };
3374
+ }
3375
+ const isNewBinding = currentConnectionId !== input.connectionId || currentAuthorityInstanceId !== input.authorityInstanceId;
3376
+ if (isNewBinding) {
3377
+ clearReferences("remote service authority or connection changed");
3378
+ lastRevision = void 0;
3379
+ hasBaseline = false;
3380
+ }
3381
+ currentConnectionId = input.connectionId;
3382
+ currentAuthorityInstanceId = input.authorityInstanceId;
3383
+ currentState = "handshaking";
3384
+ notify();
3385
+ return { accepted: true };
3386
+ },
3387
+ applySnapshot(snapshot) {
3388
+ if (snapshot.connectionId !== currentConnectionId) {
3389
+ return {
3390
+ accepted: false,
3391
+ reason: "wrong-connection",
3392
+ receivedRevision: snapshot.snapshotRevision
3393
+ };
3394
+ }
3395
+ if (snapshot.authorityInstanceId !== currentAuthorityInstanceId) {
3396
+ return {
3397
+ accepted: false,
3398
+ reason: "wrong-authority",
3399
+ receivedRevision: snapshot.snapshotRevision
3400
+ };
3401
+ }
3402
+ if (!Number.isSafeInteger(snapshot.snapshotRevision) || snapshot.snapshotRevision < 0) {
3403
+ return {
3404
+ accepted: false,
3405
+ reason: "stale-revision",
3406
+ receivedRevision: snapshot.snapshotRevision
3407
+ };
3408
+ }
3409
+ if (!snapshot.baseline && !hasBaseline) {
3410
+ currentState = "stale";
3411
+ clearReferences("service bridge baseline required");
3412
+ notify();
3413
+ return {
3414
+ accepted: false,
3415
+ reason: "baseline-required",
3416
+ receivedRevision: snapshot.snapshotRevision
3417
+ };
3418
+ }
3419
+ if (lastRevision !== void 0 && snapshot.snapshotRevision <= lastRevision) {
3420
+ return {
3421
+ accepted: false,
3422
+ reason: "stale-revision",
3423
+ receivedRevision: snapshot.snapshotRevision
3424
+ };
3425
+ }
3426
+ if (!snapshot.baseline && lastRevision !== void 0 && snapshot.snapshotRevision !== lastRevision + 1) {
3427
+ currentState = "stale";
3428
+ clearReferences("service bridge revision gap");
3429
+ hasBaseline = false;
3430
+ notify();
3431
+ return {
3432
+ accepted: false,
3433
+ reason: "revision-gap",
3434
+ expectedRevision: lastRevision + 1,
3435
+ receivedRevision: snapshot.snapshotRevision
3436
+ };
3437
+ }
3438
+ const next = /* @__PURE__ */ new Map();
3439
+ for (const reference of snapshot.services) {
3440
+ if (reference.authorityInstanceId !== currentAuthorityInstanceId || reference.snapshotRevision !== snapshot.snapshotRevision) continue;
3441
+ next.set(referenceKey(reference), Object.freeze({ ...reference }));
3442
+ }
3443
+ references.clear();
3444
+ for (const [key, reference] of next) references.set(key, reference);
3445
+ const validKeys = new Set(
3446
+ [...next.values()].filter((reference) => reference.status === "ready").map(referenceKey)
3447
+ );
3448
+ invalidateProxies("remote service reference is no longer ready", validKeys);
3449
+ lastRevision = snapshot.snapshotRevision;
3450
+ hasBaseline = true;
3451
+ currentState = "ready";
3452
+ notify();
3453
+ return {
3454
+ accepted: true,
3455
+ state: currentState,
3456
+ snapshotRevision: snapshot.snapshotRevision
3457
+ };
3458
+ },
3459
+ getProxy(lookup, scope) {
3460
+ if (currentState !== "ready") return void 0;
3461
+ const matches = [...references.values()].filter((candidate) => lookupMatches(candidate, lookup));
3462
+ if (matches.length > 1) {
3463
+ throw new RemoteServiceUnavailableError(
3464
+ `Service reference is ambiguous for "${lookup.capabilityId}" version "${lookup.contractVersion}"`
3465
+ );
3466
+ }
3467
+ const reference = matches[0];
3468
+ if (!reference) return void 0;
3469
+ const record = {
3470
+ reference,
3471
+ revoked: false,
3472
+ reason: "remote service proxy revoked",
3473
+ controller: new AbortController()
3474
+ };
3475
+ proxies.add(record);
3476
+ let removeScopeRevoke;
3477
+ let removeScopeDispose;
3478
+ let proxy;
3479
+ const revokeFromScope = (reason) => {
3480
+ revokeProxy(record, reason);
3481
+ proxies.delete(record);
3482
+ removeScopeRevoke?.();
3483
+ removeScopeDispose?.();
3484
+ };
3485
+ if (scope) {
3486
+ try {
3487
+ removeScopeRevoke = scope.onRevoke(revokeFromScope);
3488
+ removeScopeDispose = scope.onDispose(revokeFromScope, `service-proxy:${reference.capabilityId}`);
3489
+ } catch {
3490
+ record.revoked = true;
3491
+ }
3492
+ }
3493
+ if (record.revoked) {
3494
+ proxies.delete(record);
3495
+ removeScopeRevoke?.();
3496
+ removeScopeDispose?.();
3497
+ return void 0;
3498
+ }
3499
+ proxy = {
3500
+ reference,
3501
+ get revoked() {
3502
+ return record.revoked;
3503
+ },
3504
+ async call(request, callOptions = {}) {
3505
+ if (record.revoked) throw new RemoteServiceUnavailableError(record.reason);
3506
+ const current = references.get(referenceKey(reference));
3507
+ if (currentState !== "ready" || !current || current.status !== "ready") {
3508
+ record.revoked = true;
3509
+ record.reason = "Remote service reference is no longer ready";
3510
+ throw new RemoteServiceUnavailableError(record.reason);
3511
+ }
3512
+ const merged = mergeSignals2(scope?.signal, record.controller.signal, callOptions.signal);
3513
+ const connectionId = currentConnectionId;
3514
+ if (!connectionId) {
3515
+ merged.dispose();
3516
+ throw new RemoteServiceUnavailableError("Remote service connection is no longer active");
3517
+ }
3518
+ const context = {
3519
+ // requestId 只作为旧调用方的业务 operationId 别名;真正的
3520
+ // MessagePort callId 由传输层每次调用单独生成。
3521
+ operationId: callOptions.operationId ?? callOptions.requestId ?? makeRequestId(),
3522
+ connectionId,
3523
+ reference,
3524
+ grantId: reference.grantId,
3525
+ signal: merged.signal
3526
+ };
3527
+ let result;
3528
+ try {
3529
+ result = Promise.resolve(options.transport.call(request, context));
3530
+ } catch (error) {
3531
+ merged.dispose();
3532
+ throw error;
3533
+ }
3534
+ try {
3535
+ const value = await Promise.race([
3536
+ result,
3537
+ new Promise((_, reject) => {
3538
+ if (merged.signal.aborted) {
3539
+ reject(merged.signal.reason ?? new RemoteServiceUnavailableError("Remote service request aborted"));
3540
+ return;
3541
+ }
3542
+ const onAbort = () => reject(merged.signal.reason ?? new RemoteServiceUnavailableError("Remote service request aborted"));
3543
+ merged.signal.addEventListener("abort", onAbort, { once: true });
3544
+ result.finally(() => merged.signal.removeEventListener("abort", onAbort)).catch(() => void 0);
3545
+ })
3546
+ ]);
3547
+ if (record.revoked || currentState !== "ready" || references.get(referenceKey(reference))?.status !== "ready") {
3548
+ throw new RemoteServiceUnavailableError("Remote service request belongs to an invalidated proxy");
3549
+ }
3550
+ return value;
3551
+ } finally {
3552
+ merged.dispose();
3553
+ if (record.revoked) {
3554
+ proxies.delete(record);
3555
+ removeScopeRevoke?.();
3556
+ removeScopeDispose?.();
3557
+ }
3558
+ }
3559
+ },
3560
+ revoke(reason = "remote service proxy revoked") {
3561
+ revokeProxy(record, reason);
3562
+ proxies.delete(record);
3563
+ removeScopeRevoke?.();
3564
+ removeScopeDispose?.();
3565
+ }
3566
+ };
3567
+ return proxy;
3568
+ },
3569
+ requireProxy(lookup, scope) {
3570
+ const proxy = bridge.getProxy(lookup, scope);
3571
+ if (!proxy) {
3572
+ throw new RemoteServiceUnavailableError(
3573
+ `Service "${lookup.capabilityId}" version "${lookup.contractVersion}" is not ready`
3574
+ );
3575
+ }
3576
+ return proxy;
3577
+ },
3578
+ invalidate(reason = "remote service bridge invalidated") {
3579
+ clearReferences(reason);
3580
+ currentState = "stale";
3581
+ lastRevision = void 0;
3582
+ hasBaseline = false;
3583
+ notify();
3584
+ },
3585
+ disconnect(reason = "remote service connection disconnected") {
3586
+ clearReferences(reason);
3587
+ currentConnectionId = void 0;
3588
+ currentAuthorityInstanceId = void 0;
3589
+ lastRevision = void 0;
3590
+ hasBaseline = false;
3591
+ currentState = "disconnected";
3592
+ notify();
3593
+ },
3594
+ subscribe(listener) {
3595
+ listeners.add(listener);
3596
+ return () => listeners.delete(listener);
3597
+ },
3598
+ services() {
3599
+ return [...references.values()];
3600
+ }
3601
+ };
3602
+ return bridge;
3603
+ }
3604
+ var createRemoteServiceBridge = createServiceBridge;
3605
+
3606
+ // src/transport/messagePortServiceTransport.ts
3607
+ var nextTransportId = 0;
3608
+ var nextCallSequence = 0;
3609
+ function makeTransportId() {
3610
+ nextTransportId += 1;
3611
+ return nextTransportId;
3612
+ }
3613
+ function makeCallId(transportId) {
3614
+ nextCallSequence += 1;
3615
+ return `remote-call:${transportId}:${nextCallSequence}`;
3616
+ }
3617
+ function errorFromWire(input) {
3618
+ const error = new Error(
3619
+ typeof input?.message === "string" ? input.message : "Remote service call failed"
3620
+ );
3621
+ if (typeof input?.name === "string" && input.name.length > 0) error.name = input.name;
3622
+ if (typeof input?.code === "string" && input.code.length > 0) {
3623
+ Object.defineProperty(error, "code", {
3624
+ configurable: true,
3625
+ enumerable: true,
3626
+ value: input.code,
3627
+ writable: false
3628
+ });
3629
+ }
3630
+ return error;
3631
+ }
3632
+ function abortReason(signal) {
3633
+ return signal.reason ?? new RemoteServiceUnavailableError("Remote service request aborted");
3634
+ }
3635
+ function isResponseMessage(input, codec) {
3636
+ if (!input || typeof input !== "object") return false;
3637
+ const message = input;
3638
+ return (message.type === codec.type("result") || message.type === codec.type("error")) && typeof message.callId === "string" && typeof message.connectionId === "string" && typeof message.providerInstanceId === "string";
3639
+ }
3640
+ function createMessagePortServiceTransport(options) {
3641
+ const pending = /* @__PURE__ */ new Map();
3642
+ let disposed = false;
3643
+ const transportId = makeTransportId();
3644
+ const codec = options.codec ?? createRemoteServiceMessageCodec();
3645
+ const onMessage = (event) => {
3646
+ const decoded = codec.decode(event.data);
3647
+ if (!isResponseMessage(decoded, codec)) return;
3648
+ const call = pending.get(decoded.callId);
3649
+ if (!call) return;
3650
+ if (decoded.connectionId !== call.connectionId || decoded.providerInstanceId !== call.providerInstanceId) return;
3651
+ pending.delete(decoded.callId);
3652
+ call.removeAbort();
3653
+ if (decoded.type === codec.type("error")) {
3654
+ call.reject(errorFromWire(decoded.error));
3655
+ } else {
3656
+ call.resolve(decoded.result);
3657
+ }
3658
+ };
3659
+ options.port.addEventListener("message", onMessage);
3660
+ options.port.start();
3661
+ const dispose = () => {
3662
+ if (disposed) return;
3663
+ disposed = true;
3664
+ options.port.removeEventListener("message", onMessage);
3665
+ for (const [callId, call] of pending) {
3666
+ pending.delete(callId);
3667
+ call.removeAbort();
3668
+ call.reject(new RemoteServiceUnavailableError("Remote service transport disposed"));
3669
+ }
3670
+ if (options.closeOnDispose) options.port.close();
3671
+ };
3672
+ const transport = {
3673
+ call(request, context) {
3674
+ if (disposed) return Promise.reject(new RemoteServiceUnavailableError("Remote service transport disposed"));
3675
+ if (context.signal.aborted) return Promise.reject(abortReason(context.signal));
3676
+ const callId = makeCallId(transportId);
3677
+ const providerInstanceId = context.reference.providerInstanceId;
3678
+ return new Promise((resolve, reject) => {
3679
+ let settled = false;
3680
+ const finish = (callback) => {
3681
+ if (settled) return;
3682
+ settled = true;
3683
+ callback();
3684
+ };
3685
+ const onAbort = () => {
3686
+ if (!pending.delete(callId)) return;
3687
+ finish(() => reject(abortReason(context.signal)));
3688
+ try {
3689
+ const cancel = {
3690
+ type: codec.type("cancel"),
3691
+ callId,
3692
+ connectionId: context.connectionId,
3693
+ providerInstanceId
3694
+ };
3695
+ options.port.postMessage(codec.encode(cancel));
3696
+ } catch {
3697
+ }
3698
+ };
3699
+ const pendingCall = {
3700
+ connectionId: context.connectionId,
3701
+ providerInstanceId,
3702
+ resolve: (value) => finish(() => resolve(value)),
3703
+ reject: (error) => finish(() => reject(error)),
3704
+ removeAbort: () => context.signal.removeEventListener("abort", onAbort)
3705
+ };
3706
+ pending.set(callId, pendingCall);
3707
+ context.signal.addEventListener("abort", onAbort, { once: true });
3708
+ try {
3709
+ const message = {
3710
+ type: codec.type("call"),
3711
+ callId,
3712
+ connectionId: context.connectionId,
3713
+ providerInstanceId,
3714
+ ...context.operationId ? { operationId: context.operationId } : {},
3715
+ ...context.grantId ?? context.reference.grantId ? { grantId: context.grantId ?? context.reference.grantId } : {},
3716
+ reference: context.reference,
3717
+ request
3718
+ };
3719
+ const transfer = options.transferForRequest?.(request, context) ?? [];
3720
+ options.port.postMessage(codec.encode(message), [...transfer]);
3721
+ if (context.signal.aborted) onAbort();
3722
+ } catch (error) {
3723
+ pending.delete(callId);
3724
+ pendingCall.removeAbort();
3725
+ pendingCall.reject(error);
3726
+ }
3727
+ });
3728
+ },
3729
+ dispose
3730
+ };
3731
+ return transport;
3732
+ }
3733
+
3734
+ export { LIFECYCLE_ERROR_TEXT, LifecycleScopeRevokedError, PermissionDeniedError, PermissionLeaseRevokedError, PluginGraphValidationError, RESOURCE_OWNER, RESOURCE_REGISTRY_CAPABILITY, RUNTIME_MESSAGE_BUS, RemoteServiceUnavailableError, SCOPED_TASK_SCHEDULER_CAPABILITY, StartupCapabilityError, StartupPluginError, UpgradeGateRejectedError, buildPluginGraph, createCapabilityRegistry, createInMemoryPluginConfigStore, createLifecycleScope, createMessageBus, createMessagePortServiceTransport, createPermissionLease, createPluginHost, createPluginIntentController, createRemoteServiceBridge, createRemoteServiceMessageCodec, createResourceRegistry, createResourceScope, createResourceStore, createRuntimeUnitImplementationRegistry, createScopedMessageBus, createScopedTaskScheduler, createServiceBridge, dependenciesOfManifest, lifecycleErrorText, providesOfManifest, registerOwnedResource, reverseDependentsOf, validatePluginGraph, validateRuntimeUnitDependencyContracts };
3735
+ //# sourceMappingURL=chunk-KGNF36DZ.js.map
3736
+ //# sourceMappingURL=chunk-KGNF36DZ.js.map