Files
deepseek-harness/packages/core/agent-loop/src/runtime-context.ts
imccyu ec601ca13d build(vendor): rescope the vendored Cordis packages into @deepseek-ai
Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it
prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`,
`verify-translation-pairing --write` for the touched bilingual pairs,
`gen-doc-graphs`, and one typert snapshot whose ids embed character offsets.
`pnpm run rescope-vendor --check` verifies the result.

Renames nine vendored packages (cordis, cosmokit, schemastery and the six
@cordisjs plugins) and every reference that resolves them: manifest names and
dependency keys, module specifiers including declare-module merges, cordis.yml
plugin names, tsconfig paths, every Markdown fence, and `docs/` prose.
Directory names, upstream versions, and dependency ranges are unchanged, so
vendor/README.md still reads as an upstream snapshot; its manifest table gains
an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed
at each fork's origin.

The tutorial tier follows the rename end to end: its yaml fences named plugins
the Loader can no longer resolve, its `ts ignore-check` fences disagreed with
the compiled fences beside them, and its prose quoted both. The contracts that
told readers to keep upstream names — the root convention and the vendoring
cookbook's tree comment and manifest invariant — now say to rescope instead.

Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle
purity gate now names the vendored libraries a browser bundle inlines, and the
files where a bare `cordis` is an agent-preset id keep that product data.
2026-08-10 22:04:13 +08:00

77 lines
3.1 KiB
TypeScript

/**
* Durable projection state for dynamic runtime context.
* @module @deepseek-ai/dsh-agent-loop/runtime-context
*/
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import type { ContextSnapshotSection } from '@deepseek-ai/dsh-llm'
import type { Session, UserMessage } from '@deepseek-ai/dsh-session'
import { isReplacementSurfaceEvent } from '@deepseek-ai/dsh-session'
import type { Context } from '@deepseek-ai/cordis'
const SOURCE = '@deepseek-ai/dsh-system-prompt'
const CLEARED = 'Current runtime context: none. Earlier runtime-context snapshots no longer apply.'
function isOwned(message: UserMessage): boolean {
return message.source.kind === 'plugin' && message.source.plugin === SOURCE
}
function textOf(message: UserMessage): string | undefined {
const [block] = message.content
return message.content.length === 1 && block?.type === 'text' ? block.text : undefined
}
/** Tracks the last retained runtime-context snapshot without owning its commit. */
export class RuntimeContextProjection {
/** `undefined` means no snapshot ever existed; `null` means none is retained. */
private retained: { seq: number; text: string | undefined } | null | undefined
/**
* Restore projection state once, then follow authoritative session events.
* @param ctx - agent-scoped event context.
* @param session - session receiving projected messages.
*/
constructor(ctx: Context, session: Session) {
const surface = new Set(session.surface.nodes)
for (let index = session.events.length - 1; index >= 0; index -= 1) {
const event = session.events[index]
if (event?.type !== 'user/message' || !isOwned(event.data)) continue
this.retained ??= null
if (surface.has(event.seq)) {
this.retained = { seq: event.seq, text: textOf(event.data) }
break
}
}
ctx.on('session/event', (subject, event) => {
if (subject !== session) return
if (event.type === 'user/message' && isOwned(event.data)) {
this.retained = { seq: event.seq, text: textOf(event.data) }
} else if (this.retained
&& isReplacementSurfaceEvent(event)
&& event.sourceEventSeqs?.includes(this.retained.seq) === true) {
this.retained = null
}
})
}
/**
* Create an uncommitted snapshot only when the retained value differs.
* @param current - fully rendered dynamic context.
* @param sections - named contributions that formed the current snapshot.
* @returns a candidate user message, or `undefined` when no update is needed.
*/
project(current: string, sections: readonly ContextSnapshotSection[]): UserMessage | undefined {
if (this.retained === undefined && current.length === 0) return
const snapshot = current.length === 0 ? CLEARED : current
if (this.retained?.text === snapshot) return
return createUserMessage({
content: [{ type: 'text', text: snapshot }],
// The cleared marker has no contributions left to attribute.
source: sections.length === 0
? { kind: 'plugin', plugin: SOURCE }
: { kind: 'plugin', plugin: SOURCE, form: 'snapshot', sections },
})
}
}