Skip to content
Merged
1,088 changes: 1,088 additions & 0 deletions docs/LOG_PARSER_ARCHITECTURE.md

Large diffs are not rendered by default.

60 changes: 59 additions & 1 deletion docs/TOOL_ONLY_ORCHESTRATOR_PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
2. 可追溯:输出可回溯到 task/node/line 等证据源。
3. 可演进:通过 `api_version` 做版本管理。
4. 低耦合:协议不依赖 UI 状态与会话 API Key。
5. 多输入定位:涉及行号证据时,必须能通过 `source_key + 1-based line` 回溯原文。

---

Expand Down Expand Up @@ -172,19 +173,38 @@ interface GetNodeTimelineArgs {
session_id: string
task_id: number
node_id: number
scope_id?: string
occurrence_index?: number
limit?: number
}
```

定位规则:

1. `task_id + node_id` 表示逻辑节点,不保证唯一对应某一次执行。
2. `scope_id` 优先级最高,用于唯一定位某一次节点执行实例。
3. `occurrence_index` 表示该 `task_id + node_id` 在时序上的第几次出现,使用 1-based。
4. 当 `scope_id` 与 `occurrence_index` 都缺失时,允许返回该节点的所有匹配实例,但每条结果必须带上实例标识。

补充约束:

1. `scope_id` 必须是同输入下可复现的确定性标识,不能使用随机值。
2. 建议格式为 `<scope_kind>:<task_id_or_0>:<local_id_or_0>:seq<start_seq>`。
3. `occurrence_index` 只按 `(task_id, node_id)` 对 `PipelineNode` 执行实例做计数。
4. 同一轮节点执行内的 `NextList / Recognition / Action / WaitFreezes / ActionNode / RecognitionNode` 共享该次 `occurrence_index`。

返回结构(示意):

```ts
interface GetNodeTimelineResult {
timeline: Array<{
scope_id: string
occurrence_index: number
ts: string
event: string
node_id: number
name: string
source_key: string | null
line: number | null
}>
evidences: Evidence[]
Expand All @@ -204,15 +224,27 @@ interface GetNextListHistoryArgs {
session_id: string
task_id: number
node_id: number
scope_id?: string
occurrence_index?: number
limit?: number
}
```

定位规则沿用 `get_node_timeline`。

查询语义:

1. 若仅提供 `task_id + node_id`,允许返回该逻辑节点的全部实例 history。
2. 若提供 `scope_id` 或 `occurrence_index`,则只返回对应单次节点执行实例的数据。

返回结构(示意):

```ts
interface GetNextListHistoryResult {
history: Array<{
scope_id: string
occurrence_index: number
source_key: string | null
line: number | null
candidates: Array<{
name: string
Expand All @@ -238,16 +270,29 @@ interface GetParentChainArgs {
session_id: string
task_id: number
node_id: number
scope_id?: string
occurrence_index?: number
}
```

定位规则沿用 `get_node_timeline`。

查询语义:

1. `get_parent_chain` 面向单个节点执行实例。
2. 若 `task_id + node_id` 命中多个实例,且请求未提供 `scope_id` 或 `occurrence_index`,则应返回歧义错误,而不是隐式选择其中一个。

返回结构(示意):

```ts
interface GetParentChainResult {
chain: Array<{
node_id: number
scope_id: string
scope_kind: 'task' | 'pipeline_node' | 'recognition_node' | 'action_node' | 'next_list' | 'recognition' | 'action' | 'wait_freezes'
task_id?: number
node_id?: number
name: string
occurrence_index?: number
relation: 'self' | 'parent' | 'ancestor'
}>
evidences: Evidence[]
Expand All @@ -266,6 +311,7 @@ interface GetParentChainResult {
interface GetRawLinesArgs {
session_id: string
task_id: number
source_key?: string
keywords?: string[]
line_start?: number
line_end?: number
Expand All @@ -278,6 +324,7 @@ interface GetRawLinesArgs {
```ts
interface GetRawLinesResult {
lines: Array<{
source_key: string
line: number
text: string
}>
Expand All @@ -295,8 +342,11 @@ interface Evidence {
source_tool: string
source_range: {
session_id: string
source_key?: string
task_id?: number
node_id?: number
scope_id?: string
occurrence_index?: number
line_start?: number
line_end?: number
}
Expand All @@ -309,6 +359,9 @@ interface Evidence {

1. 工具层事实证据默认 `confidence=1.0`。
2. `source_range` 必须可用于回溯原始来源。
3. 当 `line_start/line_end` 有值时,多输入场景下应同时提供 `source_key`。
4. 当证据针对某一次具体节点执行时,应优先提供 `scope_id`,必要时补充 `occurrence_index`。
5. `task_id + node_id` 在证据模型中默认表示逻辑节点;若需唯一定位单次执行,应使用 `scope_id`。

---

Expand All @@ -321,6 +374,8 @@ interface Evidence {
| `SESSION_NOT_FOUND` | 会话不存在或已过期 | false | 重新调用 `parse_log_bundle` |
| `TASK_NOT_FOUND` | `task_id` 不存在 | false | 校验任务选择 |
| `NODE_NOT_FOUND` | `node_id` 不存在 | false | 校验节点选择 |
| `SCOPE_NOT_FOUND` | `scope_id` 不存在或不属于该 `task_id/node_id` | false | 校验实例定位参数 |
| `AMBIGUOUS_SCOPE_SELECTOR` | 仅凭当前定位条件命中多个节点实例 | false | 补充 `scope_id` 或 `occurrence_index` |
| `DATA_NOT_READY` | 数据尚未就绪 | true | 指数退避重试 |
| `INTERNAL_ERROR` | 内部处理错误 | true | 有限重试并记录日志 |

Expand All @@ -333,6 +388,9 @@ interface Evidence {
3. 行号统一 1-based。
4. 无数据列表返回空数组,不返回 `null`。
5. 新增字段只能追加,不得改变旧字段语义。
6. `scope_id` 只要求在同一 `session_id` 内稳定唯一,不要求跨会话复用。
7. `task_id + node_id` 默认表示逻辑节点;需要精确到单次执行时,调用方应传 `scope_id` 或 `occurrence_index`。
8. 列表型接口可接受逻辑节点宽查询;单实例接口不得在多命中时隐式选取结果。

---

Expand Down
53 changes: 53 additions & 0 deletions packages/maa-log-parser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Repository-specific parser package for MaaLogAnalyzer.
- Keep parser entry stable for other internal packages
- Provide raw value transformer hooks used by parser runtime

Architecture design:

- `../../docs/LOG_PARSER_ARCHITECTURE.md`

## Exports

- `LogParser`
Expand All @@ -27,6 +31,55 @@ Subpath export:
- `parseMaaMessageMeta`
- `resolveTaskLifecyclePhase`
- `buildEventDedupSignature`
- `@windsland52/maa-log-parser/protocol-types`
- `SourceRef`
- `ProtocolEvent`
- protocol event kind/type exports
- `@windsland52/maa-log-parser/protocol-event-factory`
- `createSourceRef`
- `createProtocolEvent`
- `@windsland52/maa-log-parser/trace-scope-types`
- `ScopeKind`
- `ScopeNode`
- `@windsland52/maa-log-parser/trace-scope-id`
- `buildScopeId`
- `createScopeId`
- `resolveScopeLocalId`
- `@windsland52/maa-log-parser/trace-reducer`
- `buildTraceTree`
- `TraceScopePayload`
- `@windsland52/maa-log-parser/query-types`
- `NodeExecutionRef`
- `QueryResult`
- `@windsland52/maa-log-parser/query-locator`
- `ScopeLocator`
- `NodeExecutionLocator`
- `UniqueScopeLocator`
- `buildTaskNodeKey`
- `@windsland52/maa-log-parser/trace-index`
- `TraceIndex`
- `createEmptyTraceIndex`
- `buildTraceIndex`
- `@windsland52/maa-log-parser/query-helpers`
- `findScopeById`
- `findScopesByLocator`
- `findNodeExecution`
- `getParentChain`
- `getNodeTimeline`
- `getNextListHistory`
- `createQueryHelpers`
- `@windsland52/maa-log-parser/raw-line-store`
- `createRawLineStore`
- `getRawLine`
- `getRawLinesByRefs`
- `@windsland52/maa-log-parser/service-session-store`
- `AnalyzerSessionStore`
- `createAnalyzerSessionStore`
- `@windsland52/maa-log-parser/service-evidence-builders`
- `buildEvidence`
- `buildLineEvidence`
- `@windsland52/maa-log-parser/service-tool-handlers`
- `createAnalyzerToolHandlers`
- `@windsland52/maa-log-parser/types`
- Type re-exports for parser-related data structures
- `@windsland52/maa-log-parser/log-event-decoders`
Expand Down
56 changes: 56 additions & 0 deletions packages/maa-log-parser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,62 @@
"import": "./dist/event/meta.js",
"types": "./dist/event/meta.d.ts"
},
"./protocol-types": {
"import": "./dist/protocol/types.js",
"types": "./dist/protocol/types.d.ts"
},
"./protocol-event-factory": {
"import": "./dist/protocol/eventFactory.js",
"types": "./dist/protocol/eventFactory.d.ts"
},
"./trace-scope-types": {
"import": "./dist/trace/scopeTypes.js",
"types": "./dist/trace/scopeTypes.d.ts"
},
"./trace-scope-id": {
"import": "./dist/trace/scopeId.js",
"types": "./dist/trace/scopeId.d.ts"
},
"./trace-reducer": {
"import": "./dist/trace/reducer.js",
"types": "./dist/trace/reducer.d.ts"
},
"./query-types": {
"import": "./dist/query/queryTypes.js",
"types": "./dist/query/queryTypes.d.ts"
},
"./query-locator": {
"import": "./dist/query/locator.js",
"types": "./dist/query/locator.d.ts"
},
"./trace-index": {
"import": "./dist/query/traceIndex.js",
"types": "./dist/query/traceIndex.d.ts"
},
"./query-helpers": {
"import": "./dist/query/helpers.js",
"types": "./dist/query/helpers.d.ts"
},
"./raw-line-store": {
"import": "./dist/raw/store.js",
"types": "./dist/raw/store.d.ts"
},
"./service-types": {
"import": "./dist/service/types.js",
"types": "./dist/service/types.d.ts"
},
"./service-session-store": {
"import": "./dist/service/sessionStore.js",
"types": "./dist/service/sessionStore.d.ts"
},
"./service-evidence-builders": {
"import": "./dist/service/evidenceBuilders.js",
"types": "./dist/service/evidenceBuilders.d.ts"
},
"./service-tool-handlers": {
"import": "./dist/service/toolHandlers.js",
"types": "./dist/service/toolHandlers.d.ts"
},
"./types": {
"import": "./dist/shared/types.js",
"types": "./dist/shared/types.d.ts"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[2026-03-09 19:46:17.712][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Tasker.Task.Starting] [details={"task_id":200000001,"entry":"CritterCrash","hash":"h-main","uuid":"u-main"}]
[2026-03-09 19:46:17.712][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.PipelineNode.Starting] [details={"task_id":200000001,"node_id":300001880,"name":"CCFlagInCombatMain"}]
[2026-03-09 19:46:17.712][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.NextList.Starting] [details={"task_id":200000001,"name":"CCFlagInCombatMain","list":[{"name":"CritterCrash","anchor":false,"jump_back":false},{"name":"CCBuyCard","anchor":false,"jump_back":true}]}]
[2026-03-09 19:46:17.807][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.Recognition.Starting] [details={"focus":null,"name":"CCBuyCard","reco_id":400003684,"task_id":200000001}]
[2026-03-09 19:46:17.822][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.RecognitionNode.Starting] [details={"focus":null,"name":"CCBuyCardAwardEmptyRec","node_id":300001881,"task_id":200001552}]
[2026-03-09 19:46:17.824][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.Recognition.Starting] [details={"focus":null,"name":"CCBuyCardAwardEmptyRec","reco_id":400003685,"task_id":200001552}]
[2026-03-09 19:46:17.858][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.RecognitionNode.Starting] [details={"focus":null,"name":"CCFlagInCombatMain","node_id":300001883,"task_id":200001554}]
[2026-03-09 19:46:17.859][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.Recognition.Starting] [details={"focus":null,"name":"CCFlagInCombatMain","reco_id":400003687,"task_id":200001554}]
[2026-03-09 19:46:17.864][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.Recognition.Succeeded] [details={"focus":null,"name":"CCFlagInCombatMain","reco_details":{"algorithm":"TemplateMatch","box":[232,527,158,37],"detail":{"all":[{"box":[232,527,158,37],"score":0.99801}],"best":{"box":[232,527,158,37],"score":0.99801},"filtered":[{"box":[232,527,158,37],"score":0.99801}]},"name":"CCFlagInCombatMain","reco_id":400003687},"reco_id":400003687,"task_id":200001554}]
[2026-03-09 19:46:17.866][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.RecognitionNode.Succeeded] [details={"focus":null,"name":"CCFlagInCombatMain","node_id":300001883,"node_details":{"action_id":0,"completed":true,"name":"CCFlagInCombatMain","node_id":300001883,"reco_id":400003687},"reco_details":{"algorithm":"TemplateMatch","box":[232,527,158,37],"detail":{"all":[{"box":[232,527,158,37],"score":0.99801}],"best":{"box":[232,527,158,37],"score":0.99801},"filtered":[{"box":[232,527,158,37],"score":0.99801}]},"name":"CCFlagInCombatMain","reco_id":400003687},"task_id":200001554}]
[2026-03-09 19:46:17.868][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.Recognition.Failed] [details={"focus":null,"name":"CCBuyCardAwardEmptyRec","reco_details":{"algorithm":"Custom","box":null,"detail":{"all":[{"box":[0,0,0,0],"detail":""}],"best":null,"filtered":[]},"name":"CCBuyCardAwardEmptyRec","reco_id":400003685},"reco_id":400003685,"task_id":200001552}]
[2026-03-09 19:46:17.870][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.RecognitionNode.Failed] [details={"focus":null,"name":"CCBuyCardAwardEmptyRec","node_id":300001881,"node_details":{"action_id":0,"completed":false,"name":"CCBuyCardAwardEmptyRec","node_id":300001881,"reco_id":400003685},"reco_details":{"algorithm":"Custom","box":null,"detail":{"all":[{"box":[0,0,0,0],"detail":""}],"best":null,"filtered":[]},"name":"CCBuyCardAwardEmptyRec","reco_id":400003685},"task_id":200001552}]
[2026-03-09 19:46:18.163][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.Recognition.Succeeded] [details={"focus":null,"name":"CCBuyCard","reco_details":{"algorithm":"Custom","box":[701,594,96,60],"detail":{"all":[{"box":[701,594,96,60],"detail":{"action":1,"name":"Deer","type":0}}],"best":{"box":[701,594,96,60],"detail":{"action":1,"name":"Deer","type":0}},"filtered":[{"box":[701,594,96,60],"detail":{"action":1,"name":"Deer","type":0}}]},"name":"CCBuyCard","reco_id":400003684},"reco_id":400003684,"task_id":200000001}]
[2026-03-09 19:46:18.164][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.NextList.Succeeded] [details={"task_id":200000001,"name":"CCFlagInCombatMain","list":[{"name":"CritterCrash","anchor":false,"jump_back":false},{"name":"CCBuyCard","anchor":false,"jump_back":true}]}]
[2026-03-09 19:46:18.563][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.Action.Starting] [details={"action_id":500000368,"focus":null,"name":"CCBuyCard","task_id":200000001}]
[2026-03-09 19:46:18.700][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Tasker.Task.Starting] [details={"task_id":200001564,"entry":"CCUpdate","hash":"h-update","uuid":"u-update"}]
[2026-03-09 19:46:18.701][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.PipelineNode.Starting] [details={"task_id":200001564,"node_id":300001903,"name":"CCUpdate"}]
[2026-03-09 19:46:35.398][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.PipelineNode.Failed] [details={"task_id":200001564,"node_id":300001903,"name":"CCUpdate"}]
[2026-03-09 19:46:35.399][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Tasker.Task.Failed] [details={"task_id":200001564,"entry":"CCUpdate","hash":"h-update","uuid":"u-update"}]
[2026-03-09 19:46:35.952][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.Action.Succeeded] [details={"action_details":{"action":"Custom","action_id":500000368,"box":[701,594,96,60],"detail":{},"name":"CCBuyCard","success":true,"ts":"2026-03-09 19:46:18.563","end_ts":"2026-03-09 19:46:35.952"},"action_id":500000368,"focus":null,"name":"CCBuyCard","task_id":200000001}]
[2026-03-09 19:46:35.953][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Node.PipelineNode.Failed] [details={"action_details":{"action":"Custom","action_id":500000368,"box":[701,594,96,60],"detail":{},"name":"CCBuyCard","success":true,"ts":"2026-03-09 19:46:18.563","end_ts":"2026-03-09 19:46:35.952"},"focus":null,"name":"CCFlagInCombatMain","node_details":{"action_id":500000368,"completed":true,"name":"CCBuyCard","node_id":300001880,"reco_id":400003684},"node_id":300001880,"reco_details":{"algorithm":"Custom","box":[701,594,96,60],"detail":{"all":[{"box":[701,594,96,60],"detail":{"action":1,"name":"Deer","type":0}}],"best":{"box":[701,594,96,60],"detail":{"action":1,"name":"Deer","type":0}},"filtered":[{"box":[701,594,96,60],"detail":{"action":1,"name":"Deer","type":0}}]},"name":"CCBuyCard","reco_id":400003684},"task_id":200000001}]
[2026-03-09 19:46:35.954][INF][Px15124][Tx21858][test] !!!OnEventNotify!!! [handle=1] [msg=Tasker.Task.Failed] [details={"task_id":200000001,"entry":"CritterCrash","hash":"h-main","uuid":"u-main"}]
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ const collectActionItems = (items: UnifiedFlowItem[] | undefined): UnifiedFlowIt
return result
}

describe('LogParser action fallback', () => {
it('creates fallback action flow item from PipelineNode.Failed action_details', async () => {
describe('LogParser strict action projection', () => {
it('does not create fallback action flow item from PipelineNode.Failed action_details', async () => {
const lines = [
makeEventLine(1, 'Tasker.Task.Starting', { task_id: 81, entry: 'MainTask', hash: 'h-81', uuid: 'u-81' }),
makeEventLine(2, 'Node.PipelineNode.Starting', { task_id: 81, node_id: 8101, name: 'MainNode' }),
Expand Down Expand Up @@ -66,12 +66,7 @@ describe('LogParser action fallback', () => {

const node = task!.nodes[0]
const actionItems = collectActionItems(node.node_flow)

expect(actionItems.length).toBeGreaterThanOrEqual(1)
expect(actionItems[0].name).toBe('FallbackAction')
expect(actionItems[0].status).toBe('failed')
expect(actionItems[0].action_id).toBe(9001)
expect(actionItems[0].action_details?.success).toBe(false)
expect(actionItems).toHaveLength(0)
})

it('does not create fallback action flow item when action_details is absent', async () => {
Expand Down
Loading
Loading