Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts: # apps/cli/src/web.ts # apps/web/tests/smoke-fixture.e2e.ts # docs/architecture.i18n.yaml # packages/client/connection/src/client/fixture.ts # packages/client/ui-conversation/README.md # packages/client/ui-conversation/package.json # packages/client/ui-conversation/src/client/apply.ts # packages/client/ui-conversation/src/client/chat/ChatView.tsx # packages/client/ui-conversation/src/client/chat/register.ts # packages/client/ui-conversation/src/client/contract/slots.ts # packages/client/ui-conversation/src/client/contract/views.ts # packages/client/ui-conversation/src/client/service.ts # packages/client/ui-conversation/src/client/skeleton/ConversationRoot.tsx # packages/client/ui-conversation/src/client/stores.ts # packages/client/ui-conversation/tests/skeleton-branches.spec.tsx # packages/cordis/tool-cordis/src/api-catalog.ts # packages/host/runtime/src/boot.ts # packages/host/runtime/tests/host-runtime.spec.ts # pnpm-lock.yaml
This commit is contained in:
390 files changed
+18322
-5565
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { existsSync, mkdtempSync, readFileSync } from 'node:fs'
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
@@ -15,6 +15,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')[],
|
||||
private readonly inputModalities: readonly ModelModality[] = ['text', 'image'],
|
||||
@@ -31,6 +33,7 @@ class ScriptedAdapter extends LlmAdapter {
|
||||
}
|
||||
|
||||
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') {
|
||||
@@ -92,7 +95,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
|
||||
@@ -100,12 +108,25 @@ 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()
|
||||
})
|
||||
|
||||
it('uses the JSONL backend compressed default', async () => {
|
||||
const handle: HostHandle = await bootHost({
|
||||
persistenceRoot: mkdtempSync(join(tmpdir(), 'dsh-boot-zstd-')),
|
||||
workspaceContext: false,
|
||||
})
|
||||
const session = handle.ctx.sessions.create()
|
||||
expect(handle.ctx.sessionPersistence.locate(session.header)?.path).toMatch(/\.jsonl\.zstd$/)
|
||||
await handle.dispose()
|
||||
})
|
||||
|
||||
it('startHost assembles api + handler over the same defaults and dedupes dispose', async () => {
|
||||
const running = await boot()
|
||||
expect(running.defaults).toMatchObject({ provider: 'scripted', model: 'test-model' })
|
||||
@@ -133,6 +154,7 @@ describe('bootHost / startHost', () => {
|
||||
it('mounts configured pi-ai providers while accepting an explicit empty list', async () => {
|
||||
const empty = await bootHost({
|
||||
persistenceRoot: mkdtempSync(join(tmpdir(), 'dsh-boot-pi-empty-')),
|
||||
workspaceContext: false,
|
||||
piAiProviders: [],
|
||||
})
|
||||
expect(empty.ctx.llm.listProviders()).toEqual([{ id: 'deepseek', name: 'DeepSeek' }])
|
||||
@@ -140,11 +162,47 @@ describe('bootHost / startHost', () => {
|
||||
|
||||
const configured = await bootHost({
|
||||
persistenceRoot: mkdtempSync(join(tmpdir(), 'dsh-boot-pi-')),
|
||||
workspaceContext: false,
|
||||
piAiProviders: [{ provider: 'openai' }],
|
||||
})
|
||||
expect(configured.ctx.llm.listProviders()).toContainEqual({ id: 'openai', name: 'openai' })
|
||||
await configured.dispose()
|
||||
})
|
||||
|
||||
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', () => {
|
||||
@@ -158,6 +216,7 @@ describe('host.describe', () => {
|
||||
host = await startHost({
|
||||
boot: {
|
||||
persistenceRoot: mkdtempSync(join(tmpdir(), 'dsh-host-describe-missing-model-')),
|
||||
workspaceContext: false,
|
||||
provider: 'scripted',
|
||||
model: 'missing-model',
|
||||
},
|
||||
@@ -239,7 +298,7 @@ describe('sessions.prompt / cancel', () => {
|
||||
const persistenceRoot = mkdtempSync(join(tmpdir(), 'dsh-image-session-'))
|
||||
const dshHome = mkdtempSync(join(tmpdir(), 'dsh-image-home-'))
|
||||
host = await startHost({
|
||||
boot: { persistenceRoot, dshHome, provider: 'scripted', model: 'test-model' },
|
||||
boot: { persistenceRoot, workspaceContext: false, dshHome, provider: 'scripted', model: 'test-model' },
|
||||
})
|
||||
host.ctx.llm.registerAdapter(['scripted'], new ScriptedAdapter([textResponse('seen')]))
|
||||
const { sessionId } = expectOk(await host.api.sessions.create(request({})))
|
||||
@@ -404,7 +463,7 @@ describe('sessions.prompt / cancel', () => {
|
||||
const persistenceRoot = mkdtempSync(join(tmpdir(), 'dsh-text-session-'))
|
||||
const dshHome = mkdtempSync(join(tmpdir(), 'dsh-text-home-'))
|
||||
host = await startHost({
|
||||
boot: { persistenceRoot, dshHome, provider: 'scripted', model: 'test-model' },
|
||||
boot: { persistenceRoot, workspaceContext: false, dshHome, provider: 'scripted', model: 'test-model' },
|
||||
})
|
||||
host.ctx.llm.registerAdapter(['scripted'], new ScriptedAdapter([], ['text']))
|
||||
const { sessionId } = expectOk(await host.api.sessions.create(request({})))
|
||||
@@ -423,7 +482,7 @@ describe('sessions.prompt / cancel', () => {
|
||||
const persistenceRoot = mkdtempSync(join(tmpdir(), 'dsh-routed-session-'))
|
||||
const dshHome = mkdtempSync(join(tmpdir(), 'dsh-routed-home-'))
|
||||
host = await startHost({
|
||||
boot: { persistenceRoot, dshHome, provider: 'scripted', model: 'test-model' },
|
||||
boot: { persistenceRoot, workspaceContext: false, dshHome, provider: 'scripted', model: 'test-model' },
|
||||
})
|
||||
host.ctx.llm.registerAdapter(['scripted'], new ScriptedAdapter([], ['text']))
|
||||
host.ctx.llm.registerAdapter(
|
||||
@@ -451,6 +510,7 @@ describe('sessions.prompt / cancel', () => {
|
||||
host = await startHost({
|
||||
boot: {
|
||||
persistenceRoot: mkdtempSync(join(tmpdir(), 'dsh-host-route-default-')),
|
||||
workspaceContext: false,
|
||||
provider: 'scripted',
|
||||
model: 'test-model',
|
||||
},
|
||||
@@ -487,7 +547,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
|
||||
@@ -496,7 +558,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([
|
||||
|
||||
Reference in New Issue
Block a user