From 5c076183ac99830ea75e33cd33b6c6a8ddb08067 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Wed, 8 Jul 2026 18:14:07 +0800 Subject: [PATCH] fix(agent-core-v2): preserve compaction hook session --- .../agent-core-v2/docs/di-scope-domains.puml | 1 + .../agent-core-v2/docs/di-scope-domains.svg | 2 +- .../externalHooks/externalHooksService.ts | 18 +++++++++++++++--- .../test/fullCompaction/full.test.ts | 13 +++++++++---- packages/agent-core-v2/test/harness/agent.ts | 6 +++++- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/agent-core-v2/docs/di-scope-domains.puml b/packages/agent-core-v2/docs/di-scope-domains.puml index 0d103484f..7cbdc8d22 100644 --- a/packages/agent-core-v2/docs/di-scope-domains.puml +++ b/packages/agent-core-v2/docs/di-scope-domains.puml @@ -315,6 +315,7 @@ externalHooks --> config #34495E externalHooks --> bootstrap #34495E externalHooks --> plugin #34495E externalHooks --> contextMemory #34495E +externalHooks --> session_context #34495E sessionExternalHooks --> session_lifecycle #34495E sessionExternalHooks --> agent_lifecycle #34495E sessionExternalHooks --> config #34495E diff --git a/packages/agent-core-v2/docs/di-scope-domains.svg b/packages/agent-core-v2/docs/di-scope-domains.svg index f5038c661..1aeeba443 100644 --- a/packages/agent-core-v2/docs/di-scope-domains.svg +++ b/packages/agent-core-v2/docs/di-scope-domains.svg @@ -1 +1 @@ -App scope (process-wide)Session scope (per session)Agent scope (per agent)bootstrapAppIBootstrapServicelog_base · App bindingILogServicetelemetryAppITelemetryServiceeventAppIEventServicestorageAppIFileSystemStorageServiceIAppendLogStoreIAtomicDocumentStoreIAtomicTomlDocumentStorefilestoreAppIFileStoregatewayAppIRestGatewayIWSGatewaysessionLifecycleAppISessionLifecycleServicesessionExportAppISessionExportServicesessionIndexAppISessionIndexhostFsAppIHostFileSystemworkspaceRegistryAppIWorkspaceRegistryworkspaceLocalConfigAppIWorkspaceLocalConfigServicehostFolderBrowserAppIHostFolderBrowserhostEnvironmentAppIHostEnvironmenthostProcessAppIHostProcessServiceauthAppIOAuthServiceIAuthSummaryServiceIWebSearchProviderServicewebAppIWebFetchServiceeditAppIFileEditServiceproviderAppIProviderServiceplatformAppIPlatformServiceflagAppIFlagServiceIFlagRegistryconfigAppIConfigRegistryIConfigServicepluginAppIPluginServicechatProviderAppIChatProviderFactorymodelAppIModelServicemodelCatalogAppIModelCatalogServiceagentProfileCatalogAppIAgentProfileCatalogServiceskillCatalogAppISkillDiscoveryISkillSourceBuiltin/UserSkillSourcesessionContextSessionISessionContext (seed)sessionMetadataSessionISessionMetadatasessionActivitySessionISessionActivityagentLifecycleSessionIAgentLifecycleServicesessionSwarmSessionISessionSwarmServiceinteractionSessionIInteractionServiceworkspaceContextSessionIWorkspaceContextworkspaceCommandSessionISessionWorkspaceCommandServicesessionLogSession bindingILogServicesessionSkillCatalogSessionISessionSkillCatalogISkillCatalogSinkWorkspace/PluginSkillSourceexternalHooksSessionISessionExternalHooksServicesessionFsSessionISessionFsServiceapprovalSessionIApprovalServicequestionSessionIQuestionServiceprocessSessionIProcessRunnerIProcessterminalAppIHostTerminalServicesessionTerminalSessionISessionTerminalServicemodelProviderSessionIModelProvider (seed)todoSessionISessionTodoServicewireRecordAgentIAgentWireRecordService (event hub)wireAgentIAgentWireServiceblobStoreAgentIAgentBlobStoreServicecontextMemoryAgentIAgentContextMemoryServicecontextProjectorAgentIAgentContextProjectorServicecontextInjectorAgentIAgentContextInjectorServicecontextSizeAgentIAgentContextSizeServicesystemReminderAgentIAgentSystemReminderServiceprofileAgentIAgentProfileServicepromptAgentIAgentPromptServicepromptLegacyAgentIAgentPromptLegacyServiceturnAgentIAgentTurnServiceloopAgentIAgentLoopServicellmRequesterAgentIAgentLLMRequesterServicetoolRegistryAgentIAgentToolRegistryServicetoolExecutorAgentIAgentToolExecutorServicetoolDedupeAgentIAgentToolDedupeServicepermissionGateAgentIAgentPermissionGatepermissionModeAgentIAgentPermissionModeServicepermissionPolicyAgentIAgentPermissionPolicyServicepermissionRulesAgentIAgentPermissionRulesServiceplanAgentIAgentPlanServicegoalAgentIAgentGoalServiceskillAgentIAgentSkillServicequestionToolsAgentIAgentQuestionToolsServiceuserToolAgentIAgentUserToolServicetaskAgentIAgentTaskServicecronAgentIAgentCronServiceswarmAgentIAgentSwarmServicemcpAgentIAgentMcpServicefullCompactionAgentIAgentFullCompactionServicemicroCompactionAgentIAgentMicroCompactionServiceexternalHooksAgentIAgentExternalHooksServiceusageAgentIAgentUsageServicerpcAgentIAgentRPCServicefileToolsAgentRead/Write/Grep/Glob toolseditAgentEditToolshellToolsAgentIAgentShellToolsServicescopeContextAgentIAgentScopeContext (seed)environmentrecordeventSinkonDidChangeConfigurationcontext_size.measuredconfig.update / tools.set_active_toolsagent.status.updated / warningturn.launchhooks.onSplicedpermission.set_modepermission.rules.add / record_approval_resultgoal.create/update/clearturn lifecycle hooksstep/usage hooksskill.activatetools.register_/unregister_user_toolhooks.onSplicedcron.add / delete / cursorswarm_mode.enter/exithooks.onErrormicro_compaction.applySessionStart/End hooksSubagentStart/Stop (observes run hooks)usage.recordcontext.splicetodo.set (main agent)full_compaction.begin/cancel/completehook.result / goal.updatedhooks.onEndedtask.started/terminatedonWillExecuteTool / onDidExecuteToolapproval hooksprompt/end hooksstop hookcompaction hooksnotification hookhooks.onResumeEndedplan_mode.enter/cancel/exitScope = node color      App (process-wide)      Session (per session)      Agent (per agent)Edgessolid: DI injection (ctor @IX)dashed: event-driven (subscribe/emit)direction: consumer ---> providerNotesGenerated from `node scripts/dep-graph.mjs` output;`_base` / seed / options deps are omitted. +App scope (process-wide)Session scope (per session)Agent scope (per agent)bootstrapAppIBootstrapServicelog_base · App bindingILogServicetelemetryAppITelemetryServiceeventAppIEventServicestorageAppIFileSystemStorageServiceIAppendLogStoreIAtomicDocumentStoreIAtomicTomlDocumentStorefilestoreAppIFileStoregatewayAppIRestGatewayIWSGatewaysessionLifecycleAppISessionLifecycleServicesessionExportAppISessionExportServicesessionIndexAppISessionIndexhostFsAppIHostFileSystemworkspaceRegistryAppIWorkspaceRegistryworkspaceLocalConfigAppIWorkspaceLocalConfigServicehostFolderBrowserAppIHostFolderBrowserhostEnvironmentAppIHostEnvironmenthostProcessAppIHostProcessServiceauthAppIOAuthServiceIAuthSummaryServiceIWebSearchProviderServicewebAppIWebFetchServiceeditAppIFileEditServiceproviderAppIProviderServiceplatformAppIPlatformServiceflagAppIFlagServiceIFlagRegistryconfigAppIConfigRegistryIConfigServicepluginAppIPluginServicechatProviderAppIChatProviderFactorymodelAppIModelServicemodelCatalogAppIModelCatalogServiceagentProfileCatalogAppIAgentProfileCatalogServiceskillCatalogAppISkillDiscoveryISkillSourceBuiltin/UserSkillSourcesessionContextSessionISessionContext (seed)sessionMetadataSessionISessionMetadatasessionActivitySessionISessionActivityagentLifecycleSessionIAgentLifecycleServicesessionSwarmSessionISessionSwarmServiceinteractionSessionIInteractionServiceworkspaceContextSessionIWorkspaceContextworkspaceCommandSessionISessionWorkspaceCommandServicesessionLogSession bindingILogServicesessionSkillCatalogSessionISessionSkillCatalogISkillCatalogSinkWorkspace/PluginSkillSourceexternalHooksSessionISessionExternalHooksServicesessionFsSessionISessionFsServiceapprovalSessionIApprovalServicequestionSessionIQuestionServiceprocessSessionIProcessRunnerIProcessterminalAppIHostTerminalServicesessionTerminalSessionISessionTerminalServicemodelProviderSessionIModelProvider (seed)todoSessionISessionTodoServicewireRecordAgentIAgentWireRecordService (event hub)wireAgentIAgentWireServiceblobStoreAgentIAgentBlobStoreServicecontextMemoryAgentIAgentContextMemoryServicecontextProjectorAgentIAgentContextProjectorServicecontextInjectorAgentIAgentContextInjectorServicecontextSizeAgentIAgentContextSizeServicesystemReminderAgentIAgentSystemReminderServiceprofileAgentIAgentProfileServicepromptAgentIAgentPromptServicepromptLegacyAgentIAgentPromptLegacyServiceturnAgentIAgentTurnServiceloopAgentIAgentLoopServicellmRequesterAgentIAgentLLMRequesterServicetoolRegistryAgentIAgentToolRegistryServicetoolExecutorAgentIAgentToolExecutorServicetoolDedupeAgentIAgentToolDedupeServicepermissionGateAgentIAgentPermissionGatepermissionModeAgentIAgentPermissionModeServicepermissionPolicyAgentIAgentPermissionPolicyServicepermissionRulesAgentIAgentPermissionRulesServiceplanAgentIAgentPlanServicegoalAgentIAgentGoalServiceskillAgentIAgentSkillServicequestionToolsAgentIAgentQuestionToolsServiceuserToolAgentIAgentUserToolServicetaskAgentIAgentTaskServicecronAgentIAgentCronServiceswarmAgentIAgentSwarmServicemcpAgentIAgentMcpServicefullCompactionAgentIAgentFullCompactionServicemicroCompactionAgentIAgentMicroCompactionServiceexternalHooksAgentIAgentExternalHooksServiceusageAgentIAgentUsageServicerpcAgentIAgentRPCServicefileToolsAgentRead/Write/Grep/Glob toolseditAgentEditToolshellToolsAgentIAgentShellToolsServicescopeContextAgentIAgentScopeContext (seed)environmenteventSinkonDidChangeConfigurationcontext_size.measuredconfig.update / tools.set_active_toolsagent.status.updated / warningturn.launchhooks.onSplicedpermission.set_modepermission.rules.add / record_approval_resultplan_mode.enter/cancel/exitgoal.create/update/clearturn lifecycle hooksstep/usage hooksskill.activatetools.register_/unregister_user_toolhooks.onSplicedcron.add / delete / cursorswarm_mode.enter/exithooks.onErrormicro_compaction.applySessionStart/End hooksSubagentStart/Stop (observes run hooks)usage.recordcontext.splicetodo.set (main agent)full_compaction.begin/cancel/completehook.result / goal.updatedhooks.onEndedtask.started/terminatedonWillExecuteTool / onDidExecuteToolapproval hooksprompt/end hooksstop hookcompaction hooksnotification hookhooks.onResumeEndedScope = node color      App (process-wide)      Session (per session)      Agent (per agent)Edgessolid: DI injection (ctor @IX)dashed: event-driven (subscribe/emit)direction: consumer ---> providerNotesGenerated from `node scripts/dep-graph.mjs` output;`_base` / seed / options deps are omitted. \ No newline at end of file diff --git a/packages/agent-core-v2/src/agent/externalHooks/externalHooksService.ts b/packages/agent-core-v2/src/agent/externalHooks/externalHooksService.ts index 51c585fd1..2b0075850 100644 --- a/packages/agent-core-v2/src/agent/externalHooks/externalHooksService.ts +++ b/packages/agent-core-v2/src/agent/externalHooks/externalHooksService.ts @@ -10,8 +10,9 @@ * of its own). The requester-side `SubagentStart` / `SubagentStop` hooks are * translated by the Session-scope `SessionExternalHooksService`, which observes * the `agentLifecycle` run slots hosted on `IAgentLifecycleService`. Appends - * UserPromptSubmit hook results - * and Stop hook continuation prompts through `contextMemory`. + * UserPromptSubmit hook results and Stop hook continuation prompts through + * `contextMemory`, and passes the current session id from `sessionContext` + * into hook runner payloads. */ import { IInstantiationService } from '#/_base/di/instantiation'; @@ -42,6 +43,7 @@ import type { ToolDidExecuteContext, ToolWillExecuteContext } from '#/agent/tool import { IAgentToolExecutorService } from '#/agent/toolExecutor/toolExecutor'; import { IAgentTurnService } from '#/agent/turn/turn'; import { toKimiErrorPayload } from '#/errors'; +import { ISessionContext } from '#/session/sessionContext/sessionContext'; import { IAgentExternalHooksService } from './externalHooks'; import { IExternalHooksRunnerService } from '#/app/externalHooksRunner/externalHooksRunner'; @@ -66,6 +68,7 @@ export class AgentExternalHooksService extends Disposable implements IAgentExter @IAgentContextMemoryService private readonly context: IAgentContextMemoryService, @IEventBus private readonly eventBus: IEventBus, @IInstantiationService private readonly instantiation: IInstantiationService, + @ISessionContext private readonly sessionContext: ISessionContext, ) { super(); this.registerListeners(); @@ -82,7 +85,12 @@ export class AgentExternalHooksService extends Disposable implements IAgentExter // output), and throwing here would clobber that with a finalize-abort error. // The runner mirrors the legacy fire-and-forget behavior. try { - void this.runner.fireAndForgetTrigger(event, { matcherValue, signal, inputData }); + void this.runner.fireAndForgetTrigger(event, { + matcherValue, + signal, + sessionId: this.sessionContext.sessionId, + inputData, + }); } catch {} } @@ -222,6 +230,7 @@ export class AgentExternalHooksService extends Disposable implements IAgentExter const block = await this.runner.triggerBlock('PreToolUse', { matcherValue: ctx.toolCall.name, signal: ctx.signal, + sessionId: this.sessionContext.sessionId, inputData: { toolName: ctx.toolCall.name, toolInput, @@ -260,6 +269,7 @@ export class AgentExternalHooksService extends Disposable implements IAgentExter const results = await this.runner.trigger('UserPromptSubmit', { matcherValue: input, signal, + sessionId: this.sessionContext.sessionId, inputData: { prompt: input, isSteer: ctx.isSteer }, }); signal.throwIfAborted(); @@ -331,6 +341,7 @@ export class AgentExternalHooksService extends Disposable implements IAgentExter const block = await this.runner.triggerBlock('Stop', { signal: ctx.signal, + sessionId: this.sessionContext.sessionId, inputData: { stopHookActive: false }, }); ctx.signal.throwIfAborted(); @@ -343,6 +354,7 @@ export class AgentExternalHooksService extends Disposable implements IAgentExter await this.runner.trigger('PreCompact', { matcherValue: ctx.trigger, signal, + sessionId: this.sessionContext.sessionId, inputData: { trigger: ctx.trigger, tokenCount: ctx.tokenCount, diff --git a/packages/agent-core-v2/test/fullCompaction/full.test.ts b/packages/agent-core-v2/test/fullCompaction/full.test.ts index c657cf455..cc3fcc430 100644 --- a/packages/agent-core-v2/test/fullCompaction/full.test.ts +++ b/packages/agent-core-v2/test/fullCompaction/full.test.ts @@ -1,4 +1,4 @@ -import { existsSync, mkdtempSync, readFileSync } from 'node:fs'; +import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'pathe'; @@ -511,14 +511,14 @@ describe('FullCompaction', () => { const [pre, post] = readHookPayloads(hookLog); expect(pre).toMatchObject({ hook_event_name: 'PreCompact', - session_id: 'session-hooks', + session_id: 'test-session', cwd: dir, trigger: 'auto', token_count: 39, }); expect(post).toMatchObject({ hook_event_name: 'PostCompact', - session_id: 'session-hooks', + session_id: 'test-session', cwd: dir, trigger: 'auto', estimated_token_count: ctx.contextData().tokenCount, @@ -2545,6 +2545,10 @@ function messageText(message: Message | undefined): string { } function hookPayloadLoggerCommand(logPath: string): string { + // Write the hook script to a file and run it with node, instead of + // `node -e `; cmd.exe on Windows mangles the escaped quotes in the + // inline form and corrupts the script before it can run. + const scriptPath = `${logPath}.cjs`; const script = [ "const fs = require('node:fs');", "let input = '';", @@ -2553,7 +2557,8 @@ function hookPayloadLoggerCommand(logPath: string): string { ` fs.appendFileSync(${JSON.stringify(logPath)}, JSON.stringify(JSON.parse(input)) + '\\n');`, '});', ].join(''); - return `node -e ${JSON.stringify(script)}`; + writeFileSync(scriptPath, script); + return `${process.execPath} ${scriptPath}`; } function readHookPayloads(logPath: string): Array> { diff --git a/packages/agent-core-v2/test/harness/agent.ts b/packages/agent-core-v2/test/harness/agent.ts index 84c8c1b87..105db7f0e 100644 --- a/packages/agent-core-v2/test/harness/agent.ts +++ b/packages/agent-core-v2/test/harness/agent.ts @@ -576,7 +576,11 @@ function resolveExternalHooksRunner( function isRunnerLike( value: Pick, ): value is IExternalHooksRunnerService { - return '_serviceBrand' in value; + return ( + typeof value.trigger === 'function' && + typeof value.triggerBlock === 'function' && + typeof value.fireAndForgetTrigger === 'function' + ); } const noopHookRunner: IExternalHooksRunnerService = {