fix(web): correct the preset-layer contracts review found stale

None of these change behavior; each said something that was not true.

`SessionCwdConflict`'s doc block had been left stranded above the
`AgentPresetConflict` inserted under it, so one class carried a comment
about the other and the second carried none.

The roster comment named a `.system` directory that does not exist; the
shipped root is `config/agent-presets/`, and `system` is the trust its
entries carry.

The real-composition test attributed the disabled `api-gateway` row to
"side effects outside this process" alongside the port and the exporter.
It is disabled for a different reason — the api-proxy cannot mount in
this layer at all — and hiding that behind the same phrase would leave a
later layer unable to tell whether the line can come out.

One test claimed to refuse an adoption while asserting only that the
header records the preset; it now says what it checks.

`PERSONA_SECTION`/`PERSONA_ORDER` existed twice, once in the registry
that declares the slot and once restated in the row that replaces it —
a drift that would land a preset's persona beside the deployment's
instead of shadowing it. The registry exports them now.

The preset conflict message read "already runs agent preset undefined"
for a session that records none, which is the shape a deployment with no
roster produces; it names that case instead, with the regression that
reaches it through the gateway.

Finally, `PresetTree.write()` drops the `loader/config-update` the
inherited method emits — recorded on the override, since a future
edit-while-running flow needs its own persistence path.
This commit is contained in:
Yichen Jiang
2026-08-06 13:43:32 +08:00
parent c58cc23d45
commit 5ed79887fb
19 changed files with 119 additions and 37 deletions

View File

@@ -642,7 +642,6 @@ class SubagentSessionOwnership extends Error {
}
}
/** Requested identity already belongs to a session with another project cwd. */
/**
* The requested preset differs from the one this session already runs.
*
@@ -658,12 +657,16 @@ class AgentPresetConflict extends Error {
readonly existingPreset: string | undefined,
) {
super(
`session "${sessionId}" already runs agent preset ${JSON.stringify(existingPreset)}; `
existingPreset === undefined
? `session "${sessionId}" records no agent preset, so it cannot be adopted under one; `
+ 'a deployment composing no roster records none on any session — '
: `session "${sessionId}" already runs agent preset ${JSON.stringify(existingPreset)}; `
+ `requested ${JSON.stringify(requestedPreset)}. A session's preset is fixed at creation.`,
)
}
}
/** Requested identity already belongs to a session with another project cwd. */
class SessionCwdConflict extends Error {
constructor(
readonly sessionId: SessionId,

View File

@@ -153,6 +153,28 @@ describe('session.create with an agent preset', () => {
expect(ctx.sessions.get(SessionId('s6'))?.header.agentPreset).toBeUndefined()
})
it('says why a preset-less session cannot be adopted under one', async () => {
// Two callers reach this: a deployment that composes no roster, and a
// session created before one existed. Both record no preset, so naming
// any is a conflict rather than an adoption — the history was produced
// under a composition this roster cannot name. The message has to say
// that, because "already runs agent preset undefined" reads as a bug.
const { api } = await harness()
await api.sessions.create(request({ sessionId: SessionId('s7') }))
const response = await api.sessions.create(request({ sessionId: SessionId('s7'), agentPreset: 'standard' }))
expect(response.result.ok).toBe(false)
if (response.result.ok) throw new Error('unreachable')
expect(response.result.error.code).toBe('agent-preset-conflict')
expect(response.result.error.message).toContain('records no agent preset')
expect(response.result.error.details).toEqual({
sessionId: 's7',
requestedPreset: 'standard',
existingPreset: undefined,
})
})
})
/**