ci(examples): run examples / testing from built lib/js
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-llm": "workspace:^",
|
||||
"@deepseek-ai/dsh-loader-smoke": "workspace:^",
|
||||
"@deepseek-ai/dsh-subagent": "workspace:^",
|
||||
"@deepseek-ai/dsh-subagent-subprocess": "workspace:^",
|
||||
"@cordisjs/plugin-loader": "^1.0.0-rc.5",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Minimal no-network ACP child process for keyless backend tests. Environment variables script its
|
||||
* text and stop reason, a cancel-cooperative or cancel-ignoring hang, permission requests, and a
|
||||
* readiness marker. Disposal fixtures can delay an EOF flush, ignore EOF but exit and mark
|
||||
* SIGTERM, or trap SIGTERM to require SIGKILL. The specs spawn this non-test module under tsx with
|
||||
* an explicit tsconfig, mirroring real example boot.
|
||||
* SIGTERM, or trap SIGTERM to require SIGKILL. The specs run this protocol-only fixture directly
|
||||
* with Node's type stripping; it imports no harness code or workspace paths.
|
||||
* @module @deepseek-ai/dsh-subagent-acp/tests/mock-acp-server
|
||||
*/
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import SubagentService from '@deepseek-ai/dsh-subagent'
|
||||
import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
|
||||
import * as acp from '../src/index.ts'
|
||||
|
||||
/**
|
||||
@@ -17,9 +18,22 @@ import * as acp from '../src/index.ts'
|
||||
// The real acp-agent example: its bin + cordis.yml (the live DeepSeek config).
|
||||
const binScript = fileURLToPath(new URL('../../../examples/acp-demo/src/bin.ts', import.meta.url))
|
||||
const exampleConfig = fileURLToPath(new URL('../../../../examples/acp-agent/cordis.yml', import.meta.url))
|
||||
const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
|
||||
const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url))
|
||||
|
||||
// How to launch the child acp-agent (src via tsx / lib via plain node, per DSH_EXAMPLE_MODE).
|
||||
// buildChildEnv scrubs ambient creds but keeps these extras, so the model key is
|
||||
// forwarded explicitly; TSX_TSCONFIG_PATH is added by the resolver in src mode only.
|
||||
const childLaunch = resolveExampleLaunch({
|
||||
srcBin: binScript,
|
||||
configArgs: ['--config', exampleConfig],
|
||||
tsconfigPath: repoTsconfig,
|
||||
env: {
|
||||
...process.env.DEEPSEEK_API_KEY !== undefined ? { DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY } : {},
|
||||
...process.env.DEEPSEEK_BASE_URL !== undefined ? { DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL } : {},
|
||||
DSH_PERMISSION_MODE: 'danger-full-access',
|
||||
},
|
||||
})
|
||||
|
||||
/** The ACP backend ignores the parent, but the seam requires one. */
|
||||
const fakeParent = { id: 'parent', session: { header: {} } } as unknown as Agent
|
||||
|
||||
@@ -40,18 +54,11 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('ACP backend with-key e2e (drive
|
||||
await ctx.plugin(SubagentService)
|
||||
await ctx.plugin(acp, {
|
||||
providerName: 'acp',
|
||||
command: process.execPath,
|
||||
args: ['--import', tsxLoader, binScript, '--config', exampleConfig],
|
||||
command: childLaunch.command,
|
||||
args: childLaunch.args,
|
||||
cwd: workdir,
|
||||
permission: 'reject',
|
||||
// The child harness needs the key to reach the model; forward it
|
||||
// explicitly (buildChildEnv scrubs ambient creds but keeps these extras).
|
||||
env: {
|
||||
...process.env.DEEPSEEK_API_KEY !== undefined ? { DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY } : {},
|
||||
...process.env.DEEPSEEK_BASE_URL !== undefined ? { DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL } : {},
|
||||
TSX_TSCONFIG_PATH: repoTsconfig,
|
||||
DSH_PERMISSION_MODE: 'danger-full-access',
|
||||
},
|
||||
env: childLaunch.env as Record<string, string>,
|
||||
})
|
||||
|
||||
const run = await ctx.subagents.start('acp', {
|
||||
@@ -76,17 +83,12 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('ACP backend with-key e2e (drive
|
||||
await ctx.plugin(SubagentService)
|
||||
await ctx.plugin(acp, {
|
||||
providerName: 'acp',
|
||||
command: process.execPath,
|
||||
args: ['--import', tsxLoader, binScript, '--config', exampleConfig],
|
||||
command: childLaunch.command,
|
||||
args: childLaunch.args,
|
||||
cwd: workdir,
|
||||
// The child needs to act (run bash), so approve its permission prompts.
|
||||
permission: 'allow',
|
||||
env: {
|
||||
...process.env.DEEPSEEK_API_KEY !== undefined ? { DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY } : {},
|
||||
...process.env.DEEPSEEK_BASE_URL !== undefined ? { DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL } : {},
|
||||
TSX_TSCONFIG_PATH: repoTsconfig,
|
||||
DSH_PERMISSION_MODE: 'danger-full-access',
|
||||
},
|
||||
env: childLaunch.env as Record<string, string>,
|
||||
})
|
||||
|
||||
const run = await ctx.subagents.start('acp', {
|
||||
|
||||
@@ -21,8 +21,6 @@ import { acpStopReason, acpContentText, DEFAULT_DISPOSE_EOF_GRACE_MS, DEFAULT_DI
|
||||
*/
|
||||
|
||||
const mockServer = fileURLToPath(new URL('./mock-acp-server.ts', import.meta.url))
|
||||
const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
|
||||
const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url))
|
||||
|
||||
/** A throwaway parent Agent — the ACP backend ignores it, but the seam requires one. */
|
||||
const fakeParent = { id: 'parent', session: { header: {} } } as unknown as Agent
|
||||
@@ -46,11 +44,9 @@ async function setup(mockEnv: SetupEnv = {}, permission: 'allow' | 'reject' = 'r
|
||||
await ctx.plugin(acp, {
|
||||
providerName: 'acp',
|
||||
command: process.execPath,
|
||||
args: ['--import', tsxLoader, mockServer],
|
||||
args: [mockServer],
|
||||
permission,
|
||||
// The mock-server scripting vars must reach the child; TSX_TSCONFIG_PATH lets
|
||||
// tsx resolve @deepseek-ai/* from a child cwd outside the repo.
|
||||
env: { ...mockEnv, TSX_TSCONFIG_PATH: repoTsconfig },
|
||||
env: mockEnv,
|
||||
})
|
||||
return ctx
|
||||
}
|
||||
@@ -197,10 +193,10 @@ describe('dsh-subagent-acp', () => {
|
||||
try {
|
||||
const spec: AcpRunSpec = {
|
||||
command: process.execPath,
|
||||
args: ['--import', tsxLoader, mockServer],
|
||||
args: [mockServer],
|
||||
cwd: process.cwd(),
|
||||
permission: 'reject',
|
||||
env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready, TSX_TSCONFIG_PATH: repoTsconfig },
|
||||
env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready },
|
||||
// Short on BOTH tiers: the trap ignores EOF and SIGTERM, so dispose must
|
||||
// burn the EOF window, then the SIGTERM window, then SIGKILL — keep each
|
||||
// small so the whole ladder finishes well within the 4000ms bound.
|
||||
@@ -240,7 +236,7 @@ describe('dsh-subagent-acp', () => {
|
||||
try {
|
||||
const spec: AcpRunSpec = {
|
||||
command: process.execPath,
|
||||
args: ['--import', tsxLoader, mockServer],
|
||||
args: [mockServer],
|
||||
cwd: process.cwd(),
|
||||
permission: 'reject',
|
||||
// MOCK_HANG so the prompt never resolves on its own — we tear down a live
|
||||
@@ -249,7 +245,7 @@ describe('dsh-subagent-acp', () => {
|
||||
// wider grace.
|
||||
env: {
|
||||
MOCK_HANG: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready,
|
||||
MOCK_FLUSH_ON_EOF: flushed, MOCK_FLUSH_DELAY_MS: '400', TSX_TSCONFIG_PATH: repoTsconfig,
|
||||
MOCK_FLUSH_ON_EOF: flushed, MOCK_FLUSH_DELAY_MS: '400',
|
||||
},
|
||||
disposeEofGraceMs: 2000,
|
||||
disposeGraceMs: 50,
|
||||
@@ -280,12 +276,12 @@ describe('dsh-subagent-acp', () => {
|
||||
try {
|
||||
const spec: AcpRunSpec = {
|
||||
command: process.execPath,
|
||||
args: ['--import', tsxLoader, mockServer],
|
||||
args: [mockServer],
|
||||
cwd: process.cwd(),
|
||||
permission: 'reject',
|
||||
env: {
|
||||
MOCK_HANG: '1', MOCK_IGNORE_EOF: '1', MOCK_TEXT: 'x',
|
||||
MOCK_READY_FILE: ready, MOCK_SIGTERM_FILE: sigterm, TSX_TSCONFIG_PATH: repoTsconfig,
|
||||
MOCK_READY_FILE: ready, MOCK_SIGTERM_FILE: sigterm,
|
||||
},
|
||||
// Tiny EOF grace so the ignored-EOF window elapses fast, then SIGTERM.
|
||||
disposeEofGraceMs: 150,
|
||||
@@ -404,9 +400,9 @@ describe('dsh-subagent-acp', () => {
|
||||
await ctx.plugin(acp, {
|
||||
providerName: 'acp',
|
||||
command: process.execPath,
|
||||
args: ['--import', tsxLoader, mockServer],
|
||||
args: [mockServer],
|
||||
permission: 'reject',
|
||||
env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready, TSX_TSCONFIG_PATH: repoTsconfig },
|
||||
env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready },
|
||||
disposeEofGraceMs: 150,
|
||||
disposeGraceMs: 150,
|
||||
})
|
||||
@@ -455,10 +451,10 @@ describe('dsh-subagent-acp', () => {
|
||||
request(),
|
||||
{
|
||||
command: process.execPath,
|
||||
args: ['--import', tsxLoader, mockServer],
|
||||
args: [mockServer],
|
||||
cwd: process.cwd(),
|
||||
permission: 'reject',
|
||||
env: { MOCK_CRASH_ON_PROMPT: '1', TSX_TSCONFIG_PATH: repoTsconfig },
|
||||
env: { MOCK_CRASH_ON_PROMPT: '1' },
|
||||
disposeEofGraceMs: DEFAULT_DISPOSE_EOF_GRACE_MS,
|
||||
disposeGraceMs: DEFAULT_DISPOSE_GRACE_MS,
|
||||
onError: (error, stopReason) => { errors.push({ message: error.message, stopReason }) },
|
||||
@@ -493,10 +489,10 @@ describe('dsh-subagent-acp', () => {
|
||||
request(),
|
||||
{
|
||||
command: process.execPath,
|
||||
args: ['--import', tsxLoader, mockServer],
|
||||
args: [mockServer],
|
||||
cwd: process.cwd(),
|
||||
permission: 'reject',
|
||||
env: { MOCK_CRASH_ON_PROMPT: '1', TSX_TSCONFIG_PATH: repoTsconfig },
|
||||
env: { MOCK_CRASH_ON_PROMPT: '1' },
|
||||
disposeEofGraceMs: DEFAULT_DISPOSE_EOF_GRACE_MS,
|
||||
disposeGraceMs: DEFAULT_DISPOSE_GRACE_MS,
|
||||
onError: () => { throw new Error('sink boom') },
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../subagent-subprocess"
|
||||
},
|
||||
{
|
||||
"path": "../../support/loader-smoke"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user