coding-task.e2e.ts 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import { spawnSync } from 'node:child_process'
  2. import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
  3. import { tmpdir } from 'node:os'
  4. import { join } from 'node:path'
  5. import { afterEach, describe, expect, it } from 'vitest'
  6. import type { Context } from 'cordis'
  7. import { codingHarness, finalText, SYSTEM_PROMPT, waitForIdle } from './harness'
  8. /**
  9. * The swebench-style smoke test: a real model fixes a real bug in a temp
  10. * directory using only the bash tool, and the fix is verified OUTSIDE the
  11. * agent by re-running the test script. Key-gated.
  12. */
  13. const TEST_FILE = [
  14. "const assert = require('node:assert');",
  15. "const { add } = require('./add.js');",
  16. 'assert.strictEqual(add(2, 3), 5);',
  17. 'assert.strictEqual(add(-1, 1), 0);',
  18. "console.log('PASS');",
  19. '',
  20. ].join('\n')
  21. const BUGGY_ADD = [
  22. '// A tiny module with an obvious bug.',
  23. 'function add(a, b) {',
  24. ' return a - b;',
  25. '}',
  26. 'module.exports = { add };',
  27. '',
  28. ].join('\n')
  29. let workdir: string | undefined
  30. let ctx: Context | undefined
  31. afterEach(async () => {
  32. // Dispose the harness even on failure/retry: agent-loop teardown stops the
  33. // loop and LocalBashExecutor teardown kills anything the model left running.
  34. await ctx?.fiber.dispose()
  35. ctx = undefined
  36. if (workdir !== undefined) await rm(workdir, { recursive: true, force: true })
  37. workdir = undefined
  38. })
  39. describe.skipIf(!process.env.DEEPSEEK_API_KEY)('coding task: fix a failing test via bash', () => {
  40. it('repairs add.js so node add.test.js passes', async () => {
  41. workdir = await mkdtemp(join(tmpdir(), 'dsh-coding-task-'))
  42. await writeFile(join(workdir, 'add.js'), BUGGY_ADD)
  43. await writeFile(join(workdir, 'add.test.js'), TEST_FILE)
  44. // Confirm the fixture actually fails before the agent touches it.
  45. const before = spawnSync('node', ['add.test.js'], { cwd: workdir })
  46. expect(before.status).not.toBe(0)
  47. ctx = await codingHarness(workdir)
  48. const agent = ctx.agentLoop.create('e2e-task', {
  49. model: 'deepseek-v4-flash',
  50. systemPrompt: SYSTEM_PROMPT,
  51. })
  52. agent.send([{
  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. }])
  58. await waitForIdle(ctx, agent)
  59. // The agent claims success…
  60. const summary = finalText([...agent.session.events]).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. })