Merge branch 'codex/tool-json-schema-dsl' into codex/canonical-tool-output

# Conflicts:
#	docs/architecture.md
#	docs/config-catalog.md
#	docs/persistence-catalog.md
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-escalation-approved/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-write/session.jsonl
#	packages/ui/tui/src/index.ts
#	scripts/type-equiv.manifest.json
This commit is contained in:
Tianyi Cui
2026-07-22 00:23:00 +08:00
252 changed files with 11596 additions and 6037 deletions

View File

@@ -29,7 +29,7 @@ The config-driven `ctx.agentLoop.create()` path keeps its agent owned by the loo
### Invariant companion
The optional `@deepseek-ai/dsh-agent-loop/invariant` companion registers request reconstruction with `ctx.invariants`. The loop marks each request with an internal non-enumerable identity before freezing it; the companion then requires a live session and independently rebuilds the message boundary and folded request header from the log. Direct one-shot calls remain outside this contract even when callers freeze them or attach a session id.
The optional `@deepseek-ai/dsh-agent-loop/invariant` companion registers request reconstruction with `ctx.invariants`. The loop records each exact frozen request in the process-local identity set owned by `dsh-llm`; the companion then requires a live session and independently rebuilds the message boundary and folded request header from the log. Direct one-shot calls remain outside this contract even when callers freeze them or attach a session id.
### Configuration (schemastery)

View File

@@ -4,10 +4,9 @@
*/
import type { Context } from 'cordis'
import type { GenerateOptions } from '@deepseek-ai/dsh-llm'
import { isAgentLoopRequest, type GenerateOptions } from '@deepseek-ai/dsh-llm'
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
import { Session, SessionId, foldRequestHeader } from '@deepseek-ai/dsh-session'
import { isLoopRequest } from './request-marker.ts'
const PACKAGE_NAME = '@deepseek-ai/dsh-agent-loop'
@@ -21,7 +20,7 @@ const install: InvariantInstaller = Object.assign((ctx: Context, fail: Invariant
// Prepend prevents a short-circuiting replay listener from silencing the
// check; correctness itself comes from the sequence-bounded reconstruction.
ctx.on('llm/stream', (options: GenerateOptions, next) => {
if (!isLoopRequest(options)) return next()
if (!isAgentLoopRequest(options)) return next()
if (!Object.isFrozen(options)) fail('a loop-built request must be frozen')
if (options.sessionId === undefined) fail('a loop-built request must carry a session id')
const session = ctx.sessions.get(options.sessionId)

View File

@@ -8,14 +8,13 @@
import type { Context } from 'cordis'
import type { ContentBlock, FinishReason, GenerateOptions, LlmCallConfig, LlmFailure, Message } from '@deepseek-ai/dsh-llm'
import { isDeepStrictEqual } from 'node:util'
import { BlockAssembler, HarnessError, LlmError, assertNever, deepFreeze, errorChain, llmFailureOf } from '@deepseek-ai/dsh-llm'
import { BlockAssembler, HarnessError, LlmError, assertNever, deepFreeze, errorChain, llmFailureOf, markAgentLoopRequest } from '@deepseek-ai/dsh-llm'
import { agentEvents, agentInterruptReasonOf, assembleContextFor } from '@deepseek-ai/dsh-agent'
import type { AgentEventDispatch, ContinuationDecision, HookContext, PromptDecision, RequestError, RequestErrorDecision } from '@deepseek-ai/dsh-agent'
import { canonicalHeader } from '@deepseek-ai/dsh-session'
import type { Session, TurnEndReason, TurnTrigger } from '@deepseek-ai/dsh-session'
import { createTransmissionLog, recordRequestHeader } from './request-log.ts'
import type { TransmissionLog } from './request-log.ts'
import { markLoopRequest } from './request-marker.ts'
import { renderPrompt } from '@deepseek-ai/dsh-system-prompt'
import type { PromptAssembly } from '@deepseek-ai/dsh-system-prompt'
import type {} from '@deepseek-ai/dsh-tools'
@@ -598,7 +597,7 @@ async function runStep(
recordRequestHeader(session, transmission, header)
// Freeze the logged header plus boundary snapshot; the prefix precedes derived history.
const request: GenerateOptions = deepFreeze(markLoopRequest({
const request: GenerateOptions = markAgentLoopRequest(deepFreeze({
provider: header.config.provider,
model: header.config.model,
messages: [...header.messagePrefix ?? [], ...boundaryMessages],

View File

@@ -1,22 +0,0 @@
/** Internal identity shared by the independently bundled loop and invariant companion. */
const LOOP_REQUEST = Symbol.for('@deepseek-ai/dsh-agent-loop/request')
/**
* Mark a request as owned by the agent loop before it is frozen.
* @param request - mutable request object being assembled by the loop.
* @returns the same request with a non-enumerable loop identity.
*/
export function markLoopRequest<T extends object>(request: T): T {
Object.defineProperty(request, LOOP_REQUEST, { value: true })
return request
}
/**
* Test whether a request carries the agent loop's internal identity.
* @param request - request observed at the LLM stream boundary.
* @returns whether the loop marked this exact request object.
*/
export function isLoopRequest(request: object): boolean {
return Reflect.get(request, LOOP_REQUEST) === true
}

View File

@@ -3,7 +3,7 @@ import { Context } from 'cordis'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import InvariantService from '@deepseek-ai/dsh-invariants'
import * as AgentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant'
import { markLoopRequest } from '../src/request-marker.ts'
import { markAgentLoopRequest, type GenerateOptions } from '@deepseek-ai/dsh-llm'
async function setup(): Promise<Context> {
const ctx = new Context()
@@ -18,7 +18,8 @@ function dispatch(ctx: Context, options: unknown): void {
}
function loopRequest<T extends object>(options: T): Readonly<T> {
return Object.freeze(markLoopRequest(options))
markAgentLoopRequest(options as GenerateOptions)
return Object.freeze(options)
}
async function requestSetup() {
@@ -94,8 +95,10 @@ describe('request-reconstruction invariant', () => {
it('rejects malformed requests carrying the loop marker', async () => {
const { ctx, session } = await requestSetup()
const messages: GenerateOptions['messages'] = []
Object.freeze(messages)
expect(() => {
dispatch(ctx, markLoopRequest({ model: 'm', messages: Object.freeze([]), sessionId: session.id }))
dispatch(ctx, markAgentLoopRequest({ provider: 'p', model: 'm', messages, sessionId: session.id }))
}).toThrow(/request must be frozen/)
expect(() => {
dispatch(ctx, loopRequest({ model: 'm', messages: Object.freeze([]) }))