diff --git a/apps/cli/src/ai/agent-stream-runner.ts b/apps/cli/src/ai/agent-stream-runner.ts index 9852303e..eb30eed6 100644 --- a/apps/cli/src/ai/agent-stream-runner.ts +++ b/apps/cli/src/ai/agent-stream-runner.ts @@ -86,7 +86,6 @@ export function createCliRunAgentStream( crossChatAnalysisService?: CrossChatAnalysisService sessionAdapter?: SessionRuntimeAdapter memoryService?: AIMemoryService - getAllowProactiveMemory?: () => boolean } = {} ): (params: AgentStreamRequest, onEvent: (chunk: AgentStreamChunk) => void, abortSignal: AbortSignal) => Promise { const aiDataDir = getAiDir(dbManager) @@ -161,7 +160,6 @@ export function createCliRunAgentStream( maxToolResultTokens: resolveCrossChatToolResultTokenBudget(contextWindow), memoryService: options.memoryService, aiChatId, - allowProactiveMemory: options.getAllowProactiveMemory?.() ?? true, }) await runCrossChatAgent({ userMessage, diff --git a/apps/cli/src/ai/cross-chat-tool-adapter.ts b/apps/cli/src/ai/cross-chat-tool-adapter.ts index 37a3e591..1529e385 100644 --- a/apps/cli/src/ai/cross-chat-tool-adapter.ts +++ b/apps/cli/src/ai/cross-chat-tool-adapter.ts @@ -23,7 +23,6 @@ export function createCliCrossChatTools(options: { maxToolResultTokens: number memoryService: AIMemoryService aiChatId: string - allowProactiveMemory: boolean }): AgentTool[] { const context: Omit = { locale: options.locale, @@ -31,7 +30,6 @@ export function createCliCrossChatTools(options: { analysisService: options.analysisService, memoryService: options.memoryService, aiChatId: options.aiChatId, - allowProactiveMemory: options.allowProactiveMemory, maxToolResultTokens: options.maxToolResultTokens, countTokens, preprocessMessagesBySession: (sessionId, messages) => diff --git a/apps/cli/src/http/index.ts b/apps/cli/src/http/index.ts index d38b0dd1..2f1d8a52 100644 --- a/apps/cli/src/http/index.ts +++ b/apps/cli/src/http/index.ts @@ -233,7 +233,6 @@ export async function startHttpServer(options?: HttpServerOptions): Promise<{ crossChatAnalysisService, sessionAdapter, memoryService: aiMemoryService, - getAllowProactiveMemory: () => preferencesManager.load().aiGlobalSettings.allowProactiveMemory, }), }, }) diff --git a/apps/desktop/main/ai/agent-stream-runner.ts b/apps/desktop/main/ai/agent-stream-runner.ts index 4e48a481..f908aecf 100644 --- a/apps/desktop/main/ai/agent-stream-runner.ts +++ b/apps/desktop/main/ai/agent-stream-runner.ts @@ -79,7 +79,6 @@ export function createElectronRunAgentStream( analysisService: CrossChatAnalysisService sessionAdapter: SessionRuntimeAdapter memoryService: AIMemoryService - getAllowProactiveMemory: () => boolean } ): ( params: AgentStreamRequest, @@ -156,7 +155,6 @@ export function createElectronRunAgentStream( maxToolResultTokens: resolveCrossChatToolResultTokenBudget(resolvedContextWindow), memoryService: crossChatOptions.memoryService, aiChatId, - allowProactiveMemory: crossChatOptions.getAllowProactiveMemory(), }) await runCrossChatAgent({ userMessage, diff --git a/apps/desktop/main/ai/cross-chat-tool-adapter.ts b/apps/desktop/main/ai/cross-chat-tool-adapter.ts index 3bc7a82e..97f76114 100644 --- a/apps/desktop/main/ai/cross-chat-tool-adapter.ts +++ b/apps/desktop/main/ai/cross-chat-tool-adapter.ts @@ -23,7 +23,6 @@ export function createElectronCrossChatTools(options: { maxToolResultTokens: number memoryService: AIMemoryService aiChatId: string - allowProactiveMemory: boolean }): AgentTool[] { const context: Omit = { locale: options.locale, @@ -31,7 +30,6 @@ export function createElectronCrossChatTools(options: { analysisService: options.analysisService, memoryService: options.memoryService, aiChatId: options.aiChatId, - allowProactiveMemory: options.allowProactiveMemory, maxToolResultTokens: options.maxToolResultTokens, countTokens, preprocessMessagesBySession: (sessionId, messages) => diff --git a/apps/desktop/main/internal-api/server.ts b/apps/desktop/main/internal-api/server.ts index 2b765bb2..3422e89d 100644 --- a/apps/desktop/main/internal-api/server.ts +++ b/apps/desktop/main/internal-api/server.ts @@ -229,7 +229,6 @@ export async function startInternalServer( analysisService: crossChatAnalysisService, sessionAdapter, memoryService: newAIMemoryService, - getAllowProactiveMemory: () => preferencesManager.load().aiGlobalSettings.allowProactiveMemory, }), executeAiTool: createExecuteElectronAiTool(newSemanticIndexService ?? undefined), } diff --git a/packages/node-runtime/src/preferences.test.ts b/packages/node-runtime/src/preferences.test.ts index 4db1c39a..03a474fc 100644 --- a/packages/node-runtime/src/preferences.test.ts +++ b/packages/node-runtime/src/preferences.test.ts @@ -195,7 +195,7 @@ test('loads thinkingLevels as empty object when field is absent in legacy prefer // Write a legacy preferences.json that has no thinkingLevels field writeFileSync( join(systemDir, 'preferences.json'), - JSON.stringify({ aiGlobalSettings: { maxMessagesPerRequest: 500 } }) + JSON.stringify({ aiGlobalSettings: { maxMessagesPerRequest: 500, allowProactiveMemory: false } }) ) const loaded = new PreferencesManager(systemDir).load() @@ -204,7 +204,7 @@ test('loads thinkingLevels as empty object when field is absent in legacy prefer // Existing fields must survive the migration assert.equal(loaded.aiGlobalSettings.maxMessagesPerRequest, 500) assert.equal(loaded.aiGlobalSettings.chartAutoMode, 'suggest') - assert.equal(loaded.aiGlobalSettings.allowProactiveMemory, true) + assert.equal('allowProactiveMemory' in loaded.aiGlobalSettings, false) } finally { rmSync(systemDir, { recursive: true, force: true }) } diff --git a/packages/node-runtime/src/preferences.ts b/packages/node-runtime/src/preferences.ts index d1c640a1..4334ddc9 100644 --- a/packages/node-runtime/src/preferences.ts +++ b/packages/node-runtime/src/preferences.ts @@ -49,7 +49,6 @@ const DEFAULTS: Preferences = { sqlExportFormat: 'csv', enableAutoSkill: true, chartAutoMode: 'suggest', - allowProactiveMemory: true, searchContextBefore: 2, searchContextAfter: 2, }, @@ -152,10 +151,10 @@ export class PreferencesManager { private normalizeAiGlobalSettings(partial: Partial | undefined): AIGlobalSettings { const normalized = { ...(partial ?? {}) } as Record - // Context compression became an automatic runtime capability. Ignore the - // legacy user preference so a previously disabled value cannot survive - // after the setting is removed. + // Ignore removed settings so legacy values cannot survive normalization + // and accidentally re-enable obsolete behavior after a later save. delete normalized.contextCompression + delete normalized.allowProactiveMemory return { ...DEFAULTS.aiGlobalSettings, ...(normalized as Partial) } } diff --git a/packages/shared-types/index.ts b/packages/shared-types/index.ts index 15f4a6b9..7127ee84 100644 --- a/packages/shared-types/index.ts +++ b/packages/shared-types/index.ts @@ -295,7 +295,6 @@ export interface AIGlobalSettings { sqlExportFormat: 'csv' | 'json' enableAutoSkill: boolean chartAutoMode: ChartAutoMode - allowProactiveMemory: boolean searchContextBefore: number searchContextAfter: number } diff --git a/packages/tools/src/agent-adapter.test.ts b/packages/tools/src/agent-adapter.test.ts index e3615723..e29276e4 100644 --- a/packages/tools/src/agent-adapter.test.ts +++ b/packages/tools/src/agent-adapter.test.ts @@ -64,7 +64,6 @@ test('the shared Desktop and CLI Web adapter exposes and executes all thirteen c analysisService, memoryService: {} as Parameters[0]['memoryService'], aiChatId: 'global-chat-test', - allowProactiveMemory: true, maxToolResultTokens: 16_000, preprocessMessagesBySession: async (_sessionId, messages) => messages, preprocessSummariesBySession: async (_sessionId, summaries) => summaries, @@ -128,7 +127,6 @@ test('cross-chat tool adapters share entities resolved during the current Agent analysisService, memoryService, aiChatId: 'global-chat-test', - allowProactiveMemory: true, maxToolResultTokens: 16_000, preprocessMessagesBySession: async (_sessionId, messages) => messages, preprocessSummariesBySession: async (_sessionId, summaries) => summaries, diff --git a/packages/tools/src/definitions/cross-chat-tools.test.ts b/packages/tools/src/definitions/cross-chat-tools.test.ts index 8429b5d9..5cf140ec 100644 --- a/packages/tools/src/definitions/cross-chat-tools.test.ts +++ b/packages/tools/src/definitions/cross-chat-tools.test.ts @@ -499,7 +499,6 @@ function createContext(overrides: Partial = {}): C analysisService: service, memoryService: {} as CrossChatToolExecutionContext['memoryService'], aiChatId: 'global-chat-test', - allowProactiveMemory: true, preprocessMessagesBySession: (_sessionId, messages) => messages.map((message) => ({ ...message, senderName: 'U1', content: '[redacted]' })), preprocessSummariesBySession: (_sessionId, summaries) => summaries, diff --git a/packages/tools/src/definitions/memory-tools.test.ts b/packages/tools/src/definitions/memory-tools.test.ts index 20618ed5..462e4bf6 100644 --- a/packages/tools/src/definitions/memory-tools.test.ts +++ b/packages/tools/src/definitions/memory-tools.test.ts @@ -101,7 +101,7 @@ function entityResolution(refs: AIEntityRef[]): CrossChatEntityResolution { } function createContext( - options: { allowProactiveMemory?: boolean; entityRefs?: AIEntityRef[]; resolvedEntityRefs?: AIEntityRef[] } = {} + options: { entityRefs?: AIEntityRef[]; resolvedEntityRefs?: AIEntityRef[] } = {} ): CrossChatToolExecutionContext { const analysisService = { resolveEntities: async (refs: AIEntityRef[]) => entityResolution(refs), @@ -113,7 +113,6 @@ function createContext( analysisService, memoryService: new FakeMemoryService(), aiChatId: 'global-chat-1', - allowProactiveMemory: options.allowProactiveMemory ?? true, preprocessMessagesBySession: (_sessionId, messages) => messages, preprocessSummariesBySession: (_sessionId, summaries) => summaries, preprocessModelLabel: (value) => value, @@ -185,19 +184,13 @@ describe('global memory tools', () => { ) }) - it('blocks proactive AI writes when disabled but keeps explicit user writes available', async () => { - const context = createContext({ allowProactiveMemory: false }) - await assert.rejects( - async () => - memoryWriteTool.handler({ scope_type: 'global', content: 'AI inference', source_type: 'ai' }, context), - /disabled/i - ) - + it('writes durable AI-derived memories', async () => { + const context = createContext() const result = await memoryWriteTool.handler( - { scope_type: 'global', content: '用户明确要求记住', source_type: 'user' }, + { scope_type: 'global', content: 'AI 根据聊天证据形成的稳定结论', source_type: 'ai' }, context ) - assert.equal((result.data as AIMemoryEntry).sourceType, 'user') + assert.equal((result.data as AIMemoryEntry).sourceType, 'ai') }) it('reads and writes self memory on demand without an entity id', async () => { diff --git a/packages/tools/src/definitions/memory-tools.ts b/packages/tools/src/definitions/memory-tools.ts index 0f5771eb..153318f8 100644 --- a/packages/tools/src/definitions/memory-tools.ts +++ b/packages/tools/src/definitions/memory-tools.ts @@ -95,9 +95,6 @@ async function memoryWriteHandler( await assertResolvableEntityScope(scope, context) const content = requireString(params.content, 'content') const sourceType = parseSourceType(params.source_type) - if (sourceType === 'ai' && !context.allowProactiveMemory) { - throw new Error('Proactive AI memory is disabled; only explicit user memory writes are allowed') - } const id = optionalString(params.id) let entry: AIMemoryEntry diff --git a/packages/tools/src/types.ts b/packages/tools/src/types.ts index 1f849250..2460dc69 100644 --- a/packages/tools/src/types.ts +++ b/packages/tools/src/types.ts @@ -433,7 +433,6 @@ export interface CrossChatToolExecutionContext { analysisService: CrossChatAnalysisToolService memoryService: AIMemoryToolService aiChatId: string - allowProactiveMemory: boolean preprocessMessagesBySession: ( sessionId: string, messages: CrossChatMessageSource[] diff --git a/src/components/AIChat/memory/AIMemoryManagerModal.vue b/src/components/AIChat/memory/AIMemoryManagerModal.vue index 9332efd4..c0771ab4 100644 --- a/src/components/AIChat/memory/AIMemoryManagerModal.vue +++ b/src/components/AIChat/memory/AIMemoryManagerModal.vue @@ -1,11 +1,9 @@