@midscene/core 1.15.2-beta-20261012123219.0 → 1.15.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 (180) hide show
  1. package/dist/es/agent/action-readiness.mjs +6 -3
  2. package/dist/es/agent/action-readiness.mjs.map +1 -1
  3. package/dist/es/agent/agent.mjs +4 -2
  4. package/dist/es/agent/agent.mjs.map +1 -1
  5. package/dist/es/agent/execution-session.mjs.map +1 -1
  6. package/dist/es/agent/task-builder.mjs +35 -16
  7. package/dist/es/agent/task-builder.mjs.map +1 -1
  8. package/dist/es/agent/tasks.mjs +35 -12
  9. package/dist/es/agent/tasks.mjs.map +1 -1
  10. package/dist/es/agent/utils.mjs +1 -1
  11. package/dist/es/device/index.mjs +7 -6
  12. package/dist/es/device/index.mjs.map +1 -1
  13. package/dist/es/report-generator.mjs +30 -14
  14. package/dist/es/report-generator.mjs.map +1 -1
  15. package/dist/es/report-html-template.mjs +1 -1
  16. package/dist/es/report.mjs +121 -44
  17. package/dist/es/report.mjs.map +1 -1
  18. package/dist/es/service/index.mjs.map +1 -1
  19. package/dist/es/task-runner.mjs +22 -12
  20. package/dist/es/task-runner.mjs.map +1 -1
  21. package/dist/es/test-run-report.mjs.map +1 -1
  22. package/dist/es/test-runner/engine/case-runner.mjs +1 -1
  23. package/dist/es/test-runner/engine/case-runner.mjs.map +1 -1
  24. package/dist/es/test-runner/engine/document-runtime.mjs +42 -12
  25. package/dist/es/test-runner/engine/document-runtime.mjs.map +1 -1
  26. package/dist/es/test-runner/engine/execute-step.mjs +130 -12
  27. package/dist/es/test-runner/engine/execute-step.mjs.map +1 -1
  28. package/dist/es/test-runner/engine/index.mjs +1 -0
  29. package/dist/es/test-runner/engine/project-runtime.mjs +14 -5
  30. package/dist/es/test-runner/engine/project-runtime.mjs.map +1 -1
  31. package/dist/es/test-runner/engine/resource-operations.mjs +286 -35
  32. package/dist/es/test-runner/engine/resource-operations.mjs.map +1 -1
  33. package/dist/es/test-runner/engine/result-metadata.mjs +21 -0
  34. package/dist/es/test-runner/engine/result-metadata.mjs.map +1 -0
  35. package/dist/es/test-runner/engine/run-collected-case.mjs +30 -9
  36. package/dist/es/test-runner/engine/run-collected-case.mjs.map +1 -1
  37. package/dist/es/test-runner/engine/run-workflow-document.mjs +57 -10
  38. package/dist/es/test-runner/engine/run-workflow-document.mjs.map +1 -1
  39. package/dist/es/test-runner/errors.mjs +26 -13
  40. package/dist/es/test-runner/errors.mjs.map +1 -1
  41. package/dist/es/test-runner/execution-record.mjs +2 -0
  42. package/dist/es/test-runner/execution-record.mjs.map +1 -1
  43. package/dist/es/test-runner/index.mjs +3 -0
  44. package/dist/es/test-runner/reporting/agent-report.mjs +2 -7
  45. package/dist/es/test-runner/reporting/agent-report.mjs.map +1 -1
  46. package/dist/es/test-runner/reporting/merge-report.mjs +207 -6
  47. package/dist/es/test-runner/reporting/merge-report.mjs.map +1 -1
  48. package/dist/es/test-runner/reporting/metrics.mjs +77 -0
  49. package/dist/es/test-runner/reporting/metrics.mjs.map +1 -0
  50. package/dist/es/test-runner/reporting/sanitize.mjs +92 -16
  51. package/dist/es/test-runner/reporting/sanitize.mjs.map +1 -1
  52. package/dist/es/test-runner/reporting/test-plan.mjs +56 -0
  53. package/dist/es/test-runner/reporting/test-plan.mjs.map +1 -0
  54. package/dist/es/test-runner/reporting/test-run-report.mjs +208 -21
  55. package/dist/es/test-runner/reporting/test-run-report.mjs.map +1 -1
  56. package/dist/es/types.mjs.map +1 -1
  57. package/dist/es/utils.mjs +1 -1
  58. package/dist/es/yaml/legacy-yaml-runtime.mjs +3 -3
  59. package/dist/es/yaml/legacy-yaml-runtime.mjs.map +1 -1
  60. package/dist/lib/agent/action-readiness.js +6 -3
  61. package/dist/lib/agent/action-readiness.js.map +1 -1
  62. package/dist/lib/agent/agent.js +4 -2
  63. package/dist/lib/agent/agent.js.map +1 -1
  64. package/dist/lib/agent/execution-session.js.map +1 -1
  65. package/dist/lib/agent/task-builder.js +42 -23
  66. package/dist/lib/agent/task-builder.js.map +1 -1
  67. package/dist/lib/agent/tasks.js +35 -12
  68. package/dist/lib/agent/tasks.js.map +1 -1
  69. package/dist/lib/agent/utils.js +1 -1
  70. package/dist/lib/device/index.js +7 -6
  71. package/dist/lib/device/index.js.map +1 -1
  72. package/dist/lib/report-generator.js +30 -14
  73. package/dist/lib/report-generator.js.map +1 -1
  74. package/dist/lib/report-html-template.js +1 -1
  75. package/dist/lib/report.js +121 -44
  76. package/dist/lib/report.js.map +1 -1
  77. package/dist/lib/service/index.js.map +1 -1
  78. package/dist/lib/task-runner.js +22 -12
  79. package/dist/lib/task-runner.js.map +1 -1
  80. package/dist/lib/test-run-report.js.map +1 -1
  81. package/dist/lib/test-runner/engine/case-runner.js +1 -1
  82. package/dist/lib/test-runner/engine/case-runner.js.map +1 -1
  83. package/dist/lib/test-runner/engine/document-runtime.js +41 -11
  84. package/dist/lib/test-runner/engine/document-runtime.js.map +1 -1
  85. package/dist/lib/test-runner/engine/execute-step.js +131 -10
  86. package/dist/lib/test-runner/engine/execute-step.js.map +1 -1
  87. package/dist/lib/test-runner/engine/index.js +9 -2
  88. package/dist/lib/test-runner/engine/index.js.map +1 -1
  89. package/dist/lib/test-runner/engine/project-runtime.js +13 -4
  90. package/dist/lib/test-runner/engine/project-runtime.js.map +1 -1
  91. package/dist/lib/test-runner/engine/resource-operations.js +319 -38
  92. package/dist/lib/test-runner/engine/resource-operations.js.map +1 -1
  93. package/dist/lib/test-runner/engine/result-metadata.js +58 -0
  94. package/dist/lib/test-runner/engine/result-metadata.js.map +1 -0
  95. package/dist/lib/test-runner/engine/run-collected-case.js +29 -8
  96. package/dist/lib/test-runner/engine/run-collected-case.js.map +1 -1
  97. package/dist/lib/test-runner/engine/run-workflow-document.js +57 -10
  98. package/dist/lib/test-runner/engine/run-workflow-document.js.map +1 -1
  99. package/dist/lib/test-runner/errors.js +42 -23
  100. package/dist/lib/test-runner/errors.js.map +1 -1
  101. package/dist/lib/test-runner/execution-record.js +2 -0
  102. package/dist/lib/test-runner/execution-record.js.map +1 -1
  103. package/dist/lib/test-runner/index.js +88 -16
  104. package/dist/lib/test-runner/index.js.map +1 -1
  105. package/dist/lib/test-runner/node/index.js +2 -2
  106. package/dist/lib/test-runner/reporting/agent-report.js +2 -7
  107. package/dist/lib/test-runner/reporting/agent-report.js.map +1 -1
  108. package/dist/lib/test-runner/reporting/merge-report.js +207 -6
  109. package/dist/lib/test-runner/reporting/merge-report.js.map +1 -1
  110. package/dist/lib/test-runner/reporting/metrics.js +114 -0
  111. package/dist/lib/test-runner/reporting/metrics.js.map +1 -0
  112. package/dist/lib/test-runner/reporting/sanitize.js +94 -15
  113. package/dist/lib/test-runner/reporting/sanitize.js.map +1 -1
  114. package/dist/lib/test-runner/reporting/test-plan.js +90 -0
  115. package/dist/lib/test-runner/reporting/test-plan.js.map +1 -0
  116. package/dist/lib/test-runner/reporting/test-run-report.js +208 -21
  117. package/dist/lib/test-runner/reporting/test-run-report.js.map +1 -1
  118. package/dist/lib/types.js.map +1 -1
  119. package/dist/lib/utils.js +1 -1
  120. package/dist/lib/yaml/legacy-yaml-runtime.js +2 -2
  121. package/dist/lib/yaml/legacy-yaml-runtime.js.map +1 -1
  122. package/dist/types/agent/action-readiness.d.ts +1 -1
  123. package/dist/types/agent/action-readiness.d.ts.map +1 -1
  124. package/dist/types/agent/agent.d.ts +1 -1
  125. package/dist/types/agent/agent.d.ts.map +1 -1
  126. package/dist/types/agent/execution-session.d.ts +1 -1
  127. package/dist/types/agent/execution-session.d.ts.map +1 -1
  128. package/dist/types/agent/task-builder.d.ts +4 -2
  129. package/dist/types/agent/task-builder.d.ts.map +1 -1
  130. package/dist/types/agent/tasks.d.ts +3 -0
  131. package/dist/types/agent/tasks.d.ts.map +1 -1
  132. package/dist/types/device/index.d.ts +3 -3
  133. package/dist/types/device/index.d.ts.map +1 -1
  134. package/dist/types/report-generator.d.ts +4 -2
  135. package/dist/types/report-generator.d.ts.map +1 -1
  136. package/dist/types/report.d.ts.map +1 -1
  137. package/dist/types/service/index.d.ts +2 -2
  138. package/dist/types/service/index.d.ts.map +1 -1
  139. package/dist/types/task-runner.d.ts +1 -1
  140. package/dist/types/task-runner.d.ts.map +1 -1
  141. package/dist/types/test-run-report.d.ts +157 -8
  142. package/dist/types/test-run-report.d.ts.map +1 -1
  143. package/dist/types/test-runner/engine/document-runtime.d.ts.map +1 -1
  144. package/dist/types/test-runner/engine/execute-step.d.ts +1 -0
  145. package/dist/types/test-runner/engine/execute-step.d.ts.map +1 -1
  146. package/dist/types/test-runner/engine/index.d.ts +1 -0
  147. package/dist/types/test-runner/engine/index.d.ts.map +1 -1
  148. package/dist/types/test-runner/engine/project-runtime.d.ts.map +1 -1
  149. package/dist/types/test-runner/engine/resource-operations.d.ts +41 -3
  150. package/dist/types/test-runner/engine/resource-operations.d.ts.map +1 -1
  151. package/dist/types/test-runner/engine/result-metadata.d.ts +18 -0
  152. package/dist/types/test-runner/engine/result-metadata.d.ts.map +1 -0
  153. package/dist/types/test-runner/engine/run-collected-case.d.ts.map +1 -1
  154. package/dist/types/test-runner/engine/run-workflow-document.d.ts.map +1 -1
  155. package/dist/types/test-runner/engine/types.d.ts +47 -8
  156. package/dist/types/test-runner/engine/types.d.ts.map +1 -1
  157. package/dist/types/test-runner/errors.d.ts +7 -0
  158. package/dist/types/test-runner/errors.d.ts.map +1 -1
  159. package/dist/types/test-runner/execution-record.d.ts.map +1 -1
  160. package/dist/types/test-runner/index.d.ts +2 -0
  161. package/dist/types/test-runner/index.d.ts.map +1 -1
  162. package/dist/types/test-runner/node/types.d.ts +9 -1
  163. package/dist/types/test-runner/node/types.d.ts.map +1 -1
  164. package/dist/types/test-runner/parser/types.d.ts +5 -0
  165. package/dist/types/test-runner/parser/types.d.ts.map +1 -1
  166. package/dist/types/test-runner/reporting/agent-report.d.ts.map +1 -1
  167. package/dist/types/test-runner/reporting/merge-report.d.ts +1 -1
  168. package/dist/types/test-runner/reporting/merge-report.d.ts.map +1 -1
  169. package/dist/types/test-runner/reporting/metrics.d.ts +7 -0
  170. package/dist/types/test-runner/reporting/metrics.d.ts.map +1 -0
  171. package/dist/types/test-runner/reporting/sanitize.d.ts +1 -0
  172. package/dist/types/test-runner/reporting/sanitize.d.ts.map +1 -1
  173. package/dist/types/test-runner/reporting/test-plan.d.ts +8 -0
  174. package/dist/types/test-runner/reporting/test-plan.d.ts.map +1 -0
  175. package/dist/types/test-runner/reporting/test-run-report.d.ts.map +1 -1
  176. package/dist/types/test-runner/reporting/types.d.ts +6 -1
  177. package/dist/types/test-runner/reporting/types.d.ts.map +1 -1
  178. package/dist/types/types.d.ts +3 -1
  179. package/dist/types/types.d.ts.map +1 -1
  180. package/package.json +2 -2
