Merge latest master into manual compaction

# Conflicts:
#	apps/cli/README.i18n.yaml
#	docs/architecture.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	packages/compact/compact-basic/README.i18n.yaml
#	packages/pty/pty-local/tests/index.spec.ts
This commit is contained in:
Tianyi Cui
2026-07-31 17:57:11 +08:00
419 changed files with 78109 additions and 4095 deletions

View File

@@ -35,6 +35,7 @@ import {
LlmError,
assertNever,
createAssistantMessage,
createUserMessage,
deepFreeze,
errorChain,
freezeMessage,
@@ -45,8 +46,8 @@ import {
} from '@deepseek-ai/dsh-llm'
import type { GenerateOptions, LlmCallConfig, LlmFailure, Message, PreparedLlmCall, ResolvedRetryPolicy } from '@deepseek-ai/dsh-llm'
import { canonicalHeader, headerEquals } from '@deepseek-ai/dsh-session'
import type { AssistantMessage, EpochHeader, Session, SessionId, TurnEndReason, TurnTrigger, UserMessage } from '@deepseek-ai/dsh-session'
import { renderPrompt } from '@deepseek-ai/dsh-system-prompt'
import type { AssistantMessage, EpochHeader, RequestContext, Session, SessionId, TurnEndReason, TurnTrigger, UserMessage } from '@deepseek-ai/dsh-session'
import { renderContextSnapshot, renderPrompt } from '@deepseek-ai/dsh-system-prompt'
import type {} from '@deepseek-ai/dsh-tools'
import { executeToolCalls } from './tool-calls.ts'
@@ -55,6 +56,47 @@ type StepOutcome =
| { kind: 'completed'; continueTurn: boolean; concluded: boolean; maxTokens: boolean }
| { kind: 'request-failed'; error: RequestError; failure: LlmFailure; retryPolicy: ResolvedRetryPolicy | undefined }
const RUNTIME_CONTEXT_SOURCE = '@deepseek-ai/dsh-system-prompt'
/** Clearing marker kept distinct from every prefixed {@link renderContextSnapshot} result. */
const CLEARED_RUNTIME_CONTEXT = 'Current runtime context: none. Earlier runtime-context snapshots no longer apply.'
/** Whether one user message is owned by runtime-context materialization. */
function isRuntimeContextMessage(message: UserMessage): boolean {
return message.source.kind === 'plugin' && message.source.plugin === RUNTIME_CONTEXT_SOURCE
}
/** Latest retained runtime-context snapshot; `found` distinguishes malformed content from absence. */
function retainedRuntimeContext(session: Session): { found: boolean; text: string | undefined } {
const events = session.events
const nodes = session.surface.nodes
for (let index = nodes.length - 1; index >= 0; index -= 1) {
const event = events[nodes[index] as number]
if (event?.type !== 'user/message' || !isRuntimeContextMessage(event.data)) continue
const [block] = event.data.content
return {
found: true,
text: event.data.content.length === 1 && block?.type === 'text' ? block.text : undefined,
}
}
return { found: false, text: undefined }
}
/** Append a full current snapshot only when it changed or compaction removed it. */
function materializeRuntimeContext(session: Session, current: string): void {
const previous = retainedRuntimeContext(session)
if (!previous.found && current.length === 0) {
const compactedPriorSnapshot = session.surface.replaceGeneration > 0
&& session.events.some(event => event.type === 'user/message' && isRuntimeContextMessage(event.data))
if (!compactedPriorSnapshot) return
}
const snapshot = current.length === 0 ? CLEARED_RUNTIME_CONTEXT : current
if (previous.text === snapshot) return
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: snapshot }],
source: { kind: 'plugin', plugin: RUNTIME_CONTEXT_SOURCE },
}), { surfaceOp: 'append' })
}
/** Remove adapter-derived values before plugins propose the next request config. */
function requestProposal(header: EpochHeader): LlmCallConfig {
if (header.adapterDefaults === undefined) return header.config
@@ -568,10 +610,13 @@ export class ReactLoopAgent implements Agent {
// this request together.
this.drainOutbox(turn)
// Assemble the system prompt fresh each step (it may depend on log state).
// Assemble request-owned prompt inputs fresh each step. Dynamic context is
// committed at the tail before deriving history once, preserving the stable
// system/history cache prefix while keeping every model-visible byte logged.
const assembly = await this.loopCtx.systemPrompt.assemble(assembleContextFor(this, signal))
signal.throwIfAborted()
const system = renderPrompt(assembly)
materializeRuntimeContext(session, renderContextSnapshot(assembly))
// Snapshot the exact log prefix: the reconstruction boundary. Appends
// after this synchronous snapshot join the next request.
@@ -728,6 +773,24 @@ export class ReactLoopAgent implements Agent {
session.append('request/header', { header, reason: 'change' })
}
// TODO: This looks like code smell.
// Context metadata for the route this request resolved to, recorded from the same
// registration-bound lookup that prepared the call (no second resolve).
// A route with unknown capacity is still recorded so it clears any older
// denominator; an unchanged route logs nothing.
const contextWindow = preparedCall?.context?.contextWindow
const requestContext: RequestContext = {
provider: config.provider,
model: config.model,
...contextWindow === undefined ? {} : { contextWindow },
}
const previous = session.requestContext()
if (previous?.provider !== requestContext.provider
|| previous.model !== requestContext.model
|| previous.contextWindow !== requestContext.contextWindow) {
session.append('request/context', requestContext)
}
const request = markAgentLoopRequest(deepFreeze({
...header.config,
messages: boundaryMessages,