refactor agent pre-step inbox lifecycle
This commit is contained in:
@@ -1,26 +1,26 @@
|
||||
/**
|
||||
* Opt-in request-preparation clock context. Eligible pre-step attempts append
|
||||
* durable, source-attributed time readings to conversation history.
|
||||
* Opt-in request clock context. Eligible steps add durable,
|
||||
* source-attributed time readings to the request history.
|
||||
*
|
||||
* @module @deepseek-ai/dsh-time-context
|
||||
*/
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import z from 'schemastery'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent, PreStepDecision } from '@deepseek-ai/dsh-agent'
|
||||
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
/** Cordis plugin name used by loader diagnostics. */
|
||||
export const name = 'time-context'
|
||||
|
||||
/** The agent registry that owns the pre-step lifecycle seam. */
|
||||
/** The agent registry that owns pre-step processing. */
|
||||
export const inject = ['agents']
|
||||
|
||||
/** Request-preparation clock formatting and append scheduling. Invalid values fail plugin load. */
|
||||
export interface Config {
|
||||
/** IANA time zone used for the rendered timestamp. Omit to resolve the Node process's system zone at plugin load. */
|
||||
timeZone?: string
|
||||
/** Minimum milliseconds between durable injections in one session. Omit or set to 0 to inject on every eligible pre-step attempt. */
|
||||
/** Minimum milliseconds between durable injections in one session. Omit or set to 0 to inject at every eligible step. */
|
||||
refreshIntervalMs?: number
|
||||
}
|
||||
|
||||
@@ -157,23 +157,33 @@ export function apply(ctx: Context, config: Config): void {
|
||||
}
|
||||
const resolvedTimeZone = formatter.resolvedOptions().timeZone
|
||||
|
||||
ctx.on('agent/step', (
|
||||
ctx.on('agent/pre-step', async (
|
||||
agent: Agent,
|
||||
turn: number,
|
||||
step: number,
|
||||
signal: AbortSignal,
|
||||
) => {
|
||||
if (signal.aborted) return
|
||||
_messages,
|
||||
{ turn, step, signal },
|
||||
next,
|
||||
): Promise<PreStepDecision> => {
|
||||
const decision = await next()
|
||||
if (decision.kind === 'reject' || signal.aborted) return decision
|
||||
const now = Date.now()
|
||||
if (refreshIntervalMs !== undefined && refreshIntervalMs > 0) {
|
||||
const lastInjection = latestInjectionTime(agent)
|
||||
if (lastInjection !== undefined
|
||||
&& now >= lastInjection
|
||||
&& now - lastInjection < refreshIntervalMs) return
|
||||
&& now - lastInjection < refreshIntervalMs) return decision
|
||||
}
|
||||
const previous = step === 1
|
||||
? precedingMessageTime(agent)
|
||||
: precedingStepContextTime(agent, turn)
|
||||
agent.session.append('user/message', createUserMessage({ content: [{ type: 'text', text: renderText(now, turn, step, previous, formatter, resolvedTimeZone) }], source: { kind: 'plugin', plugin: name } }), { surfaceOp: 'append' })
|
||||
return {
|
||||
kind: 'enter',
|
||||
messages: [
|
||||
...decision.messages,
|
||||
createUserMessage({
|
||||
content: [{ type: 'text', text: renderText(now, turn, step, previous, formatter, resolvedTimeZone) }],
|
||||
source: { kind: 'plugin', plugin: name },
|
||||
}),
|
||||
],
|
||||
}
|
||||
}, { prepend: true })
|
||||
}
|
||||
|
||||
@@ -18,22 +18,23 @@ export const name = 'time-context-invariant'
|
||||
/** Service required before the companion can reserve package ownership. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/** Derive the open step in which a time-context reading may append. */
|
||||
/** Derive the next request boundary at which a time-context reading may append. */
|
||||
function preparationPosition(history: readonly SessionEvent[], fail: InvariantFailure): { turn: number; step: number } {
|
||||
for (const event of history.slice().reverse()) {
|
||||
switch (event.type) {
|
||||
case 'step/start':
|
||||
return event.data
|
||||
case 'step/end':
|
||||
return { turn: event.data.turn, step: event.data.step + 1 }
|
||||
case 'turn/start':
|
||||
return { turn: event.data.turn, step: 1 }
|
||||
case 'step/start':
|
||||
case 'turn/end':
|
||||
fail('time-context reading must be appended inside an open step')
|
||||
fail('time-context reading must be appended at a prompt boundary')
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
fail('time-context reading must be appended inside an open step')
|
||||
fail('time-context reading must be appended at a prompt boundary')
|
||||
}
|
||||
|
||||
/** Validate one plugin-attributed time reading against its session position and timestamp. */
|
||||
|
||||
Reference in New Issue
Block a user