@windsland52/maa-log-tools 1.2.0 → 1.2.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.
package/README.md CHANGED
@@ -71,7 +71,7 @@ process-start-bounded interval contains the timestamp; otherwise it returns `nul
71
71
 
72
72
  ## Runtime inspection
73
73
 
74
- `buildRuntimeInspection(kernelOutput, frameworkExtraction, sourceSegments?)` emits
74
+ `buildRuntimeInspection(kernelOutput, frameworkExtraction, sourceSegments)` emits
75
75
  `mla-runtime-inspection/v1`. It nests task executions under their runtime session and keeps three
76
76
  different semantics separate:
77
77
 
@@ -92,9 +92,8 @@ ambiguous tasks remain in `unscopedTasks` and produce a warning.
92
92
  Use `--runtime-inspection` to emit this result directly from a file, zip, or directory. It is a
93
93
  separate output mode from `--preflight`; the two flags are mutually exclusive.
94
94
 
95
- When `sourceSegments` is provided, each evidence position is enriched with `source`, `path`, and
96
- `localLine` describing which original log file the evidence came from and its 1-based line within
97
- that file. Segments are built by the Node extraction helpers (`loadNodeLogDirectory`,
98
- `extractZipContentFromNodeFile`) when merging multiple log files; `parserInputLine` remains the
99
- line in the merged parser input, while `localLine` is the offset within the individual source
100
- file. Callers without segments leave these fields as `null`.
95
+ Each evidence position carries `source`, `path`, and `localLine` describing which original log
96
+ file the evidence came from and its 1-based line within that file. Segments are built by the Node
97
+ extraction helpers (`loadNodeLogDirectory`, `extractZipContentFromNodeFile`) when merging multiple
98
+ log files and are required by `buildRuntimeInspection`; `localLine` is the offset within the
99
+ individual source file, not the merged parser input line.
package/dist/cli.js CHANGED
@@ -108,7 +108,7 @@ export const main = async () => {
108
108
  ? extractFrameworkSessions(await loadFrameworkLogSources(resolvedPath))
109
109
  : EMPTY_FRAMEWORK_EXTRACTION;
110
110
  let result = null;
111
- let sourceSegments;
111
+ let sourceSegments = [];
112
112
  if (targetStat.isDirectory()) {
113
113
  const extracted = await loadNodeLogDirectory(resolvedPath);
114
114
  if (extracted) {
@@ -3,7 +3,6 @@ import type { FrameworkSession, FrameworkSessionExtraction } from './frameworkVe
3
3
  export declare const MLA_RUNTIME_INSPECTION_SCHEMA_VERSION = "mla-runtime-inspection/v1";
4
4
  export interface RuntimeEvidencePosition {
5
5
  timestamp: string | null;
6
- parserInputLine: number | null;
7
6
  source: string | null;
8
7
  path: string | null;
9
8
  localLine: number | null;
@@ -62,6 +61,15 @@ export interface RecognitionOccurrenceSample {
62
61
  end: RuntimeEvidencePosition;
63
62
  };
64
63
  }
64
+ export interface RepeatedNodeOccurrenceSample {
65
+ pattern: string[];
66
+ firstSeenAt: string;
67
+ lastSeenAt: string;
68
+ repeatCount: number;
69
+ durationMs: number;
70
+ termination: 'left_pattern' | 'task_ended' | 'still_repeating_at_log_end';
71
+ evidence: RuntimeEvidencePosition;
72
+ }
65
73
  export interface RecognitionActivitySignal extends RuntimeScope {
66
74
  signalId: string;
67
75
  kind: 'recognition_activity';
@@ -117,24 +125,9 @@ export interface RepeatedNodeSequenceSignal extends RuntimeScope {
117
125
  stillRepeatingAtLogEnd: number;
118
126
  };
119
127
  representatives: {
120
- first: {
121
- firstSeenAt: string;
122
- lastSeenAt: string;
123
- repeatCount: number;
124
- evidence: RuntimeEvidencePosition;
125
- };
126
- longest: {
127
- firstSeenAt: string;
128
- lastSeenAt: string;
129
- repeatCount: number;
130
- evidence: RuntimeEvidencePosition;
131
- };
132
- last: {
133
- firstSeenAt: string;
134
- lastSeenAt: string;
135
- repeatCount: number;
136
- evidence: RuntimeEvidencePosition;
137
- };
128
+ first: RepeatedNodeOccurrenceSample;
129
+ longest: RepeatedNodeOccurrenceSample;
130
+ last: RepeatedNodeOccurrenceSample;
138
131
  };
139
132
  detector: {
140
133
  name: 'repeated-completed-node-sequence';
@@ -221,5 +214,5 @@ export interface RuntimeInspection {
221
214
  signals: RuntimeSignal[];
222
215
  warnings: string[];
223
216
  }
224
- export declare const buildRuntimeInspection: (output: KernelOutput, framework: FrameworkSessionExtraction, sourceSegments?: readonly SourceSegment[]) => RuntimeInspection;
217
+ export declare const buildRuntimeInspection: (output: KernelOutput, framework: FrameworkSessionExtraction, sourceSegments: readonly SourceSegment[]) => RuntimeInspection;
225
218
  export {};
@@ -30,11 +30,11 @@ const evidence = (index, timestamp) => {
30
30
  if (timestamp) {
31
31
  const exactLine = index.exactLines.get(timestamp);
32
32
  if (exactLine != null)
33
- return { timestamp, parserInputLine: exactLine, source: null, path: null, localLine: null };
33
+ return { timestamp, mergedLine: exactLine };
34
34
  }
35
35
  const target = timestampMs(timestamp);
36
36
  if (target == null || index.ordered.length === 0) {
37
- return { timestamp: timestamp ?? null, parserInputLine: null, source: null, path: null, localLine: null };
37
+ return { timestamp: timestamp ?? null, mergedLine: null };
38
38
  }
39
39
  let low = 0;
40
40
  let high = index.ordered.length;
@@ -50,7 +50,7 @@ const evidence = (index, timestamp) => {
50
50
  const nearest = before == null
51
51
  ? after
52
52
  : after == null || target - before.timestamp <= after.timestamp - target ? before : after;
53
- return { timestamp: timestamp ?? null, parserInputLine: nearest?.line ?? null, source: null, path: null, localLine: null };
53
+ return { timestamp: timestamp ?? null, mergedLine: nearest?.line ?? null };
54
54
  };
55
55
  const recognitionItems = (items) => ((items ?? []).flatMap(item => [
56
56
  ...(item.type === 'recognition' || item.type === 'recognition_node' ? [item] : []),
@@ -161,25 +161,27 @@ const canonicalCycle = (pattern) => {
161
161
  rotations.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
162
162
  return rotations[0] ?? [...pattern];
163
163
  };
164
- const findSourceSegment = (segments, parserInputLine) => {
164
+ const findSourceSegment = (segments, mergedLine) => {
165
165
  for (const segment of segments) {
166
- if (parserInputLine >= segment.startLine && parserInputLine < segment.startLine + segment.lineCount) {
166
+ if (mergedLine >= segment.startLine && mergedLine < segment.startLine + segment.lineCount) {
167
167
  return segment;
168
168
  }
169
169
  }
170
170
  return null;
171
171
  };
172
172
  const enrichWithSource = (position, segments) => {
173
- if (!segments || position.parserInputLine == null)
174
- return position;
175
- const segment = findSourceSegment(segments, position.parserInputLine);
176
- if (!segment)
177
- return position;
173
+ if (position.mergedLine == null) {
174
+ return { timestamp: position.timestamp, source: null, path: null, localLine: null };
175
+ }
176
+ const segment = findSourceSegment(segments, position.mergedLine);
177
+ if (!segment) {
178
+ return { timestamp: position.timestamp, source: null, path: null, localLine: null };
179
+ }
178
180
  return {
179
- ...position,
181
+ timestamp: position.timestamp,
180
182
  source: segment.source,
181
183
  path: segment.path,
182
- localLine: position.parserInputLine - segment.startLine + 1,
184
+ localLine: position.mergedLine - segment.startLine + 1,
183
185
  };
184
186
  };
185
187
  const createEvidenceFn = (segments) => (index, timestamp) => enrichWithSource(evidence(index, timestamp), segments);
@@ -393,21 +395,30 @@ export const buildRuntimeInspection = (output, framework, sourceSegments) => {
393
395
  const last = completed[lastIndex];
394
396
  if (!first || !last)
395
397
  continue;
396
- const reachesEnd = lastIndex === completed.length - 1;
397
398
  const rawPattern = completed.slice(repeated.start, repeated.start + repeated.length).map(node => node.name);
398
399
  const pattern = repeated.length === 1 ? rawPattern : canonicalCycle(rawPattern);
399
400
  const kind = repeated.length === 1 ? 'repeated_node' : 'repeated_node_cycle';
400
401
  const key = JSON.stringify([kind, pattern]);
401
402
  const group = repetitionGroups.get(key) ?? [];
403
+ const timelineLastIndex = timeline.findIndex(item => item.nodeInfo === last);
404
+ const trailing = timelineLastIndex < 0 ? [] : timeline.slice(timelineLastIndex + 1);
405
+ const reachesCompletedEnd = lastIndex === completed.length - 1;
406
+ const continuesAtLogEnd = reachesCompletedEnd
407
+ && task.status === 'running'
408
+ && trailing.every((item, offset) => (item.nodeInfo.status === 'running'
409
+ && item.nodeInfo.name === rawPattern[offset % rawPattern.length]));
410
+ const taskEndedAtPattern = reachesCompletedEnd
411
+ && timelineLastIndex === timeline.length - 1
412
+ && task.status !== 'running';
402
413
  group.push({
403
414
  pattern,
404
415
  repeatCount: repeated.count,
405
416
  durationMs: elapsed(first.ts, last.end_ts ?? last.ts) ?? 0,
406
417
  firstSeenAt: first.ts,
407
418
  lastSeenAt: last.end_ts ?? last.ts,
408
- termination: reachesEnd
409
- ? task.status === 'running' ? 'still_repeating_at_log_end' : 'task_ended'
410
- : 'left_pattern',
419
+ termination: continuesAtLogEnd
420
+ ? 'still_repeating_at_log_end'
421
+ : taskEndedAtPattern ? 'task_ended' : 'left_pattern',
411
422
  evidence: evidenceAt(evidenceIndex, first.ts),
412
423
  });
413
424
  repetitionGroups.set(key, group);
@@ -427,8 +438,6 @@ export const buildRuntimeInspection = (output, framework, sourceSegments) => {
427
438
  const totalRepeatCount = group.reduce((sum, item) => sum + item.repeatCount, 0);
428
439
  const maximumRepeatCount = Math.max(...group.map(item => item.repeatCount));
429
440
  const repetitionReasons = [];
430
- if (terminations.leftPattern > 0)
431
- repetitionReasons.push('incomplete_repetition');
432
441
  if (terminations.stillRepeatingAtLogEnd > 0)
433
442
  repetitionReasons.push('still_repeating_at_log_end');
434
443
  if (maximumRepeatCount >= 10 || totalRepeatCount >= 20)
@@ -481,8 +490,9 @@ export const buildRuntimeInspection = (output, framework, sourceSegments) => {
481
490
  const visionImages = imageSets.flatMap(set => set.vision);
482
491
  const ownFailures = failures.filter(failure => directFailureIds.includes(failure.failureId));
483
492
  const ownSignals = signals.filter(signal => signalIds.includes(signal.signalId));
484
- const recognitionActivity = ownSignals
485
- .filter((signal) => (signal.kind === 'recognition_activity'))
493
+ const recognitionSignals = ownSignals
494
+ .filter((signal) => (signal.kind === 'recognition_activity'));
495
+ const recognitionActivity = [...recognitionSignals]
486
496
  .sort((left, right) => (right.unsuccessfulAttempts.maximum - left.unsuccessfulAttempts.maximum
487
497
  || right.occurrenceCount - left.occurrenceCount))
488
498
  .slice(0, 5)
@@ -517,7 +527,7 @@ export const buildRuntimeInspection = (output, framework, sourceSegments) => {
517
527
  const statuses = new Set(attempts.map(attempt => attempt.status));
518
528
  return statuses.has('failed') && statuses.has('success');
519
529
  }).length,
520
- recognitionActivityGroups: recognitionActivity.length,
530
+ recognitionActivityGroups: recognitionSignals.length,
521
531
  maximumRecognitionAttemptsPerNode: Math.max(0, ...attemptsByNode.map(attempts => attempts.length)),
522
532
  maximumUnsuccessfulRecognitionAttemptsPerNode: Math.max(0, ...attemptsByNode.map(attempts => attempts.filter(attempt => attempt.status === 'failed').length)),
523
533
  actionAttempts: timeline.filter(item => item.nodeInfo.action_details != null).length,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windsland52/maa-log-tools",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "bin": {
@@ -43,9 +43,9 @@
43
43
  "dependencies": {
44
44
  "fflate": "^0.8.2",
45
45
  "@windsland52/maa-log-adapter": "1.0.1",
46
- "@windsland52/maa-log-parser": "1.0.1",
47
46
  "@windsland52/maa-log-runtime": "1.0.1",
48
- "@windsland52/maa-log-kernel": "1.0.1"
47
+ "@windsland52/maa-log-kernel": "1.0.1",
48
+ "@windsland52/maa-log-parser": "1.0.1"
49
49
  },
50
50
  "engines": {
51
51
  "node": ">=24.0.0"