mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
# Conflicts: # apps/web/tests/smoke-real.e2e.ts # packages/client/connection/tests/fixture.spec.ts # packages/host/runtime/README.md # packages/host/runtime/src/api-proxy.ts
172 lines
7.9 KiB
TypeScript
172 lines
7.9 KiB
TypeScript
/**
|
|
* Core spine composition for the dsh host: mounts the harness core plugins
|
|
* one by one (each awaited so a load failure surfaces deterministically at
|
|
* boot, unlike bundle plugins whose children mount unawaited).
|
|
*/
|
|
|
|
import { Context } from 'cordis'
|
|
import Timer from '@cordisjs/plugin-timer'
|
|
import LlmService from '@deepseek-ai/dsh-llm'
|
|
import SessionStore from '@deepseek-ai/dsh-session'
|
|
import SessionTitleService, { type Config as SessionTitleConfig } from '@deepseek-ai/dsh-session-title'
|
|
import * as SessionTitleFirstMessageLlm from '@deepseek-ai/dsh-session-title-first-message-llm'
|
|
import type { Config as SessionTitleLlmConfig } from '@deepseek-ai/dsh-session-title-first-message-llm'
|
|
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
|
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
|
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
|
import TaskService from '@deepseek-ai/dsh-tasks'
|
|
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
|
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
|
|
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
|
|
import LocalBashExecutor from '@deepseek-ai/dsh-bash-local'
|
|
import * as toolBash from '@deepseek-ai/dsh-tool-bash'
|
|
import * as toolTodo from '@deepseek-ai/dsh-tool-todo'
|
|
import * as toolTasks from '@deepseek-ai/dsh-tool-tasks'
|
|
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'
|
|
import TokenMeter from '@deepseek-ai/dsh-token-meter'
|
|
import CompactBasic from '@deepseek-ai/dsh-compact-basic'
|
|
import SubagentService from '@deepseek-ai/dsh-subagent'
|
|
import * as SubagentSpawn from '@deepseek-ai/dsh-subagent-spawn'
|
|
import * as SubagentFork from '@deepseek-ai/dsh-subagent-fork'
|
|
import * as toolSubagent from '@deepseek-ai/dsh-tool-subagent'
|
|
import WorkflowWorkerthread from '@deepseek-ai/dsh-workflow-workerthread'
|
|
import * as toolWorkflow from '@deepseek-ai/dsh-tool-workflow'
|
|
import * as timeoutPolicy from '@deepseek-ai/dsh-timeout-policy'
|
|
import SpillLocal from '@deepseek-ai/dsh-spill-local'
|
|
import * as spillPolicy from '@deepseek-ai/dsh-spill-policy'
|
|
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
|
|
|
|
/** Default deterministic title policy for sessions created through the host. */
|
|
const DEFAULT_SESSION_TITLE_CONFIG: SessionTitleConfig = {
|
|
fallbackMaxWords: 5,
|
|
fallbackMaxBytes: 40,
|
|
maxTitleBytes: 80,
|
|
}
|
|
|
|
/** Default first-message model-title policy for sessions created through the host. */
|
|
const DEFAULT_SESSION_TITLE_LLM_CONFIG: SessionTitleLlmConfig = {
|
|
targetWords: 5,
|
|
targetCjkCharacters: 10,
|
|
maxInputBytes: 4_096,
|
|
maxOutputTokens: 64,
|
|
timeoutMs: 60_000,
|
|
}
|
|
|
|
/** Options for bootHost — the assembly-layer composition knobs. */
|
|
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
|
|
/** Default provider route for created/resumed agents (defaults to 'deepseek', the only adapter bootHost registers). */
|
|
provider?: string
|
|
/** Default model id (defaults to 'deepseek-v4-flash', matching the demos). */
|
|
model?: string
|
|
/** Deterministic fallback-title limits. */
|
|
sessionTitle?: SessionTitleConfig
|
|
/** Opt-in first-message model-title policy; `true` selects host defaults and an explicit config overrides them. */
|
|
sessionTitleLlm?: true | SessionTitleLlmConfig
|
|
/**
|
|
* Default project directory for sessions created without an explicit cwd
|
|
* (defaults to the host process working directory). A session's cwd is its
|
|
* project path — a per-session choice, not a host property; this option only
|
|
* supplies the value used when the creator does not choose one.
|
|
*/
|
|
cwd?: string
|
|
}
|
|
|
|
/** Host-level default agent routing: the single source injected on create and reported by host.describe. */
|
|
export interface HostDefaults {
|
|
provider: string
|
|
model: string
|
|
/** Default project directory for new sessions whose create request carries no cwd. */
|
|
cwd: string
|
|
}
|
|
|
|
/** Booted host handle: composed root context + resolved defaults + disposer. */
|
|
export interface HostHandle {
|
|
/** Root context with the full plugin assembly mounted. */
|
|
ctx: Context
|
|
/** Resolved default agent routing (options ?? built-in fallbacks). */
|
|
defaults: HostDefaults
|
|
/** Tear down the whole plugin tree. */
|
|
dispose(): Promise<void>
|
|
}
|
|
|
|
/**
|
|
* 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, workspace instructions, and optional default routing.
|
|
* @returns the booted handle (ctx + defaults + dispose).
|
|
*/
|
|
export async function bootHost(options: BootHostOptions): Promise<HostHandle> {
|
|
const defaults: HostDefaults = {
|
|
provider: options.provider ?? 'deepseek',
|
|
model: options.model ?? 'deepseek-v4-flash',
|
|
cwd: options.cwd ?? process.cwd(),
|
|
}
|
|
const ctx = new Context()
|
|
await ctx.plugin(Timer)
|
|
await ctx.plugin(LlmService)
|
|
await ctx.plugin(SessionStore)
|
|
await ctx.plugin(SessionTitleService, options.sessionTitle ?? DEFAULT_SESSION_TITLE_CONFIG)
|
|
if (options.sessionTitleLlm !== undefined) {
|
|
await ctx.plugin(
|
|
SessionTitleFirstMessageLlm,
|
|
options.sessionTitleLlm === true ? DEFAULT_SESSION_TITLE_LLM_CONFIG : options.sessionTitleLlm,
|
|
)
|
|
}
|
|
await ctx.plugin(SystemPrompt, { persona: '' })
|
|
await ctx.plugin(ToolRegistry)
|
|
await ctx.plugin(UserInteractionService)
|
|
await ctx.plugin(AgentRegistry)
|
|
await ctx.plugin(TaskService)
|
|
await ctx.plugin(AgentLoop, { agents: [] })
|
|
await ctx.plugin(LlmDeepSeek, {})
|
|
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
|
|
// face; deviations are noted inline.
|
|
await ctx.plugin(toolBash, {})
|
|
await ctx.plugin(toolTodo)
|
|
await ctx.plugin(toolTasks, {})
|
|
// fs paths resolve against the host default project rather than the raw
|
|
// process cwd — the same source create() injects into session.cwd.
|
|
await ctx.plugin(FsLocal, { cwd: defaults.cwd })
|
|
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, {})
|
|
await ctx.plugin(toolSkill, {})
|
|
// Request pressure + compaction (service-wide defaults, as in repl-agent).
|
|
await ctx.plugin(TokenMeter)
|
|
await ctx.plugin(CompactBasic)
|
|
// Subagent spawn/fork backends and their two model-facing tool instances.
|
|
await ctx.plugin(SubagentService)
|
|
await ctx.plugin(SubagentSpawn, { providerName: 'spawn' })
|
|
await ctx.plugin(SubagentFork, { providerName: 'fork' })
|
|
await ctx.plugin(toolSubagent, { provider: 'spawn', toolName: 'subagent' })
|
|
await ctx.plugin(toolSubagent, { provider: 'fork', toolName: 'subagent_fork' })
|
|
await ctx.plugin(WorkflowWorkerthread, { provider: 'spawn' })
|
|
await ctx.plugin(toolWorkflow, {})
|
|
// Declared per-tool timeouts become enforced deadlines.
|
|
await ctx.plugin(timeoutPolicy)
|
|
// Oversized tool output spills to session-scoped files (repl-agent budget).
|
|
await ctx.plugin(SpillLocal, {})
|
|
await ctx.plugin(spillPolicy, { maxInlineBytes: 50000 })
|
|
return { ctx, defaults, dispose: () => ctx.fiber.dispose() }
|
|
}
|