From fa8cd4ce58eba2e57fe6013f2f4b21ba16fc1222 Mon Sep 17 00:00:00 2001 From: 7Sageer <7sageer@djwcb.cn> Date: Thu, 30 Jul 2026 16:07:13 +0800 Subject: [PATCH] refactor(agent-core-v2): remove the agents_md context injection --- .../agent-core-v2/docs/state-manifest.d.ts | 54 +--- .../agent-core-v2/docs/wire-manifest.d.ts | 2 - .../contextInjector/disclosureBaseline.ts | 2 +- .../src/agent/contextMemory/types.ts | 7 - .../src/agent/profile/agentsMdReminder.ts | 17 - .../agent/profile/agentsMdReminderService.ts | 159 ---------- .../src/agent/profile/context.ts | 28 +- .../agentProfileCatalog.ts | 7 - .../app/agentProfileCatalog/profile-shared.ts | 22 -- packages/agent-core-v2/src/index.ts | 2 - .../dateChange/dateChangeInjection.test.ts | 2 - .../agent/profile/agentsMdReminder.test.ts | 299 ------------------ .../test/agent/profile/profileOps.test.ts | 8 - .../profile-shared.test.ts | 36 +-- .../test/app/config/config.test.ts | 2 +- 15 files changed, 9 insertions(+), 638 deletions(-) delete mode 100644 packages/agent-core-v2/src/agent/profile/agentsMdReminder.ts delete mode 100644 packages/agent-core-v2/src/agent/profile/agentsMdReminderService.ts delete mode 100644 packages/agent-core-v2/test/agent/profile/agentsMdReminder.test.ts diff --git a/packages/agent-core-v2/docs/state-manifest.d.ts b/packages/agent-core-v2/docs/state-manifest.d.ts index fe03bdf15..2b3a2e571 100644 --- a/packages/agent-core-v2/docs/state-manifest.d.ts +++ b/packages/agent-core-v2/docs/state-manifest.d.ts @@ -23,7 +23,7 @@ // references become '(circular)', and class instances collapse to a '(ClassName)' // marker — the wire shape of an entry is the JSON projection of the type here. // -// Index (Session: 28 keys · Agent: 70 keys) +// Index (Session: 28 keys · Agent: 69 keys) // Session // cron.inFlight src/session/cron/sessionCronServiceImpl.ts // cron.lastSeenAt src/session/cron/sessionCronServiceImpl.ts @@ -59,7 +59,6 @@ // activityView.lastTurn src/agent/activityView/activityViewService.ts // activityView.lifecycle src/agent/activityView/activityViewService.ts // activityView.turn src/agent/activityView/activityViewService.ts -// agentsMdReminder.seed src/agent/profile/agentsMdReminderService.ts // contextInjector.isNewTurn src/agent/contextInjector/contextInjectorService.ts // contextProjector.lastRepairSignature src/agent/contextProjector/contextProjectorService.ts // contextSize.lastEmittedTokens src/agent/contextSize/contextSizeService.ts @@ -204,7 +203,6 @@ export interface SessionStateSnapshot { readonly skillActive?: boolean; readonly productName?: string; readonly replyStyleGuide?: string; - readonly agentsMdStatus?: 'missing' | 'empty' | 'present'; [key: string]: unknown; }) => string; readonly renderSystemPrompt?: (context: /* AgentProfileContext — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ { @@ -220,7 +218,6 @@ export interface SessionStateSnapshot { readonly skillActive?: boolean; readonly productName?: string; readonly replyStyleGuide?: string; - readonly agentsMdStatus?: 'missing' | 'empty' | 'present'; [key: string]: unknown; }) => /* SystemPromptRenderResult — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ { readonly text: string; @@ -235,15 +232,6 @@ export interface SessionStateSnapshot { } | { readonly disclosed: false; }; - readonly agentsMd: { - readonly disclosed: true; - readonly value: { - readonly fingerprint: string; - readonly status: /* AgentsMdStatus — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ 'missing' | 'empty' | 'present'; - }; - } | { - readonly disclosed: false; - }; }; }; readonly promptPrefix?: (ctx: /* AgentProfilePromptPrefixContext — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ { @@ -310,7 +298,6 @@ export interface SessionStateSnapshot { readonly skillActive?: boolean; readonly productName?: string; readonly replyStyleGuide?: string; - readonly agentsMdStatus?: 'missing' | 'empty' | 'present'; [key: string]: unknown; }) => string; readonly renderSystemPrompt?: (context: /* AgentProfileContext — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ { @@ -326,7 +313,6 @@ export interface SessionStateSnapshot { readonly skillActive?: boolean; readonly productName?: string; readonly replyStyleGuide?: string; - readonly agentsMdStatus?: 'missing' | 'empty' | 'present'; [key: string]: unknown; }) => /* SystemPromptRenderResult — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ { readonly text: string; @@ -341,15 +327,6 @@ export interface SessionStateSnapshot { } | { readonly disclosed: false; }; - readonly agentsMd: { - readonly disclosed: true; - readonly value: { - readonly fingerprint: string; - readonly status: /* AgentsMdStatus — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ 'missing' | 'empty' | 'present'; - }; - } | { - readonly disclosed: false; - }; }; }; readonly promptPrefix?: (ctx: /* AgentProfilePromptPrefixContext — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ { @@ -749,11 +726,6 @@ export interface SessionStateSnapshot { readonly renderGeneration: number; readonly localDate: string; readonly timeZone: string; - } | { - readonly kind: 'agents_md'; - readonly renderGeneration: number; - readonly fingerprint: string; - readonly status: /* AgentsMdStatus — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ 'missing' | 'empty' | 'present'; } | { readonly kind: 'skills'; readonly renderGeneration: number; @@ -840,11 +812,6 @@ export interface AgentStateSnapshot { readonly renderGeneration: number; readonly localDate: string; readonly timeZone: string; - } | { - readonly kind: 'agents_md'; - readonly renderGeneration: number; - readonly fingerprint: string; - readonly status: /* AgentsMdStatus — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ 'missing' | 'empty' | 'present'; } | { readonly kind: 'skills'; readonly renderGeneration: number; @@ -979,11 +946,6 @@ export interface AgentStateSnapshot { readonly renderGeneration: number; readonly localDate: string; readonly timeZone: string; - } | { - readonly kind: 'agents_md'; - readonly renderGeneration: number; - readonly fingerprint: string; - readonly status: /* AgentsMdStatus — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ 'missing' | 'empty' | 'present'; } | { readonly kind: 'skills'; readonly renderGeneration: number; @@ -1050,11 +1012,6 @@ export interface AgentStateSnapshot { readonly renderGeneration: number; readonly localDate: string; readonly timeZone: string; - } | { - readonly kind: 'agents_md'; - readonly renderGeneration: number; - readonly fingerprint: string; - readonly status: /* AgentsMdStatus — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ 'missing' | 'empty' | 'present'; } | { readonly kind: 'skills'; readonly renderGeneration: number; @@ -1162,7 +1119,7 @@ export interface AgentStateSnapshot { 'llmRequester.lastConfigLogSignature': string | undefined; 'llmRequester.mediaDegradedTurns': Set; 'llmRequester.mediaStrippedTurns': Map; 'llmRequester.turnConfigs': Map = - createDecorator('agentAgentsMdReminderService'); diff --git a/packages/agent-core-v2/src/agent/profile/agentsMdReminderService.ts b/packages/agent-core-v2/src/agent/profile/agentsMdReminderService.ts deleted file mode 100644 index 3ff2ff335..000000000 --- a/packages/agent-core-v2/src/agent/profile/agentsMdReminderService.ts +++ /dev/null @@ -1,159 +0,0 @@ -/** - * `profile` domain (L4) — `IAgentAgentsMdReminderService` implementation. - * - * Owns the `agents_md` context-injection provider. The AGENTS.md instruction - * hierarchy is baked into the system prompt at (re)bind and after compaction, - * so a user editing AGENTS.md mid-session leaves the model following stale - * rules; this provider injects the fresh content at the next step boundary - * when it differs. Unlike skills, removals and content edits announce too — - * a deleted rule fails silently, never on invocation. The provider runs only - * while the profile's rendered snapshot exists and matches the live cwd. The - * baseline prefers the typed disclosure on the newest surviving `agents_md` - * injection, then the persisted rendered snapshot, then a runtime seed kept - * in `agentState`: a profile whose snapshot declares no AGENTS.md disclosure - * is seeded with the first observed fingerprint (quietly), so a later - * creation, edit, or removal still announces. The live content is re-read at - * every step boundary — the candidate chain is a handful of small instruction - * files, so no filesystem watch is kept. Bound at Agent scope. - */ - -import { Disposable } from '#/_base/di/lifecycle'; -import { LifecycleScope, ScopeActivation, registerScopedService } from '#/_base/di/scope'; -import { defineState } from '#/_base/state/stateRegistry'; -import { - IAgentContextInjectorService, - type ContextInjectionContext, - type ContextInjectionResult, -} from '#/agent/contextInjector/contextInjector'; -import { - disclosureOfKind, - pickDisclosureBaseline, -} from '#/agent/contextInjector/disclosureBaseline'; -import { IAgentStateService } from '#/agent/state/agentState'; -import type { AgentsMdStatus } from '#/app/agentProfileCatalog/agentProfileCatalog'; -import { fingerprintDisclosureContent } from '#/app/agentProfileCatalog/profile-shared'; -import { IBootstrapService } from '#/app/bootstrap/bootstrap'; -import { IHostEnvironment } from '#/os/interface/hostEnvironment'; -import { IHostFileSystem } from '#/os/interface/hostFileSystem'; - -import { loadAgentsMdSnapshot } from './context'; -import { IAgentProfileService } from './profile'; -import { IAgentAgentsMdReminderService } from './agentsMdReminder'; - -const AGENTS_MD_INJECTION_VARIANT = 'agents_md'; - -const CURRENT_BLOCK_START = ''; -const CURRENT_BLOCK_END = ''; - -export const agentsMdReminderSeedKey = defineState( - 'agentsMdReminder.seed', - () => undefined, -); - -export class AgentAgentsMdReminderService extends Disposable implements IAgentAgentsMdReminderService { - declare readonly _serviceBrand: undefined; - - constructor( - @IAgentContextInjectorService dynamicInjector: IAgentContextInjectorService, - @IAgentProfileService private readonly profile: IAgentProfileService, - @IAgentStateService private readonly states: IAgentStateService, - @IHostFileSystem private readonly fs: IHostFileSystem, - @IHostEnvironment private readonly env: IHostEnvironment, - @IBootstrapService private readonly bootstrap: IBootstrapService, - ) { - super(); - this.states.register(agentsMdReminderSeedKey); - this._register( - dynamicInjector.register(AGENTS_MD_INJECTION_VARIANT, (ctx) => this.reminder(ctx)), - ); - } - - private async reminder({ - lastDisclosure, - }: ContextInjectionContext): Promise { - try { - let profileData = this.profile.data(); - if (profileData.environmentDisclosure?.cwd !== profileData.cwd) return undefined; - const current = await loadAgentsMdSnapshot( - { fs: this.fs, homeDir: this.env.homeDir }, - profileData.cwd, - this.bootstrap.homeDir, - ); - profileData = this.profile.data(); - if (profileData.environmentDisclosure?.cwd !== profileData.cwd) return undefined; - const renderGeneration = profileData.renderGeneration ?? 0; - const fingerprint = fingerprintDisclosureContent(current.content); - const baseline = pickDisclosureBaseline( - disclosureOfKind(lastDisclosure, 'agents_md'), - this.contentFromProfile(), - this.seed(), - ); - if (baseline === undefined) { - this.states.set(agentsMdReminderSeedKey, { - fingerprint, - status: current.status, - renderGeneration, - cwd: profileData.cwd, - }); - return undefined; - } - if (baseline.fingerprint === fingerprint) { - return undefined; - } - return { - content: buildAgentsMdReminder(current.content), - disclosure: { - kind: 'agents_md', - renderGeneration, - fingerprint, - status: current.status, - }, - }; - } catch { - return undefined; - } - } - - private contentFromProfile(): AgentsMdDisclosure | undefined { - const profileData = this.profile.data(); - if (profileData.environmentDisclosure?.cwd !== profileData.cwd) return undefined; - const agentsMd = profileData.environmentDisclosure.agentsMd; - if (!agentsMd?.disclosed) return undefined; - return { - ...agentsMd.value, - renderGeneration: profileData.renderGeneration ?? 0, - }; - } - - private seed(): AgentsMdDisclosure | undefined { - const seed = this.states.get(agentsMdReminderSeedKey); - if (seed === undefined || seed.cwd !== this.profile.data().cwd) return undefined; - return seed; - } -} - -interface AgentsMdDisclosure { - readonly fingerprint: string; - readonly status: AgentsMdStatus; - readonly renderGeneration: number; -} - -interface AgentsMdSeed extends AgentsMdDisclosure { - readonly cwd: string; -} - -function buildAgentsMdReminder(current: string): string { - const body = - current.length > 0 - ? 'The AGENTS.md instructions have changed since your system prompt was rendered. The content below is current and supersedes the AGENTS.md instructions in your system prompt.' - : 'The AGENTS.md instructions that fed your system prompt have been removed (or are now empty); they no longer apply.'; - return `${body}\n\n${CURRENT_BLOCK_START}\n${current}\n${CURRENT_BLOCK_END}\n\nDO NOT mention this to the user explicitly.`; -} - -registerScopedService( - LifecycleScope.Agent, - IAgentAgentsMdReminderService, - AgentAgentsMdReminderService, - ScopeActivation.OnScopeCreated, - 'profile', -); diff --git a/packages/agent-core-v2/src/agent/profile/context.ts b/packages/agent-core-v2/src/agent/profile/context.ts index 2c1d2969a..475832778 100644 --- a/packages/agent-core-v2/src/agent/profile/context.ts +++ b/packages/agent-core-v2/src/agent/profile/context.ts @@ -20,7 +20,6 @@ import { dirname, join, normalize } from 'pathe'; import type { IHostFileSystem } from '#/os/interface/hostFileSystem'; -import type { AgentsMdStatus } from '#/app/agentProfileCatalog/agentProfileCatalog'; import type { SystemPromptContext } from './profile'; export const AGENTS_MD_RECOMMENDED_MAX_BYTES = 32 * 1024; @@ -38,7 +37,6 @@ export interface PreparedSystemPromptContext extends SystemPromptContext { readonly agentsMd?: string; readonly additionalDirsInfo?: string; readonly agentsMdWarning?: string; - readonly agentsMdStatus?: AgentsMdStatus; } export interface PrepareSystemPromptContextOptions { @@ -60,7 +58,6 @@ export async function prepareSystemPromptContext( return { cwdListing, agentsMd: agentsMdResult.content, - agentsMdStatus: agentsMdResult.status, additionalDirsInfo, agentsMdWarning: agentsMdResult.warning, }; @@ -71,17 +68,8 @@ export async function loadAgentsMd( workDir: string, brandHome?: string, ): Promise { - const result = await loadAgentsMdSnapshot(deps, workDir, brandHome); - return result.content; -} - -export async function loadAgentsMdSnapshot( - deps: ProfileContextDeps, - workDir: string, - brandHome?: string, -): Promise<{ readonly content: string; readonly status: AgentsMdStatus }> { const result = await loadAgentsMdForRoots(deps, brandHome, [workDir]); - return { content: result.content, status: result.status }; + return result.content; } async function agentsMdCandidateGroups( @@ -109,7 +97,6 @@ async function agentsMdCandidateGroups( interface LoadedAgentsMd { readonly content: string; readonly warning: string | undefined; - readonly status: AgentsMdStatus; } async function loadAgentsMdForRoots( @@ -120,15 +107,12 @@ async function loadAgentsMdForRoots( const discovered: AgentFile[] = []; const seen = new Set(); const loadWarnings: string[] = []; - let emptyFileFound = false; const warnLoad = (message: string): void => { loadWarnings.push(message); }; const collect = async (path: string): Promise => { - const file = await readAgentFile(deps, path, warnLoad, () => { - emptyFileFound = true; - }); + const file = await readAgentFile(deps, path, warnLoad); if (file === undefined) return false; const key = normalize(file.path); if (seen.has(key)) return false; @@ -154,11 +138,7 @@ async function loadAgentsMdForRoots( ); } const warning = loadWarnings.length > 0 ? loadWarnings.join('\n') : undefined; - return { - content, - warning, - status: content.length > 0 ? 'present' : emptyFileFound ? 'empty' : 'missing', - }; + return { content, warning }; } async function loadAdditionalDirsInfo( @@ -210,7 +190,6 @@ async function readAgentFile( deps: ProfileContextDeps, path: string, warn: (message: string) => void, - markEmpty: () => void, ): Promise { if (!(await isFile(deps, path))) { if (await entryExists(deps, path)) { @@ -226,7 +205,6 @@ async function readAgentFile( return undefined; } if (content.length === 0) { - markEmpty(); return undefined; } return { path, content }; diff --git a/packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts b/packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts index 77ace7879..5de2a061f 100644 --- a/packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts +++ b/packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts @@ -67,20 +67,14 @@ export interface AgentProfileContext { readonly skillActive?: boolean; readonly productName?: string; readonly replyStyleGuide?: string; - readonly agentsMdStatus?: AgentsMdStatus; readonly [key: string]: unknown; } -export type AgentsMdStatus = 'missing' | 'empty' | 'present'; - export interface EnvironmentDisclosureSnapshot { readonly cwd: string; readonly date: | { readonly disclosed: true; readonly value: { readonly localDate: string; readonly timeZone: string } } | { readonly disclosed: false }; - readonly agentsMd: - | { readonly disclosed: true; readonly value: { readonly fingerprint: string; readonly status: AgentsMdStatus } } - | { readonly disclosed: false }; } export interface SystemPromptRenderResult { @@ -124,7 +118,6 @@ export function renderAgentProfile( environment: { cwd: context.cwd ?? '', date: { disclosed: false }, - agentsMd: { disclosed: false }, }, } ); diff --git a/packages/agent-core-v2/src/app/agentProfileCatalog/profile-shared.ts b/packages/agent-core-v2/src/app/agentProfileCatalog/profile-shared.ts index 312f70c78..3cd9955fc 100644 --- a/packages/agent-core-v2/src/app/agentProfileCatalog/profile-shared.ts +++ b/packages/agent-core-v2/src/app/agentProfileCatalog/profile-shared.ts @@ -28,8 +28,6 @@ * parse the rendered text. */ -import { createHash } from 'node:crypto'; - import { renderPrompt } from '#/_base/utils/render-prompt'; import { @@ -183,7 +181,6 @@ function environmentForTemplate( context: AgentProfileContext, ): EnvironmentDisclosureSnapshot { const usesNow = template.includes('${now}'); - const usesAgentsMd = template.includes('${agents_md}'); return { cwd: context.cwd ?? '', date: usesNow @@ -195,15 +192,6 @@ function environmentForTemplate( }, } : { disclosed: false }, - agentsMd: usesAgentsMd - ? { - disclosed: true, - value: { - fingerprint: fingerprintDisclosureContent(context.agentsMd ?? ''), - status: context.agentsMdStatus ?? agentsMdStatus(context.agentsMd), - }, - } - : { disclosed: false }, }; } @@ -211,7 +199,6 @@ function undisclosedEnvironment(context: AgentProfileContext): EnvironmentDisclo return { cwd: context.cwd ?? '', date: { disclosed: false }, - agentsMd: { disclosed: false }, }; } @@ -223,18 +210,9 @@ function mergeEnvironmentDisclosure( return { cwd: direct.cwd || base.cwd, date: direct.date.disclosed ? direct.date : base.date, - agentsMd: direct.agentsMd.disclosed ? direct.agentsMd : base.agentsMd, }; } -export function fingerprintDisclosureContent(content: string): string { - return createHash('sha256').update(content, 'utf8').digest('hex'); -} - -export function agentsMdStatus(content: string | undefined): 'missing' | 'empty' | 'present' { - return content === undefined ? 'missing' : content.length === 0 ? 'empty' : 'present'; -} - function localDateKey(now: string | undefined): string { const date = now === undefined ? new Date() : new Date(now); if (Number.isNaN(date.getTime())) return localDateKey(undefined); diff --git a/packages/agent-core-v2/src/index.ts b/packages/agent-core-v2/src/index.ts index 32d39f7c4..8ff021abc 100644 --- a/packages/agent-core-v2/src/index.ts +++ b/packages/agent-core-v2/src/index.ts @@ -529,8 +529,6 @@ export * from '#/agent/permissionRules/permissionRulesService'; export * from '#/agent/profile/profile'; export * from '#/agent/profile/profileService'; export * from '#/agent/profile/context'; -export * from '#/agent/profile/agentsMdReminder'; -export * from '#/agent/profile/agentsMdReminderService'; export * from '#/agent/prompt/prompt'; export * from '#/agent/prompt/promptService'; import '#/app/messageLegacy/errors'; diff --git a/packages/agent-core-v2/test/agent/dateChange/dateChangeInjection.test.ts b/packages/agent-core-v2/test/agent/dateChange/dateChangeInjection.test.ts index 7fb987a2b..cea95b0fd 100644 --- a/packages/agent-core-v2/test/agent/dateChange/dateChangeInjection.test.ts +++ b/packages/agent-core-v2/test/agent/dateChange/dateChangeInjection.test.ts @@ -64,7 +64,6 @@ function updateSystemPromptWithDate( timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC', }, }, - agentsMd: { disclosed: false }, }; profile.update({ systemPrompt: systemPromptWithDate(iso), @@ -77,7 +76,6 @@ function updateSystemPromptWithoutDate(profile: IAgentProfileService): void { const environment: EnvironmentDisclosureSnapshot = { cwd: profile.data().cwd, date: { disclosed: false }, - agentsMd: { disclosed: false }, }; profile.update({ systemPrompt: 'You are a deterministic test agent.', diff --git a/packages/agent-core-v2/test/agent/profile/agentsMdReminder.test.ts b/packages/agent-core-v2/test/agent/profile/agentsMdReminder.test.ts deleted file mode 100644 index 9b6dfda75..000000000 --- a/packages/agent-core-v2/test/agent/profile/agentsMdReminder.test.ts +++ /dev/null @@ -1,299 +0,0 @@ -/** - * Scenario: `agents_md` context injection announces AGENTS.md content changes. - * - * Exercises the real provider through the harness injector against a fake - * host fs whose AGENTS.md files the test edits and deletes: the provider - * re-reads the candidate chain on every injection, so a filesystem edit - * lands on the next `inject()` call. Baselines come from typed reminder - * metadata, the persisted rendered snapshot, and a runtime seed recorded on - * first observation for prompts that never disclose AGENTS.md. Edits, - * creations, and removals all announce. Run: `pnpm --filter - * @moonshot-ai/agent-core-v2 exec vitest run - * test/agent/profile/agentsMdReminder.test.ts`. - */ - -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; - -import { IAgentContextInjectorService } from '#/agent/contextInjector/contextInjector'; -import { IAgentContextMemoryService } from '#/agent/contextMemory/contextMemory'; -import type { ContextMessage } from '#/agent/contextMemory/types'; -import { loadAgentsMd } from '#/agent/profile/context'; -import { IAgentProfileService } from '#/agent/profile/profile'; -import { - fingerprintDisclosureContent, - agentsMdStatus, -} from '#/app/agentProfileCatalog/profile-shared'; -import type { EnvironmentDisclosureSnapshot } from '#/app/agentProfileCatalog/agentProfileCatalog'; -import type { HostFileStat, IHostFileSystem } from '#/os/interface/hostFileSystem'; - -import { createFakeHostFs } from '../../tools/fixtures/fake-exec'; -import { createTestAgent, execEnvServices, type TestAgentContext } from '../../harness'; - -const TEST_HOME_DIR = '/home/test'; -const BRAND_HOME_DIR = '/tmp/kimi-code-agent-app-v2-test'; - -function systemPromptWithAgentsMd(content: string): string { - return [ - 'You are a deterministic test agent.', - '', - 'The applicable `AGENTS.md` instructions are:', - '', - '```````', - content, - '```````', - ].join('\n'); -} - -function updateSystemPromptWithAgentsMd( - profile: IAgentProfileService, - content: string, -): void { - const environment: EnvironmentDisclosureSnapshot = { - cwd: profile.data().cwd, - date: { disclosed: false }, - agentsMd: { - disclosed: true, - value: { - fingerprint: fingerprintDisclosureContent(content), - status: agentsMdStatus(content), - }, - }, - }; - profile.update({ - systemPrompt: systemPromptWithAgentsMd(content), - environmentDisclosure: environment, - }); -} - -function updateSystemPromptWithoutAgentsMd(profile: IAgentProfileService): void { - const environment: EnvironmentDisclosureSnapshot = { - cwd: profile.data().cwd, - date: { disclosed: false }, - agentsMd: { disclosed: false }, - }; - profile.update({ - systemPrompt: 'You are a deterministic test agent.', - environmentDisclosure: environment, - }); -} - -function agentsMdReminders(context: IAgentContextMemoryService): readonly ContextMessage[] { - return context.get().filter((message) => { - return message.origin?.kind === 'injection' && message.origin.variant === 'agents_md'; - }); -} - -function messageText(message: ContextMessage): string { - return message.content - .map((part) => (part.type === 'text' ? part.text : '')) - .join(''); -} - -function deferred(): { - readonly promise: Promise; - readonly resolve: (value: T) => void; -} { - let resolve!: (value: T) => void; - const promise = new Promise((done) => { - resolve = done; - }); - return { promise, resolve }; -} - -describe('AgentAgentsMdReminderService', () => { - let cwd: string; - let agentsMdPath: string; - let files: Map; - let dirs: Set; - let hostFs: IHostFileSystem; - let ctx: TestAgentContext; - let context: IAgentContextMemoryService; - let injector: IAgentContextInjectorService; - let profile: IAgentProfileService; - let readTextOverride: ((path: string) => Promise) | undefined; - - function fileStat(path: string): HostFileStat { - return { isFile: true, isDirectory: false, size: files.get(path)?.length ?? 0 }; - } - - async function currentAgentsMd(): Promise { - return loadAgentsMd({ fs: hostFs, homeDir: TEST_HOME_DIR }, cwd, BRAND_HOME_DIR); - } - - beforeEach(() => { - cwd = process.cwd(); - agentsMdPath = `${cwd}/AGENTS.md`; - files = new Map(); - dirs = new Set([cwd, `${cwd}/.git`]); - readTextOverride = undefined; - hostFs = createFakeHostFs({ - stat: async (path: string) => { - if (files.has(path)) return fileStat(path); - if (dirs.has(path)) return { isFile: false, isDirectory: true, size: 0 }; - throw new Error(`ENOENT: ${path}`); - }, - lstat: async (path: string) => { - if (files.has(path)) return fileStat(path); - if (dirs.has(path)) return { isFile: false, isDirectory: true, size: 0 }; - throw new Error(`ENOENT: ${path}`); - }, - readText: async (path: string) => { - if (readTextOverride !== undefined) return readTextOverride(path); - const content = files.get(path); - if (content === undefined) throw new Error(`ENOENT: ${path}`); - return content; - }, - readdir: async () => [], - realpath: async (path: string) => path, - }); - ctx = createTestAgent(execEnvServices({ hostFs })); - context = ctx.get(IAgentContextMemoryService); - injector = ctx.get(IAgentContextInjectorService); - profile = ctx.get(IAgentProfileService); - }); - - afterEach(async () => { - try { - await ctx.expectResumeMatches(); - } finally { - await ctx.dispose(); - } - }); - - it('stays quiet when no AGENTS.md exists and the prompt has no fenced block', async () => { - updateSystemPromptWithoutAgentsMd(profile); - - await injector.inject(); - - expect(agentsMdReminders(context)).toHaveLength(0); - expect(context.get()).toHaveLength(0); - }); - - it('announces a file created after the silent seed', async () => { - updateSystemPromptWithoutAgentsMd(profile); - await injector.inject(); - expect(agentsMdReminders(context)).toHaveLength(0); - - files.set(agentsMdPath, 'fresh rule'); - await injector.inject(); - - const reminders = agentsMdReminders(context); - expect(reminders).toHaveLength(1); - expect(messageText(reminders[0] as ContextMessage)).toContain('fresh rule'); - - await injector.inject(); - expect(agentsMdReminders(context)).toHaveLength(1); - }); - - it('stays quiet when the file content matches the system prompt block', async () => { - files.set(agentsMdPath, 'rule one'); - updateSystemPromptWithAgentsMd(profile, await currentAgentsMd()); - - await injector.inject(); - - expect(agentsMdReminders(context)).toHaveLength(0); - }); - - it('injects the fresh content after an edit, then stays quiet', async () => { - files.set(agentsMdPath, 'rule one'); - updateSystemPromptWithAgentsMd(profile, await currentAgentsMd()); - await injector.inject(); - - files.set(agentsMdPath, 'rule two'); - await injector.inject(); - - const reminders = agentsMdReminders(context); - expect(reminders).toHaveLength(1); - const first = reminders[0]; - expect(first).toBeDefined(); - const text = messageText(first as ContextMessage); - expect(text).toContain('rule two'); - expect(text).toContain('supersedes'); - expect(text).toContain('DO NOT mention this to the user explicitly'); - expect(first?.origin).toMatchObject({ - kind: 'injection', - variant: 'agents_md', - disclosure: { - kind: 'agents_md', - fingerprint: fingerprintDisclosureContent(await currentAgentsMd()), - status: 'present', - }, - }); - - await injector.inject(); - expect(agentsMdReminders(context)).toHaveLength(1); - }); - - it('announces an edit that lands while fresh content is being read', async () => { - files.set(agentsMdPath, 'rule one'); - updateSystemPromptWithAgentsMd(profile, await currentAgentsMd()); - await injector.inject(); - - files.set(agentsMdPath, 'rule two'); - - const readStarted = deferred(); - const releaseRead = deferred(); - let pauseRead = true; - readTextOverride = async (path) => { - const content = files.get(path); - if (content === undefined) throw new Error(`ENOENT: ${path}`); - if (path === agentsMdPath && pauseRead) { - pauseRead = false; - readStarted.resolve(undefined); - await releaseRead.promise; - } - return content; - }; - - const firstInjection = injector.inject(); - await readStarted.promise; - files.set(agentsMdPath, 'rule three'); - releaseRead.resolve(undefined); - await firstInjection; - - await injector.inject(); - - const reminders = agentsMdReminders(context); - expect(reminders).toHaveLength(2); - expect(messageText(reminders[0] as ContextMessage)).toContain('rule two'); - expect(messageText(reminders[1] as ContextMessage)).toContain('rule three'); - }); - - it('announces removal when the last AGENTS.md file disappears', async () => { - files.set(agentsMdPath, 'rule one'); - updateSystemPromptWithAgentsMd(profile, await currentAgentsMd()); - await injector.inject(); - - files.clear(); - await injector.inject(); - - const reminders = agentsMdReminders(context); - expect(reminders).toHaveLength(1); - const first = reminders[0]; - expect(first).toBeDefined(); - expect(messageText(first as ContextMessage)).toContain('removed'); - expect(first?.origin).toMatchObject({ - disclosure: { - kind: 'agents_md', - fingerprint: - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', - status: 'missing', - }, - }); - }); - - it('announces a file created after an empty baseline', async () => { - updateSystemPromptWithAgentsMd(profile, ''); - await injector.inject(); - - files.set(agentsMdPath, 'fresh rule'); - - await injector.inject(); - - const reminders = agentsMdReminders(context); - expect(reminders).toHaveLength(1); - const first = reminders[0]; - expect(first).toBeDefined(); - expect(messageText(first as ContextMessage)).toContain('fresh rule'); - }); -}); diff --git a/packages/agent-core-v2/test/agent/profile/profileOps.test.ts b/packages/agent-core-v2/test/agent/profile/profileOps.test.ts index 1824932d5..9010dac98 100644 --- a/packages/agent-core-v2/test/agent/profile/profileOps.test.ts +++ b/packages/agent-core-v2/test/agent/profile/profileOps.test.ts @@ -357,14 +357,6 @@ describe('AgentProfileService (wire-backed config.update)', () => { disclosed: true, value: { localDate: '2026-07-29', timeZone: 'Asia/Shanghai' }, }, - agentsMd: { - disclosed: true, - value: { - fingerprint: - 'a3a18567b84af6528d171050e54bcf75e0a1c94c8db8a165d03e9866cfa95bce', - status: 'present', - }, - }, }; svc.applyBindingSnapshot({ cwd: '/work', diff --git a/packages/agent-core-v2/test/app/agentProfileCatalog/profile-shared.test.ts b/packages/agent-core-v2/test/app/agentProfileCatalog/profile-shared.test.ts index 3348d90be..f3204d5a8 100644 --- a/packages/agent-core-v2/test/app/agentProfileCatalog/profile-shared.test.ts +++ b/packages/agent-core-v2/test/app/agentProfileCatalog/profile-shared.test.ts @@ -141,14 +141,13 @@ describe('renderPromptTemplate', () => { ); }); - it('records the environment facts used by now and agents_md placeholders', () => { + it('records the environment facts used by the now placeholder', () => { const result = renderPromptTemplateResult( 'date=${now} agents=${agents_md}', { cwd: '/work', now: '2026-07-29T12:00:00', agentsMd: 'AGENTS', - agentsMdStatus: 'present', }, { skillActive: true }, ); @@ -159,13 +158,6 @@ describe('renderPromptTemplate', () => { disclosed: true, value: { localDate: '2026-07-29' }, }); - expect(result.environment.agentsMd).toEqual({ - disclosed: true, - value: { - fingerprint: '370f346f47cb65d94fd904ecb4ceeb17d784965984b0f8e52cce79ff95451a54', - status: 'present', - }, - }); }); it('merges disclosure metadata from a structured base_prompt render', () => { @@ -181,14 +173,6 @@ describe('renderPromptTemplate', () => { disclosed: true, value: { localDate: '2026-07-28', timeZone: 'UTC' }, }, - agentsMd: { - disclosed: true, - value: { - fingerprint: - 'a3a18567b84af6528d171050e54bcf75e0a1c94c8db8a165d03e9866cfa95bce', - status: 'present', - }, - }, }, }), ); @@ -200,14 +184,6 @@ describe('renderPromptTemplate', () => { disclosed: true, value: { localDate: '2026-07-28', timeZone: 'UTC' }, }, - agentsMd: { - disclosed: true, - value: { - fingerprint: - 'a3a18567b84af6528d171050e54bcf75e0a1c94c8db8a165d03e9866cfa95bce', - status: 'present', - }, - }, }); }); }); @@ -289,14 +265,13 @@ describe('renderSystemPrompt', () => { expect(overridden).not.toContain('Kimi Code CLI'); }); - it('returns disclosure metadata for the builtin now and agents_md sections', () => { + it('returns disclosure metadata for the builtin now section', () => { const result = renderSystemPromptResult( '', { cwd: '/work', now: '2026-07-29T12:00:00', agentsMd: 'AGENTS', - agentsMdStatus: 'present', }, { skillActive: true }, ); @@ -307,12 +282,5 @@ describe('renderSystemPrompt', () => { disclosed: true, value: { localDate: '2026-07-29' }, }); - expect(result.environment.agentsMd).toEqual({ - disclosed: true, - value: { - fingerprint: '370f346f47cb65d94fd904ecb4ceeb17d784965984b0f8e52cce79ff95451a54', - status: 'present', - }, - }); }); }); diff --git a/packages/agent-core-v2/test/app/config/config.test.ts b/packages/agent-core-v2/test/app/config/config.test.ts index 698876ee5..6e234a48f 100644 --- a/packages/agent-core-v2/test/app/config/config.test.ts +++ b/packages/agent-core-v2/test/app/config/config.test.ts @@ -195,7 +195,7 @@ describe('Agent config', () => { }); expect(ctx.newEvents()).toMatchInlineSnapshot(` - [wire] config.update { "profileName": "test-profile", "systemPrompt": "Profile system prompt.", "environmentDisclosure": { "cwd": "", "date": { "disclosed": false }, "agentsMd": { "disclosed": false } }, "renderGeneration": 2, "disallowedTools": [], "time": "