Skip to content

Commit 0092543

Browse files
committed
refactor(agent-core-v2): move dateChange to an agent runtime injection effect
1 parent 698bab7 commit 0092543

10 files changed

Lines changed: 236 additions & 176 deletions

File tree

packages/agent-core-v2/docs/state-manifest.d.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
// references become '(circular)', and class instances collapse to a '(ClassName)'
2828
// marker — the wire shape of an entry is the JSON projection of the type here.
2929
//
30-
// Index (App: 0 keys · Workspace: 6 keys · Session: 9 keys · Agent: 85 keys)
30+
// Index (App: 0 keys · Workspace: 6 keys · Session: 9 keys · Agent: 84 keys)
3131
// App
3232
// Workspace
3333
// workspaceDirs.ephemeralDirs src/workspace/workspaceDirs/workspaceDirsService.ts
@@ -58,7 +58,6 @@
5858
// agentsMdReminder.seeded src/agent/agentsMdReminder/agentsMdReminderService.ts
5959
// contextMemory src/agent/contextMemory/contextOps.ts
6060
// contextProjector.lastRepairSignature src/agent/contextProjector/contextProjectorService.ts
61-
// dateChange.seed src/features/dateChange/dateChangeService.ts
6261
// dynamic_workflow src/features/dynamic_workflow/dynamicWorkflowOps.ts
6362
// externalHooks.stopHookContinuationUsed src/features/externalHooks/agent/agentExternalHooksService.ts
6463
// fullCompaction src/agent/fullCompaction/compactionOps.ts
@@ -1448,12 +1447,6 @@ export interface AgentStateSnapshot {
14481447
readonly parameters: Record<string, unknown>;
14491448
readonly disclosure?: 'deferred' | 'inline';
14501449
}>;
1451-
// src/features/dateChange/dateChangeService.ts
1452-
'dateChange.seed': /* DateDisclosure — packages/agent-core-v2/src/features/dateChange/dateChangeService.ts */ {
1453-
readonly localDate: string;
1454-
readonly timeZone: string;
1455-
readonly renderGeneration: number;
1456-
} | undefined;
14571450
// src/features/dynamic_workflow/dynamicWorkflowOps.ts
14581451
// replayable · durable — folds: DynamicWorkflowModeEnter, DynamicWorkflowModeExit
14591452
'dynamic_workflow': 'task' | 'tool' | 'manual' | null;

packages/agent-core-v2/src/agent/runtime/agentRuntimeSet.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,11 @@ export class AgentRuntimeSet {
6868
restored: false,
6969
};
7070
this.entries.set(descriptor.id, entry);
71-
if (descriptor.durable !== undefined && this.durableHost !== undefined) {
71+
if (this.durableHost === undefined) return;
72+
if (descriptor.durable !== undefined) {
7273
this.attachDurableEntry(entry, this.durableHost);
74+
} else if (descriptor.eager === true) {
75+
this.runtime(entry);
7376
}
7477
}
7578

