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:
@@ -1,6 +1,6 @@
|
||||
# @deepseek-ai/dsh-host-runtime
|
||||
|
||||
Host runtime assembly for `dsc`: `bootHost` composes the core plugin spine (LLM service + DeepSeek adapter, sessions with JSONL persistence, system prompt, tools, agents, agent loop, local bash), `createApiProxy` implements the [`dsh-host-apiproxy`](../apiproxy/README.md) contract over that composition, and `startHost` is the one-step shell seam returning `{ api, handler, defaults, ctx, dispose }`.
|
||||
Host runtime assembly for `dsh`: `bootHost` composes the core plugin spine (LLM service + DeepSeek adapter, sessions with JSONL persistence, system prompt, tools, agents, agent loop, workspace instructions, local bash), `createApiProxy` implements the [`dsh-host-apiproxy`](../apiproxy/README.md) contract over that composition, and `startHost` is the one-step shell seam returning `{ api, handler, defaults, ctx, dispose }`.
|
||||
|
||||
Which plugins mount and with what defaults is decided only here — shells must not `ctx.plugin` to alter the assembly. `RunningHost.ctx` is a formal seam with exactly two sanctioned uses: mounting protocol front-door plugins (e.g. a future `dsh acp`) and headless session-event subscription; consuming clients must not bypass `api` through it.
|
||||
|
||||
@@ -9,6 +9,7 @@ Which plugins mount and with what defaults is decided only here — shells must
|
||||
| Key | Default | Contract |
|
||||
|---|---:|---|
|
||||
| `persistenceRoot` | (required) | Root directory for JSONL session persistence. |
|
||||
| `workspaceContext` | (required) | [`AGENTS.md`/`CLAUDE.md` loader](../../context/workspace-context/README.md) config with an explicit `maxBytes`, or `false` to disable it. |
|
||||
| `provider` | `'deepseek'` | Default provider route injected as agentOptions on create/resume and reported by `host.describe`. |
|
||||
| `model` | `'deepseek-v4-flash'` | Default model id, same single source as `provider`. |
|
||||
|
||||
@@ -18,7 +19,7 @@ Unary methods take the narrow `RpcRequest<P>` and echo `request.rpcId`; a prompt
|
||||
|
||||
## Model Experience
|
||||
|
||||
Indirectly, through the model-facing plugins bootHost mounts and the provider/model defaults injected into created and resumed agents.
|
||||
Indirectly, through the model-facing plugins bootHost mounts and the provider/model defaults injected into created and resumed agents. When `workspaceContext` is enabled, each agent-loop instance freezes the applicable workspace instructions into its logged request prefix; the owning package documents the exact [model-visible framing](../../context/workspace-context/README.md#prompt-shape).
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
@@ -27,5 +28,5 @@ No direct invalidation; the mounted model-facing plugins own their request-prefi
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **`respond` is a stub** — it always returns `not-pending`; the approval/question pending registry (stable-rpcId mint on accept, baseline replay on stream reopen, wire answerer) is the next host-side step.
|
||||
- **`session.list` covers live sessions only** — cold sessions in the persistence directory are not yet merged into the listing; `host.describe.version` is a placeholder rather than the `apps/cli` package version.
|
||||
- **`host.describe.version` is a placeholder** — it does not yet report the `apps/cli` package version.
|
||||
- **The assembly is fixed** — per-deployment plugin selection (user profile, log sinks, alternative persistence) has a documented home here but no configuration surface yet.
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
"@deepseek-ai/dsh-tool-todo": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-workflow": "workspace:^",
|
||||
"@deepseek-ai/dsh-tools": "workspace:^",
|
||||
"@deepseek-ai/dsh-workspace-context": "workspace:^",
|
||||
"@deepseek-ai/dsh-workflow-workerthread": "workspace:^"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent'
|
||||
import { AttachmentError } from '@deepseek-ai/dsh-attachment-local'
|
||||
import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment-local'
|
||||
import type { ContentBlock, MessageSource } from '@deepseek-ai/dsh-llm'
|
||||
import type { Session, SessionEvent, SessionHeader, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { JsonValue, Session, SessionEvent, SessionHeader, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence'
|
||||
import type { ApiProxy, HistoryEntry, HostFrame, MuxFrame, PromptContentPart, SessionSummary, ToolEventView } from '@deepseek-ai/dsh-host-apiproxy/api'
|
||||
import type { ClientResponse, RpcError, RpcReceipt, RpcRequest, RpcResponse } from '@deepseek-ai/dsh-host-apiproxy/api/rpc'
|
||||
@@ -218,7 +218,7 @@ export interface ApiProxyDefaults {
|
||||
/** The tool/call payload fields the presenter path reads. */
|
||||
interface ToolCallData { callId: string; name: string; arguments: string }
|
||||
/** The tool/result payload fields the presenter path reads. */
|
||||
interface ToolResultData { callId: string; content: ContentBlock[]; isError: boolean; meta?: unknown }
|
||||
interface ToolResultData { callId: string; content: ContentBlock[]; isError: boolean; meta?: JsonValue }
|
||||
|
||||
/**
|
||||
* Compute the render intent for a tool/call or tool/result event through the
|
||||
|
||||
@@ -26,6 +26,7 @@ import FsLocal from '@deepseek-ai/dsh-fs-local'
|
||||
import * as fsPolicy from '@deepseek-ai/dsh-fs-policy'
|
||||
import * as toolFs from '@deepseek-ai/dsh-tool-fs'
|
||||
import * as toolFsSearch from '@deepseek-ai/dsh-tool-fs-search'
|
||||
import * as workspaceContext from '@deepseek-ai/dsh-workspace-context'
|
||||
import SkillService from '@deepseek-ai/dsh-skill'
|
||||
import * as SkillLocal from '@deepseek-ai/dsh-skill-local'
|
||||
import * as toolSkill from '@deepseek-ai/dsh-tool-skill'
|
||||
@@ -45,6 +46,8 @@ import * as spillPolicy from '@deepseek-ai/dsh-spill-policy'
|
||||
export interface BootHostOptions {
|
||||
/** Root directory for JSONL session persistence. */
|
||||
persistenceRoot: string
|
||||
/** Workspace-instruction byte budget/config, or false to disable AGENTS.md/CLAUDE.md loading. */
|
||||
workspaceContext: workspaceContext.Config | false
|
||||
/** Explicit harness home for durable attachments; omitted follows DSH_HOME then ~/.dsh. */
|
||||
dshHome?: string
|
||||
/** Default provider route for created/resumed agents (defaults to 'deepseek', the only adapter bootHost registers). */
|
||||
@@ -83,7 +86,7 @@ export interface HostHandle {
|
||||
/**
|
||||
* Compose the harness host plugin assembly (the one place deciding which plugins mount and
|
||||
* with what defaults — shells must not alter the assembly).
|
||||
* @param options - persistence root and optional default provider/model.
|
||||
* @param options - persistence, workspace instructions, attachment storage, and optional default routing.
|
||||
* @returns the booted handle (ctx + defaults + dispose).
|
||||
*/
|
||||
export async function bootHost(options: BootHostOptions): Promise<HostHandle> {
|
||||
@@ -108,7 +111,7 @@ export async function bootHost(options: BootHostOptions): Promise<HostHandle> {
|
||||
if (options.piAiProviders !== undefined && options.piAiProviders.length > 0) {
|
||||
await ctx.plugin(LlmPiAi, { providers: options.piAiProviders })
|
||||
}
|
||||
await ctx.plugin(SessionPersistenceJsonl, { root: options.persistenceRoot, compression: 'none' })
|
||||
await ctx.plugin(SessionPersistenceJsonl, { root: options.persistenceRoot })
|
||||
await ctx.plugin(LocalBashExecutor, {})
|
||||
// Tool suite mirroring the demo:repl composition (repl-agent/cordis.yml +
|
||||
// the agent-spine bundle) so web sessions get the same coding-agent tool
|
||||
@@ -122,6 +125,9 @@ export async function bootHost(options: BootHostOptions): Promise<HostHandle> {
|
||||
await ctx.plugin(fsPolicy)
|
||||
await ctx.plugin(toolFs, {})
|
||||
await ctx.plugin(toolFsSearch, {})
|
||||
if (options.workspaceContext !== false) {
|
||||
await ctx.plugin(workspaceContext, options.workspaceContext)
|
||||
}
|
||||
// Skill stack with the demo default dshHome (~/.dsh via resolveDshHome).
|
||||
await ctx.plugin(SkillService, {})
|
||||
await ctx.plugin(SkillLocal, {})
|
||||
|
||||
@@ -16,10 +16,9 @@ import { createApiProxy } from './api-proxy.ts'
|
||||
/** Options for startHost. */
|
||||
export interface StartHostOptions {
|
||||
/**
|
||||
* Passed through to bootHost verbatim (persistenceRoot required +
|
||||
* provider?/model?). Future host-level knobs (profile, log sink — any
|
||||
* output added to the assembly MUST be switchable off here) land as
|
||||
* additive fields.
|
||||
* Passed through to bootHost verbatim. Future host-level knobs (profile,
|
||||
* log sink — any output added to the assembly MUST be switchable off here)
|
||||
* land as additive fields.
|
||||
*/
|
||||
boot: BootHostOptions
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Tool-card view computation over the mux live path: three standard card types
|
||||
* arrive on the frame, a presenterless tool ships no view field, and a throwing
|
||||
* presenter soft-falls to no view (the event still ships). Result pairing works
|
||||
* both through the live open-call table and the backscan fallback after
|
||||
* arrive on the frame, a presenterless tool ships no view field, a call-only
|
||||
* presenter keeps raw result content out of the view payload, and a throwing
|
||||
* presenter soft-falls to no view (the event still ships). Result pairing
|
||||
* works both through the live open-call table and the backscan fallback after
|
||||
* turn/end cleared it.
|
||||
*/
|
||||
|
||||
@@ -12,7 +13,7 @@ import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import ToolRegistry, { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import type { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
@@ -24,13 +25,13 @@ import { createApiProxy } from '../src/api-proxy.ts'
|
||||
const reply = (text: string): Promise<ContentBlock[]> => Promise.resolve([{ type: 'text', text }])
|
||||
|
||||
function tool(name: string, presenters: Pick<ToolDefinition, 'presentCall' | 'presentResult'>): ToolDefinition {
|
||||
return {
|
||||
return defineContentToolFixture({
|
||||
name,
|
||||
description: `tool ${name}`,
|
||||
parameters: { type: 'object', properties: {} },
|
||||
parameters: {},
|
||||
execute: () => reply(`ran:${name}`),
|
||||
...presenters,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function harness(): Promise<{ ctx: Context }> {
|
||||
@@ -50,6 +51,9 @@ async function harness(): Promise<{ ctx: Context }> {
|
||||
ctx.tools.register(tool('diffy', {
|
||||
presentCall: () => ({ card: 'diff', title: 'Write f.txt', diffs: [{ path: 'f.txt', oldText: null, newText: 'x' }] }),
|
||||
}))
|
||||
ctx.tools.register(tool('call-only', {
|
||||
presentCall: () => ({ card: 'generic', title: 'program', kind: 'execute', rawInput: 'return value' }),
|
||||
}))
|
||||
ctx.tools.register(tool('plain', {}))
|
||||
ctx.tools.register(tool('boom', {
|
||||
presentCall: () => { throw new Error('presenter exploded') },
|
||||
@@ -73,13 +77,16 @@ describe('mux live view computation', () => {
|
||||
const api = createApiProxy(ctx, { provider: 'p', model: 'm', cwd: '/tmp' })
|
||||
const abort = new AbortController()
|
||||
const stream = api.events.mux({ rpcId: RpcId('t-mux'), payload: {} }, abort.signal)
|
||||
const collected = collect(stream, 7, abort)
|
||||
const collected = collect(stream, 9, abort)
|
||||
const rawResult = `RAW_RESULT:${'x'.repeat(64 * 1024)}`
|
||||
|
||||
const session = ctx.sessions.create()
|
||||
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
session.append('tool/call', { turn: 1, step: 1, callId: CallId('c-gen'), name: 'gen', arguments: '{}' })
|
||||
session.append('tool/call', { turn: 1, step: 1, callId: CallId('c-term'), name: 'term', arguments: '{"cmd":"echo hi"}' })
|
||||
session.append('tool/call', { turn: 1, step: 1, callId: CallId('c-diff'), name: 'diffy', arguments: '{}' })
|
||||
session.append('tool/call', { turn: 1, step: 1, callId: CallId('c-call-only'), name: 'call-only', arguments: '{}' })
|
||||
session.append('tool/result', { turn: 1, step: 1, callId: CallId('c-call-only'), content: [{ type: 'text', text: rawResult }], isError: false }, { surfaceOp: 'append' })
|
||||
session.append('tool/call', { turn: 1, step: 1, callId: CallId('c-plain'), name: 'plain', arguments: '{}' })
|
||||
session.append('tool/call', { turn: 1, step: 1, callId: CallId('c-boom'), name: 'boom', arguments: '{}' })
|
||||
session.append('tool/result', { turn: 1, step: 1, callId: CallId('c-gen'), content: [{ type: 'text', text: 'ok' }], isError: false }, { surfaceOp: 'append' })
|
||||
@@ -93,6 +100,15 @@ describe('mux live view computation', () => {
|
||||
expect(byCall.get('tool/call:c-gen')?.view).toEqual({ for: 'call', view: { card: 'generic', title: 'gen call' } })
|
||||
expect(byCall.get('tool/call:c-term')?.view).toEqual({ for: 'call', view: { card: 'terminal', title: 'echo hi' } })
|
||||
expect(byCall.get('tool/call:c-diff')?.view?.view.card).toBe('diff')
|
||||
expect(byCall.get('tool/call:c-call-only')?.view).toEqual({
|
||||
for: 'call',
|
||||
view: { card: 'generic', title: 'program', kind: 'execute', rawInput: 'return value' },
|
||||
})
|
||||
const callOnlyResult = byCall.get('tool/result:c-call-only')
|
||||
expect('view' in (callOnlyResult ?? {})).toBe(false)
|
||||
const serializedResult = JSON.stringify(callOnlyResult)
|
||||
expect(serializedResult.indexOf(rawResult)).toBeGreaterThanOrEqual(0)
|
||||
expect(serializedResult.indexOf(rawResult)).toBe(serializedResult.lastIndexOf(rawResult))
|
||||
// No presenter → the frame carries no view property at all.
|
||||
expect('view' in (byCall.get('tool/call:c-plain') ?? {})).toBe(false)
|
||||
// Throwing presenter → soft-fall: event ships, no view.
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -68,6 +68,9 @@
|
||||
{
|
||||
"path": "../../fs/tool-fs-search"
|
||||
},
|
||||
{
|
||||
"path": "../../context/workspace-context"
|
||||
},
|
||||
{
|
||||
"path": "../../llm/token-meter"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user