diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 91fe7c6497..e346e0114d 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -1279,6 +1279,7 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them. - `@deepseek-ai/create-sdk` ([`packages/sdk/create-sdk/src/index.ts`](../packages/sdk/create-sdk/src/index.ts)) - `@deepseek-ai/dsh-acp-snapshot` ([`packages/support/acp-snapshot/src/index.ts`](../packages/support/acp-snapshot/src/index.ts)) +- `@deepseek-ai/dsh-agent-loop-testkit` ([`packages/support/agent-loop-testkit/src/index.ts`](../packages/support/agent-loop-testkit/src/index.ts)) - `@deepseek-ai/dsh-app-boot` ([`packages/ui/app-boot/src/index.ts`](../packages/ui/app-boot/src/index.ts)) - `@deepseek-ai/dsh-brand` ([`packages/util/brand/src/index.ts`](../packages/util/brand/src/index.ts)) - `@deepseek-ai/dsh-helper` ([`packages/sdk/helper/src/index.ts`](../packages/sdk/helper/src/index.ts)) diff --git a/docs/module-graph.md b/docs/module-graph.md index 82763bdc55..df4e680ee0 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -86,6 +86,7 @@ flowchart TD end subgraph group_support["packages/support"] pkg_acp_snapshot["acp-snapshot"] + pkg_agent_loop_testkit["agent-loop-testkit"] pkg_invariants["invariants"] pkg_llm_replay["llm-replay"] pkg_loader_smoke["loader-smoke"] @@ -274,6 +275,11 @@ flowchart TD pkg_hooks_codex --> pkg_llm pkg_hooks_codex --> pkg_session pkg_hooks_codex --> pkg_tools + pkg_agent_loop_testkit --> pkg_agent + pkg_agent_loop_testkit --> pkg_llm + pkg_agent_loop_testkit --> pkg_session + pkg_agent_loop_testkit --> pkg_system_prompt + pkg_agent_loop_testkit --> pkg_tools pkg_acp --> pkg_agent pkg_acp --> pkg_bash pkg_acp --> pkg_llm @@ -435,6 +441,7 @@ flowchart TD | [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | | [`tool-cordis`](../packages/cordis/tool-cordis) | `cordis` | [`scope`](../packages/core/scope), [`tools`](../packages/core/tools) | | [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | +| [`agent-loop-testkit`](../packages/support/agent-loop-testkit) | `support` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`acp`](../packages/ui/acp) | `ui` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`permission`](../packages/ui/permission), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval), [`user-interaction`](../packages/ui/user-interaction) | | [`tool-ask-user`](../packages/ui/tool-ask-user) | `ui` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) | | [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools) | diff --git a/examples/coding-agent/tests/harness.ts b/examples/coding-agent/tests/harness.ts index dd0bc42a1b..22c8c3f662 100644 --- a/examples/coding-agent/tests/harness.ts +++ b/examples/coding-agent/tests/harness.ts @@ -1,11 +1,7 @@ import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' -import SessionStore from '@deepseek-ai/dsh-session' 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 from '@deepseek-ai/dsh-agent' import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' import * as ToolBash from '@deepseek-ai/dsh-tool-bash' import * as ToolTodo from '@deepseek-ai/dsh-tool-todo' @@ -50,11 +46,9 @@ export interface CodingHarnessOptions { export async function codingHarness(workdir: string, options: CodingHarnessOptions = {}): Promise { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt, { persona: options.persona ?? '' }) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx, { + systemPrompt: { persona: options.persona ?? '' }, + }) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LlmDeepSeek, { models: ['deepseek-v4-flash'] }) await ctx.plugin(LocalBashExecutor, { cwd: workdir, timeoutMs: 30_000 }) diff --git a/examples/cordis-agent/tests/harness.ts b/examples/cordis-agent/tests/harness.ts index 78e5b0bb93..260a68bfbb 100644 --- a/examples/cordis-agent/tests/harness.ts +++ b/examples/cordis-agent/tests/harness.ts @@ -1,10 +1,6 @@ import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' -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 AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek' import * as ToolCordis from '@deepseek-ai/dsh-tool-cordis' @@ -23,11 +19,9 @@ const PERSONA = 'You are cordis-agent, a self-referential harness demo. ' export async function cordisHarness(): Promise { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt, { persona: PERSONA }) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx, { + systemPrompt: { persona: PERSONA }, + }) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LlmDeepSeek, { models: ['deepseek-v4-flash'] }) await ctx.plugin(ToolCordis) diff --git a/packages/README.md b/packages/README.md index 8f1bf3dde2..a3e0b1ac0a 100644 --- a/packages/README.md +++ b/packages/README.md @@ -31,7 +31,7 @@ Packages live at `packages///`; groups are containers, while names r | [`sdk/`](sdk/README.md) | Project SDK tooling | Product — stable surface | | [`ui/`](ui/README.md) | Editor/client integration surfaces: ACP bridge, JSON-RPC SDK server, user-approval/user-interaction seams, ask-user tool | Product — stable surface | | [`examples/`](examples/README.md) | Demo bundles (agent-spine + stdio/ACP/JSON-RPC bins) the leaves load | Support — example infra | -| [`support/`](support/README.md) | Support infrastructure (invariants, replay, Loader smokes) | Support — lower compatibility expectations | +| [`support/`](support/README.md) | Support infrastructure (testkits, invariants, replay, Loader smokes) | Support — lower compatibility expectations | | [`util/`](util/README.md) | Low-level zero-dependency utilities shared across groups (the `Branded` primitive) | Support — small, stable, harness-dep-free | Groups distinguish product API from support infrastructure. New packages join an existing group; a new group updates its README and this table. diff --git a/packages/bash/tool-bash/package.json b/packages/bash/tool-bash/package.json index 035b16aeec..c8d082f46a 100644 --- a/packages/bash/tool-bash/package.json +++ b/packages/bash/tool-bash/package.json @@ -38,6 +38,7 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-user-approval": "workspace:^", "@deepseek-ai/dsh-bash": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", diff --git a/packages/bash/tool-bash/tests/integration.spec.ts b/packages/bash/tool-bash/tests/integration.spec.ts index 238176b8a6..0a2a1ba883 100644 --- a/packages/bash/tool-bash/tests/integration.spec.ts +++ b/packages/bash/tool-bash/tests/integration.spec.ts @@ -1,12 +1,9 @@ import { describe, expect, it } from 'vitest' import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' -import SessionStore from '@deepseek-ai/dsh-session' 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 { AgentId } from '@deepseek-ai/dsh-agent' import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import TaskService from '@deepseek-ai/dsh-tasks' import * as ToolTasks from '@deepseek-ai/dsh-tool-tasks' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' @@ -21,11 +18,7 @@ import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent */ async function harness(adapter: MockAdapter) { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(TaskService) await ctx.plugin(ToolTasks) diff --git a/packages/compact/compact-basic/package.json b/packages/compact/compact-basic/package.json index 32852a060f..fae0af5c62 100644 --- a/packages/compact/compact-basic/package.json +++ b/packages/compact/compact-basic/package.json @@ -31,11 +31,11 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-compact": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", - "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/compact/compact-basic/tests/compact-loop-repro.spec.ts b/packages/compact/compact-basic/tests/compact-loop-repro.spec.ts index dbf8a3b737..f2d1a60ad8 100644 --- a/packages/compact/compact-basic/tests/compact-loop-repro.spec.ts +++ b/packages/compact/compact-basic/tests/compact-loop-repro.spec.ts @@ -1,14 +1,12 @@ import { describe, expect, it } from 'vitest' import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' import type { ContentBlock, GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm' -import SessionStore from '@deepseek-ai/dsh-session' 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 { defineTool } from '@deepseek-ai/dsh-tools' +import { AgentId } from '@deepseek-ai/dsh-agent' import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import * as Invariants from '@deepseek-ai/dsh-invariants' import { BasicCompactService } from '@deepseek-ai/dsh-compact-basic' import type { SurfaceEvent } from '@deepseek-ai/dsh-session' @@ -60,12 +58,8 @@ class StepwiseToolAdapter extends LlmAdapter { async function harness(toolSteps: number): Promise<{ ctx: Context; compact: ReproCompactService }> { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(Invariants) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) await ctx.plugin(AgentLoop, { agents: [] }) ctx.llm.registerAdapter(['mock'], new StepwiseToolAdapter(toolSteps)) ctx.tools.register(defineTool({ diff --git a/packages/context/time-context/package.json b/packages/context/time-context/package.json index f319c7a5b1..906adf854e 100644 --- a/packages/context/time-context/package.json +++ b/packages/context/time-context/package.json @@ -32,6 +32,7 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", diff --git a/packages/context/time-context/tests/time-context.spec.ts b/packages/context/time-context/tests/time-context.spec.ts index 562b002b68..92637e8277 100644 --- a/packages/context/time-context/tests/time-context.spec.ts +++ b/packages/context/time-context/tests/time-context.spec.ts @@ -1,14 +1,15 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { Context } from 'cordis' import Loader from '@cordisjs/plugin-loader' -import LlmService, { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm' +import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm' import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' -import SessionStore, { Session, SessionId, foldRequestHeader } from '@deepseek-ai/dsh-session' +import { Session, SessionId, foldRequestHeader } from '@deepseek-ai/dsh-session' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' -import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent' +import { AgentId } from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' -import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools' +import { defineTool } from '@deepseek-ai/dsh-tools' import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import * as timeContext from '@deepseek-ai/dsh-time-context' import type { Config } from '@deepseek-ai/dsh-time-context' @@ -89,11 +90,7 @@ class ScriptedAdapter extends LlmAdapter { async function loopHarness(adapter: ScriptedAdapter, config: Config = {}): Promise { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(timeContext, config) ctx.llm.registerAdapter(['mock'], adapter) diff --git a/packages/cordis/tool-cordis/package.json b/packages/cordis/tool-cordis/package.json index fd9c35e48e..e13de3e58b 100644 --- a/packages/cordis/tool-cordis/package.json +++ b/packages/cordis/tool-cordis/package.json @@ -33,6 +33,7 @@ "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-scope": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", diff --git a/packages/cordis/tool-cordis/tests/integration.spec.ts b/packages/cordis/tool-cordis/tests/integration.spec.ts index 94331df7c0..8892dd47e5 100644 --- a/packages/cordis/tool-cordis/tests/integration.spec.ts +++ b/packages/cordis/tool-cordis/tests/integration.spec.ts @@ -1,11 +1,8 @@ import { describe, expect, it } from 'vitest' import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' -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 { AgentId } from '@deepseek-ai/dsh-agent' import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import * as ToolCordis from '../src/index.ts' import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' import { REVERSE_TOOL_CODE } from './helpers.ts' @@ -20,11 +17,7 @@ import { REVERSE_TOOL_CODE } from './helpers.ts' async function harness(adapter: MockAdapter): Promise { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(ToolCordis) ctx.llm.registerAdapter(['mock'], adapter) diff --git a/packages/fs/tool-fs/package.json b/packages/fs/tool-fs/package.json index c21c806e98..f9ef3136bf 100644 --- a/packages/fs/tool-fs/package.json +++ b/packages/fs/tool-fs/package.json @@ -36,6 +36,7 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-fs-policy": "workspace:^", "@deepseek-ai/dsh-fs": "workspace:^", "@deepseek-ai/dsh-fs-local": "workspace:^", diff --git a/packages/fs/tool-fs/tests/harness.ts b/packages/fs/tool-fs/tests/harness.ts index 0487962922..3666447ad9 100644 --- a/packages/fs/tool-fs/tests/harness.ts +++ b/packages/fs/tool-fs/tests/harness.ts @@ -1,10 +1,7 @@ import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' -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 type { Agent } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import LocalFileSystem from '@deepseek-ai/dsh-fs-local' import * as FsPolicy from '@deepseek-ai/dsh-fs-policy' import * as ToolFs from '@deepseek-ai/dsh-tool-fs' @@ -17,11 +14,7 @@ import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek' */ export async function fsHarness(fsCwd: string, persona = ''): Promise { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt, { persona }) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx, { systemPrompt: { persona } }) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LlmDeepSeek, { models: ['deepseek-v4-flash'] }) await ctx.plugin(LocalFileSystem, { cwd: fsCwd }) diff --git a/packages/guard/repeat-tool-guard/package.json b/packages/guard/repeat-tool-guard/package.json index 0cc99b6976..92d49c548f 100644 --- a/packages/guard/repeat-tool-guard/package.json +++ b/packages/guard/repeat-tool-guard/package.json @@ -32,9 +32,9 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", - "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/guard/repeat-tool-guard/tests/repeat-tool-guard.spec.ts b/packages/guard/repeat-tool-guard/tests/repeat-tool-guard.spec.ts index 565f1076b5..81fe63aaca 100644 --- a/packages/guard/repeat-tool-guard/tests/repeat-tool-guard.spec.ts +++ b/packages/guard/repeat-tool-guard/tests/repeat-tool-guard.spec.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from 'vitest' import { Context } from 'cordis' -import LlmService, { CallId } from '@deepseek-ai/dsh-llm' -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 { CallId } from '@deepseek-ai/dsh-llm' +import type { SessionEvent } from '@deepseek-ai/dsh-session' +import { defineTool } from '@deepseek-ai/dsh-tools' +import { AgentId } from '@deepseek-ai/dsh-agent' import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import * as RepeatToolGuard from '@deepseek-ai/dsh-repeat-tool-guard' import type { Config } from '@deepseek-ai/dsh-repeat-tool-guard' import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -21,11 +21,7 @@ import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent /** Boot the core spine + the guard; the caller registers adapters and extra listeners. */ async function harness(config: Config = {}): Promise { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) 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' }] } })) @@ -363,11 +359,7 @@ describe('fold onto the downstream decision', () => { describe('config validation fails loud', () => { async function spine(): Promise { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) return ctx } diff --git a/packages/hooks/hooks-claude/package.json b/packages/hooks/hooks-claude/package.json index 21f08965d8..abb68a061e 100644 --- a/packages/hooks/hooks-claude/package.json +++ b/packages/hooks/hooks-claude/package.json @@ -36,13 +36,13 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-bash": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", "@deepseek-ai/dsh-hook-protocol": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-subagent": "workspace:^", - "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/hooks/hooks-claude/tests/bridge.spec.ts b/packages/hooks/hooks-claude/tests/bridge.spec.ts index 506d613c73..6d9e89d5ed 100644 --- a/packages/hooks/hooks-claude/tests/bridge.spec.ts +++ b/packages/hooks/hooks-claude/tests/bridge.spec.ts @@ -4,12 +4,11 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { Context, type Fiber } from 'cordis' import Loader from '@cordisjs/plugin-loader' -import LlmService from '@deepseek-ai/dsh-llm' -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 type { SessionEvent } from '@deepseek-ai/dsh-session' +import { defineTool } from '@deepseek-ai/dsh-tools' +import { AgentId } from '@deepseek-ai/dsh-agent' import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' import * as HooksClaude from '@deepseek-ai/dsh-hooks-claude' import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -42,11 +41,7 @@ async function harness(configDir: string, adapter: MockAdapter): Promise { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 }) const hooks = await ctx.plugin(HooksClaude, { configPath: join(configDir, 'hooks.json') }) @@ -337,11 +332,7 @@ describe('hooks-claude bridge — load resilience', () => { it('a missing config file registers no hooks and does not crash the loop', async () => { const adapter = new MockAdapter([textResponse('fine')]) const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 }) await ctx.plugin(HooksClaude, { configPath: '/nonexistent/hooks.json' }) @@ -359,11 +350,7 @@ describe('hooks-claude bridge — load resilience', () => { const dir = writeConfig({ UserPromptSubmit: [{ hooks: [{ type: 'command', command: 'exit 2' }] }] }) 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(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 }) const fiber = await ctx.plugin(HooksClaude, { configPath: join(dir, 'hooks.json') }) diff --git a/packages/hooks/hooks-claude/tests/coverage.spec.ts b/packages/hooks/hooks-claude/tests/coverage.spec.ts index f376708688..5fb975b3dd 100644 --- a/packages/hooks/hooks-claude/tests/coverage.spec.ts +++ b/packages/hooks/hooks-claude/tests/coverage.spec.ts @@ -3,12 +3,11 @@ import { mkdtempSync, rmSync, writeFileSync, chmodSync, existsSync } from 'node: import { tmpdir } from 'node:os' import { join } from 'node:path' import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' -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 type { SessionEvent } from '@deepseek-ai/dsh-session' +import { defineTool } from '@deepseek-ai/dsh-tools' +import { AgentId } from '@deepseek-ai/dsh-agent' import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' import * as HooksClaude from '@deepseek-ai/dsh-hooks-claude' import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -30,11 +29,7 @@ function hooks(d: string, h: unknown): string { type HarnessOpts = { pluginRoot?: string; projectDir?: string; stderrSummaryMaxChars?: number } async function harness(configPath: string, adapter: MockAdapter, opts: HarnessOpts = {}): Promise { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 }) await ctx.plugin(HooksClaude, { configPath, ...opts }) @@ -329,11 +324,7 @@ describe('hooks-claude coverage — schema-bypass apply + unspawnable hook', () hooks(d, { UserPromptSubmit: [{ hooks: [{ type: 'command', command: s }] }] }) 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(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 }) // Direct apply with only configPath — bypasses schemastery's defaults, so @@ -594,11 +585,7 @@ describe('hooks-claude coverage — hook runs in the session cwd, not the server hooks(serverDir, { PreToolUse: [{ hooks: [{ type: 'command', command: 'pwd > where' }] }] }) const adapter = new MockAdapter([toolCallResponse('c1', 'echo', {}), textResponse('done')]) const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) // Executor default cwd = serverDir (deliberately NOT the session cwd). await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000, cwd: serverDir }) @@ -627,11 +614,7 @@ describe('hooks-claude coverage — hook runs in the session cwd, not the server const marker = join(childDir, 'stopwhere') hooks(serverDir, { SubagentStop: [{ hooks: [{ type: 'command', command: 'pwd > stopwhere' }] }] }) const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) // Executor default cwd = serverDir (deliberately NOT the child session cwd). await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000, cwd: serverDir }) diff --git a/packages/hooks/hooks-codex/package.json b/packages/hooks/hooks-codex/package.json index fe667b0302..a481162a11 100644 --- a/packages/hooks/hooks-codex/package.json +++ b/packages/hooks/hooks-codex/package.json @@ -35,12 +35,12 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-bash": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", "@deepseek-ai/dsh-hook-protocol": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", - "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/hooks/hooks-codex/tests/bridge.spec.ts b/packages/hooks/hooks-codex/tests/bridge.spec.ts index d4a5797b96..72e3f45184 100644 --- a/packages/hooks/hooks-codex/tests/bridge.spec.ts +++ b/packages/hooks/hooks-codex/tests/bridge.spec.ts @@ -4,12 +4,11 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { Context } from 'cordis' import Loader from '@cordisjs/plugin-loader' -import LlmService from '@deepseek-ai/dsh-llm' -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 type { SessionEvent } from '@deepseek-ai/dsh-session' +import { defineTool } from '@deepseek-ai/dsh-tools' +import { AgentId } from '@deepseek-ai/dsh-agent' import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' import * as HooksCodex from '@deepseek-ai/dsh-hooks-codex' import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -40,11 +39,7 @@ function writeHooks(dir: string, hooks: unknown): void { async function harness(dir: string, adapter: MockAdapter): Promise { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 }) await ctx.plugin(HooksCodex, { configPath: join(dir, 'hooks.json'), model: 'test-model' }) @@ -141,11 +136,7 @@ describe('hooks-codex bridge', () => { writeHooks(dir, { UserPromptSubmit: [{ hooks: [{ type: 'command', command: deny }] }] }) 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(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) 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' }) @@ -167,11 +158,7 @@ describe('hooks-codex bridge', () => { const slow = script(dir, 'slow.sh', `#!/usr/bin/env bash\necho $$ > "${pidFile}"\ntouch "${marker}"\nsleep 30\n`) writeHooks(dir, { SessionStart: [{ hooks: [{ type: 'command', command: slow }] }] }) const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) 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' }) diff --git a/packages/hooks/hooks-codex/tests/coverage.spec.ts b/packages/hooks/hooks-codex/tests/coverage.spec.ts index c287d86b23..6d2ac699eb 100644 --- a/packages/hooks/hooks-codex/tests/coverage.spec.ts +++ b/packages/hooks/hooks-codex/tests/coverage.spec.ts @@ -3,12 +3,11 @@ import { mkdtempSync, rmSync, writeFileSync, chmodSync, existsSync, readFileSync import { tmpdir } from 'node:os' import { join } from 'node:path' import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' -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 type { SessionEvent } from '@deepseek-ai/dsh-session' +import { defineTool } from '@deepseek-ai/dsh-tools' +import { AgentId } from '@deepseek-ai/dsh-agent' import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' import * as HooksCodex from '@deepseek-ai/dsh-hooks-codex' import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -25,8 +24,8 @@ function hooks(d: string, h: unknown): string { async function harness(configPath: string, adapter: MockAdapter, opts: { stderrSummaryMaxChars?: number } = {}): Promise { const ctx = new Context() - await ctx.plugin(LlmService); await ctx.plugin(SessionStore); await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry); await ctx.plugin(AgentRegistry); await ctx.plugin(AgentLoop, { agents: [] }) + await mountAgentLoopTestDependencies(ctx) + await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 }) await ctx.plugin(HooksCodex, { configPath, model: 'm', ...opts }) ctx.llm.registerAdapter(['mock'], adapter) @@ -238,8 +237,8 @@ describe('hooks-codex coverage — decision mapping paths', () => { const warn = vi.fn() 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(ToolRegistry); await ctx.plugin(AgentRegistry); await ctx.plugin(AgentLoop, { agents: [] }) + await mountAgentLoopTestDependencies(ctx) + await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 }) ctx.logger.warn = warn as never // Direct apply (schema bypass) → the `model ?? ''` fallback is exercised. @@ -543,8 +542,8 @@ describe('hooks-codex coverage — decision mapping paths', () => { hooks(serverDir, { PreToolUse: [{ hooks: [{ type: 'command', command: 'pwd > where' }] }] }) 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(ToolRegistry); await ctx.plugin(AgentRegistry); await ctx.plugin(AgentLoop, { agents: [] }) + await mountAgentLoopTestDependencies(ctx) + 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' }) ctx.llm.registerAdapter(['mock'], adapter) diff --git a/packages/subagent/subagent-fork/package.json b/packages/subagent/subagent-fork/package.json index 8794884518..63f548d217 100644 --- a/packages/subagent/subagent-fork/package.json +++ b/packages/subagent/subagent-fork/package.json @@ -34,14 +34,13 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-subagent": "workspace:^", "@deepseek-ai/dsh-subagent-inprocess": "workspace:^", "@deepseek-ai/dsh-subagent-spawn": "workspace:^", - "@deepseek-ai/dsh-system-prompt": "workspace:^", - "@deepseek-ai/dsh-tools": "workspace:^", "@cordisjs/plugin-loader": "^1.0.0-rc.5", "cordis": "^4.0.0-rc.7" } diff --git a/packages/subagent/subagent-fork/tests/multi-subagent.spec.ts b/packages/subagent/subagent-fork/tests/multi-subagent.spec.ts index 8060a77fd4..110c4f83e4 100644 --- a/packages/subagent/subagent-fork/tests/multi-subagent.spec.ts +++ b/packages/subagent/subagent-fork/tests/multi-subagent.spec.ts @@ -1,11 +1,8 @@ import { describe, expect, it } from 'vitest' import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' -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 { AgentId } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import * as Invariants from '@deepseek-ai/dsh-invariants' import SubagentService, { type SubagentStartRequest } from '@deepseek-ai/dsh-subagent' import * as Spawn from '@deepseek-ai/dsh-subagent-spawn' @@ -26,11 +23,7 @@ function start(ctx: Context, provider: string, request: Omit Promise.resolve({ logs: [] })), } as never) } - await ctx.plugin(AgentRegistry) await ctx.plugin(Invariants) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SubagentService) diff --git a/packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts b/packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts index 0005246523..e7d856e112 100644 --- a/packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts +++ b/packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts @@ -1,11 +1,8 @@ import { describe, expect, it } from 'vitest' import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' -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 { AgentId, type Agent } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import * as Invariants from '@deepseek-ai/dsh-invariants' import SubagentService from '@deepseek-ai/dsh-subagent' import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -15,11 +12,7 @@ type Script = ConstructorParameters[0] async function setup(script: Script) { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(Invariants) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SubagentService) diff --git a/packages/subagent/subagent-spawn/package.json b/packages/subagent/subagent-spawn/package.json index f2500a4a56..1a986e69aa 100644 --- a/packages/subagent/subagent-spawn/package.json +++ b/packages/subagent/subagent-spawn/package.json @@ -32,6 +32,7 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", @@ -39,10 +40,8 @@ "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-subagent": "workspace:^", "@deepseek-ai/dsh-subagent-inprocess": "workspace:^", - "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/dsh-tool-bash": "workspace:^", "@deepseek-ai/dsh-tool-subagent": "workspace:^", - "@deepseek-ai/dsh-tools": "workspace:^", "@cordisjs/plugin-loader": "^1.0.0-rc.5", "cordis": "^4.0.0-rc.7" } diff --git a/packages/subagent/subagent-spawn/tests/harness.ts b/packages/subagent/subagent-spawn/tests/harness.ts index e4ee2e6ab7..bb0b49fbcb 100644 --- a/packages/subagent/subagent-spawn/tests/harness.ts +++ b/packages/subagent/subagent-spawn/tests/harness.ts @@ -1,10 +1,7 @@ import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' -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 type { Agent } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' import * as ToolBash from '@deepseek-ai/dsh-tool-bash' import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek' @@ -21,15 +18,13 @@ import * as ToolSubagent from '@deepseek-ai/dsh-tool-subagent' */ export async function spawnHarness(workdir: string): Promise { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) // This harness installs only the global default persona, so both parent and // spawned children render it. It stays neutral for both roles; the // delegation nudge lives in the e2e's user prompt and the subagent tool's // own description. - 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 mountAgentLoopTestDependencies(ctx, { + systemPrompt: { persona: 'You are a coding agent. Report only when the requested work is done.' }, + }) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(LlmDeepSeek, { models: ['deepseek-v4-flash'] }) await ctx.plugin(LocalBashExecutor, { cwd: workdir, timeoutMs: 30_000 }) diff --git a/packages/subagent/subagent-spawn/tests/subagent-spawn.spec.ts b/packages/subagent/subagent-spawn/tests/subagent-spawn.spec.ts index 43ec8ee1ac..3862f110c5 100644 --- a/packages/subagent/subagent-spawn/tests/subagent-spawn.spec.ts +++ b/packages/subagent/subagent-spawn/tests/subagent-spawn.spec.ts @@ -1,13 +1,10 @@ import { describe, expect, it } from 'vitest' import { Context } from 'cordis' import Loader from '@cordisjs/plugin-loader' -import LlmService from '@deepseek-ai/dsh-llm' -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 { SessionId } from '@deepseek-ai/dsh-session' import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import * as Invariants from '@deepseek-ai/dsh-invariants' import SubagentService, { type SubagentStartRequest } from '@deepseek-ai/dsh-subagent' import { MockAdapter, maxTokensResponse, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -26,11 +23,7 @@ type Script = ConstructorParameters[0] async function setup(script: Script) { const ctx = new Context() const adapter = new MockAdapter(script) - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(Invariants) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SubagentService) @@ -303,11 +296,7 @@ describe('dsh-subagent-spawn', () => { // Rebuild the stack by hand so we hold the backend's fiber. const ctx = new Context() const adapter = new MockAdapter(['hang']) - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(Invariants) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SubagentService) @@ -336,11 +325,7 @@ describe('dsh-subagent-spawn', () => { it('a start racing an already-unloading backend cannot begin child creation', 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) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SubagentService) const fiber = await ctx.plugin(spawn, { providerName: 'spawn' }) diff --git a/packages/support/README.md b/packages/support/README.md index d1bb1883ed..a85fffcdac 100644 --- a/packages/support/README.md +++ b/packages/support/README.md @@ -5,9 +5,10 @@ Packages that exist to serve development, testing, and the examples rather than | Package | Role | ctx key | |---|---|---| | `acp-snapshot/` | ACP snapshot suite kit: subprocess scenario harness + golden normalizers + the `defineAcpSnapshotSuite` factory | (library — imported by example `*.snapshot.ts` suites) | +| `agent-loop-testkit/` | Shared prerequisite mounting for tests that exercise the concrete agent loop | (library — imported by AgentLoop integration tests) | | `invariants/` | Runtime event-contract assertions for development diagnostics | (listens on `session/*`, `agent/*`) | | `loader-smoke/` | Shared real-Loader subprocess harness for keyless example smokes | (library — imported by example e2e suites) | | `llm-replay/` | Record/replay adapter: short-circuits `llm/stream` from a recorded session JSONL (keyless snapshot tests) | (listens on `llm/stream`) | | `subagent-mock/` | Scripted `SubagentProvider` for deterministic seam/tool tests | (registers on `ctx.subagents`) | -`invariants` is development support but has no environment guard: it runs wherever registered, and the default `dsh-agent-spine-demo` bundle mounts it unconditionally. `llm-replay` backs the demos and the snapshot test tier under the per-file coverage gate. `acp-snapshot` carries the snapshot tier's harness/normalizer/suite machinery, while `loader-smoke` owns the parallel stdio/Loader process boundary used by keyless example e2e suites. `subagent-mock` exercises the real `ctx.subagents` load path without a model or child agent. A package graduates OUT of `support/` into a product group only when it gains documented product consumers. +`invariants` is development support but has no environment guard: it runs wherever registered, and the default `dsh-agent-spine-demo` bundle mounts it unconditionally. `agent-loop-testkit` centralizes the mandatory service spine for hand-built AgentLoop tests without owning their loop or scenario. `llm-replay` backs the demos and the snapshot test tier under the per-file coverage gate. `acp-snapshot` carries the snapshot tier's harness/normalizer/suite machinery, while `loader-smoke` owns the parallel stdio/Loader process boundary used by keyless example e2e suites. `subagent-mock` exercises the real `ctx.subagents` load path without a model or child agent. A package graduates OUT of `support/` into a product group only when it gains documented product consumers. diff --git a/packages/support/agent-loop-testkit/README.md b/packages/support/agent-loop-testkit/README.md new file mode 100644 index 0000000000..350a8643e1 --- /dev/null +++ b/packages/support/agent-loop-testkit/README.md @@ -0,0 +1,27 @@ +# `@deepseek-ai/dsh-agent-loop-testkit` + +Shared prerequisite mounting for tests that exercise the concrete `AgentLoop`. `mountAgentLoopTestDependencies(ctx, options?)` installs the LLM, session, system-prompt, tool, and agent services in dependency order, then returns before the loop is mounted. + +The caller registers adapters and optional plugins, mounts `AgentLoop` with the configuration under test, and disposes its own Context. System-prompt and tool-registry configuration can be forwarded through `options`; the helper does not provide test defaults beyond those owned by the services. A plugin-load failure rejects the helper call, while services activated earlier in the sequence remain owned by the caller's Context. + +```ts +import { Context } from 'cordis' +import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' + +const ctx = new Context() + +await mountAgentLoopTestDependencies(ctx) +// Register the test adapter and any optional plugins here. +await ctx.plugin(AgentLoop, { agents: [] }) +``` + +Tests of injection failures, partial topology, service load order, or service teardown mount their dependencies directly instead of using this helper. + +## Model Experience + +None, as this test-only composition helper neither drives nor modifies model requests. + +## Known Limitations and Deferred Work + +- **Only the mandatory prerequisite spine is shared** — adapters, optional plugins, `AgentLoop`, agents, and Context teardown remain caller-owned so scenario-specific ordering stays visible. diff --git a/packages/support/agent-loop-testkit/package.json b/packages/support/agent-loop-testkit/package.json new file mode 100644 index 0000000000..423bd3e80d --- /dev/null +++ b/packages/support/agent-loop-testkit/package.json @@ -0,0 +1,41 @@ +{ + "name": "@deepseek-ai/dsh-agent-loop-testkit", + "description": "Shared prerequisite mounting for tests that exercise the concrete agent loop", + "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" + }, + "./src/*": "./src/*", + "./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", + "@deepseek-ai/dsh-llm": "^0.0.1", + "@deepseek-ai/dsh-session": "^0.0.1", + "@deepseek-ai/dsh-system-prompt": "^0.0.1", + "@deepseek-ai/dsh-tools": "^0.0.1", + "cordis": "^4.0.0-rc.7" + }, + "devDependencies": { + "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-llm": "workspace:^", + "@deepseek-ai/dsh-session": "workspace:^", + "@deepseek-ai/dsh-system-prompt": "workspace:^", + "@deepseek-ai/dsh-tools": "workspace:^", + "cordis": "^4.0.0-rc.7" + } +} diff --git a/packages/support/agent-loop-testkit/src/index.ts b/packages/support/agent-loop-testkit/src/index.ts new file mode 100644 index 0000000000..c7b0cb7304 --- /dev/null +++ b/packages/support/agent-loop-testkit/src/index.ts @@ -0,0 +1,46 @@ +/** + * Shared mounting for the services required before tests load the concrete + * agent loop. The caller retains ownership of the context, loop, adapters, + * optional plugins, and teardown. + * @module @deepseek-ai/dsh-agent-loop-testkit + */ + +import type { Context } from 'cordis' +import AgentRegistry from '@deepseek-ai/dsh-agent' +import LlmService from '@deepseek-ai/dsh-llm' +import SessionStore from '@deepseek-ai/dsh-session' +import SystemPrompt from '@deepseek-ai/dsh-system-prompt' +import type { Config as SystemPromptConfig } from '@deepseek-ai/dsh-system-prompt' +import ToolRegistry from '@deepseek-ai/dsh-tools' +import type { Config as ToolRegistryConfig } from '@deepseek-ai/dsh-tools' + +/** Configuration forwarded to the prerequisite service plugins. */ +export interface AgentLoopTestDependenciesOptions { + /** Configuration for the system-prompt registry. */ + readonly systemPrompt?: SystemPromptConfig + /** Configuration for the tool registry. */ + readonly tools?: ToolRegistryConfig +} + +/** + * Mount the standard prerequisite services for an AgentLoop test. + * + * The function deliberately does not mount AgentLoop or register an adapter, + * so tests retain control of load order and the topology under test. The + * context owns every mounted service and remains responsible for disposal. A + * plugin-load failure rejects the promise; services activated earlier in the + * sequence remain context-owned and unwind with that context. + * @param ctx - test context that owns the mounted services. + * @param options - optional service configuration forwarded without mutation. + * @returns after every prerequisite service has activated. + */ +export async function mountAgentLoopTestDependencies( + ctx: Context, + options: AgentLoopTestDependenciesOptions = {}, +): Promise { + await ctx.plugin(LlmService) + await ctx.plugin(SessionStore) + await ctx.plugin(SystemPrompt, options.systemPrompt ?? {}) + await ctx.plugin(ToolRegistry, options.tools ?? {}) + await ctx.plugin(AgentRegistry) +} diff --git a/packages/support/agent-loop-testkit/tests/agent-loop-testkit.spec.ts b/packages/support/agent-loop-testkit/tests/agent-loop-testkit.spec.ts new file mode 100644 index 0000000000..aa125b561f --- /dev/null +++ b/packages/support/agent-loop-testkit/tests/agent-loop-testkit.spec.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest' +import { Context } from 'cordis' +import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { renderPrompt } from '@deepseek-ai/dsh-system-prompt' +import { mountAgentLoopTestDependencies } from '../src/index.ts' + +describe('dsh-agent-loop-testkit', () => { + it('mounts a configurable prerequisite spine that can activate AgentLoop', async () => { + const ctx = new Context() + await mountAgentLoopTestDependencies(ctx, { + systemPrompt: { persona: 'Test persona.' }, + tools: { mode: 'native' }, + }) + + expect(renderPrompt(await ctx.systemPrompt.assemble())).toContain('Test persona.') + await expect(ctx.plugin(AgentLoop, { agents: [] })).resolves.toBeDefined() + + await ctx.fiber.dispose() + }) +}) diff --git a/packages/support/agent-loop-testkit/tsconfig.json b/packages/support/agent-loop-testkit/tsconfig.json new file mode 100644 index 0000000000..5e5b3c47f2 --- /dev/null +++ b/packages/support/agent-loop-testkit/tsconfig.json @@ -0,0 +1,33 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../core/agent" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../core/session" + }, + { + "path": "../../core/system-prompt" + }, + { + "path": "../../core/tools" + } + ] +} diff --git a/packages/todo/tool-todo/package.json b/packages/todo/tool-todo/package.json index 9ff69d7c76..bab0f1230c 100644 --- a/packages/todo/tool-todo/package.json +++ b/packages/todo/tool-todo/package.json @@ -30,6 +30,7 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", diff --git a/packages/todo/tool-todo/tests/integration.spec.ts b/packages/todo/tool-todo/tests/integration.spec.ts index 739367a699..c13a0f76f8 100644 --- a/packages/todo/tool-todo/tests/integration.spec.ts +++ b/packages/todo/tool-todo/tests/integration.spec.ts @@ -1,12 +1,9 @@ import { describe, expect, it } from 'vitest' import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' -import SessionStore from '@deepseek-ai/dsh-session' 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 { AgentId } from '@deepseek-ai/dsh-agent' import AgentLoop, { ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import * as ToolTodo from '@deepseek-ai/dsh-tool-todo' import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -18,11 +15,7 @@ import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent */ async function harness(adapter: MockAdapter): Promise { const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(ToolTodo) ctx.llm.registerAdapter(['mock'], adapter) diff --git a/packages/ui/acp/package.json b/packages/ui/acp/package.json index 71efc51a07..0a28c4f6da 100644 --- a/packages/ui/acp/package.json +++ b/packages/ui/acp/package.json @@ -42,6 +42,7 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-bash": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", "@deepseek-ai/dsh-fs-local": "workspace:^", diff --git a/packages/ui/acp/tests/harness.ts b/packages/ui/acp/tests/harness.ts index 440120943f..f8e2e37627 100644 --- a/packages/ui/acp/tests/harness.ts +++ b/packages/ui/acp/tests/harness.ts @@ -5,13 +5,10 @@ */ import { Context } from 'cordis' -import LlmService, { CallId, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm' +import { CallId, type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm' import { LlmAdapter } from '@deepseek-ai/dsh-llm' -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 AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' import LocalFileSystem from '@deepseek-ai/dsh-fs-local' @@ -188,11 +185,9 @@ export async function makeBridgeHarness(options: { const adapter = new MockAdapter(options.script ?? []) const ctx = new Context() - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt, { persona: options.persona ?? '' }) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx, { + systemPrompt: { persona: options.persona ?? '' }, + }) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SessionPersistenceJsonl, { root: options.storageDir }) await ctx.plugin(UserInteractionService) diff --git a/packages/workflow/workflow-workerthread/package.json b/packages/workflow/workflow-workerthread/package.json index 485e252c97..1cd6e07e17 100644 --- a/packages/workflow/workflow-workerthread/package.json +++ b/packages/workflow/workflow-workerthread/package.json @@ -42,6 +42,7 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", "@deepseek-ai/dsh-brand": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", diff --git a/packages/workflow/workflow-workerthread/tests/integration.spec.ts b/packages/workflow/workflow-workerthread/tests/integration.spec.ts index 0e1727f877..7b30f13f4f 100644 --- a/packages/workflow/workflow-workerthread/tests/integration.spec.ts +++ b/packages/workflow/workflow-workerthread/tests/integration.spec.ts @@ -1,11 +1,8 @@ import { describe, expect, it } from 'vitest' import { Context } from 'cordis' -import LlmService from '@deepseek-ai/dsh-llm' -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 { AgentId } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import * as Invariants from '@deepseek-ai/dsh-invariants' import SubagentService from '@deepseek-ai/dsh-subagent' import * as spawn from '@deepseek-ai/dsh-subagent-spawn' @@ -26,11 +23,7 @@ type Script = ConstructorParameters[0] async function setup(script: Script) { const ctx = new Context() const adapter = new MockAdapter(script) - await ctx.plugin(LlmService) - await ctx.plugin(SessionStore) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRegistry) - await ctx.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(ctx) await ctx.plugin(Invariants) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SubagentService) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1a3a78030..7b12699ad8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -155,6 +155,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-bash': specifier: workspace:^ version: link:../bash @@ -228,6 +231,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-compact': specifier: workspace:^ version: link:../compact @@ -240,9 +246,6 @@ importers: '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt '@deepseek-ai/dsh-tools': specifier: workspace:^ version: link:../../core/tools @@ -262,6 +265,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -296,6 +302,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -648,6 +657,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-fs': specifier: workspace:^ version: link:../fs @@ -688,15 +700,15 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt '@deepseek-ai/dsh-tools': specifier: workspace:^ version: link:../../core/tools @@ -728,6 +740,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-bash': specifier: workspace:^ version: link:../../bash/bash @@ -746,9 +761,6 @@ importers: '@deepseek-ai/dsh-subagent': specifier: workspace:^ version: link:../../subagent/subagent - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt '@deepseek-ai/dsh-tools': specifier: workspace:^ version: link:../../core/tools @@ -768,6 +780,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-bash': specifier: workspace:^ version: link:../../bash/bash @@ -783,9 +798,6 @@ importers: '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt '@deepseek-ai/dsh-tools': specifier: workspace:^ version: link:../../core/tools @@ -1152,6 +1164,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-invariants': specifier: workspace:^ version: link:../../support/invariants @@ -1170,12 +1185,6 @@ importers: '@deepseek-ai/dsh-subagent-spawn': specifier: workspace:^ version: link:../subagent-spawn - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt - '@deepseek-ai/dsh-tools': - specifier: workspace:^ - version: link:../../core/tools cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) @@ -1188,6 +1197,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-invariants': specifier: workspace:^ version: link:../../support/invariants @@ -1225,6 +1237,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-bash-local': specifier: workspace:^ version: link:../../bash/bash-local @@ -1246,18 +1261,12 @@ importers: '@deepseek-ai/dsh-subagent-inprocess': specifier: workspace:^ version: link:../subagent-inprocess - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt '@deepseek-ai/dsh-tool-bash': specifier: workspace:^ version: link:../../bash/tool-bash '@deepseek-ai/dsh-tool-subagent': specifier: workspace:^ version: link:../tool-subagent - '@deepseek-ai/dsh-tools': - specifier: workspace:^ - version: link:../../core/tools cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) @@ -1321,6 +1330,30 @@ importers: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) + packages/support/agent-loop-testkit: + devDependencies: + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent + '@deepseek-ai/dsh-agent-loop': + specifier: workspace:^ + version: link:../../core/agent-loop + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + '@deepseek-ai/dsh-system-prompt': + specifier: workspace:^ + version: link:../../core/system-prompt + '@deepseek-ai/dsh-tools': + specifier: workspace:^ + version: link:../../core/tools + cordis: + specifier: ^4.0.0-rc.7 + version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) + packages/support/invariants: devDependencies: '@deepseek-ai/dsh-agent': @@ -1464,6 +1497,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1498,6 +1534,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-bash': specifier: workspace:^ version: link:../../bash/bash @@ -1894,6 +1933,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit '@deepseek-ai/dsh-brand': specifier: workspace:^ version: link:../../util/brand diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 8220324210..bfcb6f684e 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -57,6 +57,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/subagent/subagent': { kind: 'indirect', reason: 'The provider registry delegates parent-model rendering to dsh-tool-subagent.' }, 'packages/subagent/subagent-subprocess': { kind: 'indirect', reason: 'Only process-based subagent backends compose a child model request.' }, 'packages/support/acp-snapshot': { kind: 'none', reason: 'The test harness observes and normalizes transcripts without changing live requests.' }, + 'packages/support/agent-loop-testkit': { kind: 'none', reason: 'The test helper mounts services but neither drives nor modifies model requests.' }, 'packages/support/invariants': { kind: 'none', reason: 'The observer validates requests but never rewrites their context.' }, 'packages/support/loader-smoke': { kind: 'none', reason: 'The test harness observes child-process streams without changing live requests.' }, 'packages/support/llm-replay': { kind: 'none', reason: 'The keyless adapter invokes no provider model.' }, diff --git a/tsconfig.build.json b/tsconfig.build.json index 3a57169005..edb1479088 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -56,6 +56,7 @@ { "path": "./packages/web/tool-web" }, { "path": "./packages/timeout/timeout-policy" }, { "path": "./packages/support/invariants" }, + { "path": "./packages/support/agent-loop-testkit" }, { "path": "./packages/ui/acp" }, { "path": "./packages/examples/acp-demo" }, { "path": "./packages/ui/app-boot" }, diff --git a/tsconfig.json b/tsconfig.json index 6585a987d6..5a09464252 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -67,6 +67,7 @@ { "path": "./packages/web/tool-web" }, { "path": "./packages/timeout/timeout-policy" }, { "path": "./packages/support/invariants" }, + { "path": "./packages/support/agent-loop-testkit" }, { "path": "./packages/ui/acp" }, { "path": "./packages/examples/acp-demo" }, { "path": "./packages/ui/app-boot" },