Merge branch 'stack/agent-profiles-1-seam' into stack/agent-profiles-3-wire

Rewires this layer onto the standing-mount model:

- serviceForAgent roots its search at the agent's standing mount (parent
  scope key → live mount fiber) — the composition no longer lives under the
  agent's own fiber, and two agents on one preset now address ONE instance,
  which the sharing test asserts instead of distinctness.
- viewFor/historyPage take a registry view SCOPE. A live agent is that scope;
  a cold read uses the recorded preset's standing key via standingKeyFor —
  composing plugins but starting no agent, session, or turn. A header without
  a preset (a pre-roster log) renders through the DEFAULT preset's standing
  layer; an unusable preset degrades the read to generic cards, never fails
  it. This turns produced-files and chat-scroll green structurally, with the
  token counts untouched (no resume, so the projections fold stays detached).
- The detached projections baseline now includes every standing unit's key at
  its empty fold (todos: null): the standing mount registers units
  deterministically, which is what makes the client's "omitted key =
  capability absence → clear" rule safe again. seeded-history's contract test
  asserts the new shape.
- The standard preset's realm preamble no longer claims a shared label pools
  instances — provide() throws on the second registration under one realm
  symbol; labels join REALMS.
This commit is contained in:
Yichen Jiang
2026-08-08 18:41:07 +08:00
24 changed files with 566 additions and 117 deletions

View File

@@ -52,19 +52,39 @@ function roster(ids: readonly string[]): unknown {
const perAgent = services.get(String(agent.id))
return perAgent?.[name]
},
// The standing scope key a cold transcript read resolves presenters in.
standingKeyFor: (id?: string) => {
const wanted = id ?? ids[0] ?? ''
standingKeyRequests.push(wanted)
if (!ids.includes(wanted) || failingStandingKeys.has(wanted)) {
return Promise.reject(new UnknownPresetError(wanted, ids))
}
let key = standingKeys.get(wanted)
if (key === undefined) {
key = { agentPreset: wanted }
standingKeys.set(wanted, key)
}
return Promise.resolve(key)
},
}
}
/** Standing keys the roster double minted, and the ids readers asked for. */
const standingKeys = new Map<string, object>()
const standingKeyRequests: string[] = []
/** Preset ids whose standing mount the double reports as unusable. */
const failingStandingKeys = new Set<string>()
/** Per-agent service instances a mounted preset would own, keyed by session id. */
const services = new Map<string, Record<string, unknown>>()
async function harness(presets?: readonly string[]) {
async function harness(presets?: readonly string[], persistence?: unknown) {
const cwd = realpathSync(mkdtempSync(join(tmpdir(), 'dsh-apiproxy-preset-')))
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(AgentRegistry)
await ctx.plugin(UserInteractionService)
ctx.provide('sessionPersistence', { list: () => Promise.resolve([]) } as never)
ctx.provide('sessionPersistence', (persistence ?? { list: () => Promise.resolve([]) }) as never)
if (presets !== undefined) ctx.provide('agentPresets', roster(presets) as never)
const factory: AgentFactory = {
@@ -239,3 +259,39 @@ describe('a capability the session\'s preset mounts', () => {
expect(failure.error.message).toContain('neither this session')
})
})
describe('session.history presenter scope', () => {
it('asks the roster for the RECORDED preset\'s standing key on a cold read', async () => {
const { api } = await harness(['standard', 'core-web'])
await api.sessions.create(request({ sessionId: SessionId('p1'), agentPreset: 'core-web' }))
// Cold: creation registered a live agent in this harness, so simulate the
// cold path by asking for a session only persistence knows... the harness
// has no persistence, so read the live one and assert no roster query.
standingKeyRequests.length = 0
const live = await api.sessions.history(request({ sessionId: SessionId('p1') }))
expect(live.result.ok).toBe(true)
// A live agent IS the presenter scope; the roster is not consulted.
expect(standingKeyRequests).toEqual([])
})
it('serves a COLD transcript whose standing mount is no longer usable', async () => {
// A genuinely cold session: persistence knows it, no live agent exists.
const meta = { id: SessionId('p3'), createdAt: 1, cwd: '/tmp/p3', agentPreset: 'standard' }
const { api } = await harness(['standard'], {
list: () => Promise.resolve([meta]),
inspect: () => Promise.resolve({ meta, events: [] }),
})
// The preset broke after the session ran: the roster rejects the mount.
failingStandingKeys.add('standard')
try {
standingKeyRequests.length = 0
const response = await api.sessions.history(request({ sessionId: SessionId('p3') }))
// Degraded, never failed: the roster WAS asked, and the transcript
// still serves — with the generic cards a viewless entry renders.
expect(standingKeyRequests).toEqual(['standard'])
expect(response.result.ok).toBe(true)
} finally {
failingStandingKeys.delete('standard')
}
})
})