fix: appendSystemReminder

This commit is contained in:
_Kerman 2026-07-04 21:20:18 +08:00
parent bec0b085f2
commit 0d0bb736a8
3 changed files with 59 additions and 42 deletions

View file

@ -16,7 +16,7 @@ import { createDecorator } from "#/_base/di";
import type { ContextMessage, PromptOrigin } from '#/agent/contextMemory';
export type ContextAppendArgs = readonly ContextMessage[];
export type ContextAppendSystemReminderArgs = [message: ContextMessage];
export type ContextAppendSystemReminderArgs = [message: string, origin: PromptOrigin];
export type ContextReplaceArgs = [index: number, message: ContextMessage];
/**
@ -41,7 +41,7 @@ export interface IAgentContextOpsService {
/**
* Append a `<system-reminder>` message to the end of the history.
* Returns the raw message recorded by the wire operation.
* Returns the message inserted into history.
*/
appendSystemReminder(content: string, origin: PromptOrigin): ContextMessage;

View file

@ -6,9 +6,9 @@
* (Infinity start) and mirror each message into the replay read model;
* `remove` splices out each resolved target and drops the matching replay
* messages; `clear` drops the whole history and cuts a new replay segment;
* legacy `replace` swaps one message in place for old migrated wire. Message
* content is offloaded to the blob store through each operation's blob
* selector. Bound at Agent scope.
* legacy `replace` swaps one message in place for old migrated wire. Structured
* message content is offloaded to the blob store through the operations that
* carry full messages. Bound at Agent scope.
*/
import { Disposable } from "#/_base/di";
@ -63,17 +63,6 @@ function messageContentBlobTarget<T extends readonly unknown[]>(
};
}
function toSystemReminderMessage(message: ContextMessage): ContextMessage {
return {
...message,
content: message.content.map((part) =>
part.type === 'text'
? { ...part, text: toSystemReminderText(part.text) }
: part,
),
};
}
function toSystemReminderText(content: string): string {
const trimmed = content.trim();
if (trimmed.startsWith('<system-reminder>') && trimmed.endsWith('</system-reminder>')) {
@ -82,6 +71,20 @@ function toSystemReminderText(content: string): string {
return `<system-reminder>\n${trimmed}\n</system-reminder>`;
}
function toSystemReminderMessage(content: string, origin: PromptOrigin): ContextMessage {
return ensureMessageId({
role: 'user',
content: [
{
type: 'text',
text: toSystemReminderText(content),
},
],
toolCalls: [],
origin,
});
}
export class AgentContextOpsService extends Disposable implements IAgentContextOpsService {
declare readonly _serviceBrand: undefined;
@ -121,19 +124,15 @@ export class AgentContextOpsService extends Disposable implements IAgentContextO
this.appendSystemReminderOperation =
context.defineOperation<ContextAppendSystemReminderArgs>({
type: 'append_system_reminder',
apply: (splice, message) => {
splice(Number.POSITIVE_INFINITY, 0, [toSystemReminderMessage(message)]);
apply: (splice, message, origin) => {
splice(Number.POSITIVE_INFINITY, 0, [toSystemReminderMessage(message, origin)]);
},
replay: (replay, message) => {
replay.push({ type: 'message', message: toSystemReminderMessage(message) });
replay: (replay, message, origin) => {
replay.push({
type: 'message',
message: toSystemReminderMessage(message, origin),
});
},
blobs: ([message]) => [
messageContentBlobTarget<ContextAppendSystemReminderArgs>(
message,
(_current, next) => [next],
([current]) => current,
),
],
});
this.removeOperation = context.defineOperation<ContextRemoveArgs>({
@ -187,19 +186,8 @@ export class AgentContextOpsService extends Disposable implements IAgentContextO
}
appendSystemReminder(content: string, origin: PromptOrigin): ContextMessage {
const message = ensureMessageId({
role: 'user',
content: [
{
type: 'text',
text: content.trim(),
},
],
toolCalls: [],
origin,
});
this.appendSystemReminderOperation(message);
return message;
this.appendSystemReminderOperation(content.trim(), origin);
return this.context.get().at(-1)!;
}
remove(removals: readonly ContextRemovalTarget[]): void {

View file

@ -310,6 +310,10 @@ describe('AgentWireRecordService persistence', () => {
expect(context.get()[0]?.content).toEqual([
{ type: 'text', text: '<system-reminder>\nRemember this.\n</system-reminder>' },
]);
expect(context.get()[0]?.origin).toEqual({
kind: 'injection',
variant: 'host',
});
const records = await readPersistedWireRecords(storage);
expect(records.map((record) => record.type)).toEqual([
@ -318,9 +322,34 @@ describe('AgentWireRecordService persistence', () => {
]);
const record = records[1] as {
type: 'context.append_system_reminder';
args: readonly [ContextMessage];
args: readonly [string, ContextMessage['origin']];
};
expect(record.args[0].content).toEqual([{ type: 'text', text: 'Remember this.' }]);
expect(record.args).toEqual(['Remember this.', { kind: 'injection', variant: 'host' }]);
});
it('restores string system reminder args with origin as wrapped context messages', async () => {
const { wire, context } = await createWireHarness();
await wire.restore([
{
type: 'metadata',
protocol_version: AGENT_WIRE_PROTOCOL_VERSION,
created_at: 1,
},
{
type: 'context.append_system_reminder',
args: ['Remember this.', { kind: 'injection', variant: 'host' }],
},
]);
const message = context.get()[0];
expect(message?.content).toEqual([
{ type: 'text', text: '<system-reminder>\nRemember this.\n</system-reminder>' },
]);
expect(message?.origin).toEqual({
kind: 'injection',
variant: 'host',
});
});
it('offloads large content part data URIs to blobsDir during append', async () => {