fix(cli): register web prompt context before boot
This commit is contained in:
@@ -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' })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
29
apps/cli/tests/web-prompt-context.spec.ts
Normal file
29
apps/cli/tests/web-prompt-context.spec.ts
Normal 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()
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user