@ai-setting/roy-agent-core 1.3.10 → 1.3.11
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/dist/config/index.js +1647 -0
- package/dist/index.js +12579 -89691
- package/package.json +19 -56
- package/src/config/config-component.test.ts +0 -627
- package/src/config/config-component.ts +0 -906
- package/src/config/config-parser.test.ts +0 -319
- package/src/config/config-parser.ts +0 -203
- package/src/config/decentralized-config.test.ts +0 -740
- package/src/config/env-key.ts +0 -210
- package/src/config/env-source.test.ts +0 -252
- package/src/config/env-source.ts +0 -301
- package/src/config/file-source.test.ts +0 -357
- package/src/config/file-source.ts +0 -421
- package/src/config/index.ts +0 -24
- package/src/config/protocol-resolver.test.ts +0 -217
- package/src/config/protocol-resolver.ts +0 -228
- package/src/env/agent/agent-component.abort.test.ts +0 -511
- package/src/env/agent/agent-component.record-session.test.ts +0 -349
- package/src/env/agent/agent-component.test.ts +0 -1389
- package/src/env/agent/agent-component.tool-error.test.ts +0 -327
- package/src/env/agent/agent-component.ts +0 -1711
- package/src/env/agent/agent-config-registration.test.ts +0 -226
- package/src/env/agent/agent-config-registration.ts +0 -46
- package/src/env/agent/agent-reminder-plugin.integration.test.ts +0 -243
- package/src/env/agent/index.ts +0 -10
- package/src/env/agent/summary-agent.parse-hint.test.ts +0 -360
- package/src/env/agent/summary-agent.ts +0 -508
- package/src/env/agent/types.ts +0 -536
- package/src/env/commands/commands-component.test.ts +0 -364
- package/src/env/commands/commands-component.ts +0 -604
- package/src/env/commands/commands-config-registration.test.ts +0 -198
- package/src/env/commands/commands-config-registration.ts +0 -38
- package/src/env/commands/index.ts +0 -21
- package/src/env/commands/parser.test.ts +0 -203
- package/src/env/commands/parser.ts +0 -115
- package/src/env/commands/types.ts +0 -184
- package/src/env/commands-prompt-integration.test.ts +0 -243
- package/src/env/component-env.test.ts +0 -119
- package/src/env/component.ts +0 -335
- package/src/env/constants.test.ts +0 -72
- package/src/env/constants.ts +0 -123
- package/src/env/debug/debug-component.test.ts +0 -114
- package/src/env/debug/debug-component.ts +0 -547
- package/src/env/debug/formatters/index.ts +0 -9
- package/src/env/debug/formatters/repl-formatter.test.ts +0 -139
- package/src/env/debug/formatters/repl-formatter.ts +0 -358
- package/src/env/debug/formatters/trace-formatter.test.ts +0 -119
- package/src/env/debug/formatters/trace-formatter.ts +0 -191
- package/src/env/debug/formatters/tree-formatter.test.ts +0 -107
- package/src/env/debug/formatters/tree-formatter.ts +0 -325
- package/src/env/debug/index.ts +0 -38
- package/src/env/debug/parser/regex-parser.test.ts +0 -201
- package/src/env/debug/parser/regex-parser.ts +0 -196
- package/src/env/debug/parser/span-builder.test.ts +0 -241
- package/src/env/debug/parser/span-builder.ts +0 -386
- package/src/env/debug/reader/log-reader.test.ts +0 -170
- package/src/env/debug/reader/log-reader.ts +0 -186
- package/src/env/debug/reader/span-db-reader.test.ts +0 -118
- package/src/env/debug/reader/span-db-reader.ts +0 -201
- package/src/env/debug/types.test.ts +0 -187
- package/src/env/debug/types.ts +0 -171
- package/src/env/environment-init.test.ts +0 -183
- package/src/env/environment-lifecycle.test.ts +0 -516
- package/src/env/environment-service.test.ts +0 -332
- package/src/env/environment.handle-query.test.ts +0 -96
- package/src/env/environment.test.ts +0 -232
- package/src/env/environment.ts +0 -708
- package/src/env/errors.test.ts +0 -165
- package/src/env/errors.ts +0 -157
- package/src/env/event-source/event-source-agent-handler.test.ts +0 -193
- package/src/env/event-source/event-source-agent-handler.ts +0 -111
- package/src/env/event-source/event-source-component.process-cleanup.test.ts +0 -236
- package/src/env/event-source/event-source-component.stop.test.ts +0 -346
- package/src/env/event-source/event-source-component.test.ts +0 -1207
- package/src/env/event-source/event-source-component.ts +0 -1379
- package/src/env/event-source/event-source-config-registration.test.ts +0 -242
- package/src/env/event-source/event-source-config-registration.ts +0 -37
- package/src/env/event-source/event-source-integration.test.ts +0 -320
- package/src/env/event-source/event-source-platform.test.ts +0 -630
- package/src/env/event-source/types.ts +0 -298
- package/src/env/hook/global-hook-manager.ts +0 -162
- package/src/env/hook/hook-manager.test.ts +0 -374
- package/src/env/hook/hook-manager.ts +0 -309
- package/src/env/hook/index.ts +0 -38
- package/src/env/hook/types.ts +0 -138
- package/src/env/index.ts +0 -144
- package/src/env/interface.ts +0 -203
- package/src/env/llm/hooks.test.ts +0 -293
- package/src/env/llm/hooks.ts +0 -316
- package/src/env/llm/index.ts +0 -61
- package/src/env/llm/invoke-threshold-check.test.ts +0 -88
- package/src/env/llm/invoke-timeout.test.ts +0 -54
- package/src/env/llm/invoke.test.ts +0 -71
- package/src/env/llm/invoke.ts +0 -1039
- package/src/env/llm/llm-config.test.ts +0 -523
- package/src/env/llm/llm.test.ts +0 -233
- package/src/env/llm/llm.ts +0 -568
- package/src/env/llm/provider.test.ts +0 -182
- package/src/env/llm/provider.ts +0 -108
- package/src/env/llm/transform.test.ts +0 -251
- package/src/env/llm/transform.ts +0 -286
- package/src/env/llm/types.test.ts +0 -580
- package/src/env/llm/types.ts +0 -424
- package/src/env/log-trace/decorator-otel.test.ts +0 -182
- package/src/env/log-trace/decorator.ts +0 -230
- package/src/env/log-trace/index.ts +0 -79
- package/src/env/log-trace/log-trace-component.test.ts +0 -242
- package/src/env/log-trace/log-trace-component.ts +0 -497
- package/src/env/log-trace/log-trace-config-registration.test.ts +0 -348
- package/src/env/log-trace/log-trace-config-registration.ts +0 -45
- package/src/env/log-trace/logger.test.ts +0 -149
- package/src/env/log-trace/logger.ts +0 -522
- package/src/env/log-trace/opentelemetry/cli-propagation.test.ts +0 -147
- package/src/env/log-trace/opentelemetry/cli-propagation.ts +0 -194
- package/src/env/log-trace/opentelemetry/integration.test.ts +0 -668
- package/src/env/log-trace/opentelemetry/mod.ts +0 -25
- package/src/env/log-trace/opentelemetry/propagation-env.test.ts +0 -181
- package/src/env/log-trace/opentelemetry/propagation-env.ts +0 -136
- package/src/env/log-trace/opentelemetry/propagation.test.ts +0 -259
- package/src/env/log-trace/opentelemetry/propagation.ts +0 -215
- package/src/env/log-trace/opentelemetry/tracer-provider-context.test.ts +0 -166
- package/src/env/log-trace/opentelemetry/tracer-provider.test.ts +0 -379
- package/src/env/log-trace/opentelemetry/tracer-provider.ts +0 -612
- package/src/env/log-trace/span-storage.test.ts +0 -145
- package/src/env/log-trace/span-storage.ts +0 -230
- package/src/env/log-trace/trace-context.test.ts +0 -187
- package/src/env/log-trace/trace-context.ts +0 -162
- package/src/env/log-trace/types.test.ts +0 -63
- package/src/env/log-trace/types.ts +0 -172
- package/src/env/mcp/README.md +0 -244
- package/src/env/mcp/__integration__/mcp-component.integration.test.ts +0 -373
- package/src/env/mcp/config.test.ts +0 -74
- package/src/env/mcp/config.ts +0 -116
- package/src/env/mcp/index.ts +0 -41
- package/src/env/mcp/loader.test.ts +0 -161
- package/src/env/mcp/loader.ts +0 -209
- package/src/env/mcp/mcp-component.test.ts +0 -111
- package/src/env/mcp/mcp-component.ts +0 -358
- package/src/env/mcp/mcp-config-registration.test.ts +0 -304
- package/src/env/mcp/mcp-config-registration.ts +0 -50
- package/src/env/mcp/scanner.test.ts +0 -170
- package/src/env/mcp/scanner.ts +0 -246
- package/src/env/mcp/tool/adapter.test.ts +0 -520
- package/src/env/mcp/tool/adapter.ts +0 -521
- package/src/env/mcp/tool/index.ts +0 -5
- package/src/env/mcp/types.test.ts +0 -171
- package/src/env/mcp/types.ts +0 -79
- package/src/env/memory/README.md +0 -177
- package/src/env/memory/built-in/index.ts +0 -59
- package/src/env/memory/built-in/recall-memory.ts +0 -103
- package/src/env/memory/built-in/record-memory.ts +0 -148
- package/src/env/memory/index.ts +0 -20
- package/src/env/memory/memory-component.test.ts +0 -239
- package/src/env/memory/memory-component.ts +0 -503
- package/src/env/memory/memory-config-registration.test.ts +0 -67
- package/src/env/memory/memory-config-registration.ts +0 -48
- package/src/env/memory/memory-config.ts +0 -45
- package/src/env/memory/memory-file.test.ts +0 -268
- package/src/env/memory/plugin/index.ts +0 -48
- package/src/env/memory/plugin/memory-agent.test.ts +0 -249
- package/src/env/memory/plugin/memory-agent.ts +0 -365
- package/src/env/memory/plugin/memory-manager.ts +0 -198
- package/src/env/memory/plugin/memory-plugin-agent.test.ts +0 -145
- package/src/env/memory/plugin/memory-plugin.ts +0 -210
- package/src/env/memory/plugin/plugin-simplified.test.ts +0 -51
- package/src/env/memory/plugin/recall-memory.test.ts +0 -106
- package/src/env/memory/plugin/recall-memory.ts +0 -53
- package/src/env/memory/plugin/types.ts +0 -101
- package/src/env/memory/tools/memory-agent-tools.ts +0 -228
- package/src/env/memory/types.ts +0 -85
- package/src/env/paths.ts +0 -118
- package/src/env/prompt/index.ts +0 -18
- package/src/env/prompt/memory-prompts.test.ts +0 -91
- package/src/env/prompt/prompt-component.test.ts +0 -491
- package/src/env/prompt/prompt-component.ts +0 -619
- package/src/env/prompt/prompt-config-registration.test.ts +0 -213
- package/src/env/prompt/prompt-config-registration.ts +0 -39
- package/src/env/prompt/prompts-index.ts +0 -504
- package/src/env/prompt/renderer.ts +0 -67
- package/src/env/prompt/types.ts +0 -136
- package/src/env/session/hooks.ts +0 -18
- package/src/env/session/index.ts +0 -37
- package/src/env/session/search-query-parser.test.ts +0 -425
- package/src/env/session/search-query-parser.ts +0 -171
- package/src/env/session/session-checkpoint.test.ts +0 -523
- package/src/env/session/session-component.extract-recent-messages.test.ts +0 -209
- package/src/env/session/session-component.test.ts +0 -132
- package/src/env/session/session-component.ts +0 -1249
- package/src/env/session/session-config-registration.test.ts +0 -138
- package/src/env/session/session-config-registration.ts +0 -52
- package/src/env/session/session-message-converter.test.ts +0 -763
- package/src/env/session/session-message-converter.ts +0 -415
- package/src/env/session/session-message-e2e.test.ts +0 -448
- package/src/env/session/session-search.test.ts +0 -391
- package/src/env/session/session-store.test.ts +0 -362
- package/src/env/session/session-store.ts +0 -141
- package/src/env/session/storage/index.ts +0 -6
- package/src/env/session/storage/memory.ts +0 -502
- package/src/env/session/storage/sqlite.ts +0 -794
- package/src/env/session/types.ts +0 -742
- package/src/env/skill/config.ts +0 -39
- package/src/env/skill/index.ts +0 -6
- package/src/env/skill/parser.test.ts +0 -116
- package/src/env/skill/parser.ts +0 -77
- package/src/env/skill/scanner.test.ts +0 -211
- package/src/env/skill/scanner.ts +0 -119
- package/src/env/skill/skill-component.test.ts +0 -234
- package/src/env/skill/skill-component.ts +0 -352
- package/src/env/skill/skill-config-registration.test.ts +0 -60
- package/src/env/skill/skill-config-registration.ts +0 -43
- package/src/env/skill/tool/index.ts +0 -1
- package/src/env/skill/tool/skill-tool.test.ts +0 -100
- package/src/env/skill/tool/skill-tool.ts +0 -72
- package/src/env/skill/types.ts +0 -64
- package/src/env/task/delegate/delegate-tool.test.ts +0 -498
- package/src/env/task/delegate/delegate-tool.ts +0 -1014
- package/src/env/task/delegate/index.ts +0 -18
- package/src/env/task/delegate/stop-tool.test.ts +0 -140
- package/src/env/task/delegate/stop-tool.ts +0 -119
- package/src/env/task/delegate/task-events.test.ts +0 -178
- package/src/env/task/delegate/task-events.ts +0 -143
- package/src/env/task/hooks/contexts.test.ts +0 -92
- package/src/env/task/hooks/contexts.ts +0 -192
- package/src/env/task/hooks/index.ts +0 -23
- package/src/env/task/hooks/task-hook-points.test.ts +0 -32
- package/src/env/task/hooks/task-hook-points.ts +0 -54
- package/src/env/task/index.ts +0 -7
- package/src/env/task/plugins/index.ts +0 -13
- package/src/env/task/plugins/task-plugin.test.ts +0 -74
- package/src/env/task/plugins/task-plugin.ts +0 -89
- package/src/env/task/plugins/task-tag-plugin.test.ts +0 -377
- package/src/env/task/plugins/task-tag-plugin.ts +0 -319
- package/src/env/task/plugins/task-workflow-extractor.integration.test.ts +0 -226
- package/src/env/task/plugins/workflow-extractor-agent.test.ts +0 -107
- package/src/env/task/plugins/workflow-extractor-agent.ts +0 -225
- package/src/env/task/storage/index.ts +0 -6
- package/src/env/task/storage/sqlite-task-store.test.ts +0 -283
- package/src/env/task/storage/sqlite-task-store.ts +0 -903
- package/src/env/task/storage/task-search.test.ts +0 -291
- package/src/env/task/tag-service.test.ts +0 -198
- package/src/env/task/tag-service.ts +0 -264
- package/src/env/task/task-component.test.ts +0 -193
- package/src/env/task/task-component.ts +0 -658
- package/src/env/task/task-config-registration.test.ts +0 -57
- package/src/env/task/task-config-registration.ts +0 -37
- package/src/env/task/task-types.test.ts +0 -137
- package/src/env/task/tools/complete-tool.ts +0 -44
- package/src/env/task/tools/create-tool.ts +0 -49
- package/src/env/task/tools/delete-tool.ts +0 -43
- package/src/env/task/tools/get-tool.ts +0 -59
- package/src/env/task/tools/index.ts +0 -10
- package/src/env/task/tools/list-tool.ts +0 -40
- package/src/env/task/tools/operation/create-tool.ts +0 -48
- package/src/env/task/tools/operation/delete-tool.ts +0 -43
- package/src/env/task/tools/operation/get-tool.ts +0 -43
- package/src/env/task/tools/operation/index.ts +0 -9
- package/src/env/task/tools/operation/list-tool.ts +0 -40
- package/src/env/task/tools/operation/operation-tools.test.ts +0 -274
- package/src/env/task/tools/operation/operation-types.ts +0 -75
- package/src/env/task/tools/operation/update-tool.ts +0 -47
- package/src/env/task/tools/task-tools.test.ts +0 -203
- package/src/env/task/tools/task-types.test.ts +0 -75
- package/src/env/task/tools/task-types.ts +0 -68
- package/src/env/task/tools/update-tool.ts +0 -70
- package/src/env/task/types.ts +0 -160
- package/src/env/tool/built-in/bash.ts +0 -201
- package/src/env/tool/built-in/echo.ts +0 -29
- package/src/env/tool/built-in/edit-file.test.ts +0 -136
- package/src/env/tool/built-in/edit-file.ts +0 -92
- package/src/env/tool/built-in/glob.test.ts +0 -94
- package/src/env/tool/built-in/glob.ts +0 -65
- package/src/env/tool/built-in/grep.test.ts +0 -122
- package/src/env/tool/built-in/grep.ts +0 -108
- package/src/env/tool/built-in/index.ts +0 -44
- package/src/env/tool/built-in/read-file.test.ts +0 -84
- package/src/env/tool/built-in/read-file.ts +0 -75
- package/src/env/tool/built-in/write-file.test.ts +0 -119
- package/src/env/tool/built-in/write-file.ts +0 -68
- package/src/env/tool/index.ts +0 -24
- package/src/env/tool/registry.test.ts +0 -257
- package/src/env/tool/registry.ts +0 -167
- package/src/env/tool/tool-component.test.ts +0 -559
- package/src/env/tool/tool-component.ts +0 -563
- package/src/env/tool/tool-config-registration.test.ts +0 -249
- package/src/env/tool/tool-config-registration.ts +0 -46
- package/src/env/tool/types.ts +0 -267
- package/src/env/tool/validator.test.ts +0 -143
- package/src/env/tool/validator.ts +0 -44
- package/src/env/types.ts +0 -180
- package/src/env/workflow/ask-user-tool-registration.test.ts +0 -216
- package/src/env/workflow/complex-workflow.integration.test.ts +0 -1900
- package/src/env/workflow/decorators/decorator-node.ts +0 -229
- package/src/env/workflow/decorators/decorator.test.ts +0 -196
- package/src/env/workflow/decorators/edge.ts +0 -82
- package/src/env/workflow/decorators/index.ts +0 -31
- package/src/env/workflow/decorators/node-as.ts +0 -98
- package/src/env/workflow/decorators/workflow.ts +0 -54
- package/src/env/workflow/engine/dag-manager.test.ts +0 -570
- package/src/env/workflow/engine/dag-manager.ts +0 -594
- package/src/env/workflow/engine/engine.ts +0 -1422
- package/src/env/workflow/engine/event-bus.test.ts +0 -359
- package/src/env/workflow/engine/event-bus.ts +0 -156
- package/src/env/workflow/engine/executor-agent-session.test.ts +0 -84
- package/src/env/workflow/engine/executor.test.ts +0 -619
- package/src/env/workflow/engine/executor.ts +0 -593
- package/src/env/workflow/engine/index.ts +0 -24
- package/src/env/workflow/engine/node-registry.test.ts +0 -560
- package/src/env/workflow/engine/node-registry.ts +0 -289
- package/src/env/workflow/engine/resume-removed.test.ts +0 -22
- package/src/env/workflow/engine/scheduler.test.ts +0 -715
- package/src/env/workflow/engine/scheduler.ts +0 -318
- package/src/env/workflow/engine/workflow-engine.test.ts +0 -815
- package/src/env/workflow/extractor/workflow-converter.ts +0 -306
- package/src/env/workflow/fixtures.ts +0 -380
- package/src/env/workflow/index.ts +0 -38
- package/src/env/workflow/integration/run-resume-unified.test.ts +0 -186
- package/src/env/workflow/integration/service-integration.test.ts +0 -267
- package/src/env/workflow/metadata/keys.ts +0 -12
- package/src/env/workflow/nodes/agent-component-adapter.test.ts +0 -318
- package/src/env/workflow/nodes/agent-component-adapter.ts +0 -448
- package/src/env/workflow/nodes/agent-node.test.ts +0 -371
- package/src/env/workflow/nodes/agent-node.ts +0 -598
- package/src/env/workflow/nodes/ask-user-node.ts +0 -113
- package/src/env/workflow/nodes/condition-node.ts +0 -200
- package/src/env/workflow/nodes/index.ts +0 -9
- package/src/env/workflow/nodes/merge-node.ts +0 -141
- package/src/env/workflow/nodes/skill-node.test.ts +0 -253
- package/src/env/workflow/nodes/skill-node.ts +0 -393
- package/src/env/workflow/nodes/tool-node.test.ts +0 -251
- package/src/env/workflow/nodes/tool-node.ts +0 -493
- package/src/env/workflow/nodes/workflow-llm-history.test.ts +0 -455
- package/src/env/workflow/nodes/workflow-node.test.ts +0 -315
- package/src/env/workflow/nodes/workflow-node.ts +0 -311
- package/src/env/workflow/service/index.ts +0 -27
- package/src/env/workflow/service/registry.test.ts +0 -133
- package/src/env/workflow/service/registry.ts +0 -71
- package/src/env/workflow/service/workflow-service.test.ts +0 -310
- package/src/env/workflow/service/workflow-service.ts +0 -393
- package/src/env/workflow/storage/index.ts +0 -28
- package/src/env/workflow/storage/mock-repositories.ts +0 -385
- package/src/env/workflow/storage/sqlite.test.ts +0 -179
- package/src/env/workflow/storage/sqlite.ts +0 -163
- package/src/env/workflow/storage/workflow-repo.test.ts +0 -780
- package/src/env/workflow/storage/workflow-repo.ts +0 -342
- package/src/env/workflow/tools/ask-user-tool.ts +0 -82
- package/src/env/workflow/tools/index.ts +0 -26
- package/src/env/workflow/tools/run-workflow.test.ts +0 -352
- package/src/env/workflow/tools/run-workflow.ts +0 -214
- package/src/env/workflow/types/context.ts +0 -18
- package/src/env/workflow/types/decorators-types.ts +0 -198
- package/src/env/workflow/types/event.test.ts +0 -515
- package/src/env/workflow/types/event.ts +0 -193
- package/src/env/workflow/types/index.ts +0 -49
- package/src/env/workflow/types/run.test.ts +0 -437
- package/src/env/workflow/types/run.ts +0 -173
- package/src/env/workflow/types/workflow-hil.ts +0 -114
- package/src/env/workflow/types/workflow-message.test.ts +0 -138
- package/src/env/workflow/types/workflow-message.ts +0 -196
- package/src/env/workflow/types/workflow-session.test.ts +0 -95
- package/src/env/workflow/types/workflow-session.ts +0 -59
- package/src/env/workflow/types/workflow.test.ts +0 -495
- package/src/env/workflow/types/workflow.ts +0 -195
- package/src/env/workflow/types_compat.ts +0 -51
- package/src/env/workflow/utils/create-workflow.ts +0 -47
- package/src/env/workflow/utils/execution-state.ts +0 -245
- package/src/env/workflow/utils/index.ts +0 -18
- package/src/env/workflow/utils/node-registry-helper.ts +0 -58
- package/src/env/workflow/utils/recovery-validator.test.ts +0 -460
- package/src/env/workflow/utils/recovery-validator.ts +0 -377
- package/src/env/workflow/utils/session-parser.test.ts +0 -111
- package/src/env/workflow/utils/session-parser.ts +0 -94
- package/src/env/workflow/utils/session-recovery.test.ts +0 -334
- package/src/env/workflow/utils/session-recovery.ts +0 -188
- package/src/env/workflow/utils/template-resolver.test.ts +0 -258
- package/src/env/workflow/utils/template-resolver.ts +0 -436
- package/src/env/workflow/utils/validation-rules.ts +0 -149
- package/src/env/workflow/workflow-component.ts +0 -544
- package/src/index.ts +0 -422
- package/src/utils/id.ts +0 -21
|
@@ -1,1379 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EventSourceComponent
|
|
3
|
-
*
|
|
4
|
-
* 事件源组件,管理所有事件源的注册、配置和生命周期
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { ChildProcess, spawn, exec } from "child_process";
|
|
8
|
-
import { promisify } from "util";
|
|
9
|
-
|
|
10
|
-
// execAsync 函数,在测试时会被 mock 替换
|
|
11
|
-
let execAsyncImpl: (command: string) => Promise<{ stdout: string; stderr: string }> =
|
|
12
|
-
promisify(exec);
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 设置 execAsync 实现(用于测试)
|
|
16
|
-
*/
|
|
17
|
-
export function setExecAsync(fn: (command: string) => Promise<{ stdout: string; stderr: string }>): void {
|
|
18
|
-
execAsyncImpl = fn;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 获取 execAsync 函数
|
|
23
|
-
*/
|
|
24
|
-
function getExecAsync(): (command: string) => Promise<{ stdout: string; stderr: string }> {
|
|
25
|
-
return execAsyncImpl;
|
|
26
|
-
}
|
|
27
|
-
import { join } from "path";
|
|
28
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
29
|
-
import { BaseComponent, type ComponentConfig } from "../component";
|
|
30
|
-
import type { ConfigComponent } from "../../config/config-component";
|
|
31
|
-
import { toEnvKey, envKeyToConfigKey } from "../../config/env-key";
|
|
32
|
-
import type {
|
|
33
|
-
EventSourceConfig,
|
|
34
|
-
EventSourceStatus,
|
|
35
|
-
EventSourceEvent,
|
|
36
|
-
EventSourceEventHandler,
|
|
37
|
-
EventSourceType,
|
|
38
|
-
EventSourcePersistenceConfig,
|
|
39
|
-
ReplyChannel,
|
|
40
|
-
EventMetadata,
|
|
41
|
-
RecommendedAction
|
|
42
|
-
} from "./types";
|
|
43
|
-
import { createLogger } from "../log-trace/logger";
|
|
44
|
-
import { EVENT_SOURCE_CONFIG_REGISTRATION, EVENT_SOURCE_DEFAULTS } from "./event-source-config-registration";
|
|
45
|
-
|
|
46
|
-
const logger = createLogger("event-source");
|
|
47
|
-
|
|
48
|
-
// ============================================================================
|
|
49
|
-
// EventSourceComponentOptions
|
|
50
|
-
// ============================================================================
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* EventSourceComponent 配置选项
|
|
54
|
-
*/
|
|
55
|
-
export interface EventSourceComponentOptions {
|
|
56
|
-
/** ConfigComponent 实例(必填) */
|
|
57
|
-
configComponent: ConfigComponent;
|
|
58
|
-
|
|
59
|
-
/** 配置文件相对路径(可选) */
|
|
60
|
-
configPath?: string;
|
|
61
|
-
|
|
62
|
-
/** 直接传入的配置对象(可选,优先级最高) */
|
|
63
|
-
config?: Partial<EventSourceConfigOptions>;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* EventSourceComponent 内部配置
|
|
68
|
-
*/
|
|
69
|
-
interface EventSourceConfigOptions {
|
|
70
|
-
enabled: boolean;
|
|
71
|
-
persistenceEnabled: boolean;
|
|
72
|
-
configPath: string;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ============================================================================
|
|
76
|
-
// EventSourceComponent
|
|
77
|
-
// ============================================================================
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* EventSourceComponent
|
|
81
|
-
*
|
|
82
|
-
* 事件源组件,管理所有事件源的注册、配置和生命周期
|
|
83
|
-
*/
|
|
84
|
-
// ============================================================================
|
|
85
|
-
// ProcessInfo
|
|
86
|
-
// ============================================================================
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* 扩展的进程信息接口
|
|
90
|
-
*/
|
|
91
|
-
interface ProcessInfo {
|
|
92
|
-
/** 基础进程对象 */
|
|
93
|
-
child: ChildProcess;
|
|
94
|
-
/** 进程组 ID */
|
|
95
|
-
pgid?: number;
|
|
96
|
-
/** 相关进程列表 */
|
|
97
|
-
pids: number[];
|
|
98
|
-
/** 执行的命令 */
|
|
99
|
-
command?: string;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ============================================================================
|
|
103
|
-
// EventSourceComponent
|
|
104
|
-
// ============================================================================
|
|
105
|
-
|
|
106
|
-
export class EventSourceComponent extends BaseComponent {
|
|
107
|
-
readonly name = "event-source";
|
|
108
|
-
readonly version = "1.0.0";
|
|
109
|
-
|
|
110
|
-
// 内部状态
|
|
111
|
-
private sources: Map<string, EventSourceConfig> = new Map();
|
|
112
|
-
private statuses: Map<string, EventSourceStatus> = new Map();
|
|
113
|
-
private processes: Map<string, ProcessInfo> = new Map();
|
|
114
|
-
private handlers: Map<string, EventSourceEventHandler> = new Map();
|
|
115
|
-
private timers: Map<string, NodeJS.Timeout> = new Map();
|
|
116
|
-
private buffers: Map<string, string> = new Map();
|
|
117
|
-
|
|
118
|
-
// 持久化配置
|
|
119
|
-
private configPath: string = "";
|
|
120
|
-
private persistenceEnabled: boolean = true;
|
|
121
|
-
|
|
122
|
-
/** ConfigComponent 用于配置管理 */
|
|
123
|
-
private configComponent?: ConfigComponent;
|
|
124
|
-
|
|
125
|
-
/** 配置热更新 watcher 清理函数 */
|
|
126
|
-
private configWatcher?: () => void;
|
|
127
|
-
|
|
128
|
-
// ========================================================================
|
|
129
|
-
// 组件生命周期
|
|
130
|
-
// ========================================================================
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* 初始化
|
|
134
|
-
*/
|
|
135
|
-
protected async onInit(): Promise<void> {
|
|
136
|
-
// 配置注册已在 init() 方法中完成(通过 ComponentConfig.options 获取)
|
|
137
|
-
// 加载已保存的事件源配置
|
|
138
|
-
await this.loadConfig();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* 公开的初始化方法,用于子类覆盖
|
|
143
|
-
*
|
|
144
|
-
* 这个方法会被 BaseComponent.init() 调用,用于执行子类特有的初始化逻辑。
|
|
145
|
-
* 它接收 ComponentConfig,并通过 ComponentConfig.options 获取 EventSourceComponentOptions。
|
|
146
|
-
*/
|
|
147
|
-
async init(options?: ComponentConfig): Promise<void> {
|
|
148
|
-
// 如果提供了 env,则注入
|
|
149
|
-
if (options?.env) {
|
|
150
|
-
this.env = options.env;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
this.setStatus("initializing");
|
|
154
|
-
|
|
155
|
-
if (options?.name) Object.defineProperty(this, "name", { value: options.name, writable: false });
|
|
156
|
-
if (options?.version) Object.defineProperty(this, "version", { value: options.version, writable: false });
|
|
157
|
-
if (options?.enabled !== undefined) this._enabled = options.enabled;
|
|
158
|
-
|
|
159
|
-
// 从 options 获取 EventSourceComponentOptions
|
|
160
|
-
const opts = options?.options as unknown as EventSourceComponentOptions | undefined;
|
|
161
|
-
if (opts?.configComponent) {
|
|
162
|
-
this.configComponent = opts.configComponent;
|
|
163
|
-
await this.registerConfig(opts);
|
|
164
|
-
} else {
|
|
165
|
-
throw new Error("ConfigComponent is required for EventSourceComponent initialization");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
await this.onInit();
|
|
169
|
-
this.setStatus("running");
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
protected async onStart(): Promise<void> {
|
|
173
|
-
// onStart 时不再重复加载(已在 onInit 中加载)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
protected async onStop(): Promise<void> {
|
|
177
|
-
// 清理配置 watcher
|
|
178
|
-
this.configWatcher?.();
|
|
179
|
-
this.configWatcher = undefined;
|
|
180
|
-
|
|
181
|
-
// 停止所有运行中的事件源
|
|
182
|
-
const runningSources = Array.from(this.statuses.entries())
|
|
183
|
-
.filter(([_, status]) => status === "running")
|
|
184
|
-
.map(([id]) => id);
|
|
185
|
-
|
|
186
|
-
for (const id of runningSources) {
|
|
187
|
-
await this.stopSource(id);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// 清空所有状态
|
|
191
|
-
this.sources.clear();
|
|
192
|
-
this.statuses.clear();
|
|
193
|
-
this.processes.clear();
|
|
194
|
-
this.handlers.clear();
|
|
195
|
-
this.timers.clear();
|
|
196
|
-
this.buffers.clear();
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// ========================================================================
|
|
200
|
-
// 配置注册
|
|
201
|
-
// ========================================================================
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* 注册配置到 ConfigComponent
|
|
205
|
-
*
|
|
206
|
-
* 配置加载顺序(优先级从低到高):
|
|
207
|
-
* 1. Defaults - 默认值(最低)
|
|
208
|
-
* 2. FileSource - 从配置文件加载
|
|
209
|
-
* 3. EnvSource - 从环境变量加载
|
|
210
|
-
* 4. config 对象 - 直接传入的配置(最高)
|
|
211
|
-
*/
|
|
212
|
-
private async registerConfig(options: EventSourceComponentOptions): Promise<void> {
|
|
213
|
-
const configComponent = options.configComponent;
|
|
214
|
-
if (!configComponent) return;
|
|
215
|
-
|
|
216
|
-
const { configPath, config } = options;
|
|
217
|
-
const envPrefix = "EVENT_SOURCE";
|
|
218
|
-
|
|
219
|
-
// 1. 使用 registerComponent 注册配置结构(keys 和 sources,不含 defaults)
|
|
220
|
-
configComponent.registerComponent(EVENT_SOURCE_CONFIG_REGISTRATION);
|
|
221
|
-
|
|
222
|
-
// 2. 注册 FileSource(如果提供了 configPath)
|
|
223
|
-
if (configPath) {
|
|
224
|
-
configComponent.registerSource({
|
|
225
|
-
type: "file",
|
|
226
|
-
relativePath: configPath,
|
|
227
|
-
optional: true,
|
|
228
|
-
watch: false
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// 3. 注册 EnvSource
|
|
233
|
-
configComponent.registerSource({
|
|
234
|
-
type: "env",
|
|
235
|
-
envPrefix: envPrefix,
|
|
236
|
-
priority: 20,
|
|
237
|
-
watch: false
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// 4. 从 sources 加载配置(FileSource 和 EnvSource)
|
|
241
|
-
await configComponent.load("event-source");
|
|
242
|
-
|
|
243
|
-
// 5. 后备方案:直接读取环境变量(确保所有环境变量都被处理)
|
|
244
|
-
for (const envKey of Object.keys(process.env)) {
|
|
245
|
-
const configKey = envKeyToConfigKey(envKey, envPrefix, "event-source");
|
|
246
|
-
if (!configKey) continue;
|
|
247
|
-
const value = process.env[envKey];
|
|
248
|
-
if (value !== undefined) {
|
|
249
|
-
await configComponent.set(configKey, value);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// 6. 设置默认值(只有当配置不存在时)
|
|
254
|
-
for (const [key, value] of Object.entries(EVENT_SOURCE_DEFAULTS)) {
|
|
255
|
-
if (configComponent.get(key) === undefined) {
|
|
256
|
-
await configComponent.set(key, value);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// 6.5 特殊处理 configPath 动态默认值
|
|
261
|
-
if (configComponent.get("event-source.configPath") === undefined) {
|
|
262
|
-
// 默认路径: ~/.roy-agent/event-sources.json
|
|
263
|
-
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
264
|
-
const dataDir = join(home, ".roy-agent");
|
|
265
|
-
await configComponent.set("event-source.configPath", join(dataDir, "event-sources.json"));
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// 7. 直接配置对象(最高优先级)
|
|
269
|
-
if (config) {
|
|
270
|
-
const flatConfig = this.flattenConfig(config);
|
|
271
|
-
for (const [key, value] of Object.entries(flatConfig)) {
|
|
272
|
-
await configComponent.set(key, value);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// 8. 注册配置热更新监听
|
|
277
|
-
this.registerConfigWatcher(configComponent);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* 构建最终配置对象
|
|
282
|
-
*/
|
|
283
|
-
private buildConfig(): EventSourceConfigOptions {
|
|
284
|
-
const configComponent = this.configComponent;
|
|
285
|
-
if (!configComponent) {
|
|
286
|
-
return {
|
|
287
|
-
enabled: true,
|
|
288
|
-
persistenceEnabled: true,
|
|
289
|
-
configPath: "",
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return {
|
|
294
|
-
enabled: configComponent.get("event-source.enabled") as boolean ?? true,
|
|
295
|
-
persistenceEnabled: configComponent.get("event-source.persistenceEnabled") as boolean ?? true,
|
|
296
|
-
configPath: configComponent.get("event-source.configPath") as string ?? "",
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* 将配置对象展平为点号路径
|
|
302
|
-
*/
|
|
303
|
-
private flattenConfig(
|
|
304
|
-
obj: Record<string, unknown>,
|
|
305
|
-
prefix = "event-source"
|
|
306
|
-
): Record<string, unknown> {
|
|
307
|
-
const result: Record<string, unknown> = {};
|
|
308
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
309
|
-
const fullKey = `${prefix}.${key}`;
|
|
310
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
311
|
-
Object.assign(
|
|
312
|
-
result,
|
|
313
|
-
this.flattenConfig(value as Record<string, unknown>, fullKey)
|
|
314
|
-
);
|
|
315
|
-
} else {
|
|
316
|
-
result[fullKey] = value;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
return result;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* 注册配置热更新监听
|
|
324
|
-
*/
|
|
325
|
-
private registerConfigWatcher(configComponent: ConfigComponent): void {
|
|
326
|
-
if (typeof configComponent.watch !== "function") {
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
this.configWatcher = configComponent.watch("event-source.*", (event) => {
|
|
331
|
-
this.onConfigChange(event);
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* 处理配置变更
|
|
337
|
-
*/
|
|
338
|
-
protected onConfigChange(event: { key: string; oldValue?: unknown; newValue?: unknown }): void {
|
|
339
|
-
logger.info(`EventSource config changed: ${event.key}`, {
|
|
340
|
-
oldValue: event.oldValue,
|
|
341
|
-
newValue: event.newValue
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// ========================================================================
|
|
346
|
-
// 事件源注册与管理
|
|
347
|
-
// ========================================================================
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* 注册事件源
|
|
351
|
-
*/
|
|
352
|
-
register(config: EventSourceConfig): void {
|
|
353
|
-
if (this.sources.has(config.id)) {
|
|
354
|
-
throw new Error(`EventSource already exists: ${config.id}`);
|
|
355
|
-
}
|
|
356
|
-
this.sources.set(config.id, config);
|
|
357
|
-
this.statuses.set(config.id, "created");
|
|
358
|
-
logger.debug(`EventSource registered: ${config.id} (${config.type})`);
|
|
359
|
-
|
|
360
|
-
// 自动保存配置
|
|
361
|
-
this.saveConfig().catch((err) => {
|
|
362
|
-
logger.error(`Failed to save config after registering ${config.id}:`, err);
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* 注销事件源
|
|
368
|
-
*/
|
|
369
|
-
unregister(id: string): boolean {
|
|
370
|
-
const status = this.statuses.get(id);
|
|
371
|
-
if (status === "running") {
|
|
372
|
-
this.stopSource(id); // 自动停止
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const existed = this.sources.has(id);
|
|
376
|
-
this.sources.delete(id);
|
|
377
|
-
this.statuses.delete(id);
|
|
378
|
-
this.handlers.delete(id);
|
|
379
|
-
this.buffers.delete(id);
|
|
380
|
-
|
|
381
|
-
if (existed) {
|
|
382
|
-
logger.debug(`EventSource unregistered: ${id}`);
|
|
383
|
-
// 自动保存配置
|
|
384
|
-
this.saveConfig().catch((err) => {
|
|
385
|
-
logger.error(`Failed to save config after unregistering ${id}:`, err);
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
return existed;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* 获取事件源配置
|
|
393
|
-
*/
|
|
394
|
-
get(id: string): EventSourceConfig | undefined {
|
|
395
|
-
return this.sources.get(id);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* 列出所有事件源
|
|
400
|
-
*/
|
|
401
|
-
list(): EventSourceConfig[] {
|
|
402
|
-
return Array.from(this.sources.values());
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* 获取事件源状态
|
|
407
|
-
*/
|
|
408
|
-
getEventSourceStatus(id: string): EventSourceStatus | undefined {
|
|
409
|
-
return this.statuses.get(id);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* 获取指定事件源的状态
|
|
414
|
-
*/
|
|
415
|
-
getEventSourceStatusById(id: string): EventSourceStatus | undefined {
|
|
416
|
-
return this.statuses.get(id);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* 设置事件处理器
|
|
421
|
-
*/
|
|
422
|
-
onEvent(id: string, handler: EventSourceEventHandler): void {
|
|
423
|
-
this.handlers.set(id, handler);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* 移除事件处理器
|
|
428
|
-
*/
|
|
429
|
-
offEvent(id: string): void {
|
|
430
|
-
this.handlers.delete(id);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// ========================================================================
|
|
434
|
-
// 生命周期控制
|
|
435
|
-
// ========================================================================
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* 启动事件源
|
|
439
|
-
*
|
|
440
|
-
* 注意:方法名为 startSource 而非 start,以避免覆盖 Component 接口的 start() 方法
|
|
441
|
-
*/
|
|
442
|
-
async startSource(id: string): Promise<void> {
|
|
443
|
-
const config = this.sources.get(id);
|
|
444
|
-
if (!config) {
|
|
445
|
-
throw new Error(`EventSource not found: ${id}`);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const currentStatus = this.statuses.get(id);
|
|
449
|
-
if (currentStatus === "running") {
|
|
450
|
-
return; // 已运行
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
this.statuses.set(id, "starting");
|
|
454
|
-
|
|
455
|
-
try {
|
|
456
|
-
switch (config.type) {
|
|
457
|
-
case "lark-cli":
|
|
458
|
-
await this.startLarkCli(id, config);
|
|
459
|
-
break;
|
|
460
|
-
case "timer":
|
|
461
|
-
await this.startTimer(id, config);
|
|
462
|
-
break;
|
|
463
|
-
case "websocket":
|
|
464
|
-
await this.startWebSocket(id, config);
|
|
465
|
-
break;
|
|
466
|
-
default:
|
|
467
|
-
throw new Error(`Unsupported event source type: ${config.type}`);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
this.statuses.set(id, "running");
|
|
471
|
-
|
|
472
|
-
// 发布事件
|
|
473
|
-
this.getEnv()?.pushEnvEvent({
|
|
474
|
-
type: "event-source.started",
|
|
475
|
-
payload: { sourceId: id, sourceType: config.type },
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
logger.info(`EventSource started: ${id} (${config.type})`);
|
|
479
|
-
|
|
480
|
-
} catch (error) {
|
|
481
|
-
this.statuses.set(id, "error");
|
|
482
|
-
logger.error(`EventSource failed to start: ${id}`, error);
|
|
483
|
-
throw error;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* 停止事件源
|
|
489
|
-
*
|
|
490
|
-
* 注意:方法名为 stopSource 而非 stop,以避免覆盖 Component 接口的 stop() 方法
|
|
491
|
-
*/
|
|
492
|
-
async stopSource(id: string): Promise<void> {
|
|
493
|
-
const status = this.statuses.get(id);
|
|
494
|
-
logger.info(`[EventSource ${id}] stopSource called, current status: ${status}`);
|
|
495
|
-
|
|
496
|
-
// 停止任意非 stopped 状态的事件源
|
|
497
|
-
if (status === "stopped" || status === "created") {
|
|
498
|
-
logger.info(`[EventSource ${id}] already stopped/created, skipping`);
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
this.statuses.set(id, "stopping");
|
|
503
|
-
|
|
504
|
-
// 停止子进程
|
|
505
|
-
const processInfo = this.processes.get(id);
|
|
506
|
-
const child = processInfo?.child;
|
|
507
|
-
logger.info(`[EventSource ${id}] process info: ${processInfo ? `pid=${processInfo.child.pid}` : "none"}`);
|
|
508
|
-
|
|
509
|
-
if (processInfo && child) {
|
|
510
|
-
// 清理事件监听器,防止内存泄漏
|
|
511
|
-
child.removeAllListeners("exit");
|
|
512
|
-
child.removeAllListeners("error");
|
|
513
|
-
child.stdout?.removeAllListeners("data");
|
|
514
|
-
child.stderr?.removeAllListeners("data");
|
|
515
|
-
|
|
516
|
-
// 杀死所有相关进程的辅助函数
|
|
517
|
-
const killProcess = (pid: number, signal: string): boolean => {
|
|
518
|
-
try {
|
|
519
|
-
process.kill(pid, signal);
|
|
520
|
-
return true;
|
|
521
|
-
} catch (e) {
|
|
522
|
-
return false;
|
|
523
|
-
}
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
// 尝试 SIGTERM(优雅终止)给所有相关进程
|
|
527
|
-
const pidsToKill = processInfo.pids.length > 0 ? processInfo.pids : (processInfo.child.pid ? [processInfo.child.pid] : []);
|
|
528
|
-
logger.info(`[EventSource ${id}] sending SIGTERM to ${pidsToKill.length} processes: ${pidsToKill.join(", ")}`);
|
|
529
|
-
|
|
530
|
-
for (const pid of pidsToKill) {
|
|
531
|
-
killProcess(pid, "SIGTERM");
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// 等待进程退出,最多 3 秒
|
|
535
|
-
await new Promise<void>((resolve) => {
|
|
536
|
-
const timeout = setTimeout(() => {
|
|
537
|
-
// SIGTERM 超时,使用 SIGKILL 强制杀死
|
|
538
|
-
logger.warn(`[EventSource ${id}] SIGTERM timeout, sending SIGKILL to ${pidsToKill.length} processes`);
|
|
539
|
-
for (const pid of pidsToKill) {
|
|
540
|
-
killProcess(pid, "SIGKILL");
|
|
541
|
-
}
|
|
542
|
-
resolve();
|
|
543
|
-
}, 3000);
|
|
544
|
-
|
|
545
|
-
// 安全地注册退出监听器
|
|
546
|
-
try {
|
|
547
|
-
const onceResult = child.once?.("exit", () => {
|
|
548
|
-
clearTimeout(timeout);
|
|
549
|
-
// 即使主进程退出,也确保其他相关进程被杀死
|
|
550
|
-
for (const pid of pidsToKill) {
|
|
551
|
-
if (pid !== processInfo.child.pid) {
|
|
552
|
-
killProcess(pid, "SIGKILL");
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
resolve();
|
|
556
|
-
});
|
|
557
|
-
// 如果 once 不返回支持链式调用的对象,也尝试使用 on
|
|
558
|
-
if (!onceResult || typeof onceResult.once !== "function") {
|
|
559
|
-
child.on("exit", () => {
|
|
560
|
-
clearTimeout(timeout);
|
|
561
|
-
for (const pid of pidsToKill) {
|
|
562
|
-
if (pid !== processInfo.child.pid) {
|
|
563
|
-
killProcess(pid, "SIGKILL");
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
resolve();
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
} catch (e) {
|
|
570
|
-
logger.warn(`[EventSource ${id}] failed to register exit listener: ${e}`);
|
|
571
|
-
// 继续执行,让超时处理清理
|
|
572
|
-
}
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
this.processes.delete(id);
|
|
576
|
-
} else if (child) {
|
|
577
|
-
// 兼容旧格式:直接存储 ChildProcess
|
|
578
|
-
// 清理事件监听器,防止内存泄漏
|
|
579
|
-
child.removeAllListeners("exit");
|
|
580
|
-
child.removeAllListeners("error");
|
|
581
|
-
child.stdout?.removeAllListeners("data");
|
|
582
|
-
child.stderr?.removeAllListeners("data");
|
|
583
|
-
|
|
584
|
-
const pid = (child as ChildProcess).pid!;
|
|
585
|
-
const pgid = (child as any).pgid;
|
|
586
|
-
|
|
587
|
-
// 尝试 SIGTERM(优雅终止)
|
|
588
|
-
try {
|
|
589
|
-
// 1. 首先尝试杀死整个进程组
|
|
590
|
-
if (pgid && pgid > 0 && pgid !== pid) {
|
|
591
|
-
logger.info(`[EventSource ${id}] killing process group ${pgid}`);
|
|
592
|
-
try {
|
|
593
|
-
process.kill(-pgid, "SIGTERM");
|
|
594
|
-
} catch (e) {
|
|
595
|
-
logger.warn(`[EventSource ${id}] failed to kill process group: ${e}`);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// 2. 同时杀死主进程
|
|
600
|
-
logger.info(`[EventSource ${id}] killing main process ${pid}`);
|
|
601
|
-
(child as ChildProcess).kill("SIGTERM");
|
|
602
|
-
|
|
603
|
-
// 等待进程退出,最多 3 秒
|
|
604
|
-
await new Promise<void>((resolve) => {
|
|
605
|
-
const timeout = setTimeout(() => {
|
|
606
|
-
// SIGTERM 超时,使用 SIGKILL 强制杀死
|
|
607
|
-
logger.warn(`[EventSource ${id}] SIGTERM timeout, sending SIGKILL`);
|
|
608
|
-
if (pgid && pgid > 0 && pgid !== pid) {
|
|
609
|
-
try {
|
|
610
|
-
process.kill(-pgid, "SIGKILL");
|
|
611
|
-
} catch (e) {
|
|
612
|
-
logger.warn(`[EventSource ${id}] failed to kill process group with SIGKILL: ${e}`);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
(child as ChildProcess).kill("SIGKILL");
|
|
616
|
-
resolve();
|
|
617
|
-
}, 3000);
|
|
618
|
-
|
|
619
|
-
(child as ChildProcess).once("exit", () => {
|
|
620
|
-
clearTimeout(timeout);
|
|
621
|
-
resolve();
|
|
622
|
-
});
|
|
623
|
-
});
|
|
624
|
-
} catch (error) {
|
|
625
|
-
logger.error(`[EventSource ${id}] Failed to kill process:`, error);
|
|
626
|
-
// 确保最终使用 SIGKILL
|
|
627
|
-
try {
|
|
628
|
-
(child as ChildProcess).kill("SIGKILL");
|
|
629
|
-
} catch {
|
|
630
|
-
// ignore
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
this.processes.delete(id);
|
|
635
|
-
} else {
|
|
636
|
-
logger.warn(`[EventSource ${id}] no child process found in processes map`);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// 停止定时器
|
|
640
|
-
const timer = this.timers.get(id);
|
|
641
|
-
if (timer) {
|
|
642
|
-
clearInterval(timer);
|
|
643
|
-
this.timers.delete(id);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// 清理缓冲区
|
|
647
|
-
this.buffers.delete(id);
|
|
648
|
-
|
|
649
|
-
const config = this.sources.get(id);
|
|
650
|
-
this.statuses.set(id, "stopped");
|
|
651
|
-
|
|
652
|
-
// 发布事件
|
|
653
|
-
this.getEnv()?.pushEnvEvent({
|
|
654
|
-
type: "event-source.stopped",
|
|
655
|
-
payload: { sourceId: id, sourceType: config?.type },
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
logger.info(`EventSource stopped: ${id}`);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
// ========================================================================
|
|
662
|
-
// 跨平台进程查找
|
|
663
|
-
// ========================================================================
|
|
664
|
-
|
|
665
|
-
/**
|
|
666
|
-
* 根据命令模式查找所有相关进程 PID
|
|
667
|
-
*
|
|
668
|
-
* @param pattern 命令模式,用于匹配进程
|
|
669
|
-
* @returns 匹配的 PID 列表
|
|
670
|
-
*/
|
|
671
|
-
private async findRelatedProcesses(pattern: string): Promise<number[]> {
|
|
672
|
-
const pids: number[] = [];
|
|
673
|
-
const platform = process.platform;
|
|
674
|
-
|
|
675
|
-
try {
|
|
676
|
-
const exec = getExecAsync();
|
|
677
|
-
|
|
678
|
-
if (platform === "win32") {
|
|
679
|
-
// Windows: 使用 PowerShell
|
|
680
|
-
// 使用 -Command 参数执行 PowerShell 命令
|
|
681
|
-
// Get-Process 获取所有进程,然后用 Where-Object 过滤
|
|
682
|
-
// 注意:需要处理命令注入,使用 -LiteralPath 或适当转义
|
|
683
|
-
const escapedPattern = pattern.replace(/'/g, "''").replace(/"/g, '`"');
|
|
684
|
-
const psCommand = `Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like '*${escapedPattern}*' } | Select-Object -ExpandProperty ProcessId`;
|
|
685
|
-
const { stdout } = await exec(`powershell -Command "${psCommand}"`);
|
|
686
|
-
// 解析输出,提取 PID(处理 \r\n)
|
|
687
|
-
const matches = stdout.replace(/\r\n/g, "\n").match(/\d+/g);
|
|
688
|
-
if (matches) {
|
|
689
|
-
for (const match of matches) {
|
|
690
|
-
const pid = parseInt(match, 10);
|
|
691
|
-
if (!isNaN(pid) && pid > 0) {
|
|
692
|
-
pids.push(pid);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
} else {
|
|
697
|
-
// Unix (Linux/macOS): 使用 pgrep
|
|
698
|
-
// 用引号包裹模式,避免注入
|
|
699
|
-
const { stdout } = await exec(`pgrep -f "${pattern.replace(/"/g, '\\"')}"`);
|
|
700
|
-
const lines = stdout.trim().split("\n");
|
|
701
|
-
for (const line of lines) {
|
|
702
|
-
const trimmed = line.trim();
|
|
703
|
-
if (trimmed) {
|
|
704
|
-
const pid = parseInt(trimmed, 10);
|
|
705
|
-
if (!isNaN(pid) && pid > 0) {
|
|
706
|
-
pids.push(pid);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
} catch (error) {
|
|
712
|
-
// pgrep 在没有匹配进程时返回错误,这是正常的
|
|
713
|
-
logger.debug(`[EventSource] findRelatedProcesses: no processes found for pattern "${pattern}"`);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
return pids;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
/**
|
|
720
|
-
* 查找并记录所有相关进程 PID
|
|
721
|
-
*
|
|
722
|
-
* @param id 事件源 ID
|
|
723
|
-
*/
|
|
724
|
-
private async trackRelatedProcesses(id: string): Promise<void> {
|
|
725
|
-
const processInfo = this.processes.get(id);
|
|
726
|
-
if (!processInfo) return;
|
|
727
|
-
|
|
728
|
-
// 查找匹配命令模式的进程
|
|
729
|
-
// 使用更宽松的模式来匹配所有相关进程
|
|
730
|
-
const command = processInfo?.command || "";
|
|
731
|
-
|
|
732
|
-
// 提取命令的关键部分用于匹配(如 "lark-cli")
|
|
733
|
-
// 这样可以匹配 node lark-cli 和 lark-cli binary 两种进程
|
|
734
|
-
const commandParts = command.split(" ");
|
|
735
|
-
const baseCommand = commandParts[0]; // 如 "lark-cli"
|
|
736
|
-
const eventKeyword = "event"; // 用于进一步匹配事件订阅相关的进程
|
|
737
|
-
|
|
738
|
-
// 先用基础命令查找所有相关进程
|
|
739
|
-
let relatedPids = await this.findRelatedProcesses(baseCommand);
|
|
740
|
-
|
|
741
|
-
// 如果找到的进程太少,再用更完整的模式查找
|
|
742
|
-
if (relatedPids.length < 2) {
|
|
743
|
-
const morePids = await this.findRelatedProcesses(eventKeyword);
|
|
744
|
-
for (const pid of morePids) {
|
|
745
|
-
if (!relatedPids.includes(pid)) {
|
|
746
|
-
relatedPids.push(pid);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
// 过滤掉 roy-agent 自身的进程(通过进程名判断)
|
|
752
|
-
const ownPid = process.pid;
|
|
753
|
-
relatedPids = relatedPids.filter(pid => pid !== ownPid);
|
|
754
|
-
|
|
755
|
-
// 添加到已有的 PID 列表中
|
|
756
|
-
for (const pid of relatedPids) {
|
|
757
|
-
if (!processInfo.pids.includes(pid)) {
|
|
758
|
-
processInfo.pids.push(pid);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// 确保主进程 PID 也在列表中
|
|
763
|
-
const mainPid = processInfo.child.pid;
|
|
764
|
-
if (mainPid && !processInfo.pids.includes(mainPid)) {
|
|
765
|
-
processInfo.pids.unshift(mainPid);
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
logger.info(`[EventSource ${id}] tracked ${processInfo.pids.length} related processes: ${processInfo.pids.join(", ")}`);
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// ========================================================================
|
|
772
|
-
// 事件源启动实现
|
|
773
|
-
// ========================================================================
|
|
774
|
-
|
|
775
|
-
/**
|
|
776
|
-
* 启动 lark-cli 事件源
|
|
777
|
-
* 返回 Promise,只有在进程真正成功启动后才 resolve
|
|
778
|
-
*/
|
|
779
|
-
private startLarkCli(id: string, config: EventSourceConfig): Promise<void> {
|
|
780
|
-
return new Promise((resolve, reject) => {
|
|
781
|
-
const command = config.command || "lark-cli event +subscribe";
|
|
782
|
-
|
|
783
|
-
logger.info(`Starting lark-cli event source: ${id}`);
|
|
784
|
-
logger.debug(`Executing command: ${command}`);
|
|
785
|
-
|
|
786
|
-
// 启动子进程
|
|
787
|
-
// 根据平台选择正确的 shell
|
|
788
|
-
// - Unix (Linux/macOS): 使用 sh
|
|
789
|
-
// - Windows: 使用 cmd.exe
|
|
790
|
-
const isWindows = process.platform === "win32";
|
|
791
|
-
const shell = isWindows ? "cmd.exe" : "sh";
|
|
792
|
-
const shellArgs = isWindows
|
|
793
|
-
? ["/c", command] // Windows: /c 执行命令后退出
|
|
794
|
-
: ["-c", `exec ${command}`]; // Unix: -c 执行命令
|
|
795
|
-
|
|
796
|
-
const child = spawn(shell, shellArgs, {
|
|
797
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
798
|
-
detached: false,
|
|
799
|
-
windowsHide: true,
|
|
800
|
-
});
|
|
801
|
-
|
|
802
|
-
// 存储进程信息,包括主 PID 和后续查找的相关 PID 列表
|
|
803
|
-
this.processes.set(id, {
|
|
804
|
-
child,
|
|
805
|
-
pgid: (child as any).pgid,
|
|
806
|
-
pids: [], // 存储所有相关进程 PID
|
|
807
|
-
command,
|
|
808
|
-
});
|
|
809
|
-
|
|
810
|
-
let startupTimeout: NodeJS.Timeout | null = null;
|
|
811
|
-
let settled = false;
|
|
812
|
-
let startupConfirmed = false;
|
|
813
|
-
|
|
814
|
-
// 启动成功时的处理:标记状态并追踪所有相关进程
|
|
815
|
-
const handleStartupSuccess = async () => {
|
|
816
|
-
if (settled) return;
|
|
817
|
-
settled = true;
|
|
818
|
-
startupConfirmed = true;
|
|
819
|
-
clearTimeout(startupTimeout!);
|
|
820
|
-
this.statuses.set(id, "running");
|
|
821
|
-
logger.info(`[EventSource ${id}] confirmed running`);
|
|
822
|
-
|
|
823
|
-
// 追踪所有相关进程(同步,确保启动后立即记录)
|
|
824
|
-
try {
|
|
825
|
-
await this.trackRelatedProcesses(id);
|
|
826
|
-
} catch (e) {
|
|
827
|
-
logger.warn(`[EventSource ${id}] failed to track related processes: ${e}`);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
resolve();
|
|
831
|
-
};
|
|
832
|
-
|
|
833
|
-
// 启动成功确认:1秒内没有退出且没有错误输出,则认为成功
|
|
834
|
-
startupTimeout = setTimeout(() => {
|
|
835
|
-
if (!settled && !startupConfirmed) {
|
|
836
|
-
handleStartupSuccess();
|
|
837
|
-
}
|
|
838
|
-
}, 1000);
|
|
839
|
-
|
|
840
|
-
// 处理输出流
|
|
841
|
-
child.stdout?.on("data", (data: Buffer) => {
|
|
842
|
-
const output = data.toString();
|
|
843
|
-
|
|
844
|
-
// 处理事件流
|
|
845
|
-
this.processStream(id, output);
|
|
846
|
-
|
|
847
|
-
// 检查是否成功订阅(根据 lark-cli 输出 ok: true)
|
|
848
|
-
if (output.includes('"ok": true') || output.includes('"ok":true')) {
|
|
849
|
-
if (!settled) {
|
|
850
|
-
handleStartupSuccess();
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
});
|
|
854
|
-
|
|
855
|
-
// 处理错误流
|
|
856
|
-
child.stderr?.on("data", (data: Buffer) => {
|
|
857
|
-
const output = data.toString();
|
|
858
|
-
|
|
859
|
-
// 检查是否是致命错误(如 "another instance is running")
|
|
860
|
-
if (output.includes('"ok": false') || output.includes('"ok":false')) {
|
|
861
|
-
if (!settled) {
|
|
862
|
-
settled = true;
|
|
863
|
-
clearTimeout(startupTimeout!);
|
|
864
|
-
this.statuses.set(id, "error");
|
|
865
|
-
child.kill();
|
|
866
|
-
// 提取错误消息
|
|
867
|
-
try {
|
|
868
|
-
const errorObj = JSON.parse(output);
|
|
869
|
-
reject(new Error(errorObj.error?.message || `lark-cli error: ${output}`));
|
|
870
|
-
} catch {
|
|
871
|
-
reject(new Error(`lark-cli error: ${output}`));
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
});
|
|
876
|
-
|
|
877
|
-
// 进程退出处理(只有非正常退出才拒绝)
|
|
878
|
-
child.on("exit", (code) => {
|
|
879
|
-
if (!settled) {
|
|
880
|
-
if (code !== 0 && code !== null) {
|
|
881
|
-
settled = true;
|
|
882
|
-
clearTimeout(startupTimeout!);
|
|
883
|
-
logger.warn(`[EventSource ${id}] exited with code ${code}`);
|
|
884
|
-
this.statuses.set(id, "error");
|
|
885
|
-
reject(new Error(`lark-cli exited with code ${code}`));
|
|
886
|
-
}
|
|
887
|
-
// 如果 code === 0(正常退出但不应该发生),则由 startupTimeout 处理
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
child.on("error", (error) => {
|
|
892
|
-
if (!settled) {
|
|
893
|
-
settled = true;
|
|
894
|
-
clearTimeout(startupTimeout!);
|
|
895
|
-
logger.error(`[EventSource ${id}] process error:`, error);
|
|
896
|
-
this.statuses.set(id, "error");
|
|
897
|
-
reject(error);
|
|
898
|
-
}
|
|
899
|
-
});
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
/**
|
|
904
|
-
* 启动定时器事件源
|
|
905
|
-
*/
|
|
906
|
-
private async startTimer(id: string, config: EventSourceConfig): Promise<void> {
|
|
907
|
-
const interval = config.interval || 60000; // 默认 1 分钟
|
|
908
|
-
|
|
909
|
-
logger.info(`Starting timer event source: ${id}, interval: ${interval}ms`);
|
|
910
|
-
|
|
911
|
-
// 创建定时器
|
|
912
|
-
const timer = setInterval(() => {
|
|
913
|
-
const message = config.options?.message as string || `Timer event from ${config.name}`;
|
|
914
|
-
this.handleEvent(id, JSON.stringify({
|
|
915
|
-
type: "timer.tick",
|
|
916
|
-
payload: {
|
|
917
|
-
message,
|
|
918
|
-
timestamp: Date.now(),
|
|
919
|
-
},
|
|
920
|
-
}));
|
|
921
|
-
}, interval);
|
|
922
|
-
|
|
923
|
-
this.timers.set(id, timer);
|
|
924
|
-
|
|
925
|
-
// 立即触发一次
|
|
926
|
-
this.handleEvent(id, JSON.stringify({
|
|
927
|
-
type: "timer.tick",
|
|
928
|
-
payload: {
|
|
929
|
-
message: config.options?.message as string || `Timer event from ${config.name}`,
|
|
930
|
-
timestamp: Date.now(),
|
|
931
|
-
},
|
|
932
|
-
}));
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
/**
|
|
936
|
-
* 启动 WebSocket 事件源
|
|
937
|
-
*/
|
|
938
|
-
private async startWebSocket(id: string, config: EventSourceConfig): Promise<void> {
|
|
939
|
-
const url = config.url;
|
|
940
|
-
if (!url) {
|
|
941
|
-
throw new Error("WebSocket URL is required");
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
logger.info(`Starting WebSocket event source: ${id}, url: ${url}`);
|
|
945
|
-
|
|
946
|
-
// 动态导入 ws 模块
|
|
947
|
-
try {
|
|
948
|
-
const { WebSocket } = await import("ws");
|
|
949
|
-
|
|
950
|
-
const ws = new WebSocket(url, {
|
|
951
|
-
headers: config.headers,
|
|
952
|
-
});
|
|
953
|
-
|
|
954
|
-
// 将 WebSocket 包装为可关闭的进程
|
|
955
|
-
const wrapper: ProcessInfo = {
|
|
956
|
-
child: {
|
|
957
|
-
kill: () => ws.close(),
|
|
958
|
-
on: (event: string, cb: (...args: unknown[]) => void) => {
|
|
959
|
-
ws.on(event as any, cb as any);
|
|
960
|
-
},
|
|
961
|
-
} as any,
|
|
962
|
-
pids: [],
|
|
963
|
-
};
|
|
964
|
-
|
|
965
|
-
this.processes.set(id, wrapper);
|
|
966
|
-
|
|
967
|
-
ws.on("message", (data: any) => {
|
|
968
|
-
this.processStream(id, data.toString());
|
|
969
|
-
});
|
|
970
|
-
|
|
971
|
-
ws.on("error", (error) => {
|
|
972
|
-
logger.error(`[EventSource ${id}] WebSocket error:`, error);
|
|
973
|
-
});
|
|
974
|
-
|
|
975
|
-
ws.on("close", () => {
|
|
976
|
-
logger.info(`[EventSource ${id}] WebSocket closed`);
|
|
977
|
-
this.statuses.set(id, "stopped");
|
|
978
|
-
});
|
|
979
|
-
|
|
980
|
-
} catch (error) {
|
|
981
|
-
throw new Error(`Failed to import ws module: ${error}`);
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// ========================================================================
|
|
986
|
-
// 事件处理
|
|
987
|
-
// ========================================================================
|
|
988
|
-
|
|
989
|
-
/**
|
|
990
|
-
* 处理数据流
|
|
991
|
-
*/
|
|
992
|
-
private processStream(sourceId: string, data: string): void {
|
|
993
|
-
// 获取或创建缓冲区
|
|
994
|
-
let buffer = this.buffers.get(sourceId) || "";
|
|
995
|
-
buffer += data;
|
|
996
|
-
|
|
997
|
-
// 按行处理 NDJSON
|
|
998
|
-
const lines = buffer.split("\n");
|
|
999
|
-
buffer = lines.pop() || ""; // 保留未完成行
|
|
1000
|
-
this.buffers.set(sourceId, buffer);
|
|
1001
|
-
|
|
1002
|
-
for (const line of lines) {
|
|
1003
|
-
if (line.trim()) {
|
|
1004
|
-
this.handleEvent(sourceId, line);
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
/**
|
|
1010
|
-
* 处理事件
|
|
1011
|
-
*/
|
|
1012
|
-
handleEvent(sourceId: string, rawData: string): void {
|
|
1013
|
-
const config = this.sources.get(sourceId);
|
|
1014
|
-
if (!config) return;
|
|
1015
|
-
|
|
1016
|
-
try {
|
|
1017
|
-
// 解析事件
|
|
1018
|
-
const rawEvent = JSON.parse(rawData);
|
|
1019
|
-
|
|
1020
|
-
// 提取事件类型(新格式优先使用 header.event_type)
|
|
1021
|
-
let eventType: string;
|
|
1022
|
-
if (rawEvent.header?.event_type) {
|
|
1023
|
-
// 新格式:lark-cli 实际输出格式
|
|
1024
|
-
eventType = rawEvent.header.event_type;
|
|
1025
|
-
} else if (rawEvent.schema) {
|
|
1026
|
-
// 旧格式:直接使用 schema
|
|
1027
|
-
eventType = `lark.${rawEvent.schema}`;
|
|
1028
|
-
} else if (rawEvent.type) {
|
|
1029
|
-
// 其他格式
|
|
1030
|
-
eventType = rawEvent.type;
|
|
1031
|
-
} else {
|
|
1032
|
-
eventType = "unknown";
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// 检查事件类型过滤
|
|
1036
|
-
if (config.eventTypes?.length && !this.matchEventType(eventType, config.eventTypes)) {
|
|
1037
|
-
return;
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
// 提取事件元信息、反馈通道和推荐动作
|
|
1041
|
-
const { metadata, replyChannel, recommendedAction } = this.extractMetadata(config.type, rawEvent, eventType);
|
|
1042
|
-
|
|
1043
|
-
// 格式化消息
|
|
1044
|
-
const message = this.formatEventMessage(config.type, rawEvent, eventType);
|
|
1045
|
-
|
|
1046
|
-
// 创建事件对象
|
|
1047
|
-
const event: EventSourceEvent = {
|
|
1048
|
-
sourceId,
|
|
1049
|
-
type: eventType,
|
|
1050
|
-
timestamp: Date.now(),
|
|
1051
|
-
payload: {
|
|
1052
|
-
sourceId,
|
|
1053
|
-
sourceType: config.type,
|
|
1054
|
-
rawEvent,
|
|
1055
|
-
message,
|
|
1056
|
-
metadata,
|
|
1057
|
-
replyChannel,
|
|
1058
|
-
recommendedAction,
|
|
1059
|
-
timestamp: Date.now(),
|
|
1060
|
-
},
|
|
1061
|
-
};
|
|
1062
|
-
|
|
1063
|
-
// 调用处理器
|
|
1064
|
-
const handler = this.handlers.get(sourceId);
|
|
1065
|
-
if (handler) {
|
|
1066
|
-
try {
|
|
1067
|
-
const result = handler(event);
|
|
1068
|
-
if (result instanceof Promise) {
|
|
1069
|
-
result.catch((error) => {
|
|
1070
|
-
logger.error(`[EventSource ${sourceId}] Handler error:`, error);
|
|
1071
|
-
});
|
|
1072
|
-
}
|
|
1073
|
-
} catch (error) {
|
|
1074
|
-
logger.error(`[EventSource ${sourceId}] Handler error:`, error);
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
// 发布到环境事件总线
|
|
1079
|
-
this.getEnv()?.pushEnvEvent({
|
|
1080
|
-
type: `event-source.event.${config.type}`,
|
|
1081
|
-
payload: event,
|
|
1082
|
-
});
|
|
1083
|
-
|
|
1084
|
-
} catch (error) {
|
|
1085
|
-
// JSON 解析失败,尝试作为普通文本处理
|
|
1086
|
-
if (error instanceof SyntaxError) {
|
|
1087
|
-
logger.debug(`[EventSource ${sourceId}] Non-JSON data: ${rawData.substring(0, 100)}`);
|
|
1088
|
-
|
|
1089
|
-
// 作为普通消息处理
|
|
1090
|
-
const config = this.sources.get(sourceId);
|
|
1091
|
-
if (config) {
|
|
1092
|
-
const event: EventSourceEvent = {
|
|
1093
|
-
sourceId,
|
|
1094
|
-
type: "raw",
|
|
1095
|
-
timestamp: Date.now(),
|
|
1096
|
-
payload: {
|
|
1097
|
-
sourceId,
|
|
1098
|
-
sourceType: config.type,
|
|
1099
|
-
rawEvent: rawData,
|
|
1100
|
-
message: rawData,
|
|
1101
|
-
metadata: {},
|
|
1102
|
-
timestamp: Date.now(),
|
|
1103
|
-
},
|
|
1104
|
-
};
|
|
1105
|
-
|
|
1106
|
-
const handler = this.handlers.get(sourceId);
|
|
1107
|
-
if (handler) {
|
|
1108
|
-
handler(event);
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
} else {
|
|
1112
|
-
logger.error(`[EventSource ${sourceId}] Failed to handle event:`, error);
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
/**
|
|
1118
|
-
* 事件类型匹配(支持通配符)
|
|
1119
|
-
*/
|
|
1120
|
-
private matchEventType(eventType: string, patterns: string[]): boolean {
|
|
1121
|
-
for (const pattern of patterns) {
|
|
1122
|
-
if (pattern === "*") return true;
|
|
1123
|
-
if (pattern.endsWith(".*")) {
|
|
1124
|
-
const prefix = pattern.slice(0, -2);
|
|
1125
|
-
if (eventType.startsWith(prefix + ".") || eventType === prefix) return true;
|
|
1126
|
-
}
|
|
1127
|
-
if (eventType === pattern) return true;
|
|
1128
|
-
}
|
|
1129
|
-
return false;
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
/**
|
|
1133
|
-
* 提取事件元信息
|
|
1134
|
-
*/
|
|
1135
|
-
private extractMetadata(sourceType: EventSourceType, rawEvent: unknown, eventType?: string): {
|
|
1136
|
-
metadata: EventMetadata;
|
|
1137
|
-
replyChannel?: ReplyChannel;
|
|
1138
|
-
recommendedAction?: RecommendedAction;
|
|
1139
|
-
} {
|
|
1140
|
-
const event = rawEvent as Record<string, unknown>;
|
|
1141
|
-
const metadata: EventMetadata = {};
|
|
1142
|
-
let replyChannel: ReplyChannel | undefined;
|
|
1143
|
-
let recommendedAction: RecommendedAction | undefined;
|
|
1144
|
-
|
|
1145
|
-
switch (sourceType) {
|
|
1146
|
-
case "lark-cli":
|
|
1147
|
-
// 飞书事件元信息
|
|
1148
|
-
// 消息可能在 event.message(新版)或直接在 message(旧版)
|
|
1149
|
-
const larkInnerEvent = event.event as Record<string, unknown>;
|
|
1150
|
-
const header = event.header as Record<string, unknown>;
|
|
1151
|
-
const message = (larkInnerEvent?.message || event.message) as Record<string, unknown> | undefined;
|
|
1152
|
-
const sender = (larkInnerEvent?.sender || event.sender) as Record<string, unknown> | undefined;
|
|
1153
|
-
const senderId = sender?.sender_id as Record<string, unknown>;
|
|
1154
|
-
|
|
1155
|
-
if (header) {
|
|
1156
|
-
metadata.eventType = header.event_type as string;
|
|
1157
|
-
metadata.appId = header.app_id as string;
|
|
1158
|
-
metadata.tenantKey = header.tenant_key as string;
|
|
1159
|
-
}
|
|
1160
|
-
if (message) {
|
|
1161
|
-
metadata.chatId = message.chat_id as string;
|
|
1162
|
-
metadata.chatType = message.chat_type as string;
|
|
1163
|
-
metadata.messageId = message.message_id as string;
|
|
1164
|
-
metadata.messageType = message.message_type as string;
|
|
1165
|
-
}
|
|
1166
|
-
if (senderId) {
|
|
1167
|
-
metadata.senderId = senderId.open_id as string || senderId.user_id as string || senderId.union_id as string;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
// 飞书消息的反馈通道和推荐动作
|
|
1171
|
-
if (eventType === "im.message.receive_v1") {
|
|
1172
|
-
replyChannel = {
|
|
1173
|
-
type: "lark-cli",
|
|
1174
|
-
appId: metadata.appId,
|
|
1175
|
-
chatId: metadata.chatId,
|
|
1176
|
-
messageId: metadata.messageId,
|
|
1177
|
-
};
|
|
1178
|
-
recommendedAction = {
|
|
1179
|
-
action: "处理飞书消息并回复",
|
|
1180
|
-
replyTo: {
|
|
1181
|
-
appId: metadata.appId,
|
|
1182
|
-
chatId: metadata.chatId,
|
|
1183
|
-
messageId: metadata.messageId,
|
|
1184
|
-
},
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1187
|
-
break;
|
|
1188
|
-
|
|
1189
|
-
case "timer":
|
|
1190
|
-
// 定时器元信息
|
|
1191
|
-
metadata.eventType = "timer.tick";
|
|
1192
|
-
recommendedAction = {
|
|
1193
|
-
action: "执行定时任务",
|
|
1194
|
-
};
|
|
1195
|
-
break;
|
|
1196
|
-
|
|
1197
|
-
case "websocket":
|
|
1198
|
-
// WebSocket 元信息
|
|
1199
|
-
metadata.eventType = eventType;
|
|
1200
|
-
replyChannel = {
|
|
1201
|
-
type: "websocket",
|
|
1202
|
-
};
|
|
1203
|
-
recommendedAction = {
|
|
1204
|
-
action: "处理 WebSocket 消息",
|
|
1205
|
-
};
|
|
1206
|
-
break;
|
|
1207
|
-
|
|
1208
|
-
default:
|
|
1209
|
-
metadata.eventType = eventType;
|
|
1210
|
-
recommendedAction = {
|
|
1211
|
-
action: "处理事件",
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
return { metadata, replyChannel, recommendedAction };
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
/**
|
|
1219
|
-
* 格式化事件消息
|
|
1220
|
-
*/
|
|
1221
|
-
private formatEventMessage(sourceType: EventSourceType, rawEvent: unknown, eventType?: string): string {
|
|
1222
|
-
const event = rawEvent as Record<string, unknown>;
|
|
1223
|
-
|
|
1224
|
-
switch (sourceType) {
|
|
1225
|
-
case "lark-cli": {
|
|
1226
|
-
// 飞书消息格式:message 和 sender 在 event 字段下(不是直接在顶层)
|
|
1227
|
-
const larkEvent = event as Record<string, unknown>;
|
|
1228
|
-
const larkInnerEvent = larkEvent.event as Record<string, unknown> | undefined;
|
|
1229
|
-
|
|
1230
|
-
// 尝试从 event.message 获取消息
|
|
1231
|
-
const larkMessage = (larkInnerEvent?.message || larkEvent.message) as Record<string, unknown> | undefined;
|
|
1232
|
-
const larkSender = (larkInnerEvent?.sender || larkEvent.sender) as Record<string, unknown> | undefined;
|
|
1233
|
-
|
|
1234
|
-
if (larkMessage) {
|
|
1235
|
-
const senderId = larkSender?.sender_id as Record<string, unknown> | undefined;
|
|
1236
|
-
|
|
1237
|
-
// 提取发送者 ID
|
|
1238
|
-
const openId = senderId?.open_id as string ||
|
|
1239
|
-
senderId?.user_id as string ||
|
|
1240
|
-
senderId?.email as string ||
|
|
1241
|
-
senderId?.union_id as string ||
|
|
1242
|
-
"未知用户";
|
|
1243
|
-
|
|
1244
|
-
// 提取消息内容(content 是 JSON 字符串)
|
|
1245
|
-
let content = "无消息内容";
|
|
1246
|
-
if (larkMessage.content) {
|
|
1247
|
-
try {
|
|
1248
|
-
// 尝试解析 JSON 字符串
|
|
1249
|
-
const contentObj = JSON.parse(larkMessage.content as string);
|
|
1250
|
-
content = contentObj.text || contentObj.content || larkMessage.content as string;
|
|
1251
|
-
} catch {
|
|
1252
|
-
// 解析失败,直接使用原始内容
|
|
1253
|
-
content = larkMessage.content as string;
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
return `[飞书消息] ${openId}: ${content}`;
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
// 旧格式处理
|
|
1261
|
-
const larkSenderRecord = larkEvent.sender as Record<string, unknown> | undefined;
|
|
1262
|
-
const senderIdRecord = larkSenderRecord?.sender_id as Record<string, unknown> | undefined;
|
|
1263
|
-
if (larkEvent.message_id || (larkEvent.schema as string)?.includes("message")) {
|
|
1264
|
-
const senderId = senderIdRecord?.open_id as string ||
|
|
1265
|
-
senderIdRecord?.user_id as string ||
|
|
1266
|
-
senderIdRecord?.email as string ||
|
|
1267
|
-
senderIdRecord?.union_id as string ||
|
|
1268
|
-
"未知用户";
|
|
1269
|
-
const content = larkEvent.text || larkEvent.content || JSON.stringify(larkEvent);
|
|
1270
|
-
return `[飞书消息] ${senderId}: ${content}`;
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
// 飞书其他事件
|
|
1274
|
-
return `[飞书事件] ${eventType || larkEvent.schema || "unknown"}`;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
case "timer": {
|
|
1278
|
-
const timerEvent = event as Record<string, unknown>;
|
|
1279
|
-
const timerMessage = (timerEvent.payload as any)?.message || timerEvent.message || "tick";
|
|
1280
|
-
return `[定时任务] ${timerMessage}`;
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
case "websocket":
|
|
1284
|
-
return `[WebSocket] ${JSON.stringify(rawEvent).substring(0, 200)}`;
|
|
1285
|
-
|
|
1286
|
-
default:
|
|
1287
|
-
return `[${sourceType}] ${JSON.stringify(rawEvent).substring(0, 200)}`;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
// ========================================================================
|
|
1292
|
-
// 配置持久化
|
|
1293
|
-
// ========================================================================
|
|
1294
|
-
|
|
1295
|
-
/**
|
|
1296
|
-
* 获取配置文件路径
|
|
1297
|
-
*/
|
|
1298
|
-
private getConfigFilePath(): string {
|
|
1299
|
-
if (this.configPath) {
|
|
1300
|
-
return this.configPath;
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
// 默认路径: ~/.roy-agent/event-sources.json
|
|
1304
|
-
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
1305
|
-
const dataDir = join(home, ".roy-agent");
|
|
1306
|
-
|
|
1307
|
-
if (!existsSync(dataDir)) {
|
|
1308
|
-
mkdirSync(dataDir, { recursive: true });
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
return join(dataDir, "event-sources.json");
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
/**
|
|
1315
|
-
* 持久化配置
|
|
1316
|
-
*/
|
|
1317
|
-
async saveConfig(): Promise<void> {
|
|
1318
|
-
if (!this.persistenceEnabled) {
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
try {
|
|
1323
|
-
const configPath = this.getConfigFilePath();
|
|
1324
|
-
const config: EventSourcePersistenceConfig = {
|
|
1325
|
-
version: "1.0.0",
|
|
1326
|
-
sources: Array.from(this.sources.values()),
|
|
1327
|
-
};
|
|
1328
|
-
|
|
1329
|
-
const content = JSON.stringify(config, null, 2);
|
|
1330
|
-
writeFileSync(configPath, content, "utf-8");
|
|
1331
|
-
|
|
1332
|
-
logger.debug(`Saved ${this.sources.size} event source configurations to ${configPath}`);
|
|
1333
|
-
} catch (error) {
|
|
1334
|
-
logger.error("Failed to save event source configurations:", error);
|
|
1335
|
-
throw error;
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
/**
|
|
1340
|
-
* 加载配置
|
|
1341
|
-
*/
|
|
1342
|
-
async loadConfig(): Promise<void> {
|
|
1343
|
-
if (!this.persistenceEnabled) {
|
|
1344
|
-
return;
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
try {
|
|
1348
|
-
const configPath = this.getConfigFilePath();
|
|
1349
|
-
|
|
1350
|
-
if (!existsSync(configPath)) {
|
|
1351
|
-
logger.debug(`No event source config file found at ${configPath}`);
|
|
1352
|
-
return;
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
const content = readFileSync(configPath, "utf-8");
|
|
1356
|
-
const config: EventSourcePersistenceConfig = JSON.parse(content);
|
|
1357
|
-
|
|
1358
|
-
if (!config.sources || !Array.isArray(config.sources)) {
|
|
1359
|
-
logger.warn("Invalid event source config format, skipping load");
|
|
1360
|
-
return;
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
// 加载所有事件源配置(不覆盖已存在的)
|
|
1364
|
-
let loadedCount = 0;
|
|
1365
|
-
for (const source of config.sources) {
|
|
1366
|
-
if (!this.sources.has(source.id)) {
|
|
1367
|
-
this.sources.set(source.id, source);
|
|
1368
|
-
this.statuses.set(source.id, "created");
|
|
1369
|
-
loadedCount++;
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
logger.info(`Loaded ${loadedCount} event source configurations from ${configPath}`);
|
|
1374
|
-
} catch (error) {
|
|
1375
|
-
logger.error("Failed to load event source configurations:", error);
|
|
1376
|
-
// 不抛出错误,允许组件继续运行
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
}
|