built-runtime.e2e.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import { existsSync } from 'node:fs'
  2. import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
  3. import { homedir } from 'node:os'
  4. import { join } from 'node:path'
  5. import { execFile } from 'node:child_process'
  6. import { promisify } from 'node:util'
  7. import { fileURLToPath } from 'node:url'
  8. import { describe, expect, it } from 'vitest'
  9. const packageRoot = fileURLToPath(new URL('..', import.meta.url))
  10. const built = [
  11. new URL('../lib/index.js', import.meta.url),
  12. new URL('../../../ptc-runtime/ptc-runtime-node/lib/index.js', import.meta.url),
  13. new URL('../../../ptc-runtime/ptc-runtime-node/lib/process.js', import.meta.url),
  14. ].every(path => existsSync(path))
  15. const runNode = promisify(execFile)
  16. /** Plain Node loads the published workflow package and PTC child bootstrap. */
  17. describe.skipIf(!built)('built workflow PTC runtime', () => {
  18. it('runs a structured child and confines direct writes through installed exports', async () => {
  19. const driverRoot = await mkdtemp(join(packageRoot, '.built-runtime-'))
  20. let root: string | undefined
  21. try {
  22. root = await mkdtemp(join(homedir(), '.dsh-built-workflow-'))
  23. const cwd = join(root, 'workspace')
  24. const outside = join(root, 'outside.txt')
  25. await mkdir(cwd)
  26. await writeFile(outside, 'unchanged')
  27. const driver = join(driverRoot, 'driver.mjs')
  28. await writeFile(driver, `
  29. import { Context } from '@deepseek-ai/cordis'
  30. import PtcWorkflowEngine from '@deepseek-ai/dsh-workflow-ptc'
  31. const ctx = new Context()
  32. try {
  33. for (const name of ['session', 'session-projection', 'fs-local', 'subprocess-local', 'sandbox-local']) {
  34. await ctx.plugin((await import('@deepseek-ai/dsh-' + name)).default, {})
  35. }
  36. await ctx.plugin((await import('@deepseek-ai/dsh-sandbox-policy')).default, { mode: 'read-only', workspaceRoot: process.argv[2] })
  37. await ctx.plugin((await import('@deepseek-ai/dsh-ptc-runtime-node')).default, {})
  38. await ctx.plugin((await import('@deepseek-ai/dsh-subagent')).default, {})
  39. let selectedStarts = 0
  40. ctx.subagents.registerProvider({
  41. name: 'built-selected',
  42. capabilities: { agentOptions: true, outputSchema: true, depthLimit: false, toolFilter: false, persona: false },
  43. inheritsParentContext: false,
  44. async start() {
  45. selectedStarts += 1
  46. return {
  47. id: 'built-child', localAgent: undefined,
  48. result: Promise.resolve({ output: [], structured: { answer: 42 }, stopReason: 'completed' }),
  49. dispose: () => Promise.resolve(),
  50. }
  51. },
  52. })
  53. await ctx.plugin(PtcWorkflowEngine, { provider: 'must-not-be-used' })
  54. const session = ctx.sessions.create(undefined, { meta: { cwd: process.argv[2] } })
  55. const run = ctx.workflowEngine.start({
  56. script: "const proc = globalThis.constructor.constructor('return process')(); try { proc.getBuiltinModule('node:fs').writeFileSync(args.outside, 'changed') } catch {} const value = await agent('answer', { schema: { type: 'object', properties: { answer: { type: 'number' } }, required: ['answer'] } }); return value.answer",
  57. args: { outside: process.argv[3] },
  58. meta: { name: 'built-smoke', description: 'built workflow runtime' },
  59. subagentProvider: 'built-selected',
  60. parent: { id: session.id, session, options: {} },
  61. })
  62. try {
  63. const result = await run.result
  64. if (result.stopReason !== 'completed' || result.value !== 42 || selectedStarts !== 1) {
  65. throw new Error('unexpected result: ' + JSON.stringify(result))
  66. }
  67. console.log('built-runtime-smoke-ok')
  68. } finally { await run.dispose() }
  69. } finally { await ctx.fiber.dispose() }
  70. `, 'utf8')
  71. const { stdout } = await runNode(process.execPath, [driver, cwd, outside], { cwd: packageRoot, timeout: 60_000 })
  72. expect(stdout).toContain('built-runtime-smoke-ok')
  73. expect(await readFile(outside, 'utf8')).toBe('unchanged')
  74. } finally {
  75. await rm(driverRoot, { recursive: true, force: true })
  76. if (root !== undefined) await rm(root, { recursive: true, force: true })
  77. }
  78. }, 120_000)
  79. })