refactor(session-projection): clarify retained handle names
This commit is contained in:
1 parent
d7a1aa839a
commit
491fa4a394
20 files changed
+63
-63
No files matched your search
@@ -170,7 +170,7 @@ export async function inspectApiSession(
|
||||
|
||||
/** Owns every operation that may create, resume, or configure a Web Agent. */
|
||||
export class ApiSessionAgentController {
|
||||
private modelSelection!: ProjectionHandle<ModelSelectionProjectionState>
|
||||
private modelSelectionProjectionHandle!: ProjectionHandle<ModelSelectionProjectionState>
|
||||
private readonly resumes = new Map<SessionId, Promise<Agent>>()
|
||||
private readonly claims = new Map<SessionId, Promise<void>>()
|
||||
private readonly selections = new WeakMap<Agent, InstalledSelection>()
|
||||
@@ -185,7 +185,7 @@ export class ApiSessionAgentController {
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
const ctx = this.ctx
|
||||
this.modelSelection = await ctx.sessionProjections.register(modelSelectionProjection)
|
||||
this.modelSelectionProjectionHandle = await ctx.sessionProjections.register(modelSelectionProjection)
|
||||
ctx.typert.lookups.configure('agent', async (sessionId: SessionId) => {
|
||||
const found = await this.resolveAgent(sessionId)
|
||||
if ('error' in found) throw found.error
|
||||
@@ -300,7 +300,7 @@ export class ApiSessionAgentController {
|
||||
selectionFor(agent: Agent): InstalledSelection {
|
||||
const installed = this.selections.get(agent)
|
||||
if (installed !== undefined) return installed
|
||||
const projectionState = this.modelSelection.stateOf(agent.session)
|
||||
const projectionState = this.modelSelectionProjectionHandle.stateOf(agent.session)
|
||||
let picked = projectionState.pending === null
|
||||
? undefined
|
||||
: agentModelSelection(projectionState.pending)
|
||||
|
||||
@@ -127,7 +127,7 @@ const ADMITS_NOTHING: LegacyPrompt = { source: null, web: null }
|
||||
* @returns after the projection reader and prompt registrations are ready.
|
||||
*/
|
||||
export async function apply(ctx: Context): Promise<void> {
|
||||
const legacyPrompt = await ctx.sessionProjections.register({
|
||||
const legacyPromptProjectionHandle = await ctx.sessionProjections.register({
|
||||
key: LEGACY_PROMPT,
|
||||
stateVersion: 1,
|
||||
stateSchema: legacyPromptSchema,
|
||||
@@ -136,7 +136,7 @@ export async function apply(ctx: Context): Promise<void> {
|
||||
})
|
||||
const admitted = (context: AssembleContext): LegacyPrompt => {
|
||||
const session = context.agent?.session
|
||||
return session === undefined ? ADMITS_NOTHING : legacyPrompt.stateOf(session)
|
||||
return session === undefined ? ADMITS_NOTHING : legacyPromptProjectionHandle.stateOf(session)
|
||||
}
|
||||
ctx.systemPrompt.section({
|
||||
name: HARNESS_SOURCE,
|
||||
|
||||
@@ -155,7 +155,7 @@ export async function apply(ctx: Context, config: Config): Promise<void> {
|
||||
return created
|
||||
}
|
||||
|
||||
const projection = await ctx.sessionProjections.register({
|
||||
const timeContextProjectionHandle = await ctx.sessionProjections.register({
|
||||
key: 'timeContext',
|
||||
stateVersion: 2,
|
||||
stateSchema: timeContextStateSchema,
|
||||
@@ -190,7 +190,7 @@ export async function apply(ctx: Context, config: Config): Promise<void> {
|
||||
const decision = await next()
|
||||
if (decision.kind === 'reject' || signal.aborted) return decision
|
||||
const now = Date.now()
|
||||
const state = projection.stateOf(agent.session)
|
||||
const state = timeContextProjectionHandle.stateOf(agent.session)
|
||||
if (refreshIntervalMs > 0) {
|
||||
const lastInjection = state.lastInjectionTime
|
||||
if (lastInjection != null
|
||||
|
||||
@@ -308,7 +308,7 @@ export class AgentLoop extends Service implements AgentFactory {
|
||||
private readonly ownership: FactoryOwnership
|
||||
/** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
|
||||
private readonly runtime: { ctx: Context }
|
||||
private inbox!: ProjectionHandle<InboxState>
|
||||
private inboxProjectionHandle!: ProjectionHandle<InboxState>
|
||||
|
||||
constructor(ctx: Context, config: Config) {
|
||||
super(ctx, 'agentLoop')
|
||||
@@ -327,7 +327,7 @@ export class AgentLoop extends Service implements AgentFactory {
|
||||
const ownership = this.ownership
|
||||
// The inbox remains readable until agent teardown finishes.
|
||||
ctx.effect(() => () => ownership.dispose(), 'agentLoop.transactions()')
|
||||
this.inbox = await ctx.sessionProjections.register(inboxProjectionDefinition)
|
||||
this.inboxProjectionHandle = await ctx.sessionProjections.register(inboxProjectionDefinition)
|
||||
ctx.effect(() => ctx.agents.setFactory(this), 'agentLoop.setFactory()')
|
||||
ctx.systemPrompt.variable('provider', context => context.agent?.options.provider)
|
||||
ctx.systemPrompt.variable('model', context => context.agent?.options.model)
|
||||
@@ -543,10 +543,10 @@ export class AgentLoop extends Service implements AgentFactory {
|
||||
}
|
||||
}
|
||||
const untrack = this.ownership.track(dispose)
|
||||
const inbox = this.inbox
|
||||
const inboxProjectionHandle = this.inboxProjectionHandle
|
||||
try {
|
||||
unfollowOwner = ownerCtx.effect(function* () {
|
||||
const created = machine = new ReactLoopAgent(loopCtx, id, options, session, () => inbox.stateOf(session), lastTurn)
|
||||
const created = machine = new ReactLoopAgent(loopCtx, id, options, session, () => inboxProjectionHandle.stateOf(session), lastTurn)
|
||||
machineReady.resolve()
|
||||
// Adopt the scope on its original fiber so it cannot unload before teardown.
|
||||
// A loop already unloading during minting owns the provisional scope itself.
|
||||
|
||||
@@ -247,7 +247,7 @@ interface FactorySlot {
|
||||
export class AgentRegistry extends Service {
|
||||
static inject = ['sessionProjections']
|
||||
|
||||
private turnBoundaryProjection!: ProjectionHandle<TurnBoundaryProjection>
|
||||
private turnBoundaryProjectionHandle!: ProjectionHandle<TurnBoundaryProjection>
|
||||
private store = new Map<SessionId, AgentEntry>()
|
||||
private factory: FactorySlot | undefined
|
||||
private readonly initiators = new AsyncLocalStorage<Agent | undefined>()
|
||||
@@ -290,7 +290,7 @@ export class AgentRegistry extends Service {
|
||||
}
|
||||
|
||||
protected async [Service.init](): Promise<void> {
|
||||
this.turnBoundaryProjection = await this.ctx.sessionProjections.register(turnBoundaryProjectionDefinition)
|
||||
this.turnBoundaryProjectionHandle = await this.ctx.sessionProjections.register(turnBoundaryProjectionDefinition)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,7 +299,7 @@ export class AgentRegistry extends Service {
|
||||
* @returns the current turn and step positions; do not mutate the returned value.
|
||||
*/
|
||||
turnBoundaryOf(session: Session): TurnBoundaryProjection {
|
||||
return this.turnBoundaryProjection.stateOf(session)
|
||||
return this.turnBoundaryProjectionHandle.stateOf(session)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -90,8 +90,8 @@ export class TeamService extends Service {
|
||||
|
||||
protected async [Service.init](): Promise<void> {
|
||||
const ctx = this.ctx
|
||||
const projection = await ctx.sessionProjections.register(teamProjectionDefinition)
|
||||
this.journal = new TeamJournal(ctx, projection, (root) => { this.activity.notify(TeamId(root.id)) })
|
||||
const teamProjectionHandle = await ctx.sessionProjections.register(teamProjectionDefinition)
|
||||
this.journal = new TeamJournal(ctx, teamProjectionHandle, (root) => { this.activity.notify(TeamId(root.id)) })
|
||||
this.roster = new TeamRoster(ctx, this.journal, this.lifecycle, this.config.maxMembers)
|
||||
this.tasks = new TeamTaskBoard(this.journal, this.config.maxTasks)
|
||||
ctx.effect(() => () => this.disposeRuntime(), 'agentTeams.runtimeLifecycle()')
|
||||
|
||||
@@ -15,12 +15,12 @@ export class TeamJournal {
|
||||
|
||||
/**
|
||||
* @param ctx - Team service context with the injected Session service.
|
||||
* @param projection - Team state registration owned by the Team service.
|
||||
* @param teamProjectionHandle - Team state registration owned by the Team service.
|
||||
* @param onCommit - synchronous notification after the Team event flush succeeds.
|
||||
*/
|
||||
constructor(
|
||||
private readonly ctx: Context,
|
||||
private readonly projection: ProjectionHandle<TeamProjectionState>,
|
||||
private readonly teamProjectionHandle: ProjectionHandle<TeamProjectionState>,
|
||||
private readonly onCommit: (root: Agent) => void,
|
||||
) {}
|
||||
|
||||
@@ -31,7 +31,7 @@ export class TeamJournal {
|
||||
* @throws when the durable Team projection has failed.
|
||||
*/
|
||||
state(root: Agent): TeamState {
|
||||
const projection = this.projection.stateOf(root.session)
|
||||
const projection = this.teamProjectionHandle.stateOf(root.session)
|
||||
if (projection.failure !== undefined) throw new Error(projection.failure)
|
||||
return projection
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ export class GoalService extends TypertRemoteService {
|
||||
private readonly resolved: ResolvedConfig
|
||||
private readonly runtimeStates = new WeakMap<Session, GoalRuntimeState>()
|
||||
|
||||
private projection!: ProjectionHandle<GoalProjectionState>
|
||||
private goalProjectionHandle!: ProjectionHandle<GoalProjectionState>
|
||||
|
||||
constructor(ctx: Context, config: Config = {}) {
|
||||
super(ctx, 'goals')
|
||||
@@ -258,7 +258,7 @@ export class GoalService extends TypertRemoteService {
|
||||
|
||||
protected async [Service.init](): Promise<void> {
|
||||
const ctx = this.ctx
|
||||
this.projection = await ctx.sessionProjections.register(goalProjectionDefinition)
|
||||
this.goalProjectionHandle = await ctx.sessionProjections.register(goalProjectionDefinition)
|
||||
ctx.on('agent/created', ({ agent }) => {
|
||||
this.setActivation(agent.session, 'disarmed')
|
||||
})
|
||||
@@ -480,7 +480,7 @@ export class GoalService extends TypertRemoteService {
|
||||
|
||||
/** Read the current durable projection maintained by the registry. */
|
||||
private state(session: Session): GoalProjection | null {
|
||||
const state = this.projection.stateOf(session)
|
||||
const state = this.goalProjectionHandle.stateOf(session)
|
||||
if (state.failure !== null) throw new Error(state.failure)
|
||||
return state.current
|
||||
}
|
||||
@@ -502,7 +502,7 @@ export class GoalService extends TypertRemoteService {
|
||||
const runtime = this.runtimeState(session)
|
||||
if (runtime.activation === activation) return
|
||||
runtime.activation = activation
|
||||
const state = this.projection.stateOf(session)
|
||||
const state = this.goalProjectionHandle.stateOf(session)
|
||||
if (state.failure !== null) return
|
||||
const goal = this.view(state.current, runtime)
|
||||
this.ctx.emit('goal/activation-changed', {
|
||||
|
||||
@@ -204,7 +204,7 @@ export class PermissionPresetService extends TypertRemoteService {
|
||||
private autoAdmit: (() => void) | undefined
|
||||
private defaultSettings: () => PermissionSettings
|
||||
|
||||
private projection!: ProjectionHandle<PermissionProjectionState>
|
||||
private permissionProjectionHandle!: ProjectionHandle<PermissionProjectionState>
|
||||
|
||||
constructor(ctx: Context, config: Config) {
|
||||
super(ctx, 'permissionPresets')
|
||||
@@ -239,7 +239,7 @@ export class PermissionPresetService extends TypertRemoteService {
|
||||
const selectionSchema = zod.object({
|
||||
currentValue: zod.string().min(1),
|
||||
}) as zod.ZodType<PermissionSelection>
|
||||
this.projection = await ctx.sessionProjections.register({
|
||||
this.permissionProjectionHandle = await ctx.sessionProjections.register({
|
||||
key: 'permissions',
|
||||
stateVersion: 2,
|
||||
stateSchema: permissionStateSchema,
|
||||
@@ -331,7 +331,7 @@ export class PermissionPresetService extends TypertRemoteService {
|
||||
}
|
||||
|
||||
private permissionState(session: Session): PermissionProjectionState {
|
||||
return this.projection.stateOf(session)
|
||||
return this.permissionProjectionHandle.stateOf(session)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -78,7 +78,7 @@ type QueuedReply = { messageId: MessageId; claimedTurn?: number }
|
||||
/** `ctx.userQuestions`: validation plus the scoped answerer waterfall. */
|
||||
export class UserQuestionService extends TypertRemoteService {
|
||||
static Config = z.object({})
|
||||
private projection: ProjectionHandle<UserQuestionProjectionState> | undefined
|
||||
private userQuestionProjectionHandle: ProjectionHandle<UserQuestionProjectionState> | undefined
|
||||
private readonly waits = new Map<Agent, Map<ToolCallId, TimedQuestionWait>>()
|
||||
private readonly queuedReplies = new WeakMap<Session, Map<ToolCallId, QueuedReply>>()
|
||||
|
||||
@@ -128,8 +128,8 @@ export class UserQuestionService extends TypertRemoteService {
|
||||
|
||||
protected async [Service.init](): Promise<void> {
|
||||
const binding = this.ctx.inject(['sessionProjections'], async (ctx) => {
|
||||
this.projection = await ctx.sessionProjections.register(userQuestionProjectionDefinition)
|
||||
return () => { this.projection = undefined }
|
||||
this.userQuestionProjectionHandle = await ctx.sessionProjections.register(userQuestionProjectionDefinition)
|
||||
return () => { this.userQuestionProjectionHandle = undefined }
|
||||
})
|
||||
if (this.ctx.get('sessionProjections') !== undefined) await binding
|
||||
}
|
||||
@@ -150,7 +150,7 @@ export class UserQuestionService extends TypertRemoteService {
|
||||
}
|
||||
|
||||
private continued(agent: Agent): readonly PendingUserQuestion[] {
|
||||
return this.projection?.stateOf(agent.session).questions.active.filter(question => question.state === 'continued') ?? []
|
||||
return this.userQuestionProjectionHandle?.stateOf(agent.session).questions.active.filter(question => question.state === 'continued') ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -123,7 +123,7 @@ declare module '@deepseek-ai/dsh-session-projection/types' {
|
||||
*/
|
||||
export async function apply(ctx: Context, config: Config = {}, internals: RetryInternals = {}): Promise<void> {
|
||||
validateConfig(config)
|
||||
const projection = await ctx.sessionProjections.register({
|
||||
const llmRetryProjectionHandle = await ctx.sessionProjections.register({
|
||||
key: 'llmRetry',
|
||||
stateVersion: 1,
|
||||
stateSchema: llmRetryStateSchema,
|
||||
@@ -218,7 +218,7 @@ export async function apply(ctx: Context, config: Config = {}, internals: RetryI
|
||||
}
|
||||
|
||||
const policyKey = retryPolicyKey(policy)
|
||||
const retryState = projection.stateOf(agent.session)
|
||||
const retryState = llmRetryProjectionHandle.stateOf(agent.session)
|
||||
const previous = retryState[retryStateKey(provider, policyKey)]
|
||||
const previousRetry = previous?.retry ?? 0
|
||||
if (policy.mode === 'normal' && previousRetry >= policy.maxRetries) return next()
|
||||
|
||||
@@ -177,7 +177,7 @@ export class PlanModeController extends Service {
|
||||
|
||||
/** Validated deployment-owned guidance. */
|
||||
private readonly section: string
|
||||
private projection!: ProjectionHandle<PlanUnitState>
|
||||
private planProjectionHandle!: ProjectionHandle<PlanUnitState>
|
||||
|
||||
/**
|
||||
* Latest selection per session awaiting the next accepted in-turn pre-step.
|
||||
@@ -193,7 +193,7 @@ export class PlanModeController extends Service {
|
||||
|
||||
protected async [Service.init](): Promise<void> {
|
||||
const ctx = this.ctx
|
||||
this.projection = await ctx.sessionProjections.register(planProjectionDefinition)
|
||||
this.planProjectionHandle = await ctx.sessionProjections.register(planProjectionDefinition)
|
||||
let disposed = false
|
||||
// Pre-step is outside Session.append publication, so it can append the
|
||||
// log-only mode event inside an open turn without re-entering the session.
|
||||
@@ -389,7 +389,7 @@ export class PlanModeController extends Service {
|
||||
}
|
||||
|
||||
private planState(session: Session): PlanUnitState {
|
||||
return this.projection.stateOf(session)
|
||||
return this.planProjectionHandle.stateOf(session)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,7 +57,7 @@ export class AgentPresetRegistry extends TypertRemoteService {
|
||||
default: z.string().required(),
|
||||
selectedDefault: z.string().volatile(),
|
||||
})
|
||||
private agentPreset!: ProjectionHandle<string | null>
|
||||
private agentPresetProjectionHandle!: ProjectionHandle<string | null>
|
||||
private readonly owner: Context
|
||||
private readonly definitions = new Map<string, Definition>()
|
||||
private readonly generations = new Map<ScopeKey, Generation>()
|
||||
@@ -71,7 +71,7 @@ export class AgentPresetRegistry extends TypertRemoteService {
|
||||
|
||||
protected async [Service.init](): Promise<void> {
|
||||
const ctx = this.owner
|
||||
this.agentPreset = await ctx.sessionProjections.register(agentPresetProjectionDefinition)
|
||||
this.agentPresetProjectionHandle = await ctx.sessionProjections.register(agentPresetProjectionDefinition)
|
||||
ctx.inject(['settings'], (child) => { child.effect(() => child.settings.configure({ auto: false }, ctx.fiber)) })
|
||||
ctx.on('session/event', (session, event) => {
|
||||
if (event.type === 'agent-preset/selected') ctx.emit('agent-preset/selected', session.id, event.data.agentPreset)
|
||||
@@ -87,7 +87,7 @@ export class AgentPresetRegistry extends TypertRemoteService {
|
||||
* @returns the recorded preset, or null when none was selected.
|
||||
*/
|
||||
presetForSession(session: Session): string | null {
|
||||
return this.agentPreset.stateOf(session)
|
||||
return this.agentPresetProjectionHandle.stateOf(session)
|
||||
}
|
||||
|
||||
/** Register and eagerly load a definition; activation failure remains visible in the roster.
|
||||
@@ -370,7 +370,7 @@ export class AgentPresetRegistry extends TypertRemoteService {
|
||||
}
|
||||
const current = this.definitions.get(agentPreset)?.generation
|
||||
if (current !== undefined && this.generationFor(agent.ctx) === current
|
||||
&& this.agentPreset.stateOf(agent.session) === agentPreset) return agentPreset
|
||||
&& this.agentPresetProjectionHandle.stateOf(agent.session) === agentPreset) return agentPreset
|
||||
const preset = await this.recompose(agent.ctx, agentPreset)
|
||||
agent.session.append('agent-preset/selected', { agentPreset: preset.id })
|
||||
return preset.id
|
||||
|
||||
@@ -122,7 +122,7 @@ export class SandboxPolicyService extends Service {
|
||||
readonly defaultMode: SandboxMode
|
||||
/** The absolute `workspace-write` fallback root for calls without a session cwd. */
|
||||
readonly workspaceRoot: string
|
||||
private projection!: ProjectionHandle<SandboxMode | null>
|
||||
private sandboxModeProjectionHandle!: ProjectionHandle<SandboxMode | null>
|
||||
|
||||
constructor(ctx: Context, config: Config) {
|
||||
super(ctx, 'sandboxPolicy')
|
||||
@@ -135,7 +135,7 @@ export class SandboxPolicyService extends Service {
|
||||
|
||||
protected async [Service.init](): Promise<void> {
|
||||
const ctx = this.ctx
|
||||
this.projection = await ctx.sessionProjections.register({
|
||||
this.sandboxModeProjectionHandle = await ctx.sessionProjections.register({
|
||||
key: 'sandboxMode',
|
||||
stateVersion: 1,
|
||||
stateSchema: sandboxModeStateSchema,
|
||||
@@ -181,7 +181,7 @@ export class SandboxPolicyService extends Service {
|
||||
* @returns the last logged mode, or `undefined` without one.
|
||||
*/
|
||||
overrideOf(session: Session): SandboxMode | undefined {
|
||||
return this.projection.stateOf(session) ?? undefined
|
||||
return this.sandboxModeProjectionHandle.stateOf(session) ?? undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,8 +58,8 @@ const definition = {
|
||||
`await register(definition)` installs the unit and returns a handle with synchronous `stateOf(session)` reads and `dispose(): Promise<void>`. Await `dispose()` to release the registration; repeated calls are safe. Registrants with the same key and `stateVersion` share cells; incompatible or invalid versions reject registration. Registration belongs to the calling fiber. The last unregister removes the unit from global event drives, snapshots, checkpoints, and change notifications. Existing handles remain readable: each retains its original unit and folds new Session events on demand, even if another version later registers the same key. Reading a handle does not initialize unrelated units or register it again. Carriers use `snapshot(session)` for a consistent client-view cut and `onChanged(listener)` for changes to registered units.
|
||||
|
||||
```text
|
||||
const projection = await ctx.sessionProjections.register(definition)
|
||||
const state = projection.stateOf(session)
|
||||
const todoProjectionHandle = await ctx.sessionProjections.register(definition)
|
||||
const state = todoProjectionHandle.stateOf(session)
|
||||
const { asOfSeq, values } = ctx.sessionProjections.snapshot(session)
|
||||
```
|
||||
|
||||
|
||||
@@ -58,8 +58,8 @@ const definition = {
|
||||
`await register(definition)` 安装单元,返回提供同步 `stateOf(session)` 读取和 `dispose(): Promise<void>` 的 handle。等待 `dispose()` 完成即可释放注册;重复调用是安全的。具有相同 key 和 `stateVersion` 的注册方共享 cell;版本不兼容或非法时注册会拒绝。注册归调用方 fiber 所有。最后一个注册方注销后,单元退出全局事件驱动、快照、检查点和变更通知。已有 handle 仍可读取:它保留原有单元,按需折叠新增的 Session 事件,即使同一 key 后来注册了其他版本也不受影响。读取 handle 不会初始化无关单元,也不会重新注册。载体用 `snapshot(session)` 读取一致的客户端视图切面,用 `onChanged(listener)` 订阅已注册单元的变更。
|
||||
|
||||
```text
|
||||
const projection = await ctx.sessionProjections.register(definition)
|
||||
const state = projection.stateOf(session)
|
||||
const todoProjectionHandle = await ctx.sessionProjections.register(definition)
|
||||
const state = todoProjectionHandle.stateOf(session)
|
||||
const { asOfSeq, values } = ctx.sessionProjections.snapshot(session)
|
||||
```
|
||||
|
||||
|
||||
@@ -306,7 +306,7 @@ export class SessionTitleService extends Service {
|
||||
maxTitleBytes: z.number().step(1).min(1).required(),
|
||||
})
|
||||
|
||||
private titleInput!: ProjectionHandle<TitleInputState>
|
||||
private titleInputProjectionHandle!: ProjectionHandle<TitleInputState>
|
||||
private readonly config: ResolvedConfig
|
||||
private readonly ownerFiber: Fiber
|
||||
private registration: ProviderRegistration | undefined
|
||||
@@ -347,7 +347,7 @@ export class SessionTitleService extends Service {
|
||||
|
||||
await ctx.sessionProjections.register(titleProjectionDefinition)
|
||||
|
||||
this.titleInput = await ctx.sessionProjections.register<'titleInput', TitleInputState>({
|
||||
this.titleInputProjectionHandle = await ctx.sessionProjections.register<'titleInput', TitleInputState>({
|
||||
key: 'titleInput',
|
||||
stateVersion: 3,
|
||||
stateSchema: titleInputStateSchema,
|
||||
@@ -734,7 +734,7 @@ export class SessionTitleService extends Service {
|
||||
}
|
||||
|
||||
private titleInputOf(session: Session): TitleInputState {
|
||||
return this.titleInput.stateOf(session)
|
||||
return this.titleInputProjectionHandle.stateOf(session)
|
||||
}
|
||||
|
||||
/** Queue detached service work and retain it through service disposal. */
|
||||
|
||||
@@ -72,7 +72,7 @@ export class WorkingDirectoryService extends Service {
|
||||
private readonly operations = new Set<Promise<unknown>>()
|
||||
private readonly lifecycle = new AbortController()
|
||||
|
||||
private projection!: ProjectionHandle<string | null>
|
||||
private workingDirectoryProjectionHandle!: ProjectionHandle<string | null>
|
||||
|
||||
constructor(ctx: Context, config: Config) {
|
||||
super(ctx, 'workingDirectory')
|
||||
@@ -84,7 +84,7 @@ export class WorkingDirectoryService extends Service {
|
||||
protected async [Service.init](): Promise<void> {
|
||||
const ctx = this.ctx
|
||||
const directorySchema = zod.string().min(1).refine(isAbsolute).nullable()
|
||||
this.projection = await ctx.sessionProjections.register({
|
||||
this.workingDirectoryProjectionHandle = await ctx.sessionProjections.register({
|
||||
key: 'workingDirectory',
|
||||
stateVersion: 1,
|
||||
stateSchema: directorySchema,
|
||||
@@ -125,7 +125,7 @@ export class WorkingDirectoryService extends Service {
|
||||
* @returns its effective absolute directory.
|
||||
*/
|
||||
get(session: Session): string {
|
||||
return this.projection.stateOf(session) ?? this.defaultDirectory
|
||||
return this.workingDirectoryProjectionHandle.stateOf(session) ?? this.defaultDirectory
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,7 +144,7 @@ export class WorkingDirectoryService extends Service {
|
||||
control.throwIfAborted()
|
||||
agent.ctx.fiber.assertActive()
|
||||
if (info?.type === 'directory') {
|
||||
if (this.projection.stateOf(agent.session) === null) {
|
||||
if (this.workingDirectoryProjectionHandle.stateOf(agent.session) === null) {
|
||||
agent.session.append('working-directory/change', { cwd: current })
|
||||
}
|
||||
return current
|
||||
|
||||
@@ -160,7 +160,7 @@ export async function apply(ctx: Context, config: Config, session?: Session): Pr
|
||||
const toolName = config.toolName ?? 'subagent'
|
||||
|
||||
const modelSelectionCapable = config.modelSelectionSettings === true
|
||||
const modelSelectionProjection = await ctx.sessionProjections.register(subagentModelSelectionProjectionDefinition)
|
||||
const subagentModelSelectionProjectionHandle = await ctx.sessionProjections.register(subagentModelSelectionProjectionDefinition)
|
||||
|
||||
const assertSubagentProviderConfiguration = (subagentProvider: SubagentProvider): void => {
|
||||
presets.assertProvider(subagentProvider)
|
||||
@@ -416,7 +416,7 @@ export async function apply(ctx: Context, config: Config, session?: Session): Pr
|
||||
const freshSession = target.firstLiveSeq === 0
|
||||
// oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
|
||||
&& target.eventAt(SessionSeq(0))?.type !== 'session/end-seed'
|
||||
let allowedModels = subagentModelSelectionPolicy(modelSelectionProjection, target)
|
||||
let allowedModels = subagentModelSelectionPolicy(subagentModelSelectionProjectionHandle, target)
|
||||
if (allowedModels === undefined) {
|
||||
const parentId = target.header.origin === 'subagent'
|
||||
? target.header.parentSession
|
||||
@@ -429,14 +429,14 @@ export async function apply(ctx: Context, config: Config, session?: Session): Pr
|
||||
const parent = sessions.get(parentId)
|
||||
allowedModels = parent === undefined
|
||||
? undefined
|
||||
: subagentModelSelectionPolicy(modelSelectionProjection, parent)
|
||||
: subagentModelSelectionPolicy(subagentModelSelectionProjectionHandle, parent)
|
||||
} else if (freshSession) {
|
||||
const current = settings.current()
|
||||
allowedModels = current.enabled ? current.allowedModels : undefined
|
||||
}
|
||||
}
|
||||
if (allowedModels !== undefined) {
|
||||
recordSubagentModelSelection(modelSelectionProjection, target, allowedModels)
|
||||
recordSubagentModelSelection(subagentModelSelectionProjectionHandle, target, allowedModels)
|
||||
}
|
||||
return allowedModels === undefined ? undefined : { routes: allowedModels }
|
||||
}
|
||||
|
||||
@@ -52,29 +52,29 @@ export const subagentModelSelectionProjectionDefinition = {
|
||||
|
||||
/**
|
||||
* Read the exact route list captured for a model-selectable definition.
|
||||
* @param projection - registered policy state reader.
|
||||
* @param subagentModelSelectionProjectionHandle - registered policy state reader.
|
||||
* @param session - session whose durable decision is read.
|
||||
* @returns a detached route list, or undefined for the fixed-route definition.
|
||||
*/
|
||||
export function subagentModelSelectionPolicy(
|
||||
projection: Pick<ProjectionHandle<AllowedModelRoute[] | null>, 'stateOf'>,
|
||||
subagentModelSelectionProjectionHandle: Pick<ProjectionHandle<AllowedModelRoute[] | null>, 'stateOf'>,
|
||||
session: Session,
|
||||
): AllowedModelRoute[] | undefined {
|
||||
return projection.stateOf(session)?.map(route => ({ ...route }))
|
||||
return subagentModelSelectionProjectionHandle.stateOf(session)?.map(route => ({ ...route }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the route policy once, before its definition can reach a model request.
|
||||
* @param projection - registered policy state reader.
|
||||
* @param subagentModelSelectionProjectionHandle - registered policy state reader.
|
||||
* @param session - session receiving the model-selectable definition.
|
||||
* @param allowedModels - exact routes the definition may select explicitly.
|
||||
*/
|
||||
export function recordSubagentModelSelection(
|
||||
projection: Pick<ProjectionHandle<AllowedModelRoute[] | null>, 'stateOf'>,
|
||||
subagentModelSelectionProjectionHandle: Pick<ProjectionHandle<AllowedModelRoute[] | null>, 'stateOf'>,
|
||||
session: Session,
|
||||
allowedModels: readonly AllowedModelRoute[],
|
||||
): void {
|
||||
if (subagentModelSelectionPolicy(projection, session) !== undefined) return
|
||||
if (subagentModelSelectionPolicy(subagentModelSelectionProjectionHandle, session) !== undefined) return
|
||||
session.append('subagent/model-selection-policy', {
|
||||
allowedModels: allowedModels.map(route => ({ ...route })),
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user