Merge remote-tracking branch 'origin/master' into jsonl-packed-chunk-rows
# Conflicts: # docs/event-producer-consumer.md
This commit is contained in:
@@ -30,6 +30,8 @@ Because the package wires no logger entry, an ACP leaf has **nothing to get wron
|
||||
| `toolOrder` | — | explicit model-facing tool order (a name list with one `'<unlisted-tools>'` rest entry; absent — lexicographic; an unregistered name fails each turn at prompt assembly), routed to `dsh-system-prompt` |
|
||||
| `tools` | `{ mode: 'native' }` | tool-registry presentation config (`native` / `code` / `both`), routed through `dsh-agent-spine-demo` |
|
||||
| `skills` | owner defaults | registry-cache, local-provider, and model-facing skill-tool config, routed through `dsh-agent-spine-demo` |
|
||||
| `toolBash` | owner defaults | model-facing bash config routed through `dsh-agent-spine-demo`, including bash's producer-local `enableRunInBackground` |
|
||||
| `toolTasks` | owner defaults | generic `task_output` wait bounds routed through `dsh-agent-spine-demo` |
|
||||
| `persistenceRoot` | `./.sessions` | the JSONL backend's root directory |
|
||||
| `packChunks` | `false` | write delta-chunk runs as packed storage rows (the JSONL backend's `packChunks`) |
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ export interface Config {
|
||||
packChunks?: boolean
|
||||
/** Skill registry, local-provider, and model-facing consumer config forwarded to agent-spine-demo. */
|
||||
skills?: agentCore.SkillConfig
|
||||
/** Model-facing bash tool config forwarded through agent-core. */
|
||||
toolBash?: NonNullable<agentCore.Config['toolBash']>
|
||||
/** Generic background-task control-tool config forwarded through agent-core. */
|
||||
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
|
||||
}
|
||||
|
||||
// Each front door owns a complete, directly readable config schema; extracting
|
||||
@@ -61,6 +65,8 @@ export const Config: z<Config> = z.object({
|
||||
persistenceRoot: z.string().default('./.sessions'),
|
||||
packChunks: z.boolean().default(false),
|
||||
skills: agentCore.SkillConfigSchema,
|
||||
toolBash: agentCore.ToolBashConfigSchema,
|
||||
toolTasks: agentCore.ToolTasksConfigSchema,
|
||||
})
|
||||
/* jscpd:ignore-end */
|
||||
|
||||
@@ -77,6 +83,8 @@ export function apply(ctx: Context, config: Config): void {
|
||||
...config.toolOrder !== undefined ? { toolOrder: config.toolOrder } : {},
|
||||
...config.tools !== undefined ? { tools: config.tools } : {},
|
||||
...config.skills !== undefined ? { skills: config.skills } : {},
|
||||
...config.toolBash !== undefined ? { toolBash: config.toolBash } : {},
|
||||
...config.toolTasks !== undefined ? { toolTasks: config.toolTasks } : {},
|
||||
})
|
||||
ctx.plugin(UserInteractionService)
|
||||
ctx.plugin(SessionPersistenceJsonl, {
|
||||
|
||||
@@ -19,8 +19,9 @@ import * as acpAgent from '../src/index.ts'
|
||||
* ACP operations end-to-end) is the keyless bin smoke in `load-path.e2e.ts`;
|
||||
* this spec asserts the composition and the persistenceRoot default branch.
|
||||
*/
|
||||
async function mount(config: acpAgent.Config): Promise<Context> {
|
||||
async function mount(config: acpAgent.Config, withBash = false): Promise<Context> {
|
||||
const ctx = new Context()
|
||||
if (withBash) ctx.provide('bash', { sandboxMode: undefined })
|
||||
await ctx.plugin(acpAgent, config)
|
||||
// The bundle mounts its children inside apply() (not awaited there); let their
|
||||
// fibers settle so the spine services are ready.
|
||||
@@ -112,6 +113,19 @@ describe('dsh-acp-demo composition', () => {
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('forwards bundled tool config into agent-core', async () => {
|
||||
const ctx = await mount({
|
||||
model: 'mock',
|
||||
toolBash: { enableRunInBackground: false },
|
||||
toolTasks: { waitTimeoutMs: 7, maxWaitTimeoutMs: 11 },
|
||||
skills: await isolatedSkillsConfig(),
|
||||
}, true)
|
||||
const bash = ctx.tools.schemas().find(tool => tool.name === 'bash')
|
||||
expect(Object.keys((bash!.parameters as { properties: Record<string, unknown> }).properties))
|
||||
.not.toContain('run_in_background')
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('exposes its plugin shape', () => {
|
||||
expect(acpAgent.name).toBe('acp-demo')
|
||||
expect(acpAgent.Config).toBeDefined()
|
||||
@@ -134,7 +148,7 @@ describe('dsh-acp-demo composition', () => {
|
||||
})
|
||||
}
|
||||
const assembly = await ctx.get('systemPrompt')!.assemble()
|
||||
expect(assembly.tools.map(tool => tool.name)).toEqual(['zulu', 'alpha', 'skill'])
|
||||
expect(assembly.tools.map(tool => tool.name)).toEqual(['zulu', 'alpha', 'skill', 'task_kill', 'task_list', 'task_output'])
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@ Read this package for the whole plugin tree and its composition order.
|
||||
@deepseek-ai/dsh-skill skill provider registry
|
||||
@deepseek-ai/dsh-skill-local local filesystem skill provider
|
||||
@deepseek-ai/dsh-agent agent registry + agent/* event vocabulary
|
||||
@deepseek-ai/dsh-invariants runtime event-contract assertions
|
||||
@deepseek-ai/dsh-tool-bash the model-facing bash/bash_output/bash_kill schemas
|
||||
@deepseek-ai/dsh-tasks generic background-task registry
|
||||
@deepseek-ai/dsh-invariants dev-mode event-contract assertions
|
||||
@deepseek-ai/dsh-tool-bash the model-facing bash schema
|
||||
@deepseek-ai/dsh-tool-skill session-prefix skill catalog + model-facing loader schema
|
||||
@deepseek-ai/dsh-tool-tasks task_output/task_list/task_kill schemas + completion notices
|
||||
@deepseek-ai/dsh-agent-loop THE concrete loop (gets the forwarded `agents`)
|
||||
(dsh-system-prompt gets the forwarded `persona`)
|
||||
```
|
||||
@@ -39,11 +41,12 @@ This is the [interface/implementation/consumer seam](../../../docs/rfc/implement
|
||||
|
||||
```ts
|
||||
import type { Config } from '@deepseek-ai/dsh-agent-spine-demo'
|
||||
// { agents?, persona?, toolOrder?, tools?, skills? } — the schema intersects the owner schemas,
|
||||
// { agents?, persona?, toolOrder?, tools?, skills?, toolBash?, toolTasks? }
|
||||
// The schema intersects the owner schemas,
|
||||
// so validation and defaulting can never drift from the owners.
|
||||
```
|
||||
|
||||
The bundle FORWARDS each field to the child that owns it: `agents` to `agent-loop` (default `[]`), so each app supplies its own pre-created agents — a stdio app pre-creates a `main`; the ACP app pre-creates none (it creates agents on demand at `session/new`) — `persona` and `toolOrder` to `dsh-system-prompt`; `tools` to the tool registry for its presentation mode; and `skills.registry`, `skills.local`, and `skills.tool` to the skill registry, local provider, and model-facing consumer. Forwarding is exactly why the owners can live in the shared spine even though the apps disagree on what to configure.
|
||||
The bundle FORWARDS each field to the child that owns it: `agents` to `agent-loop` (default `[]`), so each app supplies its own pre-created agents — a stdio app pre-creates a `main`; the ACP app pre-creates none (it creates agents on demand at `session/new`) — `persona` and `toolOrder` to `dsh-system-prompt`; `tools` to the tool registry for its presentation mode; `skills.registry`, `skills.local`, and `skills.tool` to the skill registry, local provider, and model-facing consumer; and `toolBash`/`toolTasks` to the two model-facing tool plugins the bundle owns. `toolBash.enableRunInBackground` controls only the bash producer, while `toolTasks` controls generic `task_output` wait bounds; independently loaded producers keep their own config. Forwarding is exactly why the owners can live in the shared spine even though the apps disagree on what to configure.
|
||||
|
||||
## Why a code bundle, not a shared YAML include
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-agent-spine-demo",
|
||||
"description": "The default executor-less/UI-less agent spine as one Cordis bundle plugin (timer + llm + sessions + system-prompt + tools + skills + agents + invariants + tool-bash + tool-skill + agent-loop)",
|
||||
"description": "The default executor-less/UI-less agent spine as one Cordis bundle plugin (timer + llm + sessions + system-prompt + tools + skills + agents + tasks + invariants + tool-bash + tool-skill + tool-tasks + agent-loop)",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -31,8 +31,10 @@
|
||||
"@deepseek-ai/dsh-skill": "^0.0.1",
|
||||
"@deepseek-ai/dsh-skill-local": "^0.0.1",
|
||||
"@deepseek-ai/dsh-system-prompt": "^0.0.1",
|
||||
"@deepseek-ai/dsh-tasks": "^0.0.1",
|
||||
"@deepseek-ai/dsh-tool-bash": "^0.0.1",
|
||||
"@deepseek-ai/dsh-tool-skill": "^0.0.1",
|
||||
"@deepseek-ai/dsh-tool-tasks": "^0.0.1",
|
||||
"@deepseek-ai/dsh-tools": "^0.0.1",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
},
|
||||
@@ -46,8 +48,10 @@
|
||||
"@deepseek-ai/dsh-skill": "workspace:^",
|
||||
"@deepseek-ai/dsh-skill-local": "workspace:^",
|
||||
"@deepseek-ai/dsh-system-prompt": "workspace:^",
|
||||
"@deepseek-ai/dsh-tasks": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-bash": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-skill": "workspace:^",
|
||||
"@deepseek-ai/dsh-tool-tasks": "workspace:^",
|
||||
"@deepseek-ai/dsh-tools": "workspace:^",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Default executor-less, UI-less agent spine. It bundles the common services,
|
||||
* concrete loop, local skill provider, and model-facing bash/skill consumers;
|
||||
* deployments still choose the LLM adapter, bash executor, and presentation.
|
||||
* background-task registry and controls, concrete loop, local skill provider,
|
||||
* and model-facing bash/skill consumers; deployments still choose the LLM
|
||||
* adapter, bash executor, and presentation.
|
||||
* The plugin intentionally exposes named exports only because Loader default
|
||||
* unwrapping would discard its `Config` schema (see docs/postmortem/0001).
|
||||
* @module @deepseek-ai/dsh-agent-spine-demo
|
||||
@@ -17,9 +18,11 @@ import ToolRegistry, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools
|
||||
import SkillService, { type Config as SkillRegistryConfig } from '@deepseek-ai/dsh-skill'
|
||||
import * as SkillLocal from '@deepseek-ai/dsh-skill-local'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import TaskService from '@deepseek-ai/dsh-tasks'
|
||||
import * as invariants from '@deepseek-ai/dsh-invariants'
|
||||
import * as toolBash from '@deepseek-ai/dsh-tool-bash'
|
||||
import * as toolSkill from '@deepseek-ai/dsh-tool-skill'
|
||||
import * as toolTasks from '@deepseek-ai/dsh-tool-tasks'
|
||||
import AgentLoop, { type Config as AgentLoopConfig } from '@deepseek-ai/dsh-agent-loop'
|
||||
|
||||
export const name = 'agent-spine-demo'
|
||||
@@ -35,13 +38,19 @@ export interface SkillConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 bridge, omits it),
|
||||
* `persona` and `toolOrder` to the system-prompt plugin (the deployment's persona section and
|
||||
* the explicit model-facing tool order), the `tools` object to the tool registry (its
|
||||
* presentation `mode`), and `skills` to the skill registry/local provider/tool consumer.
|
||||
* The schema intersects the owners' schemas, which supply defaults for every
|
||||
* optional input and keep validation from drifting.
|
||||
* 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
|
||||
* bridge, simply omits it), `persona` and `toolOrder` to the system-prompt
|
||||
* plugin (the deployment's persona section and the explicit model-facing tool
|
||||
* order), the `tools` object to the tool registry (its presentation `mode`),
|
||||
* and `toolBash`/`toolTasks` to the two model-facing tool plugins this bundle
|
||||
* owns. Producer opt-in stays producer-local: `toolBash` configures bash only;
|
||||
* future background-capable tools remain independently composed plugins.
|
||||
* Every field is optional INPUT here because each owner's schema
|
||||
* supplies the default (`[]` / `''` / absent — lexicographic / `native`); the
|
||||
* schema is the INTERSECTION of the owners' own schemas (the registry's
|
||||
* nested under its `tools` key), so validation and defaulting can never
|
||||
* drift from them.
|
||||
*/
|
||||
export interface Config {
|
||||
/** The agent-loop `agents` list (see dsh-agent-loop's `Config`). */
|
||||
@@ -54,6 +63,10 @@ export interface Config {
|
||||
tools?: ToolsConfig
|
||||
/** Skill registry, local provider, and model-facing consumer config. */
|
||||
skills?: SkillConfig
|
||||
/** Model-facing bash tool config, including this producer's background opt-in. */
|
||||
toolBash?: toolBash.Config
|
||||
/** Generic background-task control-tool wait bounds. */
|
||||
toolTasks?: toolTasks.Config
|
||||
}
|
||||
|
||||
/** The skill config schema exported for app packages that forward `skills`. */
|
||||
@@ -63,11 +76,22 @@ export const SkillConfigSchema: z<SkillConfig> = z.object({
|
||||
tool: toolSkill.Config,
|
||||
})
|
||||
|
||||
/** The bash-tool config schema exported for app packages that forward `toolBash`. */
|
||||
export const ToolBashConfigSchema: z<toolBash.Config> = toolBash.Config
|
||||
|
||||
/** The task-control-tool config schema exported for app packages that forward `toolTasks`. */
|
||||
export const ToolTasksConfigSchema: z<toolTasks.Config> = toolTasks.Config
|
||||
|
||||
/** Intersect the owners' schemas so validation + defaulting stay identical. */
|
||||
export const Config = z.intersect([
|
||||
AgentLoop.Config,
|
||||
SystemPrompt.Config,
|
||||
z.object({ tools: ToolRegistry.Config, skills: SkillConfigSchema }),
|
||||
z.object({
|
||||
tools: ToolRegistry.Config,
|
||||
skills: SkillConfigSchema,
|
||||
toolBash: ToolBashConfigSchema,
|
||||
toolTasks: ToolTasksConfigSchema,
|
||||
}),
|
||||
]) as unknown as z<Config>
|
||||
|
||||
/**
|
||||
@@ -92,8 +116,10 @@ export function apply(ctx: Context, config: Config): void {
|
||||
ctx.plugin(SkillService, config.skills?.registry ?? {})
|
||||
ctx.plugin(SkillLocal, config.skills?.local ?? {})
|
||||
ctx.plugin(AgentRegistry)
|
||||
ctx.plugin(TaskService)
|
||||
ctx.plugin(invariants)
|
||||
ctx.plugin(toolBash)
|
||||
ctx.plugin(toolBash, config.toolBash ?? {})
|
||||
ctx.plugin(toolSkill, config.skills?.tool ?? {})
|
||||
ctx.plugin(toolTasks, config.toolTasks ?? {})
|
||||
ctx.plugin(AgentLoop, { agents: config.agents ?? [] })
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { tmpdir } from 'node:os'
|
||||
@@ -7,7 +7,13 @@ import Loader from '@cordisjs/plugin-loader'
|
||||
import { TOOL_ORDER_REST } from '@deepseek-ai/dsh-system-prompt'
|
||||
import * as agentCore from '../src/index.ts'
|
||||
import { AgentId, agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import type { Message } from '@deepseek-ai/dsh-llm'
|
||||
import { CallId, type Message } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
declare module '@deepseek-ai/dsh-tasks' {
|
||||
interface TaskKindMap {
|
||||
probe: 'probe'
|
||||
}
|
||||
}
|
||||
|
||||
async function composePrefix(ctx: Context, cwd: string): Promise<Message[]> {
|
||||
const agent = { session: { header: { cwd } } } as unknown as Agent
|
||||
@@ -28,12 +34,13 @@ async function composePrefix(ctx: Context, cwd: string): Promise<Message[]> {
|
||||
* Loader-path guard (export shape, `unwrapExports`) is the app packages' keyless
|
||||
* bin smokes; here we assert the composition + config forwarding.
|
||||
*/
|
||||
async function mount(config?: agentCore.Config): Promise<Context> {
|
||||
async function mount(config?: agentCore.Config, withBash = false): Promise<Context> {
|
||||
const oldDshHome = process.env.DSH_HOME
|
||||
const oldAgentsHome = process.env.DSH_AGENTS_HOME
|
||||
process.env.DSH_HOME = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-home-'))
|
||||
process.env.DSH_AGENTS_HOME = await mkdtemp(join(tmpdir(), 'dsh-agent-spine-demo-agents-'))
|
||||
const ctx = new Context()
|
||||
if (withBash) ctx.provide('bash', { sandboxMode: undefined })
|
||||
try {
|
||||
await ctx.plugin(agentCore, config)
|
||||
// The bundle mounts its children inside apply() (not awaited there); let their
|
||||
@@ -86,6 +93,7 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
expect(ctx.get('tools')).toBeDefined()
|
||||
expect(ctx.get('skills')).toBeDefined()
|
||||
expect(ctx.get('agents')).toBeDefined()
|
||||
expect(ctx.get('tasks')).toBeDefined()
|
||||
expect(ctx.get('agentLoop')).toBeDefined()
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
@@ -153,6 +161,33 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('forwards its bundled tool configs to tool-bash and tool-tasks', async () => {
|
||||
const ctx = await mount({
|
||||
toolBash: { enableRunInBackground: false },
|
||||
toolTasks: { waitTimeoutMs: 7, maxWaitTimeoutMs: 11 },
|
||||
}, true)
|
||||
|
||||
const bash = ctx.tools.schemas().find(tool => tool.name === 'bash')
|
||||
expect(bash).toBeDefined()
|
||||
expect(Object.keys((bash!.parameters as { properties: Record<string, unknown> }).properties))
|
||||
.not.toContain('run_in_background')
|
||||
|
||||
const id = ctx.tasks.start({
|
||||
kind: 'probe',
|
||||
label: 'config forwarding probe',
|
||||
run: () => ({ cancel: () => {}, done: Promise.resolve({ status: 'completed' }) }),
|
||||
})
|
||||
const wait = vi.spyOn(ctx.tasks, 'wait')
|
||||
await ctx.tools.execute({
|
||||
callId: CallId('task-config-forwarding'),
|
||||
name: 'task_output',
|
||||
arguments: { task_id: id, wait: true },
|
||||
})
|
||||
expect(wait).toHaveBeenCalledWith(id, 7, undefined, undefined)
|
||||
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('uses the default skill config when apply is called directly without skills', async () => {
|
||||
await withIsolatedSkillHomes(async () => {
|
||||
const ctx = new Context()
|
||||
@@ -177,7 +212,7 @@ describe('dsh-agent-spine-demo bundle', () => {
|
||||
})
|
||||
}
|
||||
const assembly = await ctx.get('systemPrompt')!.assemble()
|
||||
expect(assembly.tools.map(tool => tool.name)).toEqual(['zulu', 'alpha', 'skill'])
|
||||
expect(assembly.tools.map(tool => tool.name)).toEqual(['zulu', 'alpha', 'skill', 'task_kill', 'task_list', 'task_output'])
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
{
|
||||
"path": "../../../vendor/cordis"
|
||||
},
|
||||
{
|
||||
"path": "../../../vendor/schemastery"
|
||||
},
|
||||
{
|
||||
"path": "../../../vendor/timer"
|
||||
},
|
||||
@@ -52,6 +49,12 @@
|
||||
},
|
||||
{
|
||||
"path": "../../bash/tool-bash"
|
||||
},
|
||||
{
|
||||
"path": "../../tasks/tasks"
|
||||
},
|
||||
{
|
||||
"path": "../../tasks/tool-tasks"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ The leaf `cordis.yml` supplies only the **swappable backends** — an LLM adapte
|
||||
| `toolOrder` | — | explicit model-facing tool order (a name list with one `'<unlisted-tools>'` rest entry; absent — lexicographic; an unregistered name fails each turn at prompt assembly), routed to `dsh-system-prompt` |
|
||||
| `tools` | `{ mode: 'native' }` | tool-registry presentation config (`native` / `code` / `both`), routed through `dsh-agent-spine-demo` |
|
||||
| `skills` | owner defaults | registry-cache, local-provider, and model-facing skill-tool config, routed through `dsh-agent-spine-demo` |
|
||||
| `toolBash` | owner defaults | model-facing bash config routed through `dsh-agent-spine-demo`, including bash's producer-local `enableRunInBackground` |
|
||||
| `toolTasks` | owner defaults | generic `task_output` wait bounds routed through `dsh-agent-spine-demo` |
|
||||
| `persistenceRoot` | `./.sessions` | the JSONL backend's root directory |
|
||||
| `packChunks` | `false` | write delta-chunk runs as packed storage rows (the JSONL backend's `packChunks`) |
|
||||
| `welcome` | `ready.` | the stdin-chat banner |
|
||||
|
||||
@@ -50,6 +50,10 @@ export interface Config {
|
||||
welcome?: string
|
||||
/** Skill registry, local-provider, and model-facing consumer config forwarded to agent-spine-demo. */
|
||||
skills?: agentCore.SkillConfig
|
||||
/** Model-facing bash tool config forwarded through agent-core. */
|
||||
toolBash?: NonNullable<agentCore.Config['toolBash']>
|
||||
/** Generic background-task control-tool config forwarded through agent-core. */
|
||||
toolTasks?: NonNullable<agentCore.Config['toolTasks']>
|
||||
/**
|
||||
* If set, the `main` agent RESUMES this persisted session id instead of
|
||||
* starting fresh. Sourced from an env var in the leaf `cordis.yml`
|
||||
@@ -72,6 +76,8 @@ export const Config: z<Config> = z.object({
|
||||
packChunks: z.boolean().default(false),
|
||||
welcome: z.string().default('ready.'),
|
||||
skills: agentCore.SkillConfigSchema,
|
||||
toolBash: agentCore.ToolBashConfigSchema,
|
||||
toolTasks: agentCore.ToolTasksConfigSchema,
|
||||
resumeSessionId: z.string(),
|
||||
})
|
||||
|
||||
@@ -95,6 +101,8 @@ export function apply(ctx: Context, config: Config): void {
|
||||
...config.resumeSessionId !== undefined ? { resumeSessionId: SessionId(config.resumeSessionId) } : {},
|
||||
}],
|
||||
...config.skills !== undefined ? { skills: config.skills } : {},
|
||||
...config.toolBash !== undefined ? { toolBash: config.toolBash } : {},
|
||||
...config.toolTasks !== undefined ? { toolTasks: config.toolTasks } : {},
|
||||
})
|
||||
ctx.plugin(SessionPersistenceJsonl, {
|
||||
root: config.persistenceRoot ?? './.sessions',
|
||||
|
||||
@@ -15,8 +15,9 @@ import * as stdioAgent from '../src/index.ts'
|
||||
* keyless echo smoke; this tier pins the export shape because an inject-less app could otherwise
|
||||
* survive namespace collapse while silently losing its schema.
|
||||
*/
|
||||
async function mount(config: stdioAgent.Config): Promise<Context> {
|
||||
async function mount(config: stdioAgent.Config, withBash = false): Promise<Context> {
|
||||
const ctx = new Context()
|
||||
if (withBash) ctx.provide('bash', { sandboxMode: undefined })
|
||||
await ctx.plugin(stdioAgent, config)
|
||||
// The app mounts its children inside apply() (not awaited there); let their
|
||||
// fibers settle so the spine services + the pre-created agent are ready.
|
||||
@@ -126,6 +127,19 @@ describe('dsh-stdio-demo app', () => {
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('forwards bundled tool config into agent-core', async () => {
|
||||
const ctx = await mount({
|
||||
model: 'mock',
|
||||
toolBash: { enableRunInBackground: false },
|
||||
toolTasks: { waitTimeoutMs: 7, maxWaitTimeoutMs: 11 },
|
||||
skills: await isolatedSkillsConfig(),
|
||||
}, true)
|
||||
const bash = ctx.tools.schemas().find(tool => tool.name === 'bash')
|
||||
expect(Object.keys((bash!.parameters as { properties: Record<string, unknown> }).properties))
|
||||
.not.toContain('run_in_background')
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('exposes its name and Config schema', () => {
|
||||
expect(stdioAgent.name).toBe('stdio-demo')
|
||||
expect(stdioAgent.Config).toBeDefined()
|
||||
@@ -148,7 +162,7 @@ describe('dsh-stdio-demo app', () => {
|
||||
})
|
||||
}
|
||||
const assembly = await ctx.get('systemPrompt')!.assemble()
|
||||
expect(assembly.tools.map(tool => tool.name)).toEqual(['zulu', 'alpha', 'ask_user_question', 'skill'])
|
||||
expect(assembly.tools.map(tool => tool.name)).toEqual(['zulu', 'alpha', 'ask_user_question', 'skill', 'task_kill', 'task_list', 'task_output'])
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user