@@ -103,7 +106,10 @@ export class AgentRuntimeSet {
103106
if (this.closed) return;
104107
this.durableHost = host;
105108
for (const entry of this.entries.values()) {
106-
if (entry.descriptor.durable === undefined) continue;
109+
if (entry.descriptor.durable === undefined) {
110+
if (entry.descriptor.eager === true) this.runtime(entry);
111+
continue;
112+
}
107113
this.attachDurableEntry(entry, host);
108114
}
109115
}
Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
1-
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
2-
31
export interface DateInjectionDisclosure {
42
readonly kind: 'date';
53
readonly renderGeneration: number;
64
readonly localDate: string;
75
readonly timeZone: string;
86
}
9-
10-
export interface IAgentDateChangeService {
11-
readonly _serviceBrand: undefined;
12-
}
13-
14-
export const IAgentDateChangeService: ServiceIdentifier<IAgentDateChangeService> =
15-
createDecorator<IAgentDateChangeService>('agentDateChangeService');
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { assign, fromCallback, setup } from 'xstate';
2+
3+
import { IAgentProfileService } from '#/agent/profile/profile';
4+
import {
5+
defineAgentRuntimeContract,
6+
defineAgentRuntimeProvider,
7+
type AgentRuntimeContext,
8+
type AgentRuntimeRestoreEvent,
9+
} from '#/agent/runtime/agentRuntime';
10+
import { AgentReminder } from '#/features/reminder/reminderAgentRuntime';
11+
import type {
12+
ContextInjectionContext,
13+
ContextInjectionResult,
14+
} from '#/features/reminder/types';
15+
import { IHostClock } from '#/os/interface/hostClock';
16+
import { IAgentLifecycleService } from '#/session/agentLifecycle/agentLifecycle';
17+
import { ISessionContext } from '#/session/sessionContext/sessionContext';
18+
19+
import type { DateInjectionDisclosure } from './dateChange';
20+
import { pickDisclosureBaseline } from './disclosureBaseline';
21+
22+
const DATE_CHANGE_INJECTION_VARIANT = 'date_change';
23+
24+
interface DateDisclosure {
25+
readonly localDate: string;
26+
readonly timeZone: string;
27+
readonly renderGeneration: number;
28+
}
29+
30+
interface DateChangeActorContext {
31+
readonly seed: DateDisclosure | undefined;
32+
readonly runtime: AgentRuntimeContext<null>;
33+
}
34+
35+
interface DateChangeDiscloseEvent {
36+
readonly type: 'dateChange.disclose';
37+
readonly seed: DateDisclosure;
38+
}
39+
40+
function currentDateDisclosure(clock: IHostClock): Omit<DateDisclosure, 'renderGeneration'> {
41+
const date = clock.now();
42+
const timeZone = clock.timeZone();
43+
const parts = new Intl.DateTimeFormat('en-US', {
44+
timeZone,
45+
year: 'numeric',
46+
month: '2-digit',
47+
day: '2-digit',
48+
}).formatToParts(date);
49+
const part = (type: Intl.DateTimeFormatPartTypes): string =>
50+
parts.find((candidate) => candidate.type === type)?.value ?? '';
51+
return {
52+
localDate: `${part('year')}-${part('month')}-${part('day')}`,
53+
timeZone,
54+
};
55+
}
56+
57+
const dateChangeInjection = fromCallback(({
58+
input,
59+
}: {
60+
input: {
61+
readonly runtime: AgentRuntimeContext<null>;
62+
};
63+
}) => {
64+
const runtime = input.runtime;
65+
const reminder = runtime
66+
.get(IAgentLifecycleService)
67+
.resolve(runtime.agent, AgentReminder);
68+
const profile = runtime.get(IAgentProfileService);
69+
const clock = runtime.get(IHostClock);
70+
const sessionContext = runtime.get(ISessionContext);
71+
const belongsToCurrentCwd = (): boolean => {
72+
const environment = profile.data().environmentDisclosure;
73+
return !(
74+
environment !== undefined &&
75+
environment.cwd !== '' &&
76+
environment.cwd !== sessionContext.cwd
77+
);
78+
};
79+
const dateFromProfile = (): DateDisclosure | undefined => {
80+
if (!belongsToCurrentCwd()) return undefined;
81+
const profileData = profile.data();
82+
const date = profileData.environmentDisclosure?.date;
83+
if (!date?.disclosed) return undefined;
84+
return {
85+
...date.value,
86+
renderGeneration: profileData.renderGeneration ?? 0,
87+
};
88+
};
89+
const registration = reminder.register<DateInjectionDisclosure>(
90+
DATE_CHANGE_INJECTION_VARIANT,
91+
({
92+
lastDisclosure,
93+
}: ContextInjectionContext<DateInjectionDisclosure>): ContextInjectionResult<DateInjectionDisclosure> | undefined => {
94+
const profileData = profile.data();
95+
if (!belongsToCurrentCwd()) return undefined;
96+
const renderGeneration = profileData.renderGeneration ?? 0;
97+
const current = currentDateDisclosure(clock);
98+
const profileDate = dateFromProfile();
99+
const seed = runtime.getLogicState<DateChangeActorContext>().seed;
100+
const baseline = pickDisclosureBaseline<DateDisclosure>(
101+
lastDisclosure,
102+
profileDate,
103+
seed,
104+
);
105+
if (baseline !== undefined && baseline.localDate !== current.localDate) {
106+
return {
107+
content: `The date has changed. Today's date is now ${current.localDate}. Rely on this reminder over any earlier date statement for the current date. DO NOT mention this to the user explicitly.`,
108+
disclosure: {
109+
kind: 'date',
110+
renderGeneration,
111+
localDate: current.localDate,
112+
timeZone: current.timeZone,
113+
},
114+
};
115+
}
116+
if (lastDisclosure !== undefined || profileDate !== undefined) return undefined;
117+
if (seed === undefined) {
118+
runtime.send({
119+
type: 'dateChange.disclose',
120+
seed: { ...current, renderGeneration },
121+
});
122+
}
123+
return {
124+
content: `Today's date is ${current.localDate}. The current date is restated in a reminder whenever it changes; rely on the latest such reminder for the current date. DO NOT mention this to the user explicitly.`,
125+
disclosure: {
126+
kind: 'date',
127+
renderGeneration,
128+
localDate: current.localDate,
129+
timeZone: current.timeZone,
130+
},
131+
};
132+
},
133+
);
134+
return () => { registration.dispose(); };
135+
});
136+
137+
const dateChangeActorLogic = setup({
138+
types: {} as {
139+
context: DateChangeActorContext;
140+
input: AgentRuntimeContext<null>;
141+
events: DateChangeDiscloseEvent | AgentRuntimeRestoreEvent;
142+
},
143+
actors: { dateChangeInjection },
144+
}).createMachine({
145+
context: ({ input }) => ({ seed: undefined, runtime: input }),
146+
initial: 'beforeRestore',
147+
states: {
148+
beforeRestore: {
149+
on: { 'runtime.restore': 'active' },
150+
},
151+
active: {
152+
invoke: {
153+
src: 'dateChangeInjection',
154+
input: ({ context }) => ({ runtime: context.runtime }),
155+
},
156+
},
157+
},
158+
on: {
159+
'dateChange.disclose': {
160+
actions: assign({ seed: ({ event }) => event.seed }),
161+
},
162+
},
163+
});
164+
165+
export class DateChangeRuntime {}
166+
167+
export const AgentDateChange = defineAgentRuntimeContract<DateChangeRuntime>('dateChange');
168+
169+
export const dateChangeAgentRuntimeProvider = defineAgentRuntimeProvider<null, DateChangeRuntime>(
170+
AgentDateChange,
171+
{
172+
id: 'dateChange',
173+
logic: dateChangeActorLogic,
174+
eager: true,
175+
createApi: () => new DateChangeRuntime(),
176+
},
177+
);

packages/agent-core-v2/src/features/dateChange/dateChangeFeature.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
1-
import { ScopeActivation } from '#/_base/di/instantiation';
21
import { Feature } from '#/features/feature';
32
import { registerFeature } from '#/features/featureRegistry';
43

5-
import { IAgentDateChangeService } from './dateChange';
6-
import { AgentDateChangeService } from './dateChangeService';
4+
import { dateChangeAgentRuntimeProvider } from './dateChangeAgentRuntime';
75

86
export class DateChangeFeature extends Feature {
97
static override readonly name = 'dateChange';
108

119
constructor() {
1210
super();
13-
this.contributeAgentService(IAgentDateChangeService, AgentDateChangeService, {
14-
activation: ScopeActivation.OnScopeCreated,
15-
});
11+
this.contributeAgentRuntime(dateChangeAgentRuntimeProvider);
1612
}
1713
}
1814

0 commit comments

Comments
 (0)