From 4739284fb90c30023ee425d69dc777e742a2dcc6 Mon Sep 17 00:00:00 2001 From: Haozhe Date: Thu, 13 Aug 2026 18:12:08 +0800 Subject: [PATCH] refactor(features): extract session init feature (#2887) - move the session init domain under features - contribute the session service through SessionInitFeature - cover feature withdrawal and restoration --- apps/kimi-inspect/src/panels.ts | 2 +- .../sessionInit/profile/init.md | 0 .../sessionInit/profile/init.ts | 0 .../sessionInit/sessionInit.ts | 0 .../sessionInit/sessionInitFeature.ts | 21 ++++++ .../sessionInit/sessionInitService.ts | 11 --- packages/agent-core-v2/src/index.ts | 7 +- .../sessionInit/sessionInit.test.ts | 4 +- .../sessionInit/sessionInitFeature.test.ts | 72 +++++++++++++++++++ 9 files changed, 100 insertions(+), 17 deletions(-) rename packages/agent-core-v2/src/{session => features}/sessionInit/profile/init.md (100%) rename packages/agent-core-v2/src/{session => features}/sessionInit/profile/init.ts (100%) rename packages/agent-core-v2/src/{session => features}/sessionInit/sessionInit.ts (100%) create mode 100644 packages/agent-core-v2/src/features/sessionInit/sessionInitFeature.ts rename packages/agent-core-v2/src/{session => features}/sessionInit/sessionInitService.ts (95%) rename packages/agent-core-v2/test/{session => features}/sessionInit/sessionInit.test.ts (98%) create mode 100644 packages/agent-core-v2/test/features/sessionInit/sessionInitFeature.test.ts diff --git a/apps/kimi-inspect/src/panels.ts b/apps/kimi-inspect/src/panels.ts index 555647012..ce241fbbe 100644 --- a/apps/kimi-inspect/src/panels.ts +++ b/apps/kimi-inspect/src/panels.ts @@ -35,7 +35,7 @@ import { IProviderService } from '@moonshot-ai/agent-core-v2/kosong/provider/pro import { ISessionApprovalService } from '@moonshot-ai/agent-core-v2/session/approval/approval'; import { ISessionInteractionService } from '@moonshot-ai/agent-core-v2/session/interaction/interaction'; import { ISessionQuestionService } from '@moonshot-ai/agent-core-v2/session/question/question'; -import { ISessionInitService } from '@moonshot-ai/agent-core-v2/session/sessionInit/sessionInit'; +import { ISessionInitService } from '@moonshot-ai/agent-core-v2/features/sessionInit/sessionInit'; import { ISessionMetadata } from '@moonshot-ai/agent-core-v2/session/sessionMetadata/sessionMetadata'; import { ISessionWorkspaceContext } from '@moonshot-ai/agent-core-v2/session/workspaceContext/workspaceContext'; diff --git a/packages/agent-core-v2/src/session/sessionInit/profile/init.md b/packages/agent-core-v2/src/features/sessionInit/profile/init.md similarity index 100% rename from packages/agent-core-v2/src/session/sessionInit/profile/init.md rename to packages/agent-core-v2/src/features/sessionInit/profile/init.md diff --git a/packages/agent-core-v2/src/session/sessionInit/profile/init.ts b/packages/agent-core-v2/src/features/sessionInit/profile/init.ts similarity index 100% rename from packages/agent-core-v2/src/session/sessionInit/profile/init.ts rename to packages/agent-core-v2/src/features/sessionInit/profile/init.ts diff --git a/packages/agent-core-v2/src/session/sessionInit/sessionInit.ts b/packages/agent-core-v2/src/features/sessionInit/sessionInit.ts similarity index 100% rename from packages/agent-core-v2/src/session/sessionInit/sessionInit.ts rename to packages/agent-core-v2/src/features/sessionInit/sessionInit.ts diff --git a/packages/agent-core-v2/src/features/sessionInit/sessionInitFeature.ts b/packages/agent-core-v2/src/features/sessionInit/sessionInitFeature.ts new file mode 100644 index 000000000..30d9f7c7c --- /dev/null +++ b/packages/agent-core-v2/src/features/sessionInit/sessionInitFeature.ts @@ -0,0 +1,21 @@ +import { LifecycleScope } from '#/app/scopes'; +import { Feature } from '#/features/feature'; +import { registerFeature } from '#/features/featureRegistry'; + +import { ISessionInitService } from './sessionInit'; +import { SessionInitService } from './sessionInitService'; + +export class SessionInitFeature extends Feature { + static override readonly name = 'sessionInit'; + + constructor() { + super(); + this.contributeService( + LifecycleScope.Session, + ISessionInitService, + SessionInitService, + ); + } +} + +registerFeature(SessionInitFeature); diff --git a/packages/agent-core-v2/src/session/sessionInit/sessionInitService.ts b/packages/agent-core-v2/src/features/sessionInit/sessionInitService.ts similarity index 95% rename from packages/agent-core-v2/src/session/sessionInit/sessionInitService.ts rename to packages/agent-core-v2/src/features/sessionInit/sessionInitService.ts index 613a1d847..f0a4d7d5b 100644 --- a/packages/agent-core-v2/src/session/sessionInit/sessionInitService.ts +++ b/packages/agent-core-v2/src/features/sessionInit/sessionInitService.ts @@ -22,9 +22,6 @@ * `SESSION_INIT_FAILED`) so callers can tell "aborted" from "failed". */ -import { LifecycleScope } from '#/app/scopes'; - -import { ScopeActivation, registerScopedService } from '#/_base/di/scope'; import { isAbortError, isUserCancellation, userCancellationReason } from '#/_base/utils/abort'; import { IBootstrapService } from '#/app/bootstrap/bootstrap'; import { IHostEnvironment } from '#/os/interface/hostEnvironment'; @@ -144,11 +141,3 @@ export class SessionInitService implements ISessionInitService { } } } - -registerScopedService( - LifecycleScope.Session, - ISessionInitService, - SessionInitService, - ScopeActivation.OnScopeCreated, - 'session-init', -); diff --git a/packages/agent-core-v2/src/index.ts b/packages/agent-core-v2/src/index.ts index 92bf96165..3efa13e51 100644 --- a/packages/agent-core-v2/src/index.ts +++ b/packages/agent-core-v2/src/index.ts @@ -640,9 +640,10 @@ export * from '#/agent/shellCommand/shellCommandService'; export * from '#/agent/scopeContext/scopeContext'; export * from '#/agent/stepRetry/stepRetry'; export * from '#/agent/stepRetry/stepRetryService'; -export * from '#/session/sessionInit/sessionInit'; -export * from '#/session/sessionInit/sessionInitService'; -export * from '#/session/sessionInit/profile/init'; +export * from '#/features/sessionInit/sessionInit'; +export * from '#/features/sessionInit/sessionInitService'; +export * from '#/features/sessionInit/profile/init'; +import '#/features/sessionInit/sessionInitFeature'; export * from '#/session/todo/todoItem'; export * from '#/session/todo/todoListReminder'; export * from '#/session/todo/sessionTodo'; diff --git a/packages/agent-core-v2/test/session/sessionInit/sessionInit.test.ts b/packages/agent-core-v2/test/features/sessionInit/sessionInit.test.ts similarity index 98% rename from packages/agent-core-v2/test/session/sessionInit/sessionInit.test.ts rename to packages/agent-core-v2/test/features/sessionInit/sessionInit.test.ts index 47da972bf..a42a70d04 100644 --- a/packages/agent-core-v2/test/session/sessionInit/sessionInit.test.ts +++ b/packages/agent-core-v2/test/features/sessionInit/sessionInit.test.ts @@ -17,8 +17,8 @@ import { IWireService } from '#/wire/wire'; import { ErrorCodes, Error2 } from '#/errors'; import { IAgentLifecycleService } from '#/session/agentLifecycle/agentLifecycle'; import { ISessionContext } from '#/session/sessionContext/sessionContext'; -import { ISessionInitService } from '#/session/sessionInit/sessionInit'; -import { SessionInitService } from '#/session/sessionInit/sessionInitService'; +import { ISessionInitService } from '#/features/sessionInit/sessionInit'; +import { SessionInitService } from '#/features/sessionInit/sessionInitService'; import { ISessionSubagentService } from '#/session/subagent/subagent'; const WORK_DIR = '/project'; diff --git a/packages/agent-core-v2/test/features/sessionInit/sessionInitFeature.test.ts b/packages/agent-core-v2/test/features/sessionInit/sessionInitFeature.test.ts new file mode 100644 index 000000000..ac896b6c2 --- /dev/null +++ b/packages/agent-core-v2/test/features/sessionInit/sessionInitFeature.test.ts @@ -0,0 +1,72 @@ +import { beforeEach, describe, expect, it } from 'vitest'; + +import { ScopeActivation } from '#/_base/di/instantiation'; +import { + _clearScopedRegistryForTests, + registerScopedService, +} from '#/_base/di/scope'; +import { createScopedTestHost, stubPair } from '#/_base/di/test'; +import { IBootstrapService } from '#/app/bootstrap/bootstrap'; +import { IFeatureManager } from '#/app/feature/featureManager'; +import { FeatureManagerService } from '#/app/feature/featureManagerService'; +import { LifecycleScope } from '#/app/scopes'; +import { SessionInitFeature } from '#/features/sessionInit/sessionInitFeature'; +import { ISessionInitService } from '#/features/sessionInit/sessionInit'; +import { IHostEnvironment } from '#/os/interface/hostEnvironment'; +import { IHostFileSystem } from '#/os/interface/hostFileSystem'; +import { IFeatureAssemblyService } from '#/features/featureAssembly'; +import { FeatureAssemblyService } from '#/features/featureAssemblyService'; +import { + _clearFeatureRecipesForTests, + registerFeature, +} from '#/features/featureRegistry'; +import { IAgentLifecycleService } from '#/session/agentLifecycle/agentLifecycle'; +import { ISessionContext } from '#/session/sessionContext/sessionContext'; +import { ISessionSubagentService } from '#/session/subagent/subagent'; + +describe('SessionInitFeature', () => { + beforeEach(() => { + _clearScopedRegistryForTests(); + _clearFeatureRecipesForTests(); + registerScopedService( + LifecycleScope.App, + IFeatureManager, + FeatureManagerService, + ScopeActivation.OnScopeCreated, + 'feature', + ); + registerScopedService( + LifecycleScope.App, + IFeatureAssemblyService, + FeatureAssemblyService, + ScopeActivation.OnScopeCreated, + 'features', + ); + registerFeature(SessionInitFeature); + }); + + it('withdraws and restores the Session service with the Feature', async () => { + const host = createScopedTestHost(); + const session = host.child(LifecycleScope.Session, 'session-1', [ + stubPair(IAgentLifecycleService, {} as IAgentLifecycleService), + stubPair(ISessionSubagentService, {} as ISessionSubagentService), + stubPair(IHostFileSystem, {} as IHostFileSystem), + stubPair(IHostEnvironment, {} as IHostEnvironment), + stubPair(IBootstrapService, {} as IBootstrapService), + stubPair(ISessionContext, {} as ISessionContext), + ]); + const manager = host.app.accessor.get(IFeatureManager); + + expect(session.accessor.get(ISessionInitService)).toBeDefined(); + + await manager.unprovideUnit('sessionInit'); + await host.app.instantiation.cascade.whenIdle(); + expect(() => session.accessor.get(ISessionInitService)).toThrow(); + + manager.provideUnit(SessionInitFeature); + await host.app.instantiation.cascade.whenIdle(); + expect(session.accessor.get(ISessionInitService)).toBeDefined(); + + host.dispose(); + }); +});