Merge remote-tracking branch 'origin/worktree/context-source-cards' into worktree/context-forms-remaining

# Conflicts:
#	apps/web/tests/snapshots/queue-actions/layout.expected.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.i18n.yaml
#	docs/core-data-structures/goal.i18n.yaml
#	docs/core-data-structures/goal.md
#	docs/core-data-structures/goal.zh.md
#	docs/event-producer-consumer.md
#	examples/acp-agent/tests/goal-snapshots/goal-session/session.expected.jsonl
#	examples/acp-agent/tests/goal-snapshots/goal-wrapup/session.expected.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl
#	examples/acp-agent/tests/snapshots/bash-spill/session.jsonl
#	examples/acp-agent/tests/snapshots/bash-tool-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/cancel-tool-calls/session.jsonl
#	examples/acp-agent/tests/snapshots/cancel/session.jsonl
#	examples/acp-agent/tests/snapshots/code-mode-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/code-mode-workspace-context/session.jsonl
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	examples/acp-agent/tests/snapshots/empty-response-retry/session.jsonl
#	examples/acp-agent/tests/snapshots/error-finish/session.jsonl
#	examples/acp-agent/tests/snapshots/escalation-approved/session.jsonl
#	examples/acp-agent/tests/snapshots/escalation-rejected/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-edit/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-escalation-approved/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-policy-reject/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-read-window/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-read/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-write-overwrite/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-write/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-invalid-matcher/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-posttool-block/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-posttool-context/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-pretool-deny/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-promptsubmit-context/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-cc-stop-continue/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-invalid-matcher/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-posttool-block/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-posttool-context/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-pretool-block/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-promptsubmit-context/session.jsonl
#	examples/acp-agent/tests/snapshots/hook-codex-stop-continue/session.jsonl
#	examples/acp-agent/tests/snapshots/lsp-definition/session.jsonl
#	examples/acp-agent/tests/snapshots/missing-sandbox-runner/session.jsonl
#	examples/acp-agent/tests/snapshots/multi-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/packed-chunks/session.jsonl
#	examples/acp-agent/tests/snapshots/parallel-tool-calls/session.jsonl
#	examples/acp-agent/tests/snapshots/partial-landlock-child-failure/session.jsonl
#	examples/acp-agent/tests/snapshots/pty-tools/session.jsonl
#	examples/acp-agent/tests/snapshots/repeat-tool-guard/session.jsonl
#	examples/acp-agent/tests/snapshots/session-query-spill/session.jsonl
#	examples/acp-agent/tests/snapshots/session-sandbox-root/session.jsonl
#	examples/acp-agent/tests/snapshots/session-title-after-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/skill-load/session.jsonl
#	examples/acp-agent/tests/snapshots/subagent-continuable/session.1.jsonl
#	examples/acp-agent/tests/snapshots/subagent-continuable/session.jsonl
#	examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.1.jsonl
#	examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.2.jsonl
#	examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.jsonl
#	examples/acp-agent/tests/snapshots/subagent-fork/session.1.jsonl
#	examples/acp-agent/tests/snapshots/subagent-fork/session.jsonl
#	examples/acp-agent/tests/snapshots/subagent-list-agents/session.1.jsonl
#	examples/acp-agent/tests/snapshots/subagent-list-agents/session.jsonl
#	examples/acp-agent/tests/snapshots/subagent-mixed/session.1.jsonl
#	examples/acp-agent/tests/snapshots/subagent-mixed/session.2.jsonl
#	examples/acp-agent/tests/snapshots/subagent-mixed/session.jsonl
#	examples/acp-agent/tests/snapshots/subagent-multi/session.1.jsonl
#	examples/acp-agent/tests/snapshots/subagent-multi/session.2.jsonl
#	examples/acp-agent/tests/snapshots/subagent-multi/session.jsonl
#	examples/acp-agent/tests/snapshots/subagent-published-run-failure/session.jsonl
#	examples/acp-agent/tests/snapshots/subagent-report/session.1.jsonl
#	examples/acp-agent/tests/snapshots/subagent-report/session.jsonl
#	examples/acp-agent/tests/snapshots/subagent-spawn/session.1.jsonl
#	examples/acp-agent/tests/snapshots/subagent-spawn/session.jsonl
#	examples/acp-agent/tests/snapshots/text-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/todo-write/session.jsonl
#	examples/acp-agent/tests/snapshots/tool-call-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/web-fetch/session.jsonl
#	examples/acp-agent/tests/snapshots/workflow-run/session.1.jsonl
#	examples/acp-agent/tests/snapshots/workflow-run/session.jsonl
#	examples/acp-agent/tests/snapshots/workspace-context/session.jsonl
#	examples/acp-agent/tests/snapshots/workspace-edit/session.jsonl
#	examples/headless-agent/tests/snapshots/goal-tools/stream-json.expected.jsonl
#	examples/headless-agent/tests/snapshots/pty-tools/session.jsonl
#	examples/headless-agent/tests/snapshots/pty-tools/stream-json.expected.jsonl
#	examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/child.expected.jsonl
#	examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/parent.expected.jsonl
#	examples/jsonrpc-agent/tests/snapshots/persistent-tools/notifications.expected.jsonl
#	examples/jsonrpc-agent/tests/snapshots/persistent-tools/session.jsonl
#	packages/bash/tool-bash/tests/integration.spec.ts
#	packages/context/time-context/src/index.ts
#	packages/context/tmux-context/src/index.ts
#	packages/core/agent-loop/src/agent.ts
#	packages/core/system-prompt/src/index.ts
#	packages/goal/goal/src/domain.ts
#	packages/goal/goal/src/index.ts
#	packages/goal/goal/src/render.ts
#	packages/plan/plan-mode/src/index.ts
This commit is contained in:
creatixchu
2026-08-06 11:49:03 +08:00
1357 changed files with 27674 additions and 18803 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,7 @@ import type {
SessionStartSource,
} from '@deepseek-ai/dsh-agent'
import { errorChain } from '@deepseek-ai/dsh-llm'
import { SessionId } from '@deepseek-ai/dsh-session'
import { SessionId, SessionPreparation } from '@deepseek-ai/dsh-session'
import type { Session, SessionHeader } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-system-prompt'
import type {} from '@deepseek-ai/dsh-tools'
@@ -104,6 +104,30 @@ async function raceAbort<T>(operation: PromiseLike<T> | T, signal: AbortSignal,
}
}
/** Start an abortable operation and release a value that arrives after cancellation. */
async function raceAbortCall<T>(
operation: () => PromiseLike<T> | T,
signal: AbortSignal,
id: SessionId,
releaseAbandoned?: (value: T) => void,
): Promise<T> {
if (signal.aborted) {
throw signal.reason instanceof Error
? signal.reason
: new Error(`agent "${id}" creation aborted`, { cause: signal.reason })
}
const pending = Promise.resolve().then(operation)
try {
return await raceAbort(pending, signal, id)
} catch (error: unknown) {
// oxlint-disable-next-line typescript/no-unnecessary-condition -- the signal can abort while the operation is awaited.
if (signal.aborted && releaseAbandoned !== undefined) {
void pending.then(releaseAbandoned, () => undefined)
}
throw error
}
}
/** Resolve the deployment-wide scheduler cap at the owning config boundary. */
function resolveMaxParallelToolCalls(value: number | undefined): number {
const maxParallelToolCalls = value ?? DEFAULT_MAX_PARALLEL_TOOL_CALLS
@@ -442,18 +466,7 @@ export class AgentLoop extends Service implements AgentFactory {
if (machine === undefined) await machineReady.promise
if (machine !== undefined) {
machine.cancel({ kind: 'disposed' })
// Drain to TRUE quiescence: cancel's own synchronous event chain
// (running→idle) can legitimately re-enter through an automation
// listener (goal-session's idle drive) and replace `done` with a
// fresh admission before this await captures it. The replacement
// work is cancelled and drained in turn until the slot stabilizes.
let done = machine.done
while (true) {
await Promise.allSettled([done])
if (machine.done === done) break
done = machine.done
machine.cancel({ kind: 'disposed' })
}
await machine.whenIdle()
await machine.scope.dispose()
}
} finally {
@@ -510,7 +523,7 @@ export class AgentLoop extends Service implements AgentFactory {
loopCtx.agents.announce(agent)
assertLive()
// A synchronous announce/session-start listener may have started
// teardown; the machine is already live (send() works from the
// teardown; the machine is already live (delivery works from the
// session-start seam), so only the liveness recheck is owed.
emitAgentEvent(loopCtx, agent, 'agent/session-start', source)
assertLive()
@@ -535,8 +548,8 @@ export class AgentLoop extends Service implements AgentFactory {
* @returns the published running agent.
*/
create(id: SessionId, options: AgentOptions = {}, meta: Pick<SessionHeader, 'cwd'> = {}): Agent {
const session = this.runtime.ctx.sessions.prepare(id, { meta })
const prepared = this.prepare(this.ctx, id, options, session)
using preparation = SessionPreparation.create(this.runtime.ctx.sessions.prepare(id, { meta }))
const prepared = this.prepare(this.ctx, id, options, preparation.session)
try {
return prepared.publish('startup').agent
} catch (error: unknown) {
@@ -552,14 +565,14 @@ export class AgentLoop extends Service implements AgentFactory {
* @returns the published handle.
*/
async createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle> {
const session = this.runtime.ctx.sessions.prepare(options.sessionId, {
const preparation = SessionPreparation.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
...options.seed === undefined ? {} : { seed: options.seed },
...options.meta === undefined ? {} : { meta: options.meta },
})
}))
const published = this.setupAndPublish(
ownerCtx,
options.sessionId,
session,
preparation,
options.agentOptions ?? {},
options.setup,
options.signal,
@@ -573,12 +586,14 @@ export class AgentLoop extends Service implements AgentFactory {
private async setupAndPublish(
ownerCtx: Context,
id: SessionId,
session: Session,
preparation: SessionPreparation,
agentOptions: AgentOptions,
setup: AgentSetup | undefined,
signal: AbortSignal | undefined,
source: SessionStartSource,
): Promise<AgentHandle> {
using ownedPreparation = preparation
const session = ownedPreparation.session
const prepared = this.prepare(ownerCtx, id, agentOptions, session, signal)
try {
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx), prepared.signal, id)
@@ -624,26 +639,31 @@ export class AgentLoop extends Service implements AgentFactory {
ownerAbort.signal,
this.ownership.signal,
])
let loaded: Awaited<ReturnType<SessionPersistence['load']>>
let preparation: SessionPreparation | undefined
try {
loaded = await raceAbort(persistence.load(id), fused, id)
try {
preparation = await raceAbortCall(
() => persistence.prepare(id, fused),
fused,
id,
(abandoned) => { abandoned[Symbol.dispose]() },
)
} finally {
await unfollowOwner()
}
ownerCtx.fiber.assertActive()
if (!this.ownership.isActive()) throw new Error('agent loop is not active')
return await this.setupAndPublish(
ownerCtx,
id,
preparation,
options.agentOptions ?? {},
options.setup,
options.signal,
'resume',
)
} finally {
await unfollowOwner()
}
ownerCtx.fiber.assertActive()
if (!this.ownership.isActive()) throw new Error('agent loop is not active')
const session = this.runtime.ctx.sessions.prepare(id, {
seed: loaded.events,
meta: loaded.meta,
})
const prepared = this.prepare(ownerCtx, id, options.agentOptions ?? {}, session, options.signal)
try {
const setupCommit = await raceAbort(options.setup?.(prepared.agent.ctx), prepared.signal, id)
setupCommit?.commit()
return prepared.publish('resume')
} catch (error: unknown) {
await prepared.dispose()
throw error
preparation?.[Symbol.dispose]()
}
})()
this.ownership.trackWrapper(published)

View File

@@ -6,7 +6,7 @@
import type { Context } from 'cordis'
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 { foldRequestHeader } from '@deepseek-ai/dsh-session'
const PACKAGE_NAME = '@deepseek-ai/dsh-agent-loop'
@@ -17,8 +17,7 @@ export const inject = ['invariants']
/** Install the request-reconstruction contribution into its child registration fiber. */
const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => {
// Prepend prevents a short-circuiting replay listener from silencing the
// check; correctness itself comes from the sequence-bounded reconstruction.
// Prepend prevents a short-circuiting replay listener from silencing the check.
ctx.on('llm/stream', (options: GenerateOptions, next) => {
if (!isAgentLoopRequest(options)) return next()
if (!Object.isFrozen(options)) fail('a loop-built request must be frozen')
@@ -30,27 +29,16 @@ const install: InvariantInstaller = Object.assign((ctx: Context, fail: Invariant
}
const events = session.events
let boundary = -1
for (let index = events.length - 1; index >= 0; index -= 1) {
if (events[index]?.type === 'step/start') {
boundary = index
break
}
}
if (boundary === -1) {
if (!events.some(event => event.type === 'step/start')) {
return fail('a loop-built request with no step/start in its session log')
}
const header = foldRequestHeader(events)
if (header === undefined) {
return fail('a loop-built request with no request/header event in its session log')
}
const rebuilt = Session.create(
SessionId(`${String(session.id)}-invariant-rebuild`),
structuredClone(events.slice(0, boundary)),
)
const expected = rebuilt.deriveMessages()
const expected = session.deriveMessages()
if (JSON.stringify(options.messages) !== JSON.stringify(expected)) {
fail(`llm request for session "${String(session.id)}" diverges from the boundary derivation (log-reconstruction desync)`)
fail(`llm request for session "${String(session.id)}" diverges from the dispatch-time durable derivation (log-reconstruction desync)`)
}
const headerMatches = options.model === header.config.model

View File

@@ -0,0 +1,76 @@
/**
* 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 '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 },
})
}
}

View File

@@ -42,7 +42,7 @@ interface GroupOutcome {
* Ordinary completion and abort commit started-call results in order. Abort
* drains them, records synthetic results for unstarted calls, and returns with
* the signal still aborted after accepting started-call context through the
* caller-supplied acceptor (the machine stages it on its outbox for the next
* caller-supplied acceptor (the machine stages it in its next-step inbox for the
* step boundary). An internal scheduler failure stops new dispatches, drains
* already-started dispatches, and rejects with the first failure without
* fabricating tool results.