mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge remote-tracking branch 'origin/master' into codex/send-one-turn
# Conflicts: # docs/architecture.md # docs/core-data-structures/core.md # docs/rfc/implemented/feature/2026-06-30-interception-seams.md # packages/core/agent-loop/src/loop.ts # packages/core/agent/src/types.ts
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import { agentEvents } from '@deepseek-ai/dsh-agent'
|
||||
import type { AgentId, AgentOptions, AgentStatus, SendOptions } from '@deepseek-ai/dsh-agent'
|
||||
import type { AgentId, AgentOptions, AgentStatus, InjectOptions, SendOptions } from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { deepFreeze } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContentBlock, MessageSource } from '@deepseek-ai/dsh-llm'
|
||||
@@ -225,14 +225,20 @@ export class ReactLoopAgent implements Agent {
|
||||
agentEvents(this.loopCtx, this).emit('agent/queued', accepted.content, info)
|
||||
}
|
||||
|
||||
inject(content: ContentBlock[], options?: SendOptions): void {
|
||||
inject(content: ContentBlock[], options?: InjectOptions): void {
|
||||
this.assertNotDisposed()
|
||||
const source = this.resolveSource(options)
|
||||
const context = {
|
||||
content,
|
||||
source,
|
||||
...options?.envelope !== undefined ? { envelope: options.envelope } : {},
|
||||
...options?.meta !== undefined ? { meta: options.meta } : {},
|
||||
}
|
||||
if (isTurnOpen(this.session)) {
|
||||
// A turn is open in the LOG (decided from the log, not agent status —
|
||||
// status can be `running` with no turn open): the context/message is
|
||||
// turn-enclosed by that turn, so append it directly.
|
||||
this.session.append('context/message', { content, source }, { surfaceOp: 'append' })
|
||||
this.session.append('context/message', context, { surfaceOp: 'append' })
|
||||
return
|
||||
}
|
||||
// No turn open: wrap the injection in a one-shot turn so every event stays
|
||||
@@ -244,7 +250,7 @@ export class ReactLoopAgent implements Agent {
|
||||
// are contained by Session and cannot create a false append failure.
|
||||
try {
|
||||
this.session.append('turn/start', { turn, trigger: { kind: 'injection', source } })
|
||||
this.session.append('context/message', { content, source }, { surfaceOp: 'append' })
|
||||
this.session.append('context/message', context, { surfaceOp: 'append' })
|
||||
} finally {
|
||||
// Close the turn if turn/start made it into the log. A pre-commit veto
|
||||
// must escape rather than being mistaken for a committed turn/end.
|
||||
|
||||
@@ -217,10 +217,15 @@ async function runTurn(
|
||||
// `allow.content` REPLACES the prompt bytes (a rewrite); absent keeps them.
|
||||
const content = promptDecision.content ?? message.content
|
||||
session.append('user/message', { content, source: message.source }, { surfaceOp: 'append' })
|
||||
// `allow.additionalContext` is a SEPARATE context/message the next request
|
||||
// also sees. The turn is open, so inject() appends it into THIS turn.
|
||||
if (promptDecision.additionalContext) {
|
||||
agent.inject(promptDecision.additionalContext.content, { source: promptDecision.additionalContext.source })
|
||||
// Every `allow.additionalContexts` entry is a separate context/message the
|
||||
// next request also sees. The turn is open, so inject() appends each one
|
||||
// into THIS turn without flattening provenance, framing, or metadata.
|
||||
for (const context of promptDecision.additionalContexts ?? []) {
|
||||
agent.inject(context.content, {
|
||||
source: context.source,
|
||||
...context.envelope !== undefined ? { envelope: context.envelope } : {},
|
||||
...context.meta !== undefined ? { meta: context.meta } : {},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +572,7 @@ async function runStep(
|
||||
// Persist tool-owned presentation data for replay.
|
||||
...result.meta !== undefined ? { meta: result.meta } : {},
|
||||
}, { surfaceOp: 'append', sourceEventSeqs: [callEvent.seq] })
|
||||
if (result.additionalContext) pendingContext.push(result.additionalContext)
|
||||
pendingContext.push(...result.additionalContexts ?? [])
|
||||
// The signal may flip while the tool is awaited.
|
||||
/* v8 ignore start -- signal.reason default unreachable: cancel()/disposal always set it */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
@@ -577,7 +582,11 @@ async function runStep(
|
||||
|
||||
// Append buffered context after the complete result batch.
|
||||
for (const context of pendingContext) {
|
||||
agent.inject(context.content, { source: context.source })
|
||||
agent.inject(context.content, {
|
||||
source: context.source,
|
||||
...context.envelope !== undefined ? { envelope: context.envelope } : {},
|
||||
...context.meta !== undefined ? { meta: context.meta } : {},
|
||||
})
|
||||
}
|
||||
|
||||
return { hadToolCalls: toolCalls.length > 0, finish: assembler.finish }
|
||||
|
||||
Reference in New Issue
Block a user