@windsland52/maa-log-tools 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -87,7 +87,9 @@ process-start-bounded interval contains the timestamp; otherwise it returns `nul
87
87
  `mla-runtime-inspection/v1`. It nests task executions under their runtime session and keeps three
88
88
  different semantics separate:
89
89
 
90
- - `failures`: direct `next_list_timeout` and `action_failed` facts.
90
+ - `failures`: directly observed `next_list_timeout` and `action_failed` facts, including failures
91
+ inside tasks launched by custom actions. Nested failures retain the nested task identity and
92
+ images while sharing the enclosing top-level `executionId`.
91
93
  - `outcomes`: failed or still-running pipeline nodes and tasks, with direct-failure references
92
94
  when the propagation can be linked deterministically.
93
95
  - `signals`: useful non-failure behavior such as recognition succeeding after earlier misses and
@@ -97,6 +99,10 @@ An unsuccessful recognition attempt is retry telemetry, not a failure. A next-li
97
99
  reported only when the node finishes without matching a candidate. Repeated recognition attempts
98
100
  inside one node are not treated as pipeline loops.
99
101
 
102
+ `RuntimeTaskExecution.directFailureIds` and its failure statistics remain limited to the
103
+ top-level task's own pipeline nodes. Nested task failures are available through `failures` and
104
+ `outcomes`, so a propagated parent action failure and its underlying nested failure stay distinct.
105
+
100
106
  Tasks are assigned to a process-start session by timestamp. A file segment without a
101
107
  `MAA Process Start` marker can also contain tasks when it is the only matching partial interval;
102
108
  ambiguous tasks remain in `unscopedTasks` and produce a warning.
@@ -64,6 +64,39 @@ const imagesFor = (node) => {
64
64
  vision: attempts.map(item => item.vision_image).filter((item) => Boolean(item)),
65
65
  };
66
66
  };
67
+ const ownedRecognitionItems = (items) => ((items ?? []).flatMap(item => {
68
+ if (item.type === 'task' || item.type === 'pipeline_node')
69
+ return [];
70
+ return [
71
+ ...(item.type === 'recognition' || item.type === 'recognition_node' ? [item] : []),
72
+ ...ownedRecognitionItems(item.children),
73
+ ];
74
+ }));
75
+ const imagesForFlowItem = (item) => {
76
+ const attempts = ownedRecognitionItems(item.children);
77
+ return {
78
+ error: [item.error_image, ...attempts.map(attempt => attempt.error_image)]
79
+ .filter((image) => Boolean(image)),
80
+ vision: [item.vision_image, ...attempts.map(attempt => attempt.vision_image)]
81
+ .filter((image) => Boolean(image)),
82
+ };
83
+ };
84
+ const hasFailedOwnedAction = (items) => ((items ?? []).some(item => {
85
+ if (item.type === 'task' || item.type === 'pipeline_node')
86
+ return false;
87
+ if ((item.type === 'action' || item.type === 'action_node')
88
+ && (item.status === 'failed' || item.action_details?.success === false))
89
+ return true;
90
+ return hasFailedOwnedAction(item.children);
91
+ }));
92
+ const hasFailedNestedTask = (items) => ((items ?? []).some(item => (item.type === 'task' ? item.status === 'failed' : hasFailedNestedTask(item.children))));
93
+ const nestedPipelineFailureKind = (item) => {
94
+ if (item.status !== 'failed')
95
+ return null;
96
+ if (item.action_details?.success === false || hasFailedOwnedAction(item.children))
97
+ return 'action_failed';
98
+ return hasFailedNestedTask(item.children) ? null : 'next_list_timeout';
99
+ };
67
100
  const scopeFor = (task, sessionId, executionId) => ({
68
101
  sessionId,
69
102
  executionId,
@@ -215,7 +248,99 @@ export const buildRuntimeInspection = (output, framework, sourceSegments) => {
215
248
  const recognitionOccurrences = [];
216
249
  const evidenceIndex = buildEvidenceIndex(task);
217
250
  const timeline = buildNodeExecutionTimeline(task.nodes, { rootTaskId: task.task_id });
251
+ const seenNestedTasks = new Set();
252
+ const seenNestedPipelines = new Set();
253
+ const inspectNestedTask = (taskItem) => {
254
+ if (seenNestedTasks.has(taskItem.id))
255
+ return;
256
+ seenNestedTasks.add(taskItem.id);
257
+ const taskId = taskItem.task_details?.task_id ?? taskItem.task_id;
258
+ if (taskId == null) {
259
+ inspectNestedTasks(taskItem.children);
260
+ return;
261
+ }
262
+ const nestedScope = {
263
+ sessionId,
264
+ executionId,
265
+ taskId,
266
+ taskName: taskItem.task_details?.entry ?? taskItem.name,
267
+ };
268
+ const nestedDirectFailureIds = [];
269
+ const inspectOwnedItems = (items) => {
270
+ for (const child of items ?? []) {
271
+ if (child.type === 'task') {
272
+ inspectNestedTask(child);
273
+ continue;
274
+ }
275
+ if (child.type !== 'pipeline_node') {
276
+ inspectOwnedItems(child.children);
277
+ continue;
278
+ }
279
+ if (seenNestedPipelines.has(child.id))
280
+ continue;
281
+ seenNestedPipelines.add(child.id);
282
+ inspectOwnedItems(child.children);
283
+ const failureKind = nestedPipelineFailureKind(child);
284
+ let nodeFailureId = null;
285
+ if (failureKind && child.node_id != null) {
286
+ nodeFailureId = `failure-${failures.length + 1}`;
287
+ const images = imagesForFlowItem(child);
288
+ failures.push({
289
+ ...nestedScope,
290
+ failureId: nodeFailureId,
291
+ kind: failureKind,
292
+ nodeId: child.node_id,
293
+ nodeName: child.name,
294
+ startedAt: child.ts,
295
+ endedAt: child.end_ts ?? null,
296
+ errorImages: [...new Set(images.error)],
297
+ visionImages: [...new Set(images.vision)],
298
+ evidence: evidenceAt(evidenceIndex, child.end_ts ?? child.ts),
299
+ });
300
+ nestedDirectFailureIds.push(nodeFailureId);
301
+ }
302
+ if (child.status !== 'success') {
303
+ const outcomeId = `outcome-${outcomes.length + 1}`;
304
+ outcomes.push({
305
+ ...nestedScope,
306
+ outcomeId,
307
+ kind: 'pipeline_node',
308
+ status: child.status,
309
+ nodeId: child.node_id ?? null,
310
+ nodeName: child.name,
311
+ directFailureIds: nodeFailureId ? [nodeFailureId] : [],
312
+ evidence: evidenceAt(evidenceIndex, child.end_ts ?? child.ts),
313
+ });
314
+ outcomeIds.push(outcomeId);
315
+ }
316
+ }
317
+ };
318
+ inspectOwnedItems(taskItem.children);
319
+ if (taskItem.status !== 'success') {
320
+ const outcomeId = `outcome-${outcomes.length + 1}`;
321
+ outcomes.push({
322
+ ...nestedScope,
323
+ outcomeId,
324
+ kind: 'task',
325
+ status: taskItem.status,
326
+ nodeId: null,
327
+ nodeName: null,
328
+ directFailureIds: nestedDirectFailureIds,
329
+ evidence: evidenceAt(evidenceIndex, taskItem.end_ts ?? taskItem.ts),
330
+ });
331
+ outcomeIds.push(outcomeId);
332
+ }
333
+ };
334
+ const inspectNestedTasks = (items) => {
335
+ for (const item of items ?? []) {
336
+ if (item.type === 'task')
337
+ inspectNestedTask(item);
338
+ else
339
+ inspectNestedTasks(item.children);
340
+ }
341
+ };
218
342
  for (const item of timeline) {
343
+ inspectNestedTasks(item.nodeInfo.node_flow);
219
344
  const failureKind = item.navStatus === 'action-failed'
220
345
  ? 'action_failed'
221
346
  : item.navStatus === 'timeout' && item.nodeInfo.next_list.length > 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windsland52/maa-log-tools",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "bin": {
@@ -42,9 +42,9 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "fflate": "^0.8.2",
45
- "@windsland52/maa-log-adapter": "1.1.0",
46
45
  "@windsland52/maa-log-parser": "1.1.0",
47
46
  "@windsland52/maa-log-kernel": "1.0.2",
47
+ "@windsland52/maa-log-adapter": "1.1.0",
48
48
  "@windsland52/maa-log-runtime": "1.1.0"
49
49
  },
50
50
  "engines": {