fix: address ds-review-bot round — insert-aliasing clones, settlement gates, closure module fallback

- Clone patch lists per generation (boot + composeLive): the include pushes
  insert rows by reference and mutates them in place, so a reused object
  baked user overrides into bundle rows and removal could not revert; the
  built-bin hot-reload e2e now asserts an override AND its removal reverting.
- The headless runner awaits Loader settlement before prompting (its inject
  gate covers only apiProxy/httpServer) and abandons cleanly when the tree
  died during the wait.
- healProfilesModuleFallback walks the app's full dependency+peer closure:
  out-of-tree plugins import seam packages (dsh-compact, dsh-subprocess, ...)
  that only implementations reach, and peers are how seams are declared.
- Profile init writes pnpm-workspace.yaml (nodeLinker: hoisted), not .npmrc
  — pnpm >=10 reads settings from the workspace manifest.
- Web dumps reject boot-only flags instead of printing a tree that differs
  from the same invocation's boot; --port validates at the flag;
  --dump-default-config no longer parses the (possibly broken) user layer;
  trustedHosts flag derivation merges over the composed value instead of
  replacing it; web-runtime gains surfaceContext (headless disables the GUI
  prompt/bash-vars the old -p never mounted); 'node_modules' is a reserved
  profile name; plugin-warning names the recovery step; client AGENTS.md
  registration surfaces point at the web-app bundle.
- Ship session-reference/tmux-context/tool-ask-user as app dependencies for
  terminal front-door patch layers (turtle-ui), same stance as mcp-client.
This commit is contained in:
Turtle
2026-08-06 09:27:44 +08:00
parent 273f27260d
commit 925daf141b
20 changed files with 240 additions and 83 deletions

View File

