fix(web): return every service a host row injects, and run the browser lane
The browser e2e lane had been failing wholesale since this stack moved the agent plane into presets, and nothing caught it: 34 of 48 files. Two of the causes are product defects, not test breakage. `bashEnv` goes back to the host plane. `apps/cli/src/web.ts` injects it to publish `DSH_WEB_URL`/`DSH_WEB_MODE`, so the earlier note that "nothing outside the agent plane injects bashEnv" was simply wrong — behind a preset's `shell` realm those variables reached no shell at all, and a `dsh web` agent could not find the address of its own interface. This is the same criterion that returned `subagents`: a host row that injects a service resolves before any session exists and has no agent to key by, so the service is host-plane. `tool-bash` consumes the host registry from inside the preset, which works because an agent context chains to the host; only the reverse is invisible. `tool-subagent-report` goes back with it. It is not a tool this agent calls: it registers a continuable SETUP on the host `subagents` singleton, and that list is not scope-aware. One copy per mounted preset meant every child was handed `report` once per live session, so the second registration threw and a cold subagent resume failed with `subagent-not-resumable` — a diagnostic three layers removed from the cause. The lane's own composition facts follow. Skill roots resolve inside a preset now, a subtree include patches cannot reach, so the scaffold pins the roots' documented environment fallback for its whole lifetime rather than for the boot — presets mount per session. Without it the developer's real `~/.dsh/skills` enters replay requests and goldens while CI sees none. The `apps/cli` composition test pins `storage-json` for the same reason: unpinned it wrote, and then read back, the developer's own `~/.dsh/storages/`. Three tests now address through an agent what they used to read off the root context, because that is where the thing lives: the tool catalog, the skill registry, and the token meter. The seeded-history projection baseline asserts the opposite of what it did — a detached session yields a preset-plane projection only from a durable checkpoint written while it was live, and this seed was written straight to persistence and never ran. Goldens re-recorded for the hero's preset chip and the settings nav entry.
This commit is contained in:
@@ -18,6 +18,7 @@ import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContentBlock, Message } from '@deepseek-ai/dsh-llm'
|
||||
import { deriveEventMessage, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type {} from '@deepseek-ai/dsh-agent-presets'
|
||||
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import type { TokenMeterService } from '@deepseek-ai/dsh-token-meter'
|
||||
import { join } from 'node:path'
|
||||
@@ -170,10 +171,22 @@ describe('web e2e: seeded history renders through cold resume', () => {
|
||||
if (MODE !== 'record') {
|
||||
const raw = await readFile(SEED, 'utf8')
|
||||
expect(fixtureUserPrompts(raw), 'seed fixture must carry exactly the drive prompt').toEqual([PROMPT])
|
||||
const meter = scaffold.ctx.get('tokenMeter')
|
||||
if (meter === undefined) throw new Error('seeded-history requires the composed token meter')
|
||||
const realized = realizeSeedFixture(scaffold, raw, SEED_ID)
|
||||
await seedSession(scaffold, withCompaction(realized, meter), SEED_ID)
|
||||
// The meter belongs to an agent's preset, not to the process — token
|
||||
// accounting is per session. It is used here as a pure pricing function
|
||||
// over fixture content, so a throwaway composition is enough to reach one.
|
||||
const priced = await scaffold.ctx.agents.create({
|
||||
sessionId: SessionId('seeded-history-pricing'),
|
||||
setup: agentCtx => scaffold.ctx.agentPresets.mount(agentCtx).then(() => undefined),
|
||||
})
|
||||
let realizedWithCompaction: string
|
||||
try {
|
||||
const meter = scaffold.ctx.agentPresets.serviceFor(priced.agent, 'tokenMeter')
|
||||
if (meter === undefined) throw new Error('seeded-history requires the composed token meter')
|
||||
realizedWithCompaction = withCompaction(realizeSeedFixture(scaffold, raw, SEED_ID), meter)
|
||||
} finally {
|
||||
await priced.dispose()
|
||||
}
|
||||
await seedSession(scaffold, realizedWithCompaction, SEED_ID)
|
||||
}
|
||||
browser = await chromium.launch()
|
||||
page = await newEnglishPage(browser)
|
||||
@@ -220,11 +233,17 @@ describe('web e2e: seeded history renders through cold resume', () => {
|
||||
const projections = body.result.value?.projections
|
||||
expect(projections).toBeDefined()
|
||||
expect(projections?.asOfSeq).toBeGreaterThanOrEqual(0)
|
||||
// The seed carries a session/title event: the title unit must serve it.
|
||||
// The seed carries a session/title event: the title unit is host-plane, so
|
||||
// it folds the detached log and serves the value with nothing composed.
|
||||
expect(typeof projections?.values.title).toBe('string')
|
||||
// tool-todo is composed but the seed has no todo/write: whole-value null,
|
||||
// key PRESENT (absence would mean the unit never registered).
|
||||
expect(projections?.values).toHaveProperty('todos', null)
|
||||
// `todos` is NOT here, and that is the contract rather than a gap. Its unit
|
||||
// is registered by `tool-todo` inside an agent's preset, so a detached
|
||||
// session yields it from exactly one place: a durable checkpoint written
|
||||
// while the session was live. This seed was written straight to persistence
|
||||
// and never ran, so it recorded none — and the answer no longer depends on
|
||||
// whether some UNRELATED session happens to be composed right now, which is
|
||||
// the whole reason the checkpoint row carries its own view.
|
||||
expect(projections?.values).not.toHaveProperty('todos')
|
||||
})
|
||||
|
||||
it.skipIf(MODE === 'record')('lists the seeded session cold and renders its history from the log', async () => {
|
||||
|
||||
Reference in New Issue
Block a user