feat(schedule): add absolute-time reminders
This commit is contained in:
@@ -3,12 +3,15 @@
|
||||
import type { Context } from 'cordis'
|
||||
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
import { decodeTimeContextSource, renderTimeContextAuthority } from './authority.ts'
|
||||
|
||||
const PACKAGE_NAME = '@deepseek-ai/dsh-time-context'
|
||||
const SOURCE_NAME = 'time-context'
|
||||
const READING = new RegExp(
|
||||
'^Time sampled while preparing turn (\\d+), step (\\d+): '
|
||||
+ '(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:Z|[+-]\\d{2}:\\d{2})\\[[^\\]]+\\])\\n'
|
||||
+ 'Session time zone: ([^.]+)\\.\\n'
|
||||
+ 'Client time zone for this request: (.+)\\.\\n'
|
||||
+ 'Elapsed since the preceding (model-visible message|step context): '
|
||||
+ '(?:unavailable|(?:(?:\\d+d )?(?:\\d+h )?(?:\\d+m )?\\d+s))\\.$',
|
||||
)
|
||||
@@ -18,27 +21,43 @@ export const name = 'time-context-invariant'
|
||||
/** Service required before the companion can reserve package ownership. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/** Derive the entered step boundary at which a time-context reading may append. */
|
||||
/**
|
||||
* Derive the step preparation owned by a time-context reading. A normal
|
||||
* reading follows `step/start`; a pre-step failure may settle context-only
|
||||
* output in the still-open turn before that boundary.
|
||||
*/
|
||||
function preparationPosition(history: readonly SessionEvent[], fail: InvariantFailure): { turn: number; step: number } {
|
||||
for (const event of history.slice().reverse()) {
|
||||
let openTurn: number | undefined
|
||||
let openStep: number | undefined
|
||||
let nextStep = 1
|
||||
for (const event of history) {
|
||||
switch (event.type) {
|
||||
case 'step/start':
|
||||
return { turn: event.data.turn, step: event.data.step }
|
||||
case 'turn/start':
|
||||
case 'step/end':
|
||||
case 'turn/end':
|
||||
case 'request/header':
|
||||
case 'assistant/chunk':
|
||||
case 'assistant/message':
|
||||
case 'tool/call':
|
||||
case 'tool/result':
|
||||
fail('time-context reading must be appended at a prompt boundary')
|
||||
case 'turn/start': {
|
||||
openTurn = event.data.turn
|
||||
openStep = undefined
|
||||
nextStep = 1
|
||||
break
|
||||
}
|
||||
case 'step/start': {
|
||||
openStep = event.data.step
|
||||
break
|
||||
}
|
||||
case 'step/end': {
|
||||
openStep = undefined
|
||||
nextStep = event.data.step + 1
|
||||
break
|
||||
}
|
||||
case 'turn/end': {
|
||||
openTurn = undefined
|
||||
openStep = undefined
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
fail('time-context reading must be appended at a prompt boundary')
|
||||
if (openTurn === undefined) fail('time-context reading must be appended inside an open turn')
|
||||
return { turn: openTurn, step: openStep ?? nextStep }
|
||||
}
|
||||
|
||||
/** Validate one plugin-attributed time reading against its session position and timestamp. */
|
||||
@@ -62,7 +81,20 @@ function validateReading(
|
||||
if (turn !== expected.turn || step !== expected.step) {
|
||||
fail(`time-context reading names turn ${turn}/step ${step}, expected turn ${expected.turn}/step ${expected.step}`)
|
||||
}
|
||||
const baseline = match[4]
|
||||
let source: ReturnType<typeof decodeTimeContextSource>
|
||||
try {
|
||||
source = decodeTimeContextSource(event.data.source)
|
||||
} catch (error: unknown) {
|
||||
fail(error instanceof Error ? error.message : String(error))
|
||||
}
|
||||
if (source.authority.turn !== turn || source.authority.step !== step) {
|
||||
fail('time-context text and source authority name different positions')
|
||||
}
|
||||
const renderedAuthority = `Session time zone: ${match[4]}.\nClient time zone for this request: ${match[5]}.`
|
||||
if (renderedAuthority !== renderTimeContextAuthority(source.authority)) {
|
||||
fail('time-context text and source authority describe different zones')
|
||||
}
|
||||
const baseline = match[6]
|
||||
if ((step === 1) !== (baseline === 'model-visible message')) {
|
||||
fail(`time-context step ${step} uses the wrong elapsed-time baseline ${JSON.stringify(baseline)}`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user