@@ -1,13 +1,29 @@
1
+ import { getDebug } from "@midscene/shared/logger";
2
+ import { uuid } from "@midscene/shared/utils";
1
3
  import { WorkflowError } from "../errors.mjs";
2
- const stateKey = Symbol.for('@midscene/core/resource-operations/v3');
4
+ import { sanitizeReportError } from "../reporting/sanitize.mjs";
5
+ function _define_property(obj, key, value) {
6
+ if (key in obj) Object.defineProperty(obj, key, {
7
+ value: value,
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true
11
+ });
12
+ else obj[key] = value;
13
+ return obj;
14
+ }
15
+ const admissionErrorKey = Symbol.for('@midscene/core/resource-admission-error');
16
+ const stateKey = Symbol.for('@midscene/core/resource-operations/v4');
3
17
  const host = globalThis;
4
18
  host[stateKey] ??= {
5
19
  resources: new WeakMap(),
6
20
  scopes: new WeakMap(),
7
21
  parents: new WeakMap(),
8
- cleanupOwners: new WeakMap()
22
+ cleanupOwners: new WeakMap(),
23
+ identities: new WeakMap(),
24
+ listeners: new WeakMap()
9
25
  };
10
- const { resources: resource_operations_resources, scopes, parents, cleanupOwners } = host[stateKey];
26
+ const { resources: resource_operations_resources, scopes, parents, cleanupOwners, identities, listeners } = host[stateKey];
11
27
  const resourceHandle = (resource)=>{
12
28
  let handle = resource_operations_resources.get(resource);
13
29
  if (!handle) {
@@ -20,8 +36,50 @@ const resourceHandle = (resource)=>{
20
36
  return handle;
21
37
  };
22
38
  const getResourceCleanupCompletion = (owner)=>resource_operations_resources.get(owner)?.cleanup;
39
+ function blockingReferences(blockers) {
40
+ const references = new Map();
41
+ const visited = new Set();
42
+ const visit = (record)=>{
43
+ if (visited.has(record)) return;
44
+ visited.add(record);
45
+ if (!record.settled) references.set(record.operationId, {
46
+ operationId: record.operationId,
47
+ ...record.identity
48
+ });
49
+ for (const blocker of record.blockers ?? [])visit(blocker);
50
+ };
51
+ for (const blocker of blockers)visit(blocker);
52
+ return [
53
+ ...references.values()
54
+ ];
55
+ }
56
+ function getResourceCleanupSnapshot(owner) {
57
+ const state = resource_operations_resources.get(owner)?.cleanupState;
58
+ return freezeEvent({
59
+ capturedAt: new Date().toISOString(),
60
+ status: state?.status ?? 'not-required',
61
+ ...state?.operationId ? {
62
+ operationId: state.operationId
63
+ } : {},
64
+ ...state?.status === 'pending' ? {
65
+ blockingOperations: blockingReferences(state.blockers)
66
+ } : {},
67
+ ...state?.error === void 0 ? {} : {
68
+ error: sanitizeReportError(state.error)
69
+ }
70
+ });
71
+ }
23
72
  const RESOURCE_CLEANUP_GRACE_MS = 1000;
24
73
  const RESOURCE_RECOVERY_GRACE_MS = 30000;
74
+ const RESOURCE_TEARDOWN_GRACE_MS = 10000;
75
+ function getPendingScopeOperations(signal) {
76
+ return freezeEvent(blockingReferences([
77
+ ...scopes.get(signal)?.operations.keys() ?? []
78
+ ].flatMap((resource)=>[
79
+ ...resource_operations_resources.get(resource)?.operations.values() ?? []
80
+ ])));
81
+ }
82
+ const hasPendingResourceOperation = (resource)=>(resource_operations_resources.get(resource)?.operations.size ?? 0) > 0;
25
83
  function linkResourceSignal(signal, parent) {
26
84
  if (parent && parent !== signal) parents.set(signal, parent);
27
85
  }
@@ -66,23 +124,167 @@ function assertResourceAvailable(resource, exclusive = false, signal) {
66
124
  code: 'RESOURCE_CLEANUP_PENDING'
67
125
  });
68
126
  const pending = resource_operations_resources.get(resource)?.operations;
69
- if (exclusive && pending?.size && !isOwnerCleanup) throw new WorkflowError('The same Agent instance cannot execute overlapping Runner operations.', {
127
+ if (exclusive && pending?.size) throw new WorkflowError('The same Agent instance cannot execute overlapping Runner operations.', {
70
128
  code: 'RESOURCE_OPERATION_PENDING'
71
129
  });
72
- if (pending && !isOwnerCleanup && [
130
+ if (pending && [
73
131
  ...pending.values()
74
- ].some((signal)=>signal.aborted)) throw new WorkflowError('Cannot reuse an Agent while an aborted YAML action or Runner operation is still running.', {
132
+ ].some((operation)=>operation.signal.aborted)) throw new WorkflowError('Cannot reuse an Agent while an aborted YAML action or Runner operation is still running.', {
75
133
  code: 'RESOURCE_OPERATION_PENDING'
76
134
  });
77
135
  return isOwnerCleanup;
78
136
  }
137
+ function observeResourceOperations(signal, listener) {
138
+ const subscribers = listeners.get(signal) ?? new Set();
139
+ listeners.set(signal, subscribers);
140
+ subscribers.add(listener);
141
+ return ()=>{
142
+ subscribers.delete(listener);
143
+ if (!subscribers.size) listeners.delete(signal);
144
+ };
145
+ }
146
+ function setResourceOperationIdentity(signal, identity) {
147
+ identities.set(signal, Object.freeze({
148
+ ...identity
149
+ }));
150
+ }
151
+ function freezeEvent(value) {
152
+ if ('object' == typeof value && null !== value && !Object.isFrozen(value)) {
153
+ for (const child of Object.values(value))freezeEvent(child);
154
+ Object.freeze(value);
155
+ }
156
+ return value;
157
+ }
158
+ function recordOperation(operation, signal, operationKind) {
159
+ const owners = [
160
+ ...ownerSignals(signal)
161
+ ];
162
+ const identity = owners.map((owner)=>identities.get(owner)).find(Boolean);
163
+ const record = {
164
+ signal,
165
+ operationId: uuid(),
166
+ identity
167
+ };
168
+ const subscribers = new Set(owners.flatMap((owner)=>[
169
+ ...listeners.get(owner) ?? []
170
+ ]));
171
+ let settled = false;
172
+ const emit = (event)=>{
173
+ const frozen = freezeEvent({
174
+ ...identity,
175
+ operationId: record.operationId,
176
+ operationKind,
177
+ at: new Date().toISOString(),
178
+ ...event
179
+ });
180
+ for (const subscriber of subscribers)try {
181
+ subscriber(frozen);
182
+ } catch (error) {
183
+ getDebug('runner:operations', {
184
+ console: true
185
+ })(`Operation observer failed: ${String(error)}`);
186
+ }
187
+ };
188
+ const abort = ()=>emit({
189
+ kind: 'cancel-requested',
190
+ error: sanitizeReportError(signal.reason)
191
+ });
192
+ emit({
193
+ kind: 'started'
194
+ });
195
+ if (signal.aborted) abort();
196
+ else signal.addEventListener('abort', abort, {
197
+ once: true
198
+ });
199
+ const finish = (status, error)=>{
200
+ settled = true;
201
+ record.settled = true;
202
+ signal.removeEventListener('abort', abort);
203
+ emit({
204
+ kind: 'settled',
205
+ status,
206
+ ...'success' !== status ? {
207
+ error: sanitizeReportError(error)
208
+ } : {}
209
+ });
210
+ };
211
+ operation.then(()=>finish('success'), (error)=>finish(isResourceAdmissionError(error) ? 'not-run' : 'failed', error));
212
+ return {
213
+ record,
214
+ runnerReturned (status) {
215
+ emit({
216
+ kind: 'runner-returned',
217
+ status,
218
+ pending: !settled
219
+ });
220
+ },
221
+ cleanupDeferred (blockers) {
222
+ emit({
223
+ kind: 'cleanup-deferred',
224
+ blockingOperations: blockingReferences(blockers)
225
+ });
226
+ }
227
+ };
228
+ }
229
+ let _admissionErrorKey = admissionErrorKey;
230
+ class ResourceAdmissionError extends WorkflowError {
231
+ constructor(code, blockers){
232
+ super('The Agent is occupied by an unfinished operation or pending cleanup; this action was not started.', {
233
+ code,
234
+ details: {
235
+ blockingOperations: blockers.map(({ operationId, identity })=>({
236
+ operationId,
237
+ ...identity
238
+ }))
239
+ }
240
+ }), _define_property(this, "blockedBy", void 0), _define_property(this, _admissionErrorKey, true);
241
+ this.blockedBy = blockers.find((blocker)=>blocker.identity?.stepId)?.identity?.stepId;
242
+ }
243
+ }
244
+ function isResourceAdmissionError(error) {
245
+ return 'object' == typeof error && null !== error && true === error[admissionErrorKey];
246
+ }
247
+ function admitResourceOperation(resource, signal, exclusive = true) {
248
+ let isCleanupOwner;
249
+ try {
250
+ isCleanupOwner = assertResourceAvailable(resource, exclusive, signal);
251
+ } catch (error) {
252
+ if (error instanceof WorkflowError && [
253
+ 'RESOURCE_OPERATION_PENDING',
254
+ 'RESOURCE_CLEANUP_PENDING'
255
+ ].includes(error.code)) throw new ResourceAdmissionError(error.code, [
256
+ ...resource_operations_resources.get(resource)?.operations.values() ?? [],
257
+ ...[
258
+ ...resource_operations_resources.get(resource)?.retiring ?? []
259
+ ].flatMap((owner)=>[
260
+ ...owner.blockers
261
+ ])
262
+ ]);
263
+ throw error;
264
+ }
265
+ return isCleanupOwner;
266
+ }
79
267
  const retainForCleanup = (owner, resource)=>{
80
268
  owner.resources.add(resource);
81
- resourceHandle(resource).retiring.add(owner);
269
+ const handle = resourceHandle(resource);
270
+ for (const operation of handle.operations.values())owner.blockers.add(operation);
271
+ handle.retiring.add(owner);
82
272
  };
83
- function trackResourceOperation(resource, operation, signal) {
273
+ function retainResourceForScope(resource, signal) {
274
+ let retained = false;
275
+ for (const ownerSignal of ownerSignals(signal)){
276
+ const scope = scopes.get(ownerSignal);
277
+ if (!retained && scope?.resources) {
278
+ scope.resources.add(resource);
279
+ retained = true;
280
+ }
281
+ if (scope?.cleanupOwner) retainForCleanup(scope.cleanupOwner, resource);
282
+ }
283
+ }
284
+ function trackResourceOperation(resource, operation, signal, kind = 'resource', existingObservation) {
285
+ const observation = existingObservation ?? recordOperation(operation, signal, kind);
84
286
  const active = resourceHandle(resource).operations;
85
- active.set(operation, signal);
287
+ active.set(operation, observation.record);
86
288
  const ownerScopes = [];
87
289
  let retainedByScope = false;
88
290
  for (const ownerSignal of ownerSignals(signal)){
@@ -100,7 +302,10 @@ function trackResourceOperation(resource, operation, signal) {
100
302
  retainedByScope = true;
101
303
  }
102
304
  const cleanupOwner = cleanupOwners.get(ownerSignal);
103
- if (cleanupOwner) retainForCleanup(cleanupOwner, resource);
305
+ if (cleanupOwner) {
306
+ cleanupOwner.blockers.add(observation.record);
307
+ retainForCleanup(cleanupOwner, resource);
308
+ }
104
309
  }
105
310
  const finish = ()=>{
106
311
  active.delete(operation);
@@ -121,19 +326,24 @@ async function waitForResourceIdle(resource) {
121
326
  }
122
327
  async function recoverCancelledScopeResources(signal, graceMs = RESOURCE_RECOVERY_GRACE_MS) {
123
328
  signal.throwIfAborted();
329
+ const startedAt = Date.now();
124
330
  const pendingOperations = ()=>{
125
331
  const pending = [];
126
- for (const resource of scopes.get(signal)?.operations.keys() ?? [])for (const [operation, ownerSignal] of resource_operations_resources.get(resource)?.operations ?? [])if (ownerSignal.aborted && [
127
- ...ownerSignals(ownerSignal)
128
- ].includes(signal)) pending.push({
129
- operation,
130
- signal: ownerSignal,
131
- resource
132
- });
332
+ for (const resource of scopes.get(signal)?.operations.keys() ?? [])for (const [operation, tracked] of resource_operations_resources.get(resource)?.operations ?? []){
333
+ const ownerSignal = tracked.signal;
334
+ if (ownerSignal.aborted && [
335
+ ...ownerSignals(ownerSignal)
336
+ ].includes(signal)) pending.push({
337
+ operation,
338
+ signal: ownerSignal,
339
+ resource,
340
+ record: tracked
341
+ });
342
+ }
133
343
  return pending;
134
344
  };
135
345
  let pending = pendingOperations();
136
- if (!pending.length) return;
346
+ if (!pending.length) return 0;
137
347
  let timer;
138
348
  let onAbort;
139
349
  const stopped = new Promise((_resolve, reject)=>{
@@ -141,14 +351,21 @@ async function recoverCancelledScopeResources(signal, graceMs = RESOURCE_RECOVER
141
351
  signal.addEventListener('abort', onAbort, {
142
352
  once: true
143
353
  });
144
- timer = setTimeout(()=>reject(new WorkflowError(`Cancelled operations did not finish within ${graceMs}ms; execution cannot continue safely.`, {
354
+ timer = setTimeout(()=>{
355
+ const blockingOperations = blockingReferences(pending.map(({ record })=>record));
356
+ const blockedByStepId = blockingOperations.every(({ stepId })=>stepId === blockingOperations[0]?.stepId) ? blockingOperations[0]?.stepId : void 0;
357
+ reject(new WorkflowError(`The previous operation was still running after ${graceMs / 1000} s. Later steps were not started.`, {
145
358
  code: 'RESOURCE_RECOVERY_TIMEOUT',
146
359
  details: {
147
360
  graceMs,
148
- resourceCount: new Set(pending.map(({ resource })=>resource)).size
361
+ waitedMs: Date.now() - startedAt,
362
+ resourceCount: new Set(pending.map(({ resource })=>resource)).size,
363
+ blockingOperations,
364
+ blockedByStepId
149
365
  },
150
366
  cause: pending[0]?.signal.reason
151
- })), graceMs);
367
+ }));
368
+ }, graceMs);
152
369
  });
153
370
  try {
154
371
  while(pending.length){
@@ -159,18 +376,23 @@ async function recoverCancelledScopeResources(signal, graceMs = RESOURCE_RECOVER
159
376
  signal.throwIfAborted();
160
377
  pending = pendingOperations();
161
378
  }
379
+ return Date.now() - startedAt;
162
380
  } finally{
163
381
  clearTimeout(timer);
164
382
  signal.removeEventListener('abort', onAbort);
165
383
  }
166
384
  }
167
385
  class ResourceCleanupDeferredError extends WorkflowError {
168
- constructor(graceMs, resourceCount){
169
- super('Resource cleanup was deferred because an operation did not stop after cancellation. The resource remains quarantined and cleanup will run when that operation settles.', {
386
+ constructor(graceMs, resourceCount, blockers = []){
387
+ super(`Resource cleanup did not finish within ${graceMs}ms. Resources remain quarantined until cleanup settles.`, {
170
388
  code: 'RESOURCE_CLEANUP_DEFERRED',
171
389
  details: {
172
390
  graceMs,
173
- resourceCount
391
+ resourceCount,
392
+ blockingOperations: blockers.map(({ operationId, identity })=>({
393
+ operationId,
394
+ ...identity
395
+ }))
174
396
  }
175
397
  });
176
398
  }
@@ -178,25 +400,31 @@ class ResourceCleanupDeferredError extends WorkflowError {
178
400
  async function runResourceCleanup(resources, cleanup, options, beforeCleanup) {
179
401
  const owner = {
180
402
  resources: new Set(),
403
+ blockers: new Set(),
181
404
  signal: beforeCleanup?.signal
182
405
  };
183
406
  for (const resource of resources)retainForCleanup(owner, resource);
184
407
  if (owner.signal) cleanupOwners.set(owner.signal, owner);
408
+ const ownerScope = options.signal ? scopes.get(options.signal) : void 0;
409
+ if (ownerScope) ownerScope.cleanupOwner = owner;
185
410
  let defer;
186
411
  const deferred = new Promise((resolve)=>{
187
412
  defer = ()=>resolve({
188
413
  deferred: true
189
414
  });
190
415
  });
191
- const graceMs = options.graceMs ?? RESOURCE_CLEANUP_GRACE_MS;
416
+ let graceMs = options.graceMs ?? RESOURCE_CLEANUP_GRACE_MS;
417
+ let timer;
192
418
  const drain = async ()=>{
193
- const pending = [
194
- ...owner.resources
195
- ].filter((resource)=>resourceHandle(resource).operations.size);
196
- if (!pending.length) return;
197
- const timer = setTimeout(defer, graceMs);
419
+ timer = setTimeout(defer, graceMs);
198
420
  try {
199
- await Promise.all(pending.map(waitForResourceIdle));
421
+ while(true){
422
+ const pending = [
423
+ ...owner.resources
424
+ ].filter((resource)=>resourceHandle(resource).operations.size);
425
+ if (!pending.length) return;
426
+ await Promise.all(pending.map(waitForResourceIdle));
427
+ }
200
428
  } finally{
201
429
  clearTimeout(timer);
202
430
  }
@@ -205,17 +433,37 @@ async function runResourceCleanup(resources, cleanup, options, beforeCleanup) {
205
433
  try {
206
434
  if (beforeCleanup) await beforeCleanup.run(beforeCleanup.signal);
207
435
  await drain();
436
+ graceMs = options.graceMs ?? RESOURCE_TEARDOWN_GRACE_MS;
437
+ timer = setTimeout(defer, graceMs);
208
438
  return await cleanup();
209
439
  } finally{
440
+ clearTimeout(timer);
210
441
  if (owner.signal) cleanupOwners.delete(owner.signal);
442
+ if (ownerScope?.cleanupOwner === owner) ownerScope.cleanupOwner = void 0;
211
443
  for (const resource of owner.resources){
212
444
  const owners = resourceHandle(resource).retiring;
213
445
  owners.delete(owner);
214
446
  }
215
447
  }
216
448
  })();
449
+ const cleanupObservation = options.signal ? recordOperation(completion, options.signal, 'cleanup') : void 0;
217
450
  const ownerKey = options.owner ?? beforeCleanup?.signal ?? resources[0];
218
- if (ownerKey) resourceHandle(ownerKey).cleanup = completion;
451
+ const cleanupState = {
452
+ status: 'pending',
453
+ operationId: cleanupObservation?.record.operationId,
454
+ blockers: owner.blockers
455
+ };
456
+ if (cleanupObservation) cleanupObservation.record.blockers = owner.blockers;
457
+ if (ownerKey) {
458
+ resourceHandle(ownerKey).cleanup = completion;
459
+ resourceHandle(ownerKey).cleanupState = cleanupState;
460
+ }
461
+ completion.then(()=>{
462
+ cleanupState.status = 'completed';
463
+ }, (error)=>{
464
+ cleanupState.status = 'failed';
465
+ cleanupState.error = error;
466
+ });
219
467
  const outcome = await Promise.race([
220
468
  completion.then((value)=>({
221
469
  value
@@ -223,13 +471,16 @@ async function runResourceCleanup(resources, cleanup, options, beforeCleanup) {
223
471
  deferred
224
472
  ]);
225
473
  if ('deferred' in outcome) {
474
+ cleanupObservation?.cleanupDeferred(owner.blockers);
226
475
  completion.catch((error)=>{
227
476
  try {
228
477
  options.onDeferredError(error);
229
478
  } catch {}
230
479
  });
231
- if (options.signal) trackResourceOperation({}, completion, options.signal);
232
- throw new ResourceCleanupDeferredError(graceMs, owner.resources.size);
480
+ if (options.signal) trackResourceOperation({}, completion, options.signal, 'cleanup', cleanupObservation);
481
+ throw new ResourceCleanupDeferredError(graceMs, owner.resources.size, [
482
+ ...owner.blockers
483
+ ]);
233
484
  }
234
485
  return outcome.value;
235
486
  }
@@ -252,6 +503,6 @@ async function cleanupScopeResources(signal, cleanup, options) {
252
503
  run: options.beforeCleanup
253
504
  } : void 0);
254
505
  }
255
- export { RESOURCE_CLEANUP_GRACE_MS, RESOURCE_RECOVERY_GRACE_MS, ResourceCleanupDeferredError, assertResourceAvailable, cleanupResources, cleanupScopeResources, createResourceScope, getResourceCleanupCompletion, linkResourceSignal, recoverCancelledScopeResources, trackResourceOperation, waitForResourceIdle };
506
+ export { RESOURCE_CLEANUP_GRACE_MS, RESOURCE_RECOVERY_GRACE_MS, ResourceAdmissionError, ResourceCleanupDeferredError, admitResourceOperation, assertResourceAvailable, cleanupResources, cleanupScopeResources, createResourceScope, getPendingScopeOperations, getResourceCleanupCompletion, getResourceCleanupSnapshot, hasPendingResourceOperation, isResourceAdmissionError, linkResourceSignal, observeResourceOperations, recordOperation, recoverCancelledScopeResources, retainResourceForScope, setResourceOperationIdentity, trackResourceOperation, waitForResourceIdle };
256
507
 
257
508
  //# sourceMappingURL=resource-operations.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-runner/engine/resource-operations.mjs","sources":["../../../../src/test-runner/engine/resource-operations.ts"],"sourcesContent":["import { WorkflowError } from '../errors';\n\ninterface CleanupOwner {\n resources: Set<object>;\n signal?: AbortSignal;\n}\n\ninterface ResourceScope {\n operations: Map<object, number>;\n resources?: Set<object>;\n}\n\ninterface ResourceHandle {\n operations: Map<Promise<unknown>, AbortSignal>;\n retiring: Set<CleanupOwner>;\n cleanup?: Promise<unknown>;\n}\n\ninterface ResourceState {\n resources: WeakMap<object, ResourceHandle>;\n scopes: WeakMap<AbortSignal, ResourceScope>;\n parents: WeakMap<AbortSignal, AbortSignal>;\n cleanupOwners: WeakMap<AbortSignal, CleanupOwner>;\n}\n\n// CJS and ESM hosts must agree about ownership of the same Agent instance.\nconst stateKey = Symbol.for('@midscene/core/resource-operations/v3');\nconst host = globalThis as typeof globalThis & { [stateKey]?: ResourceState };\nhost[stateKey] ??= {\n resources: new WeakMap(),\n scopes: new WeakMap(),\n parents: new WeakMap(),\n cleanupOwners: new WeakMap(),\n};\nconst { resources, scopes, parents, cleanupOwners } = host[stateKey];\nconst resourceHandle = (resource: object): ResourceHandle => {\n let handle = resources.get(resource);\n if (!handle) {\n handle = { operations: new Map(), retiring: new Set() };\n resources.set(resource, handle);\n }\n return handle;\n};\n\n/** Actual teardown completion belongs to the owner, never to a thrown error. */\nexport const getResourceCleanupCompletion = (\n owner: object,\n): Promise<unknown> | undefined => resources.get(owner)?.cleanup;\n\nexport const RESOURCE_CLEANUP_GRACE_MS = 1000;\nexport const RESOURCE_RECOVERY_GRACE_MS = 30_000;\n\n/** Resource ancestry is independent of cancellation: cleanup can outlive abort. */\nexport function linkResourceSignal(signal: AbortSignal, parent?: AbortSignal) {\n if (parent && parent !== signal) parents.set(signal, parent);\n}\n\nfunction* ownerSignals(signal?: AbortSignal): Generator<AbortSignal> {\n const seen = new Set<AbortSignal>();\n for (\n let owner = signal;\n owner && !seen.has(owner);\n owner = parents.get(owner)\n ) {\n seen.add(owner);\n yield owner;\n }\n}\n\n/** Each Case/document owns its cleanup, even when cancellation is shared. */\nexport function createResourceScope(parent?: AbortSignal) {\n const controller = new AbortController();\n const scope: ResourceScope = { operations: new Map(), resources: new Set() };\n scopes.set(controller.signal, scope);\n linkResourceSignal(controller.signal, parent);\n const abort = () => controller.abort(parent?.reason);\n if (parent?.aborted) abort();\n else parent?.addEventListener('abort', abort, { once: true });\n return {\n signal: controller.signal,\n dispose() {\n parent?.removeEventListener('abort', abort);\n scope.resources = undefined;\n if (!scope.operations.size) scopes.delete(controller.signal);\n },\n };\n}\n\nexport function assertResourceAvailable(\n resource: object,\n exclusive = false,\n signal?: AbortSignal,\n): boolean {\n const retiringOwners = resources.get(resource)?.retiring;\n const isOwnerCleanup = [...ownerSignals(signal)].some((ownerSignal) => {\n const owner = cleanupOwners.get(ownerSignal);\n return owner !== undefined && retiringOwners?.has(owner);\n });\n if (retiringOwners?.size && !isOwnerCleanup)\n throw new WorkflowError(\n 'A resource cannot execute overlapping Runner operations while its cleanup is pending.',\n { code: 'RESOURCE_CLEANUP_PENDING' },\n );\n const pending = resources.get(resource)?.operations;\n if (exclusive && pending?.size && !isOwnerCleanup)\n throw new WorkflowError(\n 'The same Agent instance cannot execute overlapping Runner operations.',\n { code: 'RESOURCE_OPERATION_PENDING' },\n );\n if (\n pending &&\n !isOwnerCleanup &&\n [...pending.values()].some((signal) => signal.aborted)\n )\n throw new WorkflowError(\n 'Cannot reuse an Agent while an aborted YAML action or Runner operation is still running.',\n { code: 'RESOURCE_OPERATION_PENDING' },\n );\n return isOwnerCleanup;\n}\n\nconst retainForCleanup = (owner: CleanupOwner, resource: object) => {\n owner.resources.add(resource);\n resourceHandle(resource).retiring.add(owner);\n};\n\n/** Track actual completion, not the Runner timeout race. */\nexport function trackResourceOperation<T>(\n resource: object,\n operation: Promise<T>,\n signal: AbortSignal,\n): Promise<T> {\n const active = resourceHandle(resource).operations;\n active.set(operation, signal);\n const ownerScopes: Array<[AbortSignal, ResourceScope]> = [];\n let retainedByScope = false;\n for (const ownerSignal of ownerSignals(signal)) {\n const scope: ResourceScope = scopes.get(ownerSignal) ?? {\n operations: new Map(),\n };\n scopes.set(ownerSignal, scope);\n scope.operations.set(resource, (scope.operations.get(resource) ?? 0) + 1);\n ownerScopes.push([ownerSignal, scope]);\n // Only the closest owner retains idle resources for its teardown. Parents\n // wait on active operations, not every Agent used by completed Cases.\n if (!retainedByScope && scope.resources) {\n scope.resources.add(resource);\n retainedByScope = true;\n }\n const cleanupOwner = cleanupOwners.get(ownerSignal);\n if (cleanupOwner) retainForCleanup(cleanupOwner, resource);\n }\n const finish = () => {\n active.delete(operation);\n for (const [ownerSignal, scope] of ownerScopes) {\n const count = scope.operations.get(resource)! - 1;\n if (count) scope.operations.set(resource, count);\n else scope.operations.delete(resource);\n if (!scope.operations.size && !scope.resources)\n scopes.delete(ownerSignal);\n }\n };\n void operation.then(finish, finish);\n return operation;\n}\n\nexport async function waitForResourceIdle(resource: object): Promise<void> {\n while (resources.get(resource)?.operations?.size)\n await Promise.allSettled([...resources.get(resource)!.operations.keys()]);\n}\n\n/** Wait for this scope's cancelled work before scheduling its next Step. */\nexport async function recoverCancelledScopeResources(\n signal: AbortSignal,\n graceMs = RESOURCE_RECOVERY_GRACE_MS,\n): Promise<void> {\n signal.throwIfAborted();\n const pendingOperations = () => {\n const pending: Array<{\n operation: Promise<unknown>;\n signal: AbortSignal;\n resource: object;\n }> = [];\n for (const resource of scopes.get(signal)?.operations.keys() ?? []) {\n for (const [operation, ownerSignal] of resources.get(resource)\n ?.operations ?? []) {\n if (\n ownerSignal.aborted &&\n [...ownerSignals(ownerSignal)].includes(signal)\n )\n pending.push({ operation, signal: ownerSignal, resource });\n }\n }\n return pending;\n };\n let pending = pendingOperations();\n if (!pending.length) return;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let onAbort!: () => void;\n const stopped = new Promise<never>((_resolve, reject) => {\n onAbort = () => reject(signal.reason);\n signal.addEventListener('abort', onAbort, { once: true });\n timer = setTimeout(\n () =>\n reject(\n new WorkflowError(\n `Cancelled operations did not finish within ${graceMs}ms; execution cannot continue safely.`,\n {\n code: 'RESOURCE_RECOVERY_TIMEOUT',\n details: {\n graceMs,\n resourceCount: new Set(pending.map(({ resource }) => resource))\n .size,\n },\n cause: pending[0]?.signal.reason,\n },\n ),\n ),\n graceMs,\n );\n });\n try {\n while (pending.length) {\n await Promise.race([\n Promise.allSettled(pending.map(({ operation }) => operation)),\n stopped,\n ]);\n signal.throwIfAborted();\n pending = pendingOperations();\n }\n } finally {\n clearTimeout(timer);\n signal.removeEventListener('abort', onAbort);\n }\n}\n\nexport class ResourceCleanupDeferredError extends WorkflowError {\n constructor(graceMs: number, resourceCount: number) {\n super(\n 'Resource cleanup was deferred because an operation did not stop after cancellation. The resource remains quarantined and cleanup will run when that operation settles.',\n {\n code: 'RESOURCE_CLEANUP_DEFERRED',\n details: { graceMs, resourceCount },\n },\n );\n }\n}\n\ninterface CleanupOptions {\n /** Stable owner of this cleanup, distinct from borrowed resource identities. */\n owner?: object;\n /** Parent scope observes deferred completion without host-specific forwarding. */\n signal?: AbortSignal;\n graceMs?: number;\n onDeferredError(error: unknown): void;\n}\n\n/** Bound draining, but keep ownership until the actual cleanup has finished. */\nasync function runResourceCleanup<T>(\n resources: readonly object[],\n cleanup: () => Promise<T>,\n options: CleanupOptions,\n beforeCleanup?: {\n signal: AbortSignal;\n run(signal: AbortSignal): Promise<void>;\n },\n): Promise<T> {\n const owner: CleanupOwner = {\n resources: new Set(),\n signal: beforeCleanup?.signal,\n };\n for (const resource of resources) retainForCleanup(owner, resource);\n if (owner.signal) cleanupOwners.set(owner.signal, owner);\n\n let defer!: () => void;\n const deferred = new Promise<{ deferred: true }>((resolve) => {\n defer = () => resolve({ deferred: true });\n });\n const graceMs = options.graceMs ?? RESOURCE_CLEANUP_GRACE_MS;\n const drain = async () => {\n const pending = [...owner.resources].filter(\n (resource) => resourceHandle(resource).operations.size,\n );\n if (!pending.length) return;\n const timer = setTimeout(defer, graceMs);\n try {\n await Promise.all(pending.map(waitForResourceIdle));\n } finally {\n clearTimeout(timer);\n }\n };\n const completion = (async () => {\n try {\n if (beforeCleanup) {\n // Authored lifecycle cleanup must be allowed to stop or reset a\n // resource whose timed-out operation is still settling. Its signal is\n // registered as the retiring owner's cleanup signal, so unrelated\n // scopes remain quarantined while this hook runs.\n await beforeCleanup.run(beforeCleanup.signal);\n }\n // Lifecycle hooks can themselves time out with an action still running.\n // Drain both the original work and any operation started by the hook\n // before disposing the resource.\n await drain();\n return await cleanup();\n } finally {\n if (owner.signal) cleanupOwners.delete(owner.signal);\n for (const resource of owner.resources) {\n const owners = resourceHandle(resource).retiring;\n owners.delete(owner);\n }\n }\n })();\n const ownerKey = options.owner ?? beforeCleanup?.signal ?? resources[0];\n if (ownerKey) resourceHandle(ownerKey).cleanup = completion;\n const outcome = await Promise.race([\n completion.then((value) => ({ value })),\n deferred,\n ]);\n if ('deferred' in outcome) {\n void completion.catch((error) => {\n try {\n options.onDeferredError(error);\n } catch {\n // The returned completion still carries the original cleanup failure.\n }\n });\n if (options.signal) trackResourceOperation({}, completion, options.signal);\n throw new ResourceCleanupDeferredError(graceMs, owner.resources.size);\n }\n return outcome.value;\n}\n\nexport function cleanupResources<T>(\n resources: readonly object[],\n cleanup: () => Promise<T>,\n options: CleanupOptions,\n): Promise<T> {\n return runResourceCleanup(resources, cleanup, options);\n}\n\nexport async function cleanupScopeResources<T>(\n signal: AbortSignal,\n cleanup: () => Promise<T>,\n options: CleanupOptions & {\n beforeCleanup?(signal: AbortSignal): Promise<void>;\n },\n): Promise<T> {\n const cleanupSignal = new AbortController().signal;\n linkResourceSignal(cleanupSignal, signal);\n const scope = scopes.get(signal);\n return runResourceCleanup(\n [...(scope?.resources ?? []), ...(scope?.operations.keys() ?? [])],\n cleanup,\n { ...options, owner: signal, signal },\n options.beforeCleanup\n ? { signal: cleanupSignal, run: options.beforeCleanup }\n : undefined,\n );\n}\n"],"names":["stateKey","Symbol","host","globalThis","WeakMap","resources","scopes","parents","cleanupOwners","resourceHandle","resource","handle","Map","Set","getResourceCleanupCompletion","owner","RESOURCE_CLEANUP_GRACE_MS","RESOURCE_RECOVERY_GRACE_MS","linkResourceSignal","signal","parent","ownerSignals","seen","createResourceScope","controller","AbortController","scope","abort","undefined","assertResourceAvailable","exclusive","retiringOwners","isOwnerCleanup","ownerSignal","WorkflowError","pending","retainForCleanup","trackResourceOperation","operation","active","ownerScopes","retainedByScope","cleanupOwner","finish","count","waitForResourceIdle","Promise","recoverCancelledScopeResources","graceMs","pendingOperations","timer","onAbort","stopped","_resolve","reject","setTimeout","clearTimeout","ResourceCleanupDeferredError","resourceCount","runResourceCleanup","cleanup","options","beforeCleanup","defer","deferred","resolve","drain","completion","owners","ownerKey","outcome","value","error","cleanupResources","cleanupScopeResources","cleanupSignal"],"mappings":"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"}
1
+ {"version":3,"file":"test-runner/engine/resource-operations.mjs","sources":["../../../../src/test-runner/engine/resource-operations.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\nimport { uuid } from '@midscene/shared/utils';\nimport type {\n TestRunReportCleanup,\n TestRunReportOperationEvent,\n TestRunReportOperationReference,\n} from '../../test-run-report';\nimport { WorkflowError } from '../errors';\nimport { sanitizeReportError } from '../reporting/sanitize';\n\ninterface OperationIdentity {\n scopeId: string;\n stepId?: string;\n node?: string;\n}\ninterface ResourceOperation {\n signal: AbortSignal;\n operationId: string;\n identity?: OperationIdentity;\n settled?: boolean;\n blockers?: Set<ResourceOperation>;\n}\ntype OperationListener = (event: Readonly<TestRunReportOperationEvent>) => void;\n\ninterface CleanupOwner {\n resources: Set<object>;\n signal?: AbortSignal;\n blockers: Set<ResourceOperation>;\n}\n\ninterface ResourceScope {\n operations: Map<object, number>;\n resources?: Set<object>;\n cleanupOwner?: CleanupOwner;\n}\n\ninterface ResourceHandle {\n operations: Map<Promise<unknown>, ResourceOperation>;\n retiring: Set<CleanupOwner>;\n cleanup?: Promise<unknown>;\n cleanupState?: {\n operationId?: string;\n status: 'pending' | 'completed' | 'failed';\n blockers: Set<ResourceOperation>;\n error?: unknown;\n };\n}\n\ninterface ResourceState {\n resources: WeakMap<object, ResourceHandle>;\n scopes: WeakMap<AbortSignal, ResourceScope>;\n parents: WeakMap<AbortSignal, AbortSignal>;\n cleanupOwners: WeakMap<AbortSignal, CleanupOwner>;\n identities: WeakMap<AbortSignal, OperationIdentity>;\n listeners: WeakMap<AbortSignal, Set<OperationListener>>;\n}\n\n// CJS and ESM hosts must agree about ownership of the same Agent instance.\nconst admissionErrorKey = Symbol.for('@midscene/core/resource-admission-error');\nconst stateKey = Symbol.for('@midscene/core/resource-operations/v4');\nconst host = globalThis as typeof globalThis & { [stateKey]?: ResourceState };\nhost[stateKey] ??= {\n resources: new WeakMap(),\n scopes: new WeakMap(),\n parents: new WeakMap(),\n cleanupOwners: new WeakMap(),\n identities: new WeakMap(),\n listeners: new WeakMap(),\n};\nconst { resources, scopes, parents, cleanupOwners, identities, listeners } =\n host[stateKey];\nconst resourceHandle = (resource: object): ResourceHandle => {\n let handle = resources.get(resource);\n if (!handle) {\n handle = {\n operations: new Map(),\n retiring: new Set(),\n };\n resources.set(resource, handle);\n }\n return handle;\n};\n\n/** Actual teardown completion belongs to the owner, never to a thrown error. */\nexport const getResourceCleanupCompletion = (\n owner: object,\n): Promise<unknown> | undefined => resources.get(owner)?.cleanup;\n\nfunction blockingReferences(\n blockers: Iterable<ResourceOperation>,\n): TestRunReportOperationReference[] {\n const references = new Map<string, TestRunReportOperationReference>();\n const visited = new Set<ResourceOperation>();\n const visit = (record: ResourceOperation) => {\n if (visited.has(record)) return;\n visited.add(record);\n if (!record.settled)\n references.set(record.operationId, {\n operationId: record.operationId,\n ...record.identity,\n });\n for (const blocker of record.blockers ?? []) visit(blocker);\n };\n for (const blocker of blockers) visit(blocker);\n return [...references.values()];\n}\n\n/** Read a copy at the publication boundary; never expose the live cleanup state. */\nexport function getResourceCleanupSnapshot(\n owner: object,\n): TestRunReportCleanup {\n const state = resources.get(owner)?.cleanupState;\n return freezeEvent({\n capturedAt: new Date().toISOString(),\n status: state?.status ?? 'not-required',\n ...(state?.operationId ? { operationId: state.operationId } : {}),\n ...(state?.status === 'pending'\n ? { blockingOperations: blockingReferences(state.blockers) }\n : {}),\n ...(state?.error === undefined\n ? {}\n : { error: sanitizeReportError(state.error) }),\n });\n}\n\nexport const RESOURCE_CLEANUP_GRACE_MS = 1000;\nexport const RESOURCE_RECOVERY_GRACE_MS = 30_000;\nconst RESOURCE_TEARDOWN_GRACE_MS = 10_000;\n\nexport function getPendingScopeOperations(\n signal: AbortSignal,\n): TestRunReportOperationReference[] {\n return freezeEvent(\n blockingReferences(\n [...(scopes.get(signal)?.operations.keys() ?? [])].flatMap((resource) => [\n ...(resources.get(resource)?.operations.values() ?? []),\n ]),\n ),\n );\n}\n\n/** Snapshotting must not wait for a timed-out operation to release its resource. */\nexport const hasPendingResourceOperation = (resource: object): boolean =>\n (resources.get(resource)?.operations.size ?? 0) > 0;\n\n/** Resource ancestry is independent of cancellation: cleanup can outlive abort. */\nexport function linkResourceSignal(signal: AbortSignal, parent?: AbortSignal) {\n if (parent && parent !== signal) parents.set(signal, parent);\n}\n\nfunction* ownerSignals(signal?: AbortSignal): Generator<AbortSignal> {\n const seen = new Set<AbortSignal>();\n for (\n let owner = signal;\n owner && !seen.has(owner);\n owner = parents.get(owner)\n ) {\n seen.add(owner);\n yield owner;\n }\n}\n\n/** Each Case/document owns its cleanup, even when cancellation is shared. */\nexport function createResourceScope(parent?: AbortSignal) {\n const controller = new AbortController();\n const scope: ResourceScope = { operations: new Map(), resources: new Set() };\n scopes.set(controller.signal, scope);\n linkResourceSignal(controller.signal, parent);\n const abort = () => controller.abort(parent?.reason);\n if (parent?.aborted) abort();\n else parent?.addEventListener('abort', abort, { once: true });\n return {\n signal: controller.signal,\n dispose() {\n parent?.removeEventListener('abort', abort);\n scope.resources = undefined;\n if (!scope.operations.size) scopes.delete(controller.signal);\n },\n };\n}\n\nexport function assertResourceAvailable(\n resource: object,\n exclusive = false,\n signal?: AbortSignal,\n): boolean {\n const retiringOwners = resources.get(resource)?.retiring;\n const isOwnerCleanup = [...ownerSignals(signal)].some((ownerSignal) => {\n const owner = cleanupOwners.get(ownerSignal);\n return owner !== undefined && retiringOwners?.has(owner);\n });\n if (retiringOwners?.size && !isOwnerCleanup)\n throw new WorkflowError(\n 'A resource cannot execute overlapping Runner operations while its cleanup is pending.',\n { code: 'RESOURCE_CLEANUP_PENDING' },\n );\n const pending = resources.get(resource)?.operations;\n if (exclusive && pending?.size)\n throw new WorkflowError(\n 'The same Agent instance cannot execute overlapping Runner operations.',\n { code: 'RESOURCE_OPERATION_PENDING' },\n );\n if (\n pending &&\n [...pending.values()].some((operation) => operation.signal.aborted)\n )\n throw new WorkflowError(\n 'Cannot reuse an Agent while an aborted YAML action or Runner operation is still running.',\n { code: 'RESOURCE_OPERATION_PENDING' },\n );\n return isOwnerCleanup;\n}\n\n/** Capture subscribers at registration: late settlement stays with its original run. */\nexport function observeResourceOperations(\n signal: AbortSignal,\n listener: OperationListener,\n) {\n const subscribers = listeners.get(signal) ?? new Set<OperationListener>();\n listeners.set(signal, subscribers);\n subscribers.add(listener);\n return () => {\n subscribers.delete(listener);\n if (!subscribers.size) listeners.delete(signal);\n };\n}\n\nexport function setResourceOperationIdentity(\n signal: AbortSignal,\n identity: OperationIdentity,\n) {\n identities.set(signal, Object.freeze({ ...identity }));\n}\n\nfunction freezeEvent<T>(value: T): T {\n if (typeof value === 'object' && value !== null && !Object.isFrozen(value)) {\n for (const child of Object.values(value)) freezeEvent(child);\n Object.freeze(value);\n }\n return value;\n}\n\nexport function recordOperation(\n operation: Promise<unknown>,\n signal: AbortSignal,\n operationKind: TestRunReportOperationEvent['operationKind'],\n) {\n const owners = [...ownerSignals(signal)];\n const identity = owners.map((owner) => identities.get(owner)).find(Boolean);\n const record: ResourceOperation = { signal, operationId: uuid(), identity };\n const subscribers = new Set(\n owners.flatMap((owner) => [...(listeners.get(owner) ?? [])]),\n );\n let settled = false;\n const emit = (\n event: Omit<\n TestRunReportOperationEvent,\n 'operationId' | 'operationKind' | 'at'\n >,\n ) => {\n const frozen = freezeEvent({\n ...identity,\n operationId: record.operationId,\n operationKind,\n at: new Date().toISOString(),\n ...event,\n });\n for (const subscriber of subscribers) {\n try {\n subscriber(frozen);\n } catch (error) {\n getDebug('runner:operations', { console: true })(\n `Operation observer failed: ${String(error)}`,\n );\n }\n }\n };\n const abort = () =>\n emit({\n kind: 'cancel-requested',\n error: sanitizeReportError(signal.reason),\n });\n emit({ kind: 'started' });\n if (signal.aborted) abort();\n else signal.addEventListener('abort', abort, { once: true });\n const finish = (\n status: 'success' | 'failed' | 'not-run',\n error?: unknown,\n ) => {\n settled = true;\n record.settled = true;\n signal.removeEventListener('abort', abort);\n emit({\n kind: 'settled',\n status,\n ...(status !== 'success' ? { error: sanitizeReportError(error) } : {}),\n });\n };\n void operation.then(\n () => finish('success'),\n (error) =>\n finish(isResourceAdmissionError(error) ? 'not-run' : 'failed', error),\n );\n return {\n record,\n runnerReturned(status: 'success' | 'failed' | 'not-run') {\n emit({ kind: 'runner-returned', status, pending: !settled });\n },\n cleanupDeferred(blockers: Iterable<ResourceOperation>) {\n emit({\n kind: 'cleanup-deferred',\n blockingOperations: blockingReferences(blockers),\n });\n },\n };\n}\n\n/** A scheduling decision, distinct from an action that ran and failed. */\nexport class ResourceAdmissionError extends WorkflowError {\n readonly blockedBy?: string;\n readonly [admissionErrorKey] = true;\n constructor(code: string, blockers: readonly ResourceOperation[]) {\n super(\n 'The Agent is occupied by an unfinished operation or pending cleanup; this action was not started.',\n {\n code,\n details: {\n blockingOperations: blockers.map(({ operationId, identity }) => ({\n operationId,\n ...identity,\n })),\n },\n },\n );\n this.blockedBy = blockers.find(\n (blocker) => blocker.identity?.stepId,\n )?.identity?.stepId;\n }\n}\n\nexport function isResourceAdmissionError(\n error: unknown,\n): error is ResourceAdmissionError {\n return (\n typeof error === 'object' &&\n error !== null &&\n (error as ResourceAdmissionError)[admissionErrorKey] === true\n );\n}\n\nexport function admitResourceOperation(\n resource: object,\n signal: AbortSignal,\n exclusive = true,\n): boolean {\n let isCleanupOwner: boolean;\n try {\n isCleanupOwner = assertResourceAvailable(resource, exclusive, signal);\n } catch (error) {\n if (\n error instanceof WorkflowError &&\n ['RESOURCE_OPERATION_PENDING', 'RESOURCE_CLEANUP_PENDING'].includes(\n error.code,\n )\n )\n throw new ResourceAdmissionError(error.code, [\n ...(resources.get(resource)?.operations.values() ?? []),\n ...[...(resources.get(resource)?.retiring ?? [])].flatMap((owner) => [\n ...owner.blockers,\n ]),\n ]);\n throw error;\n }\n return isCleanupOwner;\n}\n\nconst retainForCleanup = (owner: CleanupOwner, resource: object) => {\n owner.resources.add(resource);\n const handle = resourceHandle(resource);\n for (const operation of handle.operations.values())\n owner.blockers.add(operation);\n handle.retiring.add(owner);\n};\n\n/** Acquisition establishes cleanup ownership even if action admission is rejected. */\nexport function retainResourceForScope(resource: object, signal: AbortSignal) {\n let retained = false;\n for (const ownerSignal of ownerSignals(signal)) {\n const scope = scopes.get(ownerSignal);\n if (!retained && scope?.resources) {\n scope.resources.add(resource);\n retained = true;\n }\n if (scope?.cleanupOwner) retainForCleanup(scope.cleanupOwner, resource);\n }\n}\n\n/** Track actual completion, not the Runner timeout race. */\nexport function trackResourceOperation<T>(\n resource: object,\n operation: Promise<T>,\n signal: AbortSignal,\n kind: TestRunReportOperationEvent['operationKind'] = 'resource',\n existingObservation?: ReturnType<typeof recordOperation>,\n): Promise<T> {\n const observation =\n existingObservation ?? recordOperation(operation, signal, kind);\n const active = resourceHandle(resource).operations;\n active.set(operation, observation.record);\n const ownerScopes: Array<[AbortSignal, ResourceScope]> = [];\n let retainedByScope = false;\n for (const ownerSignal of ownerSignals(signal)) {\n const scope: ResourceScope = scopes.get(ownerSignal) ?? {\n operations: new Map(),\n };\n scopes.set(ownerSignal, scope);\n scope.operations.set(resource, (scope.operations.get(resource) ?? 0) + 1);\n ownerScopes.push([ownerSignal, scope]);\n // Only the closest owner retains idle resources for its teardown. Parents\n // wait on active operations, not every Agent used by completed Cases.\n if (!retainedByScope && scope.resources) {\n scope.resources.add(resource);\n retainedByScope = true;\n }\n const cleanupOwner = cleanupOwners.get(ownerSignal);\n if (cleanupOwner) {\n cleanupOwner.blockers.add(observation.record);\n retainForCleanup(cleanupOwner, resource);\n }\n }\n const finish = () => {\n active.delete(operation);\n for (const [ownerSignal, scope] of ownerScopes) {\n const count = scope.operations.get(resource)! - 1;\n if (count) scope.operations.set(resource, count);\n else scope.operations.delete(resource);\n if (!scope.operations.size && !scope.resources)\n scopes.delete(ownerSignal);\n }\n };\n void operation.then(finish, finish);\n return operation;\n}\n\nexport async function waitForResourceIdle(resource: object): Promise<void> {\n while (resources.get(resource)?.operations?.size)\n await Promise.allSettled([...resources.get(resource)!.operations.keys()]);\n}\n\n/** Wait for this scope's cancelled work before scheduling its next Step. */\nexport async function recoverCancelledScopeResources(\n signal: AbortSignal,\n graceMs = RESOURCE_RECOVERY_GRACE_MS,\n): Promise<number> {\n signal.throwIfAborted();\n const startedAt = Date.now();\n const pendingOperations = () => {\n const pending: Array<{\n operation: Promise<unknown>;\n signal: AbortSignal;\n resource: object;\n record: ResourceOperation;\n }> = [];\n for (const resource of scopes.get(signal)?.operations.keys() ?? []) {\n for (const [operation, tracked] of resources.get(resource)?.operations ??\n []) {\n const ownerSignal = tracked.signal;\n if (\n ownerSignal.aborted &&\n [...ownerSignals(ownerSignal)].includes(signal)\n )\n pending.push({\n operation,\n signal: ownerSignal,\n resource,\n record: tracked,\n });\n }\n }\n return pending;\n };\n let pending = pendingOperations();\n if (!pending.length) return 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let onAbort!: () => void;\n const stopped = new Promise<never>((_resolve, reject) => {\n onAbort = () => reject(signal.reason);\n signal.addEventListener('abort', onAbort, { once: true });\n timer = setTimeout(() => {\n const blockingOperations = blockingReferences(\n pending.map(({ record }) => record),\n );\n const blockedByStepId = blockingOperations.every(\n ({ stepId }) => stepId === blockingOperations[0]?.stepId,\n )\n ? blockingOperations[0]?.stepId\n : undefined;\n reject(\n new WorkflowError(\n `The previous operation was still running after ${graceMs / 1000} s. Later steps were not started.`,\n {\n code: 'RESOURCE_RECOVERY_TIMEOUT',\n details: {\n graceMs,\n waitedMs: Date.now() - startedAt,\n resourceCount: new Set(pending.map(({ resource }) => resource))\n .size,\n blockingOperations,\n blockedByStepId,\n },\n cause: pending[0]?.signal.reason,\n },\n ),\n );\n }, graceMs);\n });\n try {\n while (pending.length) {\n await Promise.race([\n Promise.allSettled(pending.map(({ operation }) => operation)),\n stopped,\n ]);\n signal.throwIfAborted();\n pending = pendingOperations();\n }\n return Date.now() - startedAt;\n } finally {\n clearTimeout(timer);\n signal.removeEventListener('abort', onAbort);\n }\n}\n\nexport class ResourceCleanupDeferredError extends WorkflowError {\n constructor(\n graceMs: number,\n resourceCount: number,\n blockers: readonly ResourceOperation[] = [],\n ) {\n super(\n `Resource cleanup did not finish within ${graceMs}ms. Resources remain quarantined until cleanup settles.`,\n {\n code: 'RESOURCE_CLEANUP_DEFERRED',\n details: {\n graceMs,\n resourceCount,\n blockingOperations: blockers.map(({ operationId, identity }) => ({\n operationId,\n ...identity,\n })),\n },\n },\n );\n }\n}\n\ninterface CleanupOptions {\n /** Stable owner of this cleanup, distinct from borrowed resource identities. */\n owner?: object;\n /** Parent scope observes deferred completion without host-specific forwarding. */\n signal?: AbortSignal;\n graceMs?: number;\n onDeferredError(error: unknown): void;\n}\n\n/** Bound waiting, but keep ownership until the actual cleanup has finished. */\nasync function runResourceCleanup<T>(\n resources: readonly object[],\n cleanup: () => Promise<T>,\n options: CleanupOptions,\n beforeCleanup?: {\n signal: AbortSignal;\n run(signal: AbortSignal): Promise<void>;\n },\n): Promise<T> {\n const owner: CleanupOwner = {\n resources: new Set(),\n blockers: new Set(),\n signal: beforeCleanup?.signal,\n };\n for (const resource of resources) retainForCleanup(owner, resource);\n if (owner.signal) cleanupOwners.set(owner.signal, owner);\n const ownerScope = options.signal ? scopes.get(options.signal) : undefined;\n if (ownerScope) ownerScope.cleanupOwner = owner;\n\n let defer!: () => void;\n const deferred = new Promise<{ deferred: true }>((resolve) => {\n defer = () => resolve({ deferred: true });\n });\n let graceMs = options.graceMs ?? RESOURCE_CLEANUP_GRACE_MS;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const drain = async () => {\n timer = setTimeout(defer, graceMs);\n try {\n // An acquisition may finish late and add another shared resource to its\n // owner. Keep draining that resource before calling any disposer.\n while (true) {\n const pending = [...owner.resources].filter(\n (resource) => resourceHandle(resource).operations.size,\n );\n if (!pending.length) return;\n await Promise.all(pending.map(waitForResourceIdle));\n }\n } finally {\n clearTimeout(timer);\n }\n };\n const completion = (async () => {\n try {\n if (beforeCleanup) {\n // Hooks may use idle resources owned by this cleanup. Busy resources\n // still reject overlapping actions, including cleanup actions.\n await beforeCleanup.run(beforeCleanup.signal);\n }\n // Lifecycle hooks can themselves time out with an action still running.\n // Drain both the original work and any operation started by the hook\n // before disposing the resource.\n await drain();\n graceMs = options.graceMs ?? RESOURCE_TEARDOWN_GRACE_MS;\n timer = setTimeout(defer, graceMs);\n return await cleanup();\n } finally {\n clearTimeout(timer);\n if (owner.signal) cleanupOwners.delete(owner.signal);\n if (ownerScope?.cleanupOwner === owner)\n ownerScope.cleanupOwner = undefined;\n for (const resource of owner.resources) {\n const owners = resourceHandle(resource).retiring;\n owners.delete(owner);\n }\n }\n })();\n const cleanupObservation = options.signal\n ? recordOperation(completion, options.signal, 'cleanup')\n : undefined;\n const ownerKey = options.owner ?? beforeCleanup?.signal ?? resources[0];\n const cleanupState: NonNullable<ResourceHandle['cleanupState']> = {\n status: 'pending',\n operationId: cleanupObservation?.record.operationId,\n blockers: owner.blockers,\n };\n if (cleanupObservation) cleanupObservation.record.blockers = owner.blockers;\n if (ownerKey) {\n resourceHandle(ownerKey).cleanup = completion;\n resourceHandle(ownerKey).cleanupState = cleanupState;\n }\n void completion.then(\n () => {\n cleanupState.status = 'completed';\n },\n (error) => {\n cleanupState.status = 'failed';\n cleanupState.error = error;\n },\n );\n const outcome = await Promise.race([\n completion.then((value) => ({ value })),\n deferred,\n ]);\n if ('deferred' in outcome) {\n cleanupObservation?.cleanupDeferred(owner.blockers);\n void completion.catch((error) => {\n try {\n options.onDeferredError(error);\n } catch {\n // The returned completion still carries the original cleanup failure.\n }\n });\n if (options.signal)\n trackResourceOperation(\n {},\n completion,\n options.signal,\n 'cleanup',\n cleanupObservation,\n );\n throw new ResourceCleanupDeferredError(graceMs, owner.resources.size, [\n ...owner.blockers,\n ]);\n }\n return outcome.value;\n}\n\nexport function cleanupResources<T>(\n resources: readonly object[],\n cleanup: () => Promise<T>,\n options: CleanupOptions,\n): Promise<T> {\n return runResourceCleanup(resources, cleanup, options);\n}\n\nexport async function cleanupScopeResources<T>(\n signal: AbortSignal,\n cleanup: () => Promise<T>,\n options: CleanupOptions & {\n beforeCleanup?(signal: AbortSignal): Promise<void>;\n },\n): Promise<T> {\n const cleanupSignal = new AbortController().signal;\n linkResourceSignal(cleanupSignal, signal);\n const scope = scopes.get(signal);\n return runResourceCleanup(\n [...(scope?.resources ?? []), ...(scope?.operations.keys() ?? [])],\n cleanup,\n { ...options, owner: signal, signal },\n options.beforeCleanup\n ? { signal: cleanupSignal, run: options.beforeCleanup }\n : undefined,\n );\n}\n"],"names":["admissionErrorKey","Symbol","stateKey","host","globalThis","WeakMap","resources","scopes","parents","cleanupOwners","identities","listeners","resourceHandle","resource","handle","Map","Set","getResourceCleanupCompletion","owner","blockingReferences","blockers","references","visited","visit","record","blocker","getResourceCleanupSnapshot","state","freezeEvent","Date","undefined","sanitizeReportError","RESOURCE_CLEANUP_GRACE_MS","RESOURCE_RECOVERY_GRACE_MS","RESOURCE_TEARDOWN_GRACE_MS","getPendingScopeOperations","signal","hasPendingResourceOperation","linkResourceSignal","parent","ownerSignals","seen","createResourceScope","controller","AbortController","scope","abort","assertResourceAvailable","exclusive","retiringOwners","isOwnerCleanup","ownerSignal","WorkflowError","pending","operation","observeResourceOperations","listener","subscribers","setResourceOperationIdentity","identity","Object","value","child","recordOperation","operationKind","owners","Boolean","uuid","settled","emit","event","frozen","subscriber","error","getDebug","String","finish","status","isResourceAdmissionError","_admissionErrorKey","ResourceAdmissionError","code","operationId","admitResourceOperation","isCleanupOwner","retainForCleanup","retainResourceForScope","retained","trackResourceOperation","kind","existingObservation","observation","active","ownerScopes","retainedByScope","cleanupOwner","count","waitForResourceIdle","Promise","recoverCancelledScopeResources","graceMs","startedAt","pendingOperations","tracked","timer","onAbort","stopped","_resolve","reject","setTimeout","blockingOperations","blockedByStepId","stepId","clearTimeout","ResourceCleanupDeferredError","resourceCount","runResourceCleanup","cleanup","options","beforeCleanup","ownerScope","defer","deferred","resolve","drain","completion","cleanupObservation","ownerKey","cleanupState","outcome","cleanupResources","cleanupScopeResources","cleanupSignal"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ function phaseResult(plannedStepCount, steps) {
2
+ const executedStepCount = steps.filter((step)=>'not-run' !== step.status).length;
3
+ return {
4
+ plannedStepCount,
5
+ executedStepCount,
6
+ status: 0 === plannedStepCount ? 'not-required' : steps.some((step)=>'failed' === step.status) ? 'failed' : executedStepCount < plannedStepCount ? 'not-run' : 'success'
7
+ };
8
+ }
9
+ function decideRetry(options) {
10
+ const reason = options.infrastructureError ? 'infrastructure-error' : options.cleanupPending ? 'cleanup-pending' : options.cleanupFailed ? 'cleanup-failed' : options.operationPending ? 'operation-pending' : options.fatalError ? 'fatal-error' : options.interrupted ? 'interrupted' : options.shouldStop ? 'bail' : 'success' === options.status ? 'passed' : options.attemptIndex >= options.retryCount ? 'budget-exhausted' : 'attempt-failed';
11
+ return {
12
+ scope: options.scope,
13
+ retryCount: options.retryCount,
14
+ attemptIndex: options.attemptIndex,
15
+ decision: 'attempt-failed' === reason ? 'retry' : 'stop',
16
+ reason
17
+ };
18
+ }
19
+ export { decideRetry, phaseResult };
20
+
21
+ //# sourceMappingURL=result-metadata.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-runner/engine/result-metadata.mjs","sources":["../../../../src/test-runner/engine/result-metadata.ts"],"sourcesContent":["import type {\n TestRunReportPhaseResult,\n TestRunReportRetryDecision,\n} from '../../test-run-report';\nimport type { StepRunResult } from './types';\n\nexport function phaseResult(\n plannedStepCount: number,\n steps: readonly StepRunResult[],\n): TestRunReportPhaseResult {\n const executedStepCount = steps.filter(\n (step) => step.status !== 'not-run',\n ).length;\n return {\n plannedStepCount,\n executedStepCount,\n status:\n plannedStepCount === 0\n ? 'not-required'\n : steps.some((step) => step.status === 'failed')\n ? 'failed'\n : executedStepCount < plannedStepCount\n ? 'not-run'\n : 'success',\n };\n}\n\n/** Facts at the retry boundary, separate from the eventual cleanup outcome. */\nexport function decideRetry(options: {\n scope: 'case' | 'document';\n retryCount: number;\n attemptIndex: number;\n status: 'success' | 'failed';\n cleanupPending?: boolean;\n cleanupFailed?: boolean;\n operationPending?: boolean;\n infrastructureError?: boolean;\n fatalError?: boolean;\n interrupted?: boolean;\n shouldStop?: boolean;\n}): TestRunReportRetryDecision {\n const reason = options.infrastructureError\n ? 'infrastructure-error'\n : options.cleanupPending\n ? 'cleanup-pending'\n : options.cleanupFailed\n ? 'cleanup-failed'\n : options.operationPending\n ? 'operation-pending'\n : options.fatalError\n ? 'fatal-error'\n : options.interrupted\n ? 'interrupted'\n : options.shouldStop\n ? 'bail'\n : options.status === 'success'\n ? 'passed'\n : options.attemptIndex >= options.retryCount\n ? 'budget-exhausted'\n : 'attempt-failed';\n return {\n scope: options.scope,\n retryCount: options.retryCount,\n attemptIndex: options.attemptIndex,\n decision: reason === 'attempt-failed' ? 'retry' : 'stop',\n reason,\n };\n}\n"],"names":["phaseResult","plannedStepCount","steps","executedStepCount","step","decideRetry","options","reason"],"mappings":"AAMO,SAASA,YACdC,gBAAwB,EACxBC,KAA+B;IAE/B,MAAMC,oBAAoBD,MAAM,MAAM,CACpC,CAACE,OAASA,AAAgB,cAAhBA,KAAK,MAAM,EACrB,MAAM;IACR,OAAO;QACLH;QACAE;QACA,QACEF,AAAqB,MAArBA,mBACI,iBACAC,MAAM,IAAI,CAAC,CAACE,OAASA,AAAgB,aAAhBA,KAAK,MAAM,IAC9B,WACAD,oBAAoBF,mBAClB,YACA;IACZ;AACF;AAGO,SAASI,YAAYC,OAY3B;IACC,MAAMC,SAASD,QAAQ,mBAAmB,GACtC,yBACAA,QAAQ,cAAc,GACpB,oBACAA,QAAQ,aAAa,GACnB,mBACAA,QAAQ,gBAAgB,GACtB,sBACAA,QAAQ,UAAU,GAChB,gBACAA,QAAQ,WAAW,GACjB,gBACAA,QAAQ,UAAU,GAChB,SACAA,AAAmB,cAAnBA,QAAQ,MAAM,GACZ,WACAA,QAAQ,YAAY,IAAIA,QAAQ,UAAU,GACxC,qBACA;IACpB,OAAO;QACL,OAAOA,QAAQ,KAAK;QACpB,YAAYA,QAAQ,UAAU;QAC9B,cAAcA,QAAQ,YAAY;QAClC,UAAUC,AAAW,qBAAXA,SAA8B,UAAU;QAClDA;IACF;AACF"}