mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(schedule): validate time context snapshot markers
This commit is contained in:
@@ -107,7 +107,7 @@ The design does not recognize or migrate any unmerged Schedule implementation or
|
||||
|
||||
Package tests pin strict decoding, transitions, fork suffixes, id reuse, offset and local-calendar profiles, IANA validation, gap rejection, overlap-first selection, mismatch confirmation, time bounds, bounded waits, wall-clock movement, overdue admission, fixed framing, enqueue and append failures, barrier recovery, registration rollback, and quiescent disposal at 100% per-file coverage. Persistence tests cover new, fork, and resumed initialization failures against the actual durable cursor, optional header round-trips, a real SQLite v13-to-v14 migration, and a production JSONL restart. The assembled Loader/Web restart lane proves pending recovery, fork isolation, one durable dispatch, cold-history rendering without Agent activation, and no redelivery after another restart. Host/client tests cover zone identity across live, stored, and concurrent-create paths; per-operation prompt provenance; commit gating; reversed watermarks; semantic header identity; per-event prefix matching; same-seq upgrades; every window merge exit; and reconnect generations.
|
||||
|
||||
Time-context tests cover final pre-step messages, current-turn unique/mixed/missing zone derivation, post-claim steering entering the next step, cancellation, empty suppression, retry, simple source validation, and in-flight disposal. Schedule tests independently derive the same request zones from durable `user-rpc` sources, reuse a same-turn marker across an empty continuation, and fail closed without an open-turn marker. The opt-in Loader composition boots the source and built packages. Keyless real-browser scenarios execute `schedule_create` through the complete tool pipeline for the existing short `after` case and one absolute-time case, observe the identity-matched persisted prefix, and render the durable reminder card from attached history. The deliberately absent model adapter closes the turn with an error after dispatch, proving that model failure does not remove the receipt.
|
||||
Time-context tests cover final pre-step messages, current-turn unique/mixed/missing zone derivation, post-claim steering entering the next step, cancellation, empty suppression, retry, exact snapshot-source validation, and in-flight disposal. Schedule tests independently derive the same request zones from durable `user-rpc` sources, reuse a same-turn marker across an empty continuation, and fail closed without an open-turn marker. The opt-in Loader composition boots the source and built packages. Keyless real-browser scenarios execute `schedule_create` through the complete tool pipeline for the existing short `after` case and one absolute-time case, observe the identity-matched persisted prefix, and render the durable reminder card from attached history. The deliberately absent model adapter closes the turn with an error after dispatch, proving that model failure does not remove the receipt.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ due → admission → followup → dispatch → flush(true) → session/flushed
|
||||
|
||||
package 测试以逐文件 100% coverage 固定严格 decoding、transition、fork suffix、id 不复用、offset 与 local-calendar profile、IANA 校验、gap 拒绝、overlap-first 选择、mismatch confirmation、时间边界、有界等待、墙钟变化、overdue 准入、固定 framing、入队与 append 失败、barrier 恢复、注册 rollback 和完全停稳 dispose。persistence 测试依据实际 durable cursor 覆盖 new、fork 与 resumed 初始化失败、可选 header round-trip、一次真实 SQLite v13 到 v14 migration,以及 production JSONL restart。组装后的 Loader/Web restart lane 证明 pending 恢复、fork 隔离、单次 durable dispatch、无需激活 agent 的 cold-history rendering,以及再次 restart 后不重投。Host/client 测试覆盖 live、stored 与 concurrent-create 路径中的 zone identity、逐操作提示词 provenance、commit gating、反序 watermark、语义 header identity、逐 event 前缀匹配、same-seq 升级、每个 window merge 出口和 reconnect generation。
|
||||
|
||||
Time-context 测试覆盖最终 pre-step 消息、当前 turn 的唯一/混合/缺失时区派生、领取后 steering 进入下一步骤、取消、空值抑制、重试、简单来源校验和执行中释放。Schedule 测试会从持久 `user-rpc` 来源独立派生同一组请求时区,在空的续跑中复用同 turn 标记,并在缺少 open-turn 标记时 fail closed。显式 Loader 组合可以启动 source 与 built package。无密钥真实浏览器场景会通过完整工具 pipeline,针对既有的短 `after` case 和一个 absolute-time case 执行 `schedule_create`,观察 identity-matched 持久前缀,并从已附加 history 渲染 durable reminder card。刻意缺少的模型 adapter 会在 dispatch 后以错误关闭 turn,从而证明模型失败不会移除回执。
|
||||
Time-context 测试覆盖最终 pre-step 消息、当前 turn 的唯一/混合/缺失时区派生、领取后 steering 进入下一步骤、取消、空值抑制、重试、精确 snapshot 来源校验和执行中释放。Schedule 测试会从持久 `user-rpc` 来源独立派生同一组请求时区,在空的续跑中复用同 turn 标记,并在缺少 open-turn 标记时 fail closed。显式 Loader 组合可以启动 source 与 built package。无密钥真实浏览器场景会通过完整工具 pipeline,针对既有的短 `after` case 和一个 absolute-time case 执行 `schedule_create`,观察 identity-matched 持久前缀,并从已附加 history 渲染 durable reminder card。刻意缺少的模型 adapter 会在 dispatch 后以错误关闭 turn,从而证明模型失败不会移除回执。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import type { Context } from 'cordis'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import { deriveClientTimeZoneContext } from '@deepseek-ai/dsh-time-context'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import type { GenericCallView } from '@deepseek-ai/dsh-tools'
|
||||
@@ -217,6 +218,22 @@ interface AtTimeZoneContext {
|
||||
readonly clientTimeZones: string[]
|
||||
}
|
||||
|
||||
/** Whether one durable message is the exact time-context snapshot marker. */
|
||||
function isTimeContextReading(event: SessionEvent): boolean {
|
||||
if (event.type !== 'user/message') return false
|
||||
const source = event.data.source
|
||||
const [block] = event.data.content
|
||||
return event.data.content.length === 1
|
||||
&& block?.type === 'text'
|
||||
&& source.kind === 'plugin'
|
||||
&& source.plugin === 'time-context'
|
||||
&& Object.keys(source).length === 4
|
||||
&& source.form === 'snapshot'
|
||||
&& source.sections.length === 1
|
||||
&& source.sections[0]?.name === 'time-context'
|
||||
&& source.sections[0].text === block.text
|
||||
}
|
||||
|
||||
/** Derive request zones only while the current open turn contains a time-context reading. */
|
||||
function currentClientTimeZoneContext(agent: Agent): ReturnType<typeof deriveClientTimeZoneContext> | undefined {
|
||||
const events = agent.session.events
|
||||
@@ -236,10 +253,7 @@ function currentClientTimeZoneContext(agent: Agent): ReturnType<typeof deriveCli
|
||||
if (stepStart < 0) return undefined
|
||||
const turnStart = events.findLastIndex(event => event.type === 'turn/start' && event.data.turn === turn)
|
||||
if (turnStart < 0) return undefined
|
||||
const hasReading = events.slice(turnStart + 1).some(event => event.type === 'user/message'
|
||||
&& event.data.source.kind === 'plugin'
|
||||
&& event.data.source.plugin === 'time-context'
|
||||
&& Object.keys(event.data.source).length === 2)
|
||||
const hasReading = events.slice(turnStart + 1).some(isTimeContextReading)
|
||||
if (!hasReading) return undefined
|
||||
const messages = events.slice(turnStart + 1)
|
||||
.flatMap(event => event.type === 'user/message' ? [event.data] : [])
|
||||
|
||||
@@ -98,9 +98,15 @@ function appendRequestContext(agent: Agent, clientTimeZones: readonly string[]):
|
||||
source: { kind: 'user', clientTimeZone } as never,
|
||||
}), { surfaceOp: 'append' })
|
||||
}
|
||||
const text = 'time context'
|
||||
agent.session.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: 'time context' }],
|
||||
source: { kind: 'plugin', plugin: 'time-context' },
|
||||
content: [{ type: 'text', text }],
|
||||
source: {
|
||||
kind: 'plugin',
|
||||
plugin: 'time-context',
|
||||
form: 'snapshot',
|
||||
sections: [{ name: 'time-context', text }],
|
||||
},
|
||||
}), { surfaceOp: 'append' })
|
||||
}
|
||||
|
||||
@@ -329,7 +335,7 @@ describe('Schedule tool protocol', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('reuses a simple same-turn marker across an empty continuation and ignores a malformed source', async () => {
|
||||
it('reuses a same-turn snapshot marker across an empty continuation and ignores a malformed source', async () => {
|
||||
const test = await harness(true, 'Asia/Shanghai')
|
||||
test.agent.session.append('turn/start', { turn: 1 })
|
||||
test.agent.session.append('step/start', { turn: 1, step: 1 })
|
||||
|
||||
Reference in New Issue
Block a user