@@ -34,6 +34,13 @@ export interface Config {
mode: WebMode
/** Print the URL line on activation; a headless layer over this bundle turns it off. */
printUrl: boolean
/**
* Register the model-visible surface context (the `app:web-surface` prompt
* section and the `DSH_WEB_URL`/`DSH_WEB_MODE` bash variables). A one-shot
* layer turns it off: its user is not interacting through the GUI, so the
* orientation text would be false.
*/
surfaceContext: boolean
/**
* LAN IPv4 addresses sampled once by the launcher when the effective bind
* is all-interfaces — the exact snapshot the /api trust fence was
@@ -46,6 +53,7 @@ export interface Config {
export const Config: z<Config> = z.object({
mode: z.union([z.const('production'), z.const('development')]).default('production'),
printUrl: z.boolean().default(true),
surfaceContext: z.boolean().default(true),
lanAddresses: z.array(String).default([]),
})
@@ -104,23 +112,25 @@ export const internals: { resolveDistIndex: () => string } = { resolveDistIndex
*/
export function apply(ctx: Context, config: Config): void {
ctx.plugin(FrontendStatic, { distIndex: internals.resolveDistIndex() })
ctx.inject(['systemPrompt'], (promptCtx) => {
promptCtx.systemPrompt.section({
name: 'app:web-surface',
order: -98,
text: () => webSurfacePrompt(localWebUrl(promptCtx), config.mode),
if (config.surfaceContext) {
ctx.inject(['systemPrompt'], (promptCtx) => {
promptCtx.systemPrompt.section({
name: 'app:web-surface',
order: -98,
text: () => webSurfacePrompt(localWebUrl(promptCtx), config.mode),
})
})
})
ctx.inject(['bashEnv'], (runtimeCtx) => {
runtimeCtx.bashEnv.register({
name: 'web-runtime',
variables: {
[DSH_WEB_URL]: { description: 'Canonical local URL of the DeepSeek Harness Web GUI serving this session.' },
[DSH_WEB_MODE]: { description: 'Web runtime mode: production, or development when the client-plugin HMR receiver is active.' },
},
resolve: () => ({ [DSH_WEB_URL]: localWebUrl(runtimeCtx), [DSH_WEB_MODE]: config.mode }),
ctx.inject(['bashEnv'], (runtimeCtx) => {
runtimeCtx.bashEnv.register({
name: 'web-runtime',
variables: {
[DSH_WEB_URL]: { description: 'Canonical local URL of the DeepSeek Harness Web GUI serving this session.' },
[DSH_WEB_MODE]: { description: 'Web runtime mode: production, or development when the client-plugin HMR receiver is active.' },
},
resolve: () => ({ [DSH_WEB_URL]: localWebUrl(runtimeCtx), [DSH_WEB_MODE]: config.mode }),
})
})
})
}
if (config.printUrl) {
// The URL line is a readiness signal: supervisors (and the keyless CLI
// smoke) RPC as soon as they observe it, so it must not print while

View File

@@ -69,7 +69,7 @@ describe('web-app runtime glue', () => {
},
} as never)
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
apply(ctx, new Config({ mode: 'development', printUrl: true, lanAddresses: ['192.168.1.5'] }))
apply(ctx, new Config({ mode: 'development', printUrl: true, surfaceContext: true, lanAddresses: ['192.168.1.5'] }))
await ctx.plugin(SystemPrompt, { persona: '' })
// Settle the injected registrations.
await new Promise(resolve => setTimeout(resolve, 0))
@@ -90,7 +90,7 @@ describe('web-app runtime glue', () => {
const ctx = new Context()
ctx.provide('httpServer', fakeHttpServer().server)
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
apply(ctx, new Config({ mode: 'production', printUrl: false, lanAddresses: [] }))
apply(ctx, new Config({ mode: 'production', printUrl: false, surfaceContext: true, lanAddresses: [] }))
await ctx.plugin(SystemPrompt, { persona: '' })
await new Promise(resolve => setTimeout(resolve, 0))
expect(log).not.toHaveBeenCalled()
@@ -100,12 +100,32 @@ describe('web-app runtime glue', () => {
await ctx.fiber.dispose()
})
it('skips the surface context when disabled (the one-shot layer): no prompt section, no bash variables', async () => {
stageDist()
const ctx = new Context()
ctx.provide('httpServer', fakeHttpServer().server)
const contributions: BashContribution[] = []
ctx.provide('bashEnv', {
register: (contribution: BashContribution) => {
contributions.push(contribution)
return () => {}
},
} as never)
apply(ctx, new Config({ mode: 'production', printUrl: false, surfaceContext: false, lanAddresses: [] }))
await ctx.plugin(SystemPrompt, { persona: '' })
await new Promise(resolve => setTimeout(resolve, 0))
const assembly = await ctx.systemPrompt.assemble()
expect(assembly.sections.some(entry => entry.name === 'app:web-surface')).toBe(false)
expect(contributions).toEqual([])
await ctx.fiber.dispose()
})
it('prints the loopback-only URL line when no LAN snapshot exists', async () => {
stageDist()
const ctx = new Context()
ctx.provide('httpServer', fakeHttpServer().server)
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
apply(ctx, new Config({ mode: 'production', printUrl: true, lanAddresses: [] }))
apply(ctx, new Config({ mode: 'production', printUrl: true, surfaceContext: true, lanAddresses: [] }))
await new Promise(resolve => setTimeout(resolve, 0))
expect(log).toHaveBeenCalledWith('dsh web: http://127.0.0.1:4567')
await ctx.fiber.dispose()
@@ -121,7 +141,7 @@ describe('web-app runtime glue', () => {
const settlement = new Promise<void>((resolve) => { release = resolve })
settled.provide('loader', { await: () => settlement } as never)
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
apply(settled, new Config({ mode: 'production', printUrl: true, lanAddresses: [] }))
apply(settled, new Config({ mode: 'production', printUrl: true, surfaceContext: true, lanAddresses: [] }))
await new Promise(resolve => setTimeout(resolve, 0))
expect(log).not.toHaveBeenCalled()
release!()
@@ -140,7 +160,7 @@ describe('web-app runtime glue', () => {
let releaseTorn: () => void
const tornSettlement = new Promise<void>((resolve) => { releaseTorn = resolve })
torn.provide('loader', { await: () => tornSettlement } as never)
apply(torn, new Config({ mode: 'production', printUrl: true, lanAddresses: [] }))
apply(torn, new Config({ mode: 'production', printUrl: true, surfaceContext: true, lanAddresses: [] }))
await child.dispose() // the httpServer service goes away
releaseTorn!()
await new Promise(resolve => setTimeout(resolve, 0))
@@ -156,7 +176,7 @@ describe('web-app runtime glue', () => {
const { server } = fakeHttpServer()
Object.defineProperty(server, 'port', { get: () => undefined })
ctx.provide('httpServer', server)
apply(ctx, new Config({ mode: 'production', printUrl: false, lanAddresses: [] }))
apply(ctx, new Config({ mode: 'production', printUrl: false, surfaceContext: true, lanAddresses: [] }))
await ctx.plugin(SystemPrompt, { persona: '' })
await new Promise(resolve => setTimeout(resolve, 0))
await expect(ctx.systemPrompt.assemble()).rejects.toThrow('httpServer service missing')