From 1414d4602898f406e540b23342cb18db23ff9efc Mon Sep 17 00:00:00 2001 From: Haozhe Date: Thu, 13 Aug 2026 21:03:51 +0800 Subject: [PATCH] refactor(agent-core-v2): fold session lifecycle hooks into sessionLifecycle events (#2896) --- packages/agent-core-v2/src/index.ts | 1 - .../externalHooks/externalHooksService.ts | 26 ++++---- .../sessionLifecycleHooks.ts | 36 ----------- .../sessionLifecycle/sessionLifecycle.ts | 13 ++-- .../sessionLifecycleService.ts | 36 +++++------ .../externalHooksRunner/integration.test.ts | 61 +++++++++++-------- .../test/app/gateway/gateway.test.ts | 1 + .../app/sessionExport/sessionExport.test.ts | 1 + packages/agent-core-v2/test/harness/agent.ts | 21 +++---- .../sessionLifecycle/sessionLifecycle.test.ts | 15 ++--- .../node-sdk/test/sdk-rpc-client-v2.test.ts | 13 ++-- 11 files changed, 91 insertions(+), 133 deletions(-) delete mode 100644 packages/agent-core-v2/src/session/sessionLifecycleHooks/sessionLifecycleHooks.ts diff --git a/packages/agent-core-v2/src/index.ts b/packages/agent-core-v2/src/index.ts index cfa9e2dbf..942cc1f83 100644 --- a/packages/agent-core-v2/src/index.ts +++ b/packages/agent-core-v2/src/index.ts @@ -416,7 +416,6 @@ export * from '#/workspace/workspaceContext/workspaceContext'; export * from '#/workspace/sessionLifecycle/sessionLifecycle'; export * from '#/workspace/sessionLifecycle/sessionLifecycleService'; export * from '#/workspace/sessionLifecycle/internal/addressing'; -export * from '#/session/sessionLifecycleHooks/sessionLifecycleHooks'; export * from '#/session/externalHooks/externalHooks'; export * from '#/session/externalHooks/externalHooksService'; import '#/app/sessionExport/errors'; diff --git a/packages/agent-core-v2/src/session/externalHooks/externalHooksService.ts b/packages/agent-core-v2/src/session/externalHooks/externalHooksService.ts index cd82ec7a6..c27fb375b 100644 --- a/packages/agent-core-v2/src/session/externalHooks/externalHooksService.ts +++ b/packages/agent-core-v2/src/session/externalHooks/externalHooksService.ts @@ -28,24 +28,22 @@ import { LifecycleScope } from '#/app/scopes'; import { ScopeActivation, registerScopedService } from '#/_base/di/scope'; import { IntervalTimer } from '#/_base/utils/timer'; import { IExternalHooksRunnerService } from '#/app/externalHooksRunner/externalHooksRunner'; -import type { Hooks } from '#/hooks'; import { IModelService } from '#/kosong/model/model'; import { ISessionAgentProfileCatalog, } from '#/session/sessionAgentProfileCatalog/sessionAgentProfileCatalog'; import { ISessionContext } from '#/session/sessionContext/sessionContext'; -import { - ISessionLifecycleHooks, - type SessionCloseReason, - type SessionCreateSource, - type SessionLifecycleHookSlots, -} from '#/session/sessionLifecycleHooks/sessionLifecycleHooks'; import { ISessionMetadata } from '#/session/sessionMetadata/sessionMetadata'; import { type AgentTaskStartHookContext, type AgentTaskStopHookContext, ISessionSubagentService, } from '#/session/subagent/subagent'; +import { + ISessionLifecycleService, + type SessionCloseReason, + type SessionCreateSource, +} from '#/workspace/sessionLifecycle/sessionLifecycle'; import { ISessionExternalHooksService } from './externalHooks'; @@ -64,7 +62,7 @@ export class SessionExternalHooksService constructor( @ISessionContext private readonly context: ISessionContext, - @ISessionLifecycleHooks lifecycleHooks: Hooks, + @ISessionLifecycleService lifecycle: ISessionLifecycleService, @ISessionSubagentService subagents: ISessionSubagentService, @ISessionMetadata private readonly metadata: ISessionMetadata, @ISessionAgentProfileCatalog private readonly profiles: ISessionAgentProfileCatalog, @@ -90,17 +88,17 @@ export class SessionExternalHooksService }), ); this._register( - lifecycleHooks.onDidCreateSession.register('externalHooks', async (event, next) => { + lifecycle.onDidCreateSession((event) => { + if (event.sessionId !== this.context.sessionId) return; if (event.source !== 'fork') { - await this.triggerSessionStart(event.source); + event.waitUntil(this.triggerSessionStart(event.source)); } - await next(); }), ); this._register( - lifecycleHooks.onWillCloseSession.register('externalHooks', async (event, next) => { - await this.triggerSessionEnd(event.reason); - await next(); + lifecycle.onWillCloseSession((event) => { + if (event.sessionId !== this.context.sessionId) return; + event.waitUntil(this.triggerSessionEnd(event.reason)); }), ); this._register( diff --git a/packages/agent-core-v2/src/session/sessionLifecycleHooks/sessionLifecycleHooks.ts b/packages/agent-core-v2/src/session/sessionLifecycleHooks/sessionLifecycleHooks.ts deleted file mode 100644 index dabb8a0cf..000000000 --- a/packages/agent-core-v2/src/session/sessionLifecycleHooks/sessionLifecycleHooks.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * `sessionLifecycleHooks` domain — per-session lifecycle hook slots. - * - * Defines the `ISessionLifecycleHooks` seed: one ordered hook-slots instance - * per session, with slots around the session's create (`onDidCreateSession`) - * and close (`onWillCloseSession`). Also owns the shared - * `SessionCreateSource` / `SessionCloseReason` vocabulary. - */ - -import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; -import type { ScopeSeed } from '#/_base/di/scope'; -import type { Hooks } from '#/hooks'; - -export type SessionCreateSource = 'startup' | 'resume' | 'fork'; - -export type SessionCloseReason = 'exit' | 'archive'; - -export interface SessionStartHookEvent { - readonly source: SessionCreateSource; -} - -export interface SessionEndHookEvent { - readonly reason: SessionCloseReason; -} - -export type SessionLifecycleHookSlots = { - readonly onDidCreateSession: SessionStartHookEvent; - readonly onWillCloseSession: SessionEndHookEvent; -}; - -export const ISessionLifecycleHooks: ServiceIdentifier> = - createDecorator>('sessionLifecycleHooks'); - -export function sessionLifecycleHooksSeed(hooks: Hooks): ScopeSeed { - return [[ISessionLifecycleHooks as ServiceIdentifier, hooks]]; -} diff --git a/packages/agent-core-v2/src/workspace/sessionLifecycle/sessionLifecycle.ts b/packages/agent-core-v2/src/workspace/sessionLifecycle/sessionLifecycle.ts index 6808b4678..d3a332d90 100644 --- a/packages/agent-core-v2/src/workspace/sessionLifecycle/sessionLifecycle.ts +++ b/packages/agent-core-v2/src/workspace/sessionLifecycle/sessionLifecycle.ts @@ -24,15 +24,13 @@ import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; import type { ISessionScopeHandle } from '#/_base/di/scope'; -import type { Event } from '#/_base/event'; +import { type Event, type IWaitUntil } from '#/_base/event'; import type { BindAgentInput } from '#/agent/profile/profile'; import type { McpServerConfig } from '#/mcpCore/config-schema'; -import type { - SessionCloseReason, - SessionCreateSource, -} from '#/session/sessionLifecycleHooks/sessionLifecycleHooks'; -export type { SessionCloseReason, SessionCreateSource }; +export type SessionCreateSource = 'startup' | 'resume' | 'fork'; + +export type SessionCloseReason = 'exit' | 'archive'; export interface CreateSessionOptions { readonly sessionId?: string; @@ -124,7 +122,8 @@ export interface ISessionLifecycleService { readonly _serviceBrand: undefined; readonly onWillCreateSession: Event; - readonly onDidCreateSession: Event; + readonly onDidCreateSession: Event; + readonly onWillCloseSession: Event; readonly onDidCloseSession: Event; readonly onDidArchiveSession: Event; readonly onDidForkSession: Event; diff --git a/packages/agent-core-v2/src/workspace/sessionLifecycle/sessionLifecycleService.ts b/packages/agent-core-v2/src/workspace/sessionLifecycle/sessionLifecycleService.ts index 22493e986..604e17807 100644 --- a/packages/agent-core-v2/src/workspace/sessionLifecycle/sessionLifecycleService.ts +++ b/packages/agent-core-v2/src/workspace/sessionLifecycle/sessionLifecycleService.ts @@ -101,7 +101,7 @@ import { registerScopedService, } from '#/_base/di/scope'; import { unwrapErrorCause } from '#/_base/errors/errors'; -import { Emitter, type Event } from '#/_base/event'; +import { AsyncEmitter, Emitter, type Event, type IWaitUntil } from '#/_base/event'; import { DEFAULT_PLAN_MODE_SECTION } from '#/features/plan/configSection'; import { IAgentPlanService } from '#/features/plan/plan'; import { IBootstrapService } from '#/app/bootstrap/bootstrap'; @@ -118,7 +118,6 @@ import { } from '#/app/sessionIndex/sessionIndex'; import { ITelemetryService } from '#/app/telemetry/telemetry'; import { ErrorCodes, Error2, isError2 } from '#/errors'; -import { createHooks } from '#/hooks'; import { IHostEnvironment } from '#/os/interface/hostEnvironment'; import { IHostFileSystem, type HostDirEntry } from '#/os/interface/hostFileSystem'; import { IAppendLogStore } from '#/persistence/interface/appendLogStore'; @@ -130,11 +129,6 @@ import { ISessionContext, sessionContextSeed } from '#/session/sessionContext/se import { sessionEphemeralMcpServersSeed } from '#/session/mcp/ephemeralMcpServers'; import { sessionAgentProfileCatalogSeed } from '#/session/sessionAgentProfileCatalog/agentProfileCatalogSeed'; import { installSessionSeedAdapters } from '#/session/sessionSeed/sessionSeedAdapters'; -import { - ISessionLifecycleHooks, - sessionLifecycleHooksSeed, - type SessionLifecycleHookSlots, -} from '#/session/sessionLifecycleHooks/sessionLifecycleHooks'; import { ISessionMetadata, type SessionMeta } from '#/session/sessionMetadata/sessionMetadata'; import { drainSessionMetadataWrites, toEpochMs } from '#/session/sessionMetadata/sessionMetadataService'; import { ISessionProcessRunner } from '#/session/process/processRunner'; @@ -183,6 +177,8 @@ type MaterializeSessionOptions = Omit & { readonly sessionId: string; }; +const NO_ABORT = new AbortController().signal; + // NOTE: stays Disposable — its own 'get' and 'config' collide with the Fiber export class SessionLifecycleService extends Disposable implements ISessionLifecycleService { declare readonly _serviceBrand: undefined; @@ -192,8 +188,16 @@ export class SessionLifecycleService extends Disposable implements ISessionLifec ); readonly onWillCreateSession: Event = this._onWillCreateSession.event; - private readonly _onDidCreateSession = this._register(new Emitter()); - readonly onDidCreateSession: Event = this._onDidCreateSession.event; + private readonly _onDidCreateSession = this._register( + new AsyncEmitter(), + ); + readonly onDidCreateSession: Event = + this._onDidCreateSession.event; + private readonly _onWillCloseSession = this._register( + new AsyncEmitter(), + ); + readonly onWillCloseSession: Event = + this._onWillCloseSession.event; private readonly _onDidCloseSession = this._register(new Emitter()); readonly onDidCloseSession: Event = this._onDidCloseSession.event; private readonly _onDidArchiveSession = this._register(new Emitter()); @@ -295,10 +299,6 @@ export class SessionLifecycleService extends Disposable implements ISessionLifec scope: (subKey?: string): string => subKey === undefined || subKey === '' ? sessionScope : `${sessionScope}/${subKey}`, }; - const hooks = createHooks([ - 'onDidCreateSession', - 'onWillCloseSession', - ]); await this.hostEnv.ready; const handle = createScopedChildHandle( this.instantiation, @@ -307,7 +307,6 @@ export class SessionLifecycleService extends Disposable implements ISessionLifec { seeds: [ ...sessionContextSeed(ctx), - ...sessionLifecycleHooksSeed(hooks), [ITelemetryService, this.telemetry.withContext({ sessionId: opts.sessionId })], ...sessionAgentProfileCatalogSeed({ _serviceBrand: undefined, @@ -364,10 +363,7 @@ export class SessionLifecycleService extends Disposable implements ISessionLifec } private async announceCreated(event: SessionCreatedEvent): Promise { - await event.handle.accessor - .get(ISessionLifecycleHooks) - .onDidCreateSession.run({ source: event.source }); - this._onDidCreateSession.fire(event); + await this._onDidCreateSession.fireAsync(event, NO_ABORT); event.handle.accessor .get(ITelemetryService) .track2('session_started', { resumed: event.source === 'resume' }); @@ -491,9 +487,7 @@ export class SessionLifecycleService extends Disposable implements ISessionLifec } private async announceWillClose(event: SessionWillCloseEvent): Promise { - await event.handle.accessor - .get(ISessionLifecycleHooks) - .onWillCloseSession.run({ reason: event.reason }); + await this._onWillCloseSession.fireAsync(event, NO_ABORT); } private async drainAgents(handle: ISessionScopeHandle): Promise { diff --git a/packages/agent-core-v2/test/app/externalHooksRunner/integration.test.ts b/packages/agent-core-v2/test/app/externalHooksRunner/integration.test.ts index 934f21134..468974256 100644 --- a/packages/agent-core-v2/test/app/externalHooksRunner/integration.test.ts +++ b/packages/agent-core-v2/test/app/externalHooksRunner/integration.test.ts @@ -11,7 +11,7 @@ import { createServices, type TestInstantiationService, } from '#/_base/di/test'; -import { Emitter, Event } from '#/_base/event'; +import { AsyncEmitter, Emitter, Event, type IWaitUntil } from '#/_base/event'; import { emptyUsage } from '#/kosong/contract/usage'; import { buildContextCompactionShape } from '#/agent/contextMemory/compactionHandoff'; import { @@ -47,10 +47,13 @@ import { IPluginService } from '#/app/plugin/plugin'; import { IHostProcessService } from '#/os/interface/hostProcess'; import { HostProcessService } from '#/os/backends/node-local/hostProcessService'; import { - ISessionLifecycleHooks, - type SessionLifecycleHookSlots, -} from '#/session/sessionLifecycleHooks/sessionLifecycleHooks'; -import { createHooks, type Hooks } from '#/hooks'; + ISessionLifecycleService, + type SessionCloseReason, + type SessionCreatedEvent, + type SessionCreateSource, + type SessionWillCloseEvent, +} from '#/workspace/sessionLifecycle/sessionLifecycle'; +import { createHooks } from '#/hooks'; import { ISessionContext } from '#/session/sessionContext/sessionContext'; import { type AgentTaskHooks, @@ -229,11 +232,21 @@ function stubSessionContext(): ISessionContext { }; } -function stubSessionLifecycleHooks(): Hooks { - return createHooks([ - 'onDidCreateSession', - 'onWillCloseSession', - ]); +function stubSessionLifecycle() { + const didCreate = new AsyncEmitter(); + const willClose = new AsyncEmitter(); + const noAbort = new AbortController().signal; + const handle = {} as ISessionScopeHandle; + return { + service: { + onDidCreateSession: didCreate.event, + onWillCloseSession: willClose.event, + }, + fireDidCreate: (source: SessionCreateSource): Promise => + didCreate.fireAsync({ sessionId: 'session-1', handle, source }, noAbort), + fireWillClose: (reason: SessionCloseReason): Promise => + willClose.fireAsync({ sessionId: 'session-1', handle, reason }, noAbort), + }; } describe('IExternalHooksRunnerService integration', () => { @@ -539,7 +552,7 @@ describe('IExternalHooksRunnerService integration', () => { ? 'sessions/workspace-1/session-1' : `sessions/workspace-1/session-1/${subKey}`, }); - reg.defineInstance(ISessionLifecycleHooks, stubSessionLifecycleHooks()); + reg.definePartialInstance(ISessionLifecycleService, stubSessionLifecycle().service); reg.defineInstance(ISessionMetadata, stubSessionMetadata()); reg.defineInstance(ISessionAgentProfileCatalog, stubProfileCatalog()); reg.defineInstance(IModelService, stubModelService()); @@ -856,7 +869,7 @@ describe('IExternalHooksRunnerService integration', () => { const disposables = new DisposableStore(); let ix: TestInstantiationService | undefined; try { - const lifecycleHooks = stubSessionLifecycleHooks(); + const lifecycle = stubSessionLifecycle(); const path = hookLogPath(); const command = appendHookLogCommand(path); const cwd = mkdtempSync(join(tmpdir(), 'session-external-hooks-cwd-')); @@ -877,7 +890,7 @@ describe('IExternalHooksRunnerService integration', () => { ? 'sessions/workspace-1/session-1' : `sessions/workspace-1/session-1/${subKey}`, }); - reg.defineInstance(ISessionLifecycleHooks, lifecycleHooks); + reg.definePartialInstance(ISessionLifecycleService, lifecycle.service); reg.defineInstance(ISessionMetadata, stubSessionMetadata()); reg.defineInstance(ISessionAgentProfileCatalog, stubProfileCatalog()); reg.defineInstance(IModelService, stubModelService()); @@ -907,11 +920,11 @@ describe('IExternalHooksRunnerService integration', () => { ix.set(ISessionExternalHooksService, new SyncDescriptor(SessionExternalHooksService)); ix.get(ISessionExternalHooksService); - await lifecycleHooks.onDidCreateSession.run({ source: 'startup' }); - await lifecycleHooks.onDidCreateSession.run({ source: 'resume' }); - await lifecycleHooks.onDidCreateSession.run({ source: 'fork' }); - await lifecycleHooks.onWillCloseSession.run({ reason: 'exit' }); - await lifecycleHooks.onWillCloseSession.run({ reason: 'archive' }); + await lifecycle.fireDidCreate('startup'); + await lifecycle.fireDidCreate('resume'); + await lifecycle.fireDidCreate('fork'); + await lifecycle.fireWillClose('exit'); + await lifecycle.fireWillClose('archive'); expect(readHookLog(path)).toEqual([ { @@ -1053,7 +1066,7 @@ describe('IExternalHooksRunnerService integration', () => { const disposables = new DisposableStore(); let ix: TestInstantiationService | undefined; try { - const lifecycleHooks = stubSessionLifecycleHooks(); + const lifecycle = stubSessionLifecycle(); const path = hookLogPath(); const command = stdinScript([ 'const fs = require("node:fs");', @@ -1074,7 +1087,7 @@ describe('IExternalHooksRunnerService integration', () => { additionalServices: (reg) => { registerStateServices(reg); reg.defineInstance(ISessionContext, stubSessionContext()); - reg.defineInstance(ISessionLifecycleHooks, lifecycleHooks); + reg.definePartialInstance(ISessionLifecycleService, lifecycle.service); reg.defineInstance(ISessionMetadata, stubSessionMetadata('My Session')); reg.defineInstance(ISessionAgentProfileCatalog, stubProfileCatalog('coder')); reg.defineInstance(IModelService, stubModelService('kimi-k2')); @@ -1102,7 +1115,7 @@ describe('IExternalHooksRunnerService integration', () => { ix.get(ISessionExternalHooksService); await flushMicrotasks(); - await lifecycleHooks.onDidCreateSession.run({ source: 'startup' }); + await lifecycle.fireDidCreate('startup'); expect(readHookLog(path)).toEqual([ { @@ -1261,7 +1274,7 @@ describe('IExternalHooksRunnerService integration', () => { additionalServices: (reg) => { registerStateServices(reg); reg.defineInstance(ISessionContext, stubSessionContext()); - reg.defineInstance(ISessionLifecycleHooks, stubSessionLifecycleHooks()); + reg.definePartialInstance(ISessionLifecycleService, stubSessionLifecycle().service); reg.defineInstance(ISessionMetadata, stubSessionMetadata()); reg.defineInstance(ISessionAgentProfileCatalog, stubProfileCatalog()); reg.defineInstance(IModelService, stubModelService()); @@ -1306,7 +1319,7 @@ describe('IExternalHooksRunnerService integration', () => { additionalServices: (reg) => { registerStateServices(reg); reg.defineInstance(ISessionContext, stubSessionContext()); - reg.defineInstance(ISessionLifecycleHooks, stubSessionLifecycleHooks()); + reg.definePartialInstance(ISessionLifecycleService, stubSessionLifecycle().service); reg.defineInstance(ISessionMetadata, stubSessionMetadata()); reg.defineInstance(ISessionAgentProfileCatalog, stubProfileCatalog()); reg.defineInstance(IModelService, stubModelService()); @@ -1353,7 +1366,7 @@ describe('IExternalHooksRunnerService integration', () => { additionalServices: (reg) => { registerStateServices(reg); reg.defineInstance(ISessionContext, stubSessionContext()); - reg.defineInstance(ISessionLifecycleHooks, stubSessionLifecycleHooks()); + reg.definePartialInstance(ISessionLifecycleService, stubSessionLifecycle().service); reg.defineInstance(ISessionMetadata, stubSessionMetadata()); reg.defineInstance(ISessionAgentProfileCatalog, stubProfileCatalog()); reg.defineInstance(IModelService, stubModelService()); diff --git a/packages/agent-core-v2/test/app/gateway/gateway.test.ts b/packages/agent-core-v2/test/app/gateway/gateway.test.ts index 606fb1835..b74719870 100644 --- a/packages/agent-core-v2/test/app/gateway/gateway.test.ts +++ b/packages/agent-core-v2/test/app/gateway/gateway.test.ts @@ -95,6 +95,7 @@ describe('RestGateway', () => { _serviceBrand: undefined, onWillCreateSession: () => ({ dispose: () => {} }), onDidCreateSession: () => ({ dispose: () => {} }), + onWillCloseSession: () => ({ dispose: () => {} }), onDidCloseSession: () => ({ dispose: () => {} }), onDidArchiveSession: () => ({ dispose: () => {} }), onDidForkSession: () => ({ dispose: () => {} }), diff --git a/packages/agent-core-v2/test/app/sessionExport/sessionExport.test.ts b/packages/agent-core-v2/test/app/sessionExport/sessionExport.test.ts index 648776590..543c5c5bb 100644 --- a/packages/agent-core-v2/test/app/sessionExport/sessionExport.test.ts +++ b/packages/agent-core-v2/test/app/sessionExport/sessionExport.test.ts @@ -918,6 +918,7 @@ function registerSessionExportServices( _serviceBrand: undefined, onWillCreateSession: noopEvent, onDidCreateSession: noopEvent, + onWillCloseSession: noopEvent, onDidCloseSession: noopEvent, onDidArchiveSession: noopEvent, onDidForkSession: noopEvent, diff --git a/packages/agent-core-v2/test/harness/agent.ts b/packages/agent-core-v2/test/harness/agent.ts index c75f46ffe..4b092f3e4 100644 --- a/packages/agent-core-v2/test/harness/agent.ts +++ b/packages/agent-core-v2/test/harness/agent.ts @@ -9,7 +9,7 @@ import { toDisposable } from '#/_base/di/lifecycle'; import type { IInstantiationService } from '#/_base/di/instantiation'; import type { IAgentScopeHandle } from '#/_base/di/scope'; import { IFeatureManager } from '#/app/feature/featureManager'; -import { Emitter, Event } from '#/_base/event'; +import { Emitter, Event, type IWaitUntil } from '#/_base/event'; import { IAgentLifecycleService } from '#/session/agentLifecycle/agentLifecycle'; import type { Promisable, PromisifyMethods } from '#/_base/utils/types'; import type { AgentTaskInfo } from '#/agent/task/task'; @@ -172,11 +172,11 @@ import { type ScopeSeed, type ServiceIdentifier, } from '#/index'; -import { createHooks } from '#/hooks'; import { - ISessionLifecycleHooks, - type SessionLifecycleHookSlots, -} from '#/session/sessionLifecycleHooks/sessionLifecycleHooks'; + ISessionLifecycleService, + type SessionCreatedEvent, + type SessionWillCloseEvent, +} from '#/workspace/sessionLifecycle/sessionLifecycle'; import { IEventBus } from '#/app/event/eventBus'; import { IWireService } from '#/wire/wire'; import { WireService } from '#/wire/wireService'; @@ -1209,13 +1209,10 @@ export class AgentTestContext { scope: (subKey?: string): string => subKey === undefined || subKey === '' ? sessionScope : `${sessionScope}/${subKey}`, }); - reg.defineInstance( - ISessionLifecycleHooks, - createHooks([ - 'onDidCreateSession', - 'onWillCloseSession', - ]), - ); + reg.definePartialInstance(ISessionLifecycleService, { + onDidCreateSession: Event.None as Event, + onWillCloseSession: Event.None as Event, + }); reg.defineInstance(ISessionInteractionService, this.createInteractionService()); reg.defineInstance(ISessionApprovalService, this.createApprovalService()); reg.defineInstance(ISessionQuestionService, this.createQuestionService()); diff --git a/packages/agent-core-v2/test/workspace/sessionLifecycle/sessionLifecycle.test.ts b/packages/agent-core-v2/test/workspace/sessionLifecycle/sessionLifecycle.test.ts index 86bb82bdc..17f017311 100644 --- a/packages/agent-core-v2/test/workspace/sessionLifecycle/sessionLifecycle.test.ts +++ b/packages/agent-core-v2/test/workspace/sessionLifecycle/sessionLifecycle.test.ts @@ -15,7 +15,6 @@ import { import { type ScopedTestHost, createScopedTestHost, stubPair } from '#/_base/di/test'; import { Event } from '#/_base/event'; import { ILogService } from '#/_base/log/log'; -import type { Hooks } from '#/hooks'; import { IBootstrapService } from '#/app/bootstrap/bootstrap'; import { IConfigService } from '#/app/config/config'; import { IFlagService } from '#/app/flag/flag'; @@ -59,10 +58,6 @@ import { IWorkspaceToolPolicy } from '#/workspace/workspaceToolPolicy/workspaceT import { WorkspaceToolPolicyService } from '#/workspace/workspaceToolPolicy/workspaceToolPolicyService'; import { IAgentActivityView } from '#/agent/activityView/activityView'; import { ISessionExternalHooksService } from '#/session/externalHooks/externalHooks'; -import { - ISessionLifecycleHooks, - type SessionLifecycleHookSlots, -} from '#/session/sessionLifecycleHooks/sessionLifecycleHooks'; import { ISessionIndexMirror } from '#/app/sessionIndex/sessionIndex'; import { ISessionMetadata, @@ -521,19 +516,19 @@ class RecordingSessionExternalHooksService constructor( @ISessionContext private readonly context: ISessionContext, - @ISessionLifecycleHooks hooks: Hooks, + @ISessionLifecycleService lifecycle: ISessionLifecycleService, ) { super(); this._register( - hooks.onDidCreateSession.register('test', async (event, next) => { + lifecycle.onDidCreateSession((event) => { + if (event.sessionId !== this.context.sessionId) return; recordedSessionHookEvents.push(`create:${event.source}:${this.context.sessionId}`); - await next(); }), ); this._register( - hooks.onWillCloseSession.register('test', async (event, next) => { + lifecycle.onWillCloseSession((event) => { + if (event.sessionId !== this.context.sessionId) return; recordedSessionHookEvents.push(`close:${event.reason}:${this.context.sessionId}`); - await next(); }), ); } diff --git a/packages/node-sdk/test/sdk-rpc-client-v2.test.ts b/packages/node-sdk/test/sdk-rpc-client-v2.test.ts index 94bd1776e..697385b63 100644 --- a/packages/node-sdk/test/sdk-rpc-client-v2.test.ts +++ b/packages/node-sdk/test/sdk-rpc-client-v2.test.ts @@ -34,7 +34,6 @@ import { drainSessionIndexMirror, HostProcessError, IHostRequestHeaders, - ISessionLifecycleHooks, ISessionLifecycleService, IWorkspaceLifecycleService, OsProcessErrors, @@ -442,13 +441,11 @@ key = "${titleOAuthRef.key}" const closeGate = new Promise((resolve) => { openCloseGate = resolve; }); - tempHandle!.accessor - .get(ISessionLifecycleHooks) - .onWillCloseSession.register('test-block', async (_event, next) => { - markCloseStarted(); - await closeGate; - await next(); - }); + handler.accessor.get(ISessionLifecycleService).onWillCloseSession((event) => { + if (event.sessionId !== 'ses_title_race') return; + markCloseStarted(); + event.waitUntil(closeGate); + }); resolveFetch( new Response(JSON.stringify({ title: 'Generated title' }), {