feat(web): inject workspace instructions

This commit is contained in:
Dudu-0223
2026-07-23 14:11:25 +08:00
parent edb999fd0f
commit 1ba00bf80c
12 files changed
+199 -19

No files matched your search

@@ -1,4 +1,4 @@
import { mkdtempSync } from 'node:fs'
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -15,11 +15,14 @@ import { bootHost, startHost, type HostHandle, type RunningHost } from '../src/i
/** Scripted adapter: each model call consumes the next chunk list; 'hang' streams then waits for abort. */
class ScriptedAdapter extends LlmAdapter {
readonly requests: GenerateOptions[] = []
constructor(private script: (StreamChunk[] | 'hang')[]) {
super()
}
async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
this.requests.push(options)
const entry = this.script.shift()
if (!entry) throw new Error('ScriptedAdapter: script exhausted')
if (entry === 'hang') {
@@ -79,7 +82,12 @@ afterEach(async () => {
async function boot(script: (StreamChunk[] | 'hang')[] = []): Promise<RunningHost> {
host = await startHost({
boot: { persistenceRoot: mkdtempSync(join(tmpdir(), 'dsh-host-runtime-')), provider: 'scripted', model: 'test-model' },
boot: {
persistenceRoot: mkdtempSync(join(tmpdir(), 'dsh-host-runtime-')),
workspaceContext: false,
provider: 'scripted',
model: 'test-model',
},
})
host.ctx.llm.registerAdapter(['scripted'], new ScriptedAdapter(script))
return host
@@ -87,7 +95,10 @@ async function boot(script: (StreamChunk[] | 'hang')[] = []): Promise<RunningHos
describe('bootHost / startHost', () => {
it('falls back to the deepseek defaults and disposes idempotently', async () => {
const handle: HostHandle = await bootHost({ persistenceRoot: mkdtempSync(join(tmpdir(), 'dsh-boot-')) })
const handle: HostHandle = await bootHost({
persistenceRoot: mkdtempSync(join(tmpdir(), 'dsh-boot-')),
workspaceContext: false,
})
expect(handle.defaults).toMatchObject({ provider: 'deepseek', model: 'deepseek-v4-flash' })
expect(typeof handle.defaults.cwd).toBe('string')
await handle.dispose()
@@ -105,6 +116,41 @@ describe('bootHost / startHost', () => {
await first
host = undefined
})
it('routes workspace instructions through the assembled agent request prefix', async () => {
const workspace = mkdtempSync(join(tmpdir(), 'dsh-host-workspace-'))
mkdirSync(join(workspace, '.git'))
writeFileSync(join(workspace, 'AGENTS.md'), 'host-workspace-context-probe\n')
const adapter = new ScriptedAdapter([textResponse('done')])
host = await startHost({
boot: {
persistenceRoot: mkdtempSync(join(tmpdir(), 'dsh-host-workspace-sessions-')),
workspaceContext: { dshHome: join(workspace, '.dsh'), maxBytes: 65_536 },
provider: 'scripted',
model: 'test-model',
cwd: workspace,
},
})
host.ctx.llm.registerAdapter(['scripted'], adapter)
const { sessionId } = expectOk(await host.api.sessions.create(request({})))
const agent = host.ctx.agents.get(sessionId) as Agent
const idle = waitForIdle(host.ctx, agent)
expectOk(await host.api.sessions.prompt(request({
sessionId,
mode: 'queue' as const,
content: [{ type: 'text' as const, text: 'go' }],
})))
await idle
const requestText = adapter.requests[0]?.messages
.flatMap(message => message.content)
.filter(block => block.type === 'text')
.map(block => block.text)
.join('\n') ?? ''
expect(requestText).toContain('Instructions from: AGENTS.md')
expect(requestText).toContain('host-workspace-context-probe')
})
})
describe('host.describe', () => {
@@ -196,7 +242,9 @@ describe('sessions.prompt / cancel', () => {
describe('sessions.history', () => {
it('implicitly resumes a cold session, deduplicating concurrent calls to one attach', async () => {
const persistenceRoot = mkdtempSync(join(tmpdir(), 'dsh-host-resume-'))
const first = await startHost({ boot: { persistenceRoot, provider: 'scripted', model: 'test-model' } })
const first = await startHost({
boot: { persistenceRoot, workspaceContext: false, provider: 'scripted', model: 'test-model' },
})
first.ctx.llm.registerAdapter(['scripted'], new ScriptedAdapter([textResponse('persisted')]))
const { sessionId } = expectOk(await first.api.sessions.create(request({})))
const agent = first.ctx.agents.get(sessionId) as Agent
@@ -205,7 +253,9 @@ describe('sessions.history', () => {
await idle
await first.dispose()
host = await startHost({ boot: { persistenceRoot, provider: 'scripted', model: 'test-model' } })
host = await startHost({
boot: { persistenceRoot, workspaceContext: false, provider: 'scripted', model: 'test-model' },
})
host.ctx.llm.registerAdapter(['scripted'], new ScriptedAdapter([]))
expect(host.ctx.agents.get(sessionId)).toBeUndefined()
const [a, b] = await Promise.all([