2026-06-21 12:03:44 +08:00
|
|
|
/**
|
|
|
|
|
* The providerless, executor-less, UI-less agent spine as ONE bundle plugin.
|
|
|
|
|
*
|
|
|
|
|
* Loads the fixed set of services every harness agent needs — `timer`, the LLM
|
|
|
|
|
* service, the session store, system-prompt assembly, the tool registry, the
|
|
|
|
|
* agent registry, the dev-mode invariants, the model-facing `bash` tool
|
2026-06-25 16:04:42 +08:00
|
|
|
* schemas, project instruction loading, and the concrete `agent-loop` — and
|
|
|
|
|
* forwards the loop's `agents` list as its OWN config (default `[]`), so each
|
|
|
|
|
* app supplies its own pre-created agents.
|
2026-06-21 12:03:44 +08:00
|
|
|
*
|
|
|
|
|
* It is deliberately NOT the whole app: the swappable choices stay OUTSIDE the
|
|
|
|
|
* bundle, picked by whatever loads it.
|
|
|
|
|
* - the LLM ADAPTER (`llm-deepseek`/`llm-pi-ai`/`llm-replay`) — the bundle
|
|
|
|
|
* ships the abstract `llm` service + `tool-bash` consumer schema; the leaf
|
|
|
|
|
* registers a concrete adapter on `ctx.llm`.
|
|
|
|
|
* - the bash EXECUTOR (`bash-local` or a sandboxed impl) — the bundle ships
|
|
|
|
|
* the `bash` tool consumer; the leaf provides `ctx.bash`.
|
|
|
|
|
* - the PRESENTATION (stdio UI / ACP bridge / a logger) and the per-app infra
|
|
|
|
|
* (a console logger, `hmr`) — these are the coupled "front-door cluster" the
|
|
|
|
|
* app packages ({@link @deepseek-ai/dsh-stdio-agent},
|
|
|
|
|
* {@link @deepseek-ai/dsh-acp-agent}) bake in, NOT the shared spine.
|
|
|
|
|
*
|
|
|
|
|
* This is the interface/implementation/consumer seam at the composition level:
|
|
|
|
|
* the bundle owns the shared spine, the leaf owns the backends, the app package
|
|
|
|
|
* owns the front door. `timer` is in the spine (common to every front door — it
|
|
|
|
|
* writes nothing to stdout); the console logger is NOT (it writes to stdout,
|
|
|
|
|
* which the ACP bridge reserves for its JSON-RPC channel).
|
|
|
|
|
*
|
|
|
|
|
* Services register in the root store keyed by their isolate symbol, so a child
|
|
|
|
|
* loaded here via `ctx.plugin(...)` is visible to the bundle's SIBLINGS (the
|
|
|
|
|
* leaf's adapter and executor) exactly as a nested `plugin-include` subtree's
|
|
|
|
|
* services were before this bundle existed — cordis gates every read on
|
|
|
|
|
* `inject`, never on load order, so the fixed child set resolves regardless of
|
|
|
|
|
* which entry loads first.
|
|
|
|
|
*
|
|
|
|
|
* Plugin export shape: named `name`/`Config`/`apply`, NO default export — the
|
|
|
|
|
* cordis Loader's `unwrapExports` does `exports.default ?? exports`, so a stray
|
|
|
|
|
* default would collapse the module to the bare `apply` function and drop the
|
|
|
|
|
* `Config` schema (see docs/postmortem/0001). The keyless Loader-path smokes in
|
|
|
|
|
* the app packages guard this end-to-end.
|
|
|
|
|
*
|
|
|
|
|
* @module @deepseek-ai/dsh-agent-core
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import type { Context } from 'cordis'
|
|
|
|
|
import Timer from '@cordisjs/plugin-timer'
|
2026-06-25 16:04:42 +08:00
|
|
|
import z from 'schemastery'
|
2026-06-21 12:03:44 +08:00
|
|
|
import LlmService from '@deepseek-ai/dsh-llm'
|
|
|
|
|
import SessionStore from '@deepseek-ai/dsh-session'
|
2026-07-05 23:23:46 +08:00
|
|
|
import SystemPrompt, { type Config as SystemPromptConfig } from '@deepseek-ai/dsh-system-prompt'
|
2026-06-21 12:03:44 +08:00
|
|
|
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
|
|
|
|
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
|
|
|
|
import * as invariants from '@deepseek-ai/dsh-invariants'
|
|
|
|
|
import * as toolBash from '@deepseek-ai/dsh-tool-bash'
|
2026-06-25 16:04:42 +08:00
|
|
|
import * as projectInstructions from '@deepseek-ai/dsh-project-instructions'
|
2026-06-21 12:03:44 +08:00
|
|
|
import AgentLoop, { type Config as AgentLoopConfig } from '@deepseek-ai/dsh-agent-loop'
|
|
|
|
|
|
|
|
|
|
export const name = 'agent-core'
|
|
|
|
|
|
|
|
|
|
/**
|
2026-07-05 23:23:46 +08:00
|
|
|
* Bundle config: each field forwarded verbatim to the child that owns it —
|
|
|
|
|
* `agents` to the agent loop (an app that pre-creates no agents, like the ACP
|
2026-07-07 11:03:11 +08:00
|
|
|
* bridge, simply omits it), `persona` and `toolOrder` to the system-prompt
|
|
|
|
|
* plugin (the deployment's persona section and the explicit model-facing tool
|
2026-07-09 23:04:41 +08:00
|
|
|
* order), and `projectInstructions` to the project-instructions plugin. Every
|
|
|
|
|
* field is optional INPUT here because each owner's schema supplies the
|
|
|
|
|
* default (`[]` / `''` / absent — lexicographic / loader defaults); the schema
|
|
|
|
|
* is the INTERSECTION of the owners' own schemas, so validation and defaulting
|
2026-07-07 11:03:11 +08:00
|
|
|
* can never drift from them.
|
2026-06-21 12:03:44 +08:00
|
|
|
*/
|
2026-06-25 16:04:42 +08:00
|
|
|
export interface Config {
|
2026-07-05 23:23:46 +08:00
|
|
|
/** The agent-loop `agents` list (see dsh-agent-loop's `Config`). */
|
2026-06-25 16:04:42 +08:00
|
|
|
agents?: AgentLoopConfig['agents']
|
2026-07-05 23:23:46 +08:00
|
|
|
/** The deployment persona (see dsh-system-prompt's `Config`). */
|
|
|
|
|
persona?: SystemPromptConfig['persona']
|
2026-07-07 11:03:11 +08:00
|
|
|
/** The explicit model-facing tool order (see dsh-system-prompt's `Config`). */
|
|
|
|
|
toolOrder?: SystemPromptConfig['toolOrder']
|
2026-07-06 10:14:47 +08:00
|
|
|
/** Project-instruction loader controls; set `false` for hermetic prompts. */
|
2026-06-25 16:04:42 +08:00
|
|
|
projectInstructions?: projectInstructions.Config | false
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-06 10:14:47 +08:00
|
|
|
const ProjectInstructionsConfig = z.object({
|
2026-06-25 16:04:42 +08:00
|
|
|
projectInstructions: z.union([z.const(false), projectInstructions.Config]),
|
2026-07-06 10:14:47 +08:00
|
|
|
}) as unknown as z<Pick<Config, 'projectInstructions'>>
|
|
|
|
|
|
2026-07-05 23:23:46 +08:00
|
|
|
/** Intersect the owners' schemas so validation + defaulting stay identical. */
|
2026-07-06 10:14:47 +08:00
|
|
|
export const Config = z.intersect([
|
|
|
|
|
AgentLoop.Config,
|
|
|
|
|
SystemPrompt.Config,
|
|
|
|
|
ProjectInstructionsConfig,
|
|
|
|
|
]) as unknown as z<Config>
|
2026-06-21 12:03:44 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Load the spine. Each `ctx.plugin(...)` mounts one child of the bundle fiber;
|
2026-07-05 23:23:46 +08:00
|
|
|
* `agent-loop` receives the forwarded `agents` list and `system-prompt` the
|
2026-07-09 23:04:41 +08:00
|
|
|
* forwarded `persona` and `toolOrder`. Project-instructions receives its own
|
|
|
|
|
* forwarded config or loads with defaults. Load order is irrelevant (cordis
|
|
|
|
|
* pends each fiber on its `inject` until the services it needs exist), but the
|
2026-07-07 11:03:11 +08:00
|
|
|
* listing mirrors the dependency layering for readability: the LLM vocabulary
|
2026-07-09 23:04:41 +08:00
|
|
|
* and core registries first, then extension plugins that wrap request/tool
|
|
|
|
|
* seams, then the loop that drives them.
|
2026-06-21 12:03:44 +08:00
|
|
|
*/
|
|
|
|
|
export function apply(ctx: Context, config: Config): void {
|
|
|
|
|
ctx.plugin(Timer)
|
|
|
|
|
ctx.plugin(LlmService)
|
|
|
|
|
ctx.plugin(SessionStore)
|
2026-07-05 23:23:46 +08:00
|
|
|
// The forwarded fields are validated + defaulted by this bundle's intersected
|
|
|
|
|
// schema before apply runs, so the ?? fallbacks only narrow the
|
|
|
|
|
// optional-input TYPES — they mirror the owners' schema defaults, never
|
2026-07-07 11:03:11 +08:00
|
|
|
// introduce different ones. toolOrder has no owner-supplied default value —
|
|
|
|
|
// ABSENT means "lexicographic order" — so it is forwarded conditionally
|
|
|
|
|
// rather than via ??.
|
|
|
|
|
ctx.plugin(SystemPrompt, {
|
|
|
|
|
persona: config.persona ?? '',
|
|
|
|
|
...config.toolOrder !== undefined ? { toolOrder: config.toolOrder } : {},
|
|
|
|
|
})
|
2026-06-21 12:03:44 +08:00
|
|
|
ctx.plugin(ToolRegistry)
|
|
|
|
|
ctx.plugin(AgentRegistry)
|
|
|
|
|
ctx.plugin(invariants)
|
|
|
|
|
ctx.plugin(toolBash)
|
2026-06-25 16:04:42 +08:00
|
|
|
if (config.projectInstructions !== false) {
|
|
|
|
|
ctx.plugin(projectInstructions, config.projectInstructions ?? {})
|
|
|
|
|
}
|
|
|
|
|
ctx.plugin(AgentLoop, { agents: config.agents ?? [] })
|
2026-06-21 12:03:44 +08:00
|
|
|
}
|