fix(apiproxy): serve history events and projections from one log position

Review found two defects in the previous commit's ordering fix.

@pku-xht: `historyStateFor` copied the attached session's events, the handler
then awaited `presenterScopeFor`, and only then read the projection baseline
off the still-live Session. An append during that await served events cut at N
beside a baseline folded to N+1 — one response describing two moments. The
same restructure had also moved the baseline read outside the `try`, so a
failing snapshot escaped the structured `internal` error.

Both awaits now happen before the cut: `historySourceFor` resolves which
session serves the read, `presenterScopeFor` ensures the recorded composition,
and `historyCutOf` then reads events and baseline adjacently with nothing
between them. The whole sequence is back inside the try.

The invariant judged any scoped assembly with a chain of one as an unjoined
agent, which rejects a legitimate assembly in a standing preset key (that key
has no parent of its own). It now gates on `context.agent` — a scope-only read
is not an agent and is out of range by construction rather than by a premise
about who else calls `assemble` — and asks the roster's own
`composedPreset()` instead of introspecting chain length. The advisory warning
uses the same relation.

Also from review: the `2026-08-05-per-agent-tool-presentation` note still
described `presentAs` as per-agent, which standing mounts made false and this
branch's own rewording contradicts; the duplicated "process-wide unit table"
argument collapses to the Agent Note with pointers from the five copies; a
dead `.sort()` before `arrayContaining`; and change-history narration in the
tool-cordis README.
This commit is contained in:
Yichen Jiang
2026-08-11 10:45:15 +08:00
parent 19ea7ae573
commit 61d5cb9e41
15 changed files with 146 additions and 123 deletions

View File

@@ -107,12 +107,9 @@ async function run(ctx: Context, task: string, io: HeadlessIo): Promise<void> {
const selection = defaultModel.currentSelection()
// This bundle composes no preset roster, so the model-facing rows sit in the
// host plane and the agent reads them from the global registry layer. A
// deployment that DOES configure `dsh-agent-presets` has to join one here
// first: registration visibility inherits only along the `dsh-scope` parent
// chain, and `AgentPresets.mount()` is the only thing that links an agent to
// it, so an agent created without that link reads an empty global layer and
// reaches the model with no tools at all.
// host plane and the agent reads them from the global layer. A deployment
// that DOES configure one has to join it here first
// (@deepseek-ai/dsh-agent-presets README, "Composing a child agent").
const { agent } = await agents.create({
sessionId: SessionId(`session-${randomUUID()}`),
meta: { cwd: process.cwd() },