@zooique/memora 2.0.3 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1037 -0
- package/README.en.md +305 -272
- package/README.md +303 -260
- package/config.example.json +43 -0
- package/dist/agent/agent.d.ts +376 -408
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +1166 -926
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/assembler.d.ts +105 -43
- package/dist/agent/assembler.d.ts.map +1 -1
- package/dist/agent/assembler.js +543 -75
- package/dist/agent/assembler.js.map +1 -1
- package/dist/agent/budget.d.ts +131 -0
- package/dist/agent/budget.d.ts.map +1 -0
- package/dist/agent/budget.js +128 -0
- package/dist/agent/budget.js.map +1 -0
- package/dist/agent/builtinToolHandlers.d.ts +225 -38
- package/dist/agent/builtinToolHandlers.d.ts.map +1 -1
- package/dist/agent/builtinToolHandlers.js +748 -132
- package/dist/agent/builtinToolHandlers.js.map +1 -1
- package/dist/agent/builtinTools.d.ts +198 -7
- package/dist/agent/builtinTools.d.ts.map +1 -1
- package/dist/agent/builtinTools.js +646 -14
- package/dist/agent/builtinTools.js.map +1 -1
- package/dist/agent/compaction.d.ts +93 -0
- package/dist/agent/compaction.d.ts.map +1 -0
- package/dist/agent/compaction.js +192 -0
- package/dist/agent/compaction.js.map +1 -0
- package/dist/agent/constants.d.ts +137 -20
- package/dist/agent/constants.d.ts.map +1 -1
- package/dist/agent/constants.js +152 -20
- package/dist/agent/constants.js.map +1 -1
- package/dist/agent/contextManager.d.ts +119 -77
- package/dist/agent/contextManager.d.ts.map +1 -1
- package/dist/agent/contextManager.js +192 -135
- package/dist/agent/contextManager.js.map +1 -1
- package/dist/agent/contextPreparer.d.ts +78 -0
- package/dist/agent/contextPreparer.d.ts.map +1 -0
- package/dist/agent/contextPreparer.js +128 -0
- package/dist/agent/contextPreparer.js.map +1 -0
- package/dist/agent/duplicateInterceptor.d.ts +67 -0
- package/dist/agent/duplicateInterceptor.d.ts.map +1 -0
- package/dist/agent/duplicateInterceptor.js +82 -0
- package/dist/agent/duplicateInterceptor.js.map +1 -0
- package/dist/agent/guardRail.d.ts +166 -0
- package/dist/agent/guardRail.d.ts.map +1 -0
- package/dist/agent/guardRail.js +333 -0
- package/dist/agent/guardRail.js.map +1 -0
- package/dist/agent/loop.d.ts +716 -295
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +2178 -664
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/managers/archiveCoordinator.d.ts +14 -171
- package/dist/agent/managers/archiveCoordinator.d.ts.map +1 -1
- package/dist/agent/managers/archiveCoordinator.js +19 -287
- package/dist/agent/managers/archiveCoordinator.js.map +1 -1
- package/dist/agent/managers/chatLockManager.d.ts +20 -88
- package/dist/agent/managers/chatLockManager.d.ts.map +1 -1
- package/dist/agent/managers/chatLockManager.js +31 -103
- package/dist/agent/managers/chatLockManager.js.map +1 -1
- package/dist/agent/managers/dedupManager.d.ts +31 -115
- package/dist/agent/managers/dedupManager.d.ts.map +1 -1
- package/dist/agent/managers/dedupManager.js +48 -136
- package/dist/agent/managers/dedupManager.js.map +1 -1
- package/dist/agent/managers/goalConsistencyChecker.d.ts +42 -0
- package/dist/agent/managers/goalConsistencyChecker.d.ts.map +1 -0
- package/dist/agent/managers/goalConsistencyChecker.js +178 -0
- package/dist/agent/managers/goalConsistencyChecker.js.map +1 -0
- package/dist/agent/managers/llmCaller.d.ts +124 -0
- package/dist/agent/managers/llmCaller.d.ts.map +1 -0
- package/dist/agent/managers/llmCaller.js +417 -0
- package/dist/agent/managers/llmCaller.js.map +1 -0
- package/dist/agent/managers/llmJudgeHelper.d.ts +8 -25
- package/dist/agent/managers/llmJudgeHelper.d.ts.map +1 -1
- package/dist/agent/managers/llmJudgeHelper.js +7 -24
- package/dist/agent/managers/llmJudgeHelper.js.map +1 -1
- package/dist/agent/managers/loopMetrics.d.ts +58 -0
- package/dist/agent/managers/loopMetrics.d.ts.map +1 -0
- package/dist/agent/managers/loopMetrics.js +68 -0
- package/dist/agent/managers/loopMetrics.js.map +1 -0
- package/dist/agent/managers/memoryAdvisor.d.ts +27 -120
- package/dist/agent/managers/memoryAdvisor.d.ts.map +1 -1
- package/dist/agent/managers/memoryAdvisor.js +85 -207
- package/dist/agent/managers/memoryAdvisor.js.map +1 -1
- package/dist/agent/managers/memoryGovernance.d.ts +10 -42
- package/dist/agent/managers/memoryGovernance.d.ts.map +1 -1
- package/dist/agent/managers/memoryGovernance.js +19 -36
- package/dist/agent/managers/memoryGovernance.js.map +1 -1
- package/dist/agent/managers/memoryInspector.d.ts +59 -268
- package/dist/agent/managers/memoryInspector.d.ts.map +1 -1
- package/dist/agent/managers/memoryInspector.js +91 -442
- package/dist/agent/managers/memoryInspector.js.map +1 -1
- package/dist/agent/managers/roundSummaryGenerator.d.ts +56 -0
- package/dist/agent/managers/roundSummaryGenerator.d.ts.map +1 -0
- package/dist/agent/managers/roundSummaryGenerator.js +201 -0
- package/dist/agent/managers/roundSummaryGenerator.js.map +1 -0
- package/dist/agent/managers/sessionArchiver.d.ts +19 -68
- package/dist/agent/managers/sessionArchiver.d.ts.map +1 -1
- package/dist/agent/managers/sessionArchiver.js +76 -147
- package/dist/agent/managers/sessionArchiver.js.map +1 -1
- package/dist/agent/managers/sessionManager.d.ts +286 -57
- package/dist/agent/managers/sessionManager.d.ts.map +1 -1
- package/dist/agent/managers/sessionManager.js +846 -85
- package/dist/agent/managers/sessionManager.js.map +1 -1
- package/dist/agent/managers/sessionNamer.d.ts +35 -0
- package/dist/agent/managers/sessionNamer.d.ts.map +1 -0
- package/dist/agent/managers/sessionNamer.js +104 -0
- package/dist/agent/managers/sessionNamer.js.map +1 -0
- package/dist/agent/managers/streamAccumulator.d.ts +8 -33
- package/dist/agent/managers/streamAccumulator.d.ts.map +1 -1
- package/dist/agent/managers/streamAccumulator.js +4 -29
- package/dist/agent/managers/streamAccumulator.js.map +1 -1
- package/dist/agent/managers/textPolishManager.d.ts +8 -44
- package/dist/agent/managers/textPolishManager.d.ts.map +1 -1
- package/dist/agent/managers/textPolishManager.js +9 -41
- package/dist/agent/managers/textPolishManager.js.map +1 -1
- package/dist/agent/managers/toolCallHelpers.d.ts +105 -0
- package/dist/agent/managers/toolCallHelpers.d.ts.map +1 -0
- package/dist/agent/managers/toolCallHelpers.js +161 -0
- package/dist/agent/managers/toolCallHelpers.js.map +1 -0
- package/dist/agent/managers/workProjection.d.ts +55 -100
- package/dist/agent/managers/workProjection.d.ts.map +1 -1
- package/dist/agent/managers/workProjection.js +164 -268
- package/dist/agent/managers/workProjection.js.map +1 -1
- package/dist/agent/messageHistory.d.ts +134 -84
- package/dist/agent/messageHistory.d.ts.map +1 -1
- package/dist/agent/messageHistory.js +419 -133
- package/dist/agent/messageHistory.js.map +1 -1
- package/dist/agent/needsPlanning.d.ts +28 -0
- package/dist/agent/needsPlanning.d.ts.map +1 -0
- package/dist/agent/needsPlanning.js +68 -0
- package/dist/agent/needsPlanning.js.map +1 -0
- package/dist/agent/seed/index.d.ts +11 -0
- package/dist/agent/seed/index.d.ts.map +1 -0
- package/dist/agent/seed/index.js +10 -0
- package/dist/agent/seed/index.js.map +1 -0
- package/dist/agent/seed/orchestrator.d.ts +83 -0
- package/dist/agent/seed/orchestrator.d.ts.map +1 -0
- package/dist/agent/seed/orchestrator.js +260 -0
- package/dist/agent/seed/orchestrator.js.map +1 -0
- package/dist/agent/seed/prepare.d.ts +49 -0
- package/dist/agent/seed/prepare.d.ts.map +1 -0
- package/dist/agent/seed/prepare.js +107 -0
- package/dist/agent/seed/prepare.js.map +1 -0
- package/dist/agent/seed/types.d.ts +115 -0
- package/dist/agent/seed/types.d.ts.map +1 -0
- package/dist/agent/seed/types.js +15 -0
- package/dist/agent/seed/types.js.map +1 -0
- package/dist/agent/sessionStateMachine.d.ts +78 -0
- package/dist/agent/sessionStateMachine.d.ts.map +1 -0
- package/dist/agent/sessionStateMachine.js +182 -0
- package/dist/agent/sessionStateMachine.js.map +1 -0
- package/dist/agent/taskTableRenderer.d.ts +51 -0
- package/dist/agent/taskTableRenderer.d.ts.map +1 -0
- package/dist/agent/taskTableRenderer.js +115 -0
- package/dist/agent/taskTableRenderer.js.map +1 -0
- package/dist/agent/toolExecutor.d.ts +236 -10
- package/dist/agent/toolExecutor.d.ts.map +1 -1
- package/dist/agent/toolExecutor.js +932 -21
- package/dist/agent/toolExecutor.js.map +1 -1
- package/dist/agent/toolLedger.d.ts +120 -0
- package/dist/agent/toolLedger.d.ts.map +1 -0
- package/dist/agent/toolLedger.js +149 -0
- package/dist/agent/toolLedger.js.map +1 -0
- package/dist/agent/toolResultCache.d.ts +134 -0
- package/dist/agent/toolResultCache.d.ts.map +1 -0
- package/dist/agent/toolResultCache.js +243 -0
- package/dist/agent/toolResultCache.js.map +1 -0
- package/dist/agent/toolResultOffload.d.ts +59 -0
- package/dist/agent/toolResultOffload.d.ts.map +1 -0
- package/dist/agent/toolResultOffload.js +70 -0
- package/dist/agent/toolResultOffload.js.map +1 -0
- package/dist/agent/toolRunner.d.ts +59 -0
- package/dist/agent/toolRunner.d.ts.map +1 -0
- package/dist/agent/toolRunner.js +147 -0
- package/dist/agent/toolRunner.js.map +1 -0
- package/dist/agent/tracer.d.ts +99 -107
- package/dist/agent/tracer.d.ts.map +1 -1
- package/dist/agent/tracer.js +24 -69
- package/dist/agent/tracer.js.map +1 -1
- package/dist/agent/types.d.ts +510 -169
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js +2 -51
- package/dist/agent/types.js.map +1 -1
- package/dist/code-exec/codeExecutionProvider.d.ts +16 -0
- package/dist/code-exec/codeExecutionProvider.d.ts.map +1 -0
- package/dist/code-exec/codeExecutionProvider.js +57 -0
- package/dist/code-exec/codeExecutionProvider.js.map +1 -0
- package/dist/code-exec/types.d.ts +37 -0
- package/dist/code-exec/types.d.ts.map +1 -0
- package/dist/code-exec/types.js +12 -0
- package/dist/code-exec/types.js.map +1 -0
- package/dist/config/expandEnvVars.d.ts +22 -0
- package/dist/config/expandEnvVars.d.ts.map +1 -0
- package/dist/config/expandEnvVars.js +38 -0
- package/dist/config/expandEnvVars.js.map +1 -0
- package/dist/config/loader.d.ts +27 -98
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +110 -223
- package/dist/config/loader.js.map +1 -1
- package/dist/index.d.ts +67 -77
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +90 -69
- package/dist/index.js.map +1 -1
- package/dist/llm/abortSignal.d.ts +2 -3
- package/dist/llm/abortSignal.d.ts.map +1 -1
- package/dist/llm/abortSignal.js +2 -3
- package/dist/llm/abortSignal.js.map +1 -1
- package/dist/llm/factory.d.ts +22 -32
- package/dist/llm/factory.d.ts.map +1 -1
- package/dist/llm/factory.js +29 -46
- package/dist/llm/factory.js.map +1 -1
- package/dist/llm/openaiCompatible.d.ts +25 -40
- package/dist/llm/openaiCompatible.d.ts.map +1 -1
- package/dist/llm/openaiCompatible.js +179 -120
- package/dist/llm/openaiCompatible.js.map +1 -1
- package/dist/llm/provider.d.ts +29 -42
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/llm/provider.js +11 -10
- package/dist/llm/provider.js.map +1 -1
- package/dist/llm/types.d.ts +54 -11
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/llm/types.js +0 -5
- package/dist/llm/types.js.map +1 -1
- package/dist/logging/logger.d.ts +8 -31
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +28 -215
- package/dist/logging/logger.js.map +1 -1
- package/dist/logging/loggerInterface.d.ts +9 -8
- package/dist/logging/loggerInterface.d.ts.map +1 -1
- package/dist/memory/gcService.d.ts +164 -0
- package/dist/memory/gcService.d.ts.map +1 -0
- package/dist/memory/gcService.js +215 -0
- package/dist/memory/gcService.js.map +1 -0
- package/dist/memory/governance.d.ts +11 -44
- package/dist/memory/governance.d.ts.map +1 -1
- package/dist/memory/governance.js +11 -64
- package/dist/memory/governance.js.map +1 -1
- package/dist/memory/inMemoryRoundStore.d.ts +124 -0
- package/dist/memory/inMemoryRoundStore.d.ts.map +1 -0
- package/dist/memory/inMemoryRoundStore.js +216 -0
- package/dist/memory/inMemoryRoundStore.js.map +1 -0
- package/dist/memory/inMemorySessionStore.d.ts +92 -0
- package/dist/memory/inMemorySessionStore.d.ts.map +1 -0
- package/dist/memory/inMemorySessionStore.js +219 -0
- package/dist/memory/inMemorySessionStore.js.map +1 -0
- package/dist/memory/inMemorySessionViewLoader.d.ts +89 -0
- package/dist/memory/inMemorySessionViewLoader.d.ts.map +1 -0
- package/dist/memory/inMemorySessionViewLoader.js +183 -0
- package/dist/memory/inMemorySessionViewLoader.js.map +1 -0
- package/dist/memory/inMemoryStorage.d.ts +25 -175
- package/dist/memory/inMemoryStorage.d.ts.map +1 -1
- package/dist/memory/inMemoryStorage.js +32 -221
- package/dist/memory/inMemoryStorage.js.map +1 -1
- package/dist/memory/keywordsTouch.d.ts +20 -0
- package/dist/memory/keywordsTouch.d.ts.map +1 -0
- package/dist/memory/keywordsTouch.js +25 -0
- package/dist/memory/keywordsTouch.js.map +1 -0
- package/dist/memory/lockManager.d.ts +2 -2
- package/dist/memory/lockManager.d.ts.map +1 -1
- package/dist/memory/lockManager.js +6 -8
- package/dist/memory/lockManager.js.map +1 -1
- package/dist/memory/projectManager.d.ts +30 -129
- package/dist/memory/projectManager.d.ts.map +1 -1
- package/dist/memory/projectManager.js +39 -189
- package/dist/memory/projectManager.js.map +1 -1
- package/dist/memory/projectRegistry.d.ts +14 -62
- package/dist/memory/projectRegistry.d.ts.map +1 -1
- package/dist/memory/projectRegistry.js +27 -111
- package/dist/memory/projectRegistry.js.map +1 -1
- package/dist/memory/roundStore.d.ts +629 -0
- package/dist/memory/roundStore.d.ts.map +1 -0
- package/dist/memory/roundStore.js +116 -0
- package/dist/memory/roundStore.js.map +1 -0
- package/dist/memory/sessionStore.d.ts +70 -61
- package/dist/memory/sessionStore.d.ts.map +1 -1
- package/dist/memory/sessionStore.js +23 -1
- package/dist/memory/sessionStore.js.map +1 -1
- package/dist/memory/sessionViewLoader.d.ts +141 -0
- package/dist/memory/sessionViewLoader.d.ts.map +1 -0
- package/dist/memory/sessionViewLoader.js +65 -0
- package/dist/memory/sessionViewLoader.js.map +1 -0
- package/dist/memory/sourceValidation.d.ts +10 -12
- package/dist/memory/sourceValidation.d.ts.map +1 -1
- package/dist/memory/sourceValidation.js +16 -32
- package/dist/memory/sourceValidation.js.map +1 -1
- package/dist/memory/storageInterface.d.ts +44 -150
- package/dist/memory/storageInterface.d.ts.map +1 -1
- package/dist/memory/types.d.ts +65 -164
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/memory/types.js +81 -83
- package/dist/memory/types.js.map +1 -1
- package/dist/project-search/projectSearchProvider.d.ts +29 -0
- package/dist/project-search/projectSearchProvider.d.ts.map +1 -0
- package/dist/project-search/projectSearchProvider.js +57 -0
- package/dist/project-search/projectSearchProvider.js.map +1 -0
- package/dist/project-search/terms.d.ts +14 -0
- package/dist/project-search/terms.d.ts.map +1 -0
- package/dist/project-search/terms.js +68 -0
- package/dist/project-search/terms.js.map +1 -0
- package/dist/project-search/types.d.ts +139 -0
- package/dist/project-search/types.d.ts.map +1 -0
- package/dist/project-search/types.js +8 -0
- package/dist/project-search/types.js.map +1 -0
- package/dist/role-pack/capabilityMap.d.ts +28 -0
- package/dist/role-pack/capabilityMap.d.ts.map +1 -0
- package/dist/role-pack/capabilityMap.js +56 -0
- package/dist/role-pack/capabilityMap.js.map +1 -0
- package/dist/role-pack/constants.d.ts +21 -0
- package/dist/role-pack/constants.d.ts.map +1 -0
- package/dist/role-pack/constants.js +21 -0
- package/dist/role-pack/constants.js.map +1 -0
- package/dist/role-pack/rolePackManager.d.ts +269 -0
- package/dist/role-pack/rolePackManager.d.ts.map +1 -0
- package/dist/role-pack/rolePackManager.js +1082 -0
- package/dist/role-pack/rolePackManager.js.map +1 -0
- package/dist/role-pack/strategyKeys.d.ts +100 -0
- package/dist/role-pack/strategyKeys.d.ts.map +1 -0
- package/dist/role-pack/strategyKeys.js +184 -0
- package/dist/role-pack/strategyKeys.js.map +1 -0
- package/dist/role-pack/strategyResolver.d.ts +109 -0
- package/dist/role-pack/strategyResolver.d.ts.map +1 -0
- package/dist/role-pack/strategyResolver.js +304 -0
- package/dist/role-pack/strategyResolver.js.map +1 -0
- package/dist/role-pack/types.d.ts +237 -0
- package/dist/role-pack/types.d.ts.map +1 -0
- package/dist/role-pack/types.js +8 -0
- package/dist/role-pack/types.js.map +1 -0
- package/dist/role-pack/validator.d.ts +38 -0
- package/dist/role-pack/validator.d.ts.map +1 -0
- package/dist/role-pack/validator.js +436 -0
- package/dist/role-pack/validator.js.map +1 -0
- package/dist/security/pathGuard.d.ts +69 -72
- package/dist/security/pathGuard.d.ts.map +1 -1
- package/dist/security/pathGuard.js +151 -131
- package/dist/security/pathGuard.js.map +1 -1
- package/dist/skill/skillLayer3.d.ts +91 -0
- package/dist/skill/skillLayer3.d.ts.map +1 -0
- package/dist/skill/skillLayer3.js +104 -0
- package/dist/skill/skillLayer3.js.map +1 -0
- package/dist/skill/skillManager.d.ts +136 -19
- package/dist/skill/skillManager.d.ts.map +1 -1
- package/dist/skill/skillManager.js +312 -61
- package/dist/skill/skillManager.js.map +1 -1
- package/dist/skill/skillScriptRunner.d.ts +97 -0
- package/dist/skill/skillScriptRunner.d.ts.map +1 -0
- package/dist/skill/skillScriptRunner.js +271 -0
- package/dist/skill/skillScriptRunner.js.map +1 -0
- package/dist/skill/types.d.ts +73 -16
- package/dist/skill/types.d.ts.map +1 -1
- package/dist/skill/types.js +5 -1
- package/dist/skill/types.js.map +1 -1
- package/dist/utils/array.d.ts +5 -6
- package/dist/utils/array.d.ts.map +1 -1
- package/dist/utils/array.js +6 -6
- package/dist/utils/array.js.map +1 -1
- package/dist/utils/atomicWrite.d.ts +8 -0
- package/dist/utils/atomicWrite.d.ts.map +1 -0
- package/dist/utils/atomicWrite.js +31 -0
- package/dist/utils/atomicWrite.js.map +1 -0
- package/dist/utils/backgroundTask.d.ts +38 -0
- package/dist/utils/backgroundTask.d.ts.map +1 -0
- package/dist/utils/backgroundTask.js +117 -0
- package/dist/utils/backgroundTask.js.map +1 -0
- package/dist/utils/configResourceManager.d.ts +40 -72
- package/dist/utils/configResourceManager.d.ts.map +1 -1
- package/dist/utils/configResourceManager.js +78 -76
- package/dist/utils/configResourceManager.js.map +1 -1
- package/dist/utils/errors.d.ts +44 -69
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +54 -71
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/eventEmitter.d.ts +104 -103
- package/dist/utils/eventEmitter.d.ts.map +1 -1
- package/dist/utils/eventEmitter.js +37 -45
- package/dist/utils/eventEmitter.js.map +1 -1
- package/dist/utils/fileSafe.d.ts +20 -0
- package/dist/utils/fileSafe.d.ts.map +1 -0
- package/dist/utils/fileSafe.js +56 -0
- package/dist/utils/fileSafe.js.map +1 -0
- package/dist/utils/frontmatter.d.ts +2 -3
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +2 -3
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/hash.d.ts +12 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +27 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/http.d.ts +17 -0
- package/dist/utils/http.d.ts.map +1 -0
- package/dist/utils/http.js +29 -0
- package/dist/utils/http.js.map +1 -0
- package/dist/utils/json.d.ts +1 -1
- package/dist/utils/json.js +1 -1
- package/dist/utils/math.d.ts +25 -3
- package/dist/utils/math.d.ts.map +1 -1
- package/dist/utils/math.js +32 -16
- package/dist/utils/math.js.map +1 -1
- package/dist/utils/objects.d.ts +2 -3
- package/dist/utils/objects.d.ts.map +1 -1
- package/dist/utils/objects.js +2 -3
- package/dist/utils/objects.js.map +1 -1
- package/dist/utils/recallDefaults.d.ts +16 -0
- package/dist/utils/recallDefaults.d.ts.map +1 -0
- package/dist/utils/recallDefaults.js +16 -0
- package/dist/utils/recallDefaults.js.map +1 -0
- package/dist/utils/safeTimer.d.ts +8 -36
- package/dist/utils/safeTimer.d.ts.map +1 -1
- package/dist/utils/safeTimer.js +9 -37
- package/dist/utils/safeTimer.js.map +1 -1
- package/dist/utils/scanner.d.ts +97 -17
- package/dist/utils/scanner.d.ts.map +1 -1
- package/dist/utils/scanner.js +269 -47
- package/dist/utils/scanner.js.map +1 -1
- package/dist/utils/segmenter.d.ts +20 -19
- package/dist/utils/segmenter.d.ts.map +1 -1
- package/dist/utils/segmenter.js +230 -97
- package/dist/utils/segmenter.js.map +1 -1
- package/dist/utils/strings.d.ts +15 -32
- package/dist/utils/strings.d.ts.map +1 -1
- package/dist/utils/strings.js +25 -44
- package/dist/utils/strings.js.map +1 -1
- package/dist/utils/time.d.ts +37 -0
- package/dist/utils/time.d.ts.map +1 -1
- package/dist/utils/time.js +56 -0
- package/dist/utils/time.js.map +1 -1
- package/dist/utils/toError.d.ts +13 -1
- package/dist/utils/toError.d.ts.map +1 -1
- package/dist/utils/toError.js +18 -1
- package/dist/utils/toError.js.map +1 -1
- package/dist/web-fetch/fetchWebFetchProvider.d.ts +14 -0
- package/dist/web-fetch/fetchWebFetchProvider.d.ts.map +1 -0
- package/dist/web-fetch/fetchWebFetchProvider.js +69 -0
- package/dist/web-fetch/fetchWebFetchProvider.js.map +1 -0
- package/dist/web-fetch/types.d.ts +30 -0
- package/dist/web-fetch/types.d.ts.map +1 -0
- package/dist/web-fetch/types.js +7 -0
- package/dist/web-fetch/types.js.map +1 -0
- package/dist/web-fetch/webFetchProvider.d.ts +10 -0
- package/dist/web-fetch/webFetchProvider.d.ts.map +1 -0
- package/dist/web-fetch/webFetchProvider.js +28 -0
- package/dist/web-fetch/webFetchProvider.js.map +1 -0
- package/dist/web-search/fetchWebSearchProvider.d.ts +27 -0
- package/dist/web-search/fetchWebSearchProvider.d.ts.map +1 -0
- package/dist/web-search/fetchWebSearchProvider.js +199 -0
- package/dist/web-search/fetchWebSearchProvider.js.map +1 -0
- package/dist/web-search/types.d.ts +46 -0
- package/dist/web-search/types.d.ts.map +1 -0
- package/dist/web-search/types.js +2 -0
- package/dist/{persona → web-search}/types.js.map +1 -1
- package/dist/web-search/webSearchProvider.d.ts +10 -0
- package/dist/web-search/webSearchProvider.d.ts.map +1 -0
- package/dist/web-search/webSearchProvider.js +30 -0
- package/dist/web-search/webSearchProvider.js.map +1 -0
- package/docs/architecture/role-pack-authoring-guide.md +243 -0
- package/docs/architecture/role-pack-spec.md +767 -0
- package/docs/memora-api-reference.md +416 -476
- package/docs/memora-/346/216/245/345/205/245/346/214/207/345/215/227.md +841 -806
- package/docs/role-pack-/345/274/200/346/224/276/351/224/256/346/214/207/345/215/227.md +148 -0
- package/package.json +105 -100
- package/role-packs/README.md +41 -0
- package/role-packs/memora/345/212/251/346/211/213/manifest.json +12 -0
- package/role-packs/memora/345/212/251/346/211/213/persona.md +12 -0
- package/role-packs/memora/345/212/251/346/211/213/rules.md +4 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/manifest.json +44 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/persona.md +46 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/rules.md +15 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/character-sheet.md +34 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/core-extractor.md +37 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/craft-review.md +69 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/dialogue-craft.md +45 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/emotion-wave.md +46 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/empathy-craft.md +79 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/foreshadow.md +29 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/hotspot-mapping.md +121 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/idea-lever.md +42 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/logline-craft.md +47 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/longform-guard.md +64 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/need-finder.md +40 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/outline-expand.md +46 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/pacing-hook.md +34 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/polish-method.md +113 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/pov-discipline.md +79 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/project-init/SKILL.md +112 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/project-init/scripts/init-novel-project.mjs +441 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/relationship-web.md +47 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/scene-craft.md +46 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/story-bible.md +60 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/story-structure.md +36 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/style-weaver.md +109 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/three-layer-check.md +27 -0
- package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/world-builder.md +41 -0
- package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/manifest.json +48 -0
- package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/persona.md +73 -0
- package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/rules.md +19 -0
- package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/skills/api-doc-writer.md +40 -0
- package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/skills/doc-planner.md +42 -0
- package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/skills/nutrient-search.md +28 -0
- package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/skills/seed-finder.md +28 -0
- package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/skills/ssot-audit.md +34 -0
- package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/skills/style-guide.md +40 -0
- package/dist/agent/guardrail.d.ts +0 -49
- package/dist/agent/guardrail.d.ts.map +0 -1
- package/dist/agent/guardrail.js +0 -59
- package/dist/agent/guardrail.js.map +0 -1
- package/dist/agent/managers/autoConfigRefiner.d.ts +0 -39
- package/dist/agent/managers/autoConfigRefiner.d.ts.map +0 -1
- package/dist/agent/managers/autoConfigRefiner.js +0 -146
- package/dist/agent/managers/autoConfigRefiner.js.map +0 -1
- package/dist/agent/managers/configManager.d.ts +0 -209
- package/dist/agent/managers/configManager.d.ts.map +0 -1
- package/dist/agent/managers/configManager.js +0 -331
- package/dist/agent/managers/configManager.js.map +0 -1
- package/dist/agent/managers/insightExtractor.d.ts +0 -139
- package/dist/agent/managers/insightExtractor.d.ts.map +0 -1
- package/dist/agent/managers/insightExtractor.js +0 -401
- package/dist/agent/managers/insightExtractor.js.map +0 -1
- package/dist/agent/managers/memoryDecayScheduler.d.ts +0 -239
- package/dist/agent/managers/memoryDecayScheduler.d.ts.map +0 -1
- package/dist/agent/managers/memoryDecayScheduler.js +0 -413
- package/dist/agent/managers/memoryDecayScheduler.js.map +0 -1
- package/dist/agent/managers/relationBuilder.d.ts +0 -131
- package/dist/agent/managers/relationBuilder.d.ts.map +0 -1
- package/dist/agent/managers/relationBuilder.js +0 -192
- package/dist/agent/managers/relationBuilder.js.map +0 -1
- package/dist/agent/personaMatcher.d.ts +0 -29
- package/dist/agent/personaMatcher.d.ts.map +0 -1
- package/dist/agent/personaMatcher.js +0 -60
- package/dist/agent/personaMatcher.js.map +0 -1
- package/dist/agent/userFactExtractor.d.ts +0 -25
- package/dist/agent/userFactExtractor.d.ts.map +0 -1
- package/dist/agent/userFactExtractor.js +0 -88
- package/dist/agent/userFactExtractor.js.map +0 -1
- package/dist/eval/evalRunner.d.ts +0 -94
- package/dist/eval/evalRunner.d.ts.map +0 -1
- package/dist/eval/evalRunner.js +0 -141
- package/dist/eval/evalRunner.js.map +0 -1
- package/dist/eval/evalTypes.d.ts +0 -148
- package/dist/eval/evalTypes.d.ts.map +0 -1
- package/dist/eval/evalTypes.js +0 -118
- package/dist/eval/evalTypes.js.map +0 -1
- package/dist/eval/scenarios.d.ts +0 -33
- package/dist/eval/scenarios.d.ts.map +0 -1
- package/dist/eval/scenarios.js +0 -109
- package/dist/eval/scenarios.js.map +0 -1
- package/dist/llm/embedding.d.ts +0 -79
- package/dist/llm/embedding.d.ts.map +0 -1
- package/dist/llm/embedding.js +0 -184
- package/dist/llm/embedding.js.map +0 -1
- package/dist/memory/hybridMerge.d.ts +0 -50
- package/dist/memory/hybridMerge.d.ts.map +0 -1
- package/dist/memory/hybridMerge.js +0 -51
- package/dist/memory/hybridMerge.js.map +0 -1
- package/dist/memory/inMemoryRelationStore.d.ts +0 -58
- package/dist/memory/inMemoryRelationStore.d.ts.map +0 -1
- package/dist/memory/inMemoryRelationStore.js +0 -80
- package/dist/memory/inMemoryRelationStore.js.map +0 -1
- package/dist/memory/loader.d.ts +0 -51
- package/dist/memory/loader.d.ts.map +0 -1
- package/dist/memory/loader.js +0 -100
- package/dist/memory/loader.js.map +0 -1
- package/dist/memory/recall.d.ts +0 -82
- package/dist/memory/recall.d.ts.map +0 -1
- package/dist/memory/recall.js +0 -157
- package/dist/memory/recall.js.map +0 -1
- package/dist/memory/relationStore.d.ts +0 -95
- package/dist/memory/relationStore.d.ts.map +0 -1
- package/dist/memory/relationStore.js +0 -2
- package/dist/memory/relationStore.js.map +0 -1
- package/dist/memory/store.d.ts +0 -71
- package/dist/memory/store.d.ts.map +0 -1
- package/dist/memory/store.js +0 -199
- package/dist/memory/store.js.map +0 -1
- package/dist/memory/userProfile.d.ts +0 -180
- package/dist/memory/userProfile.d.ts.map +0 -1
- package/dist/memory/userProfile.js +0 -381
- package/dist/memory/userProfile.js.map +0 -1
- package/dist/memory/vectorStore.d.ts +0 -179
- package/dist/memory/vectorStore.d.ts.map +0 -1
- package/dist/memory/vectorStore.js +0 -284
- package/dist/memory/vectorStore.js.map +0 -1
- package/dist/persona/personaManager.d.ts +0 -109
- package/dist/persona/personaManager.d.ts.map +0 -1
- package/dist/persona/personaManager.js +0 -315
- package/dist/persona/personaManager.js.map +0 -1
- package/dist/persona/types.d.ts +0 -32
- package/dist/persona/types.d.ts.map +0 -1
- package/dist/persona/types.js +0 -5
package/dist/agent/agent.js
CHANGED
|
@@ -1,75 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent 门面类 — Memora 宿主项目接入入口
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 使用方式(最简):
|
|
9
|
-
* const agent = new Agent({ projectPath: './my-project', configDir: './agent-config' });
|
|
10
|
-
* await agent.init();
|
|
11
|
-
* for await (const chunk of agent.chat('你好')) {
|
|
12
|
-
* if (chunk.type === 'text') process.stdout.write(chunk.content);
|
|
13
|
-
* }
|
|
14
|
-
* await agent.close();
|
|
15
|
-
*
|
|
16
|
-
* 使用方式(高级):
|
|
17
|
-
* const agent = new Agent({ projectPath: './my-project', provider: myProvider, configDir: './agent-config' });
|
|
18
|
-
*
|
|
19
|
-
* 2026-06-12 God Object 拆分:
|
|
20
|
-
* - Insight 提取 → InsightExtractor
|
|
21
|
-
* - 配置管理 → ConfigManager
|
|
22
|
-
* - 记忆查看 → MemoryInspector
|
|
23
|
-
* - 薄包装方法移除,调用方改为 agent.<manager>.xxx()
|
|
4
|
+
* 宿主通过 `import { Agent } from '@zooique/memora'` 一行接入。
|
|
5
|
+
* 负责组件组装和核心对话编排,领域专属操作委托给专职 Manager
|
|
6
|
+
* (RolePackManager / ToolExecutor / SkillManager / MemoryInspector)。
|
|
24
7
|
*/
|
|
8
|
+
import { isAbsolute } from 'node:path';
|
|
25
9
|
import { getBaseName } from '../utils/path.js';
|
|
26
10
|
import { AGENT_CONSTANTS } from '../agent/constants.js';
|
|
27
11
|
import { ProjectManager } from '../memory/projectManager.js';
|
|
28
12
|
import { SecurityGuard } from '../security/pathGuard.js';
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
13
|
+
import { DEFAULT_BEHAVIOR_STRATEGY } from '../role-pack/strategyResolver.js';
|
|
14
|
+
import { isTemperature, MAX_OUTPUT_LIMIT } from '../role-pack/strategyKeys.js';
|
|
15
|
+
import { resolveCapabilityTools } from '../role-pack/capabilityMap.js';
|
|
16
|
+
import { SessionNamer } from '../agent/managers/sessionNamer.js';
|
|
17
|
+
import { createDefaultGCService } from '../memory/gcService.js';
|
|
18
|
+
import { assembleComponents, buildSystemPromptPrefix } from '../agent/assembler.js';
|
|
19
|
+
import { estimateTokensMessages } from '../agent/contextManager.js';
|
|
20
|
+
import { SeedOrchestrator } from '../agent/seed/index.js';
|
|
21
|
+
import { chatBusyError, configError, isAbortError, isTimeoutAbortSignal, isTimeoutError, } from '../utils/errors.js';
|
|
22
|
+
import { toError } from '../utils/toError.js';
|
|
34
23
|
import { ChatLockManager } from '../agent/managers/chatLockManager.js';
|
|
35
|
-
import { MemoryDecayScheduler } from '../agent/managers/memoryDecayScheduler.js';
|
|
36
24
|
import { MemoryGovernance } from '../agent/managers/memoryGovernance.js';
|
|
37
|
-
import { ArchiveCoordinator } from '../agent/managers/archiveCoordinator.js';
|
|
38
|
-
import { TypedEventEmitter, AGENT_EVENTS, AGENT_EVENT_SET } from '../utils/eventEmitter.js';
|
|
25
|
+
import { ArchiveCoordinator, } from '../agent/managers/archiveCoordinator.js';
|
|
26
|
+
import { TypedEventEmitter, AGENT_EVENTS, AGENT_EVENT_SET, } from '../utils/eventEmitter.js';
|
|
39
27
|
import { logger } from '../logging/logger.js';
|
|
40
|
-
import {
|
|
28
|
+
import { getBackgroundTaskStats as readBackgroundTaskStats, awaitBackgroundTasks, } from '../utils/backgroundTask.js';
|
|
41
29
|
// ─── 模块级常量 ─────────────────────────────────────────
|
|
42
|
-
// AGENT_EVENTS / AGENT_EVENT_SET 已迁移至 utils/eventEmitter.ts(与 AgentEventMap 同处,单一真理源)
|
|
43
30
|
// ─── Agent 门面类 ───────────────────────────────────────
|
|
44
31
|
/**
|
|
45
32
|
* Memora Agent 门面类
|
|
46
33
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* **多实例**:宿主可通过 new Agent({ dataDir: './agent2' }) 创建
|
|
52
|
-
* 独立实例。只要 dataDir 不同,它们拥有完全隔离的记忆库和会话。
|
|
53
|
-
* 内核不负责多 Agent 编排——那是宿主层的职责。
|
|
54
|
-
*
|
|
55
|
-
* **fork vs 多实例**:
|
|
56
|
-
* - forkSession() 分叉对话历史,但记忆索引全局共享
|
|
57
|
-
* - new Agent() + 独立 dataDir 实现完全隔离的记忆空间
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```ts
|
|
61
|
-
* const agent = new Agent({
|
|
62
|
-
* projectPath: '/path/to/project',
|
|
63
|
-
* provider: new OpenAIProvider({ apiKey: '...' }),
|
|
64
|
-
* configDir: '/path/to/config',
|
|
65
|
-
* dataDir: '/path/to/data',
|
|
66
|
-
* permission: 'owner',
|
|
67
|
-
* allowedPaths: ['/path/to/project'],
|
|
68
|
-
* });
|
|
69
|
-
* await agent.init();
|
|
70
|
-
* const iter = agent.chat('你好');
|
|
71
|
-
* for await (const chunk of iter) { ... }
|
|
72
|
-
* ```
|
|
34
|
+
* 单 Agent,单配置,单记忆:每个实例拥有独立的对话管线、消息历史与运行时状态。
|
|
35
|
+
* 多实例只需 dataDir 不同即可彻底隔离记忆库与会话;forkSession() 分叉对话历史但记忆索引全局共享。
|
|
36
|
+
* skill 为当轮实时注入,不跨轮缓存。
|
|
73
37
|
*/
|
|
74
38
|
export class Agent extends TypedEventEmitter {
|
|
75
39
|
// 构造参数分组
|
|
@@ -78,76 +42,62 @@ export class Agent extends TypedEventEmitter {
|
|
|
78
42
|
#provider;
|
|
79
43
|
/** 后台 Provider(独立字段,因 setBackgroundProvider() 可变) */
|
|
80
44
|
#backgroundProvider;
|
|
81
|
-
|
|
45
|
+
/** Provider 路由选择器(多模型路由基础,可选) */
|
|
46
|
+
#providerRouter = null;
|
|
47
|
+
// 运行时组件(init 后填充)——宿主 getter 契约字段(projects/memory/works/polish/sessionManager 等)保持独立
|
|
82
48
|
projectManager = null;
|
|
83
49
|
history = null;
|
|
84
50
|
loop = null;
|
|
85
51
|
toolExec = null;
|
|
86
52
|
// 新模块
|
|
87
|
-
personaManager = null;
|
|
88
|
-
#userProfile = null;
|
|
89
53
|
skillManager = null;
|
|
54
|
+
/** 角色包管理器(角色+技能+规则的唯一真理源) */
|
|
55
|
+
_rolePackManager = null;
|
|
90
56
|
// 拆分出的专职 Manager
|
|
91
|
-
insightExtractor = null;
|
|
92
|
-
configManager = null;
|
|
93
57
|
memoryInspector = null;
|
|
94
|
-
/**
|
|
95
|
-
* 语义去重管理器(L1 LLM 记忆治理)
|
|
96
|
-
*
|
|
97
|
-
* SPLIT-3 闭环(2026-07-21):从 MemoryInspector 拆分出 deduplicateMemories 职责,
|
|
98
|
-
* 让 MemoryInspector 回归纯存储读写。Agent.deduplicateMemories() 委托本对象。
|
|
99
|
-
*/
|
|
100
|
-
dedupManager = null;
|
|
101
|
-
/**
|
|
102
|
-
* 记忆顾问(L3 冲突检测 / sourceHealth / suggest)
|
|
103
|
-
*
|
|
104
|
-
* v2 PROXY-1 闭环:Agent.detectConflicts 直接调用 advisor,
|
|
105
|
-
* 不再经 MemoryInspector 转发,消除 3 层无意义代理。
|
|
106
|
-
* sourceHealth/suggest 仍由 inspector 转发以保持 agent.memory 统一入口语义。
|
|
107
|
-
*/
|
|
108
|
-
memoryAdvisor = null;
|
|
109
58
|
/** 记忆治理统一门面(L0/L1/L2/L3 + 诊断) */
|
|
110
59
|
_governance = null;
|
|
111
|
-
workProjection = null;
|
|
112
|
-
/** AutoConfigRefiner(模式 3:Agent 智能总结) */
|
|
113
|
-
autoConfigRefiner = null;
|
|
114
|
-
/** SessionArchiver(会话内容归档器,content 类记忆) */
|
|
115
|
-
sessionArchiver = null;
|
|
116
60
|
/** TextPolishManager(文本润色管理器,LLM 语法修正 + 表达优化) */
|
|
117
61
|
textPolisher = null;
|
|
118
62
|
/** 会话管理器(从 Agent 拆分出的会话管理职责) */
|
|
119
63
|
_sessionManager = null;
|
|
120
|
-
|
|
64
|
+
/** WorkProjectionManager(作品投影极简 JSON 索引,宿主经 works getter 消费) */
|
|
65
|
+
workProjection = null;
|
|
66
|
+
// ─── 纯内部组件聚合(无宿主 getter 契约,init 后填充、close 统一清空) ───
|
|
67
|
+
/**
|
|
68
|
+
* 内部组件聚合对象——收敛 12 个「仅门面内部消费、不对宿主暴露」的专职组件,
|
|
69
|
+
* 减少门面扁平字段数;宿主 getter 契约字段(memory/works/polish 等)不在此列。
|
|
70
|
+
*/
|
|
71
|
+
internals = {
|
|
72
|
+
dedupManager: null,
|
|
73
|
+
memoryAdvisor: null,
|
|
74
|
+
sessionArchiver: null,
|
|
75
|
+
sessionNamer: null,
|
|
76
|
+
roundSummaryGenerator: null,
|
|
77
|
+
contextPreparer: null,
|
|
78
|
+
chatLockManager: null,
|
|
79
|
+
archiveCoordinator: null,
|
|
80
|
+
seedOrchestrator: null,
|
|
81
|
+
gcService: null,
|
|
82
|
+
};
|
|
121
83
|
_initialized = false;
|
|
122
84
|
// 项目上下文(AgentContext 与 ProjectContext 等价,直接使用后者避免重复字段)
|
|
123
85
|
pctx = null;
|
|
124
|
-
/**
|
|
125
|
-
* chat() 并发锁管理器(init 时创建,close 时销毁)
|
|
126
|
-
*
|
|
127
|
-
* 职责:chat() 并发锁 + token 校验(race condition 防护)+ 超时保护 + 外部 signal 合并。
|
|
128
|
-
* 详见 ChatLockManager 类注释。
|
|
129
|
-
*/
|
|
130
|
-
chatLockManager = null;
|
|
131
86
|
/** 最近一次 chat() 调用的时间戳 */
|
|
132
87
|
_lastInteractionAt = null;
|
|
133
88
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
89
|
+
* 流活跃标志:chat()/resumeExecution() 的 AsyncGenerator 生命周期内置 true、finally 置 false。
|
|
90
|
+
* 与 chatLock.isBusy(并发锁,180s 超时后自动释放但**不中断生成流**)语义解耦——
|
|
91
|
+
* requestPause 空闲守卫必须用「流是否在产出」而非「锁是否持有」判任务是否结束,
|
|
92
|
+
* 否则长任务运行超锁期后点暂停会被误判为「任务已结束」——锁超时会自动释放但生成流仍在跑,
|
|
93
|
+
* 故判据只能是流活跃,不能是锁持有。
|
|
94
|
+
*/
|
|
95
|
+
_flowActive = false;
|
|
96
|
+
/**
|
|
97
|
+
* 对话中因 chatLock 冲突暂存的配置重载请求(锁释放后补执行,兑现"对话后自动加载");
|
|
98
|
+
* 用 Set 去重——同一 source 只需补执行一次。
|
|
142
99
|
*/
|
|
143
100
|
pendingConfigReload = new Set();
|
|
144
|
-
// ─── 衰减职责已拆分至 MemoryDecayScheduler ──────────
|
|
145
|
-
// metricDecayRunCount / metricTotalDecayedCount / metricLastDecayAt 字段
|
|
146
|
-
// 位于 MemoryDecayScheduler 内部,Agent 通过 memoryDecayScheduler.getMetrics() 读取
|
|
147
|
-
/** 记忆衰减调度器(init 时创建,close 时销毁) */
|
|
148
|
-
memoryDecayScheduler = null;
|
|
149
|
-
/** 归档协调器(归档操作委托给 ArchiveCoordinator) */
|
|
150
|
-
archiveCoordinator = null;
|
|
151
101
|
constructor(opts) {
|
|
152
102
|
super();
|
|
153
103
|
this.#config = {
|
|
@@ -155,25 +105,45 @@ export class Agent extends TypedEventEmitter {
|
|
|
155
105
|
dataDir: opts.dataDir,
|
|
156
106
|
registryDir: opts.registryDir,
|
|
157
107
|
maxContextTokens: opts.maxContextTokens ?? AGENT_CONSTANTS.DEFAULT_MAX_CONTEXT_TOKENS,
|
|
158
|
-
|
|
108
|
+
// per-LLM 输出预算默认值(token):宿主 per-LLM 配置透传;undefined = 不传 max_tokens
|
|
109
|
+
//(回服务端默认)。与角色包策略 act.outputLimit 的最终关系由 buildChatOptions 取更小值。
|
|
110
|
+
maxTokens: opts.maxTokens,
|
|
159
111
|
permission: opts.permission ?? 'owner',
|
|
160
112
|
allowedPaths: opts.allowedPaths ?? [],
|
|
161
113
|
confirmWrites: opts.confirmWrites ?? false,
|
|
162
|
-
|
|
163
|
-
recallExcludeSources: opts.recallExcludeSources ?? [
|
|
164
|
-
...AGENT_CONSTANTS.DEFAULT_RECALL_EXCLUDE_SOURCES,
|
|
165
|
-
],
|
|
114
|
+
confirmScripts: opts.confirmScripts ?? false,
|
|
166
115
|
storage: opts.storage,
|
|
167
|
-
relationStore: opts.relationStore,
|
|
168
116
|
sessionStore: opts.sessionStore,
|
|
117
|
+
roundStore: opts.roundStore,
|
|
169
118
|
configDir: opts.configDir,
|
|
119
|
+
// 启动时激活的角色包名(宿主注入持久化值,init 时优先激活——§4.1 单链第一层)
|
|
120
|
+
activeRolePack: opts.activeRolePack,
|
|
121
|
+
// 组(宿主装配级):组长角色包 + 组员名单(会议名单容器,非选择对象;组员仅会议参与)
|
|
122
|
+
// 内部运行态归一化必选(外部未配 = 无团队),保证装配链必传
|
|
123
|
+
rolePackTeams: opts.rolePackTeams ?? [],
|
|
124
|
+
// 程序级内置兜底角色(可选):覆盖内核常量 BUILTIN_FALLBACK_PACK;须指向存在的包,否则回退内核常量
|
|
125
|
+
builtinFallbackRole: opts.builtinFallbackRole,
|
|
170
126
|
tracer: opts.tracer,
|
|
171
127
|
messages: opts.messages,
|
|
172
128
|
enableContextSummary: opts.enableContextSummary ?? true,
|
|
173
129
|
archiveMode: opts.archiveMode ?? 'full',
|
|
130
|
+
webSearchProvider: opts.webSearchProvider,
|
|
131
|
+
fetchProvider: opts.fetchProvider,
|
|
132
|
+
codeExecutionProvider: opts.codeExecutionProvider,
|
|
133
|
+
projectSearchProvider: opts.projectSearchProvider,
|
|
134
|
+
// 脚本执行 node 路径(可选):宿主注入真实 node 路径,避免无独立 node 时 ENOENT
|
|
135
|
+
scriptNodePath: opts.scriptNodePath,
|
|
136
|
+
// 禁用技能清单(配置形态启停):透传装配 → SkillManager 过滤 L1/L2/L3
|
|
137
|
+
disabledSkills: opts.disabledSkills ?? [],
|
|
138
|
+
// 宿主审批/审计/参数改写通道,透传供装配阶段与内部幂等检查组合为一处执行前检查点
|
|
139
|
+
preExecutionCheck: opts.preExecutionCheck,
|
|
140
|
+
// 宿主装配级策略覆盖(能力边界):透传 #config → 装入策略解析链(resolveActiveStrategy),
|
|
141
|
+
// 压过角色包声明(v0.13 后无宿主注入默认值;机制保留供宿主产品能力边界使用)
|
|
142
|
+
strategyOverride: opts.strategyOverride,
|
|
174
143
|
};
|
|
175
144
|
this.#provider = opts.provider;
|
|
176
145
|
this.#backgroundProvider = opts.backgroundProvider ?? null;
|
|
146
|
+
this.#providerRouter = opts.providerRouter ?? null;
|
|
177
147
|
// 如需自定义日志,请在创建 Agent 前调用 `import { setLogger } from '@zooique/memora'` 全局设置
|
|
178
148
|
}
|
|
179
149
|
// ─── 生命周期 ─────────────────────────────────────────
|
|
@@ -190,9 +160,48 @@ export class Agent extends TypedEventEmitter {
|
|
|
190
160
|
this.pctx = pctx;
|
|
191
161
|
this.validateCoreComponents();
|
|
192
162
|
this.createPostInitComponents(pctx);
|
|
163
|
+
// 标记初始化完成
|
|
193
164
|
this._initialized = true;
|
|
165
|
+
// 注册暂停超时归档(同进程暂停超时:30min 无心跳则归档清理)
|
|
166
|
+
this.registerPauseTimeoutArchiver();
|
|
167
|
+
this.registerWorkProjectionRefresh();
|
|
168
|
+
// 注:不加载持久化检查点——中止/断电一律走
|
|
169
|
+
// 「中断轮补全为完整 turn 身份 → 参与下一轮」,不存在跨重启恢复;
|
|
170
|
+
// 运行时暂停是同 turn 内续跑(内存态),无需磁盘载体。
|
|
194
171
|
return pctx;
|
|
195
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* 登记作品投影实时刷新处理器:register_work 工具 / 右键登记成功后,
|
|
175
|
+
* 内核 WorkProjectionManager 触发 workProjectionGenerated 事件(携带已写入内存 entries 的投影),
|
|
176
|
+
* 立即重建 AgentLoop 的 systemPromptPrefix,使 AI 在下一轮对话即可感知新索引——
|
|
177
|
+
* 否则需切角色包 / reloadConfig / 开新会话才生效。
|
|
178
|
+
* 与 registerPauseTimeoutArchiver 同生命周期(init 内注册,close 的 removeAllListeners 兜底)。
|
|
179
|
+
*/
|
|
180
|
+
registerWorkProjectionRefresh() {
|
|
181
|
+
this.on(AGENT_EVENTS.workProjectionGenerated, () => {
|
|
182
|
+
this.refreshRolePackPrefixOnLoop();
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 注册暂停超时归档处理器(SessionManager 两条超时路径——启动加载与运行时定时器——统一在此消费)
|
|
187
|
+
* 注册在 init() 内(close() 的 removeAllListeners + init 起始的 disposePreviousInstance 保证实例周期内恰好一个监听器),
|
|
188
|
+
* 挂在生命周期稳定的 Agent 上,rebuildComponents() 重建管理器后仍有效。
|
|
189
|
+
*/
|
|
190
|
+
registerPauseTimeoutArchiver() {
|
|
191
|
+
this.on(AGENT_EVENTS.sessionPauseTimedOut, (payload) => {
|
|
192
|
+
// pendingPauseReason 由 resetToRunning() 内部统一清理
|
|
193
|
+
//(checkPauseTimeout 经 sessionManager resetToRunning 已覆盖,此处无需再补 cancelPendingPause)。
|
|
194
|
+
const { sessionId, date, session } = payload;
|
|
195
|
+
if (!date || !session)
|
|
196
|
+
return;
|
|
197
|
+
// fire-and-forget:归档失败不阻塞主流程,仅记录
|
|
198
|
+
this.internals.archiveCoordinator
|
|
199
|
+
?.archiveSession(date, session, { autoTriggered: true })
|
|
200
|
+
.catch((err) => {
|
|
201
|
+
logger.warn({ err, sessionId }, '暂停超时会话自动归档失败');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
}
|
|
196
205
|
/**
|
|
197
206
|
* 关闭旧实例(如果已初始化),失败不阻塞重建
|
|
198
207
|
*/
|
|
@@ -215,7 +224,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
215
224
|
storage: this.#config.storage,
|
|
216
225
|
registryDir: this.#config.registryDir,
|
|
217
226
|
// SecurityGuard 由 Agent 层创建,解除 memory→security 反向依赖
|
|
218
|
-
createSecurityGuard: (projectPath, memoraDir, configDir, agentDataDir) => new SecurityGuard(projectPath, memoraDir, this.#config.allowedPaths, this.#config.confirmWrites, this.#config.permission, configDir ?? this.#config.configDir, agentDataDir ?? this.#config.dataDir),
|
|
227
|
+
createSecurityGuard: (projectPath, memoraDir, configDir, agentDataDir) => new SecurityGuard(projectPath, memoraDir, this.#config.allowedPaths, this.#config.confirmWrites, this.#config.permission, this.#config.confirmScripts, configDir ?? this.#config.configDir, agentDataDir ?? this.#config.dataDir),
|
|
219
228
|
});
|
|
220
229
|
return this.projectManager.initProject(this.#config.projectPath, undefined, this.#config.configDir);
|
|
221
230
|
}
|
|
@@ -234,33 +243,80 @@ export class Agent extends TypedEventEmitter {
|
|
|
234
243
|
/**
|
|
235
244
|
* 创建 init() 后期组件(初始化完成后才需要的组件)
|
|
236
245
|
*/
|
|
237
|
-
createPostInitComponents(
|
|
246
|
+
createPostInitComponents(_pctx) {
|
|
238
247
|
// chat() 并发锁管理器(生命周期与 Agent 实例一致)
|
|
239
|
-
this.chatLockManager = new ChatLockManager();
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
248
|
+
this.internals.chatLockManager = new ChatLockManager();
|
|
249
|
+
// 孤儿 Round 垃圾回收服务:roundStore+storage 均为构造期稳定注入(不随 rebuildComponents 变化),
|
|
250
|
+
// 每日周期清理 refCount=0 且超龄的孤立问答闭环(崩溃残留 pending / 已删会话遗留),防止存储膨胀。
|
|
251
|
+
// shouldSkip:对话进行中跳过本次(minAgeMs 判龄对长任务不可靠,防进行中轮被误清)
|
|
252
|
+
if (this.#config.roundStore && this.#config.storage) {
|
|
253
|
+
this.internals.gcService = createDefaultGCService(this.#config.roundStore, this.#config.storage,
|
|
254
|
+
// 忙碌检查:chatLock busy = 对话/长任务进行中,跳过本次 GC
|
|
255
|
+
() => this.internals.chatLockManager?.isBusy ?? false);
|
|
256
|
+
this.internals.gcService.startPeriodic(AGENT_CONSTANTS.GC_INTERVAL_MS);
|
|
257
|
+
}
|
|
258
|
+
// 归档操作委托给 ArchiveCoordinator(content 会话归档)
|
|
259
|
+
this.internals.archiveCoordinator = new ArchiveCoordinator({
|
|
260
|
+
getSessionArchiver: () => this.internals.sessionArchiver,
|
|
245
261
|
getArchiveMode: () => this.#config.archiveMode,
|
|
246
262
|
emit: (event, payload) => this.emit(event, payload),
|
|
247
263
|
});
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
264
|
+
// 会话命名器:新建会话首次问答自动命名标题;惰性获取当前 provider(切换后仍命中最新模型)
|
|
265
|
+
// 注入 onTitleUpdated 回调:标题生成后发射 sessionTitleUpdated 事件,宿主据此刷新 UI
|
|
266
|
+
this.internals.sessionNamer = new SessionNamer({
|
|
267
|
+
getProvider: () => this.#provider,
|
|
268
|
+
sessionStore: this.#config.sessionStore,
|
|
269
|
+
onTitleUpdated: (sessionId, title) => {
|
|
270
|
+
this.emit(AGENT_EVENTS.sessionTitleUpdated, { sessionId, title });
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
// 记忆治理统一门面(去重 / 冲突 / 诊断)
|
|
274
|
+
this._governance = new MemoryGovernance(this.internals.dedupManager, this.internals.memoryAdvisor);
|
|
275
|
+
// 种子 turn 编排器(turn 唯一编排真理源):依赖 sessionManager/loop/history 等
|
|
276
|
+
// 均已就绪(assembleComponents 已完成 + sessionNamer 本方法前段创建),在此构造一次。
|
|
277
|
+
// getParts() 惰性取当前组件——rebuildComponents(switchProject)更换组件后仍取到最新引用。
|
|
278
|
+
this.internals.seedOrchestrator = this.createSeedOrchestrator();
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* 构造种子闭环编排器,注入门面稳定能力(getParts 快照 + 工具暴露 + 流收口协议)
|
|
282
|
+
*
|
|
283
|
+
* 构造偏好在 agent.ts 而非 assembler.ts(方案的"assembler 构造"):seed 依赖
|
|
284
|
+
* sessionNamer(post-init 才创建)与 consumeExecutionStream(门面私有流收口协议),
|
|
285
|
+
* 二者均无法在 assembler 阶段就绪,故收敛到 post-init 门面组装处。
|
|
286
|
+
*
|
|
287
|
+
* @returns 已接线门面能力的 SeedOrchestrator
|
|
288
|
+
*/
|
|
289
|
+
createSeedOrchestrator() {
|
|
290
|
+
return new SeedOrchestrator({
|
|
291
|
+
// 组件快照经 getter 惰性取当前引用:rebuildComponents 更换组件后仍读到最新(关键)
|
|
292
|
+
getParts: () => ({
|
|
293
|
+
loop: this.loop,
|
|
294
|
+
history: this.history,
|
|
295
|
+
sessionManager: this._sessionManager,
|
|
296
|
+
rolePackManager: this._rolePackManager,
|
|
297
|
+
contextPreparer: this.internals.contextPreparer,
|
|
298
|
+
sessionNamer: this.internals.sessionNamer,
|
|
299
|
+
roundSummaryGenerator: this.internals.roundSummaryGenerator,
|
|
300
|
+
}),
|
|
301
|
+
tracer: this.#config.tracer ?? null,
|
|
302
|
+
archiveMode: this.#config.archiveMode,
|
|
303
|
+
messages: this.#config.messages,
|
|
304
|
+
// 门面私有能力经回调注入 seed(物理实现仍在门面)
|
|
305
|
+
applyRolePackToolExposure: () => this.applyRolePackToolExposure(),
|
|
306
|
+
// 会议机制:按本轮表层装配视角刷新 loop 前缀(roundRole=null 回落 activePack)
|
|
307
|
+
refreshRolePackPrefixForRound: (roundRole) => this.refreshRolePackPrefixForRound(roundRole),
|
|
308
|
+
consumeExecutionStream: (source, signal) => this.consumeExecutionStream(source, signal),
|
|
309
|
+
getBackgroundProvider: () => this.#backgroundProvider,
|
|
310
|
+
// 宿主装配级策略覆盖(能力边界):传给策略解析链,压过角色包声明(单一语义键不变)
|
|
311
|
+
strategyOverride: this.#config.strategyOverride,
|
|
312
|
+
// 事件发射回调:orchestrator.runSummary 完成后 emit roundSummaryGenerated,
|
|
313
|
+
// 让宿主感知后台摘要收口 → 解锁 UI 生命周期(删除/分叉按钮解禁,防孤儿记忆)
|
|
314
|
+
emit: (event, payload) => {
|
|
315
|
+
if (AGENT_EVENT_SET.has(event)) {
|
|
316
|
+
this.emit(event, payload);
|
|
317
|
+
}
|
|
257
318
|
},
|
|
258
|
-
backgroundProvider: this.#backgroundProvider,
|
|
259
|
-
index: pctx.index,
|
|
260
319
|
});
|
|
261
|
-
this.memoryDecayScheduler.start(pctx.index, AGENT_CONSTANTS.DECAY_INTERVAL_MS);
|
|
262
|
-
// 记忆治理统一门面(L0/L1/L2/L3 + 诊断)
|
|
263
|
-
this._governance = new MemoryGovernance(this.dedupManager, this.memoryDecayScheduler, this.memoryAdvisor);
|
|
264
320
|
}
|
|
265
321
|
/**
|
|
266
322
|
* 流式对话(核心 API)
|
|
@@ -271,24 +327,30 @@ export class Agent extends TypedEventEmitter {
|
|
|
271
327
|
const lockCtx = this.acquireChatLock(signal);
|
|
272
328
|
const { myToken, combinedSignal, cleanupExternalSignal } = lockCtx;
|
|
273
329
|
try {
|
|
274
|
-
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
330
|
+
// 流活跃置位:requestPause 空闲守卫据此判「任务进行中」——锁 180s 超时释放后
|
|
331
|
+
// isBusy=false 但流仍在产出,必须以流生命周期(而非锁持有)为准(09-03 锁语义变更回归修补)
|
|
332
|
+
this._flowActive = true;
|
|
333
|
+
// 状态机翻转是副作用,必须在并发闸门内执行——PAUSED 态收到 chat = 自动恢复 + 继续(作为补充注入);ERROR 态仍拒绝
|
|
334
|
+
if (!this.autoResumeIfPaused()) {
|
|
278
335
|
yield {
|
|
279
|
-
type: '
|
|
280
|
-
|
|
336
|
+
type: 'error',
|
|
337
|
+
message: '会话已超时,无法自动恢复,请重新开始新对话',
|
|
338
|
+
category: 'timeout',
|
|
281
339
|
};
|
|
282
340
|
return;
|
|
283
341
|
}
|
|
284
|
-
|
|
285
|
-
|
|
342
|
+
this._lastInteractionAt = new Date();
|
|
343
|
+
// 委托种子编排器:对话路径完整闭环(prepare → act → reflect)
|
|
344
|
+
yield* this.internals.seedOrchestrator.runChat(input, combinedSignal);
|
|
286
345
|
}
|
|
287
346
|
finally {
|
|
347
|
+
// 流已退出:清除活跃标志(与置位对称;无论正常/异常退出都复位)
|
|
348
|
+
this._flowActive = false;
|
|
288
349
|
// 仅当本调用仍是当前锁持有者时才清理资源(token 校验)
|
|
289
|
-
this.chatLockManager?.release(myToken);
|
|
350
|
+
this.internals.chatLockManager?.release(myToken);
|
|
290
351
|
cleanupExternalSignal();
|
|
291
|
-
//
|
|
352
|
+
// turn 结束自动收尾:非暂停态无条件清理任务表(turn 内能力,不跨 turn 残留)
|
|
353
|
+
this.clearPlanOnTurnEnd();
|
|
292
354
|
await this.flushPendingConfigReload();
|
|
293
355
|
}
|
|
294
356
|
}
|
|
@@ -304,7 +366,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
304
366
|
* 获取对话锁 — 返回锁上下文
|
|
305
367
|
*/
|
|
306
368
|
acquireChatLock(signal) {
|
|
307
|
-
const chatLock = this.chatLockManager;
|
|
369
|
+
const chatLock = this.internals.chatLockManager;
|
|
308
370
|
if (!chatLock)
|
|
309
371
|
throw configError('ChatLockManager', 'ChatLockManager 未初始化', []);
|
|
310
372
|
if (chatLock.isBusy) {
|
|
@@ -314,79 +376,6 @@ export class Agent extends TypedEventEmitter {
|
|
|
314
376
|
const cleanupExternalSignal = chatLock.attachExternalSignal(signal, internalAbort);
|
|
315
377
|
return { myToken, internalAbort, cleanupExternalSignal, combinedSignal: internalAbort.signal };
|
|
316
378
|
}
|
|
317
|
-
/**
|
|
318
|
-
* 准备对话上下文:角色匹配 → 记忆召回 → 技能注入 → 用户消息写历史
|
|
319
|
-
* @returns 召回的记忆列表(供 loop.processUserInput 注入为 system 消息)
|
|
320
|
-
*/
|
|
321
|
-
async *prepareChatContext(input, combinedSignal) {
|
|
322
|
-
const loop = this.requireLoop;
|
|
323
|
-
loop.cleanTemporarySystemMessages();
|
|
324
|
-
// 角色自动匹配(回答前执行)
|
|
325
|
-
await this.tryAutoMatchPersona(input);
|
|
326
|
-
// 基元驱动召回(双通道:语义 + 关键词)
|
|
327
|
-
yield { type: 'thinking', phase: 'recalling' };
|
|
328
|
-
const recalledMemories = await this.recallAndInject(input);
|
|
329
|
-
if (combinedSignal.aborted)
|
|
330
|
-
return recalledMemories;
|
|
331
|
-
// 技能关键词/正则匹配(当轮立即注入生效)
|
|
332
|
-
yield { type: 'thinking', phase: 'processing' };
|
|
333
|
-
this.matchAndInjectSkill(input);
|
|
334
|
-
const history = this.requireHistory;
|
|
335
|
-
await history.appendUser(input);
|
|
336
|
-
return recalledMemories;
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* 执行 LLM 流式循环 → 追加助手消息 → 触发后处理
|
|
340
|
-
*/
|
|
341
|
-
async *executeChatLoop(input, recalledMemories, combinedSignal) {
|
|
342
|
-
const loop = this.requireLoop;
|
|
343
|
-
const history = this.requireHistory;
|
|
344
|
-
let assistantContent = '';
|
|
345
|
-
let wasAborted = false;
|
|
346
|
-
try {
|
|
347
|
-
for await (const chunk of loop.processUserInput(input, recalledMemories, combinedSignal)) {
|
|
348
|
-
yield chunk;
|
|
349
|
-
if (chunk.type === 'text') {
|
|
350
|
-
assistantContent += chunk.content;
|
|
351
|
-
}
|
|
352
|
-
else if (chunk.type === 'aborted') {
|
|
353
|
-
wasAborted = true;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
catch (err) {
|
|
358
|
-
if (isAbortError(err)) {
|
|
359
|
-
yield { type: 'aborted', reason: 'User cancelled the conversation' };
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
else {
|
|
363
|
-
yield { type: 'error', message: err instanceof Error ? err.message : String(err) };
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
if (wasAborted) {
|
|
368
|
-
if (assistantContent.trim()) {
|
|
369
|
-
const interruptedMark = this.#config.messages?.interrupted ?? '\n\n[已中断]';
|
|
370
|
-
try {
|
|
371
|
-
await history.appendAssistant(assistantContent + interruptedMark);
|
|
372
|
-
}
|
|
373
|
-
catch (err) {
|
|
374
|
-
logger.warn({ err }, '中断消息历史写入失败');
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
// 追加助手消息到历史(best-effort)
|
|
380
|
-
try {
|
|
381
|
-
await history.appendAssistant(assistantContent);
|
|
382
|
-
}
|
|
383
|
-
catch (err) {
|
|
384
|
-
logger.warn({ err }, '助手消息历史写入失败');
|
|
385
|
-
}
|
|
386
|
-
// 后处理阶段
|
|
387
|
-
yield { type: 'thinking', phase: 'archiving' };
|
|
388
|
-
await this.postProcess(input, assistantContent);
|
|
389
|
-
}
|
|
390
379
|
/**
|
|
391
380
|
* 补执行对话期间暂存的配置重载请求
|
|
392
381
|
*/
|
|
@@ -408,231 +397,590 @@ export class Agent extends TypedEventEmitter {
|
|
|
408
397
|
/**
|
|
409
398
|
* 强制释放对话锁
|
|
410
399
|
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* 此时 _chatBusy 锁未释放,用户再发消息会被 chat() 的竞态保护拒绝,
|
|
415
|
-
* 表现为"UI 能操作但发不出消息",需等到 3 分钟锁超时才能恢复。
|
|
416
|
-
*
|
|
417
|
-
* 本方法让宿主在确认 generator 挂起后强制释放锁,让用户能立即发起新对话。
|
|
418
|
-
*
|
|
419
|
-
* 安全机制(委托至 ChatLockManager.forceRelease):
|
|
420
|
-
* - 递增 _chatLockToken,让原 chat() 的 finally 块检测到 token 不匹配后
|
|
421
|
-
* 跳过资源清理(避免误清新调用者的 _chatBusy/chatAbortController)
|
|
422
|
-
* - abort chatAbortController,让响应 signal 的 await 点(如 fetch)退出
|
|
423
|
-
* - 原 generator 仍可能在后台运行(无法真正中断不响应 signal 的 await),
|
|
424
|
-
* 但其 finally 块的 token 校验会阻止它影响新调用
|
|
425
|
-
*
|
|
426
|
-
* 幂等性:_chatBusy 已 false 时 no-op(多次调用安全)
|
|
427
|
-
*
|
|
428
|
-
* 使用约束:
|
|
429
|
-
* - 仅在宿主确认 generator 挂起(如无进展超时)后调用
|
|
430
|
-
* - 不应在常规 abort 路径调用(常规 abort 走 signal,generator 自然退出)
|
|
431
|
-
* - 调用后不应再消费原 chat() generator 的后续 chunk(token 已变,chunk 无意义)
|
|
400
|
+
* 宿主在"无进展超时"确认 generator 挂起后调用,使用户能立即发起新对话(否则需等锁超时)。
|
|
401
|
+
* 安全机制委托 ChatLockManager.forceRelease:递增 token 让原 chat() 的 finally 跳过清理、abort signal 让响应点退出;
|
|
402
|
+
* 原 generator 仍可能后台运行但不影响新调用。幂等(未忙时 no-op)。
|
|
432
403
|
*/
|
|
433
404
|
forceReleaseChatLock() {
|
|
434
|
-
this.chatLockManager?.forceRelease();
|
|
405
|
+
this.internals.chatLockManager?.forceRelease();
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* 发送用户消息,非流式返回完整回复
|
|
409
|
+
*/
|
|
410
|
+
async chatSync(input, signal) {
|
|
411
|
+
let result = '';
|
|
412
|
+
for await (const chunk of this.chat(input, signal)) {
|
|
413
|
+
if (chunk.type === 'text') {
|
|
414
|
+
result += chunk.content;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return result;
|
|
435
418
|
}
|
|
419
|
+
// ─── 不中断工作模型 v2.0:公开 API ──────────────────────
|
|
436
420
|
/**
|
|
437
|
-
*
|
|
421
|
+
* 软暂停后续跑(不中断工作模型 v2.1)
|
|
438
422
|
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
423
|
+
* 翻状态机为 RUNNING(触发 sessionResumed → 宿主转发 STATUS{running}),
|
|
424
|
+
* 驱动 loop.continueAfterPause 续跑并转发 chunk,完成后追加历史 + 后处理(同 chat 尾处理)。
|
|
425
|
+
* 硬停止(signal.abort)仍是唯一霸道中止路径,与软暂停严格区分。
|
|
442
426
|
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
excludeSources: this.#config.recallExcludeSources,
|
|
427
|
+
* 归属:补充输入归属当前问答闭环(复用 prepare 分配的闭环节点 roundId,不分裂新轮),
|
|
428
|
+
* 以 kind 区分交互类型(question-answer=主动提问回答 / supplement=暂停/流中补充)。
|
|
429
|
+
*
|
|
430
|
+
* @param input - 可选补充输入(空=续跑原路径;有=注入修正后续轮)
|
|
431
|
+
* @param signal - 可选 AbortSignal(硬停止仍走此路径)
|
|
432
|
+
* @param kind - 交互输入类型(默认 supplement;对主动提问的回答传 'question-answer')
|
|
433
|
+
*/
|
|
434
|
+
async *resumeExecution(input, signal, kind = 'supplement') {
|
|
435
|
+
const resumeStatus = this._sessionManager?.status;
|
|
436
|
+
// 错误态续跑须明确失败而非静默吞没:error 态由检查点恢复回填进入,运行时异常走
|
|
437
|
+
// yield { type: 'error' } 不翻状态机,生产链路不会自然离开,必须显式提示重新开始
|
|
438
|
+
if (resumeStatus === 'error') {
|
|
439
|
+
this.emit(AGENT_EVENTS.sessionResumeFailed, {
|
|
440
|
+
sessionId: this._sessionManager?.getCheckpoint()?.sessionId,
|
|
441
|
+
reason: '会话处于错误态,无法续跑',
|
|
459
442
|
});
|
|
443
|
+
yield {
|
|
444
|
+
type: 'error',
|
|
445
|
+
message: '当前会话处于错误态,无法续跑,请重新开始',
|
|
446
|
+
category: 'unknown',
|
|
447
|
+
};
|
|
448
|
+
return;
|
|
460
449
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
450
|
+
if (resumeStatus !== 'paused')
|
|
451
|
+
return;
|
|
452
|
+
// resumeExecution 与 chat() 同构,必须加并发锁
|
|
453
|
+
const lockCtx = this.acquireChatLock(signal);
|
|
454
|
+
const { myToken, combinedSignal, cleanupExternalSignal } = lockCtx;
|
|
455
|
+
try {
|
|
456
|
+
// 流活跃置位:与 chat() 同构(requestPause 空闲守卫判据——续跑也是活跃流)
|
|
457
|
+
this._flowActive = true;
|
|
458
|
+
// 翻状态机为 RUNNING(触发 sessionResumed)
|
|
459
|
+
if (!this.resume()) {
|
|
460
|
+
// resume() 返回 false 时 yield error + 发事件,让宿主感知失败原因而非静默吞没
|
|
461
|
+
this.emit(AGENT_EVENTS.sessionResumeFailed, {
|
|
462
|
+
sessionId: this._sessionManager?.getCheckpoint()?.sessionId,
|
|
463
|
+
reason: 'resume() 返回 false,可能因暂停超时或状态机拒绝',
|
|
464
|
+
});
|
|
465
|
+
yield {
|
|
466
|
+
type: 'error',
|
|
467
|
+
message: '会话已超时,无法自动恢复,请重新开始新对话',
|
|
468
|
+
category: 'timeout',
|
|
469
|
+
};
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
// 预判短路(收窄):仅「计划全部 blocked」才拦——blocked=真停滞,继续调 LLM
|
|
473
|
+
// 只会复读卡住状态、白烧 token。全 done / 无计划不再拦:用户主动点「继续」就是要 AI 产出
|
|
474
|
+
// (可能收尾总结、补建计划、继续语境),且短问答暂停续跑本就无计划,拦了就没法继续聊。
|
|
475
|
+
// 判定下沉 sessionManager.isPlanAllBlocked()(仅全 blocked 拦;全 done/空计划放行)。
|
|
476
|
+
if (!input &&
|
|
477
|
+
this._sessionManager?.isPlanAllBlocked() &&
|
|
478
|
+
!this.requireLoop.isInAutonomousStep) {
|
|
479
|
+
yield {
|
|
480
|
+
type: 'text',
|
|
481
|
+
content: '计划任务项当前全部处于阻塞状态,无法自动推进。请提供新指令或修改计划',
|
|
482
|
+
};
|
|
483
|
+
yield { type: 'done' };
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
// 委托种子编排器:续跑路径闭环(act(continueAfterPause) → reflect;无回答前),
|
|
487
|
+
// 预判短路与锁/状态机守卫留在门面,执行语义收在编排器内。
|
|
488
|
+
yield* this.internals.seedOrchestrator.runResume(input, combinedSignal, kind);
|
|
464
489
|
}
|
|
465
490
|
finally {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
// Layer 5: 最近对话注入
|
|
480
|
-
const loop = this.requireLoop;
|
|
481
|
-
const recentHistory = loop.getRecentHistory(AGENT_CONSTANTS.DEFAULT_RECENT_HISTORY_ROUNDS);
|
|
482
|
-
if (recentHistory.length > 0) {
|
|
483
|
-
const msgs = this.#config.messages;
|
|
484
|
-
const label = msgs?.recentConversationLabel ?? '[Recent conversation]';
|
|
485
|
-
const userLabel = msgs?.userLabel ?? 'User';
|
|
486
|
-
const assistantLabel = msgs?.assistantLabel ?? 'Assistant';
|
|
487
|
-
const recentPrompt = `${label}\n` +
|
|
488
|
-
recentHistory
|
|
489
|
-
.map((m) => `${m.role === 'user' ? userLabel : assistantLabel}:${m.content}`)
|
|
490
|
-
.join('\n');
|
|
491
|
-
loop.injectSystemMessage(recentPrompt);
|
|
492
|
-
logger.debug({ turns: recentHistory.length / 2 }, '最近对话已注入');
|
|
491
|
+
// 流已退出:清除活跃标志(与置位对称;无论正常/异常退出都复位)
|
|
492
|
+
this._flowActive = false;
|
|
493
|
+
// 与 chat() 同构:释放锁 + 清理外部 signal
|
|
494
|
+
this.internals.chatLockManager?.release(myToken);
|
|
495
|
+
cleanupExternalSignal();
|
|
496
|
+
// 续跑轮(resumeExecution)是同一 turn 的收尾半程:turn 真正结束 → 与 chat() 同构,
|
|
497
|
+
// 无条件清空任务表(除非再次暂停——pauseMeta 由 loop 在边界重新挂起,guard 保留)。
|
|
498
|
+
// 必须无条件清:若仅全 done 才清,resume 以非 done 且未重暂停结束(如某任务项标 blocked
|
|
499
|
+
// 后收尾)时 plan 残留 → 跨 turn 污染下一个 chat()。
|
|
500
|
+
// 两处 turn-end 清理统一为 clearPlanOnTurnEnd(单一收口点),plan 严格 turn 内、不跨 turn 残留。
|
|
501
|
+
this.clearPlanOnTurnEnd();
|
|
493
502
|
}
|
|
494
|
-
return recalledMemories;
|
|
495
503
|
}
|
|
496
504
|
/**
|
|
497
|
-
*
|
|
505
|
+
* 消费执行流的单一真理源
|
|
498
506
|
*
|
|
499
|
-
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
507
|
+
* chat() 与续跑(continueAfterPause)必须共用同一份消费逻辑——两份同构实现会漂移
|
|
508
|
+
* (实证:续跑漏 paused 分支导致三方分叉);收口于此,新增 chunk 类型只需改一处。
|
|
509
|
+
* 暂停幂等锁的释放放 finally——清理是"退出本作用域的不变式"而非某分支动作,新增 return 分支无遗漏。
|
|
502
510
|
*
|
|
503
|
-
*
|
|
504
|
-
* 清理机制:cleanTemporarySystemMessages() 每轮开头清除临时 system 消息,
|
|
505
|
-
* 技能 prompt 自然不会累积到下一轮。
|
|
506
|
-
*
|
|
507
|
-
* @param input 用户输入文本
|
|
511
|
+
* @returns 消费结果;failed 为 true 时调用方应立即 return(错误 chunk 已 yield)
|
|
508
512
|
*/
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
513
|
+
async *consumeExecutionStream(source, signal) {
|
|
514
|
+
let content = '';
|
|
515
|
+
let aborted = false;
|
|
516
|
+
// 软暂停标记:loop 在 step 边界挂起(用户 requestPause / ask_user 主动提问)时置真,
|
|
517
|
+
// 供编排器据此推迟摘要——回合未完成不产摘要,保摘要与外部输入 1:1
|
|
518
|
+
let paused = false;
|
|
512
519
|
try {
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (
|
|
517
|
-
this.
|
|
518
|
-
|
|
520
|
+
for await (const chunk of source) {
|
|
521
|
+
// 内核事实驱动:loop 在 step 边界真正挂起时状态机才翻 PAUSED,非申请即翻转;
|
|
522
|
+
// 与 requestPause 空闲分支同源,须传 lowRisk=true 保持同一暂停事件契约(否则流中暂停计入连续暂停配额)
|
|
523
|
+
if (chunk.type === 'paused') {
|
|
524
|
+
const pendingInfo = this._sessionManager?.consumePendingPause();
|
|
525
|
+
// 暂停收口统一写 pauseMeta:reason/source 取自 pendingPause(与状态机一致)。
|
|
526
|
+
// ask_user 主动提问等"直接暂停路径"不走 loop.onPaused,在此自然补齐,
|
|
527
|
+
// 重启后宿主可展示"为什么暂停 + 问了什么"
|
|
528
|
+
const pauseReason = pendingInfo?.reason ?? '用户主动暂停';
|
|
529
|
+
const pauseSource = pendingInfo?.source ?? 'user';
|
|
530
|
+
this.pause(pauseReason, pauseSource, true);
|
|
531
|
+
this._sessionManager?.setPauseMeta({ reason: pauseReason, source: pauseSource });
|
|
532
|
+
paused = true;
|
|
533
|
+
}
|
|
534
|
+
yield chunk;
|
|
535
|
+
// 累积 assistantContent 用于持久化到 history:只累积正常回答(stage≠self_review)。
|
|
536
|
+
// 自审查文本是内部思考过程——宿主渲染进 round-block § 自审查输出折叠区(不进正文流),
|
|
537
|
+
// 也不应持久化到会话历史(重启后不应在对话记录里看到自审查内容)。
|
|
538
|
+
// 注:loop.messages 里自审查 assistant 仍保留(LLM 下一轮迭代可能需参考),过滤只在持久化出口。
|
|
539
|
+
if (chunk.type === 'text' && chunk.stage !== 'self_review') {
|
|
540
|
+
content += chunk.content;
|
|
541
|
+
}
|
|
542
|
+
else if (chunk.type === 'narrate' && chunk.withdrawn) {
|
|
543
|
+
// 回抽:首轮工具步的叙述文本先作为 text 流式累积进正文(上面分支),
|
|
544
|
+
// 确认工具轮后从**持久化正文**扣除——与宿主正文撤回同源(同一 withdrawn 字段,SSOT)。
|
|
545
|
+
// 契约:该段为最近追加的正文文本(后缀);不符(多 turn 复用流/异常序)则忽略并告警,
|
|
546
|
+
// 宁可正文多一段叙述也不误删真实答案。
|
|
547
|
+
if (content.endsWith(chunk.withdrawn)) {
|
|
548
|
+
content = content.slice(0, content.length - chunk.withdrawn.length);
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
logger.warn({ withdrawnLen: chunk.withdrawn.length, contentLen: content.length }, 'narrate 回抽:持久化正文不含该段(后缀契约不符),已忽略');
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
else if (chunk.type === 'aborted') {
|
|
555
|
+
aborted = true;
|
|
519
556
|
}
|
|
520
|
-
this.emit(AGENT_EVENTS.skillMatched, { skill: match.skill.name, score: match.score });
|
|
521
557
|
}
|
|
522
558
|
}
|
|
523
559
|
catch (err) {
|
|
524
|
-
|
|
560
|
+
if (isAbortError(err)) {
|
|
561
|
+
// signal 已 abort(宿主/插话合并信号)→ 真用户取消:非失败——置 aborted 供 act() 走
|
|
562
|
+
// 「中断保留已产出文本」分支,半截回答落盘。
|
|
563
|
+
if (signal?.aborted) {
|
|
564
|
+
// 锁超时中断(signal.reason=TimeoutError)≠ 用户取消:stopReason 区分,宿主映射超时文案
|
|
565
|
+
yield {
|
|
566
|
+
type: 'aborted',
|
|
567
|
+
reason: isTimeoutAbortSignal(signal)
|
|
568
|
+
? 'LLM request timed out (no response)'
|
|
569
|
+
: 'User cancelled the conversation',
|
|
570
|
+
stopReason: isTimeoutAbortSignal(signal) ? 'timeout' : 'user',
|
|
571
|
+
};
|
|
572
|
+
return { content, aborted: true, paused, failed: false };
|
|
573
|
+
}
|
|
574
|
+
// signal 未 abort 却抛 AbortError → provider/网络内部中断(连接被抽断/代理异常):
|
|
575
|
+
// 非用户取消,判为「连接中断」——与普通错误同归 error 分支,用结构化 category
|
|
576
|
+
// 标记连接中断(宿主按 category 映射友好文案),避免谎报为用户取消。
|
|
577
|
+
logger.warn({ err }, 'LLM 请求非用户取消原因中断,判为连接中断');
|
|
578
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
579
|
+
yield {
|
|
580
|
+
type: 'error',
|
|
581
|
+
// 连接中断分类:语义走 category 字段,不携带裸前缀;message 保留原始细节(调试/报错可追溯)
|
|
582
|
+
category: 'connection',
|
|
583
|
+
message: raw,
|
|
584
|
+
};
|
|
585
|
+
return { content, aborted, paused, failed: true };
|
|
586
|
+
}
|
|
587
|
+
// 超时(典型 = SSE 停摆看门狗 reject 的 DOMException TimeoutError)补结构化分类:
|
|
588
|
+
// 不补 → 宿主 `friendlyByCategory` 无键可映射 → 用户只能读到原始技术文案。
|
|
589
|
+
// 判据 SSOT = isTimeoutError(与上方 isTimeoutAbortSignal 同族,同一 name 判据、不同载体)。
|
|
590
|
+
// 注:非 abort 路径故不入 aborted 分支,它属「失败」并由本 catch 收口。
|
|
591
|
+
yield {
|
|
592
|
+
type: 'error',
|
|
593
|
+
...(isTimeoutError(err) ? { category: 'timeout' } : {}),
|
|
594
|
+
message: err instanceof Error ? err.message : String(err),
|
|
595
|
+
};
|
|
596
|
+
return { content, aborted, paused, failed: true };
|
|
525
597
|
}
|
|
598
|
+
finally {
|
|
599
|
+
// 释放暂停幂等锁覆盖三路——残留会让 requestPause 的幂等检查永久拒绝后续暂停请求
|
|
600
|
+
this._sessionManager?.cancelPendingPause();
|
|
601
|
+
// 同步清理 loop 的 pause 申请(clearPauseRequest 委托 interruptQueue 过滤 kind='pause' 条目)
|
|
602
|
+
this.requireLoop.clearPauseRequest();
|
|
603
|
+
}
|
|
604
|
+
return { content, aborted, paused, failed: false };
|
|
526
605
|
}
|
|
527
606
|
/**
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
|
|
531
|
-
|
|
607
|
+
* 暂停会话(用户/Agent/系统均可触发,暂停前自动创建检查点)
|
|
608
|
+
* @param lowRisk 低风险暂停不计入连续暂停计数(默认 false)
|
|
609
|
+
*/
|
|
610
|
+
pause(reason, source = 'user', lowRisk = false) {
|
|
611
|
+
this.assertInitialized('pause');
|
|
612
|
+
return this.requireSessionManager.pause(reason, source, lowRisk);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* 恢复会话(从 PAUSED 恢复到 RUNNING)
|
|
616
|
+
*/
|
|
617
|
+
resume() {
|
|
618
|
+
this.assertInitialized('resume');
|
|
619
|
+
return this.requireSessionManager.resume();
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* 自动恢复暂停状态(chat() 与续跑共用):
|
|
623
|
+
* ERROR 态直接 throw(拒绝,须先 recover 保持状态一致);PAUSED 态尝试自动恢复。
|
|
624
|
+
* 续跑(continueAfterPause)不走自动恢复,由独立的暂停恢复短路逻辑处理。
|
|
532
625
|
*
|
|
533
|
-
*
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
archiveMode: this.#config.archiveMode,
|
|
545
|
-
});
|
|
546
|
-
try {
|
|
547
|
-
await this.doPostProcess(input, assistantContent);
|
|
626
|
+
* @returns true=恢复成功或无需恢复;false=恢复失败(暂停超时等)
|
|
627
|
+
*/
|
|
628
|
+
autoResumeIfPaused() {
|
|
629
|
+
const sm = this._sessionManager;
|
|
630
|
+
if (!sm)
|
|
631
|
+
return true;
|
|
632
|
+
const status = sm.status;
|
|
633
|
+
if (status === 'error') {
|
|
634
|
+
throw configError('会话异常', '会话处于异常状态,无法接收新消息', [
|
|
635
|
+
'先标记 error.recovered=true 并调用 agent.recover()',
|
|
636
|
+
]);
|
|
548
637
|
}
|
|
549
|
-
|
|
550
|
-
|
|
638
|
+
if (status === 'paused') {
|
|
639
|
+
return this.resume();
|
|
551
640
|
}
|
|
641
|
+
return true;
|
|
552
642
|
}
|
|
553
643
|
/**
|
|
554
|
-
*
|
|
644
|
+
* 请求软暂停(不中断工作模型 v2.1)
|
|
555
645
|
*
|
|
556
|
-
*
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
logger.warn({ err, stage: 'profile' }, '归档失败');
|
|
574
|
-
this.emit(AGENT_EVENTS.archiveFailed, { stage: 'profile', message: message.slice(0, 200) });
|
|
575
|
-
});
|
|
576
|
-
history.registerPendingArchive(archiveFactsPromise);
|
|
646
|
+
* 经 sm.requestPause 暂存 pending(SessionStateMachine 是 pending 真理源)+ loop.pauseRequested
|
|
647
|
+
* 入 interruptQueue(在下一 step 边界挂起,不 abort)。
|
|
648
|
+
* 状态机翻 PAUSED 延后到 loop 边界真正挂起时——内核事实驱动而非申请即翻转;与硬停止(abort)严格区分,
|
|
649
|
+
* 软暂停保留历史、可经 resumeExecution 续跑。
|
|
650
|
+
*
|
|
651
|
+
* @returns true=请求已注册(下一 step 边界生效);false=无法暂停
|
|
652
|
+
*/
|
|
653
|
+
requestPause(reason, source = 'user') {
|
|
654
|
+
this.assertInitialized('requestPause');
|
|
655
|
+
const sm = this._sessionManager;
|
|
656
|
+
if (!sm)
|
|
657
|
+
return false;
|
|
658
|
+
// 状态机阻止:已暂停/已异常时禁止再申请暂停
|
|
659
|
+
const status = sm.status;
|
|
660
|
+
if (status === 'paused') {
|
|
661
|
+
logger.debug('requestPause 忽略:会话已处于暂停状态');
|
|
662
|
+
return false;
|
|
577
663
|
}
|
|
578
|
-
|
|
579
|
-
logger.
|
|
664
|
+
if (status === 'error') {
|
|
665
|
+
logger.debug('requestPause 忽略:会话处于异常状态,无法暂停');
|
|
666
|
+
return false;
|
|
580
667
|
}
|
|
581
|
-
//
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
668
|
+
// 空闲守卫:无活跃流时暂停无消费方——若此时同步翻状态机并落盘 checkpoint,
|
|
669
|
+
// 「turn 已完成后的暂停」会把会话钉在 paused,后续新输入被宿主路由成
|
|
670
|
+
// supplement(新意图被吞成"上一个回答的补充",毒化闭环节点;loop 无流消费的延迟翻转亦无意义)。
|
|
671
|
+
// 任务已结束 = 暂停申请作废,返回 false 供宿主明确反馈;系统/Agent 触发(ask_user/drift)均在
|
|
672
|
+
// 流中(_flowActive=true)不经过此分支,不受影响。
|
|
673
|
+
// ⚠ 判据必须用 _flowActive(流活跃)而非 chatLock.isBusy(锁持有)——chatLock 锁
|
|
674
|
+
// 180s 超时后自动释放(不中断生成流),长任务运行超锁期后 isBusy=false 但流
|
|
675
|
+
// 仍在产出,用锁判据会把「进行中的暂停申请」误作废成「任务已结束」;_flowActive 由 chat()/resumeExecution()
|
|
676
|
+
// 的流生命周期显式维护,与锁超时无关,是「任务是否仍在运行」的准确判据。
|
|
677
|
+
if (!this._flowActive) {
|
|
678
|
+
logger.debug({ reason, source }, 'requestPause 空闲守卫:任务已结束,暂停申请作废');
|
|
679
|
+
return false;
|
|
591
680
|
}
|
|
592
|
-
|
|
593
|
-
|
|
681
|
+
// 委托 SessionStateMachine 管理 pending 暂停状态(含幂等检查)
|
|
682
|
+
if (!sm.requestPause(reason, source)) {
|
|
683
|
+
logger.debug('requestPause 忽略:已有待处理的暂停请求');
|
|
684
|
+
return false;
|
|
594
685
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
686
|
+
this.requireLoop.requestPause();
|
|
687
|
+
return true;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* 取消待处理的软暂停请求:清空 loop 的 pauseRequested 标志(与 requestPause 对称),
|
|
691
|
+
* 并同步清理 SessionStateMachine 的 pending 暂停状态。
|
|
692
|
+
*/
|
|
693
|
+
cancelPauseRequest() {
|
|
694
|
+
this.assertInitialized('cancelPauseRequest');
|
|
695
|
+
this.requireLoop.clearPauseRequest();
|
|
696
|
+
this._sessionManager?.cancelPendingPause();
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* 是否存在待处理的暂停申请(申请已发、尚未在 loop 边界挂起)
|
|
700
|
+
*
|
|
701
|
+
* 供宿主 UI 区分三态:无申请(暂停)/ 申请在途(取消暂停)/ 已暂停(继续)。
|
|
702
|
+
* 状态真理源 SessionStateMachine.isPausePending():流中 requestPause 置位、边界挂起后 consumePendingPause 消费、
|
|
703
|
+
* cancelPauseRequest 主动清理;空闲态由 requestPause 空闲守卫作废(见上,return false 不触达状态机)——
|
|
704
|
+
* isPausePending 仅在流中「申请在途」时为 true(空闲态不翻 PAUSED)。
|
|
705
|
+
*/
|
|
706
|
+
isPausePending() {
|
|
707
|
+
return this._sessionManager?.isPausePending() ?? false;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* 放弃当前暂停检查点(宿主 stop 语义的内核出口)
|
|
711
|
+
*
|
|
712
|
+
* 与 pause() 对称:pause 创建检查点;discardCurrentCheckpoint 销毁检查点。
|
|
713
|
+
* 宿主 handleStop 在 paused 态调用——用户决定彻底放弃暂停执行,不再 resume。
|
|
714
|
+
*
|
|
715
|
+
* 除转发 SessionManager.discardCheckpoint(清内存 + 状态机 resetToRunning——检查点不落盘,无存储清理动作),
|
|
716
|
+
* 还须协同 loop.clearPendingInterjections() 清排队插话——pause 生效时 step 边界优先返回不消费 queue,
|
|
717
|
+
* 只清 checkpoint 会留孤儿 queue,下次 chat() 首个 step 误消费残留(坑)。
|
|
718
|
+
* 返回值以 checkpoint 清理为主(queue 有残留不判失败)。
|
|
719
|
+
* @returns true=成功清理;false=无暂停检查点(空闲态调了个空)
|
|
720
|
+
*/
|
|
721
|
+
discardCurrentCheckpoint() {
|
|
722
|
+
this.assertInitialized('discardCurrentCheckpoint');
|
|
723
|
+
// 协同清理暂停上下文的两个组成部分:checkpoint(内存态暂停点,不落盘)+ pendingInterjections(排队插话)
|
|
724
|
+
// 暂停上下文 = checkpoint + queue,stop 语义下两者必须同清,不留孤儿
|
|
725
|
+
const checkpointCleared = this._sessionManager?.discardCheckpoint() ?? false;
|
|
726
|
+
// queue 清理独立:即使 checkpoint 已不存在(空闲态调),残留 queue 也应顺手清掉
|
|
727
|
+
const queueCleared = this.loop?.clearPendingInterjections() ?? 0;
|
|
728
|
+
if (queueCleared > 0) {
|
|
729
|
+
logger.info({ count: queueCleared }, 'discardCheckpoint 协同清理残留 pendingInterjections');
|
|
607
730
|
}
|
|
731
|
+
return checkpointCleared;
|
|
608
732
|
}
|
|
609
733
|
/**
|
|
610
|
-
*
|
|
734
|
+
* 执行中插话(申请):把用户补充输入排队,当前 step 完成后在 step 边界统一注入为 user 消息。
|
|
735
|
+
*
|
|
736
|
+
* 与 requestPause(step 边界挂起待续跑)同为「申请 → 气口生效」——不中断当前 LLM/工具执行,
|
|
737
|
+
* 只在边界拿到补充输入后开始下一轮 step。插话是唯一写入口(interject → pendingInterjections 排队),
|
|
738
|
+
* 无「立即中断」模式:只有单一申请模式(无 inputInterrupt 立即中断通道)。
|
|
739
|
+
*
|
|
740
|
+
* 归属:插话同时以「补充」交互输入持久化到当前闭环节点(interactiveInputs),
|
|
741
|
+
* 保证跨重启重放时插话内容不丢失、不分裂新轮。持久化 fire-and-forget(appendUser 内部
|
|
742
|
+
* 已 catch 写入失败,仅记日志),不阻塞插话本身。
|
|
611
743
|
*/
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
744
|
+
interject(content) {
|
|
745
|
+
this.assertInitialized('interject');
|
|
746
|
+
// 持久化插话为闭环节点交互补充输入(roundId 非空时才写;空=无在途闭环,跳过)
|
|
747
|
+
const closureRoundId = this.loop?.getCurrentRoundId();
|
|
748
|
+
if (closureRoundId) {
|
|
749
|
+
void this.history?.appendUser(content, closureRoundId, {
|
|
750
|
+
interactive: true,
|
|
751
|
+
kind: 'supplement',
|
|
752
|
+
});
|
|
618
753
|
}
|
|
619
|
-
|
|
754
|
+
this.requireLoop.interject(content);
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* 删除待注入的插话(宿主 UI 层用户后悔)。与 interject 对称。
|
|
758
|
+
* 注意:interject 时已持久化到 history(交互输入归属),此处只从内核队列移除,不删除已落盘记录——
|
|
759
|
+
* 未来若需完整"撤回"需额外方案(如软删标记),当前先保证 step 边界不会注入已删除的补充。
|
|
760
|
+
* @param index 要删除的插话在队列中的位置(宿主镜像数组与内核队列同序同长度)
|
|
761
|
+
* @returns true=成功删除;false=index 越界或队列为空
|
|
762
|
+
*/
|
|
763
|
+
removePendingInterject(index) {
|
|
764
|
+
this.assertInitialized('removePendingInterject');
|
|
765
|
+
return this.requireLoop.removePendingInterject(index);
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* 清空全部待注入插话(宿主「全部清空」按钮触发,或 stop→discard 协同清理)。
|
|
769
|
+
*
|
|
770
|
+
* 逻辑下沉 loop.clearPendingInterjections 原子方法——清队列唯一入口在内核,
|
|
771
|
+
* 宿主不维护队列镜像,避免镜像与内核双写不一致;discardCurrentCheckpoint 经同一方法协同清理。
|
|
772
|
+
* @returns 被清除的条目数(宿主可用于 notice 反馈;无队列时返回 0)
|
|
773
|
+
*/
|
|
774
|
+
clearPendingInterjections() {
|
|
775
|
+
this.assertInitialized('clearPendingInterjections');
|
|
776
|
+
return this.requireLoop.clearPendingInterjections();
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* 读取当前待注入插话队列快照(宿主渲染层只读镜像,不修改内核状态)。
|
|
780
|
+
* 返回副本,宿主无法暗改内核 queue;宿主渲染直接读本快照,不另维护队列镜像。
|
|
781
|
+
*/
|
|
782
|
+
getPendingInterjections() {
|
|
783
|
+
this.assertInitialized('getPendingInterjections');
|
|
784
|
+
return this.requireLoop.getPendingInterjections();
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* 放弃在途主动提问(ask_user 工具挂起后,宿主「跳过/取消提问」时调用):
|
|
788
|
+
* 委托 loop.cancelAsk 补占位 tool 结果(防 assistant.tool_calls 无配对 → 400),
|
|
789
|
+
* 随后 resumeExecution() 续跑。与 answerQuestion 二选一消费在途提问。
|
|
790
|
+
*/
|
|
791
|
+
cancelAsk() {
|
|
792
|
+
this.assertInitialized('cancelAsk');
|
|
793
|
+
this.requireLoop.cancelAsk();
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* 回答在途主动提问(ask_user 工具):
|
|
797
|
+
* 答案以 ask_user 的 tool result 回填(与 assistant.tool_calls 配对,结构合法)。
|
|
798
|
+
* 调用后宿主以 resumeExecution(回答文本, undefined, 'question-answer') 续跑——
|
|
799
|
+
* 回答文本同时作为新 user 输入注入并记录为闭环节点交互输入(round 不分裂),
|
|
800
|
+
* 与既有续跑主流程一致(本方法只负责结构化回填,不做历史记录,避免双写)。
|
|
801
|
+
* 返回 false 表示无在途提问(提供方未调用 / 已消费)。
|
|
802
|
+
*/
|
|
803
|
+
answerQuestion(answers) {
|
|
804
|
+
this.assertInitialized('answerQuestion');
|
|
805
|
+
return this.requireLoop.answerQuestion(answers);
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* 追加计划任务项(SESSION_APPEND_TASK 落点):在现有 plan 末尾追加新任务项
|
|
809
|
+
*/
|
|
810
|
+
appendPlanItem(description) {
|
|
811
|
+
this.assertInitialized('appendPlanItem');
|
|
812
|
+
const sm = this.requireSessionManager;
|
|
813
|
+
return sm.appendPlanItem(description);
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* 卸载运行态挂载物:清空检查点计划与任务项推进日志(宿主任务流结束/停止/异常广播 idle 前调用),
|
|
817
|
+
* 回到"空闲 = 无挂载物"的资源层常态;会话历史与记忆等资源层内容不受影响。
|
|
818
|
+
*/
|
|
819
|
+
clearPlan() {
|
|
820
|
+
this.assertInitialized('clearPlan');
|
|
821
|
+
this.requireSessionManager.clearPlan();
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* turn 结束自动收尾:非暂停态 → 无条件 clearPlan(任务表收紧为 turn 内能力,不跨 turn 残留)。
|
|
825
|
+
*
|
|
826
|
+
* 任务表生命周期 = 单个 turn:LLM 在 turn 内编排并完成任务表;chat() 流退出(turn 真正结束)
|
|
827
|
+
* 时,无论任务项是否全部标记完成,都清空运行时挂载物(checkpoint.plan 与 planItemLog)。这样:
|
|
828
|
+
* ① 宏任务一个 turn 完不成 → 本 turn 结束兜底丢弃,下个 turn 由 LLM 重新规划全新任务表;
|
|
829
|
+
* ② 续跑轮(resumeExecution = ask_user 问答/暂停补充)是同一 turn 的闭环收尾,与 chat() 同构
|
|
830
|
+
* 走本方法——turn 真正结束即无条件清空,chat() 与续跑轮共用本收口点。
|
|
831
|
+
*
|
|
832
|
+
* 唯一例外是暂停态(pauseMeta):turn 尚未真正结束,暂停恢复后要继续用 plan 推进,
|
|
833
|
+
* 故保留不清;恢复完成后该 turn 最终退出时仍会走本方法无条件清空。
|
|
834
|
+
*
|
|
835
|
+
* 运行时状态(plan/planItemLog)清空,但对话记录里的 round-block 折叠块已沉淀为历史(不落盘删除)。
|
|
836
|
+
* 挂点:chat() 与 resumeExecution 的 finally 块(generator close 时触发,确保所有 yield 已被宿主消费)
|
|
837
|
+
*/
|
|
838
|
+
clearPlanOnTurnEnd() {
|
|
839
|
+
const sm = this._sessionManager;
|
|
840
|
+
if (!sm)
|
|
841
|
+
return;
|
|
842
|
+
const cp = sm.getCheckpoint();
|
|
843
|
+
if (!cp?.plan || cp.plan.length === 0)
|
|
844
|
+
return;
|
|
845
|
+
if (cp.pauseMeta)
|
|
846
|
+
return; // 暂停态:turn 未真正结束,恢复时需继续用 plan,保留不清
|
|
847
|
+
sm.clearPlan();
|
|
620
848
|
}
|
|
621
849
|
/**
|
|
622
|
-
*
|
|
850
|
+
* 内核→宿主信号:当前会话是否可"无输入续跑"(决定暂停按钮显隐 + 暂停后继续 UI)
|
|
623
851
|
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
852
|
+
* 真值条件:① 状态机已 paused(已软暂停必可续跑,最高优先级——pauseRequested 在下一 step 边界才挂起,
|
|
853
|
+
* 彼时 inAutonomousStep 已重置为 false,仅看它会误报);② loop 在自主工具步(可暴露暂停按钮在边界挂起);
|
|
854
|
+
* ③ 存在未完成的计划任务项(多轮任务可续跑下一轮)。三者皆否(纯单轮、无待续目标)→ 隐藏暂停按钮(仅停止)。
|
|
855
|
+
*/
|
|
856
|
+
canContinueWithoutInput() {
|
|
857
|
+
if (this._sessionManager?.status === 'paused')
|
|
858
|
+
return true;
|
|
859
|
+
// 错误态不展示"继续":error 态由检查点恢复回填进入,须显式处理(重新开始或 recover),避免点了静默无反应
|
|
860
|
+
if (this._sessionManager?.status === 'error')
|
|
861
|
+
return false;
|
|
862
|
+
// 仅 pending/active(可推进)任务项计入"可续跑"——blocked 不续(全 blocked 由 isPlanAllBlocked 拦,
|
|
863
|
+
// 避免全 blocked 计划按钮可点但 resumeExecution 早退)。判定走 SessionManager.hasInflightPlan
|
|
864
|
+
// 单一真理源(禁内联谓词,SSOT 见 sessionManager.ts hasInflightPlan 注释),与 prepare/loop 同源。
|
|
865
|
+
const hasPendingPlan = this._sessionManager?.hasInflightPlan() ?? false;
|
|
866
|
+
return this.requireLoop.isInAutonomousStep || hasPendingPlan;
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* 触发会话异常(公开 API,宿主显式调用):仅 RUNNING 状态可触发,异常时自动创建检查点保存状态。
|
|
870
|
+
* 生产内部无调用者——运行时异常(LLM 超时/工具失败)走 yield {type:'error'} 事件流不翻状态机;
|
|
871
|
+
* 本 API 供宿主在自定义异常(如外部服务故障)显式触发,或经检查点恢复回填进入 ERROR 态。
|
|
626
872
|
*/
|
|
627
|
-
|
|
873
|
+
triggerError(cause) {
|
|
874
|
+
this.assertInitialized('triggerError');
|
|
875
|
+
return this.requireSessionManager.triggerError(cause);
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* 从异常恢复:校验恢复条件(error.recovered === true 且 cause 已解除)
|
|
879
|
+
*/
|
|
880
|
+
recover() {
|
|
881
|
+
this.assertInitialized('recover');
|
|
882
|
+
return this.requireSessionManager.recover();
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* 创建会话检查点:快照当前运行时状态(热记忆等),生成纯内存态快照
|
|
886
|
+
* (不落盘、无序列化/恢复路径——中止/断电走「中断轮补全为完整 turn」)。
|
|
887
|
+
* 无 role/standard 会话能力位,createCheckpoint 仅收 mainGoal。
|
|
888
|
+
*/
|
|
889
|
+
createCheckpoint(mainGoal) {
|
|
890
|
+
this.assertInitialized('createCheckpoint');
|
|
891
|
+
return this.requireSessionManager.createCheckpoint(mainGoal);
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* 获取当前检查点
|
|
895
|
+
*/
|
|
896
|
+
getCheckpoint() {
|
|
897
|
+
this.assertInitialized('getCheckpoint');
|
|
898
|
+
return this.requireSessionManager.getCheckpoint();
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* 获取最近一次处理线程的工具执行历史(loop 域聚合出口,补待办 #3)
|
|
902
|
+
*
|
|
903
|
+
* 数据源自 sessionManager 检查点的 completedToolCalls(已由
|
|
904
|
+
* AGENT_CONSTANTS.COMPLETED_TOOL_CALLS_MAX 做 FIFO 封顶)。本方法剥离「恢复/幂等」内部
|
|
905
|
+
* 语义,提供稳定的 execute 域只读契约——宿主可在透明面板/观测面消费本轮及历史的工具调用
|
|
906
|
+
* 明细(工具名 / 成败 / 结果摘要),与累计 AgentMetrics.tools 互补。
|
|
907
|
+
*
|
|
908
|
+
* @param limit 返回条数上限(默认取封顶上限,超界由调用方控制,取最近 limit 条)
|
|
909
|
+
* @returns 工具执行记录(记录顺序:时间正序即旧→新;无记录返回空数组;返回浅拷贝防宿主改动检查点)
|
|
910
|
+
*/
|
|
911
|
+
getRecentToolExecutions(limit = AGENT_CONSTANTS.COMPLETED_TOOL_CALLS_MAX) {
|
|
912
|
+
this.assertInitialized('getRecentToolExecutions');
|
|
913
|
+
const calls = this.requireSessionManager.getCheckpoint()?.completedToolCalls;
|
|
914
|
+
if (!calls || calls.length === 0)
|
|
915
|
+
return [];
|
|
916
|
+
// 取最近 limit 条并浅拷贝,避免宿主引用改动污染检查点(FIFO 封顶后 slice 安全)
|
|
917
|
+
return calls.slice(-limit).map((r) => ({ ...r }));
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* 向 loop 注入 system 消息
|
|
921
|
+
*/
|
|
922
|
+
injectSystemMessage(message) {
|
|
923
|
+
this.requireLoop.injectSystemMessage(message);
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* 从指定 Round 位置分叉当前会话(委托至 SessionManager):
|
|
927
|
+
* round-based 模式唯一分叉方式——创建新会话,复制 Round ID 列表(指针复制),
|
|
928
|
+
* 新会话是完全平等的普通会话。记忆索引全局共享不受影响。
|
|
929
|
+
*
|
|
930
|
+
* 自动命名场景(未传 targetSession):分叉后异步触发 SessionNamer 为新会话生成标题
|
|
931
|
+
* (best-effort,不阻塞分叉返回);用户显式指定会话名时尊重用户命名,不触发 LLM 覆盖。
|
|
932
|
+
*
|
|
933
|
+
* @param roundId - 分叉点的 Round ID(可选;不传默认使用最后一个 Round)
|
|
934
|
+
* @param targetSession - 可选,自定义新会话名
|
|
935
|
+
*/
|
|
936
|
+
forkSession(roundId, targetSession) {
|
|
628
937
|
this.assertInitialized('forkSession');
|
|
629
|
-
|
|
938
|
+
const result = this.requireSessionManager.forkSession(roundId, targetSession);
|
|
939
|
+
// 分叉会话首轮命名:仅自动命名(未传 targetSession)时触发,
|
|
940
|
+
// 以分叉点首轮用户消息为标题依据(与新会话首次问答命名同源语义)
|
|
941
|
+
if (!targetSession) {
|
|
942
|
+
void this.ensureForkSessionTitle(result).catch((err) => {
|
|
943
|
+
logger.debug({ err, session: result.newSession }, '分叉会话首轮命名失败(best-effort,不影响分叉结果)');
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
return result;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* 分叉会话首轮命名(best-effort,fire-and-forget)
|
|
950
|
+
*
|
|
951
|
+
* 分叉新会话是平等普通会话,自动命名(未提供 targetSession)时应拥有 LLM 生成的语义标题。
|
|
952
|
+
* 取新会话第一个 Round 的用户消息作为命名输入(分叉点即新会话起点,语义=首轮问答);
|
|
953
|
+
* SessionNamer 内部已有 autoName 存在即跳过(幂等),LLM 失败/无价值降级占位标题。
|
|
954
|
+
*
|
|
955
|
+
* @param result - 分叉结果(含新会话名/日期/Round 列表)
|
|
956
|
+
*/
|
|
957
|
+
async ensureForkSessionTitle(result) {
|
|
958
|
+
const namer = this.internals.sessionNamer;
|
|
959
|
+
if (!namer) {
|
|
960
|
+
logger.debug('ensureForkSessionTitle: sessionNamer 未创建,跳过分叉会话命名');
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
const roundStore = this.#config.roundStore;
|
|
964
|
+
const firstRoundId = result.roundIds[0];
|
|
965
|
+
if (!roundStore || !firstRoundId) {
|
|
966
|
+
logger.debug({ hasRoundStore: !!roundStore }, 'ensureForkSessionTitle: 无 RoundStore/新会话为空,跳过');
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
// 分叉点首轮用户消息 = 新会话的"首条消息",作为标题生成输入
|
|
970
|
+
const firstRound = roundStore.getById(firstRoundId);
|
|
971
|
+
const firstUserContent = firstRound?.userMessage?.content;
|
|
972
|
+
if (!firstUserContent) {
|
|
973
|
+
logger.debug({ roundId: firstRoundId }, 'ensureForkSessionTitle: 首轮无用户消息,跳过');
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
await namer.ensureSessionTitle(result.date, result.newSession, firstUserContent);
|
|
630
977
|
}
|
|
631
978
|
/**
|
|
632
|
-
*
|
|
979
|
+
* 切换到指定项目:切换后自动 rebuildComponents();Agent 级记忆存储(宿主注入,实例不随项目切换重建)保留,项目级配置(.memora/)重新加载
|
|
633
980
|
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
981
|
+
* `nameOrPath` 只接受两种输入:① 项目注册表中的项目名(依赖注册表跨项目共享,见 dataDir/registryDir);
|
|
982
|
+
* ② 项目根目录的**绝对路径**。二者都不满足时抛 configError——否则相对串会以 process.cwd()
|
|
983
|
+
* 为基准解析,静默造出非预期的项目目录并占用其锁。
|
|
636
984
|
*/
|
|
637
985
|
async switchProject(nameOrPath) {
|
|
638
986
|
this.assertInitialized('switchProject', ['projectManager', 'provider']);
|
|
@@ -645,6 +993,14 @@ export class Agent extends TypedEventEmitter {
|
|
|
645
993
|
const nameOrPathLower = nameOrPath.toLowerCase();
|
|
646
994
|
target = projects.find((p) => p.name.toLowerCase() === nameOrPathLower || p.path.toLowerCase() === nameOrPathLower);
|
|
647
995
|
}
|
|
996
|
+
// 注册表未命中时按路径处理。相对路径会以 process.cwd() 为基准解析——宿主进程的 cwd
|
|
997
|
+
// 不具项目语义,会静默创建出非预期的项目目录并占用其锁,故只接受绝对路径。
|
|
998
|
+
if (!target && !isAbsolute(nameOrPath)) {
|
|
999
|
+
throw configError('项目切换失败', `「${nameOrPath}」既不是已注册的项目名,也不是绝对路径`, [
|
|
1000
|
+
'按名切换:先经 agent.projects.registerProject(项目路径, 项目名) 注册,并保证注册表跨项目共享(多项目共用同一 dataDir 或 registryDir)',
|
|
1001
|
+
'按路径切换:传入项目根目录的绝对路径',
|
|
1002
|
+
]);
|
|
1003
|
+
}
|
|
648
1004
|
const projectPath = target ? target.path : nameOrPath;
|
|
649
1005
|
const projectName = target ? target.name : getBaseName(nameOrPath);
|
|
650
1006
|
const newPctx = await pm.initProject(projectPath, projectName, this.#config.configDir);
|
|
@@ -653,7 +1009,7 @@ export class Agent extends TypedEventEmitter {
|
|
|
653
1009
|
// 先尝试重建组件,失败时回滚 pctx 防止状态不一致
|
|
654
1010
|
try {
|
|
655
1011
|
this.pctx = newPctx;
|
|
656
|
-
await this.
|
|
1012
|
+
await this.rebuildComponents();
|
|
657
1013
|
}
|
|
658
1014
|
catch (err) {
|
|
659
1015
|
// 回滚:恢复旧 pctx(若存在)
|
|
@@ -673,121 +1029,162 @@ export class Agent extends TypedEventEmitter {
|
|
|
673
1029
|
}
|
|
674
1030
|
// ─── 组件组装 ─────────────────────────────────────────
|
|
675
1031
|
/**
|
|
676
|
-
*
|
|
1032
|
+
* 组装所有运行时组件(一行委托给 assembler 工厂)
|
|
1033
|
+
*
|
|
1034
|
+
* 接线回调(工具幂等 outbox / 任务表 / loop 回调 / createSessionManager)与装配私有状态
|
|
1035
|
+
* (暂存队列 / 停滞计数器)全部由 assembler 组装收口——Agent 只注入稳定能力(hooks)。
|
|
677
1036
|
*/
|
|
678
1037
|
async assembleComponents(pctx) {
|
|
679
1038
|
const result = await assembleComponents(pctx, {
|
|
680
1039
|
provider: this.provider,
|
|
681
1040
|
backgroundProvider: this.#backgroundProvider,
|
|
1041
|
+
providerRouter: this.#providerRouter,
|
|
682
1042
|
projectPath: this.#config.projectPath,
|
|
683
1043
|
configDir: this.#config.configDir,
|
|
684
|
-
|
|
1044
|
+
activeRolePack: this.#config.activeRolePack,
|
|
1045
|
+
// 组数据(宿主装配级):组长角色包 + 组员名单(会议名单容器)
|
|
1046
|
+
rolePackTeams: this.#config.rolePackTeams,
|
|
685
1047
|
maxContextTokens: this.#config.maxContextTokens,
|
|
1048
|
+
// per-LLM 输出预算默认值:透传组装器 → AgentLoop.defaultMaxTokens(buildChatOptions 填底座)
|
|
1049
|
+
maxTokens: this.#config.maxTokens,
|
|
686
1050
|
sessionStore: this.#config.sessionStore,
|
|
687
|
-
|
|
1051
|
+
roundStore: this.#config.roundStore,
|
|
688
1052
|
tracer: this.#config.tracer,
|
|
689
1053
|
messages: this.#config.messages,
|
|
690
1054
|
enableContextSummary: this.#config.enableContextSummary,
|
|
1055
|
+
webSearchProvider: this.#config.webSearchProvider,
|
|
1056
|
+
fetchProvider: this.#config.fetchProvider,
|
|
1057
|
+
codeExecutionProvider: this.#config.codeExecutionProvider,
|
|
1058
|
+
projectSearchProvider: this.#config.projectSearchProvider,
|
|
1059
|
+
// 脚本执行 node 路径(可选):透传装配 → toolExecutor/assembler 脚本执行回调
|
|
1060
|
+
scriptNodePath: this.#config.scriptNodePath,
|
|
1061
|
+
// 禁用技能清单:透传装配 → SkillManager.setDisabledSkills
|
|
1062
|
+
disabledSkills: this.#config.disabledSkills,
|
|
691
1063
|
existingSkillManager: this.skillManager,
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
1064
|
+
// 策略覆盖不经组装器:AssembleInput 零消费,真实消费链 = 本类
|
|
1065
|
+
// 构造 SeedOrchestrator deps 时直传(见 #createSeedOrchestrator 的 strategyOverride),
|
|
1066
|
+
// 经 resolveActiveStrategy 压过角色包声明——策略解析唯一链在 seed 侧。
|
|
1067
|
+
// Agent 稳定能力(emit/守卫/暂停/角色切换):接线下沉后仅传能力,接线语义在组装器唯一实现
|
|
1068
|
+
hooks: {
|
|
1069
|
+
// 桥接 SessionManager 宽类型事件到 Agent 强类型 emit(校验事件名在 AgentEventMap 内,避免不安全断言)
|
|
1070
|
+
emit: (event, data) => {
|
|
1071
|
+
if (AGENT_EVENT_SET.has(event)) {
|
|
1072
|
+
this.emit(event, data);
|
|
1073
|
+
}
|
|
1074
|
+
},
|
|
1075
|
+
isChatBusy: () => this.internals.chatLockManager?.isBusy ?? false,
|
|
1076
|
+
requestPause: (reason, source) => {
|
|
1077
|
+
this.requestPause(reason, source);
|
|
1078
|
+
},
|
|
1079
|
+
preExecutionCheck: this.#config.preExecutionCheck,
|
|
1080
|
+
// 检查点恢复协议角色契约重注入 → Agent 生命周期(工具暴露面 / loop 前缀刷新)
|
|
1081
|
+
applyRolePackToolExposure: () => this.applyRolePackToolExposure(),
|
|
1082
|
+
refreshRolePackPrefixOnLoop: () => this.refreshRolePackPrefixOnLoop(),
|
|
1083
|
+
// 会议逐项切换:随 active 任务项刷新本轮装配视角
|
|
1084
|
+
applyActivePlanItemAssembly: () => this.applyActivePlanItemAssemblyIfChanged(),
|
|
703
1085
|
},
|
|
704
1086
|
});
|
|
705
1087
|
this.history = result.history;
|
|
706
1088
|
this.loop = result.loop;
|
|
1089
|
+
// 有效窗口变更 → 同步 ContextPreparer。有效窗口的**唯一计算点在 loop**
|
|
1090
|
+
// (min(provider 窗口, 角色包 contextLimit)),此处只做分发;用闭包读最新
|
|
1091
|
+
// internals.contextPreparer,避免与装配顺序耦合。
|
|
1092
|
+
this.loop.onContextWindowChanged = (effectiveTokens) => {
|
|
1093
|
+
this.internals.contextPreparer?.setMaxContextTokens(effectiveTokens);
|
|
1094
|
+
};
|
|
707
1095
|
this.toolExec = result.toolExec;
|
|
708
|
-
this.personaManager = result.personaManager;
|
|
709
|
-
this.#userProfile = result.userProfile;
|
|
710
1096
|
this.skillManager = result.skillManager;
|
|
711
|
-
this.
|
|
712
|
-
|
|
1097
|
+
this._rolePackManager = result.rolePackManager;
|
|
1098
|
+
// 角色包切换锁定事件:rolePackManager 触发锁定时,Agent 向宿主发射 rolePackSwitchLocked 事件
|
|
1099
|
+
this._rolePackManager.onRolePackSwitchLocked((reason, lockedSeconds) => {
|
|
1100
|
+
this.emit(AGENT_EVENTS.rolePackSwitchLocked, { reason, lockedSeconds });
|
|
1101
|
+
});
|
|
713
1102
|
this.memoryInspector = result.memoryInspector;
|
|
714
|
-
this.dedupManager = result.dedupManager;
|
|
715
|
-
this.memoryAdvisor = result.memoryAdvisor;
|
|
716
|
-
this.autoConfigRefiner = result.autoConfigRefiner;
|
|
1103
|
+
this.internals.dedupManager = result.dedupManager;
|
|
1104
|
+
this.internals.memoryAdvisor = result.memoryAdvisor;
|
|
717
1105
|
this.workProjection = result.workProjection;
|
|
718
|
-
this.sessionArchiver = result.sessionArchiver;
|
|
1106
|
+
this.internals.sessionArchiver = result.sessionArchiver;
|
|
719
1107
|
this.textPolisher = result.textPolisher;
|
|
720
|
-
|
|
721
|
-
//
|
|
722
|
-
this.
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
1108
|
+
this.internals.roundSummaryGenerator = result.roundSummaryGenerator;
|
|
1109
|
+
// 会话管理器由组装器创建(先于 loop),Agent 直接持有
|
|
1110
|
+
this._sessionManager = result.sessionManager;
|
|
1111
|
+
// 输入增强管线由组装器创建,Agent 只保留编排调用点
|
|
1112
|
+
this.internals.contextPreparer = result.contextPreparer;
|
|
1113
|
+
}
|
|
1114
|
+
// ─── Provider 管理 ────────────────────────────────────
|
|
1115
|
+
setProvider(provider) {
|
|
1116
|
+
// 对话中切换会导致同循环内前后两次 LLM 调用命中不同 Provider(上下文窗口假设/工具格式不一致)
|
|
1117
|
+
this.assertNotBusy('切换 Provider');
|
|
1118
|
+
this.#provider = provider;
|
|
1119
|
+
if (this.loop) {
|
|
1120
|
+
this.loop.setProvider(provider);
|
|
728
1121
|
}
|
|
729
|
-
|
|
730
|
-
this._sessionManager = this.createSessionManager();
|
|
1122
|
+
logger.info({ provider: this.#provider.name }, 'Provider 已切换');
|
|
731
1123
|
}
|
|
732
1124
|
/**
|
|
733
|
-
*
|
|
1125
|
+
* 运行时更新上下文窗口上限(token)
|
|
1126
|
+
*
|
|
1127
|
+
* 模型热切换配套:同步三处装配期值拷贝(loop 截断/软上限、ContextManager 阈值、
|
|
1128
|
+
* ContextPreparer 预算与占用快照),全部对新窗口即刻生效。对话进行中禁止更新
|
|
1129
|
+
* (与 setProvider 同守卫)。
|
|
1130
|
+
*
|
|
1131
|
+
* @param tokens 新窗口 token 数
|
|
734
1132
|
*/
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
1133
|
+
setContextWindow(tokens) {
|
|
1134
|
+
this.assertNotBusy('切换上下文窗口');
|
|
1135
|
+
this.#config.maxContextTokens = tokens;
|
|
1136
|
+
if (this.loop) {
|
|
1137
|
+
// loop 内部按 min(provider 窗口, 角色包 contextLimit) 重算**有效窗口**,并经
|
|
1138
|
+
// onContextWindowChanged 回调把有效值同步到 contextPreparer —— 计算点唯一(在 loop)。
|
|
1139
|
+
this.loop.setContextWindow(tokens);
|
|
1140
|
+
}
|
|
1141
|
+
else if (this.internals.contextPreparer) {
|
|
1142
|
+
// loop 尚未装配(早期阶段):无角色策略可叠加,直接用 provider 窗口
|
|
1143
|
+
this.internals.contextPreparer.setMaxContextTokens(tokens);
|
|
1144
|
+
}
|
|
1145
|
+
logger.info({ providerWindow: tokens }, 'provider 上下文窗口已更新');
|
|
741
1146
|
}
|
|
742
1147
|
/**
|
|
743
|
-
*
|
|
1148
|
+
* 运行时更新 per-LLM 输出预算默认值(token)
|
|
744
1149
|
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
* Agent 内部桥接到 TypedEventEmitter 的强类型 emit
|
|
749
|
-
* - 运行时校验事件名是否在 AgentEventMap 中,避免不安全的类型断言
|
|
1150
|
+
* Provider 热切换配套(与 setContextWindow 同链路):同步 #config 拷贝并即时生效于 loop——
|
|
1151
|
+
* 切换 Provider 后新配置的 maxTokens 无需重装 Agent。undefined = 清除(回服务端默认)。
|
|
1152
|
+
* 不设 assertNotBusy:maxTokens 是请求参数(非上下文结构),轮内下一迭代即用新值,无一致性风险。
|
|
750
1153
|
*
|
|
751
|
-
* @
|
|
1154
|
+
* @param tokens 新输出预算(token);undefined 清除
|
|
752
1155
|
*/
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
if (AGENT_EVENT_SET.has(event)) {
|
|
756
|
-
this.emit(event, data);
|
|
757
|
-
}
|
|
758
|
-
};
|
|
759
|
-
return new SessionManager(
|
|
760
|
-
// 惰性 getter:SessionManager 内部调用时 Agent 已 init,用 ! 窄化
|
|
761
|
-
() => this.history, () => this.loop, this.#config.sessionStore, () => this.chatLockManager?.isBusy ?? false, forwardEvent);
|
|
762
|
-
}
|
|
763
|
-
// ─── Provider 管理 ────────────────────────────────────
|
|
764
|
-
setProvider(provider) {
|
|
765
|
-
// 对话进行中切换 Provider 会导致同一 processUserInput 循环内前后两次 LLM 调用命中不同 Provider
|
|
766
|
-
// (模型上下文窗口假设不一致 → 可能导致上下文截断逻辑误判或 tool_call 格式不兼容)
|
|
767
|
-
this.assertNotBusy('切换 Provider');
|
|
768
|
-
this.#provider = provider;
|
|
1156
|
+
setMaxTokens(tokens) {
|
|
1157
|
+
this.#config.maxTokens = tokens;
|
|
769
1158
|
if (this.loop) {
|
|
770
|
-
this.loop.
|
|
1159
|
+
this.loop.setDefaultMaxTokens(tokens);
|
|
771
1160
|
}
|
|
772
|
-
logger.info({
|
|
1161
|
+
logger.info({ maxTokens: tokens }, 'per-LLM 输出预算已更新');
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* 读取本轮请求体**实际**携带的 max_tokens(生效值),供宿主取证面写 meta。
|
|
1165
|
+
*
|
|
1166
|
+
* 薄委托:优先级裁决收敛在 AgentLoop.buildChatOptions(见其注释),此处不复制规则——
|
|
1167
|
+
* 否则「谁压过谁」会在宿主侧长出第二真理源。loop 未装配时返回 undefined。
|
|
1168
|
+
*/
|
|
1169
|
+
getEffectiveMaxTokens() {
|
|
1170
|
+
return this.loop?.getEffectiveMaxTokens();
|
|
773
1171
|
}
|
|
774
1172
|
setBackgroundProvider(provider) {
|
|
775
1173
|
// 与 setProvider 一致,对话进行中禁止切换后台 Provider
|
|
776
1174
|
this.assertNotBusy('切换后台 Provider');
|
|
777
1175
|
this.#backgroundProvider = provider;
|
|
778
|
-
//
|
|
779
|
-
if (this.
|
|
780
|
-
this.
|
|
1176
|
+
// 同步更新所有后台组件(统一消费 backgroundProvider)
|
|
1177
|
+
if (this.internals.roundSummaryGenerator) {
|
|
1178
|
+
this.internals.roundSummaryGenerator.setBackgroundProvider(provider);
|
|
1179
|
+
}
|
|
1180
|
+
if (this.internals.sessionArchiver) {
|
|
1181
|
+
this.internals.sessionArchiver.setBackgroundProvider(provider);
|
|
781
1182
|
}
|
|
782
1183
|
logger.info({ hasBackground: !!provider }, '后台 Provider 已切换');
|
|
783
1184
|
}
|
|
784
|
-
// ───
|
|
1185
|
+
// ─── 归档模式管理 ───────────────────────────
|
|
785
1186
|
/**
|
|
786
|
-
*
|
|
787
|
-
*
|
|
788
|
-
* 与 setProvider 一致,对话进行中禁止切换(避免本轮 postProcess 行为不一致)。
|
|
789
|
-
*
|
|
790
|
-
* @param mode 目标模式
|
|
1187
|
+
* 运行时切换归档模式:对话进行中禁止切换(避免本轮 postProcess 行为不一致),无变更幂等返回
|
|
791
1188
|
*/
|
|
792
1189
|
setArchiveMode(mode) {
|
|
793
1190
|
this.assertNotBusy('切换归档模式');
|
|
@@ -799,284 +1196,172 @@ export class Agent extends TypedEventEmitter {
|
|
|
799
1196
|
logger.info({ from: prev, to: mode }, '归档模式已切换');
|
|
800
1197
|
}
|
|
801
1198
|
/**
|
|
802
|
-
*
|
|
803
|
-
*
|
|
804
|
-
* 宿主 UI(如设置面板)可据此同步显示当前模式。
|
|
1199
|
+
* 查询当前归档模式(宿主 UI 可据此同步显示)
|
|
805
1200
|
*/
|
|
806
1201
|
getArchiveMode() {
|
|
807
1202
|
return this.#config.archiveMode;
|
|
808
1203
|
}
|
|
809
1204
|
/**
|
|
810
|
-
*
|
|
811
|
-
*
|
|
812
|
-
* 与 doPostProcess 中的自动匹配走同一条事件链路,确保:
|
|
813
|
-
* 1. AgentLoop 的 systemPromptPrefix 立即刷新(下一次对话使用新角色 prompt)
|
|
814
|
-
* 2. 发射 personaSwitched 事件,触发 spriteLifecycleManager 的完整副作用:
|
|
815
|
-
* - emit('personaChanged') 通知宿主 UI
|
|
816
|
-
* - proactiveEngine.addNotice('persona', ...) 记录通知
|
|
817
|
-
* - perceptionCoordinator.refreshBeforeChat() 基于新角色 traits 重新推导情感基调
|
|
818
|
-
*
|
|
819
|
-
* 对话进行中切换角色会破坏当前 system prompt,与项目切换一致拒绝。
|
|
820
|
-
*
|
|
821
|
-
* @param name 目标角色名
|
|
822
|
-
* @returns 切换成功返回新角色的 system prompt 段;角色不存在或切换失败返回 null
|
|
823
|
-
*/
|
|
824
|
-
switchPersona(name) {
|
|
825
|
-
this.assertInitialized('switchPersona');
|
|
826
|
-
this.assertNotBusy('切换角色');
|
|
827
|
-
if (!this.personaManager)
|
|
828
|
-
return null;
|
|
829
|
-
// 同名切换幂等:直接返回当前 prompt,不触发事件链路
|
|
830
|
-
if (this.personaManager.activeName === name) {
|
|
831
|
-
return this.personaManager.buildSystemPrompt();
|
|
832
|
-
}
|
|
833
|
-
const prevName = this.personaManager.activeName;
|
|
834
|
-
try {
|
|
835
|
-
this.personaManager.switchPersona(name);
|
|
836
|
-
}
|
|
837
|
-
catch (err) {
|
|
838
|
-
// 角色不存在时 PersonaManager 抛 MemoraError,降级为 null 返回
|
|
839
|
-
logger.warn({ err: toError(err).message, name }, '手动切换角色失败');
|
|
840
|
-
return null;
|
|
841
|
-
}
|
|
842
|
-
// 同步刷新 AgentLoop 的角色前缀(关键:否则下一次对话仍用旧角色 prompt)
|
|
843
|
-
// 与 doPostProcess 自动匹配共用同一段逻辑,ADR-017 枝叶层 2 次提取
|
|
844
|
-
this.refreshPersonaPrefixOnLoop();
|
|
845
|
-
// 发射切换事件,触发宿主 UI 刷新 + 感知重推导 + 通知队列记录
|
|
846
|
-
this.emit(AGENT_EVENTS.personaSwitched, { from: prevName, to: name });
|
|
847
|
-
logger.info({ from: prevName, to: name }, '角色手动切换');
|
|
848
|
-
return this.personaManager.buildSystemPrompt();
|
|
849
|
-
}
|
|
850
|
-
/**
|
|
851
|
-
* 获取角色切换锁定状态(透传 PersonaManager,P0-2 用户体验打磨)
|
|
852
|
-
*
|
|
853
|
-
* 与 switchPersona 分离:switchPersona 仍返回 string | null 不变,
|
|
854
|
-
* 锁定原因查询走独立路径,避免破坏既有契约(cli.ts、sprite.test.ts 等消费者无感)。
|
|
855
|
-
*
|
|
856
|
-
* 宿主 IPC 层调用此方法前置判断锁定状态,区分"切换失败"原因
|
|
857
|
-
* (locked / busy / not_found / invalid),让用户知道为什么没反应。
|
|
858
|
-
*
|
|
859
|
-
* @returns locked 是否处于锁定状态;unlockAt 锁定自动恢复时间戳(ms epoch),未锁定时为 null
|
|
1205
|
+
* 获取角色包切换锁定状态(透传 RolePackManager,宿主 IPC 层据此区分"切换失败"原因)
|
|
1206
|
+
* @returns locked 是否锁定;unlockAt 锁定自动恢复时间戳(ms epoch),未锁定时为 null
|
|
860
1207
|
*/
|
|
861
|
-
|
|
862
|
-
this.assertInitialized('
|
|
863
|
-
if (!this.
|
|
1208
|
+
getRolePackSwitchLockStatus() {
|
|
1209
|
+
this.assertInitialized('getRolePackSwitchLockStatus');
|
|
1210
|
+
if (!this._rolePackManager)
|
|
864
1211
|
return { locked: false, unlockAt: null };
|
|
865
|
-
return this.
|
|
1212
|
+
return this._rolePackManager.getSwitchLockStatus();
|
|
866
1213
|
}
|
|
867
1214
|
/**
|
|
868
|
-
*
|
|
1215
|
+
* 获取当前激活角色包的 traits(宿主情感计算 API)
|
|
869
1216
|
*
|
|
870
|
-
*
|
|
871
|
-
* 组装规则:[personaPrompt, profilePrompt].filter(Boolean).join('\n\n') + 末尾分隔符 '---'
|
|
872
|
-
*
|
|
873
|
-
* 调用时机:
|
|
874
|
-
* - tryAutoMatchPersona 中角色自动匹配成功后(chat() 回答前)
|
|
875
|
-
* - switchPersona 手动切换成功后
|
|
876
|
-
* - loop 为 null 时静默跳过(init 前或 close 后的边界场景)
|
|
1217
|
+
* @returns traits 键值对,无激活角色包或无 traits 时返回 undefined
|
|
877
1218
|
*/
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
return;
|
|
881
|
-
const profilePrompt = this.userProfile?.buildSystemPrompt() ?? '';
|
|
882
|
-
const personaPrompt = this.personaManager?.buildSystemPrompt() ?? '';
|
|
883
|
-
const newPrefix = [personaPrompt, profilePrompt].filter(Boolean).join('\n\n') +
|
|
884
|
-
([personaPrompt, profilePrompt].some(Boolean) ? '\n\n---\n\n' : '');
|
|
885
|
-
this.loop.refreshPersonaPrefix(newPrefix);
|
|
1219
|
+
getActiveTraits() {
|
|
1220
|
+
return this._rolePackManager?.getActiveTraits();
|
|
886
1221
|
}
|
|
887
1222
|
/**
|
|
888
|
-
*
|
|
889
|
-
*
|
|
890
|
-
* 在 chat() 回答前执行,确保本轮 LLM 调用就用匹配到的角色 system prompt。
|
|
891
|
-
* 两层匹配策略:关键词高置信度 → LLM 辅助(低置信度且 backgroundProvider 已注入时)。
|
|
892
|
-
* LLM 辅助匹配在 agent 层执行,遵循 backend_layers_rules §分层职责(persona/ 不直接调 LLM)。
|
|
893
|
-
*
|
|
894
|
-
* 匹配成功时:切换角色 + 发射 personaSwitched 事件 + 刷新 AgentLoop 前缀。
|
|
895
|
-
* 匹配失败/异常时:静默降级,保持当前角色。
|
|
1223
|
+
* 刷新 AgentLoop 的 systemPromptPrefix(SSOT:buildSystemPromptPrefix)
|
|
896
1224
|
*
|
|
897
|
-
*
|
|
1225
|
+
* 角色包是 system prompt 的唯一注入源。调用时机:手动切换成功后、reloadConfig 重载后;
|
|
1226
|
+
* loop 为 null 时静默跳过(init 前或 close 后边界)。统一走 buildSystemPromptPrefix 真理源,
|
|
1227
|
+
* 全局技能清单也在该前缀内(不能只拼 rolePackPrompt)。等价于「按 activePack 视角刷新」(roundRole=null)。
|
|
898
1228
|
*/
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
return;
|
|
902
|
-
try {
|
|
903
|
-
let matchedPersona = null;
|
|
904
|
-
if (this.personaManager.canAutoMatch()) {
|
|
905
|
-
matchedPersona = this.personaManager.autoMatch(input);
|
|
906
|
-
// 关键词低置信度且 backgroundProvider 已注入 → LLM 辅助语义匹配
|
|
907
|
-
const bgProvider = this.#backgroundProvider;
|
|
908
|
-
if (!matchedPersona && bgProvider) {
|
|
909
|
-
matchedPersona = await matchPersonaByLlm(bgProvider, this.personaManager.list, this.personaManager.activeName, input);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
if (matchedPersona) {
|
|
913
|
-
const prevName = this.personaManager.activeName;
|
|
914
|
-
this.personaManager.switchPersona(matchedPersona);
|
|
915
|
-
this.emit(AGENT_EVENTS.personaSwitched, { from: prevName, to: matchedPersona });
|
|
916
|
-
// 刷新 AgentLoop 的角色前缀(与 switchPersona 共用同一段逻辑,ADR-017 枝叶层 2 次提取)
|
|
917
|
-
this.refreshPersonaPrefixOnLoop();
|
|
918
|
-
logger.info({ persona: matchedPersona }, '角色自动切换');
|
|
919
|
-
}
|
|
920
|
-
else {
|
|
921
|
-
// 关键词 + LLM 均未命中,且当前角色不是列表首个角色 → 回退到首个角色
|
|
922
|
-
// "切过去回不来":用户从默认切到散文作者后,输入无关话题应回到默认角色
|
|
923
|
-
// 使用 list[0] 而非硬编码 'default':防止 default 角色被删除后 fallback 抛异常
|
|
924
|
-
const shouldFallback = this.shouldFallbackToDefault();
|
|
925
|
-
if (shouldFallback) {
|
|
926
|
-
const fallbackTarget = this.personaManager.list[0]?.name ?? 'default';
|
|
927
|
-
const prevName = this.personaManager.activeName;
|
|
928
|
-
this.personaManager.switchPersona(fallbackTarget);
|
|
929
|
-
this.emit(AGENT_EVENTS.personaSwitched, { from: prevName, to: fallbackTarget });
|
|
930
|
-
this.refreshPersonaPrefixOnLoop();
|
|
931
|
-
logger.info({ persona: fallbackTarget }, '角色回退默认');
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
catch (err) {
|
|
936
|
-
logger.warn({ err }, '角色自动匹配失败');
|
|
937
|
-
}
|
|
1229
|
+
refreshRolePackPrefixOnLoop() {
|
|
1230
|
+
this.refreshRolePackPrefixForRound(null);
|
|
938
1231
|
}
|
|
939
1232
|
/**
|
|
940
|
-
*
|
|
941
|
-
*
|
|
942
|
-
* 回退条件(全部满足):
|
|
943
|
-
* 1. 当前激活角色不是默认角色(已在默认角色则无需回退)
|
|
944
|
-
* 2. 角色管理器存在且非锁定状态(锁定时 switchPersona 也会被拦截,回退无意义)
|
|
1233
|
+
* 会议阶梯推进:按当前 active 任务项的 rolePack 派生本轮装配视角。
|
|
945
1234
|
*
|
|
946
|
-
*
|
|
947
|
-
*
|
|
948
|
-
*
|
|
949
|
-
*
|
|
1235
|
+
* 由 assembler.getTaskTable 每轮注入时驱动(hooks.applyActivePlanItemAssembly),与任务表渲染同源,
|
|
1236
|
+
* 保证后继 step 换角色即时生效(装配视角不能只在 prepare.run 设一次,否则展示层正确/装配层冻结)。
|
|
1237
|
+
* roundRole=candidate(可能为 null,无有效覆盖时回落 activePack 组长),调 refreshRolePackPrefixForRound 重建前缀。
|
|
1238
|
+
* 防重:candidate 与 rolePackManager 当前 roundAssemblyPerspective 一致则跳过,避免每轮迭代重复重建。
|
|
950
1239
|
*/
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
if (this.
|
|
956
|
-
return
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
// 当前角色已是���表首个角色("默认"角色)→ 无需回退
|
|
961
|
-
const defaultName = this.personaManager.list[0]?.name;
|
|
962
|
-
return defaultName !== undefined && this.personaManager.activeName !== defaultName;
|
|
1240
|
+
applyActivePlanItemAssemblyIfChanged() {
|
|
1241
|
+
const plan = this._sessionManager?.getCheckpoint()?.plan ?? [];
|
|
1242
|
+
const active = plan.find((s) => s.status === 'active');
|
|
1243
|
+
const candidate = this._rolePackManager?.resolveRoundAssemblyRole(active?.rolePack) ?? null;
|
|
1244
|
+
if (candidate === this._rolePackManager?.roundAssemblyPerspective)
|
|
1245
|
+
return;
|
|
1246
|
+
this._rolePackManager?.setRoundAssemblyRole(candidate);
|
|
1247
|
+
this.refreshRolePackPrefixForRound(candidate);
|
|
1248
|
+
logger.debug({ roundAssemblyRole: candidate }, '会议装配视角随 active 任务项切换');
|
|
963
1249
|
}
|
|
964
1250
|
/**
|
|
965
|
-
*
|
|
966
|
-
*
|
|
967
|
-
* manual 模式下 postProcess 跳过自动归档,用户需通过此 API 主动归档。
|
|
968
|
-
* full / insights-only 模式下也可调用(会重复归档,但不推荐)。
|
|
969
|
-
*
|
|
970
|
-
* FIX-P1-4:新增 options 参数透传给 ArchiveCoordinator。
|
|
971
|
-
* 宿主自动触发时传 `{ autoTriggered: true }`,由 ArchiveCoordinator 内部按模式判断;
|
|
972
|
-
* 用户手动触发时无需传 options(默认 autoTriggered=false,无条件执行)。
|
|
973
|
-
*
|
|
974
|
-
* 归档逻辑已委托给 ArchiveCoordinator
|
|
1251
|
+
* 按本轮表层装配视角刷新 AgentLoop 前缀(会议机制)
|
|
975
1252
|
*
|
|
976
|
-
*
|
|
977
|
-
*
|
|
978
|
-
*
|
|
1253
|
+
* roundRole=null(日常态)→ 回落 activePack 前缀;roundRole 为任务项角色 → 该角色
|
|
1254
|
+
* persona/rules/skills 全量表层装配(buildSystemPrompt(name))。策略键恒为 activePack:
|
|
1255
|
+
* ChatOptions(temperature/outputLimit)由 activePack 策略构建,不随视角变(键不换防抖动)。
|
|
1256
|
+
* 由 seed prepare 每轮调用(`refreshRolePackPrefixForRound` 契约),保证前缀始终匹配本轮装配角色。
|
|
979
1257
|
*/
|
|
980
|
-
|
|
981
|
-
this.
|
|
982
|
-
|
|
1258
|
+
refreshRolePackPrefixForRound(roundRole) {
|
|
1259
|
+
if (!this.loop)
|
|
1260
|
+
return;
|
|
1261
|
+
const assemblyName = roundRole ?? this._rolePackManager?.activeName ?? undefined;
|
|
1262
|
+
const rolePackPrompt = this._rolePackManager?.buildSystemPrompt(assemblyName) ?? '';
|
|
1263
|
+
const globalSkillList = this.skillManager?.buildSkillList() ?? '';
|
|
1264
|
+
// 作品投影装配块:缓存于 manager(装配时刷新 + register_work 更新),同步读取即可
|
|
1265
|
+
const workProjectionContext = this.workProjection?.contextBlock() ?? '';
|
|
1266
|
+
const newPrefix = buildSystemPromptPrefix(rolePackPrompt, globalSkillList, undefined, workProjectionContext);
|
|
1267
|
+
this.loop.refreshRolePackPrefix(newPrefix);
|
|
1268
|
+
// 角色包底盘占用随切换实时更新(不依赖跑 prepare):装配此刻即确定的真值
|
|
1269
|
+
this.loop.setRolePackBaseTokens(estimateTokensMessages([{ content: newPrefix }]));
|
|
1270
|
+
// 同步注入角色包策略的 ChatOptions 覆盖项(temperature / outputLimit)
|
|
1271
|
+
// 键恒为 activePack:buildChatOptionsFromStrategy 读激活包策略,不随本轮装配视角变
|
|
1272
|
+
this.loop.setChatOptions(this.buildChatOptionsFromStrategy());
|
|
983
1273
|
}
|
|
984
1274
|
/**
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
* 内部仍走 classify 判断(避免无价值输入浪费 LLM 调用)。
|
|
989
|
-
*
|
|
990
|
-
* FIX-P1-4:新增 options 参数透传给 ArchiveCoordinator。
|
|
991
|
-
* 宿主自动触发时传 `{ autoTriggered: true }`,由 ArchiveCoordinator 内部按模式判断;
|
|
992
|
-
* 用户手动触发时无需传 options(默认 autoTriggered=false,无条件执行)。
|
|
993
|
-
*
|
|
994
|
-
* 归档逻辑已委托给 ArchiveCoordinator
|
|
995
|
-
*
|
|
996
|
-
* @param input 本轮用户输入
|
|
997
|
-
* @param assistantContent 本轮助手回复内容
|
|
998
|
-
* @param options 触发选项(autoTriggered 默认 false,即手动触发)
|
|
999
|
-
* @returns 写入/更新的 Memory 列表
|
|
1275
|
+
* 从当前激活的角色包策略构建 ChatOptions 覆盖项
|
|
1276
|
+
* 将 act.temperature / act.outputLimit 映射到 ChatOptions 字段;输出上限最终与 per-LLM 默认值
|
|
1277
|
+
* 取**更小值**(两者皆为「上限」,须同时满足)——收敛在 `buildChatOptions`,此处不裁决大小。
|
|
1000
1278
|
*/
|
|
1001
|
-
|
|
1002
|
-
this.
|
|
1003
|
-
|
|
1279
|
+
buildChatOptionsFromStrategy() {
|
|
1280
|
+
const strategy = this.getActiveStrategy();
|
|
1281
|
+
if (!strategy)
|
|
1282
|
+
return undefined;
|
|
1283
|
+
const chatOptions = {};
|
|
1284
|
+
const act = strategy.act;
|
|
1285
|
+
// act.temperature → ChatOptions.temperature(限 0-2;边界 SSOT = strategyKeys.isTemperature,不重复字面量)
|
|
1286
|
+
const temperature = act?.temperature;
|
|
1287
|
+
if (isTemperature(temperature)) {
|
|
1288
|
+
chatOptions.temperature = temperature;
|
|
1289
|
+
}
|
|
1290
|
+
// act.outputLimit → ChatOptions.maxTokens(需 ∈ [1, MAX_OUTPUT_LIMIT],越界忽略防资源失控)
|
|
1291
|
+
const outputLimit = act?.outputLimit;
|
|
1292
|
+
if (typeof outputLimit === 'number' && outputLimit > 0 && outputLimit <= MAX_OUTPUT_LIMIT) {
|
|
1293
|
+
chatOptions.maxTokens = outputLimit;
|
|
1294
|
+
}
|
|
1295
|
+
return Object.keys(chatOptions).length > 0 ? chatOptions : undefined;
|
|
1004
1296
|
}
|
|
1005
1297
|
/**
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1012
|
-
* 宿主自动触发时传 `{ autoTriggered: true }`,由 ArchiveCoordinator 内部按模式判断;
|
|
1013
|
-
* 用户手动触发时无需传 options(默认 autoTriggered=false,无条件执行)。
|
|
1014
|
-
*
|
|
1015
|
-
* 归档逻辑已委托给 ArchiveCoordinator
|
|
1016
|
-
*
|
|
1017
|
-
* @param date 会话日期 YYYY-MM-DD
|
|
1018
|
-
* @param session 会话标识(不含日期前缀)
|
|
1019
|
-
* @param options 触发选项(autoTriggered 默认 false,即手动触发)
|
|
1020
|
-
* @returns 归档结果(memories 可能为空,表示无归档价值或 LLM 失败)
|
|
1298
|
+
* 角色包系统的唯一切换入口(自动匹配 + 手动切换共用):activate 更新激活态 → 发射 rolePackSwitched 事件
|
|
1299
|
+
* → 刷新 AgentLoop 前缀 → 同步工具白名单。
|
|
1300
|
+
* @returns 是否切换成功。false 有两种成因(**均不发射切换事件**):角色包不存在、
|
|
1301
|
+
* 切换锁已激活(30s 内超 5 次限流);调用方须以 getRolePackSwitchLockStatus() 区分,
|
|
1302
|
+
* 不可由「返回 false + 未收到 rolePackSwitchLocked」推断为"不存在"(被锁时该事件不发射,
|
|
1303
|
+
* 仅在**触发锁定**的那一次切换中发射,而那一次返回 true)。同名切换幂等返回 true。
|
|
1021
1304
|
*/
|
|
1022
|
-
|
|
1023
|
-
this.assertInitialized('
|
|
1024
|
-
|
|
1305
|
+
switchRolePack(name) {
|
|
1306
|
+
this.assertInitialized('switchRolePack');
|
|
1307
|
+
const rpm = this._rolePackManager;
|
|
1308
|
+
if (!rpm)
|
|
1309
|
+
return false;
|
|
1310
|
+
const prevName = rpm.activeName;
|
|
1311
|
+
if (prevName === name)
|
|
1312
|
+
return true;
|
|
1313
|
+
const ok = rpm.activate(name);
|
|
1314
|
+
if (!ok) {
|
|
1315
|
+
logger.warn({ name }, '切换角色包失败(不存在或切换锁已激活)');
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
this.emit(AGENT_EVENTS.rolePackSwitched, { from: prevName ?? '', to: name });
|
|
1319
|
+
this.refreshRolePackPrefixOnLoop();
|
|
1320
|
+
this.applyRolePackToolExposure();
|
|
1321
|
+
logger.info({ rolePack: name }, '角色包切换');
|
|
1322
|
+
return true;
|
|
1025
1323
|
}
|
|
1026
1324
|
/**
|
|
1027
|
-
*
|
|
1325
|
+
* 手动归档会话(更新 SessionMeta,委托 ArchiveCoordinator)
|
|
1028
1326
|
*
|
|
1029
|
-
*
|
|
1030
|
-
*
|
|
1327
|
+
* 适用于 manual 模式(用户手动触发;full 模式由宿主切换前自动调用)。
|
|
1328
|
+
* options 透传给 ArchiveCoordinator:宿主自动触发传 { autoTriggered: true }(按其模式判断),
|
|
1329
|
+
* 用户手动触发省略 options(默认 autoTriggered=false,无条件执行)。
|
|
1031
1330
|
*
|
|
1032
|
-
* @returns
|
|
1331
|
+
* @returns 归档结果(updatedFields 可能为空,表示无归档价值或 LLM 失败)
|
|
1033
1332
|
*/
|
|
1034
|
-
|
|
1035
|
-
this.assertInitialized('
|
|
1036
|
-
return this.requireArchiveCoordinator.
|
|
1333
|
+
async archiveSession(date, session, options) {
|
|
1334
|
+
this.assertInitialized('archiveSession');
|
|
1335
|
+
return this.requireArchiveCoordinator.archiveSession(date, session, options);
|
|
1037
1336
|
}
|
|
1038
1337
|
// ─── 配置重载(事件驱动) ───────────────────────
|
|
1039
1338
|
/**
|
|
1040
|
-
* 重载配置类记忆:从 configDir 重新扫描指定 source
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
1044
|
-
*
|
|
1045
|
-
* 支持的 source:
|
|
1046
|
-
* - 'skill' → SkillManager.reload() 清空缓存重新扫描 skills/ 目录
|
|
1047
|
-
* - 'persona' → PersonaManager.reload() 清空缓存重新扫描 personas/ 目录(保持激活角色)
|
|
1048
|
-
* - 'rule' → 无操作(rule 类型由 ConfigManager CRUD 即时同步 bootstrap 段,详见 deleteRule/updateRule)
|
|
1049
|
-
* - 'guardrail' → 抛错(guardrail 是 AgentLoop 的 readonly 数组,需 rebuildComponents 才能重载)
|
|
1050
|
-
* - undefined → 重载 skill + persona(全量重载,不含 guardrail)
|
|
1051
|
-
*
|
|
1052
|
-
* @param source 配置类型,缺省时重载全部可热更新的配置
|
|
1053
|
-
* @returns 重载结果统计
|
|
1339
|
+
* 重载配置类记忆:从 configDir 重新扫描指定 source 并更新内存缓存(设定记忆纯文件装载,不写记忆库),使当前会话立即生效(无需重启)。
|
|
1340
|
+
* - 'skill' → SkillManager.reload() 重扫 skills/
|
|
1341
|
+
* - 'rolePack' → RolePackManager.reload() 重扫 role-packs/(保持激活角色,刷新 AgentLoop 前缀)
|
|
1342
|
+
* - 'rule' → 经角色包管理机制热更新(即 rolePack)
|
|
1343
|
+
* - undefined → 全量重载 skill + rolePack
|
|
1054
1344
|
*/
|
|
1055
1345
|
async reloadConfig(source) {
|
|
1056
1346
|
this.assertInitialized('reloadConfig');
|
|
1057
|
-
if (this.chatLockManager?.isBusy) {
|
|
1058
|
-
//
|
|
1059
|
-
// source 为 undefined(全量重载)时不暂存——全量重载无具体来源,补执行语义不明
|
|
1347
|
+
if (this.internals.chatLockManager?.isBusy) {
|
|
1348
|
+
// 对话中无法热重载:暂存 source 待锁释放后补执行;undefined(全量)不暂存——无具体来源,补执行语义不明
|
|
1060
1349
|
if (source) {
|
|
1061
1350
|
this.pendingConfigReload.add(source);
|
|
1062
1351
|
logger.info({ source }, '对话进行中,配置重载已暂存,将在对话结束后补执行');
|
|
1063
1352
|
}
|
|
1064
1353
|
throw chatBusyError('重载配置');
|
|
1065
1354
|
}
|
|
1066
|
-
//
|
|
1067
|
-
if (source === 'guardrail') {
|
|
1068
|
-
throw configError('guardrail 不支持热重载', 'guardrail 规则是 AgentLoop 的 readonly 数组,需调用 rebuildComponents() 重建', ['使用 rebuildComponents() 重建组件(代价较高)']);
|
|
1069
|
-
}
|
|
1070
|
-
// rule 类型由 ConfigManager.deleteRule/updateRule CRUD 即时同步 bootstrap 段(refreshBootstrapMemories 回调),无需 reloadConfig
|
|
1355
|
+
// rule 是角色包的一部分,重载走 rolePack 路径
|
|
1071
1356
|
if (source === 'rule') {
|
|
1072
|
-
logger.info('rule
|
|
1073
|
-
return
|
|
1357
|
+
logger.info('rule 类型重载:触发角色包重新扫描');
|
|
1358
|
+
return this.reloadConfig('rolePack');
|
|
1074
1359
|
}
|
|
1075
|
-
const result = { skill: 0,
|
|
1360
|
+
const result = { skill: 0, rolePack: 0 };
|
|
1076
1361
|
const errors = [];
|
|
1077
1362
|
// 按需重载:source 缺省时全量重载,否则只重载指定类型
|
|
1078
1363
|
const shouldReloadSkill = !source || source === 'skill';
|
|
1079
|
-
const
|
|
1364
|
+
const shouldReloadRolePack = !source || source === 'rolePack';
|
|
1080
1365
|
if (shouldReloadSkill && this.skillManager) {
|
|
1081
1366
|
try {
|
|
1082
1367
|
result.skill = await this.skillManager.reload();
|
|
@@ -1086,18 +1371,17 @@ export class Agent extends TypedEventEmitter {
|
|
|
1086
1371
|
logger.warn({ err: toError(err) }, 'reloadConfig: skillManager.reload 失败');
|
|
1087
1372
|
}
|
|
1088
1373
|
}
|
|
1089
|
-
if (
|
|
1374
|
+
if (shouldReloadRolePack && this._rolePackManager) {
|
|
1090
1375
|
try {
|
|
1091
|
-
result.
|
|
1092
|
-
//
|
|
1376
|
+
result.rolePack = await this._rolePackManager.reload();
|
|
1377
|
+
// 角色重载后刷新 AgentLoop 前缀
|
|
1093
1378
|
if (this.loop) {
|
|
1094
|
-
|
|
1095
|
-
this.loop.refreshPersonaPrefix(newPrefix);
|
|
1379
|
+
this.refreshRolePackPrefixOnLoop();
|
|
1096
1380
|
}
|
|
1097
1381
|
}
|
|
1098
1382
|
catch (err) {
|
|
1099
1383
|
errors.push(toError(err));
|
|
1100
|
-
logger.warn({ err: toError(err) }, 'reloadConfig:
|
|
1384
|
+
logger.warn({ err: toError(err) }, 'reloadConfig: rolePackManager.reload 失败');
|
|
1101
1385
|
}
|
|
1102
1386
|
}
|
|
1103
1387
|
if (errors.length > 0) {
|
|
@@ -1110,22 +1394,19 @@ export class Agent extends TypedEventEmitter {
|
|
|
1110
1394
|
}
|
|
1111
1395
|
// ─── 组件访问 ─────────────────────────────────────────
|
|
1112
1396
|
/**
|
|
1113
|
-
* 重建内部组件(history / loop / managers
|
|
1114
|
-
*
|
|
1115
|
-
* 通常不需要手动调用——switchProject() 已自动执行 rebuild。
|
|
1116
|
-
* 仅在宿主项目需要强制刷新组件时使用(如热更新配置后)。
|
|
1397
|
+
* 重建内部组件(history / loop / managers):通常由 switchProject() 自动触发,仅强制刷新时手动调用
|
|
1117
1398
|
*/
|
|
1118
1399
|
async rebuildComponents() {
|
|
1119
|
-
//
|
|
1400
|
+
// 对话进行中重建会导致 loop/history 引用被替换,工作记忆与持久化状态不一致
|
|
1120
1401
|
this.assertNotBusy('重建组件');
|
|
1121
|
-
|
|
1402
|
+
if (!this.pctx)
|
|
1403
|
+
return;
|
|
1404
|
+
// assembleComponents 内部重建 history/loop/managers,并返回新创建的 sessionManager
|
|
1405
|
+
await this.assembleComponents(this.pctx);
|
|
1122
1406
|
}
|
|
1123
1407
|
// ─── 守卫方法 ───────────────────────────────────────────
|
|
1124
1408
|
/**
|
|
1125
|
-
* 检查 Agent
|
|
1126
|
-
*
|
|
1127
|
-
* @param methodName 调用方法名(用于错误消息)
|
|
1128
|
-
* @param requires 需要检查的组件列表
|
|
1409
|
+
* 检查 Agent 是否已初始化(methodName 用于错误消息,requires 为需检查的组件列表)
|
|
1129
1410
|
*/
|
|
1130
1411
|
assertInitialized(methodName, requires = ['history', 'loop']) {
|
|
1131
1412
|
if (!this._initialized) {
|
|
@@ -1148,98 +1429,132 @@ export class Agent extends TypedEventEmitter {
|
|
|
1148
1429
|
}
|
|
1149
1430
|
}
|
|
1150
1431
|
/**
|
|
1151
|
-
*
|
|
1152
|
-
*
|
|
1153
|
-
* 提取原因:switchProject / setProvider / setBackgroundProvider / setArchiveMode /
|
|
1154
|
-
* switchPersona / rebuildComponents 等 6 处方法均有相同的 `if (isBusy) throw chatBusyError('XXX')` 守卫,
|
|
1155
|
-
* 违反 DRY 原则(v2 神木回天 REPEAT-4 闭环)。
|
|
1156
|
-
*
|
|
1157
|
-
* 注意:reloadConfig 不使用本方法——它在 isBusy 时需暂存 source 而非直接抛错。
|
|
1158
|
-
*
|
|
1159
|
-
* @param operation 操作名称(用于错误消息,如 "切换项目")
|
|
1432
|
+
* 断言对话未进行中(统一守卫):破坏状态一致性的入口统一在此做 isBusy 检查。
|
|
1433
|
+
* reloadConfig 不使用本方法——它在 isBusy 时需暂存 source 而非直接抛错。
|
|
1160
1434
|
*/
|
|
1161
1435
|
assertNotBusy(operation) {
|
|
1162
|
-
if (this.chatLockManager?.isBusy) {
|
|
1436
|
+
if (this.internals.chatLockManager?.isBusy) {
|
|
1163
1437
|
throw chatBusyError(operation);
|
|
1164
1438
|
}
|
|
1165
1439
|
}
|
|
1166
|
-
|
|
1167
|
-
* 断言 getter:获取 loop,若 null 则抛出明确错误
|
|
1168
|
-
* 替代 `this.loop!` 非空断言,提供更好的重构安全性
|
|
1169
|
-
*/
|
|
1440
|
+
// 断言 getter:非空或抛明确错误,替代 this.loop!/history!/pctx! 非空断言
|
|
1170
1441
|
get requireLoop() {
|
|
1171
1442
|
if (!this.loop)
|
|
1172
1443
|
throw configError('AgentLoop 未初始化', undefined, ['在调用此方法前执行 await agent.init()']);
|
|
1173
1444
|
return this.loop;
|
|
1174
1445
|
}
|
|
1446
|
+
get requireArchiveCoordinator() {
|
|
1447
|
+
if (!this.internals.archiveCoordinator)
|
|
1448
|
+
throw configError('ArchiveCoordinator 未初始化', undefined, [
|
|
1449
|
+
'在调用此方法前执行 await agent.init()',
|
|
1450
|
+
]);
|
|
1451
|
+
return this.internals.archiveCoordinator;
|
|
1452
|
+
}
|
|
1453
|
+
get requireSessionManager() {
|
|
1454
|
+
if (!this._sessionManager)
|
|
1455
|
+
throw configError('SessionManager 未初始化', undefined, [
|
|
1456
|
+
'在调用此方法前执行 await agent.init()',
|
|
1457
|
+
]);
|
|
1458
|
+
return this._sessionManager;
|
|
1459
|
+
}
|
|
1175
1460
|
/**
|
|
1176
|
-
*
|
|
1461
|
+
* 获取角色包管理器(system prompt 的唯一注入源,返回 null 表示 Agent 未初始化)
|
|
1177
1462
|
*/
|
|
1178
|
-
get
|
|
1179
|
-
|
|
1180
|
-
throw configError('MessageHistory 未初始化', undefined, ['在调用此方法前执行 await agent.init()']);
|
|
1181
|
-
return this.history;
|
|
1463
|
+
get rolePackManager() {
|
|
1464
|
+
return this._rolePackManager;
|
|
1182
1465
|
}
|
|
1183
1466
|
/**
|
|
1184
|
-
*
|
|
1467
|
+
* 获取当前激活的 L2 行为策略:从激活角色包读取,未激活时用全局默认值;供各阶段按策略调整行为
|
|
1185
1468
|
*/
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
throw configError('ProjectContext 未初始化', undefined, ['在调用此方法前执行 await agent.init()']);
|
|
1189
|
-
return this.pctx;
|
|
1190
|
-
}
|
|
1191
|
-
get requireArchiveCoordinator() {
|
|
1192
|
-
if (!this.archiveCoordinator)
|
|
1193
|
-
throw configError('ArchiveCoordinator 未初始化', undefined, ['在调用此方法前执行 await agent.init()']);
|
|
1194
|
-
return this.archiveCoordinator;
|
|
1469
|
+
getActiveStrategy() {
|
|
1470
|
+
return this._rolePackManager?.getActive()?.strategy ?? DEFAULT_BEHAVIOR_STRATEGY;
|
|
1195
1471
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1472
|
+
/**
|
|
1473
|
+
* 应用当前角色包的工具暴露面(换角色 → 工具集切换):声明 capabilities 时解析为工具白名单,未声明放行全部。
|
|
1474
|
+
* 空数组(parseCapabilities 对 undefined 返回 [])同样视为"未声明能力"放行全部,避免误解释为"禁止所有"。
|
|
1475
|
+
* toolMode=block 全禁与此正交;变更经 setToolWhitelist → onToolsChanged 链路同步 loop 快照与 system prompt。
|
|
1476
|
+
*/
|
|
1477
|
+
applyRolePackToolExposure() {
|
|
1478
|
+
if (!this.toolExec)
|
|
1479
|
+
return;
|
|
1480
|
+
const active = this._rolePackManager?.getActive();
|
|
1481
|
+
const capabilities = active?.capabilities;
|
|
1482
|
+
const whitelist = capabilities && capabilities.length > 0 ? resolveCapabilityTools(capabilities) : null;
|
|
1483
|
+
this.toolExec.setToolWhitelist(whitelist);
|
|
1200
1484
|
}
|
|
1201
1485
|
// ─── 记忆生命周期 ───────────────────────────────────────
|
|
1202
|
-
|
|
1486
|
+
/**
|
|
1487
|
+
* 立即执行一次孤儿 Round 垃圾回收(宿主手动触发入口)。
|
|
1488
|
+
*
|
|
1489
|
+
* roundStore+storage 未注入时返回空统计(降级不报错);
|
|
1490
|
+
* 对话进行中(chatLock busy)经 shouldSkip 跳过并返回空统计。
|
|
1491
|
+
*
|
|
1492
|
+
* @returns GC 结果统计(scanned/deleted/memoryCleaned 等)
|
|
1493
|
+
*/
|
|
1494
|
+
gcNow() {
|
|
1495
|
+
const gc = this.internals.gcService;
|
|
1496
|
+
if (!gc) {
|
|
1497
|
+
logger.debug('gcNow: roundStore/storage 未注入,GCService 未创建(降级为空转)');
|
|
1498
|
+
return {
|
|
1499
|
+
scanned: 0,
|
|
1500
|
+
orphaned: 0,
|
|
1501
|
+
deleted: 0,
|
|
1502
|
+
failedDueToRefCount: 0,
|
|
1503
|
+
memoryCleaned: 0,
|
|
1504
|
+
elapsedMs: 0,
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
return gc.run();
|
|
1508
|
+
}
|
|
1203
1509
|
// ─── 关闭 ─────────────────────────────────────────────
|
|
1204
1510
|
/**
|
|
1205
|
-
* 关闭 Agent
|
|
1511
|
+
* 关闭 Agent,释放存储连接等资源
|
|
1206
1512
|
*/
|
|
1207
1513
|
async close() {
|
|
1208
|
-
// 清理 chat 锁管理器(递增 token
|
|
1209
|
-
|
|
1210
|
-
this.chatLockManager
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
//
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
logger.
|
|
1514
|
+
// 清理 chat 锁管理器(递增 token 使进行中 chat() 的 finally 跳过清理,close 已接管)
|
|
1515
|
+
this.internals.chatLockManager?.dispose();
|
|
1516
|
+
this.internals.chatLockManager = null;
|
|
1517
|
+
// 孤儿 Round GC 停止定时任务(须先于下方 projectManager.shutdown——其会关闭 storage)
|
|
1518
|
+
this.internals.gcService?.stopPeriodic();
|
|
1519
|
+
this.internals.gcService = null;
|
|
1520
|
+
// 等待 WorkProjection 无 inflight 概念(作品投影改为用户主动触发同步写,无后台 LLM 任务)
|
|
1521
|
+
// 背景任务统一由下方 awaitBackgroundTasks 兜底
|
|
1522
|
+
// 关键约束:等待 backgroundTask 全局在途任务完成(如 touchScores)。
|
|
1523
|
+
// 否则 close 后后台任务仍可能 upsert 已关闭的 storage,触发写失效或异常。
|
|
1524
|
+
try {
|
|
1525
|
+
const awaited = await awaitBackgroundTasks(AGENT_CONSTANTS.SHUTDOWN_ARCHIVE_TIMEOUT_MS);
|
|
1526
|
+
if (awaited > 0) {
|
|
1527
|
+
logger.debug({ awaited }, 'close: 背景任务等待结束(awaited = 初始在途数,超时放弃时非完成数)');
|
|
1222
1528
|
}
|
|
1223
|
-
this.memoryDecayScheduler = null;
|
|
1224
1529
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1530
|
+
catch (err) {
|
|
1531
|
+
logger.warn({ err: toError(err) }, 'close: awaitBackgroundTasks 失败');
|
|
1532
|
+
}
|
|
1533
|
+
// 处理 pending 暂停残留:关闭前确保状态机与检查点一致
|
|
1534
|
+
const sm = this._sessionManager;
|
|
1535
|
+
if (sm?.isPausePending()) {
|
|
1536
|
+
const pendingInfo = sm.pendingPauseInfo;
|
|
1537
|
+
logger.warn({ reason: pendingInfo?.reason, source: pendingInfo?.source }, 'close() 时存在未消费的暂停请求,已自动清理');
|
|
1538
|
+
// 若状态机仍 running,同步翻 paused 使检查点内存态一致(无需触发挂起,仅修正一致性);
|
|
1539
|
+
// 系统清理不消耗连续暂停配额 → lowRisk=true
|
|
1540
|
+
if (sm.status === 'running') {
|
|
1541
|
+
sm.pause('close 清理残留暂停', 'system', true);
|
|
1232
1542
|
}
|
|
1543
|
+
sm.cancelPendingPause();
|
|
1544
|
+
}
|
|
1545
|
+
// 清理 SessionManager(须先于 nullifyAllComponents,destroy 需访问其内部状态)
|
|
1546
|
+
if (this._sessionManager) {
|
|
1547
|
+
// 清脏后再 destroy(纯内存,无落盘;检查点不落盘,本调用为保留语义边界的 no-op)
|
|
1548
|
+
this._sessionManager.flushOnShutdown();
|
|
1549
|
+
this._sessionManager.destroy();
|
|
1233
1550
|
}
|
|
1234
|
-
//
|
|
1235
|
-
this.archiveCoordinator = null;
|
|
1236
|
-
// 清理
|
|
1237
|
-
if (this.
|
|
1238
|
-
this.
|
|
1551
|
+
// ArchiveCoordinator 无定时器,仅释放引用
|
|
1552
|
+
this.internals.archiveCoordinator = null;
|
|
1553
|
+
// 清理 RolePackManager 切换防抖锁计时器,防止关闭后回调触发
|
|
1554
|
+
if (this._rolePackManager) {
|
|
1555
|
+
this._rolePackManager.close();
|
|
1239
1556
|
}
|
|
1240
|
-
//
|
|
1241
|
-
// 顺序敏感:若先 removeAllListeners,fire-and-forget 的归档 Promise 在 await 期间
|
|
1242
|
-
// reject 时 emit 会变成 no-op(listeners 已清空),archiveFailed 事件丢失
|
|
1557
|
+
// 顺序敏感:先等后台归档完成再移除监听器,否则 await 期间归档 reject 的 emit 变 no-op、事件丢失
|
|
1243
1558
|
if (this.history) {
|
|
1244
1559
|
try {
|
|
1245
1560
|
await this.history.awaitPendingArchives(AGENT_CONSTANTS.SHUTDOWN_ARCHIVE_TIMEOUT_MS);
|
|
@@ -1248,7 +1563,6 @@ export class Agent extends TypedEventEmitter {
|
|
|
1248
1563
|
logger.warn({ err: toError(err) }, 'close: awaitPendingArchives 失败');
|
|
1249
1564
|
}
|
|
1250
1565
|
}
|
|
1251
|
-
// pending archives 完成后再移除监听器,确保归档失败的 emit 能送达
|
|
1252
1566
|
this.removeAllListeners();
|
|
1253
1567
|
if (this.projectManager) {
|
|
1254
1568
|
try {
|
|
@@ -1265,36 +1579,35 @@ export class Agent extends TypedEventEmitter {
|
|
|
1265
1579
|
this.pendingConfigReload.clear();
|
|
1266
1580
|
}
|
|
1267
1581
|
/**
|
|
1268
|
-
* 统一 null 化所有组件字段(新增 Manager
|
|
1269
|
-
*
|
|
1270
|
-
* 仅处理"纯 null 化"字段,带副作用的清理(dispose/stop/close/shutdown)
|
|
1271
|
-
* 仍由 close() 显式调用,顺序敏感不可合并。
|
|
1582
|
+
* 统一 null 化所有组件字段(新增 Manager 时在此追加一行);带副作用的清理仍由 close() 显式调用,顺序敏感不可合并
|
|
1272
1583
|
*/
|
|
1273
1584
|
nullifyAllComponents() {
|
|
1274
|
-
// Provider
|
|
1275
1585
|
this.#backgroundProvider = null;
|
|
1276
|
-
// 核心组件
|
|
1277
1586
|
this.history = null;
|
|
1278
1587
|
this.loop = null;
|
|
1279
1588
|
this.toolExec = null;
|
|
1280
|
-
// 专职 Manager
|
|
1281
|
-
this.personaManager = null;
|
|
1282
|
-
this.#userProfile = null;
|
|
1283
1589
|
this.skillManager = null;
|
|
1284
|
-
this.
|
|
1285
|
-
this.configManager = null;
|
|
1590
|
+
this._rolePackManager = null;
|
|
1286
1591
|
this.memoryInspector = null;
|
|
1287
|
-
this.dedupManager = null;
|
|
1288
|
-
this.memoryAdvisor = null;
|
|
1289
1592
|
this._governance = null;
|
|
1290
1593
|
this.workProjection = null;
|
|
1291
|
-
this.autoConfigRefiner = null;
|
|
1292
|
-
this.sessionArchiver = null;
|
|
1293
1594
|
this.textPolisher = null;
|
|
1294
1595
|
this._sessionManager = null;
|
|
1295
|
-
// 项目管理
|
|
1296
1596
|
this.projectManager = null;
|
|
1297
1597
|
this.pctx = null;
|
|
1598
|
+
// 纯内部组件聚合整体置 null(新增内部组件只需在字段声明与 createPostInitComponents 登记)
|
|
1599
|
+
this.internals = {
|
|
1600
|
+
dedupManager: null,
|
|
1601
|
+
memoryAdvisor: null,
|
|
1602
|
+
sessionArchiver: null,
|
|
1603
|
+
sessionNamer: null,
|
|
1604
|
+
roundSummaryGenerator: null,
|
|
1605
|
+
contextPreparer: null,
|
|
1606
|
+
chatLockManager: null,
|
|
1607
|
+
archiveCoordinator: null,
|
|
1608
|
+
seedOrchestrator: null,
|
|
1609
|
+
gcService: null,
|
|
1610
|
+
};
|
|
1298
1611
|
}
|
|
1299
1612
|
// ─── 只读访问器 ───────────────────────────────────────
|
|
1300
1613
|
get initialized() {
|
|
@@ -1304,14 +1617,13 @@ export class Agent extends TypedEventEmitter {
|
|
|
1304
1617
|
return this.pctx;
|
|
1305
1618
|
}
|
|
1306
1619
|
/**
|
|
1307
|
-
*
|
|
1308
|
-
* 使用 getMessages() / getMessageCount() 等有界接口替代。
|
|
1620
|
+
* 获取 AgentLoop 实例(宿主集成面,用于 restoreHistory 等底层操作;getMessages/getMessageCount 无法替代)
|
|
1309
1621
|
*/
|
|
1310
1622
|
get agentLoop() {
|
|
1311
1623
|
return this.loop;
|
|
1312
1624
|
}
|
|
1313
1625
|
/**
|
|
1314
|
-
*
|
|
1626
|
+
* 获取当前消息列表(只读副本,不可绕过编排链路直接修改)
|
|
1315
1627
|
*/
|
|
1316
1628
|
getMessages() {
|
|
1317
1629
|
return this.loop?.getMessages() ?? [];
|
|
@@ -1321,19 +1633,15 @@ export class Agent extends TypedEventEmitter {
|
|
|
1321
1633
|
return this.loop?.getMessages().length ?? 0;
|
|
1322
1634
|
}
|
|
1323
1635
|
/**
|
|
1324
|
-
*
|
|
1325
|
-
*
|
|
1636
|
+
* 获取 MessageHistory 实例(宿主集成面,用于日期/会话判断等底层操作;
|
|
1637
|
+
* currentDateValue/currentSessionValue 等只读属性无替代接口)
|
|
1326
1638
|
*/
|
|
1327
1639
|
get agentHistory() {
|
|
1328
1640
|
return this.history;
|
|
1329
1641
|
}
|
|
1330
1642
|
/**
|
|
1331
|
-
* 注入情感基调到 system prompt
|
|
1332
|
-
*
|
|
1333
|
-
* 委托至 AgentLoop.injectAffect(),在角色前缀和 bootstrap 记忆之间插入情感描述。
|
|
1334
|
-
* 与角色切换独立——切换角色不会清除情感注入。
|
|
1335
|
-
*
|
|
1336
|
-
* @param affectString 情感描述文本,传空字符串清除注入
|
|
1643
|
+
* 注入情感基调到 system prompt:委托 AgentLoop.injectAffect(),在角色前缀与 bootstrap 记忆间插入情感描述;
|
|
1644
|
+
* 与角色切换独立(切换角色不清除情感注入),传空串清除。
|
|
1337
1645
|
*/
|
|
1338
1646
|
injectAffect(affectString) {
|
|
1339
1647
|
if (!this.loop)
|
|
@@ -1344,172 +1652,104 @@ export class Agent extends TypedEventEmitter {
|
|
|
1344
1652
|
return this.#provider;
|
|
1345
1653
|
}
|
|
1346
1654
|
get isBusy() {
|
|
1347
|
-
return this.chatLockManager?.isBusy ?? false;
|
|
1655
|
+
return this.internals.chatLockManager?.isBusy ?? false;
|
|
1348
1656
|
}
|
|
1349
1657
|
get lastInteractionAt() {
|
|
1350
1658
|
return this._lastInteractionAt;
|
|
1351
1659
|
}
|
|
1352
|
-
/**
|
|
1353
|
-
* 记忆治理统一门面(L0 衰减 / L1 去重 / L2 时效性 / L3 冲突 / 诊断 / 推荐)
|
|
1354
|
-
*
|
|
1355
|
-
* 统一入口替代散落在 Agent 上的 6 个独立方法:
|
|
1356
|
-
* agent.governance.decay() → 原 runMemoryDecayOnce()
|
|
1357
|
-
* agent.governance.deduplicate() → 原 deduplicateMemories()
|
|
1358
|
-
* agent.governance.evaluateTimeliness() → 原 evaluateTimeliness()
|
|
1359
|
-
* agent.governance.detectConflicts() → 原 detectConflicts()
|
|
1360
|
-
* agent.governance.sourceHealth() → 原 sourceHealth()
|
|
1361
|
-
* agent.governance.suggest() → 原 suggest()
|
|
1362
|
-
*/
|
|
1660
|
+
/** 记忆治理统一门面(语义去重 / 来源健康诊断 / 冲突检测 / 建议推荐),Agent 上的记忆管理方法统一收口于此(无显式衰减层面) */
|
|
1363
1661
|
get governance() {
|
|
1364
1662
|
return this._governance;
|
|
1365
1663
|
}
|
|
1366
1664
|
// ─── 运行时指标 ────────────────────────────────
|
|
1367
1665
|
/**
|
|
1368
|
-
*
|
|
1369
|
-
*
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
*
|
|
1376
|
-
*
|
|
1377
|
-
* const metrics = agent.getMetrics();
|
|
1378
|
-
* console.log(`LLM 调用 ${metrics.llm.callCount} 次,命中率 ${metrics.recall.hitRate}`);
|
|
1379
|
-
*
|
|
1380
|
-
* @returns AgentMetrics 完整快照(含衰减指标)
|
|
1666
|
+
* 后台任务统计快照(宿主观测后台副作用健康度,纯只读转发 utils 单例)。
|
|
1667
|
+
* @returns 在途 / 成功 / 失败计数
|
|
1668
|
+
*/
|
|
1669
|
+
getBackgroundTaskStats() {
|
|
1670
|
+
return readBackgroundTaskStats();
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* 获取 Agent 运行时指标快照(可观测性):聚合 AgentLoop 指标与 Agent 层治理指标;
|
|
1674
|
+
* 未初始化返回全零默认值(不抛异常),纯只读同步零副作用,适合宿主定期轮询监控面板。
|
|
1381
1675
|
*/
|
|
1382
1676
|
getMetrics() {
|
|
1383
|
-
//
|
|
1384
|
-
const decayMetrics = this.memoryDecayScheduler?.getMetrics() ?? {
|
|
1385
|
-
runCount: 0,
|
|
1386
|
-
totalDecayedCount: 0,
|
|
1387
|
-
lastRunAt: null,
|
|
1388
|
-
};
|
|
1389
|
-
// 未初始化时返回全零指标,避免调用方需要判空
|
|
1677
|
+
// 未初始化时返回全零指标,避免调用方判空
|
|
1390
1678
|
if (!this.loop) {
|
|
1391
1679
|
return {
|
|
1392
|
-
llm: {
|
|
1393
|
-
|
|
1394
|
-
|
|
1680
|
+
llm: {
|
|
1681
|
+
callCount: 0,
|
|
1682
|
+
totalInputTokens: 0,
|
|
1683
|
+
totalOutputTokens: 0,
|
|
1684
|
+
actualInputTokens: 0,
|
|
1685
|
+
actualOutputTokens: 0,
|
|
1686
|
+
emptyResponseCount: 0,
|
|
1687
|
+
truncationRecoveryCount: 0,
|
|
1688
|
+
pairingGuardFires: 0,
|
|
1689
|
+
},
|
|
1690
|
+
tools: {
|
|
1691
|
+
callCount: 0,
|
|
1692
|
+
failureCount: 0,
|
|
1693
|
+
unparsedToolIntentCount: 0,
|
|
1694
|
+
ledgerStubEchoCount: 0,
|
|
1695
|
+
readDedupBlockCount: 0,
|
|
1696
|
+
},
|
|
1395
1697
|
context: { truncationCount: 0, messageCount: 0, estimatedTokens: 0 },
|
|
1396
|
-
|
|
1698
|
+
plan: { taskTableWriteCount: 0, planItemBoundaryCount: 0 },
|
|
1699
|
+
tasks: {
|
|
1700
|
+
totalCount: 0,
|
|
1701
|
+
successCount: 0,
|
|
1702
|
+
failureCount: 0,
|
|
1703
|
+
successRate: 0,
|
|
1704
|
+
avgDurationMs: 0,
|
|
1705
|
+
},
|
|
1397
1706
|
};
|
|
1398
1707
|
}
|
|
1399
|
-
// 获取 AgentLoop 指标快照,填充衰减字段
|
|
1400
1708
|
const loopMetrics = this.loop.getMetrics();
|
|
1401
1709
|
return {
|
|
1402
1710
|
...loopMetrics,
|
|
1403
|
-
decay: decayMetrics,
|
|
1404
1711
|
};
|
|
1405
1712
|
}
|
|
1406
|
-
// ─── Manager
|
|
1407
|
-
//
|
|
1408
|
-
//
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
// assertInitialized 抛 MemoraError,提供明确错误信息
|
|
1413
|
-
// 宿主项目使用访问器时需自行判空,或使用门面方法获得自动错误处理。
|
|
1414
|
-
/**
|
|
1415
|
-
* 角色管理器(可能为 null)
|
|
1416
|
-
*
|
|
1417
|
-
* 返回 null 时表示 Agent 未初始化或角色系统未加载。
|
|
1418
|
-
* 链式调用建议使用可选链:`agent.persona?.activeName`
|
|
1419
|
-
*/
|
|
1420
|
-
get persona() {
|
|
1421
|
-
return this.personaManager;
|
|
1713
|
+
// ─── Manager 暴露(调用方直接操作 Manager)──
|
|
1714
|
+
// 设计策略:访问器(getter)返回 Manager | null 供链式调用和优雅降级(如 `if (!agent.memory) return []`);
|
|
1715
|
+
// 门面方法经 assertInitialized 抛明确错误;宿主用访问器需自行判空,或用门面方法获得自动错误处理。
|
|
1716
|
+
/** 角色包管理器(角色+技能+规则的唯一真理源,system prompt 的唯一注入源) */
|
|
1717
|
+
get rolePack() {
|
|
1718
|
+
return this._rolePackManager;
|
|
1422
1719
|
}
|
|
1423
|
-
/**
|
|
1424
|
-
* 工具执行器(可能为 null)
|
|
1425
|
-
*
|
|
1426
|
-
* 返回 null 时表示 Agent 未初始化。
|
|
1427
|
-
*/
|
|
1720
|
+
/** 工具执行器(返回 null 表示 Agent 未初始化) */
|
|
1428
1721
|
get tools() {
|
|
1429
1722
|
return this.toolExec;
|
|
1430
1723
|
}
|
|
1431
|
-
/**
|
|
1432
|
-
* 技能管理器(可能为 null)
|
|
1433
|
-
*
|
|
1434
|
-
* 返回 null 时表示 Agent 未初始化或技能系统未加载。
|
|
1435
|
-
*/
|
|
1724
|
+
/** 技能管理器(返回 null 表示未初始化或技能系统未加载) */
|
|
1436
1725
|
get skills() {
|
|
1437
1726
|
return this.skillManager;
|
|
1438
1727
|
}
|
|
1439
|
-
/**
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
* 返回 null 时表示 Agent 未初始化。
|
|
1443
|
-
*/
|
|
1444
|
-
get config() {
|
|
1445
|
-
return this.configManager;
|
|
1446
|
-
}
|
|
1447
|
-
/**
|
|
1448
|
-
* Insight 提取器(可能为 null)—— 输入分类 + 记忆提取
|
|
1449
|
-
*
|
|
1450
|
-
* 返回 null 时表示 Agent 未初始化或 LLM Provider 未配置。
|
|
1451
|
-
*/
|
|
1452
|
-
get insight() {
|
|
1453
|
-
return this.insightExtractor;
|
|
1728
|
+
/** 角色包管理器(返回 null 表示未初始化或角色包系统未加载) */
|
|
1729
|
+
get rolePacks() {
|
|
1730
|
+
return this._rolePackManager;
|
|
1454
1731
|
}
|
|
1455
|
-
/**
|
|
1456
|
-
* 记忆查看器(可能为 null)—— 快照 + 搜索 + 统计
|
|
1457
|
-
*
|
|
1458
|
-
* 返回 null 时表示 Agent 未初始化或存储层未就绪。
|
|
1459
|
-
* 宿主项目常用模式:`const mem = agent.memory; if (!mem) return [];`
|
|
1460
|
-
*/
|
|
1732
|
+
/** 记忆查看器(快照 + 搜索 + 统计;宿主常用 `const mem = agent.memory; if (!mem) return []`) */
|
|
1461
1733
|
get memory() {
|
|
1462
1734
|
return this.memoryInspector;
|
|
1463
1735
|
}
|
|
1464
|
-
/**
|
|
1465
|
-
* 项目管理器(可能为 null)
|
|
1466
|
-
*
|
|
1467
|
-
* 返回 null 时表示 Agent 未初始化。
|
|
1468
|
-
*/
|
|
1736
|
+
/** 项目管理器(返回 null 表示 Agent 未初始化) */
|
|
1469
1737
|
get projects() {
|
|
1470
1738
|
return this.projectManager;
|
|
1471
1739
|
}
|
|
1472
|
-
/**
|
|
1473
|
-
* 用户画像管理器(可能为 null)—— 实时归档 + 确认/拒绝
|
|
1474
|
-
*
|
|
1475
|
-
* 返回 null 时表示 Agent 未初始化。
|
|
1476
|
-
* 宿主常用模式:
|
|
1477
|
-
* - `agent.userProfile?.getPending()` 查询待确认条目
|
|
1478
|
-
* - `await agent.userProfile?.confirm(id)` 确认条目
|
|
1479
|
-
* - `await agent.userProfile?.reject(id)` 拒绝条目
|
|
1480
|
-
*/
|
|
1481
|
-
get userProfile() {
|
|
1482
|
-
return this.#userProfile;
|
|
1483
|
-
}
|
|
1484
|
-
/**
|
|
1485
|
-
* 安全守卫(可能为 null)—— 写入确认回调注册
|
|
1486
|
-
*
|
|
1487
|
-
* 返回 null 时表示 Agent 未初始化或 projectPath 未设置。
|
|
1488
|
-
* 宿主常用模式:
|
|
1489
|
-
* - `agent.security?.onWriteConfirmation(callback)` 注册写入确认回调
|
|
1490
|
-
* - 回调返回 true 允许写入,返回 false 拒绝写入
|
|
1491
|
-
*
|
|
1492
|
-
* 详见 M1 写入确认 UI 闭环
|
|
1493
|
-
*/
|
|
1740
|
+
/** 安全守卫(写入确认回调注册:`onWriteConfirmation` 返回 true 放行、false 拒绝;未初始化/projectPath 未设为 null) */
|
|
1494
1741
|
get security() {
|
|
1495
1742
|
return this.pctx?.security ?? null;
|
|
1496
1743
|
}
|
|
1497
|
-
/**
|
|
1498
|
-
* 作品投影管理器(可能为 null)—— 文件内容 → 概要+结构+决策
|
|
1499
|
-
*
|
|
1500
|
-
* 返回 null 时表示 Agent 未初始化。
|
|
1501
|
-
* 宿主常用模式:
|
|
1502
|
-
* - `agent.works?.ensureProjection(filePath, content)` 读取文件后触发生成
|
|
1503
|
-
* - `agent.works?.loadAll()` 获取所有投影
|
|
1504
|
-
*/
|
|
1744
|
+
/** 作品投影管理器(用户主动登记的 JSON 极简索引;`registerWork`/`listWorks`/`loadAndGetContextBlock`/`isFileMalformed`) */
|
|
1505
1745
|
get works() {
|
|
1506
1746
|
return this.workProjection;
|
|
1507
1747
|
}
|
|
1508
|
-
/**
|
|
1748
|
+
/** 会话管理器(会话恢复/切换/分叉功能) */
|
|
1509
1749
|
get sessionManager() {
|
|
1510
1750
|
return this._sessionManager;
|
|
1511
1751
|
}
|
|
1512
|
-
/**
|
|
1752
|
+
/** 文本润色管理器(polish 方法) */
|
|
1513
1753
|
get polish() {
|
|
1514
1754
|
return this.textPolisher;
|
|
1515
1755
|
}
|