@lokvis/runtime 0.2.2 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/dist/asset-store.d.ts +13 -0
  2. package/dist/asset-store.d.ts.map +1 -1
  3. package/dist/asset-store.js +15 -2
  4. package/dist/asset-store.js.map +1 -1
  5. package/dist/batch-processor.d.ts +32 -161
  6. package/dist/batch-processor.d.ts.map +1 -1
  7. package/dist/batch-processor.js +107 -333
  8. package/dist/batch-processor.js.map +1 -1
  9. package/dist/batch-progress.d.ts +94 -0
  10. package/dist/batch-progress.d.ts.map +1 -0
  11. package/dist/batch-progress.js +128 -0
  12. package/dist/batch-progress.js.map +1 -0
  13. package/dist/batch-scheduler.d.ts +70 -0
  14. package/dist/batch-scheduler.d.ts.map +1 -0
  15. package/dist/batch-scheduler.js +147 -0
  16. package/dist/batch-scheduler.js.map +1 -0
  17. package/dist/browser-detect.d.ts +115 -0
  18. package/dist/browser-detect.d.ts.map +1 -0
  19. package/dist/browser-detect.js +160 -0
  20. package/dist/browser-detect.js.map +1 -0
  21. package/dist/concurrency-controller.d.ts +31 -0
  22. package/dist/concurrency-controller.d.ts.map +1 -0
  23. package/dist/concurrency-controller.js +33 -0
  24. package/dist/concurrency-controller.js.map +1 -0
  25. package/dist/errors.d.ts +51 -0
  26. package/dist/errors.d.ts.map +1 -0
  27. package/dist/errors.js +74 -0
  28. package/dist/errors.js.map +1 -0
  29. package/dist/executor.d.ts +9 -0
  30. package/dist/executor.d.ts.map +1 -1
  31. package/dist/executor.js +102 -48
  32. package/dist/executor.js.map +1 -1
  33. package/dist/history.d.ts.map +1 -1
  34. package/dist/history.js +3 -2
  35. package/dist/history.js.map +1 -1
  36. package/dist/idb-asset-store.d.ts.map +1 -1
  37. package/dist/idb-asset-store.js +6 -1
  38. package/dist/idb-asset-store.js.map +1 -1
  39. package/dist/index.d.ts +17 -15
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +20 -15
  42. package/dist/index.js.map +1 -1
  43. package/dist/managers/asset-manager.d.ts +114 -0
  44. package/dist/managers/asset-manager.d.ts.map +1 -0
  45. package/dist/managers/asset-manager.js +238 -0
  46. package/dist/managers/asset-manager.js.map +1 -0
  47. package/dist/managers/history-manager.d.ts +100 -0
  48. package/dist/managers/history-manager.d.ts.map +1 -0
  49. package/dist/managers/history-manager.js +296 -0
  50. package/dist/managers/history-manager.js.map +1 -0
  51. package/dist/managers/mcp-manifest-builder.d.ts +18 -0
  52. package/dist/managers/mcp-manifest-builder.d.ts.map +1 -0
  53. package/dist/managers/mcp-manifest-builder.js +99 -0
  54. package/dist/managers/mcp-manifest-builder.js.map +1 -0
  55. package/dist/managers/quota-manager.d.ts +42 -0
  56. package/dist/managers/quota-manager.d.ts.map +1 -0
  57. package/dist/managers/quota-manager.js +126 -0
  58. package/dist/managers/quota-manager.js.map +1 -0
  59. package/dist/managers/workflow-coordinator.d.ts +62 -0
  60. package/dist/managers/workflow-coordinator.d.ts.map +1 -0
  61. package/dist/managers/workflow-coordinator.js +125 -0
  62. package/dist/managers/workflow-coordinator.js.map +1 -0
  63. package/dist/opfs-asset-store.d.ts.map +1 -1
  64. package/dist/opfs-asset-store.js +12 -2
  65. package/dist/opfs-asset-store.js.map +1 -1
  66. package/dist/plugin-context.d.ts +35 -0
  67. package/dist/plugin-context.d.ts.map +1 -0
  68. package/dist/plugin-context.js +62 -0
  69. package/dist/plugin-context.js.map +1 -0
  70. package/dist/plugin-permissions.d.ts +99 -0
  71. package/dist/plugin-permissions.d.ts.map +1 -0
  72. package/dist/plugin-permissions.js +205 -0
  73. package/dist/plugin-permissions.js.map +1 -0
  74. package/dist/runtime-impl.d.ts +116 -0
  75. package/dist/runtime-impl.d.ts.map +1 -0
  76. package/dist/runtime-impl.js +270 -0
  77. package/dist/runtime-impl.js.map +1 -0
  78. package/dist/runtime.d.ts +10 -233
  79. package/dist/runtime.d.ts.map +1 -1
  80. package/dist/runtime.js +12 -978
  81. package/dist/runtime.js.map +1 -1
  82. package/dist/test-utils/fakes.d.ts +35 -0
  83. package/dist/test-utils/fakes.d.ts.map +1 -0
  84. package/dist/test-utils/fakes.js +60 -0
  85. package/dist/test-utils/fakes.js.map +1 -0
  86. package/dist/types.d.ts +113 -1
  87. package/dist/types.d.ts.map +1 -1
  88. package/dist/worker-host.d.ts.map +1 -1
  89. package/dist/worker-host.js +32 -9
  90. package/dist/worker-host.js.map +1 -1
  91. package/dist/worker-protocol.d.ts +30 -0
  92. package/dist/worker-protocol.d.ts.map +1 -1
  93. package/dist/worker-protocol.js +24 -0
  94. package/dist/worker-protocol.js.map +1 -1
  95. package/package.json +2 -2
  96. package/src/asset-store.ts +32 -2
  97. package/src/batch-processor.ts +133 -510
  98. package/src/batch-progress.ts +228 -0
  99. package/src/batch-scheduler.ts +209 -0
  100. package/src/browser-detect.ts +221 -0
  101. package/src/concurrency-controller.ts +52 -0
  102. package/src/errors.ts +82 -0
  103. package/src/executor.ts +122 -55
  104. package/src/history.ts +3 -2
  105. package/src/idb-asset-store.ts +7 -1
  106. package/src/index.ts +50 -15
  107. package/src/managers/asset-manager.ts +259 -0
  108. package/src/managers/history-manager.ts +327 -0
  109. package/src/managers/mcp-manifest-builder.ts +108 -0
  110. package/src/managers/quota-manager.ts +149 -0
  111. package/src/managers/workflow-coordinator.ts +158 -0
  112. package/src/opfs-asset-store.ts +16 -2
  113. package/src/plugin-context.ts +87 -0
  114. package/src/plugin-permissions.ts +228 -0
  115. package/src/runtime-impl.ts +298 -0
  116. package/src/runtime.ts +16 -1116
  117. package/src/test-utils/fakes.ts +72 -0
  118. package/src/types.ts +116 -0
  119. package/src/worker-host.ts +35 -7
  120. package/src/worker-protocol.ts +47 -0
  121. package/dist/workflow-builder.d.ts +0 -124
  122. package/dist/workflow-builder.d.ts.map +0 -1
  123. package/dist/workflow-builder.js +0 -240
  124. package/dist/workflow-builder.js.map +0 -1
  125. package/src/workflow-builder.ts +0 -303
