2026-07-30 15:48:05 +08:00
// Web acceptance for current sandbox-policy context. A real Chromium drives
// the shipped /permission command through all three presets; record mode uses
// the real provider, while replay keeps the same provider-authored behavior
2026-07-30 22:15:34 +08:00
// keyless. Assertions read the exact durable header, runtime-context messages,
// and tool calls, so assistant prose alone cannot satisfy the scenario.
2026-07-30 15:48:05 +08:00
import { readFile } from 'node:fs/promises'
2026-07-30 19:30:11 +08:00
import { join } from 'node:path'
2026-07-30 15:48:05 +08:00
import { fileURLToPath } from 'node:url'
import type { Browser , Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll , beforeAll , describe , expect , it , onTestFailed } from 'vitest'
2026-07-30 18:51:29 +08:00
import { canonicalPath } from '@deepseek-ai/dsh-sandbox'
2026-07-30 15:48:05 +08:00
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import {
assertFixtureInventory , fixtureUserPrompts , launchWebScaffold , recordFixture ,
watchConsole , webSnapshotMode , type WebScaffold ,
} from './scaffold.ts'
import { connectFreshWorkspace , newEnglishPage , saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath ( new URL ( './snapshots/permission-policy-context' , import . meta . url ) )
const FIXTURE = fileURLToPath ( new URL ( './snapshots/permission-policy-context/session.jsonl' , import . meta . url ) )
const MODE = webSnapshotMode ( )
const PROMPTS = [
'Can you create or edit a normal file right now under the current policy? Answer directly in one sentence. Do not call a tool just to discover the policy.' ,
'Does the DSH file sandbox currently restrict file operations? Answer directly in one sentence. Do not call tools.' ,
2026-07-30 15:51:03 +08:00
'Reply with exactly WORKSPACE_POLICY_SEEN. Do not call tools.' ,
2026-07-30 22:15:34 +08:00
'Create the relative path policy-neutral.txt in the current workspace containing exactly POLICY_NEUTRAL_OK, verify its contents, then report completion.' ,
2026-07-30 15:48:05 +08:00
] as const
2026-07-31 13:17:31 +08:00
const PRESET_LABELS = [ 'Read Only' , 'Full access' , 'Workspace Write' ] as const
2026-07-30 15:48:05 +08:00
function requestSystems ( events : readonly SessionEvent [ ] ) : string [ ] {
return events . flatMap ( ( event ) = > {
if ( event . type !== 'request/header' ) return [ ]
return typeof event . data . header . system === 'string' ? [ event . data . header . system ] : [ ]
} )
}
2026-07-30 22:15:34 +08:00
function runtimeContexts ( events : readonly SessionEvent [ ] ) : string [ ] {
return events . flatMap ( ( event ) = > {
if ( event . type !== 'user/message'
|| event . data . source . kind !== 'plugin'
|| event . data . source . plugin !== '@deepseek-ai/dsh-system-prompt' ) return [ ]
return event . data . content . flatMap ( block = > block . type === 'text' ? [ block . text ] : [ ] )
} )
}
2026-07-30 15:48:05 +08:00
function assistantTexts ( events : readonly SessionEvent [ ] ) : string [ ] {
return events . flatMap ( ( event ) = > {
if ( event . type !== 'assistant/message' ) return [ ]
2026-07-30 19:30:11 +08:00
const text = event . data . message . content . flatMap ( block = > block . type === 'text' ? [ block . text ] : [ ] ) . join ( '' ) . replaceAll ( '**' , '' )
return text . length === 0 ? [ ] : [ text ]
2026-07-30 15:48:05 +08:00
} )
}
2026-07-30 19:30:11 +08:00
function callArgs ( event : Extract < SessionEvent , { type : 'tool/call' } > ) : Record < string , unknown > {
return JSON . parse ( event . data . arguments ) as Record < string , unknown >
}
2026-07-30 15:48:05 +08:00
describe ( 'web e2e: current sandbox policy reaches the model before tools' , ( ) = > {
let scaffold : WebScaffold
let browser : Browser
let page : Page
let tripwire : ReturnType < typeof watchConsole >
2026-07-30 19:30:11 +08:00
let disposeApproval : ( ( ) = > void ) | undefined
2026-07-30 15:48:05 +08:00
let sessionWorkspace : string | undefined
const sessionEvents : SessionEvent [ ] = [ ]
beforeAll ( async ( ) = > {
scaffold = await launchWebScaffold ( MODE === 'record' ? { } : { replayFixture : FIXTURE } )
2026-07-30 19:30:11 +08:00
disposeApproval = scaffold . ctx . on ( 'approval/request' , ( ) = > Promise . resolve ( 'allowed-once' ) , { prepend : true } )
2026-07-30 15:48:05 +08:00
scaffold . ctx . on ( 'session/event' , ( session , event : SessionEvent ) = > {
sessionWorkspace = session . header . cwd
sessionEvents . push ( event )
} )
browser = await chromium . launch ( )
page = await newEnglishPage ( browser )
tripwire = watchConsole ( page )
await page . goto ( scaffold . baseUrl , { waitUntil : 'load' } )
await page . waitForSelector ( '[class*="frame"]' , { timeout : 30_000 } )
2026-07-31 16:58:42 +08:00
await connectFreshWorkspace ( page , scaffold . workspaceCwd )
2026-07-30 15:48:05 +08:00
} , 120 _000 )
afterAll ( async ( ) = > {
await browser ? . close ( )
2026-07-30 19:30:11 +08:00
disposeApproval ? . ( )
2026-07-30 15:48:05 +08:00
await scaffold ? . close ( )
} )
it ( 'switches read-only, danger-full-access, and workspace-write through the real GUI command path' , async ( ) = > {
onTestFailed ( ( ) = > saveFailureShot ( page , 'web-e2e-permission-policy-context' ) )
if ( MODE !== 'record' ) {
expect ( fixtureUserPrompts ( await readFile ( FIXTURE , 'utf8' ) ) ) . toEqual ( PROMPTS )
}
const input = page . locator ( 'textarea' ) . first ( )
let sessionId : Awaited < ReturnType < WebScaffold [ 'whenTurnSettled' ] > > | undefined
for ( const [ index , preset ] of [ 'read-only' , 'danger-full-access' , 'workspace-write' ] . entries ( ) ) {
await input . fill ( ` /permission ${ preset } ` )
await input . press ( 'Enter' )
await page . getByRole ( 'button' , { name : ` Access mode, current: ${ PRESET_LABELS [ index ] } ` } )
. waitFor ( { timeout : 10_000 } )
const settled = scaffold . whenTurnSettled ( )
await input . fill ( PROMPTS [ index ] as string )
await input . press ( 'Enter' )
sessionId = await settled
await expect . poll ( ( ) = > input . isEnabled ( ) , { timeout : 10_000 } ) . toBe ( true )
}
2026-07-30 19:30:11 +08:00
await input . fill ( '/permission read-only' )
await input . press ( 'Enter' )
await page . getByRole ( 'button' , { name : 'Access mode, current: Read Only' } ) . waitFor ( { timeout : 10_000 } )
const settled = scaffold . whenTurnSettled ( )
await input . fill ( PROMPTS [ 3 ] )
await input . press ( 'Enter' )
sessionId = await settled
2026-07-30 15:48:05 +08:00
if ( sessionId === undefined ) throw new Error ( 'permission-policy scenario completed no model turn' )
if ( MODE === 'record' ) await recordFixture ( scaffold , sessionId , FIXTURE )
} , 240 _000 )
2026-07-30 22:15:34 +08:00
it . skipIf ( MODE === 'record' ) ( 'records cache-safe current policy before the corresponding model behavior' , async ( ) = > {
2026-07-30 15:48:05 +08:00
const systems = requestSystems ( sessionEvents )
2026-07-30 22:15:34 +08:00
expect ( systems ) . toHaveLength ( 1 )
expect ( systems [ 0 ] ) . not . toContain ( 'Current DSH file policy:' )
expect ( systems [ 0 ] ) . not . toContain ( 'Approval policy:' )
expect ( systems [ 0 ] ) . not . toContain ( 'Approval prompts are disabled in this session' )
const contexts = runtimeContexts ( sessionEvents )
expect ( contexts ) . toHaveLength ( 4 )
2026-07-31 13:28:02 +08:00
expect ( contexts [ 0 ] ) . toContain ( 'Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode.' )
expect ( contexts [ 0 ] ) . toContain ( 'Do not refuse a required modification from this policy alone' )
2026-07-30 22:15:34 +08:00
expect ( contexts [ 0 ] ) . toContain ( 'Approval policy: ask.' )
2026-07-31 13:28:02 +08:00
expect ( contexts [ 1 ] ) . toContain ( 'Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.' )
2026-07-30 22:15:34 +08:00
expect ( contexts [ 1 ] ) . toContain ( 'Approval prompts are disabled in this session' )
2026-07-30 15:48:05 +08:00
if ( sessionWorkspace === undefined ) throw new Error ( 'permission-policy scenario observed no session workspace' )
2026-07-31 13:28:02 +08:00
expect ( contexts [ 2 ] ) . toContain ( ` Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: ${ JSON . stringify ( canonicalPath ( sessionWorkspace ) ) } . Some platform temporary areas may also be writable. ` )
2026-07-30 22:15:34 +08:00
expect ( contexts [ 2 ] ) . toContain ( 'Approval policy: ask.' )
expect ( contexts [ 2 ] ) . not . toContain ( 'Approval prompts are disabled in this session' )
expect ( contexts [ 3 ] ) . toContain ( 'Current DSH file policy: read-only.' )
2026-07-30 15:48:05 +08:00
const answers = assistantTexts ( sessionEvents )
2026-07-30 19:30:11 +08:00
expect ( answers . length ) . toBeGreaterThanOrEqual ( 4 )
2026-07-30 22:15:34 +08:00
expect ( answers [ 0 ] ) . toMatch ( /read-only.*(?:denied|cannot modify|cannot create or edit)/i )
expect ( answers [ 1 ] ) . toMatch ( /does not restrict.*(?:file operations|(?:write\/edit tools|write and edit tools).*one-shot bash commands)/i )
2026-07-30 15:51:03 +08:00
expect ( answers [ 2 ] ) . toBe ( 'WORKSPACE_POLICY_SEEN' )
2026-07-30 19:30:11 +08:00
const calls = sessionEvents . filter (
( event ) : event is Extract < SessionEvent , { type : 'tool/call' } > = > event . type === 'tool/call' ,
)
expect ( calls . every ( call = > call . data . turn === 4 ) ) . toBe ( true )
expect ( calls . length ) . toBeGreaterThanOrEqual ( 2 )
const firstCall = calls [ 0 ]
if ( firstCall === undefined ) throw new Error ( 'neutral policy task produced no tool call' )
expect ( callArgs ( firstCall ) [ 'sandbox_permissions' ] ) . toBeUndefined ( )
expect ( calls . some ( call = > callArgs ( call ) [ 'sandbox_permissions' ] !== undefined ) ) . toBe ( true )
expect ( sessionEvents . some ( event = > event . type === 'tool/result'
&& JSON . stringify ( event . data ) . includes ( '[sandbox: file access denied under read-only mode]' ) ) ) . toBe ( true )
expect ( sessionEvents . some ( event = > event . type === 'approval/asked' ) ) . toBe ( true )
if ( sessionWorkspace === undefined ) throw new Error ( 'permission-policy scenario observed no session workspace' )
expect ( await readFile ( join ( sessionWorkspace , 'policy-neutral.txt' ) , 'utf8' ) ) . toBe ( 'POLICY_NEUTRAL_OK' )
2026-07-30 15:48:05 +08:00
} )
it . skipIf ( MODE === 'record' ) ( 'stays clean and keeps the fixture inventory closed' , async ( ) = > {
expect ( tripwire . pageErrors ) . toEqual ( [ ] )
expect ( tripwire . warnings ) . toEqual ( [ ] )
await assertFixtureInventory ( SNAPSHOT_DIR , [ 'session.jsonl' ] )
} )
} )