Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion packages/agent-core-v2/docs/state-manifest.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 (App: 0 keys · Workspace: 6 keys · Session: 18 keys · Agent: 69 keys)
// Index (App: 0 keys · Workspace: 6 keys · Session: 18 keys · Agent: 70 keys)
// App
// Workspace
// workspaceDirs.ephemeralDirs src/workspace/workspaceDirs/workspaceDirsService.ts
Expand Down Expand Up @@ -62,6 +62,7 @@
// agentsMdReminder.seeded src/agent/agentsMdReminder/agentsMdReminderService.ts
// contextInjector.isNewTurn src/agent/contextInjector/contextInjectorService.ts
// contextProjector.lastRepairSignature src/agent/contextProjector/contextProjectorService.ts
// dateChange.seed src/agent/dateChange/dateChangeService.ts
// externalHooks.stopHookContinuationUsed src/agent/externalHooks/externalHooksService.ts
// fullCompaction.activeTurnId src/agent/fullCompaction/fullCompactionService.ts
// fullCompaction.compactionCountInTurn src/agent/fullCompaction/fullCompactionService.ts
Expand Down Expand Up @@ -734,6 +735,12 @@ export interface AgentStateSnapshot {
readonly kind: 'injection';
readonly variant: string;
readonly ownerPromptId?: string;
readonly disclosure?: /* ContextInjectionDisclosure — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
readonly kind: 'date';
readonly renderGeneration: number;
readonly localDate: string;
readonly timeZone: string;
};
} | /* ShellCommandOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
readonly kind: 'shell_command';
readonly phase: 'input' | 'output';
Expand Down Expand Up @@ -858,6 +865,12 @@ export interface AgentStateSnapshot {
readonly kind: 'injection';
readonly variant: string;
readonly ownerPromptId?: string;
readonly disclosure?: /* ContextInjectionDisclosure — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
readonly kind: 'date';
readonly renderGeneration: number;
readonly localDate: string;
readonly timeZone: string;
};
} | /* ShellCommandOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
readonly kind: 'shell_command';
readonly phase: 'input' | 'output';
Expand Down Expand Up @@ -914,6 +927,12 @@ export interface AgentStateSnapshot {
readonly kind: 'injection';
readonly variant: string;
readonly ownerPromptId?: string;
readonly disclosure?: /* ContextInjectionDisclosure — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
readonly kind: 'date';
readonly renderGeneration: number;
readonly localDate: string;
readonly timeZone: string;
};
} | /* ShellCommandOrigin — packages/agent-core-v2/src/agent/contextMemory/types.ts */ {
readonly kind: 'shell_command';
readonly phase: 'input' | 'output';
Expand Down Expand Up @@ -980,6 +999,12 @@ export interface AgentStateSnapshot {
'contextInjector.isNewTurn': boolean;
// src/agent/contextProjector/contextProjectorService.ts
'contextProjector.lastRepairSignature': string | null;
// src/agent/dateChange/dateChangeService.ts
'dateChange.seed': /* DateDisclosure — packages/agent-core-v2/src/agent/dateChange/dateChangeService.ts */ {
readonly localDate: string;
readonly timeZone: string;
readonly renderGeneration: number;
} | undefined;
// src/agent/externalHooks/externalHooksService.ts
'externalHooks.stopHookContinuationUsed': boolean;
// src/agent/fullCompaction/fullCompactionService.ts
Expand Down
12 changes: 12 additions & 0 deletions packages/agent-core-v2/docs/wire-manifest.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ interface ConfigUpdatePayload {
/** ThinkingEffort */
thinkingLevel?: 'off' | 'on' | (string & {});
systemPrompt?: string;
/** EnvironmentDisclosureSnapshot */
environmentDisclosure?: {
cwd: string;
date: { disclosed: true, value: { localDate: string, timeZone: string } } | { disclosed: false };
};
renderGeneration?: number;
agentsMdPaths?: string[];
disallowedTools?: string[];
}
Expand Down Expand Up @@ -453,6 +459,12 @@ interface ProfileBindPayload {
/** ThinkingEffort */
thinkingEffort: 'off' | 'on' | (string & {});
systemPrompt: string;
/** EnvironmentDisclosureSnapshot */
environmentDisclosure?: {
cwd: string;
date: { disclosed: true, value: { localDate: string, timeZone: string } } | { disclosed: false };
};
renderGeneration?: number;
agentsMdPaths?: string[];
activeToolNames?: string[];
disallowedTools: string[];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
import { createDecorator } from "#/_base/di/instantiation";
import type { IDisposable } from "#/_base/di/lifecycle";
import type { ContentPart } from "#/kosong/contract/message";
import type { ContextInjectionDisclosure, ContextMessage } from '#/agent/contextMemory/types';

export interface ContextInjectionContext {
readonly injectedPositions: readonly number[];
readonly lastInjectedAt: number | null;
readonly lastInjection?: ContextMessage;
readonly lastDisclosure?: ContextInjectionDisclosure;
readonly isNewTurn: boolean;
}

export type ContextInjectionContent = string | readonly ContentPart[];

export interface ContextInjectionResult {
readonly content: ContextInjectionContent;
readonly disclosure?: ContextInjectionDisclosure;
}

export type ContextInjectionProvider = (
context: ContextInjectionContext,
) => ContextInjectionContent | undefined | Promise<ContextInjectionContent | undefined>;
) =>
| ContextInjectionContent
| ContextInjectionResult
| undefined
| Promise<ContextInjectionContent | ContextInjectionResult | undefined>;

export interface IAgentContextInjectorService {
readonly _serviceBrand: undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
*
* Injects registered context providers through `loop` and `systemReminder`,
* tracks their positions in `contextMemory` through `eventBus`, and reconciles
* those positions after `wire` restoration. The plain-data `isNewTurn` flag is
* registered into `agentState` (`IAgentStateService`) and read/written through
* it; `entries` stays a plain instance field (its values hold provider
* those positions after `wire` restoration. Each provider call receives the
* newest surviving injection of its own variant (`lastInjection`) and the
* typed disclosure recorded on it (`lastDisclosure`), so providers never read
* context layout or position indexes themselves. The plain-data `isNewTurn`
* flag is registered into `agentState` (`IAgentStateService`) and read/written
* through it; `entries` stays a plain instance field (its values hold provider
* functions, not plain data). Bound at Agent scope.
*/

Expand All @@ -22,7 +25,9 @@ import type { ContextMessage } from '#/agent/contextMemory/types';
import { IWireService } from '#/wire/wire';
import {
IAgentContextInjectorService,
type ContextInjectionContent,
type ContextInjectionProvider,
type ContextInjectionResult,
} from './contextInjector';

interface ContextInjectionEntry {
Expand Down Expand Up @@ -106,25 +111,41 @@ export class AgentContextInjectorService extends Disposable implements IAgentCon
private async inject(): Promise<void> {
const isNewTurn = this.isNewTurn;
this.isNewTurn = false;
const history = this.context.get();
for (const entry of this.entries) {
const injectedPositions: readonly number[] = [...entry.positions];
const lastInjectedAt = injectedPositions.at(-1) ?? null;
const lastInjection = lastInjectedAt === null ? undefined : history[lastInjectedAt];
const content = await entry.provider({
injectedPositions,
lastInjectedAt: injectedPositions.at(-1) ?? null,
lastInjectedAt,
lastInjection,
lastDisclosure:
lastInjection?.origin?.kind === 'injection'
? lastInjection.origin.disclosure
: undefined,
isNewTurn,
});
if (!this.entries.has(entry)) continue;
if (content === undefined) continue;
const origin = { kind: 'injection' as const, variant: entry.name };
if (typeof content === 'string') {
if (content.trim().length === 0) continue;
this.reminders.appendSystemReminder(content, origin);
const result: ContextInjectionResult =
typeof content === 'object' && content !== null && !Array.isArray(content)
? (content as ContextInjectionResult)
: { content: content as ContextInjectionContent };
const origin = {
kind: 'injection' as const,
variant: entry.name,
disclosure: result.disclosure,
};
if (typeof result.content === 'string') {
if (result.content.trim().length === 0) continue;
this.reminders.appendSystemReminder(result.content, origin);
continue;
}
if (content.length === 0) continue;
if (result.content.length === 0) continue;
this.context.append({
role: 'user',
content: [...content],
content: [...result.content],
toolCalls: [],
origin,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* `contextInjector` domain (L4) — disclosure-baseline helpers for reminder
* providers (currently `date_change`).
*
* A provider's baseline answers "what has the model already seen" from up to
* three sources, compared by render generation with ties won by the earlier
* argument: the typed disclosure on the provider's newest surviving
* in-context injection (newest in time on a tie, since the persisted floor
* never advances when a reminder fires), the persisted render-time floor, and
* a runtime seed recorded on first observation. Internal to the package; not
* part of the barrel export.
*/

import type { ContextInjectionDisclosure } from '#/agent/contextMemory/types';

export function disclosureOfKind<K extends ContextInjectionDisclosure['kind']>(
disclosure: ContextInjectionDisclosure | undefined,
kind: K,
): Extract<ContextInjectionDisclosure, { kind: K }> | undefined {
return disclosure?.kind === kind
? (disclosure as Extract<ContextInjectionDisclosure, { kind: K }>)
: undefined;
}

export function pickDisclosureBaseline<T extends { readonly renderGeneration: number }>(
...candidates: readonly (T | undefined)[]
): T | undefined {
let winner: T | undefined;
for (const candidate of candidates) {
if (
candidate !== undefined &&
(winner === undefined || candidate.renderGeneration > winner.renderGeneration)
) {
winner = candidate;
}
}
return winner;
}
8 changes: 8 additions & 0 deletions packages/agent-core-v2/src/agent/contextMemory/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,16 @@ export interface InjectionOrigin {
readonly kind: 'injection';
readonly variant: string;
readonly ownerPromptId?: string;
readonly disclosure?: ContextInjectionDisclosure;
}

export type ContextInjectionDisclosure = {
readonly kind: 'date';
readonly renderGeneration: number;
readonly localDate: string;
readonly timeZone: string;
};

export interface ShellCommandOrigin {
readonly kind: 'shell_command';
readonly phase: 'input' | 'output';
Expand Down
16 changes: 16 additions & 0 deletions packages/agent-core-v2/src/agent/dateChange/dateChange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* `dateChange` domain (L4) — `IAgentDateChangeService` contract.
*
* Defines the Agent-scope marker service that announces calendar-date changes
* through a `date_change` context-injection reminder when a session outlives
* the date rendered into its system prompt.
*/

import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';

export interface IAgentDateChangeService {
readonly _serviceBrand: undefined;
}

export const IAgentDateChangeService: ServiceIdentifier<IAgentDateChangeService> =
createDecorator<IAgentDateChangeService>('agentDateChangeService');
Loading
Loading