Merge remote-tracking branch 'origin/master' into worktree/web-session-titles
# Conflicts: # .agents/notes/implemented/process/2026-07-20-gui-testing-system.i18n.yaml # packages/client/ui-conversation/tests/apply-inject.spec.tsx # packages/client/ui-conversation/tests/chat-stats-bash-sample.spec.tsx # packages/client/ui-conversation/tests/gate-branch-tails.spec.tsx # packages/client/ui-conversation/tests/selection-survival.spec.ts # packages/client/ui-conversation/tests/skeleton-branches.spec.tsx # packages/client/ui-conversation/tests/skeleton.spec.tsx # packages/client/ui-layout/tests/service.spec.ts # packages/client/ui-sidebar/tests/apply.spec.tsx # packages/client/ui-sidebar/tests/store.spec.ts # packages/client/ui-trajectory/tests/views.spec.tsx # packages/client/web/src/app.tsx # packages/client/web/tests/boot.spec.tsx # packages/host/runtime/README.md # packages/host/runtime/tests/host-runtime.spec.ts
This commit is contained in:
@@ -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'
|
||||
@@ -17,6 +17,8 @@ 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()
|
||||
}
|
||||
@@ -26,6 +28,7 @@ class ScriptedAdapter extends LlmAdapter {
|
||||
yield * textResponse('Durable append-only session titles')
|
||||
return
|
||||
}
|
||||
this.requests.push(options)
|
||||
const entry = this.script.shift()
|
||||
if (!entry) throw new Error('ScriptedAdapter: script exhausted')
|
||||
if (entry === 'hang') {
|
||||
@@ -106,6 +109,7 @@ async function boot(
|
||||
host = await startHost({
|
||||
boot: {
|
||||
persistenceRoot: mkdtempSync(join(tmpdir(), 'dsh-host-runtime-')),
|
||||
workspaceContext: false,
|
||||
provider: 'scripted',
|
||||
model: 'test-model',
|
||||
...(sessionTitle === undefined ? {} : { sessionTitle }),
|
||||
@@ -118,7 +122,10 @@ async function boot(
|
||||
|
||||
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()
|
||||
@@ -136,6 +143,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', () => {
|
||||
@@ -315,7 +357,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
|
||||
@@ -325,7 +369,9 @@ describe('sessions.history', () => {
|
||||
const titleEvent = await appendTitle(first.ctx, agent, 'Persisted title')
|
||||
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 abort = new AbortController()
|
||||
|
||||
Reference in New Issue
Block a user