coding-task.e2e.ts 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import { createUserMessage } from '@deepseek-ai/dsh-llm'
  2. import { spawnSync } from 'node:child_process'
  3. import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
  4. import { tmpdir } from 'node:os'
  5. import { join } from 'node:path'
  6. import { afterEach, describe, expect, it } from 'vitest'
  7. import type { Context } from '@deepseek-ai/cordis'
  8. import { codingHarness, finalText, SYSTEM_PROMPT, waitForIdle } from './harness.ts'
  9. import { SessionId } from '@deepseek-ai/dsh-session'
  10. /**
  11. * The swebench-style smoke test: a real model fixes a real bug in a temp
  12. * directory using only the bash tool, and the fix is verified OUTSIDE the
  13. * agent by re-running the test script. Key-gated.
  14. */
  15. const TEST_FILE = [
  16. "const assert = require('node:assert');",
  17. "const { add } = require('./add.js');",
  18. 'assert.strictEqual(add(2, 3), 5);',
  19. 'assert.strictEqual(add(-1, 1), 0);',
  20. "console.log('PASS');",
  21. '',
  22. ].join('\n')
  23. const BUGGY_ADD = [
  24. '// A tiny module with an obvious bug.',
  25. 'function add(a, b) {',
  26. ' return a - b;',
  27. '}',
  28. 'module.exports = { add };',
  29. '',
  30. ].join('\n')
  31. let workdir: string | undefined
  32. let ctx: Context | undefined
  33. afterEach(async () => {
  34. // Dispose the harness even on failure/retry: agent-loop teardown stops the
  35. // loop and LocalBashExecutor teardown kills anything the model left running.
  36. await ctx?.fiber.dispose()
  37. ctx = undefined
  38. if (workdir !== undefined) await rm(workdir, { recursive: true, force: true })
  39. workdir = undefined
  40. })
  41. describe.skipIf(!process.env.DEEPSEEK_API_KEY)('coding task: fix a failing test via bash', () => {
  42. it('repairs add.js so node add.test.js passes', async () => {
  43. workdir = await mkdtemp(join(tmpdir(), 'dsh-coding-task-'))
  44. await writeFile(join(workdir, 'add.js'), BUGGY_ADD)
  45. await writeFile(join(workdir, 'add.test.js'), TEST_FILE)
  46. // Confirm the fixture actually fails before the agent touches it.
  47. const before = spawnSync('node', ['add.test.js'], { cwd: workdir })
  48. expect(before.status).not.toBe(0)
  49. ctx = await codingHarness(workdir, { personaPrefix: SYSTEM_PROMPT })
  50. const agent = await ctx.agentLoop.create(SessionId('e2e-task'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' })
  51. agent.followup(createUserMessage({
  52. content: [{
  53. type: 'text',
  54. text: 'In the current directory, `node add.test.js` fails because add.js has a bug. '
  55. + 'Fix add.js so the test passes, run `node add.test.js` to verify, and report the result. '
  56. + 'Do not modify add.test.js.',
  57. }], source: { kind: 'user' } }))
  58. await waitForIdle(ctx, agent)
  59. // The agent claims success…
  60. const summary = finalText(agent.session.snapshotEvents()).toLowerCase()
  61. expect(summary.length).toBeGreaterThan(0)
  62. // …and the world agrees: the test passes when WE run it, and the test
  63. // file is byte-identical (an agent that neutered the test instead of
  64. // fixing the bug fails here, not just on a keyword probe).
  65. const untouchedTest = await readFile(join(workdir, 'add.test.js'), 'utf8')
  66. expect(untouchedTest).toBe(TEST_FILE)
  67. const after = spawnSync('node', ['add.test.js'], { cwd: workdir, encoding: 'utf8' })
  68. expect(after.stdout).toContain('PASS')
  69. expect(after.status).toBe(0)
  70. const fixed = await readFile(join(workdir, 'add.js'), 'utf8')
  71. expect(fixed).not.toMatch(/a\s*-\s*b/)
  72. }, 180_000)
  73. })