fix(cli): register web prompt context before boot

This commit is contained in:
Tianyi Cui
2026-08-02 00:49:58 +08:00
parent d74048345a
commit 6cd9fefe88
7 changed files with 54 additions and 21 deletions

View File

@@ -155,6 +155,8 @@ export interface AppCLIEntryOptions {
workspaceRoot?: string
/** Extra authorities for the /api browser-trust fence (`host` or `host:port`), appended to the derived LAN IP literals. */
trustedHosts?: string[]
/** Surface setup registered after Loader installation and before any config-tree entry mounts. */
prepare?: (ctx: Context) => Promise<void> | void
}
/**
@@ -180,8 +182,8 @@ export class AppCLIEntry {
constructor(private readonly options: AppCLIEntryOptions) {}
/**
* Run the boot chain: patch composition → Loader include boot (dev row
* before await) → fail-loud triple.
* Run the boot chain: patch composition → Loader installation → surface
* preparation → config-tree boot (dev row before await) → fail-loud triple.
* @returns the settled root context and the listening port.
*/
async run(): Promise<{ ctx: Context; port: number }> {
@@ -246,7 +248,7 @@ export class AppCLIEntry {
if (telemetryPatch !== undefined) this.patches.push(telemetryPatch)
}
/** Shared Loader boot; the dev HMR row mounts before await so the activation audit covers it. */
/** Shared Loader boot; surface preparation precedes the tree, and the dev HMR row precedes the activation audit. */
private async bootTree(): Promise<void> {
// One include of the shared base with every overlay as a sibling patch
// list: patches never cross an include boundary, so nesting them would
@@ -260,6 +262,7 @@ export class AppCLIEntry {
...this.patches,
]
this.ctx = await boot('dsh', resolve(this.options.configPath), patches, async (ctx) => {
await this.options.prepare?.(ctx)
if (this.options.dev) await ctx.loader.create({ name: '@deepseek-ai/dsh-client-hmr' })
})
}

View File

@@ -23,17 +23,18 @@ const WEB_SURFACE_PROMPT = 'You are interacting with the user through the DeepSe
+ 'The browser provides no implicit DOM, route, or screenshot context.'
/**
* Add the launcher-owned source location and Web-surface orientation after the
* shared config tree settles. The request header logs both sections with every
* model-visible prompt.
* @param ctx - settled Web application context.
* Register the launcher-owned source location and Web-surface orientation
* before the shared config tree mounts. The injection installs both sections
* when `systemPrompt` activates; because it precedes the Loader entries, later
* prompt consumers observe them on their first activation.
* @param ctx - Web root context with Loader installed but no config tree mounted.
* @param sourceRoot - absolute checkout root resolved from the launcher module.
*/
export function installWebPromptContext(ctx: Context, sourceRoot: string): void {
const systemPrompt = ctx.get('systemPrompt')
if (systemPrompt === undefined) throw new Error('dsh web: systemPrompt service missing after settled boot')
addHarnessSourceSection(ctx, sourceRoot)
systemPrompt.section({ name: 'app:web-surface', order: -98, text: WEB_SURFACE_PROMPT })
export function prepareWebPromptContext(ctx: Context, sourceRoot: string): void {
ctx.inject(['systemPrompt'], (promptCtx) => {
addHarnessSourceSection(promptCtx, sourceRoot)
promptCtx.systemPrompt.section({ name: 'app:web-surface', order: -98, text: WEB_SURFACE_PROMPT })
})
}
// Display-only mirror of the webserver schema's loopback host: the address the
@@ -65,13 +66,13 @@ export async function runWeb(
overlayPath: WEB_OVERLAY,
...config !== undefined && { extraOverlayPath: resolveConfigPath(config, undefined) },
dev,
prepare: (ctx) => { prepareWebPromptContext(ctx, SOURCE_ROOT) },
...host !== undefined && { host },
...port !== undefined && { port },
...workspaceRoot !== undefined && { workspaceRoot },
...trustedHosts !== undefined && { trustedHosts },
})
const { ctx, port: boundPort } = await entry.run()
installWebPromptContext(ctx, SOURCE_ROOT)
let exiting = false
const shutdown = (code: number): void => {

View File

@@ -0,0 +1,29 @@
import { sep } from 'node:path'
import { Context } from 'cordis'
import { describe, expect, it } from 'vitest'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import { HARNESS_SOURCE_SECTION } from '@deepseek-ai/dsh-app-boot'
import { prepareWebPromptContext } from '../src/web.ts'
describe('prepareWebPromptContext', () => {
it('installs both sections before a later systemPrompt consumer activates', async () => {
const ctx = new Context()
const sourceRoot = `${sep}opt${sep}harness-src`
let observedNames: string[] | undefined
try {
prepareWebPromptContext(ctx, sourceRoot)
const consumer = ctx.inject(['systemPrompt'], async (promptCtx) => {
const assembly = await promptCtx.systemPrompt.assemble()
observedNames = assembly.sections.map(section => section.name)
})
await ctx.plugin(SystemPrompt, { persona: 'You are a coding agent.' })
await consumer
expect(observedNames).toContain(HARNESS_SOURCE_SECTION)
expect(observedNames).toContain('app:web-surface')
} finally {
await ctx.fiber.dispose()
}
})
})