mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
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.
100 lines
4.0 KiB
TypeScript
100 lines
4.0 KiB
TypeScript
// Boots the shipped Web composition over the built dist this lane already uses
|
|
// and asserts what that composition produces: the model-visible tool catalog
|
|
// and the sandbox/approval knobs it ships with. No browser and no model call —
|
|
// these are composition facts, and the browser scenarios in this lane cover the
|
|
// surface itself.
|
|
import { tmpdir } from 'node:os'
|
|
import { afterEach, expect, it } from 'vitest'
|
|
import { canonicalPath, writableRoots } from '@deepseek-ai/dsh-sandbox'
|
|
// Empty type imports carry the tools/sandboxPolicy/approval Context merges.
|
|
import type {} from '@deepseek-ai/dsh-tools'
|
|
import type {} from '@deepseek-ai/dsh-sandbox-policy'
|
|
import type {} from '@deepseek-ai/dsh-user-approval'
|
|
import type {} from '@deepseek-ai/dsh-permission'
|
|
import { SessionId } from '@deepseek-ai/dsh-session'
|
|
import type {} from '@deepseek-ai/dsh-agent-presets'
|
|
import { launchWebScaffold, type WebScaffold } from './scaffold.ts'
|
|
|
|
/**
|
|
* The catalog the shipped Web composition puts in front of the model, minus the
|
|
* ripgrep-dependent pair below. The absences are deliberate, not incidental
|
|
* gaps: the `cordis_*` toolset executes model-written JavaScript that no
|
|
* sandbox row confines, `web_fetch` chooses its own request target, and
|
|
* `mcp_*` servers spawn outside `ctx.bash`. The composition Agent Note owns the
|
|
* rationale and its sources.
|
|
*/
|
|
const EXPECTED_TOOLS = [
|
|
'ask_user_question',
|
|
'bash',
|
|
'create_goal',
|
|
'edit',
|
|
'exit_plan_mode',
|
|
'get_goal',
|
|
'list_agents',
|
|
'ralph',
|
|
'read',
|
|
'send_message',
|
|
'skill',
|
|
'str_replace_editor',
|
|
'subagent',
|
|
'subagent_fork',
|
|
'task_kill',
|
|
'task_list',
|
|
'task_output',
|
|
'todo_write',
|
|
'update_goal',
|
|
'web_search',
|
|
'workflow',
|
|
'write',
|
|
]
|
|
|
|
/**
|
|
* `glob` and `grep` come from `dsh-tool-fs-search`, which spawns the PACKAGED
|
|
* ripgrep binary (`@vscode/ripgrep`) through the subprocess seam, so the pair
|
|
* is always present on every host — asserted as fixed members, not a host
|
|
* dependency.
|
|
*/
|
|
const RIPGREP_TOOLS = ['glob', 'grep']
|
|
|
|
let scaffold: WebScaffold | undefined
|
|
|
|
afterEach(async () => {
|
|
await scaffold?.close()
|
|
scaffold = undefined
|
|
})
|
|
|
|
it('assembles the shipped Web catalog with the confined access default', async () => {
|
|
scaffold = await launchWebScaffold()
|
|
const ctx = scaffold.ctx
|
|
// The catalog belongs to an AGENT, not to the process: every model-facing row
|
|
// now lives in a preset mounted under one session's scope, so the global
|
|
// layer holds nothing and a caller must name the agent to see anything. This
|
|
// composes from the deployment default — what a session that names no preset
|
|
// gets — which is the shape this test has always been about.
|
|
expect(ctx.tools.schemas().map(schema => schema.name)).toEqual([])
|
|
const handle = await ctx.agents.create({
|
|
sessionId: SessionId('shipped-composition'),
|
|
setup: agentCtx => ctx.agentPresets.mount(agentCtx).then(() => undefined),
|
|
})
|
|
try {
|
|
const names = ctx.tools.schemas(handle.agent).map(schema => schema.name).sort()
|
|
expect(names.filter(name => !RIPGREP_TOOLS.includes(name))).toEqual(EXPECTED_TOOLS)
|
|
// The packaged ripgrep binary ships with the dependency, so the pair is a
|
|
// fixed roster member on every host.
|
|
expect(names.filter(name => RIPGREP_TOOLS.includes(name))).toEqual(RIPGREP_TOOLS)
|
|
} finally {
|
|
await handle.dispose()
|
|
}
|
|
// `workspace-write` is not "the workspace and nothing else": the shared roots
|
|
// helper always admits the temp directories too. Pinning it against an
|
|
// explicit mode keeps the claim independent of this surface's default, and
|
|
// keeps a future boundary test from being run inside /tmp — where an
|
|
// "escape" write succeeds by design and reads as a sandbox failure.
|
|
expect(writableRoots(scaffold.ctx.sandboxPolicy.resolve({ mode: 'workspace-write' }))).toEqual(
|
|
expect.arrayContaining([canonicalPath('/tmp'), canonicalPath(tmpdir())]),
|
|
)
|
|
expect(scaffold.ctx.sandboxPolicy.defaultMode).toBe('workspace-write')
|
|
expect(scaffold.ctx.approval.config.policy).toBe('ask')
|
|
expect(scaffold.ctx.permission.defaultPreset).toBe('workspace-write')
|
|
}, 120_000)
|