mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-22 15:16:07 +00:00
refactor: align permission mode injection
This commit is contained in:
parent
22e6413ff1
commit
07f3f1cc36
6 changed files with 116 additions and 62 deletions
|
|
@ -1,30 +1,11 @@
|
|||
import type {
|
||||
ApprovalRequest,
|
||||
ApprovalResponse,
|
||||
PermissionData,
|
||||
} from '#/agent/permissionPolicy/types';
|
||||
import { createDecorator } from "#/_base/di/instantiation";
|
||||
import type {
|
||||
PermissionData
|
||||
} from '#/agent/permissionPolicy/types';
|
||||
import type {
|
||||
AuthorizeToolExecutionResult,
|
||||
ResolvedToolExecutionHookContext,
|
||||
} from '#/agent/tool/toolHooks';
|
||||
import type { Hooks } from '#/hooks';
|
||||
|
||||
export type PermissionApprovalRequestContext = ApprovalRequest & {
|
||||
readonly sessionId?: string;
|
||||
readonly agentId?: string;
|
||||
readonly turnId: number;
|
||||
readonly toolInput: unknown;
|
||||
};
|
||||
|
||||
export type PermissionApprovalResultContext = PermissionApprovalRequestContext &
|
||||
(
|
||||
| ApprovalResponse
|
||||
| {
|
||||
readonly decision: 'error';
|
||||
readonly error: string;
|
||||
}
|
||||
);
|
||||
|
||||
export interface IAgentPermissionGate {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
|
|
|||
|
|
@ -1,33 +1,48 @@
|
|||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { IInstantiationService } from "#/_base/di/instantiation";
|
||||
import { Disposable } from "#/_base/di/lifecycle";
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { abortable, isUserCancellation } from '#/_base/utils/abort';
|
||||
import { IAgentPermissionModeService } from '#/agent/permissionMode/permissionMode';
|
||||
import { IAgentPermissionPolicyService } from '#/agent/permissionPolicy/permissionPolicy';
|
||||
import type {
|
||||
ApprovalRequest,
|
||||
ApprovalResponse,
|
||||
PermissionData,
|
||||
PermissionPolicyResolution,
|
||||
PermissionPolicyResult,
|
||||
} from '#/agent/permissionPolicy/types';
|
||||
import { IInstantiationService } from "#/_base/di/instantiation";
|
||||
import { Disposable } from "#/_base/di/lifecycle";
|
||||
import { abortable, isUserCancellation } from '#/_base/utils/abort';
|
||||
import { IAgentPermissionRulesService } from '#/agent/permissionRules/permissionRules';
|
||||
import { IAgentScopeContext } from '#/agent/scopeContext/scopeContext';
|
||||
import type {
|
||||
AuthorizeToolExecutionResult,
|
||||
ResolvedToolExecutionHookContext,
|
||||
} from '#/agent/tool/toolHooks';
|
||||
import type { ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import { ISessionApprovalService } from "#/session/approval/approval";
|
||||
import { IAgentPermissionModeService } from '#/agent/permissionMode/permissionMode';
|
||||
import { IAgentPermissionRulesService } from '#/agent/permissionRules/permissionRules';
|
||||
import { ISessionContext } from '#/session/sessionContext/sessionContext';
|
||||
import { ITelemetryService } from '#/app/telemetry/telemetry';
|
||||
import { IAgentToolExecutorService } from '#/agent/toolExecutor/toolExecutor';
|
||||
import { IAgentScopeContext } from '#/agent/scopeContext/scopeContext';
|
||||
import { IEventBus } from '#/app/event/eventBus';
|
||||
import { ITelemetryService } from '#/app/telemetry/telemetry';
|
||||
import { ISessionApprovalService } from "#/session/approval/approval";
|
||||
import { ISessionContext } from '#/session/sessionContext/sessionContext';
|
||||
import type { ToolInputDisplay } from '@moonshot-ai/protocol';
|
||||
import {
|
||||
IAgentPermissionGate,
|
||||
type PermissionApprovalRequestContext,
|
||||
type PermissionApprovalResultContext,
|
||||
} from './permissionGate';
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { IEventBus } from '#/app/event/eventBus';
|
||||
|
||||
export type PermissionApprovalRequestContext = ApprovalRequest & {
|
||||
readonly sessionId?: string;
|
||||
readonly agentId?: string;
|
||||
readonly turnId: number;
|
||||
readonly toolInput: unknown;
|
||||
};
|
||||
|
||||
export type PermissionApprovalResultContext = PermissionApprovalRequestContext &
|
||||
(
|
||||
| ApprovalResponse
|
||||
| {
|
||||
readonly decision: 'error';
|
||||
readonly error: string;
|
||||
}
|
||||
);
|
||||
|
||||
declare module '#/app/event/eventBus' {
|
||||
interface DomainEventMap {
|
||||
|
|
|
|||
|
|
@ -1,27 +1,41 @@
|
|||
import type { PermissionMode } from '#/agent/permissionPolicy/types';
|
||||
import type { IDisposable } from "#/_base/di/lifecycle";
|
||||
import type { IAgentContextInjectorService } from '#/agent/contextInjector/contextInjector';
|
||||
/**
|
||||
* `permissionMode` domain (L3) — permission-mode context injection.
|
||||
*
|
||||
* Owns the `permission_mode` context-injection provider. It reads the live mode
|
||||
* from `IAgentPermissionModeService` and registers reminders through
|
||||
* `contextInjector`.
|
||||
*/
|
||||
|
||||
import { Disposable } from '#/_base/di/lifecycle';
|
||||
import { IAgentContextInjectorService } from '#/agent/contextInjector/contextInjector';
|
||||
import type { IAgentPermissionModeService } from '#/agent/permissionMode/permissionMode';
|
||||
import type { PermissionMode } from '#/agent/permissionPolicy/types';
|
||||
import AUTO_MODE_ENTER_REMINDER from './permission-mode-auto-enter-reminder.md?raw';
|
||||
import AUTO_MODE_EXIT_REMINDER from './permission-mode-auto-exit-reminder.md?raw';
|
||||
|
||||
const PERMISSION_MODE_INJECTION_VARIANT = 'permission_mode';
|
||||
|
||||
export function registerPermissionModeInjection(
|
||||
dynamicInjector: IAgentContextInjectorService,
|
||||
permissionMode: Pick<IAgentPermissionModeService, 'mode'>,
|
||||
): IDisposable {
|
||||
let lastMode: PermissionMode | undefined;
|
||||
return dynamicInjector.register(PERMISSION_MODE_INJECTION_VARIANT, () => {
|
||||
const previousMode = lastMode;
|
||||
const currentMode = permissionMode.mode;
|
||||
export class PermissionModeInjection extends Disposable {
|
||||
private lastMode: PermissionMode | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly permissionMode: Pick<IAgentPermissionModeService, 'mode'>,
|
||||
@IAgentContextInjectorService dynamicInjector: IAgentContextInjectorService,
|
||||
) {
|
||||
super();
|
||||
this._register(
|
||||
dynamicInjector.register(PERMISSION_MODE_INJECTION_VARIANT, () => this.reminder()),
|
||||
);
|
||||
}
|
||||
|
||||
private reminder(): string | undefined {
|
||||
const previousMode = this.lastMode;
|
||||
const currentMode = this.permissionMode.mode;
|
||||
if (currentMode === previousMode) return undefined;
|
||||
|
||||
lastMode = currentMode;
|
||||
this.lastMode = currentMode;
|
||||
if (currentMode === 'auto') return AUTO_MODE_ENTER_REMINDER;
|
||||
if (previousMode === 'auto') return AUTO_MODE_EXIT_REMINDER;
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { registerPermissionModeInjection as PermissionModeInjection };
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import type { PermissionMode } from '#/agent/permissionPolicy/types';
|
||||
import { createDecorator } from "#/_base/di/instantiation";
|
||||
|
||||
import type { PermissionMode } from '#/agent/permissionPolicy/types';
|
||||
import type { Hooks } from '#/hooks';
|
||||
|
||||
export interface PermissionModeChangedContext {
|
||||
|
|
|
|||
|
|
@ -5,17 +5,17 @@
|
|||
* `PermissionModeModel`, mutating it only through the `permission.set_mode` Op
|
||||
* (`wire.dispatch(setMode({ mode }))`) and reading it through `wire.getModel`.
|
||||
* The `onChanged` hook is driven by a `wire.subscribe` on that model (firing
|
||||
* only on actual changes), and the mode-aware reminder is registered through
|
||||
* `contextInjector`. Bound at Agent scope.
|
||||
* only on actual changes), and mode-aware reminders are registered through the
|
||||
* permission-mode injection helper. Bound at Agent scope.
|
||||
*/
|
||||
|
||||
import type { PermissionMode } from '#/agent/permissionPolicy/types';
|
||||
import { IInstantiationService } from '#/_base/di/instantiation';
|
||||
import { Disposable } from '#/_base/di/lifecycle';
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { IAgentContextInjectorService } from '#/agent/contextInjector/contextInjector';
|
||||
import { OrderedHookSlot } from '#/hooks';
|
||||
import { registerPermissionModeInjection } from '#/agent/permissionMode/injection/permissionModeInjection';
|
||||
import { PermissionModeInjection } from '#/agent/permissionMode/injection/permissionModeInjection';
|
||||
import { IAgentWireService } from '#/wire/tokens';
|
||||
import type { IWireService } from '#/wire/wireService';
|
||||
import { IAgentPermissionModeService } from './permissionMode';
|
||||
|
|
@ -33,7 +33,7 @@ export class AgentPermissionModeService extends Disposable implements IAgentPerm
|
|||
|
||||
constructor(
|
||||
@IAgentWireService private readonly wire: IWireService,
|
||||
@IAgentContextInjectorService dynamicInjector: IAgentContextInjectorService,
|
||||
@IInstantiationService instantiation: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
this._register(
|
||||
|
|
@ -42,7 +42,7 @@ export class AgentPermissionModeService extends Disposable implements IAgentPerm
|
|||
void this.hooks.onChanged.run({ mode, previousMode });
|
||||
}),
|
||||
);
|
||||
this._register(registerPermissionModeInjection(dynamicInjector, this));
|
||||
this._register(instantiation.createInstance(PermissionModeInjection, this));
|
||||
}
|
||||
|
||||
get mode(): PermissionMode {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
import { SyncDescriptor } from '#/_base/di/descriptors';
|
||||
import { DisposableStore } from '#/_base/di/lifecycle';
|
||||
import { TestInstantiationService } from '#/_base/di/test';
|
||||
import { IAgentContextInjectorService } from '#/agent/contextInjector/contextInjector';
|
||||
import {
|
||||
IAgentContextInjectorService,
|
||||
type ContextInjectionProvider,
|
||||
} from '#/agent/contextInjector/contextInjector';
|
||||
import { IAgentPermissionModeService } from '#/agent/permissionMode/permissionMode';
|
||||
import { AgentPermissionModeService } from '#/agent/permissionMode/permissionModeService';
|
||||
import { PermissionModeModel } from '#/agent/permissionMode/permissionModeOps';
|
||||
|
|
@ -19,9 +22,23 @@ import { WireService } from '#/wire/wireServiceImpl';
|
|||
const SCOPE = 'wire';
|
||||
const KEY = 'permission-mode-test';
|
||||
|
||||
let registeredInjection:
|
||||
| {
|
||||
readonly name: string;
|
||||
readonly provider: ContextInjectionProvider;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
const injectorStub: IAgentContextInjectorService = {
|
||||
_serviceBrand: undefined,
|
||||
register: () => ({ dispose: () => {} }),
|
||||
register: (name, provider) => {
|
||||
registeredInjection = { name, provider };
|
||||
return {
|
||||
dispose: () => {
|
||||
if (registeredInjection?.provider === provider) registeredInjection = undefined;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
let disposables: DisposableStore;
|
||||
|
|
@ -30,6 +47,7 @@ let log: IAppendLogStore;
|
|||
let svc: IAgentPermissionModeService;
|
||||
|
||||
beforeEach(() => {
|
||||
registeredInjection = undefined;
|
||||
disposables = new DisposableStore();
|
||||
ix = disposables.add(new TestInstantiationService());
|
||||
ix.stub(IFileSystemStorageService, new InMemoryStorageService());
|
||||
|
|
@ -51,6 +69,20 @@ async function readRecords(): Promise<PersistedRecord[]> {
|
|||
return out;
|
||||
}
|
||||
|
||||
async function runRegisteredInjection(): Promise<string | undefined> {
|
||||
const provider = registeredInjection?.provider;
|
||||
if (provider === undefined) throw new Error('expected permission mode injection provider');
|
||||
const content = await provider({
|
||||
injectedPositions: [],
|
||||
lastInjectedAt: null,
|
||||
isNewTurn: true,
|
||||
});
|
||||
if (typeof content !== 'string' && content !== undefined) {
|
||||
throw new Error('expected permission mode injection provider to return text');
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
describe('AgentPermissionModeService (wire-backed)', () => {
|
||||
it('setMode updates mode and fires onChanged with mode/previousMode', () => {
|
||||
const changes: { mode: PermissionMode; previousMode: PermissionMode }[] = [];
|
||||
|
|
@ -79,6 +111,19 @@ describe('AgentPermissionModeService (wire-backed)', () => {
|
|||
expect('payload' in records[0]!).toBe(false);
|
||||
});
|
||||
|
||||
it('registers auto-mode reminder injection through the injection service', async () => {
|
||||
expect(registeredInjection?.name).toBe('permission_mode');
|
||||
|
||||
expect(await runRegisteredInjection()).toBeUndefined();
|
||||
|
||||
svc.setMode('auto');
|
||||
expect(await runRegisteredInjection()).toContain('Auto permission mode is active');
|
||||
expect(await runRegisteredInjection()).toBeUndefined();
|
||||
|
||||
svc.setMode('manual');
|
||||
expect(await runRegisteredInjection()).toContain('Auto permission mode is no longer active');
|
||||
});
|
||||
|
||||
it('replay rebuilds mode from a persisted record on a fresh WireService (silent)', async () => {
|
||||
const ix2 = disposables.add(new TestInstantiationService());
|
||||
ix2.stub(IFileSystemStorageService, new InMemoryStorageService());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue