feat(core): add agent execution context
This commit is contained in:
@@ -8,7 +8,7 @@ Packages live at `packages/<group>/<pkg>/`; groups are containers, while names r
|
||||
|
||||
| Group | Role | Release expectation |
|
||||
|---|---|---|
|
||||
| [`core/`](core/README.md) | Product API spine: session, system-prompt, tools, agent, and the concrete loop | Product — stable surface |
|
||||
| [`core/`](core/README.md) | Product API spine: session, system-prompt, tools, agent, agent-execution, and the concrete loop | Product — stable surface |
|
||||
| [`llm/`](llm/README.md) | LLM capability family: the abstract service + provider adapters | Product — stable surface |
|
||||
| [`bash/`](bash/README.md) | Bash capability family: the executor seam, a local impl, and the model-facing tool | Product — stable surface |
|
||||
| [`code-runtime/`](code-runtime/README.md) | Code-execution capability family: the abstract runtime seam for model-written programs + a worker-thread backend | Product — stable surface |
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-user-approval": "workspace:^",
|
||||
"@deepseek-ai/dsh-bash": "workspace:^",
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import TaskService from '@deepseek-ai/dsh-tasks'
|
||||
import * as ToolTasks from '@deepseek-ai/dsh-tool-tasks'
|
||||
@@ -26,6 +27,7 @@ async function harness(adapter: MockAdapter) {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(TaskService)
|
||||
await ctx.plugin(ToolTasks)
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-compact": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { isToolPairingBalanced } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as Invariants from '@deepseek-ai/dsh-invariants'
|
||||
import { BasicCompactService } from '@deepseek-ai/dsh-compact-basic'
|
||||
@@ -66,6 +67,7 @@ async function harness(toolSteps: number): Promise<{ ctx: Context; compact: Repr
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], new StepwiseToolAdapter(toolSteps))
|
||||
ctx.tools.register(defineTool({
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
|
||||
@@ -8,6 +8,7 @@ import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as timeContext from '@deepseek-ai/dsh-time-context'
|
||||
import type { Config } from '@deepseek-ai/dsh-time-context'
|
||||
@@ -94,6 +95,7 @@ async function loopHarness(adapter: ScriptedAdapter, config: Config = {}): Promi
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(timeContext, config)
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-scope": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
|
||||
@@ -52,6 +52,15 @@ export interface TypeApiEntry {
|
||||
|
||||
/** Every harness `ctx.<key>` service, sorted by key. */
|
||||
export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
{
|
||||
key: 'agentExecution',
|
||||
summary: 'Ambient Agent identity within one process-local asynchronous chain.',
|
||||
methods: [
|
||||
'current(): AgentExecution | undefined',
|
||||
'require(): AgentExecution',
|
||||
'run<T>(execution: AgentExecution | undefined, operation: () => T): T',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'agentLoop',
|
||||
summary: 'Concrete ReactLoopAgent factory and driver service.',
|
||||
@@ -505,6 +514,10 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
name: 'Agent',
|
||||
declaration: 'export interface Agent {\n readonly id: AgentId;\n readonly options: AgentOptions;\n readonly session: Session;\n readonly status: AgentStatus;\n readonly ctx: Context;\n send(content: ContentBlock[], options?: SendOptions): void;\n steer(content: ContentBlock[], options?: SendOptions): void;\n inject(content: ContentBlock[], options?: SendOptions): void;\n cancel(reason?: string): void;\n whenIdle(): Promise<void>;\n}',
|
||||
},
|
||||
{
|
||||
name: 'AgentExecution',
|
||||
declaration: 'export interface AgentExecution {\n readonly agent: Agent;\n}',
|
||||
},
|
||||
{
|
||||
name: 'AgentFactory',
|
||||
declaration: 'export interface AgentFactory {\n createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle>;\n resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandle>;\n}',
|
||||
|
||||
@@ -5,6 +5,7 @@ import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as ToolCordis from '../src/index.ts'
|
||||
import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
|
||||
@@ -25,6 +26,7 @@ async function harness(adapter: MockAdapter): Promise<Context> {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(ToolCordis)
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
|
||||
@@ -9,10 +9,11 @@ The session log, system-prompt assembly, tool registry, agent vocabulary, and co
|
||||
| `system-prompt/` | Prompt-section + tool-schema assembly registry | `ctx.systemPrompt` |
|
||||
| `tools/` | Scoped tool registry + pre-policy, guards, around-dispatch, post-policy, and final-result observation | `ctx.tools` |
|
||||
| `agent/` | Agent interface, registry, `agent/*` event vocabulary | `ctx.agents` |
|
||||
| `agent-execution/` | Process-local ambient Agent identity for asynchronous driver work | `ctx.agentExecution` |
|
||||
| `agent-loop/` | The concrete loop plugin: `ReactLoopAgent` + the loop driver | `ctx.agentLoop` |
|
||||
|
||||
`scope/` is the one non-service package here: a dependency-free library (`createScope`/`scopeOf`/`scopeTarget`) the registries and the loop build per-agent scoping on — it sits below `session/` and `system-prompt/` in the module graph precisely so they can consume it without a cycle.
|
||||
|
||||
`agent-loop` is the one concrete implementation of the `agent` seam and lives here because it is the harness's default product loop; everything else in `core/` is interface/vocabulary. Plugins depend on the `agent` vocabulary, never on `agent-loop` directly, so the loop stays swappable.
|
||||
`agent-execution` is mandatory control infrastructure shared by concrete loops and deep process-local consumers. `agent-loop` is the one concrete implementation of the `agent` seam and lives here because it is the harness's default product loop; other plugins depend on the `agent` vocabulary and execution service, never on `agent-loop` directly, so the loop stays swappable.
|
||||
|
||||
The default composition that wires this spine into a runnable agent lives in [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.md): one bundle plugin that loads the control spine plus selected default capabilities (`timer` + `llm` + sessions + system-prompt + tools + agents + invariants + the local [skill family](../skill/README.md) + `tool-bash` + `agent-loop`) and forwards `agent-loop`'s `agents` list as its own config. It sits in `examples/` — ready-to-run demo/reference bundles — not in `core/`: `core/` ships the swappable spine pieces, while a demo bundle picks one concrete composition of them and adds a front door.
|
||||
The default composition that wires this spine into a runnable agent lives in [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.md): one bundle plugin that loads the control spine plus selected default capabilities (`timer` + `llm` + sessions + system-prompt + tools + agents + agent-execution + invariants + the local [skill family](../skill/README.md) + `tool-bash` + `agent-loop`) and forwards `agent-loop`'s `agents` list as its own config. It sits in `examples/` — ready-to-run demo/reference bundles — not in `core/`: `core/` ships the swappable spine pieces, while a demo bundle picks one concrete composition of them and adds a front door.
|
||||
|
||||
23
packages/core/agent-execution/README.md
Normal file
23
packages/core/agent-execution/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# dsh-agent-execution
|
||||
|
||||
Process-local ambient Agent identity for asynchronous work initiated by a concrete agent driver. The default export, `AgentExecutionProvider`, installs the mandatory `ctx.agentExecution` service; [`dsh-agent-loop`](../agent-loop/README.md) establishes one boundary around each driver's complete lifetime.
|
||||
|
||||
## Service: `AgentExecutionService` (ctx key: `agentExecution`)
|
||||
|
||||
- `current()` returns the inherited `AgentExecution` or `undefined` outside a driver and inside an explicit clearing boundary.
|
||||
- `require()` returns the inherited execution or throws `no agent execution context is active`.
|
||||
- `run(execution, operation)` returns the exact synchronous value or Promise from `operation`. Passing `undefined` establishes a real boundary that hides an inherited Agent.
|
||||
|
||||
The store contains only `{ readonly agent: Agent }`. A Session is available through `agent.session`; turn, step, signal, cwd, sandbox, authorization, and other capability state remain with their explicit owners. Ambient presence identifies the initiator but does not prove that the Agent is live or that an operation is authorized.
|
||||
|
||||
## Lifetime and detached work
|
||||
|
||||
Provider teardown rejects new `run()` boundaries, removes the service so injected dependents drain, waits for returned Promise boundaries, then disables its `AsyncLocalStorage`. In-flight code retaining the service can call `current()` and `require()` while it drains; after disposal, all three methods throw `agent execution service is disposed`.
|
||||
|
||||
Async resources created inside `run()` inherit its Agent even when the operation does not await them. Agent-owned foreground work may inherit the boundary but keeps using the explicit cancellation and disposal contract of its execution seam. Unrelated timers, queues, and deployment infrastructure start under `run(undefined, operation)` and own an explicit stop. Queue, worker, process, and wire boundaries serialize any identity they need instead of relying on ALS propagation.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **Process-local only** — ALS does not cross workers, child processes, HTTP, durable queues, or restarts; each boundary materializes a typed identity explicitly.
|
||||
- **Agent identity only** — turn, step, signal, cwd, sandbox, and authorization stay outside the frame until a concrete cross-cutting consumer justifies a separate design.
|
||||
- **Ambient references may outlive liveness** — consumers still check `agent.status`, their explicit signal, and the owning capability contract before lifecycle-sensitive work.
|
||||
31
packages/core/agent-execution/package.json
Normal file
31
packages/core/agent-execution/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-agent-execution",
|
||||
"description": "Agent-scoped asynchronous execution context for the DeepSeek Harness",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/types/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"lib/index.js",
|
||||
"lib/types/**/*.d.ts",
|
||||
"lib/types/**/*.d.ts.map",
|
||||
"src"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "^0.0.1",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"cordis": "^4.0.0-rc.7"
|
||||
}
|
||||
}
|
||||
139
packages/core/agent-execution/src/index.ts
Normal file
139
packages/core/agent-execution/src/index.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Process-local Agent execution context backed by Node AsyncLocalStorage.
|
||||
*
|
||||
* @module @deepseek-ai/dsh-agent-execution
|
||||
*/
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import { AsyncLocalStorage } from 'node:async_hooks'
|
||||
import type { AgentExecution } from './types.ts'
|
||||
|
||||
export type { AgentExecution } from './types.ts'
|
||||
|
||||
const NO_ACTIVE_EXECUTION = 'no agent execution context is active'
|
||||
const DISPOSED_SERVICE = 'agent execution service is disposed'
|
||||
|
||||
/** Ambient Agent identity within one process-local asynchronous chain. */
|
||||
export interface AgentExecutionService {
|
||||
/**
|
||||
* Read the active execution without requiring one.
|
||||
* @returns the inherited execution, or `undefined` outside/inside a cleared boundary.
|
||||
* @throws when this service instance has been disposed.
|
||||
*/
|
||||
current(): AgentExecution | undefined
|
||||
|
||||
/**
|
||||
* Read the active execution and fail when no boundary is active.
|
||||
* @returns the inherited execution.
|
||||
* @throws when no execution is active or this service instance has been disposed.
|
||||
*/
|
||||
require(): AgentExecution
|
||||
|
||||
/**
|
||||
* Run an operation inside an execution boundary. Passing `undefined` clears
|
||||
* an inherited execution; the exact synchronous value or Promise is returned.
|
||||
* @param execution - execution to inherit, or `undefined` for a clearing boundary.
|
||||
* @param operation - synchronous or asynchronous operation to invoke.
|
||||
* @returns the exact value returned by `operation`.
|
||||
* @throws when this service is closing/disposed, or when `operation` throws.
|
||||
*/
|
||||
run<T>(execution: AgentExecution | undefined, operation: () => T): T
|
||||
}
|
||||
|
||||
declare module 'cordis' {
|
||||
interface Context {
|
||||
agentExecution: AgentExecutionService
|
||||
}
|
||||
}
|
||||
|
||||
/** One provider-owned ALS instance with quiescent shutdown. */
|
||||
class DefaultAgentExecutionService implements AgentExecutionService {
|
||||
private readonly storage = new AsyncLocalStorage<AgentExecution | undefined>()
|
||||
private state: 'active' | 'closing' | 'disposed' = 'active'
|
||||
private activeRuns = 0
|
||||
private drainWaiter: PromiseWithResolvers<void> | undefined
|
||||
private disposalTask: Promise<void> | undefined
|
||||
|
||||
current(): AgentExecution | undefined {
|
||||
this.assertReadable()
|
||||
return this.storage.getStore()
|
||||
}
|
||||
|
||||
require(): AgentExecution {
|
||||
const execution = this.current()
|
||||
if (execution === undefined) throw new Error(NO_ACTIVE_EXECUTION)
|
||||
return execution
|
||||
}
|
||||
|
||||
run<T>(execution: AgentExecution | undefined, operation: () => T): T {
|
||||
if (this.state !== 'active') throw new Error(DISPOSED_SERVICE)
|
||||
this.activeRuns += 1
|
||||
let result: T
|
||||
try {
|
||||
result = this.storage.run(execution, operation)
|
||||
} catch (error: unknown) {
|
||||
this.releaseRun()
|
||||
throw error
|
||||
}
|
||||
if (result instanceof Promise) {
|
||||
void result.then(
|
||||
() => { this.releaseRun() },
|
||||
() => { this.releaseRun() },
|
||||
)
|
||||
} else {
|
||||
this.releaseRun()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Reject new boundaries while existing continuations remain readable. */
|
||||
close(): void {
|
||||
if (this.state === 'active') this.state = 'closing'
|
||||
}
|
||||
|
||||
/** Wait for every returned Promise boundary, then invalidate retained references. */
|
||||
dispose(): Promise<void> {
|
||||
return (this.disposalTask ??= (async () => {
|
||||
this.close()
|
||||
if (this.activeRuns !== 0) {
|
||||
this.drainWaiter ??= Promise.withResolvers<void>()
|
||||
await this.drainWaiter.promise
|
||||
}
|
||||
this.state = 'disposed'
|
||||
this.storage.disable()
|
||||
})())
|
||||
}
|
||||
|
||||
private assertReadable(): void {
|
||||
if (this.state === 'disposed') throw new Error(DISPOSED_SERVICE)
|
||||
}
|
||||
|
||||
private releaseRun(): void {
|
||||
this.activeRuns -= 1
|
||||
if (this.activeRuns !== 0) return
|
||||
this.drainWaiter?.resolve()
|
||||
this.drainWaiter = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** Cordis provider for the mandatory `ctx.agentExecution` service. */
|
||||
export class AgentExecutionProvider {
|
||||
private readonly service = new DefaultAgentExecutionService()
|
||||
|
||||
/**
|
||||
* Install one isolated execution service and its ordered lifecycle.
|
||||
* @param ctx - provider-owning Cordis context.
|
||||
*/
|
||||
constructor(ctx: Context) {
|
||||
const service = this.service
|
||||
ctx.effect(function* () {
|
||||
// First yielded, disposed last: invalidate ALS only after dependents and active runs drain.
|
||||
yield () => service.dispose()
|
||||
yield ctx.provide('agentExecution', service)
|
||||
// Last yielded, disposed first: prevent a teardown race from opening another boundary.
|
||||
yield () => { service.close() }
|
||||
}, 'agentExecution.lifecycle()')
|
||||
}
|
||||
}
|
||||
|
||||
export default AgentExecutionProvider
|
||||
12
packages/core/agent-execution/src/types.ts
Normal file
12
packages/core/agent-execution/src/types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Public Agent execution-context types.
|
||||
*
|
||||
* @module @deepseek-ai/dsh-agent-execution/types
|
||||
*/
|
||||
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
|
||||
/** The exact live Agent associated with one asynchronous execution chain. */
|
||||
export interface AgentExecution {
|
||||
readonly agent: Agent
|
||||
}
|
||||
136
packages/core/agent-execution/tests/agent-execution.spec.ts
Normal file
136
packages/core/agent-execution/tests/agent-execution.spec.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { AgentId, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import type { AgentExecution, AgentExecutionService } from '@deepseek-ai/dsh-agent-execution'
|
||||
|
||||
function execution(id: string): AgentExecution {
|
||||
return { agent: { id: AgentId(id) } as Agent }
|
||||
}
|
||||
|
||||
async function harness(): Promise<{
|
||||
ctx: Context
|
||||
service: AgentExecutionService
|
||||
dispose: () => Promise<void>
|
||||
}> {
|
||||
const ctx = new Context()
|
||||
const fiber = await ctx.plugin(AgentExecutionProvider)
|
||||
return {
|
||||
ctx,
|
||||
service: ctx.agentExecution,
|
||||
dispose: fiber.dispose,
|
||||
}
|
||||
}
|
||||
|
||||
describe('AgentExecutionProvider', () => {
|
||||
it('reports an absent boundary and requires an active execution', async () => {
|
||||
const { service, dispose } = await harness()
|
||||
expect(service.current()).toBeUndefined()
|
||||
expect(() => service.require()).toThrow('no agent execution context is active')
|
||||
await dispose()
|
||||
})
|
||||
|
||||
it('preserves exact synchronous and Promise return identities across await', async () => {
|
||||
const { service, dispose } = await harness()
|
||||
const active = execution('identity')
|
||||
const value = { result: true }
|
||||
expect(service.run(active, () => {
|
||||
expect(service.require()).toBe(active)
|
||||
return value
|
||||
})).toBe(value)
|
||||
|
||||
const promise = service.run(active, async () => {
|
||||
expect(service.require()).toBe(active)
|
||||
await Promise.resolve()
|
||||
expect(service.require()).toBe(active)
|
||||
return value
|
||||
})
|
||||
expect(service.run(active, () => promise)).toBe(promise)
|
||||
await expect(promise).resolves.toBe(value)
|
||||
expect(service.current()).toBeUndefined()
|
||||
await dispose()
|
||||
})
|
||||
|
||||
it('isolates overlapping executions', async () => {
|
||||
const { service, dispose } = await harness()
|
||||
const a = execution('a')
|
||||
const b = execution('b')
|
||||
const bothStarted = Promise.withResolvers<boolean>()
|
||||
const release = Promise.withResolvers<boolean>()
|
||||
let starts = 0
|
||||
const run = (active: AgentExecution): Promise<void> => service.run(active, async () => {
|
||||
expect(service.require()).toBe(active)
|
||||
starts += 1
|
||||
if (starts === 2) bothStarted.resolve(true)
|
||||
await release.promise
|
||||
expect(service.require()).toBe(active)
|
||||
})
|
||||
|
||||
const pending = [run(a), run(b)]
|
||||
await bothStarted.promise
|
||||
expect(service.current()).toBeUndefined()
|
||||
release.resolve(true)
|
||||
await Promise.all(pending)
|
||||
await dispose()
|
||||
})
|
||||
|
||||
it('restores nested and explicitly cleared boundaries', async () => {
|
||||
const { service, dispose } = await harness()
|
||||
const parent = execution('parent')
|
||||
const child = execution('child')
|
||||
|
||||
service.run(parent, () => {
|
||||
expect(service.require()).toBe(parent)
|
||||
service.run(child, () => { expect(service.require()).toBe(child) })
|
||||
expect(service.require()).toBe(parent)
|
||||
service.run(undefined, () => {
|
||||
expect(service.current()).toBeUndefined()
|
||||
expect(() => service.require()).toThrow('no agent execution context is active')
|
||||
})
|
||||
expect(service.require()).toBe(parent)
|
||||
})
|
||||
expect(service.current()).toBeUndefined()
|
||||
await dispose()
|
||||
})
|
||||
|
||||
it('restores context after synchronous throws and rejected operations', async () => {
|
||||
const { service, dispose } = await harness()
|
||||
const parent = execution('parent')
|
||||
const child = execution('child')
|
||||
const syncError = new Error('sync failure')
|
||||
const asyncError = new Error('async failure')
|
||||
|
||||
service.run(parent, () => {
|
||||
expect(() => service.run(child, () => { throw syncError })).toThrow(syncError)
|
||||
expect(service.require()).toBe(parent)
|
||||
})
|
||||
await expect(service.run(child, async () => {
|
||||
await Promise.resolve()
|
||||
throw asyncError
|
||||
})).rejects.toBe(asyncError)
|
||||
expect(service.current()).toBeUndefined()
|
||||
await dispose()
|
||||
})
|
||||
|
||||
it('stops new boundaries, drains active Promises, and invalidates retained references', async () => {
|
||||
const { ctx, service, dispose } = await harness()
|
||||
const active = execution('draining')
|
||||
const release = Promise.withResolvers<boolean>()
|
||||
const pending = service.run(active, async () => {
|
||||
await release.promise
|
||||
expect(service.require()).toBe(active)
|
||||
})
|
||||
let disposed = false
|
||||
const disposal = dispose().then(() => { disposed = true })
|
||||
await Promise.resolve()
|
||||
|
||||
expect(() => service.run(active, () => 1)).toThrow('agent execution service is disposed')
|
||||
expect(disposed).toBe(false)
|
||||
expect(ctx.get('agentExecution')).toBeUndefined()
|
||||
release.resolve(true)
|
||||
await pending
|
||||
await disposal
|
||||
expect(() => service.current()).toThrow('agent execution service is disposed')
|
||||
expect(() => service.require()).toThrow('agent execution service is disposed')
|
||||
})
|
||||
})
|
||||
21
packages/core/agent-execution/tsconfig.json
Normal file
21
packages/core/agent-execution/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../vendor/cosmokit"
|
||||
},
|
||||
{
|
||||
"path": "../../../vendor/cordis"
|
||||
},
|
||||
{
|
||||
"path": "../../core/agent"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -23,7 +23,7 @@ The config-driven `ctx.agentLoop.create()` path keeps its agent owned by the loo
|
||||
|
||||
### Injected services
|
||||
|
||||
`agents`, `sessions`, `llm`, `tools`, `systemPrompt` — all five interface services.
|
||||
`agents`, `agentExecution`, `sessions`, `llm`, `tools`, `systemPrompt` — all six interface services. The loop cannot activate without `agentExecution`; the default bundle loads its provider before the loop.
|
||||
|
||||
### Configuration (schemastery)
|
||||
|
||||
@@ -48,7 +48,9 @@ Configured agents start automatically. `cwd` applies only to fresh sessions; `re
|
||||
|
||||
### Loop lifecycle (`loop.ts`)
|
||||
|
||||
The driver owns one agent for its lifetime. It records turn, step, request, stream, and tool boundaries in the session log; live extension events coordinate policy around those durable facts. The [architecture turn flow](../../../docs/architecture.md#turn-flow) and generated [event catalog](../../../docs/cordis-catalog/events.md) are the authoritative sequence and signatures.
|
||||
The driver owns one agent for its lifetime and runs inside `ctx.agentExecution.run({ agent }, ...)`, so process-local asynchronous continuations can recover the initiating Agent. Creation, persistence load, and unpublished setup stay outside the child boundary; explicit Agent fields remain authoritative at service, worker, process, persistence, and wire boundaries. The [execution-context package](../agent-execution/README.md) owns propagation and detached-work rules.
|
||||
|
||||
The loop records turn, step, request, stream, and tool boundaries in the session log; live extension events coordinate policy around those durable facts. The [architecture turn flow](../../../docs/architecture.md#turn-flow) and generated [event catalog](../../../docs/cordis-catalog/events.md) are the authoritative sequence and signatures.
|
||||
|
||||
Plugin failure ends the current turn, not the loop. Cancellation clears pending work and aborts the current step without leaking to the next prompt. Terminal continuation stops remain authoritative through turn close and durability flush.
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "^0.0.1",
|
||||
"@deepseek-ai/dsh-agent-execution": "^0.0.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.0.1",
|
||||
"@deepseek-ai/dsh-scope": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session": "^0.0.1",
|
||||
@@ -35,6 +36,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-scope": "workspace:^",
|
||||
|
||||
@@ -327,7 +327,7 @@ export class ReactLoopAgent implements Agent {
|
||||
[startDriver](): void {
|
||||
if (this._status === 'disposed') return
|
||||
this.driverStarted = true
|
||||
this.done = runLoop(this.loopCtx, this, {
|
||||
this.done = this.loopCtx.agentExecution.run({ agent: this }, () => runLoop(this.loopCtx, this, {
|
||||
inbox: this.#inbox,
|
||||
setStatus: (status) => { this.setStatus(status) },
|
||||
setAbort: controller => void (this.currentAbort = controller),
|
||||
@@ -338,7 +338,7 @@ export class ReactLoopAgent implements Agent {
|
||||
clearCancel: () => { this.cancelRequested = false },
|
||||
// Pre-step cancellation re-parks without emitting a status transition.
|
||||
settleIdle: () => { this.settleIdleWaiters() },
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ import z from 'schemastery'
|
||||
import { createScope } from '@deepseek-ai/dsh-scope'
|
||||
import type { Scope } from '@deepseek-ai/dsh-scope'
|
||||
import { agentEvents } from '@deepseek-ai/dsh-agent'
|
||||
import type {} from '@deepseek-ai/dsh-agent-execution'
|
||||
import type {
|
||||
AgentFactory,
|
||||
AgentHandle,
|
||||
@@ -333,7 +334,7 @@ export interface Config {
|
||||
|
||||
/** Concrete ReactLoopAgent factory and driver service. */
|
||||
export class AgentLoop extends Service implements AgentFactory {
|
||||
static inject = ['agents', 'sessions', 'llm', 'tools', 'systemPrompt']
|
||||
static inject = ['agents', 'agentExecution', 'sessions', 'llm', 'tools', 'systemPrompt']
|
||||
|
||||
/** Runtime schema for declarative agents. */
|
||||
static Config = z.object({
|
||||
|
||||
374
packages/core/agent-loop/tests/agent-execution.spec.ts
Normal file
374
packages/core/agent-loop/tests/agent-execution.spec.ts
Normal file
@@ -0,0 +1,374 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context, FiberState, type Fiber } from 'cordis'
|
||||
import AgentRegistry, { AgentId, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import type { AgentExecutionService } from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import LlmService, { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import { MockAdapter, textResponse, toolCallResponse } from './mock-adapter.ts'
|
||||
|
||||
interface Harness {
|
||||
ctx: Context
|
||||
providerFiber: Fiber
|
||||
loopFiber: Fiber
|
||||
}
|
||||
|
||||
async function harness(adapter: LlmAdapter): Promise<Harness> {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmService)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const providerFiber = await ctx.plugin(AgentExecutionProvider)
|
||||
const loopFiber = await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
return { ctx, providerFiber, loopFiber }
|
||||
}
|
||||
|
||||
function waitForIdle(ctx: Context, agent: ReactLoopAgent | Agent): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const dispose = ctx.on('agent/status', (subject, status) => {
|
||||
if (subject === agent && status === 'idle') {
|
||||
dispose()
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function send(agent: Agent, text: string): void {
|
||||
agent.send([{ type: 'text', text }])
|
||||
}
|
||||
|
||||
/** Adapter that holds both drivers at the same awaited continuation. */
|
||||
class OverlapAdapter extends LlmAdapter {
|
||||
private readonly bothStarted = Promise.withResolvers<boolean>()
|
||||
private starts = 0
|
||||
readonly observations: { sessionId: SessionId | undefined; before: Agent; after: Agent }[] = []
|
||||
|
||||
constructor(private readonly ctx: Context) {
|
||||
super()
|
||||
}
|
||||
|
||||
async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
const before = this.ctx.agentExecution.require().agent
|
||||
this.starts += 1
|
||||
if (this.starts === 2) this.bothStarted.resolve(true)
|
||||
await this.bothStarted.promise
|
||||
await Promise.resolve()
|
||||
const after = this.ctx.agentExecution.require().agent
|
||||
this.observations.push({ sessionId: options.sessionId, before, after })
|
||||
yield* textResponse('done')
|
||||
}
|
||||
}
|
||||
|
||||
/** Test-only transport that materializes ambient identity at its request boundary. */
|
||||
class TestCapabilityTransport {
|
||||
readonly requests: { path: string; headers: Record<string, string> }[] = []
|
||||
|
||||
constructor(private readonly execution: AgentExecutionService) {}
|
||||
|
||||
async request(path: string): Promise<Record<string, string>> {
|
||||
await Promise.resolve()
|
||||
const headers = {
|
||||
'X-Harness-Session-Id': this.execution.require().agent.session.id,
|
||||
}
|
||||
this.requests.push({ path, headers })
|
||||
return headers
|
||||
}
|
||||
}
|
||||
|
||||
/** Adapter whose first call waits for cancellation and whose later calls complete. */
|
||||
class ReloadAdapter extends LlmAdapter {
|
||||
readonly firstStarted = Promise.withResolvers<boolean>()
|
||||
firstAgentDuringAbort: Agent | undefined
|
||||
laterAgent: Agent | undefined
|
||||
calls = 0
|
||||
execution: AgentExecutionService | undefined
|
||||
|
||||
async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
|
||||
const execution = this.execution
|
||||
if (execution === undefined) throw new Error('execution service missing')
|
||||
this.calls += 1
|
||||
if (this.calls === 1) {
|
||||
this.firstStarted.resolve(true)
|
||||
try {
|
||||
await new Promise<void>((_resolve, reject) => {
|
||||
const abort = (): void => { reject(new Error('aborted')) }
|
||||
if (options.signal?.aborted === true) abort()
|
||||
else options.signal?.addEventListener('abort', abort, { once: true })
|
||||
})
|
||||
} catch (error: unknown) {
|
||||
await Promise.resolve()
|
||||
this.firstAgentDuringAbort = execution.require().agent
|
||||
throw error
|
||||
}
|
||||
return
|
||||
}
|
||||
await Promise.resolve()
|
||||
this.laterAgent = execution.require().agent
|
||||
yield* textResponse('reloaded')
|
||||
}
|
||||
}
|
||||
|
||||
describe('AgentLoop execution context', () => {
|
||||
it('keeps overlapping driver continuations bound to their exact Agents', async () => {
|
||||
const ctx = new Context()
|
||||
const adapter = new OverlapAdapter(ctx)
|
||||
await ctx.plugin(LlmService)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
|
||||
const a = ctx.agentLoop.create(AgentId('a'), { model: 'mock' })
|
||||
const b = ctx.agentLoop.create(AgentId('b'), { model: 'mock' })
|
||||
const idleA = waitForIdle(ctx, a)
|
||||
const idleB = waitForIdle(ctx, b)
|
||||
send(a, 'a')
|
||||
send(b, 'b')
|
||||
await Promise.all([idleA, idleB])
|
||||
|
||||
expect(adapter.observations).toHaveLength(2)
|
||||
expect(adapter.observations).toEqual(expect.arrayContaining([
|
||||
{ sessionId: a.session.id, before: a, after: a },
|
||||
{ sessionId: b.session.id, before: b, after: b },
|
||||
]))
|
||||
expect(ctx.agentExecution.current()).toBeUndefined()
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('keeps child setup under the parent boundary, switches for the child driver, then restores the parent', async () => {
|
||||
const adapter = new MockAdapter([
|
||||
toolCallResponse('spawn', 'spawn-child', {}),
|
||||
toolCallResponse('observe', 'observe-child', {}),
|
||||
textResponse('child done'),
|
||||
textResponse('parent done'),
|
||||
])
|
||||
const { ctx } = await harness(adapter)
|
||||
let parentDuringSetup: Agent | undefined
|
||||
let explicitChild: Agent | undefined
|
||||
let childDuringDriver: Agent | undefined
|
||||
let parentAfterChild: Agent | undefined
|
||||
let child: Agent | undefined
|
||||
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'spawn-child',
|
||||
description: 'create one child agent',
|
||||
parameters: {},
|
||||
execute: async (_args, exec) => {
|
||||
if (exec.agent === undefined) throw new Error('parent agent missing')
|
||||
const handle = await exec.agent.ctx.agents.create({
|
||||
agentId: AgentId('child'),
|
||||
sessionId: SessionId('child-session'),
|
||||
agentOptions: { model: 'mock' },
|
||||
setup: (agentCtx) => {
|
||||
parentDuringSetup = ctx.agentExecution.require().agent
|
||||
explicitChild = agentCtx.agent
|
||||
agentCtx.tools.register(defineTool({
|
||||
name: 'observe-child',
|
||||
description: 'observe child execution identity',
|
||||
parameters: {},
|
||||
execute: async () => {
|
||||
await Promise.resolve()
|
||||
childDuringDriver = ctx.agentExecution.require().agent
|
||||
return [{ type: 'text', text: 'observed' }]
|
||||
},
|
||||
}))
|
||||
},
|
||||
})
|
||||
child = handle.agent
|
||||
send(handle.agent, 'run child')
|
||||
await handle.agent.whenIdle()
|
||||
parentAfterChild = ctx.agentExecution.require().agent
|
||||
await handle.dispose()
|
||||
return [{ type: 'text', text: 'child completed' }]
|
||||
},
|
||||
}))
|
||||
|
||||
const parentHandle = await ctx.agents.create({
|
||||
agentId: AgentId('parent'),
|
||||
sessionId: SessionId('parent-session'),
|
||||
agentOptions: { model: 'mock' },
|
||||
})
|
||||
const idle = waitForIdle(ctx, parentHandle.agent)
|
||||
send(parentHandle.agent, 'spawn')
|
||||
await idle
|
||||
|
||||
expect(parentDuringSetup).toBe(parentHandle.agent)
|
||||
expect(explicitChild).toBe(child)
|
||||
expect(childDuringDriver).toBe(child)
|
||||
expect(parentAfterChild).toBe(parentHandle.agent)
|
||||
expect(ctx.agentExecution.current()).toBeUndefined()
|
||||
await parentHandle.dispose()
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('keeps agentless direct tools ambient-free and builds trusted transport headers internally', async () => {
|
||||
const adapter = new MockAdapter([
|
||||
toolCallResponse('capability', 'capability-request', { path: '/v1/capability' }),
|
||||
textResponse('done'),
|
||||
])
|
||||
const { ctx } = await harness(adapter)
|
||||
const transport = new TestCapabilityTransport(ctx.agentExecution)
|
||||
let directAmbient: Agent | undefined
|
||||
let captured: Agent | undefined
|
||||
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'agentless-probe',
|
||||
description: 'observe an agentless call',
|
||||
parameters: {},
|
||||
execute: async () => {
|
||||
await Promise.resolve()
|
||||
directAmbient = ctx.agentExecution.current()?.agent
|
||||
return [{ type: 'text', text: 'ok' }]
|
||||
},
|
||||
}))
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'capability-request',
|
||||
description: 'call the test capability transport',
|
||||
parameters: { path: { type: 'string' } },
|
||||
execute: async (args) => {
|
||||
captured = ctx.agentExecution.require().agent
|
||||
const path = (args as { path: string }).path
|
||||
const headers = await transport.request(path)
|
||||
return [{ type: 'text', text: JSON.stringify(headers) }]
|
||||
},
|
||||
}))
|
||||
|
||||
const direct = await ctx.tools.execute({
|
||||
callId: CallId('direct'),
|
||||
name: 'agentless-probe',
|
||||
arguments: {},
|
||||
})
|
||||
expect(direct.isError).toBe(false)
|
||||
expect(directAmbient).toBeUndefined()
|
||||
|
||||
const handle = await ctx.agents.create({
|
||||
agentId: AgentId('transport'),
|
||||
sessionId: SessionId('transport-session'),
|
||||
agentOptions: { model: 'mock' },
|
||||
})
|
||||
const idle = waitForIdle(ctx, handle.agent)
|
||||
send(handle.agent, 'call transport')
|
||||
await idle
|
||||
|
||||
expect(transport.requests).toEqual([{
|
||||
path: '/v1/capability',
|
||||
headers: { 'X-Harness-Session-Id': 'transport-session' },
|
||||
}])
|
||||
const schema = adapter.requests[0]?.tools?.find(tool => tool.name === 'capability-request')
|
||||
expect(JSON.stringify(schema?.parameters)).not.toMatch(/session|harness/i)
|
||||
const call = handle.agent.session.events.find(event => event.type === 'tool/call')
|
||||
expect(call?.type === 'tool/call' ? call.data.arguments : undefined)
|
||||
.toBe(JSON.stringify({ path: '/v1/capability' }))
|
||||
expect(captured).toBe(handle.agent)
|
||||
|
||||
await handle.dispose()
|
||||
expect(captured?.status).toBe('disposed')
|
||||
expect(ctx.agentExecution.current()).toBeUndefined()
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('keeps AgentLoop inactive until the mandatory provider appears', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmService)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const loopFiber = ctx.plugin(AgentLoop, { agents: [] })
|
||||
await Promise.resolve()
|
||||
expect(loopFiber.state).toBe(FiberState.PENDING)
|
||||
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await loopFiber
|
||||
expect(loopFiber.state).toBe(FiberState.ACTIVE)
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('drains the old driver before disabling ALS during provider restart', async () => {
|
||||
const ctx = new Context()
|
||||
const adapter = new ReloadAdapter()
|
||||
const { providerFiber, loopFiber } = await (async (): Promise<Harness> => {
|
||||
await ctx.plugin(LlmService)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
const mountedProvider = await ctx.plugin(AgentExecutionProvider)
|
||||
const mountedLoop = await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
return { ctx, providerFiber: mountedProvider, loopFiber: mountedLoop }
|
||||
})()
|
||||
const oldService = ctx.agentExecution
|
||||
adapter.execution = oldService
|
||||
const oldHandle = await ctx.agents.create({
|
||||
agentId: AgentId('before-restart'),
|
||||
sessionId: SessionId('before-restart-session'),
|
||||
agentOptions: { model: 'mock' },
|
||||
})
|
||||
const oldAgent = oldHandle.agent
|
||||
send(oldAgent, 'block')
|
||||
await adapter.firstStarted.promise
|
||||
|
||||
await providerFiber.restart()
|
||||
await loopFiber.await()
|
||||
expect(adapter.firstAgentDuringAbort?.id).toBe(oldAgent.id)
|
||||
expect(adapter.firstAgentDuringAbort?.session).toBe(oldAgent.session)
|
||||
expect(oldAgent.status).toBe('disposed')
|
||||
expect(() => oldService.current()).toThrow('agent execution service is disposed')
|
||||
expect(ctx.agentExecution).not.toBe(oldService)
|
||||
adapter.execution = ctx.agentExecution
|
||||
|
||||
const newHandle = await ctx.agents.create({
|
||||
agentId: AgentId('after-restart'),
|
||||
sessionId: SessionId('after-restart-session'),
|
||||
agentOptions: { model: 'mock' },
|
||||
})
|
||||
const newAgent = newHandle.agent
|
||||
const idle = waitForIdle(ctx, newAgent)
|
||||
send(newAgent, 'continue')
|
||||
await idle
|
||||
expect(adapter.laterAgent?.id).toBe(newAgent.id)
|
||||
expect(adapter.laterAgent?.session).toBe(newAgent.session)
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('keeps ALS readable while root disposal drains sibling AgentLoop fibers', async () => {
|
||||
const ctx = new Context()
|
||||
const adapter = new ReloadAdapter()
|
||||
await ctx.plugin(LlmService)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
const service = ctx.agentExecution
|
||||
adapter.execution = service
|
||||
const handle = await ctx.agents.create({
|
||||
agentId: AgentId('root-dispose'),
|
||||
sessionId: SessionId('root-dispose-session'),
|
||||
agentOptions: { model: 'mock' },
|
||||
})
|
||||
const agent = handle.agent
|
||||
send(agent, 'block')
|
||||
await adapter.firstStarted.promise
|
||||
|
||||
await ctx.fiber.dispose()
|
||||
expect(adapter.firstAgentDuringAbort?.id).toBe(agent.id)
|
||||
expect(adapter.firstAgentDuringAbort?.session).toBe(agent.session)
|
||||
expect(agent.status).toBe('disposed')
|
||||
expect(() => service.current()).toThrow('agent execution service is disposed')
|
||||
})
|
||||
})
|
||||
@@ -6,6 +6,7 @@ import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { bindReactLoopAgentContext, prepareReactLoopAgent } from '../src/agent.ts'
|
||||
import { MockAdapter, textResponse } from './mock-adapter.ts'
|
||||
@@ -17,6 +18,7 @@ async function harness(adapter: MockAdapter) {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
return ctx
|
||||
@@ -51,6 +53,7 @@ function send(agent: ReactLoopAgent, text: string) {
|
||||
describe('ReactLoopAgent', () => {
|
||||
it('rejects access before context binding and a second driver for one session', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(SessionStore)
|
||||
const session = ctx.sessions.create(SessionId('exclusive-driver'))
|
||||
const prepared = prepareReactLoopAgent(ctx, AgentId('first-driver'), { model: 'mock' }, session)
|
||||
@@ -252,6 +255,7 @@ describe('ReactLoopAgent', () => {
|
||||
it('disposer is idempotent (double-stop)', async () => {
|
||||
// The internal start seam exposes one idle driver's disposer for repeated invocation.
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(SessionStore)
|
||||
const session = ctx.sessions.create(SessionId('test'))
|
||||
const prepared = prepareReactLoopAgent(ctx, AgentId('bare'), { model: 'mock' }, session)
|
||||
@@ -361,6 +365,7 @@ describe('ReactLoopAgent', () => {
|
||||
// Queue the internal waiter while running, then dispose the bare driver. Its disposed branch
|
||||
// must chain the loop's `done` promise rather than resolve before exit.
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(LlmService)
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SystemPrompt)
|
||||
|
||||
@@ -14,6 +14,7 @@ import SessionStore, { SessionId, TurnEndReason } from '@deepseek-ai/dsh-session
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { MockAdapter, textResponse } from './mock-adapter.ts'
|
||||
|
||||
@@ -24,6 +25,7 @@ async function harness(adapter: MockAdapter) {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
return ctx
|
||||
@@ -194,6 +196,7 @@ describe('Agent.cancel()', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
|
||||
@@ -319,6 +322,7 @@ describe('Agent.cancel()', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { MockAdapter, textResponse } from './mock-adapter.ts'
|
||||
|
||||
@@ -31,6 +32,7 @@ describe('config-driven session id', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
const loopFiber = await ctx.plugin(AgentLoop, {
|
||||
agents: [{ id: AgentId('main'), model: 'mock', resumeSessionId: SessionId('deferred') }],
|
||||
})
|
||||
@@ -53,6 +55,7 @@ describe('config-driven session id', () => {
|
||||
await ctx1.plugin(SystemPrompt)
|
||||
await ctx1.plugin(ToolRegistry)
|
||||
await ctx1.plugin(AgentRegistry)
|
||||
await ctx1.plugin(AgentExecutionProvider)
|
||||
await ctx1.plugin(AgentLoop, { agents: [{ id: AgentId('cfg'), model: 'mock' }] })
|
||||
await ctx1.plugin(SessionPersistenceJsonl, { root })
|
||||
ctx1.llm.registerAdapter(['mock'], new MockAdapter([textResponse('cfg')]))
|
||||
@@ -70,6 +73,7 @@ describe('config-driven session id', () => {
|
||||
await ctx2.plugin(SystemPrompt)
|
||||
await ctx2.plugin(ToolRegistry)
|
||||
await ctx2.plugin(AgentRegistry)
|
||||
await ctx2.plugin(AgentExecutionProvider)
|
||||
await ctx2.plugin(AgentLoop, { agents: [{ id: AgentId('cfg'), model: 'mock' }] })
|
||||
await ctx2.plugin(SessionPersistenceJsonl, { root })
|
||||
ctx2.llm.registerAdapter(['mock'], new MockAdapter([textResponse('cfg2')]))
|
||||
@@ -93,6 +97,7 @@ describe('config-driven session id', () => {
|
||||
await ctx1.plugin(SystemPrompt)
|
||||
await ctx1.plugin(ToolRegistry)
|
||||
await ctx1.plugin(AgentRegistry)
|
||||
await ctx1.plugin(AgentExecutionProvider)
|
||||
await ctx1.plugin(AgentLoop, { agents: [] })
|
||||
await ctx1.plugin(SessionPersistenceJsonl, { root })
|
||||
ctx1.llm.registerAdapter(['mock'], new MockAdapter([textResponse('first')]))
|
||||
@@ -109,6 +114,7 @@ describe('config-driven session id', () => {
|
||||
await ctx2.plugin(SystemPrompt)
|
||||
await ctx2.plugin(ToolRegistry)
|
||||
await ctx2.plugin(AgentRegistry)
|
||||
await ctx2.plugin(AgentExecutionProvider)
|
||||
await ctx2.plugin(AgentLoop, { agents: [{ id: AgentId('main'), model: 'mock', resumeSessionId: SessionId('sticky-1') }] })
|
||||
await ctx2.plugin(SessionPersistenceJsonl, { root })
|
||||
ctx2.llm.registerAdapter(['mock'], new MockAdapter([textResponse('second')]))
|
||||
@@ -137,6 +143,7 @@ describe('config-driven session id', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [{ id: AgentId('main'), model: 'mock', resumeSessionId: SessionId('does-not-exist') }] })
|
||||
const warn = vi.spyOn((ctx.agentLoop as unknown as { ctx: { logger: { warn: (...a: unknown[]) => void } } }).ctx.logger, 'warn')
|
||||
.mockImplementation(() => undefined)
|
||||
|
||||
@@ -5,6 +5,7 @@ import SessionStore, { Session, SessionEvent, SessionId, TurnEndReason } from '@
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId, type ContinuationDecision } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { prepareReactLoopAgent } from '../src/agent.ts'
|
||||
import * as Invariants from '@deepseek-ai/dsh-invariants'
|
||||
@@ -19,6 +20,7 @@ async function harness(adapter: MockAdapter) {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
return ctx
|
||||
@@ -524,6 +526,7 @@ describe('turn numbering continues across seeded sessions', () => {
|
||||
await ctx2.plugin(SystemPrompt)
|
||||
await ctx2.plugin(ToolRegistry)
|
||||
await ctx2.plugin(AgentRegistry)
|
||||
await ctx2.plugin(AgentExecutionProvider)
|
||||
await ctx2.plugin(AgentLoop, { agents: [] })
|
||||
ctx2.llm.registerAdapter(['mock'], second)
|
||||
|
||||
@@ -664,6 +667,7 @@ describe('turn and step boundary recovery', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(Invariants)
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
@@ -1114,6 +1118,7 @@ describe('disposal and cancellation during pre-step assembly', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(Invariants)
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
@@ -1165,6 +1170,7 @@ describe('disposal and cancellation during pre-step assembly', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(Invariants)
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
@@ -1220,6 +1226,7 @@ describe('disposal and cancellation during pre-step assembly', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(Invariants)
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
@@ -1271,6 +1278,7 @@ describe('disposal and cancellation during pre-step assembly', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(Invariants)
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
@@ -1320,6 +1328,7 @@ describe('disposal and cancellation during pre-step assembly', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(Invariants)
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { MockAdapter, textResponse, toolCallResponse } from './mock-adapter.ts'
|
||||
|
||||
@@ -16,6 +17,7 @@ async function harness(adapter: MockAdapter) {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
return ctx
|
||||
|
||||
@@ -10,6 +10,7 @@ import AgentRegistry, {
|
||||
type PromptDecision,
|
||||
type SessionStartSource,
|
||||
} from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { MockAdapter, textResponse, toolCallResponse } from './mock-adapter.ts'
|
||||
|
||||
@@ -29,6 +30,7 @@ async function harness(adapter: MockAdapter) {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
return ctx
|
||||
|
||||
@@ -5,6 +5,7 @@ import SessionStore, { SessionId, TurnEndReason } from '@deepseek-ai/dsh-session
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { MockAdapter, maxTokensResponse, textResponse, toolCallResponse } from './mock-adapter.ts'
|
||||
|
||||
@@ -15,6 +16,7 @@ async function harness(adapter: MockAdapter, persona = '') {
|
||||
await ctx.plugin(SystemPrompt, { persona })
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
return ctx
|
||||
@@ -924,6 +926,7 @@ describe('agent loop', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, {
|
||||
agents: [{ id: AgentId('config-agent'), model: 'mock' }],
|
||||
})
|
||||
@@ -947,6 +950,7 @@ describe('agent loop', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, {
|
||||
agents: [{ id: AgentId('config-agent'), model: 'mock', cwd: '/work/project' }],
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import fc from 'fast-check'
|
||||
|
||||
@@ -36,6 +37,7 @@ async function harness() {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], new EchoAdapter())
|
||||
return ctx
|
||||
|
||||
@@ -5,6 +5,7 @@ import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
|
||||
|
||||
@@ -42,6 +43,7 @@ async function loopHarness(): Promise<Context> {
|
||||
await created.plugin(SystemPrompt, { persona: SYSTEM })
|
||||
await created.plugin(ToolRegistry)
|
||||
await created.plugin(AgentRegistry)
|
||||
await created.plugin(AgentExecutionProvider)
|
||||
await created.plugin(AgentLoop, { agents: [] })
|
||||
await created.plugin(LlmDeepSeek, { models: ['deepseek-v4-flash'] })
|
||||
created.tools.register(defineTool({
|
||||
|
||||
@@ -14,6 +14,7 @@ import SessionStore, { Session, SessionId, foldRequestHeader } from '@deepseek-a
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { MockAdapter, textResponse, toolCallResponse } from './mock-adapter.ts'
|
||||
|
||||
@@ -24,6 +25,7 @@ async function harness(adapter: MockAdapter, persona = 'stable base') {
|
||||
await ctx.plugin(SystemPrompt, { persona })
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
return ctx
|
||||
|
||||
@@ -10,6 +10,7 @@ import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { MockAdapter, textResponse } from './mock-adapter.ts'
|
||||
|
||||
@@ -29,6 +30,7 @@ async function mountPersistentHarness(root: string, adapter: MockAdapter): Promi
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SessionPersistenceJsonl, { root })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
@@ -138,6 +140,7 @@ describe('the session-persistence RFC: AgentLoop factory create/resume', () => {
|
||||
await ctx2.plugin(SystemPrompt)
|
||||
await ctx2.plugin(ToolRegistry)
|
||||
await ctx2.plugin(AgentRegistry)
|
||||
await ctx2.plugin(AgentExecutionProvider)
|
||||
await ctx2.plugin(AgentLoop, { agents: [] })
|
||||
await ctx2.plugin(SessionPersistenceJsonl, { root })
|
||||
ctx2.llm.registerAdapter(['mock'], adapter2)
|
||||
@@ -166,6 +169,7 @@ describe('the session-persistence RFC: AgentLoop factory create/resume', () => {
|
||||
await ctx2.plugin(SystemPrompt)
|
||||
await ctx2.plugin(ToolRegistry)
|
||||
await ctx2.plugin(AgentRegistry)
|
||||
await ctx2.plugin(AgentExecutionProvider)
|
||||
await ctx2.plugin(AgentLoop, { agents: [] })
|
||||
await ctx2.plugin(SessionPersistenceJsonl, { root })
|
||||
ctx2.llm.registerAdapter(['mock'], adapter2)
|
||||
@@ -388,6 +392,7 @@ describe('the session-persistence RFC: AgentLoop factory create/resume', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
const loopFiber = await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SessionPersistenceJsonl, { root })
|
||||
ctx.llm.registerAdapter(['mock'], new MockAdapter([textResponse('next')]))
|
||||
@@ -449,6 +454,7 @@ describe('the session-persistence RFC: AgentLoop factory create/resume', () => {
|
||||
await ctx2.plugin(SystemPrompt)
|
||||
await ctx2.plugin(ToolRegistry)
|
||||
await ctx2.plugin(AgentRegistry)
|
||||
await ctx2.plugin(AgentExecutionProvider)
|
||||
await ctx2.plugin(AgentLoop, { agents: [] })
|
||||
await ctx2.plugin(SessionPersistenceJsonl, { root })
|
||||
ctx2.llm.registerAdapter(['mock'], adapter2)
|
||||
@@ -502,6 +508,7 @@ describe('the session-persistence RFC: AgentLoop factory create/resume', () => {
|
||||
await ctx2.plugin(SystemPrompt)
|
||||
await ctx2.plugin(ToolRegistry)
|
||||
await ctx2.plugin(AgentRegistry)
|
||||
await ctx2.plugin(AgentExecutionProvider)
|
||||
await ctx2.plugin(AgentLoop, { agents: [] })
|
||||
await ctx2.plugin(SessionPersistenceJsonl, { root })
|
||||
ctx2.llm.registerAdapter(['mock'], adapter2)
|
||||
@@ -531,6 +538,7 @@ describe('the session-persistence RFC: AgentLoop factory create/resume', () => {
|
||||
await ctx2.plugin(SystemPrompt)
|
||||
await ctx2.plugin(ToolRegistry)
|
||||
await ctx2.plugin(AgentRegistry)
|
||||
await ctx2.plugin(AgentExecutionProvider)
|
||||
await ctx2.plugin(AgentLoop, { agents: [] })
|
||||
await ctx2.plugin(SessionPersistenceJsonl, { root })
|
||||
ctx2.llm.registerAdapter(['mock'], adapter2)
|
||||
@@ -561,6 +569,7 @@ describe('the session-persistence RFC: AgentLoop factory create/resume', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
await expect(ctx.agents.resume({ agentId: AgentId('m'), resumeSessionId: SessionId('nope') }))
|
||||
|
||||
@@ -7,6 +7,7 @@ import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId, agentEvents, assembleContextFor } from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { scopeOf } from '@deepseek-ai/dsh-scope'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
|
||||
import { MockAdapter, textResponse } from './mock-adapter.ts'
|
||||
@@ -18,6 +19,7 @@ async function harnessWithLoop(adapter: MockAdapter = new MockAdapter([textRespo
|
||||
await ctx.plugin(SystemPrompt, { persona: 'You are the deployment.' })
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
const loopFiber = await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
return { ctx, loopFiber }
|
||||
|
||||
@@ -14,6 +14,7 @@ import SystemPrompt, { TOOL_ORDER_REST } from '@deepseek-ai/dsh-system-prompt'
|
||||
import type { Config as SystemPromptConfig } from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { MockAdapter, textResponse } from './mock-adapter.ts'
|
||||
|
||||
@@ -24,6 +25,7 @@ async function harness(adapter: MockAdapter, toolOrder?: SystemPromptConfig['too
|
||||
await ctx.plugin(SystemPrompt, { persona: 'stable base', ...toolOrder !== undefined ? { toolOrder } : {} })
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
return ctx
|
||||
|
||||
@@ -5,6 +5,7 @@ import SessionStore, { type TurnEndReason } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId, type ContinuationStop } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as Invariants from '@deepseek-ai/dsh-invariants'
|
||||
import { MockAdapter, textResponse, toolCallResponse } from './mock-adapter.ts'
|
||||
@@ -17,6 +18,7 @@ async function harness(adapter: MockAdapter): Promise<Context> {
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(Invariants)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
return ctx
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
{
|
||||
"path": "../../core/agent"
|
||||
},
|
||||
{
|
||||
"path": "../../core/agent-execution"
|
||||
},
|
||||
{
|
||||
"path": "../../core/scope"
|
||||
}
|
||||
|
||||
@@ -160,6 +160,26 @@ export class FixService {
|
||||
expect(services[0]?.methods).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('extracts an interface service as an abstract seam', () => {
|
||||
const services = collectServices(makeService(`/** Fixture service interface. */
|
||||
export interface FixService {
|
||||
/**
|
||||
* Do the thing.
|
||||
* @param id - which thing to do.
|
||||
* @returns the outcome of doing it.
|
||||
*/
|
||||
run(id: string): string
|
||||
}`))
|
||||
expect(services).toHaveLength(1)
|
||||
expect(services[0]).toMatchObject({
|
||||
key: 'fix',
|
||||
type: 'FixService',
|
||||
abstract: true,
|
||||
doc: 'Fixture service interface.',
|
||||
})
|
||||
expect(services[0]?.methods).toEqual(['run(id: string): string'])
|
||||
})
|
||||
|
||||
it('hard-errors on a public method with no JSDoc at all', () => {
|
||||
expect(() => collectServices(makeService(
|
||||
'/** Fixture service. */\nexport class FixService {\n run(id: string): string { return id }\n}',
|
||||
|
||||
@@ -4,7 +4,7 @@ Pre-composed plugin bundles a thin leaf `cordis.yml` loads instead of assembling
|
||||
|
||||
| Package | npm name | Role |
|
||||
|---|---|---|
|
||||
| `agent-spine-demo/` | `@deepseek-ai/dsh-agent-spine-demo` | The executor-less/UI-less agent spine as one bundle plugin (`timer` + `llm` + sessions + system-prompt + tools + skills + agents + invariants + `tool-bash` + `tool-skill` + `agent-loop`) |
|
||||
| `agent-spine-demo/` | `@deepseek-ai/dsh-agent-spine-demo` | The executor-less/UI-less agent spine as one bundle plugin (`timer` + `llm` + sessions + system-prompt + tools + skills + agents + agent-execution + invariants + `tool-bash` + `tool-skill` + `agent-loop`) |
|
||||
| `stdio-demo/` | `@deepseek-ai/dsh-stdio-demo` | Terminal stdio chat app: the spine + console logger + readline UI + a pre-created `main` agent, with a boot `bin` |
|
||||
| `acp-demo/` | `@deepseek-ai/dsh-acp-demo` | ACP server app: the spine + JSONL persistence + the [`acp`](../ui/acp/README.md) bridge (no stdout logger), with a boot `bin` |
|
||||
| `jsonrpc-demo/` | `@deepseek-ai/dsh-jsonrpc-demo` | Bin-only runtime that boots an external `cordis.yml` for the stdio JSON-RPC SDK client |
|
||||
|
||||
@@ -17,6 +17,7 @@ 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-agent-execution process-local ambient Agent execution context
|
||||
@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
|
||||
|
||||
@@ -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 + tasks + invariants + tool-bash + tool-skill + tool-tasks + agent-loop)",
|
||||
"description": "The default executor-less/UI-less agent spine as one Cordis bundle plugin (timer + llm + sessions + system-prompt + tools + skills + agents + agent-execution + tasks + invariants + tool-bash + tool-skill + tool-tasks + agent-loop)",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -24,6 +24,7 @@
|
||||
"peerDependencies": {
|
||||
"@cordisjs/plugin-timer": "^1.1.2",
|
||||
"@deepseek-ai/dsh-agent": "^0.0.1",
|
||||
"@deepseek-ai/dsh-agent-execution": "^0.0.1",
|
||||
"@deepseek-ai/dsh-agent-loop": "^0.0.1",
|
||||
"@deepseek-ai/dsh-invariants": "^0.0.1",
|
||||
"@deepseek-ai/dsh-llm": "^0.0.1",
|
||||
@@ -41,6 +42,7 @@
|
||||
"devDependencies": {
|
||||
"@cordisjs/plugin-timer": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
|
||||
@@ -18,6 +18,7 @@ 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 AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import TaskService from '@deepseek-ai/dsh-tasks'
|
||||
import * as invariants from '@deepseek-ai/dsh-invariants'
|
||||
import * as toolBash from '@deepseek-ai/dsh-tool-bash'
|
||||
@@ -116,6 +117,7 @@ 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(AgentExecutionProvider)
|
||||
ctx.plugin(TaskService)
|
||||
ctx.plugin(invariants)
|
||||
ctx.plugin(toolBash, config.toolBash ?? {})
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
{
|
||||
"path": "../../core/agent"
|
||||
},
|
||||
{
|
||||
"path": "../../core/agent-execution"
|
||||
},
|
||||
{
|
||||
"path": "../../core/agent-loop"
|
||||
},
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-fs-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-fs": "workspace:^",
|
||||
|
||||
@@ -4,6 +4,7 @@ import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
|
||||
import * as FsPolicy from '@deepseek-ai/dsh-fs-policy'
|
||||
@@ -22,6 +23,7 @@ export async function fsHarness(fsCwd: string, persona = ''): Promise<Context> {
|
||||
await ctx.plugin(SystemPrompt, { persona })
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LlmDeepSeek, { models: ['deepseek-v4-flash'] })
|
||||
await ctx.plugin(LocalFileSystem, { cwd: fsCwd })
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
|
||||
@@ -5,6 +5,7 @@ import SessionStore, { type SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as RepeatToolGuard from '@deepseek-ai/dsh-repeat-tool-guard'
|
||||
import type { Config } from '@deepseek-ai/dsh-repeat-tool-guard'
|
||||
@@ -26,6 +27,7 @@ async function harness(config: Config = {}): Promise<Context> {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(RepeatToolGuard, config)
|
||||
ctx.tools.register(defineTool({ name: 'probe', description: 'p', parameters: {}, async execute() { return [{ type: 'text', text: 'ok' }] } }))
|
||||
@@ -368,6 +370,7 @@ describe('config validation fails loud', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-bash": "workspace:^",
|
||||
"@deepseek-ai/dsh-bash-local": "workspace:^",
|
||||
|
||||
@@ -9,6 +9,7 @@ import SessionStore, { type SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
|
||||
import * as HooksClaude from '@deepseek-ai/dsh-hooks-claude'
|
||||
@@ -47,6 +48,7 @@ async function harnessWithFiber(configDir: string, adapter: MockAdapter): Promis
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
|
||||
const hooks = await ctx.plugin(HooksClaude, { configPath: join(configDir, 'hooks.json') })
|
||||
@@ -342,6 +344,7 @@ describe('hooks-claude bridge — load resilience', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
|
||||
await ctx.plugin(HooksClaude, { configPath: '/nonexistent/hooks.json' })
|
||||
@@ -364,6 +367,7 @@ describe('hooks-claude bridge — load resilience', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
|
||||
const fiber = await ctx.plugin(HooksClaude, { configPath: join(dir, 'hooks.json') })
|
||||
|
||||
@@ -8,6 +8,7 @@ import SessionStore, { type SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
|
||||
import * as HooksClaude from '@deepseek-ai/dsh-hooks-claude'
|
||||
@@ -35,6 +36,7 @@ async function harness(configPath: string, adapter: MockAdapter, opts: HarnessOp
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
|
||||
await ctx.plugin(HooksClaude, { configPath, ...opts })
|
||||
@@ -334,6 +336,7 @@ describe('hooks-claude coverage — schema-bypass apply + unspawnable hook', ()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
|
||||
// Direct apply with only configPath — bypasses schemastery's defaults, so
|
||||
@@ -599,6 +602,7 @@ describe('hooks-claude coverage — hook runs in the session cwd, not the server
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
// Executor default cwd = serverDir (deliberately NOT the session cwd).
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000, cwd: serverDir })
|
||||
@@ -632,6 +636,7 @@ describe('hooks-claude coverage — hook runs in the session cwd, not the server
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
// Executor default cwd = serverDir (deliberately NOT the child session cwd).
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000, cwd: serverDir })
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-bash": "workspace:^",
|
||||
"@deepseek-ai/dsh-bash-local": "workspace:^",
|
||||
|
||||
@@ -9,6 +9,7 @@ import SessionStore, { type SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
|
||||
import * as HooksCodex from '@deepseek-ai/dsh-hooks-codex'
|
||||
@@ -45,6 +46,7 @@ async function harness(dir: string, adapter: MockAdapter): Promise<Context> {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
|
||||
await ctx.plugin(HooksCodex, { configPath: join(dir, 'hooks.json'), model: 'test-model' })
|
||||
@@ -146,6 +148,7 @@ describe('hooks-codex bridge', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
|
||||
const fiber = await ctx.plugin(HooksCodex, { configPath: join(dir, 'hooks.json'), model: 'm' })
|
||||
@@ -172,6 +175,7 @@ describe('hooks-codex bridge', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
|
||||
const fiber = await ctx.plugin(HooksCodex, { configPath: join(dir, 'hooks.json'), model: 'm' })
|
||||
|
||||
@@ -8,6 +8,7 @@ import SessionStore, { type SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
|
||||
import * as HooksCodex from '@deepseek-ai/dsh-hooks-codex'
|
||||
@@ -26,6 +27,7 @@ function hooks(d: string, h: unknown): string {
|
||||
async function harness(configPath: string, adapter: MockAdapter, opts: { stderrSummaryMaxChars?: number } = {}): Promise<Context> {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmService); await ctx.plugin(SessionStore); await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(ToolRegistry); await ctx.plugin(AgentRegistry); await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
|
||||
await ctx.plugin(HooksCodex, { configPath, model: 'm', ...opts })
|
||||
@@ -239,6 +241,7 @@ describe('hooks-codex coverage — decision mapping paths', () => {
|
||||
const adapter = new MockAdapter([textResponse('ok')])
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmService); await ctx.plugin(SessionStore); await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(ToolRegistry); await ctx.plugin(AgentRegistry); await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
|
||||
ctx.logger.warn = warn as never
|
||||
@@ -544,6 +547,7 @@ describe('hooks-codex coverage — decision mapping paths', () => {
|
||||
const adapter = new MockAdapter([toolCallResponse('c1', 'Bash', { command: 'x' }), textResponse('done')])
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmService); await ctx.plugin(SessionStore); await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(ToolRegistry); await ctx.plugin(AgentRegistry); await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000, cwd: serverDir })
|
||||
await ctx.plugin(HooksCodex, { configPath: join(serverDir, 'hooks.json'), model: 'm' })
|
||||
|
||||
@@ -36,6 +36,10 @@ class SpineOption extends FeatureOption {
|
||||
}, ['persona'], config => requiredString(config, 'persona')),
|
||||
...npmCordisConfigEntry(ID, { id: 'tools', name: '@deepseek-ai/dsh-tools' }, []),
|
||||
...npmCordisConfigEntry(ID, { id: 'agent', name: '@deepseek-ai/dsh-agent' }),
|
||||
...npmCordisConfigEntry(ID, {
|
||||
id: 'agent-execution',
|
||||
name: '@deepseek-ai/dsh-agent-execution',
|
||||
}),
|
||||
...npmCordisConfigEntry(ID, { id: 'invariants', name: '@deepseek-ai/dsh-invariants' }),
|
||||
...npmCordisConfigEntry(ID, {
|
||||
id: 'agent-loop',
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
|
||||
@@ -5,6 +5,7 @@ import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as Invariants from '@deepseek-ai/dsh-invariants'
|
||||
import SubagentService, { type SubagentStartRequest } from '@deepseek-ai/dsh-subagent'
|
||||
@@ -32,6 +33,7 @@ async function setup(script: Script) {
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(Invariants)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SubagentService)
|
||||
await ctx.plugin(Spawn, { providerName: 'spawn' })
|
||||
|
||||
@@ -6,6 +6,7 @@ import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as Invariants from '@deepseek-ai/dsh-invariants'
|
||||
import SubagentService, { type SubagentStartRequest } from '@deepseek-ai/dsh-subagent'
|
||||
@@ -39,6 +40,7 @@ async function setup(script: Script) {
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(Invariants)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SubagentService)
|
||||
await ctx.plugin(fork, { providerName: 'fork' })
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
|
||||
@@ -6,6 +6,7 @@ import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import type { ContinuationDecision } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as Invariants from '@deepseek-ai/dsh-invariants'
|
||||
import SubagentService, { type SubagentStartRequest } from '@deepseek-ai/dsh-subagent'
|
||||
@@ -56,6 +57,7 @@ async function setup(script: Script, options: SetupOptions = {}) {
|
||||
}
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(Invariants)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SubagentService)
|
||||
const disposeProvider = ctx.subagents.registerProvider({
|
||||
|
||||
@@ -5,6 +5,7 @@ import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as Invariants from '@deepseek-ai/dsh-invariants'
|
||||
import SubagentService from '@deepseek-ai/dsh-subagent'
|
||||
@@ -21,6 +22,7 @@ async function setup(script: Script) {
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(Invariants)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SubagentService)
|
||||
ctx.llm.registerAdapter(['mock'], new MockAdapter(script))
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-bash-local": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
|
||||
@@ -4,6 +4,7 @@ import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
|
||||
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
|
||||
@@ -30,6 +31,7 @@ export async function spawnHarness(workdir: string): Promise<Context> {
|
||||
await ctx.plugin(SystemPrompt, { persona: 'You are a coding agent. Report only when the requested work is done.' })
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(LlmDeepSeek, { models: ['deepseek-v4-flash'] })
|
||||
await ctx.plugin(LocalBashExecutor, { cwd: workdir, timeoutMs: 30_000 })
|
||||
|
||||
@@ -7,6 +7,7 @@ import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as Invariants from '@deepseek-ai/dsh-invariants'
|
||||
import SubagentService, { type SubagentStartRequest } from '@deepseek-ai/dsh-subagent'
|
||||
@@ -32,6 +33,7 @@ async function setup(script: Script) {
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(Invariants)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SubagentService)
|
||||
await ctx.plugin(spawn, { providerName: 'spawn' })
|
||||
@@ -309,6 +311,7 @@ describe('dsh-subagent-spawn', () => {
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(Invariants)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SubagentService)
|
||||
const fiber = await ctx.plugin(spawn, { providerName: 'spawn' })
|
||||
@@ -341,6 +344,7 @@ describe('dsh-subagent-spawn', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SubagentService)
|
||||
const fiber = await ctx.plugin(spawn, { providerName: 'spawn' })
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as ToolTodo from '@deepseek-ai/dsh-tool-todo'
|
||||
import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
|
||||
@@ -23,6 +24,7 @@ async function harness(adapter: MockAdapter): Promise<Context> {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(ToolTodo)
|
||||
ctx.llm.registerAdapter(['mock'], adapter)
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-bash": "workspace:^",
|
||||
"@deepseek-ai/dsh-bash-local": "workspace:^",
|
||||
|
||||
@@ -11,6 +11,7 @@ import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
|
||||
@@ -193,6 +194,7 @@ export async function makeBridgeHarness(options: {
|
||||
await ctx.plugin(SystemPrompt, { persona: options.persona ?? '' })
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SessionPersistenceJsonl, { root: options.storageDir })
|
||||
await ctx.plugin(UserInteractionService)
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-execution": "workspace:^",
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:^",
|
||||
"@deepseek-ai/dsh-brand": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
|
||||
@@ -5,6 +5,7 @@ import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as Invariants from '@deepseek-ai/dsh-invariants'
|
||||
import SubagentService from '@deepseek-ai/dsh-subagent'
|
||||
@@ -32,6 +33,7 @@ async function setup(script: Script) {
|
||||
await ctx.plugin(ToolRegistry)
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(Invariants)
|
||||
await ctx.plugin(AgentExecutionProvider)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SubagentService)
|
||||
await ctx.plugin(spawn, { providerName: 'spawn' })
|
||||
|
||||
@@ -5,6 +5,7 @@ import SessionStore from '@deepseek-ai/dsh-session'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
|
||||
import AgentExecutionProvider from '@deepseek-ai/dsh-agent-execution'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
|
||||
import SubagentService from '@deepseek-ai/dsh-subagent'
|
||||
@@ -34,6 +35,7 @@ async function harness(): Promise<Context> {
|
||||
await built.plugin(SystemPrompt)
|
||||
await built.plugin(ToolRegistry)
|
||||
await built.plugin(AgentRegistry)
|
||||
await built.plugin(AgentExecutionProvider)
|
||||
await built.plugin(AgentLoop, { agents: [] })
|
||||
await built.plugin(LlmDeepSeek, { models: ['deepseek-v4-flash'] })
|
||||
await built.plugin(SubagentService)
|
||||
|
||||
Reference in New Issue
Block a user