@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,160 @@
1
+ /**
2
+ * 浏览器能力检测(W22.3)
3
+ *
4
+ * 提供 Safari / Firefox 等浏览器的特性降级路径所需的能力探测:
5
+ * - 存储类:OPFS / IndexedDB / navigator.storage.estimate
6
+ * - 编解码类:WebCodecs(ImageDecoder/VideoDecoder/AudioDecoder/VideoEncoder) /
7
+ * OffscreenCanvas / createImageBitmap
8
+ * - 并发类:SharedArrayBuffer / crossOriginIsolated(WASM 线程所需)
9
+ * - 其他:WebWorker / ServiceWorker / WebAssembly
10
+ *
11
+ * 设计原则:
12
+ * 1. 优先 feature detection(typeof X !== 'undefined' / 'function'),
13
+ * 不依赖 UA 嗅探(UA 易伪装且不未来兼容)
14
+ * 2. UA 嗅探仅作辅助,用于已知 Safari/ Firefox 版本的特定 workaround
15
+ * 3. 所有检测在 SSR(Node)环境下安全返回 false / unknown
16
+ * 4. 探测结果可被 Runtime 初始化 + Playground UI 复用
17
+ *
18
+ * 现有降级链(已在其他模块实现,本模块仅提供能力探测供调用方决策):
19
+ * - AssetStore:OPFS → IndexedDB → Memory(createAssetStore 工厂,W2.8)
20
+ * - engine-image:纯 Canvas 2D API(无 WebCodecs 需要降级)
21
+ * - canvas-engine.decodeResized:Safari 16.4+ 支持 createImageBitmap resize
22
+ * 选项,不支持时回退到普通 decode + drawImage(已实现)
23
+ */
24
+ import { isOpfsSupported } from './opfs-asset-store.js';
25
+ import { isIdbSupported } from './idb-asset-store.js';
26
+ /** 缓存能力探测结果(同一会话内不变,避免重复检测) */
27
+ let cachedCapabilities;
28
+ /** 缓存 UA 识别结果(同一会话内不变) */
29
+ let cachedBrowserInfo;
30
+ /**
31
+ * 探测当前环境(浏览器/Node)的能力。
32
+ *
33
+ * 在 SSR(Node)环境下,所有浏览器 API 检测返回 false。
34
+ *
35
+ * 结果在首次调用后缓存,同一会话内重复调用返回同一对象。
36
+ * 如需强制重新探测(测试场景),传 { force: true }。
37
+ */
38
+ export function detectBrowserCapabilities(options = {}) {
39
+ if (cachedCapabilities && !options.force)
40
+ return cachedCapabilities;
41
+ const g = globalThis;
42
+ const nav = typeof navigator !== 'undefined' ? navigator : undefined;
43
+ cachedCapabilities = {
44
+ opfs: isOpfsSupported(),
45
+ indexedDB: isIdbSupported(),
46
+ storageEstimate: !!nav && typeof nav.storage?.estimate === 'function',
47
+ webCodecsImageDecoder: typeof g.ImageDecoder !== 'undefined',
48
+ webCodecsVideoDecoder: typeof g.VideoDecoder !== 'undefined',
49
+ webCodecsVideoEncoder: typeof g.VideoEncoder !== 'undefined',
50
+ webCodecsAudioDecoder: typeof g.AudioDecoder !== 'undefined',
51
+ offscreenCanvas: typeof g.OffscreenCanvas !== 'undefined',
52
+ createImageBitmap: typeof g.createImageBitmap === 'function',
53
+ sharedArrayBuffer: typeof g.SharedArrayBuffer !== 'undefined',
54
+ crossOriginIsolated: typeof g.crossOriginIsolated === 'boolean' ? g.crossOriginIsolated : false,
55
+ webAssembly: typeof g.WebAssembly !== 'undefined',
56
+ webWorker: typeof g.Worker !== 'undefined',
57
+ serviceWorker: !!nav && typeof nav.serviceWorker !== 'undefined',
58
+ };
59
+ return cachedCapabilities;
60
+ }
61
+ /** UA 嗅探正则(仅作辅助,优先用 detectBrowserCapabilities) */
62
+ const SAFARI_REGEX = /Version\/(\d+)\.\d+.*Safari\/[\d.]+/;
63
+ const FIREFOX_REGEX = /Firefox\/(\d+)\.\d+/;
64
+ const EDGE_REGEX = /Edg\/(\d+)\.\d+/;
65
+ const CHROME_REGEX = /Chrome\/(\d+)\.\d+/;
66
+ const MOBILE_REGEX = /Mobi|Android|iPhone|iPad|iPod/i;
67
+ /**
68
+ * 识别当前浏览器(基于 UA 嗅探)。
69
+ *
70
+ * 注意:UA 嗅探不可靠,浏览器可伪装 UA。优先用 detectBrowserCapabilities()
71
+ * 检测实际能力。本函数仅用于已知浏览器的特定 workaround(如 Safari 的
72
+ * createImageBitmap resize 选项兼容性)。
73
+ *
74
+ * 结果在首次调用后缓存,同一会话内重复调用返回同一对象。
75
+ * 如需强制重新识别(测试场景),传 { force: true }。
76
+ */
77
+ export function detectBrowserInfo(options = {}) {
78
+ if (cachedBrowserInfo && !options.force)
79
+ return cachedBrowserInfo;
80
+ if (typeof navigator === 'undefined') {
81
+ cachedBrowserInfo = {
82
+ name: 'unknown', version: 0,
83
+ isSafari: false, isFirefox: false, isChrome: false, isEdge: false,
84
+ isMobile: false,
85
+ };
86
+ return cachedBrowserInfo;
87
+ }
88
+ const ua = navigator.userAgent;
89
+ // Edge 必须先检测(含 Chrome 字样)
90
+ const edgeMatch = ua.match(EDGE_REGEX);
91
+ if (edgeMatch && edgeMatch[1]) {
92
+ cachedBrowserInfo = {
93
+ name: 'edge', version: parseInt(edgeMatch[1], 10),
94
+ isSafari: false, isFirefox: false, isChrome: false, isEdge: true,
95
+ isMobile: MOBILE_REGEX.test(ua),
96
+ };
97
+ return cachedBrowserInfo;
98
+ }
99
+ // Safari 必须在 Chrome 之前检测(排除 Chrome:Chrome UA 含 Chrome/ 但不含 Version/X Safari/X)
100
+ const safariMatch = ua.match(SAFARI_REGEX);
101
+ if (safariMatch && safariMatch[1] && !ua.includes('Chrome/')) {
102
+ cachedBrowserInfo = {
103
+ name: 'safari', version: parseInt(safariMatch[1], 10),
104
+ isSafari: true, isFirefox: false, isChrome: false, isEdge: false,
105
+ isMobile: MOBILE_REGEX.test(ua),
106
+ };
107
+ return cachedBrowserInfo;
108
+ }
109
+ const firefoxMatch = ua.match(FIREFOX_REGEX);
110
+ if (firefoxMatch && firefoxMatch[1]) {
111
+ cachedBrowserInfo = {
112
+ name: 'firefox', version: parseInt(firefoxMatch[1], 10),
113
+ isSafari: false, isFirefox: true, isChrome: false, isEdge: false,
114
+ isMobile: MOBILE_REGEX.test(ua),
115
+ };
116
+ return cachedBrowserInfo;
117
+ }
118
+ const chromeMatch = ua.match(CHROME_REGEX);
119
+ if (chromeMatch && chromeMatch[1]) {
120
+ cachedBrowserInfo = {
121
+ name: 'chrome', version: parseInt(chromeMatch[1], 10),
122
+ isSafari: false, isFirefox: false, isChrome: true, isEdge: false,
123
+ isMobile: MOBILE_REGEX.test(ua),
124
+ };
125
+ return cachedBrowserInfo;
126
+ }
127
+ cachedBrowserInfo = {
128
+ name: 'unknown', version: 0,
129
+ isSafari: false, isFirefox: false, isChrome: false, isEdge: false,
130
+ isMobile: MOBILE_REGEX.test(ua),
131
+ };
132
+ return cachedBrowserInfo;
133
+ }
134
+ /**
135
+ * 判断当前浏览器是否为 Safari(基于 UA 嗅探)。
136
+ *
137
+ * 用于已知 Safari 版本的特定 workaround,如:
138
+ * - Safari 16.4 以下不支持 createImageBitmap resize 选项
139
+ * - Safari 的 OffscreenCanvas 在 Worker 中行为不一致
140
+ *
141
+ * 优先用 detectBrowserCapabilities().offscreenCanvas 等能力检测。
142
+ */
143
+ export function isSafari() {
144
+ return detectBrowserInfo().isSafari;
145
+ }
146
+ /**
147
+ * 判断当前浏览器是否为 Firefox(基于 UA 嗅探)。
148
+ *
149
+ * 用于 Firefox 特定 workaround(如某些 CSS / API 差异)。
150
+ * 优先用 detectBrowserCapabilities() 检测实际能力。
151
+ */
152
+ export function isFirefox() {
153
+ return detectBrowserInfo().isFirefox;
154
+ }
155
+ /** 测试专用:重置缓存(确保下次 detectBrowserXxx 重新探测) */
156
+ export function _resetBrowserDetectCache() {
157
+ cachedCapabilities = undefined;
158
+ cachedBrowserInfo = undefined;
159
+ }
160
+ //# sourceMappingURL=browser-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-detect.js","sourceRoot":"","sources":["../src/browser-detect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
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
+ export declare const FREE_CONCURRENCY = 4;
15
+ export declare const PRO_CONCURRENCY = 16;
16
+ /** 按 MemoryPressure 收缩并发槽位 */
17
+ export declare function shrinkConcurrencyByPressure(base: number, pressure: MemoryPressure): number;
18
+ /**
19
+ * 并发控制器:基于 MemoryGuard 压力动态收缩槽位。
20
+ *
21
+ * 不持有 baseConcurrency(每 job 独立),current() 接收 base 并返回当前实际可用槽位。
22
+ */
23
+ export declare class ConcurrencyController {
24
+ private readonly memoryGuard?;
25
+ constructor(opts: {
26
+ memoryGuard?: MemoryGuard;
27
+ });
28
+ /** 当前并发槽位(按内存压力动态收缩) */
29
+ current(baseConcurrency: number): number;
30
+ }
31
+ //# sourceMappingURL=concurrency-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency-controller.d.ts","sourceRoot":"","sources":["../src/concurrency-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAErE,aAAa;AACb,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,8BAA8B;AAC9B,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,cAAc,GACvB,MAAM,CAWR;AAED;;;;GAIG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAc;gBAE/B,IAAI,EAAE;QAAE,WAAW,CAAC,EAAE,WAAW,CAAA;KAAE;IAI/C,wBAAwB;IACxB,OAAO,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM;CAIzC"}
@@ -0,0 +1,33 @@
1
+ /** 默认并发槽位 */
2
+ export const FREE_CONCURRENCY = 4;
3
+ export const PRO_CONCURRENCY = 16;
4
+ /** 按 MemoryPressure 收缩并发槽位 */
5
+ export function shrinkConcurrencyByPressure(base, pressure) {
6
+ switch (pressure) {
7
+ case 'critical':
8
+ return 1;
9
+ case 'high':
10
+ return Math.max(1, Math.floor(base / 2));
11
+ case 'elevated':
12
+ return Math.max(2, Math.floor((base * 3) / 4));
13
+ default:
14
+ return base;
15
+ }
16
+ }
17
+ /**
18
+ * 并发控制器:基于 MemoryGuard 压力动态收缩槽位。
19
+ *
20
+ * 不持有 baseConcurrency(每 job 独立),current() 接收 base 并返回当前实际可用槽位。
21
+ */
22
+ export class ConcurrencyController {
23
+ memoryGuard;
24
+ constructor(opts) {
25
+ this.memoryGuard = opts.memoryGuard;
26
+ }
27
+ /** 当前并发槽位(按内存压力动态收缩) */
28
+ current(baseConcurrency) {
29
+ const pressure = this.memoryGuard?.getPressure() ?? 'low';
30
+ return shrinkConcurrencyByPressure(baseConcurrency, pressure);
31
+ }
32
+ }
33
+ //# sourceMappingURL=concurrency-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency-controller.js","sourceRoot":"","sources":["../src/concurrency-controller.ts"],"names":[],"mappings":"AAaA,aAAa;AACb,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAClC,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAElC,8BAA8B;AAC9B,MAAM,UAAU,2BAA2B,CACzC,IAAY,EACZ,QAAwB;IAExB,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,UAAU;YACb,OAAO,CAAC,CAAC;QACX,KAAK,MAAM;YACT,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QAC3C,KAAK,UAAU;YACb,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjD;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,qBAAqB;IACf,WAAW,CAAe;IAE3C,YAAY,IAAmC;QAC7C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;IACtC,CAAC;IAED,wBAAwB;IACxB,OAAO,CAAC,eAAuB;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,KAAK,CAAC;QAC1D,OAAO,2BAA2B,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;IAChE,CAAC;CACF"}
@@ -0,0 +1,51 @@
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
+ /** 资产未找到(assetStore.get 返回空) */
18
+ export declare class AssetNotFoundError extends Error {
19
+ readonly assetId: string;
20
+ constructor(assetId: string);
21
+ }
22
+ /** 资产 Blob 未找到(getBlob 在 store 中找不到对应 blob) */
23
+ export declare class AssetBlobNotFoundError extends Error {
24
+ constructor(message: string, options?: {
25
+ cause?: unknown;
26
+ });
27
+ }
28
+ /** 工作流结构校验失败(节点 ID 重复 / edge 引用未知节点 / 自环等) */
29
+ export declare class WorkflowInvalidError extends Error {
30
+ constructor(message: string);
31
+ }
32
+ /** 工作流包含环(拓扑排序后 sorted.length !== nodes.length) */
33
+ export declare class WorkflowCycleError extends Error {
34
+ constructor(message: string);
35
+ }
36
+ /** 工作流节点执行失败(transform 节点缺 capability 字段) */
37
+ export declare class WorkflowNodeError extends Error {
38
+ readonly nodeId: string;
39
+ constructor(nodeId: string, message: string);
40
+ }
41
+ /** 能力未注册(capabilityRegistry.resolve 返回空且非 stub-only) */
42
+ export declare class CapabilityNotRegisteredError extends Error {
43
+ readonly capability: string;
44
+ constructor(capability: string);
45
+ }
46
+ /** 能力仅有 stub 实现(需安装真实 engine 插件) */
47
+ export declare class CapabilityStubOnlyError extends Error {
48
+ readonly capability: string;
49
+ constructor(capability: string);
50
+ }
51
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,gCAAgC;AAChC,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBACb,OAAO,EAAE,MAAM;CAK5B;AAED,+CAA+C;AAC/C,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI3D;AAED,8CAA8C;AAC9C,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,mDAAmD;AACnD,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,6CAA6C;AAC7C,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK5C;AAED,wDAAwD;AACxD,qBAAa,4BAA6B,SAAQ,KAAK;IACrD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAChB,UAAU,EAAE,MAAM;CAK/B;AAED,oCAAoC;AACpC,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAChB,UAAU,EAAE,MAAM;CAO/B"}
package/dist/errors.js ADDED
@@ -0,0 +1,74 @@
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
+ /** 资产未找到(assetStore.get 返回空) */
18
+ export class AssetNotFoundError extends Error {
19
+ assetId;
20
+ constructor(assetId) {
21
+ super(`Asset not found: ${assetId}`);
22
+ this.name = 'AssetNotFoundError';
23
+ this.assetId = assetId;
24
+ }
25
+ }
26
+ /** 资产 Blob 未找到(getBlob 在 store 中找不到对应 blob) */
27
+ export class AssetBlobNotFoundError extends Error {
28
+ constructor(message, options) {
29
+ super(message, options);
30
+ this.name = 'AssetBlobNotFoundError';
31
+ }
32
+ }
33
+ /** 工作流结构校验失败(节点 ID 重复 / edge 引用未知节点 / 自环等) */
34
+ export class WorkflowInvalidError extends Error {
35
+ constructor(message) {
36
+ super(message);
37
+ this.name = 'WorkflowInvalidError';
38
+ }
39
+ }
40
+ /** 工作流包含环(拓扑排序后 sorted.length !== nodes.length) */
41
+ export class WorkflowCycleError extends Error {
42
+ constructor(message) {
43
+ super(message);
44
+ this.name = 'WorkflowCycleError';
45
+ }
46
+ }
47
+ /** 工作流节点执行失败(transform 节点缺 capability 字段) */
48
+ export class WorkflowNodeError extends Error {
49
+ nodeId;
50
+ constructor(nodeId, message) {
51
+ super(message);
52
+ this.name = 'WorkflowNodeError';
53
+ this.nodeId = nodeId;
54
+ }
55
+ }
56
+ /** 能力未注册(capabilityRegistry.resolve 返回空且非 stub-only) */
57
+ export class CapabilityNotRegisteredError extends Error {
58
+ capability;
59
+ constructor(capability) {
60
+ super(`No implementation registered for capability "${capability}"`);
61
+ this.name = 'CapabilityNotRegisteredError';
62
+ this.capability = capability;
63
+ }
64
+ }
65
+ /** 能力仅有 stub 实现(需安装真实 engine 插件) */
66
+ export class CapabilityStubOnlyError extends Error {
67
+ capability;
68
+ constructor(capability) {
69
+ super(`Capability "${capability}" is not yet available (only stub engine registered). Install a real engine plugin to use this capability.`);
70
+ this.name = 'CapabilityStubOnlyError';
71
+ this.capability = capability;
72
+ }
73
+ }
74
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,gCAAgC;AAChC,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,OAAO,CAAS;IACzB,YAAY,OAAe;QACzB,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED,+CAA+C;AAC/C,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC/C,YAAY,OAAe,EAAE,OAA6B;QACxD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,8CAA8C;AAC9C,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,mDAAmD;AACnD,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,6CAA6C;AAC7C,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,MAAM,CAAS;IACxB,YAAY,MAAc,EAAE,OAAe;QACzC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,wDAAwD;AACxD,MAAM,OAAO,4BAA6B,SAAQ,KAAK;IAC5C,UAAU,CAAS;IAC5B,YAAY,UAAkB;QAC5B,KAAK,CAAC,gDAAgD,UAAU,GAAG,CAAC,CAAC;QACrE,IAAI,CAAC,IAAI,GAAG,8BAA8B,CAAC;QAC3C,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AAED,oCAAoC;AACpC,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACvC,UAAU,CAAS;IAC5B,YAAY,UAAkB;QAC5B,KAAK,CACH,eAAe,UAAU,4GAA4G,CACtI,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;QACtC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF"}
@@ -26,6 +26,15 @@ export declare class WorkflowExecutor {
26
26
  execute(workflow: Workflow, inputs: AssetId[] | Asset[]): Promise<WorkflowResult>;
27
27
  /** 取消执行 */
28
28
  cancel(workflowId: string): Promise<void>;
29
+ /**
30
+ * 取消所有运行中的工作流(W21.6 runtime.dispose 用)。
31
+ *
32
+ * 遍历 running Map,对每个 state 执行 cancel 的同步部分
33
+ * (status = cancelled + abort + 唤醒 resume resolver + emit event)。
34
+ * 不等待 executor.execute 内部循环跳出 —— abort 后循环在下个 await
35
+ * 点自然抛 AbortError,资源随 Promise reject 释放。
36
+ */
37
+ cancelAll(): void;
29
38
  /** 暂停执行 */
30
39
  pause(workflowId: string): Promise<void>;
31
40
  /** 恢复执行 */
@@ -1 +1 @@
1
- {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,KAAK,EACL,OAAO,EAEP,QAAQ,EAGR,cAAc,EACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAWnE,qBAAqB;AACrB,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,UAAU,CAAC;IACvB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB;AA0GD,mBAAmB;AACnB,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,OAAO,CAAsC;IACrD,4CAA4C;IAC5C,OAAO,CAAC,eAAe,CAAiC;gBAE5C,MAAM,EAAE,cAAc;IAIlC,YAAY;IACN,OAAO,CACX,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,GAC1B,OAAO,CAAC,cAAc,CAAC;IA+I1B,WAAW;IACL,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU/C,WAAW;IACL,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9C,WAAW;IACL,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS/C;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAMrB,+BAA+B;IAC/B,OAAO,CAAC,aAAa;CAOtB"}
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,KAAK,EACL,OAAO,EAEP,QAAQ,EAGR,cAAc,EACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAmBnE,qBAAqB;AACrB,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,UAAU,CAAC;IACvB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB;AA0GD,mBAAmB;AACnB,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,OAAO,CAAsC;IACrD,4CAA4C;IAC5C,OAAO,CAAC,eAAe,CAAiC;gBAE5C,MAAM,EAAE,cAAc;IAIlC,YAAY;IACN,OAAO,CACX,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,GAC1B,OAAO,CAAC,cAAc,CAAC;IA2L1B,WAAW;IACL,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU/C;;;;;;;OAOG;IACH,SAAS,IAAI,IAAI;IAOjB,WAAW;IACL,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9C,WAAW;IACL,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS/C;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAMrB,+BAA+B;IAC/B,OAAO,CAAC,aAAa;CAOtB"}
package/dist/executor.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * 第一年只支持 Linear Workflow(线性链)。
5
5
  * 不支持:Branch / Loop / Condition / Parallel。
6
6
  */
7
+ import { AssetNotFoundError, WorkflowInvalidError, WorkflowCycleError, WorkflowNodeError, CapabilityNotRegisteredError, CapabilityStubOnlyError, } from './errors.js';
7
8
  /** 创建执行上下文 */
8
9
  function createExecutionContext(workflowId, nodeId, signal, onProgress) {
9
10
  return {
@@ -38,7 +39,7 @@ function topologicalSort(nodes, edges) {
38
39
  const seen = new Set();
39
40
  for (const n of nodes) {
40
41
  if (seen.has(n.id)) {
41
- throw new Error(`Workflow contains duplicate node id: ${n.id}`);
42
+ throw new WorkflowInvalidError(`Workflow contains duplicate node id: ${n.id}`);
42
43
  }
43
44
  seen.add(n.id);
44
45
  }
@@ -51,18 +52,18 @@ function topologicalSort(nodes, edges) {
51
52
  // 被静默吞掉,进而导致下游节点入度无法归零 → 误判为环)
52
53
  for (const edge of edges) {
53
54
  if (!nodeMap.has(edge.from)) {
54
- throw new Error(`Workflow edge references unknown source node: "${edge.from}". ` +
55
+ throw new WorkflowInvalidError(`Workflow edge references unknown source node: "${edge.from}". ` +
55
56
  `Edge endpoints must reference existing nodes; ` +
56
57
  `reserved sentinel nodes (e.g. "__input__") are not supported ` +
57
58
  `by the executor — input assets are injected into the first node ` +
58
59
  `with in-degree 0.`);
59
60
  }
60
61
  if (!nodeMap.has(edge.to)) {
61
- throw new Error(`Workflow edge references unknown target node: "${edge.to}". ` +
62
+ throw new WorkflowInvalidError(`Workflow edge references unknown target node: "${edge.to}". ` +
62
63
  `Edge endpoints must reference existing nodes.`);
63
64
  }
64
65
  if (edge.from === edge.to) {
65
- throw new Error(`Workflow contains self-loop on node: "${edge.from}". ` +
66
+ throw new WorkflowInvalidError(`Workflow contains self-loop on node: "${edge.from}". ` +
66
67
  `Self-loops create cycles and are not allowed.`);
67
68
  }
68
69
  inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);
@@ -87,7 +88,7 @@ function topologicalSort(nodes, edges) {
87
88
  }
88
89
  }
89
90
  if (sorted.length !== nodes.length) {
90
- throw new Error('Workflow contains a cycle, cannot execute');
91
+ throw new WorkflowCycleError('Workflow contains a cycle, cannot execute');
91
92
  }
92
93
  return sorted;
93
94
  }
@@ -113,7 +114,7 @@ export class WorkflowExecutor {
113
114
  currentAssets = await Promise.all(ids.map(async (id) => {
114
115
  const asset = await this.config.assetStore.get(id);
115
116
  if (!asset)
116
- throw new Error(`Asset not found: ${id}`);
117
+ throw new AssetNotFoundError(id);
117
118
  return asset;
118
119
  }));
119
120
  }
@@ -139,53 +140,92 @@ export class WorkflowExecutor {
139
140
  const sortedNodes = topologicalSort(workflow.nodes, workflow.edges);
140
141
  // 过滤掉 load 和 export 节点(load 由输入处理,export 由结果处理)
141
142
  const transformNodes = sortedNodes.filter((n) => n.type === 'transform');
142
- // 依次执行每个 transform 节点
143
- for (const node of transformNodes) {
144
- // 暂停则挂起,等待 resume/cancel 唤醒(Promise resolver,无轮询)
145
- if (state.status === 'paused') {
146
- await this.waitForResume(workflowId);
147
- }
148
- // 唤醒后或每轮起始,若已被取消立即跳出(避免执行多余节点)
149
- if (state.status === 'cancelled') {
150
- break;
151
- }
152
- state.currentNodeId = node.id;
153
- const nodeStart = Date.now();
154
- this.config.eventBus.emit({
155
- type: 'node:started',
156
- workflowId,
157
- nodeId: node.id,
158
- inputs: currentAssets,
159
- });
160
- // 解析能力实现(transform 节点必须有 capability)
161
- const capability = node.capability;
162
- if (!capability) {
163
- throw new Error(`Transform node "${node.id}" has no capability`);
164
- }
165
- const impl = this.config.capabilityRegistry.resolve(capability);
166
- if (!impl) {
167
- if (this.config.capabilityRegistry.isStubOnly(capability)) {
168
- throw new Error(`Capability "${capability}" is not yet available (only stub engine registered). Install a real engine plugin to use this capability.`);
143
+ // E1: target 机制
144
+ // targets 存在且非空时,为每个 target 独立执行一次 transform 链,
145
+ // target.params 浅合并到每个节点的 params(target 优先)。
146
+ // targets 时等价于单次执行(targetParams = undefined)。
147
+ const targets = workflow.outputs.targets;
148
+ const targetParamSets = targets && targets.length > 0
149
+ ? targets.map((t) => t.params)
150
+ : [undefined];
151
+ // 保存原始输入资产,每个 target 执行前重置
152
+ const inputAssets = [...currentAssets];
153
+ const allOutputs = [];
154
+ /**
155
+ * 各 transform 节点的输出 AssetId(按 node.id 索引)。
156
+ * 仅单 target 场景填充(多 target 同一节点会有多份输出,
157
+ * 索引歧义,留空让调用方走事件总线监听 node:finished)。
158
+ */
159
+ const stepOutputs = {};
160
+ const isSingleTarget = targetParamSets.length === 1;
161
+ for (const targetParams of targetParamSets) {
162
+ // 每个 target 开始前重置为原始输入
163
+ currentAssets = [...inputAssets];
164
+ // 依次执行每个 transform 节点
165
+ for (const node of transformNodes) {
166
+ // 暂停则挂起,等待 resume/cancel 唤醒(Promise resolver,无轮询)
167
+ if (state.status === 'paused') {
168
+ await this.waitForResume(workflowId);
169
+ }
170
+ // 唤醒后或每轮起始,若已被取消立即跳出(避免执行多余节点)
171
+ if (state.status === 'cancelled') {
172
+ break;
173
+ }
174
+ state.currentNodeId = node.id;
175
+ const nodeStart = Date.now();
176
+ this.config.eventBus.emit({
177
+ type: 'node:started',
178
+ workflowId,
179
+ nodeId: node.id,
180
+ inputs: currentAssets,
181
+ });
182
+ // 解析能力实现(transform 节点必须有 capability)
183
+ const capability = node.capability;
184
+ if (!capability) {
185
+ throw new WorkflowNodeError(node.id, `Transform node "${node.id}" has no capability`);
186
+ }
187
+ const impl = this.config.capabilityRegistry.resolve(capability);
188
+ if (!impl) {
189
+ if (this.config.capabilityRegistry.isStubOnly(capability)) {
190
+ throw new CapabilityStubOnlyError(capability);
191
+ }
192
+ throw new CapabilityNotRegisteredError(capability);
169
193
  }
170
- throw new Error(`No implementation registered for capability "${capability}"`);
194
+ // E1: target params 覆盖 node params(浅合并,target 优先)
195
+ // 注:展开 undefined 是 no-op,无需 ?? {} fallback(oxlint no-empty-fallback-in-spread)
196
+ const mergedParams = targetParams
197
+ ? { ...node.params, ...targetParams }
198
+ : (node.params ?? {});
199
+ // 执行能力
200
+ const ctx = createExecutionContext(workflowId, node.id, abortController.signal);
201
+ const outputs = await impl.execute(currentAssets, mergedParams, ctx);
202
+ currentAssets = outputs;
203
+ // 单 target 时记录该节点输出,供调用方读取中间结果
204
+ // (多 target 场景跳过,避免 last-write-wins 语义歧义)
205
+ if (isSingleTarget) {
206
+ stepOutputs[node.id] = currentAssets.map((a) => a.id);
207
+ }
208
+ this.config.eventBus.emit({
209
+ type: 'node:finished',
210
+ workflowId,
211
+ nodeId: node.id,
212
+ capability,
213
+ params: mergedParams,
214
+ outputs: currentAssets,
215
+ duration: Date.now() - nodeStart,
216
+ });
171
217
  }
172
- // 执行能力
173
- const ctx = createExecutionContext(workflowId, node.id, abortController.signal);
174
- const outputs = await impl.execute(currentAssets, node.params ?? {}, ctx);
175
- currentAssets = outputs;
176
- this.config.eventBus.emit({
177
- type: 'node:finished',
178
- workflowId,
179
- nodeId: node.id,
180
- capability,
181
- params: node.params ?? {},
182
- outputs: currentAssets,
183
- duration: Date.now() - nodeStart,
184
- });
218
+ // 收集当前 target 的输出(cancelled 时也保留已完成的节点输出)
219
+ allOutputs.push(...currentAssets);
220
+ if (state.status === 'cancelled')
221
+ break;
185
222
  }
186
223
  const result = {
187
224
  workflowId,
188
- outputs: currentAssets.map((a) => a.id),
225
+ outputs: allOutputs.map((a) => a.id),
226
+ // 单 target 且非取消时暴露中间步骤输出;其他场景保持 undefined,
227
+ // 调用方需要中间结果时应改用 eventBus 监听 node:finished 事件
228
+ stepOutputs: isSingleTarget && state.status !== 'cancelled' ? stepOutputs : undefined,
189
229
  duration: Date.now() - startTime,
190
230
  status: state.status === 'cancelled' ? 'cancelled' : 'completed',
191
231
  };
@@ -233,6 +273,20 @@ export class WorkflowExecutor {
233
273
  this.resolveResume(workflowId);
234
274
  this.config.eventBus.emit({ type: 'workflow:cancelled', workflowId });
235
275
  }
276
+ /**
277
+ * 取消所有运行中的工作流(W21.6 runtime.dispose 用)。
278
+ *
279
+ * 遍历 running Map,对每个 state 执行 cancel 的同步部分
280
+ * (status = cancelled + abort + 唤醒 resume resolver + emit event)。
281
+ * 不等待 executor.execute 内部循环跳出 —— abort 后循环在下个 await
282
+ * 点自然抛 AbortError,资源随 Promise reject 释放。
283
+ */
284
+ cancelAll() {
285
+ // 复制一份避免 cancel 内 emit 触发的 listener 回调 mutate 原集合
286
+ for (const workflowId of [...this.running.keys()]) {
287
+ void this.cancel(workflowId);
288
+ }
289
+ }
236
290
  /** 暂停执行 */
237
291
  async pause(workflowId) {
238
292
  const state = this.running.get(workflowId);