Skip to content

Commit 241fad4

Browse files
committed
fix: 修复跨任务 wait_freezes 被错误打平的问题
- 当嵌套子任务仍处于活动状态时,让 wait_freezes 保持挂在当前同源的 - foreign pipeline/action 上下文下,而不是回退并打平到外层任务 action。 - 补充 reducer 和 parser 的回归测试,覆盖 SOSSelectNode/Click 嵌套流程场景。
1 parent fd7e029 commit 241fad4

3 files changed

Lines changed: 393 additions & 1 deletion

File tree

packages/maa-log-parser/src/__tests__/parser.subTaskScope.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,114 @@ describe('LogParser sub task scoped node aggregation', () => {
269269
expect(nestedSubPipeline[0].item.status).toBe('success')
270270
})
271271

272+
it('nests outer-task wait_freezes into the active foreign pipeline flow', async () => {
273+
const lines = [
274+
makeEventLine(201, 'Tasker.Task.Starting', { task_id: 1, entry: 'MainTask', hash: 'h-main-3', uuid: 'u-main-3' }),
275+
makeEventLine(202, 'Node.PipelineNode.Starting', { task_id: 1, node_id: 101, name: 'MainNode' }),
276+
makeEventLine(203, 'Node.Action.Starting', { task_id: 1, action_id: 1001, name: 'SOSSelectNode' }),
277+
278+
makeEventLine(204, 'Node.PipelineNode.Starting', { task_id: 2, node_id: 201, name: 'Click' }),
279+
makeEventLine(205, 'Node.NextList.Starting', {
280+
task_id: 2,
281+
name: 'Click',
282+
list: [{ name: 'Click', anchor: false, jump_back: false }],
283+
}),
284+
makeEventLine(206, 'Node.Recognition.Starting', { task_id: 2, reco_id: 3001, name: 'Click' }),
285+
makeEventLine(207, 'Node.Recognition.Succeeded', {
286+
task_id: 2,
287+
reco_id: 3001,
288+
name: 'Click',
289+
reco_details: {
290+
reco_id: 3001,
291+
algorithm: 'DirectHit',
292+
box: [0, 0, 1280, 720],
293+
detail: null,
294+
name: 'Click',
295+
},
296+
}),
297+
makeEventLine(208, 'Node.NextList.Succeeded', {
298+
task_id: 2,
299+
name: 'Click',
300+
list: [{ name: 'Click', anchor: false, jump_back: false }],
301+
}),
302+
makeEventLine(209, 'Node.Action.Starting', { task_id: 2, action_id: 3002, name: 'Click' }),
303+
makeEventLine(210, 'Node.Action.Succeeded', {
304+
task_id: 2,
305+
action_id: 3002,
306+
name: 'Click',
307+
action_details: {
308+
action_id: 3002,
309+
action: 'Click',
310+
box: [0, 0, 1280, 720],
311+
detail: {},
312+
name: 'Click',
313+
success: true,
314+
},
315+
}),
316+
makeEventLine(211, 'Node.WaitFreezes.Starting', {
317+
task_id: 1,
318+
wf_id: 4001,
319+
phase: 'post',
320+
name: 'Click',
321+
}),
322+
makeEventLine(212, 'Node.WaitFreezes.Succeeded', {
323+
task_id: 1,
324+
wf_id: 4001,
325+
phase: 'post',
326+
name: 'Click',
327+
elapsed: 33,
328+
}),
329+
makeEventLine(213, 'Node.PipelineNode.Succeeded', {
330+
task_id: 2,
331+
node_id: 201,
332+
name: 'Click',
333+
}),
334+
335+
makeEventLine(214, 'Node.Action.Succeeded', {
336+
task_id: 1,
337+
action_id: 1001,
338+
name: 'SOSSelectNode',
339+
}),
340+
makeEventLine(215, 'Node.PipelineNode.Succeeded', { task_id: 1, node_id: 101, name: 'MainNode' }),
341+
makeEventLine(216, 'Tasker.Task.Succeeded', { task_id: 1, entry: 'MainTask', hash: 'h-main-3', uuid: 'u-main-3' }),
342+
]
343+
344+
const parser = new LogParser()
345+
await parser.parseFile(lines.join('\n'))
346+
const tasks = parser.getTasksSnapshot()
347+
const mainTask = tasks.find(item => item.task_id === 1)
348+
349+
expect(mainTask).toBeTruthy()
350+
const mainNode = mainTask!.nodes[0]
351+
352+
const actionRoot = collectFlowItems(
353+
mainNode.node_flow,
354+
(item) => item.type === 'action' && item.action_id === 1001,
355+
)[0]?.item
356+
expect(actionRoot).toBeTruthy()
357+
358+
const nestedTask = collectFlowItems(
359+
actionRoot?.children,
360+
(item) => item.type === 'task' && item.task_id === 2,
361+
)[0]?.item
362+
expect(nestedTask).toBeTruthy()
363+
expect(nestedTask?.children?.map((item) => item.type)).toEqual(['pipeline_node'])
364+
365+
const nestedPipeline = nestedTask?.children?.[0]
366+
expect(nestedPipeline?.type).toBe('pipeline_node')
367+
expect(nestedPipeline?.children?.map((item) => item.type)).toEqual([
368+
'recognition',
369+
'action',
370+
'wait_freezes',
371+
])
372+
373+
const nestedWaitFreezes = nestedPipeline?.children?.[2]
374+
expect(nestedWaitFreezes?.type).toBe('wait_freezes')
375+
expect(nestedWaitFreezes?.task_id).toBe(1)
376+
expect(nestedWaitFreezes?.node_id).toBe(201)
377+
expect(nestedWaitFreezes?.wait_freezes_details?.wf_id).toBe(4001)
378+
})
379+
272380
it('tolerates malformed NextList payloads', async () => {
273381
const lines = [
274382
makeEventLine(151, 'Tasker.Task.Starting', { task_id: 31, entry: 'MainTask', hash: 'h-main-4', uuid: 'u-main-4' }),

packages/maa-log-parser/src/__tests__/traceReducer.test.ts

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,241 @@ describe('TraceReducer', () => {
407407
expect(pipeline.children.map((item) => item.kind)).toEqual(['resource_loading'])
408408
})
409409

410+
it('parents outer-task wait_freezes under the active foreign pipeline scope from the same source', () => {
411+
const events: ProtocolEvent[] = [
412+
makeEvent({
413+
kind: 'task',
414+
seq: 1,
415+
ts: '2026-04-08 00:00:01.000',
416+
tsMs: 1,
417+
processId: 'Px1',
418+
threadId: 'Tx1',
419+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 1 },
420+
rawMessage: 'Tasker.Task.Starting',
421+
phase: 'starting',
422+
rawDetails: { task_id: 1, entry: 'Main' },
423+
taskId: 1,
424+
entry: 'Main',
425+
}),
426+
makeEvent({
427+
kind: 'pipeline_node',
428+
seq: 2,
429+
ts: '2026-04-08 00:00:02.000',
430+
tsMs: 2,
431+
processId: 'Px1',
432+
threadId: 'Tx1',
433+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 2 },
434+
rawMessage: 'Node.PipelineNode.Starting',
435+
phase: 'starting',
436+
rawDetails: { task_id: 1, node_id: 101, name: 'MainNode' },
437+
taskId: 1,
438+
nodeId: 101,
439+
name: 'MainNode',
440+
}),
441+
makeEvent({
442+
kind: 'action',
443+
seq: 3,
444+
ts: '2026-04-08 00:00:03.000',
445+
tsMs: 3,
446+
processId: 'Px1',
447+
threadId: 'Tx1',
448+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 3 },
449+
rawMessage: 'Node.Action.Starting',
450+
phase: 'starting',
451+
rawDetails: { task_id: 1, action_id: 601, name: 'OuterAction' },
452+
taskId: 1,
453+
actionId: 601,
454+
name: 'OuterAction',
455+
}),
456+
makeEvent({
457+
kind: 'pipeline_node',
458+
seq: 4,
459+
ts: '2026-04-08 00:00:04.000',
460+
tsMs: 4,
461+
processId: 'Px1',
462+
threadId: 'Tx1',
463+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 4 },
464+
rawMessage: 'Node.PipelineNode.Starting',
465+
phase: 'starting',
466+
rawDetails: { task_id: 2, node_id: 201, name: 'NestedClick' },
467+
taskId: 2,
468+
nodeId: 201,
469+
name: 'NestedClick',
470+
}),
471+
makeEvent({
472+
kind: 'recognition',
473+
seq: 5,
474+
ts: '2026-04-08 00:00:05.000',
475+
tsMs: 5,
476+
processId: 'Px1',
477+
threadId: 'Tx1',
478+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 5 },
479+
rawMessage: 'Node.Recognition.Starting',
480+
phase: 'starting',
481+
rawDetails: { task_id: 2, reco_id: 701, name: 'NestedClick' },
482+
taskId: 2,
483+
recoId: 701,
484+
name: 'NestedClick',
485+
}),
486+
makeEvent({
487+
kind: 'recognition',
488+
seq: 6,
489+
ts: '2026-04-08 00:00:06.000',
490+
tsMs: 6,
491+
processId: 'Px1',
492+
threadId: 'Tx1',
493+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 6 },
494+
rawMessage: 'Node.Recognition.Succeeded',
495+
phase: 'succeeded',
496+
rawDetails: { task_id: 2, reco_id: 701, name: 'NestedClick' },
497+
taskId: 2,
498+
recoId: 701,
499+
name: 'NestedClick',
500+
}),
501+
makeEvent({
502+
kind: 'action',
503+
seq: 7,
504+
ts: '2026-04-08 00:00:07.000',
505+
tsMs: 7,
506+
processId: 'Px1',
507+
threadId: 'Tx1',
508+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 7 },
509+
rawMessage: 'Node.Action.Starting',
510+
phase: 'starting',
511+
rawDetails: { task_id: 2, action_id: 702, name: 'NestedClick' },
512+
taskId: 2,
513+
actionId: 702,
514+
name: 'NestedClick',
515+
}),
516+
makeEvent({
517+
kind: 'action',
518+
seq: 8,
519+
ts: '2026-04-08 00:00:08.000',
520+
tsMs: 8,
521+
processId: 'Px1',
522+
threadId: 'Tx1',
523+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 8 },
524+
rawMessage: 'Node.Action.Succeeded',
525+
phase: 'succeeded',
526+
rawDetails: { task_id: 2, action_id: 702, name: 'NestedClick' },
527+
taskId: 2,
528+
actionId: 702,
529+
name: 'NestedClick',
530+
}),
531+
makeEvent({
532+
kind: 'wait_freezes',
533+
seq: 9,
534+
ts: '2026-04-08 00:00:09.000',
535+
tsMs: 9,
536+
processId: 'Px1',
537+
threadId: 'Tx1',
538+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 9 },
539+
rawMessage: 'Node.WaitFreezes.Starting',
540+
phase: 'starting',
541+
rawDetails: { task_id: 1, wf_id: 901, phase: 'post', name: 'NestedClick' },
542+
taskId: 1,
543+
wfId: 901,
544+
waitPhase: 'post',
545+
name: 'NestedClick',
546+
}),
547+
makeEvent({
548+
kind: 'wait_freezes',
549+
seq: 10,
550+
ts: '2026-04-08 00:00:10.000',
551+
tsMs: 10,
552+
processId: 'Px1',
553+
threadId: 'Tx1',
554+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 10 },
555+
rawMessage: 'Node.WaitFreezes.Succeeded',
556+
phase: 'succeeded',
557+
rawDetails: { task_id: 1, wf_id: 901, phase: 'post', name: 'NestedClick', elapsed: 15 },
558+
taskId: 1,
559+
wfId: 901,
560+
waitPhase: 'post',
561+
elapsed: 15,
562+
name: 'NestedClick',
563+
}),
564+
makeEvent({
565+
kind: 'pipeline_node',
566+
seq: 11,
567+
ts: '2026-04-08 00:00:11.000',
568+
tsMs: 11,
569+
processId: 'Px1',
570+
threadId: 'Tx1',
571+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 11 },
572+
rawMessage: 'Node.PipelineNode.Succeeded',
573+
phase: 'succeeded',
574+
rawDetails: { task_id: 2, node_id: 201, name: 'NestedClick' },
575+
taskId: 2,
576+
nodeId: 201,
577+
name: 'NestedClick',
578+
}),
579+
makeEvent({
580+
kind: 'action',
581+
seq: 12,
582+
ts: '2026-04-08 00:00:12.000',
583+
tsMs: 12,
584+
processId: 'Px1',
585+
threadId: 'Tx1',
586+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 12 },
587+
rawMessage: 'Node.Action.Succeeded',
588+
phase: 'succeeded',
589+
rawDetails: { task_id: 1, action_id: 601, name: 'OuterAction' },
590+
taskId: 1,
591+
actionId: 601,
592+
name: 'OuterAction',
593+
}),
594+
makeEvent({
595+
kind: 'pipeline_node',
596+
seq: 13,
597+
ts: '2026-04-08 00:00:13.000',
598+
tsMs: 13,
599+
processId: 'Px1',
600+
threadId: 'Tx1',
601+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 13 },
602+
rawMessage: 'Node.PipelineNode.Succeeded',
603+
phase: 'succeeded',
604+
rawDetails: { task_id: 1, node_id: 101, name: 'MainNode' },
605+
taskId: 1,
606+
nodeId: 101,
607+
name: 'MainNode',
608+
}),
609+
makeEvent({
610+
kind: 'task',
611+
seq: 14,
612+
ts: '2026-04-08 00:00:14.000',
613+
tsMs: 14,
614+
processId: 'Px1',
615+
threadId: 'Tx1',
616+
source: { sourceKey: 'maa.log', inputIndex: 0, line: 14 },
617+
rawMessage: 'Tasker.Task.Succeeded',
618+
phase: 'succeeded',
619+
rawDetails: { task_id: 1, entry: 'Main' },
620+
taskId: 1,
621+
entry: 'Main',
622+
}),
623+
]
624+
625+
const trace = buildTraceTree(events)
626+
627+
const task = trace.children[0]
628+
const pipeline = task?.children[0]
629+
const action = pipeline?.children[0]
630+
631+
expect(action?.kind).toBe('action')
632+
expect(action?.children).toHaveLength(1)
633+
634+
const nestedPipeline = action?.children[0]
635+
expect(nestedPipeline?.kind).toBe('pipeline_node')
636+
expect(nestedPipeline?.taskId).toBe(2)
637+
expect(nestedPipeline?.children.map((item) => item.kind)).toEqual([
638+
'recognition',
639+
'action',
640+
'wait_freezes',
641+
])
642+
expect(nestedPipeline?.children[2]?.taskId).toBe(1)
643+
})
644+
410645
it('parents taskless foreign scopes under the active business scope from the same source', () => {
411646
const events: ProtocolEvent[] = [
412647
makeEvent({

0 commit comments

Comments
 (0)