Download packages/agent-core-v2/src/features/plan/planService.ts from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 9.78 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/agent-core-v2/src/features/plan/planService.ts
- Command line
-
hf download hf://SaylorTwift/kimi-code/packages/agent-core-v2/src/features/plan/planService.ts
-
curl -L -o planService.ts https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/agent-core-v2/src/features/plan/planService.ts
9.78 kB
| import { createHash, randomUUID } from 'node:crypto'; | |
| import { dirname, join } from 'pathe'; | |
| import { type IDisposable } from '#/_base/di/lifecycle'; | |
| import { Service } from '#/_base/di/service'; | |
| import { unwrapErrorCause } from '#/_base/errors/errors'; | |
| import { Error2, ErrorCodes } from '#/errors'; | |
| import { generateHeroSlug } from '#/_base/utils/hero-slug'; | |
| import { IAgentContextMemoryService } from '#/agent/contextMemory/contextMemory'; | |
| import { IAgentReminderService } from '#/features/reminder/reminderService'; | |
| import { IAgentPermissionModeService } from '#/agent/permissionMode/permissionMode'; | |
| import { PlanModeInjection } from '#/features/plan/injection/planModeInjection'; | |
| import { IAgentScopeContext } from '#/agent/scopeContext/scopeContext'; | |
| import { IAgentStateService } from '#/agent/state/agentState'; | |
| import { IAgentToolApprovalService } from '#/agent/toolApproval/toolApproval'; | |
| import { denyToolExecution } from '#/agent/toolExecutor/beforeToolExecuteEvent'; | |
| import { IAgentToolExecutorService } from '#/agent/toolExecutor/toolExecutor'; | |
| import type { | |
| BeforeToolExecuteEvent, | |
| ResolvedToolExecutionHookContext, | |
| } from '#/agent/toolExecutor/toolHooks'; | |
| import { IEventBus } from '#/app/event/eventBus'; | |
| import { ITelemetryService } from '#/app/telemetry/telemetry'; | |
| import { IHostFileSystem } from '#/os/interface/hostFileSystem'; | |
| import { IBlobStore } from '#/persistence/interface/blobStore'; | |
| import { ISessionContext } from '#/session/sessionContext/sessionContext'; | |
| import { IEventDispatcher } from '#/state/eventDispatcher'; | |
| import { AgentStatusUpdated } from '#/agent/usage/usageEvents'; | |
| import { ContextUndone } from '#/agent/undo/undoService'; | |
| import type { ToolFileAccess } from '#/tool/toolContract'; | |
| import { | |
| IAgentPlanService, | |
| type PlanData, | |
| type PlanFilePath, | |
| } from './plan'; | |
| import { ExitPlanModeReview } from './exitPlanModeReview'; | |
| import { | |
| PlanModeCancel, | |
| PlanModeEnter, | |
| PlanModeExit, | |
| planKey, | |
| PlanRevision, | |
| } from './planOps'; | |
| export class AgentPlanService extends Service implements IAgentPlanService { | |
| declare readonly _serviceBrand: undefined; | |
| private readonly review: ExitPlanModeReview; | |
| constructor( | |
| private readonly context: IAgentContextMemoryService, | |
| private readonly hostFs: IHostFileSystem, | |
| private readonly blobs: IBlobStore, | |
| reminder: IAgentReminderService, | |
| eventBus: IEventBus, | |
| private readonly dispatcher: IEventDispatcher, | |
| private readonly sessionCtx: ISessionContext, | |
| private readonly agentCtx: IAgentScopeContext, | |
| toolExecutor: IAgentToolExecutorService, | |
| private readonly toolApproval: IAgentToolApprovalService, | |
| private readonly modeService: IAgentPermissionModeService, | |
| private readonly telemetry: ITelemetryService, | |
| private readonly agentState: IAgentStateService, | |
| ) { | |
| super(); | |
| this.agentState.contributeState(planKey); | |
| this.review = new ExitPlanModeReview(this, this.toolApproval, telemetry); | |
| this._register( | |
| this.dispatcher.hooks.onDidRestore.register('plan', async (_ctx, next) => { | |
| this.restoreTelemetryMode(); | |
| await next(); | |
| }), | |
| ); | |
| this._register( | |
| eventBus.subscribe(ContextUndone, () => { | |
| this.restoreTelemetryMode(); | |
| void this.dispatcher.dispatch( | |
| new AgentStatusUpdated({ agentId: this.agentCtx.agentId, planMode: this.isActive }), | |
| ); | |
| }), | |
| ); | |
| this._register(new PlanModeInjection(reminder, this, this.context, agentState)); | |
| this._register(this.registerPlanGuard(toolExecutor)); | |
| } | |
| private registerPlanGuard(toolExecutor: IAgentToolExecutorService): IDisposable { | |
| return toolExecutor.onBeforeExecuteTool((event) => this.guardToolExecution(event)); | |
| } | |
| private async guardToolExecution(event: BeforeToolExecuteEvent): Promise<void> { | |
| const toolName = event.toolCall.name; | |
| const plan = await this.status(); | |
| if (toolName === 'ExitPlanMode') { | |
| if (plan !== null && this.modeService.mode !== 'auto') { | |
| event.waitUntil(() => this.review.requestApproval(event)); | |
| } | |
| return; | |
| } | |
| if (plan === null) { | |
| return; | |
| } | |
| if (toolName === 'Write' || toolName === 'Edit') { | |
| if (writesOnlyPlanFile(event, plan.path)) { | |
| event.allow(); | |
| return; | |
| } | |
| event.veto( | |
| denyToolExecution(this.toolApproval.formatDenyMessage(planModeWriteDeniedMessage(plan.path))), | |
| ); | |
| return; | |
| } | |
| if (toolName === 'TaskStop') { | |
| event.veto( | |
| denyToolExecution( | |
| this.toolApproval.formatDenyMessage( | |
| 'TaskStop is not available in plan mode. Call ExitPlanMode to exit plan mode before stopping a background task.', | |
| ), | |
| ), | |
| ); | |
| return; | |
| } | |
| if (toolName === 'CronCreate' || toolName === 'CronDelete') { | |
| event.veto( | |
| denyToolExecution( | |
| this.toolApproval.formatDenyMessage( | |
| `${toolName} is not available in plan mode because it would mutate scheduled work that runs after plan exit. Call ExitPlanMode first.`, | |
| ), | |
| ), | |
| ); | |
| return; | |
| } | |
| } | |
| private get isActive(): boolean { | |
| return this.agentState.get(planKey).active; | |
| } | |
| private currentPlanFilePath(): PlanFilePath { | |
| const state = this.agentState.get(planKey); | |
| if (!state.active || state.id === undefined) return null; | |
| return this.planFilePathFor(state.id); | |
| } | |
| private restoreTelemetryMode(): void { | |
| this.telemetry.setContext({ mode: this.isActive ? 'plan' : 'agent' }); | |
| } | |
| private createPlanId(): string { | |
| return generateHeroSlug(randomUUID(), new Set()); | |
| } | |
| async enter(id = this.createPlanId(), createFile = false): Promise<void> { | |
| if (this.isActive) { | |
| throw new Error2(ErrorCodes.SESSION_PLAN_MODE_INVALID, 'Already in plan mode'); | |
| } | |
| const planFilePath = this.planFilePathFor(id); | |
| let enterRecorded = false; | |
| try { | |
| await this.ensurePlanDirectory(planFilePath); | |
| await this.dispatcher.dispatch(new PlanModeEnter({ agentId: this.agentCtx.agentId, id })); | |
| this.telemetry.setContext({ mode: 'plan' }); | |
| enterRecorded = true; | |
| if (createFile) { | |
| await this.writeEmptyPlanFile(planFilePath); | |
| } | |
| } catch (error) { | |
| if (enterRecorded) { | |
| this.cancel(id); | |
| } | |
| throw error; | |
| } | |
| } | |
| cancel(id?: string): void { | |
| void this.dispatcher.dispatch(new PlanModeCancel({ agentId: this.agentCtx.agentId, id })); | |
| this.telemetry.setContext({ mode: 'agent' }); | |
| } | |
| async clear(): Promise<void> { | |
| const path = this.currentPlanFilePath(); | |
| if (path === null) return; | |
| await this.writeEmptyPlanFile(path); | |
| } | |
| exit(id?: string): void { | |
| void this.dispatcher.dispatch(new PlanModeExit({ agentId: this.agentCtx.agentId, id })); | |
| this.telemetry.setContext({ mode: 'agent' }); | |
| } | |
| async recordRevision(): Promise<void> { | |
| const state = this.agentState.get(planKey); | |
| if (!state.active || state.id === undefined) return; | |
| const id = state.id; | |
| const content = await this.hostFs.readText(this.planFilePathFor(id)); | |
| const bytes = Buffer.from(content, 'utf8'); | |
| const version = (state.revisionCount?.[id] ?? 0) + 1; | |
| const scope = this.agentCtx.scope(); | |
| const key = `plan/${id}/v${version}.md`; | |
| await this.blobs.put(scope, key, bytes); | |
| await this.dispatcher.dispatch( | |
| new PlanRevision({ | |
| agentId: this.agentCtx.agentId, | |
| id, | |
| version, | |
| key, | |
| sha256: createHash('sha256').update(bytes).digest('hex'), | |
| bytes: bytes.byteLength, | |
| }), | |
| ); | |
| } | |
| async status(): Promise<PlanData> { | |
| const state = this.agentState.get(planKey); | |
| if (!state.active || state.id === undefined) return null; | |
| const path = this.planFilePathFor(state.id); | |
| let content = ''; | |
| try { | |
| content = await this.hostFs.readText(path); | |
| } catch (error) { | |
| if (!isMissingFileError(error)) throw error; | |
| } | |
| return { | |
| id: state.id, | |
| content, | |
| path, | |
| }; | |
| } | |
| private planFilePathFor(id: string): string { | |
| return join(this.sessionCtx.sessionDir, 'agents', this.agentCtx.agentId, 'plans', `${id}.md`); | |
| } | |
| private async writeEmptyPlanFile(path: string): Promise<void> { | |
| await this.ensurePlanDirectory(path); | |
| await this.hostFs.writeText(path, ''); | |
| } | |
| private async ensurePlanDirectory(path: string): Promise<void> { | |
| await this.hostFs.mkdir(dirname(path), { recursive: true }); | |
| } | |
| } | |
| function isMissingFileError(error: unknown): boolean { | |
| const unwrapped = unwrapErrorCause(error); | |
| if (unwrapped === null || typeof unwrapped !== 'object') return false; | |
| const code = (unwrapped as { readonly code?: unknown }).code; | |
| return code === 'ENOENT'; | |
| } | |
| function writesOnlyPlanFile( | |
| context: ResolvedToolExecutionHookContext, | |
| planFilePath: string, | |
| ): boolean { | |
| const writeAccesses = (context.execution.accesses ?? []).filter( | |
| (access): access is ToolFileAccess => | |
| access.kind === 'file' && | |
| (access.operation === 'write' || access.operation === 'readwrite'), | |
| ); | |
| if (writeAccesses.length === 0) return false; | |
| return writeAccesses.every((access) => access.path === planFilePath); | |
| } | |
| function planModeWriteDeniedMessage(planFilePath: string | null): string { | |
| return ( | |
| `Plan mode is active. You may only write to the current plan file: ${planFilePath ?? '(no plan file selected yet)'}. ` + | |
| 'Call ExitPlanMode to exit plan mode before editing other files.' | |
| ); | |
| } | |
| export { AgentPlanService as Plan }; | |