@@ -0,0 +1,52 @@
1
+ /**
2
+ * ConcurrencyController - 批量作业并发槽位控制(W4.4 拆分)
3
+ *
4
+ * 职责:
5
+ * - 提供免费/Pro 默认并发上限常量
6
+ * - 按 MemoryGuard 压力动态收缩并发槽位
7
+ *
8
+ * 设计原则:
9
+ * - 无状态:仅依赖注入的 memoryGuard
10
+ * - 纯函数:current() 基于 baseConcurrency + 当前 pressure 计算
11
+ */
12
+ import type { MemoryGuard, MemoryPressure } from './memory-guard.js';
13
+
14
+ /** 默认并发槽位 */
15
+ export const FREE_CONCURRENCY = 4;
16
+ export const PRO_CONCURRENCY = 16;
17
+
18
+ /** 按 MemoryPressure 收缩并发槽位 */
19
+ export function shrinkConcurrencyByPressure(
20
+ base: number,
21
+ pressure: MemoryPressure
22
+ ): number {
23
+ switch (pressure) {
24
+ case 'critical':
25
+ return 1;
26
+ case 'high':
27
+ return Math.max(1, Math.floor(base / 2));
28
+ case 'elevated':
29
+ return Math.max(2, Math.floor((base * 3) / 4));
30
+ default:
31
+ return base;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * 并发控制器:基于 MemoryGuard 压力动态收缩槽位。
37
+ *
38
+ * 不持有 baseConcurrency(每 job 独立),current() 接收 base 并返回当前实际可用槽位。
39
+ */
40
+ export class ConcurrencyController {
41
+ private readonly memoryGuard?: MemoryGuard;
42
+
43
+ constructor(opts: { memoryGuard?: MemoryGuard }) {
44
+ this.memoryGuard = opts.memoryGuard;
45
+ }
46
+
47
+ /** 当前并发槽位(按内存压力动态收缩) */
48
+ current(baseConcurrency: number): number {
49
+ const pressure = this.memoryGuard?.getPressure() ?? 'low';
50
+ return shrinkConcurrencyByPressure(baseConcurrency, pressure);
51
+ }
52
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Runtime 类型化错误类(T10:消除 SDK message 模式匹配)
3
+ *
4
+ * 这些错误类供 runtime 各模块在对应场景抛出,替代原先的 `throw new Error(...)`。
5
+ * SDK 的 `fromLokvisError()` 通过 `instanceof` 检测这些类,包装为对应的 SDK
6
+ * 错误类(LokvisError 子类),无需再依赖 message 字符串模式匹配。
7
+ *
8
+ * 命名与 SDK 错误类对应(如 runtime `AssetNotFoundError` → SDK `AssetNotFoundError`),
9
+ * 但 runtime 侧继承 `Error`(不依赖 SDK 的 `LokvisError`),符合五层架构
10
+ * (Runtime 不依赖 SDK)。SDK import 时加 `Runtime` 前缀别名避免命名冲突
11
+ * (与 QuotaExceededError / Worker*Error 等现有模式一致)。
12
+ *
13
+ * super message 与原 `throw new Error(...)` 的 message 完全一致,保证:
14
+ * - 日志/堆栈输出不变
15
+ * - 依赖 message 文案的测试不破
16
+ */
17
+
18
+ /** 资产未找到(assetStore.get 返回空) */
19
+ export class AssetNotFoundError extends Error {
20
+ readonly assetId: string;
21
+ constructor(assetId: string) {
22
+ super(`Asset not found: ${assetId}`);
23
+ this.name = 'AssetNotFoundError';
24
+ this.assetId = assetId;
25
+ }
26
+ }
27
+
28
+ /** 资产 Blob 未找到(getBlob 在 store 中找不到对应 blob) */
29
+ export class AssetBlobNotFoundError extends Error {
30
+ constructor(message: string, options?: { cause?: unknown }) {
31
+ super(message, options);
32
+ this.name = 'AssetBlobNotFoundError';
33
+ }
34
+ }
35
+
36
+ /** 工作流结构校验失败(节点 ID 重复 / edge 引用未知节点 / 自环等) */
37
+ export class WorkflowInvalidError extends Error {
38
+ constructor(message: string) {
39
+ super(message);
40
+ this.name = 'WorkflowInvalidError';
41
+ }
42
+ }
43
+
44
+ /** 工作流包含环(拓扑排序后 sorted.length !== nodes.length) */
45
+ export class WorkflowCycleError extends Error {
46
+ constructor(message: string) {
47
+ super(message);
48
+ this.name = 'WorkflowCycleError';
49
+ }
50
+ }
51
+
52
+ /** 工作流节点执行失败(transform 节点缺 capability 字段) */
53
+ export class WorkflowNodeError extends Error {
54
+ readonly nodeId: string;
55
+ constructor(nodeId: string, message: string) {
56
+ super(message);
57
+ this.name = 'WorkflowNodeError';
58
+ this.nodeId = nodeId;
59
+ }
60
+ }
61
+
62
+ /** 能力未注册(capabilityRegistry.resolve 返回空且非 stub-only) */
63
+ export class CapabilityNotRegisteredError extends Error {
64
+ readonly capability: string;
65
+ constructor(capability: string) {
66
+ super(`No implementation registered for capability "${capability}"`);
67
+ this.name = 'CapabilityNotRegisteredError';
68
+ this.capability = capability;
69
+ }
70
+ }
71
+
72
+ /** 能力仅有 stub 实现(需安装真实 engine 插件) */
73
+ export class CapabilityStubOnlyError extends Error {
74
+ readonly capability: string;
75
+ constructor(capability: string) {
76
+ super(
77
+ `Capability "${capability}" is not yet available (only stub engine registered). Install a real engine plugin to use this capability.`
78
+ );
79
+ this.name = 'CapabilityStubOnlyError';
80
+ this.capability = capability;
81
+ }
82
+ }
package/src/executor.ts CHANGED
@@ -17,6 +17,14 @@ import type {
17
17
  import type { EventBus } from '@lokvis/schema';
18
18
  import type { AssetStore } from './asset-store.js';
19
19
  import type { CapabilityRegistry } from './capability-registry.js';
20
+ import {
21
+ AssetNotFoundError,
22
+ WorkflowInvalidError,
23
+ WorkflowCycleError,
24
+ WorkflowNodeError,
25
+ CapabilityNotRegisteredError,
26
+ CapabilityStubOnlyError,
27
+ } from './errors.js';
20
28
 
21
29
  /** 执行中的工作流状态 */
22
30
  interface RunningWorkflow {
@@ -75,7 +83,7 @@ function topologicalSort(nodes: WorkflowNode[], edges: WorkflowEdge[]): Workflow
75
83
  const seen = new Set<string>();
76
84
  for (const n of nodes) {
77
85
  if (seen.has(n.id)) {
78
- throw new Error(`Workflow contains duplicate node id: ${n.id}`);
86
+ throw new WorkflowInvalidError(`Workflow contains duplicate node id: ${n.id}`);
79
87
  }
80
88
  seen.add(n.id);
81
89
  }
@@ -90,7 +98,7 @@ function topologicalSort(nodes: WorkflowNode[], edges: WorkflowEdge[]): Workflow
90
98
  // 被静默吞掉,进而导致下游节点入度无法归零 → 误判为环)
91
99
  for (const edge of edges) {
92
100
  if (!nodeMap.has(edge.from)) {
93
- throw new Error(
101
+ throw new WorkflowInvalidError(
94
102
  `Workflow edge references unknown source node: "${edge.from}". ` +
95
103
  `Edge endpoints must reference existing nodes; ` +
96
104
  `reserved sentinel nodes (e.g. "__input__") are not supported ` +
@@ -99,13 +107,13 @@ function topologicalSort(nodes: WorkflowNode[], edges: WorkflowEdge[]): Workflow
99
107
  );
100
108
  }
101
109
  if (!nodeMap.has(edge.to)) {
102
- throw new Error(
110
+ throw new WorkflowInvalidError(
103
111
  `Workflow edge references unknown target node: "${edge.to}". ` +
104
112
  `Edge endpoints must reference existing nodes.`
105
113
  );
106
114
  }
107
115
  if (edge.from === edge.to) {
108
- throw new Error(
116
+ throw new WorkflowInvalidError(
109
117
  `Workflow contains self-loop on node: "${edge.from}". ` +
110
118
  `Self-loops create cycles and are not allowed.`
111
119
  );
@@ -133,7 +141,7 @@ function topologicalSort(nodes: WorkflowNode[], edges: WorkflowEdge[]): Workflow
133
141
  }
134
142
 
135
143
  if (sorted.length !== nodes.length) {
136
- throw new Error('Workflow contains a cycle, cannot execute');
144
+ throw new WorkflowCycleError('Workflow contains a cycle, cannot execute');
137
145
  }
138
146
 
139
147
  return sorted;
@@ -167,7 +175,7 @@ export class WorkflowExecutor {
167
175
  currentAssets = await Promise.all(
168
176
  ids.map(async (id) => {
169
177
  const asset = await this.config.assetStore.get(id);
170
- if (!asset) throw new Error(`Asset not found: ${id}`);
178
+ if (!asset) throw new AssetNotFoundError(id);
171
179
  return asset;
172
180
  })
173
181
  );
@@ -198,65 +206,109 @@ export class WorkflowExecutor {
198
206
  // 过滤掉 load 和 export 节点(load 由输入处理,export 由结果处理)
199
207
  const transformNodes = sortedNodes.filter((n) => n.type === 'transform');
200
208
 
201
- // 依次执行每个 transform 节点
202
- for (const node of transformNodes) {
203
- // 暂停则挂起,等待 resume/cancel 唤醒(Promise resolver,无轮询)
204
- if (state.status === 'paused') {
205
- await this.waitForResume(workflowId);
206
- }
207
- // 唤醒后或每轮起始,若已被取消立即跳出(避免执行多余节点)
208
- if (state.status === 'cancelled') {
209
- break;
210
- }
209
+ // E1: target 机制
210
+ // targets 存在且非空时,为每个 target 独立执行一次 transform 链,
211
+ // target.params 浅合并到每个节点的 params(target 优先)。
212
+ // targets 时等价于单次执行(targetParams = undefined)。
213
+ const targets = workflow.outputs.targets;
214
+ const targetParamSets: (Record<string, unknown> | undefined)[] =
215
+ targets && targets.length > 0
216
+ ? targets.map((t) => t.params)
217
+ : [undefined];
218
+
219
+ // 保存原始输入资产,每个 target 执行前重置
220
+ const inputAssets = [...currentAssets];
221
+ const allOutputs: Asset[] = [];
222
+ /**
223
+ * 各 transform 节点的输出 AssetId(按 node.id 索引)。
224
+ * 仅单 target 场景填充(多 target 同一节点会有多份输出,
225
+ * 索引歧义,留空让调用方走事件总线监听 node:finished)。
226
+ */
227
+ const stepOutputs: Record<string, AssetId[]> = {};
228
+ const isSingleTarget = targetParamSets.length === 1;
229
+
230
+ for (const targetParams of targetParamSets) {
231
+ // 每个 target 开始前重置为原始输入
232
+ currentAssets = [...inputAssets];
233
+
234
+ // 依次执行每个 transform 节点
235
+ for (const node of transformNodes) {
236
+ // 暂停则挂起,等待 resume/cancel 唤醒(Promise resolver,无轮询)
237
+ if (state.status === 'paused') {
238
+ await this.waitForResume(workflowId);
239
+ }
240
+ // 唤醒后或每轮起始,若已被取消立即跳出(避免执行多余节点)
241
+ if (state.status === 'cancelled') {
242
+ break;
243
+ }
211
244
 
212
- state.currentNodeId = node.id;
213
- const nodeStart = Date.now();
245
+ state.currentNodeId = node.id;
246
+ const nodeStart = Date.now();
214
247
 
215
- this.config.eventBus.emit({
216
- type: 'node:started',
217
- workflowId,
218
- nodeId: node.id,
219
- inputs: currentAssets,
220
- });
248
+ this.config.eventBus.emit({
249
+ type: 'node:started',
250
+ workflowId,
251
+ nodeId: node.id,
252
+ inputs: currentAssets,
253
+ });
221
254
 
222
- // 解析能力实现(transform 节点必须有 capability)
223
- const capability = node.capability;
224
- if (!capability) {
225
- throw new Error(`Transform node "${node.id}" has no capability`);
226
- }
227
- const impl = this.config.capabilityRegistry.resolve(capability);
228
- if (!impl) {
229
- if (this.config.capabilityRegistry.isStubOnly(capability)) {
230
- throw new Error(
231
- `Capability "${capability}" is not yet available (only stub engine registered). Install a real engine plugin to use this capability.`
232
- );
255
+ // 解析能力实现(transform 节点必须有 capability)
256
+ const capability = node.capability;
257
+ if (!capability) {
258
+ throw new WorkflowNodeError(node.id, `Transform node "${node.id}" has no capability`);
259
+ }
260
+ const impl = this.config.capabilityRegistry.resolve(capability);
261
+ if (!impl) {
262
+ if (this.config.capabilityRegistry.isStubOnly(capability)) {
263
+ throw new CapabilityStubOnlyError(capability);
264
+ }
265
+ throw new CapabilityNotRegisteredError(capability);
266
+ }
267
+
268
+ // E1: target params 覆盖 node params(浅合并,target 优先)
269
+ // 注:展开 undefined 是 no-op,无需 ?? {} fallback(oxlint no-empty-fallback-in-spread)
270
+ const mergedParams = targetParams
271
+ ? { ...node.params, ...targetParams }
272
+ : (node.params ?? {});
273
+
274
+ // 执行能力
275
+ const ctx = createExecutionContext(
276
+ workflowId,
277
+ node.id,
278
+ abortController.signal
279
+ );
280
+ const outputs = await impl.execute(currentAssets, mergedParams, ctx);
281
+ currentAssets = outputs;
282
+
283
+ // 单 target 时记录该节点输出,供调用方读取中间结果
284
+ // (多 target 场景跳过,避免 last-write-wins 语义歧义)
285
+ if (isSingleTarget) {
286
+ stepOutputs[node.id] = currentAssets.map((a) => a.id);
233
287
  }
234
- throw new Error(`No implementation registered for capability "${capability}"`);
288
+
289
+ this.config.eventBus.emit({
290
+ type: 'node:finished',
291
+ workflowId,
292
+ nodeId: node.id,
293
+ capability,
294
+ params: mergedParams,
295
+ outputs: currentAssets,
296
+ duration: Date.now() - nodeStart,
297
+ });
235
298
  }
236
299
 
237
- // 执行能力
238
- const ctx = createExecutionContext(
239
- workflowId,
240
- node.id,
241
- abortController.signal
242
- );
243
- const outputs = await impl.execute(currentAssets, node.params ?? {}, ctx);
244
- currentAssets = outputs;
245
-
246
- this.config.eventBus.emit({
247
- type: 'node:finished',
248
- workflowId,
249
- nodeId: node.id,
250
- capability,
251
- params: node.params ?? {},
252
- outputs: currentAssets,
253
- duration: Date.now() - nodeStart,
254
- });
300
+ // 收集当前 target 的输出(cancelled 时也保留已完成的节点输出)
301
+ allOutputs.push(...currentAssets);
302
+
303
+ if (state.status === 'cancelled') break;
255
304
  }
256
305
 
257
306
  const result: WorkflowResult = {
258
307
  workflowId,
259
- outputs: currentAssets.map((a) => a.id),
308
+ outputs: allOutputs.map((a) => a.id),
309
+ // 单 target 且非取消时暴露中间步骤输出;其他场景保持 undefined,
310
+ // 调用方需要中间结果时应改用 eventBus 监听 node:finished 事件
311
+ stepOutputs: isSingleTarget && state.status !== 'cancelled' ? stepOutputs : undefined,
260
312
  duration: Date.now() - startTime,
261
313
  status: state.status === 'cancelled' ? 'cancelled' : 'completed',
262
314
  };
@@ -308,6 +360,21 @@ export class WorkflowExecutor {
308
360
  this.config.eventBus.emit({ type: 'workflow:cancelled', workflowId });
309
361
  }
310
362
 
363
+ /**
364
+ * 取消所有运行中的工作流(W21.6 runtime.dispose 用)。
365
+ *
366
+ * 遍历 running Map,对每个 state 执行 cancel 的同步部分
367
+ * (status = cancelled + abort + 唤醒 resume resolver + emit event)。
368
+ * 不等待 executor.execute 内部循环跳出 —— abort 后循环在下个 await
369
+ * 点自然抛 AbortError,资源随 Promise reject 释放。
370
+ */
371
+ cancelAll(): void {
372
+ // 复制一份避免 cancel 内 emit 触发的 listener 回调 mutate 原集合
373
+ for (const workflowId of [...this.running.keys()]) {
374
+ void this.cancel(workflowId);
375
+ }
376
+ }
377
+
311
378
  /** 暂停执行 */
312
379
  async pause(workflowId: string): Promise<void> {
313
380
  const state = this.running.get(workflowId);
package/src/history.ts CHANGED
@@ -189,8 +189,9 @@ export class HistoryStack {
189
189
  for (const entry of entries) {
190
190
  try {
191
191
  onEvict(entry);
192
- } catch {
193
- // onEvict 失败不应阻断历史操作,由调用方日志记录
192
+ } catch (err) {
193
+ // onEvict 失败不应阻断历史操作,但需记录便于调试
194
+ console.warn(`[lokvis] history onEvict threw for entry ${entry.id}:`, err);
194
195
  }
195
196
  }
196
197
  }
@@ -16,6 +16,7 @@ import {
16
16
  parseBlobPath,
17
17
  prepareImport,
18
18
  } from './asset-store.js';
19
+ import { AssetBlobNotFoundError } from './errors.js';
19
20
 
20
21
  /** IndexedDB 不可用时抛出 */
21
22
  export class IdbUnavailableError extends Error {
@@ -97,7 +98,7 @@ export async function createIdbAssetStore(
97
98
  const id = parseBlobPath(handle.path, IDB_PATH_PREFIX);
98
99
  const record = await db.assets.get(id);
99
100
  if (!record) {
100
- throw new Error(`Blob not found in IndexedDB for path: ${handle.path}`);
101
+ throw new AssetBlobNotFoundError(`Blob not found in IndexedDB for path: ${handle.path}`);
101
102
  }
102
103
  return record.blob;
103
104
  },
@@ -117,5 +118,10 @@ export async function createIdbAssetStore(
117
118
  await db.assets.put({ id, asset, blob });
118
119
  return asset;
119
120
  },
121
+
122
+ // W21.6: 关闭 Dexie 连接(IDB 数据不删除,下次创建 store 时可恢复)。
123
+ async dispose() {
124
+ db.close();
125
+ },
120
126
  };
121
127
  }
package/src/index.ts CHANGED
@@ -3,23 +3,58 @@
3
3
  *
4
4
  * Lokvis Runtime - Browser-local workflow execution engine.
5
5
  * 所有核心工作流在浏览器执行,Cloudflare 只承担边缘服务。
6
+ *
7
+ * 公共 API 收敛策略(O-3):
8
+ * - 4 个模块全量透传:types / errors / browser-detect / runtime(仅 4 个符号)
9
+ * - 7 个模块仅命名导出被外部消费的符号(AssetStore / 错误类 / BatchProcessor 等)
10
+ * - 8 个纯内部模块(event-bus / capability-registry / executor / worker-protocol /
11
+ * history / memory-guard / plugin-permissions / plugin-context)不从公共入口透传,
12
+ * runtime 内部代码与测试通过相对路径 import。
6
13
  */
7
14
 
15
+ // ─── 全量透传(公共 API) ──────────────────────────────────────────
8
16
  export * from './types.js';
9
- export * from './event-bus.js';
10
- export * from './asset-store.js';
11
- export * from './opfs-asset-store.js';
12
- export * from './idb-asset-store.js';
13
- export * from './capability-registry.js';
14
- export * from './executor.js';
15
- export * from './worker-protocol.js';
16
- export * from './worker-host.js';
17
- export * from './history.js';
18
- export * from './history-store.js';
19
- export * from './memory-guard.js';
20
- export * from './degradation.js';
21
- export * from './batch-processor.js';
22
- export * from './workflow-builder.js';
17
+ export * from './errors.js';
18
+ export * from './browser-detect.js';
23
19
  export * from './runtime.js';
24
20
 
25
- export { RUNTIME_VERSION } from './runtime.js';
21
+ // ─── 命名导出(仅被外部消费的符号) ────────────────────────────────
22
+
23
+ // asset-store:mcp-server 的 NodeAssetStore 实现此接口
24
+ export type { AssetStore } from './asset-store.js';
25
+
26
+ // opfs-asset-store:仅错误类被 SDK errors 引用
27
+ export { OpfsUnavailableError } from './opfs-asset-store.js';
28
+
29
+ // idb-asset-store:仅错误类被 SDK errors 引用
30
+ export { IdbUnavailableError } from './idb-asset-store.js';
31
+
32
+ // degradation:仅错误类被 SDK errors 引用
33
+ export { DegradationRejectedError } from './degradation.js';
34
+
35
+ // worker-host:6 个错误类被 SDK errors 引用
36
+ export {
37
+ WorkerCrashedError,
38
+ WorkerRestartingError,
39
+ WorkerDeadError,
40
+ WorkerRequestTimeoutError,
41
+ WorkerHandshakeError,
42
+ WorkerRequestAbortedError,
43
+ } from './worker-host.js';
44
+
45
+ // batch-processor:BatchProcessor 类型 + 错误类 + 常量被 SDK 引用
46
+ export {
47
+ BatchProcessor,
48
+ BatchLimitExceededError,
49
+ FREE_BATCH_LIMIT,
50
+ } from './batch-processor.js';
51
+ export type {
52
+ BatchJob,
53
+ BatchItem,
54
+ BatchItemInput,
55
+ EnqueueOptions,
56
+ BatchProgress,
57
+ } from './batch-processor.js';
58
+
59
+ // history-store:HistoryStore / HistoryStoreOptions 是 RuntimeConfig 字段类型契约
60
+ export type { HistoryStore, HistoryStoreOptions } from './history-store.js';