agent-docs-port.test.js 3.75 KB
/**
 * agent 提示词里印的端口必须是真的。
 *
 * 为什么要一条守卫:`packages/agent-core/base/` 下那几份是**所有 agent 的工具说明**
 * (composed 进每个 agent 的提示词),里面印着 `curl http://localhost:3000/api/agent/...`,
 * 而实际端口是 **3010**(server/config.js)。agent 照着打就连不上,而它拿到的是
 * 连接被拒、不是一句「端口错了」—— 它多半会去猜别的原因。
 *
 * 这不是假想:批 5 回填出来的历史派单正文里,有人手写了
 * 「完成后调用 report_task 汇报给 innovator(端口 3010,不是 3000)」来纠正它 ——
 * 说明这个错误已经真咬过人,而且是靠人肉在每条任务书里打补丁。
 *
 * 判据刻意宽松:只禁 `localhost:3000`(那个确定是错的),不去校验「必须等于
 * config.port」—— 端口可被 PORT 覆盖,写死校验反而会在部署环境里误报。
 */
import { describe, it, expect } from 'vitest';
import { createRequire } from 'module';
import fs from 'fs';
import path from 'path';
const require_ = createRequire(import.meta.url);

const ROOT = path.resolve(__dirname, '../../..');
const SCAN = ['packages/agent-core/base', 'system/agents'];
// agent 目录下会长出运行态产物:Python venv、node_modules、下载的浏览器驱动……
// 那些是**第三方文件,不是提示词**,扫进来只会拿 playwright 的 ThirdPartyNotices 当违例
// (实测第一版就是这么红的)。守卫要盯的是人写给 agent 看的东西。
const SKIP_DIR = /(^|\/)(node_modules|\.git|dist|coverage|venv|\.venv|site-packages|__pycache__|workspace|conversations|train)(\/|$)/;

function walk(dir, out = []) {
  if (!fs.existsSync(dir) || SKIP_DIR.test(dir)) return out;
  // existsSync 与 readdirSync 之间同样有窗口(理由见下面读文件那处)——存在性检查不是锁。
  let entries;
  try {
    entries = fs.readdirSync(dir, { withFileTypes: true });
  } catch (e) {
    if (e.code === 'ENOENT') return out;
    throw e;
  }
  for (const e of entries) {
    const full = path.join(dir, e.name);
    if (e.isDirectory()) walk(full, out);
    else if (/\.(md|json|txt)$/.test(e.name)) out.push(full);
  }
  return out;
}

describe('agent 提示词里的端口', () => {
  const files = SCAN.flatMap((d) => walk(path.join(ROOT, d)));

  it('扫描样本非空 —— 防止路径写错时本测试退化成永远通过', () => {
    // 少了这条前置断言,SCAN 里的路径一旦写错就会以「零违例」的形式全绿,
    // 而一条永远通过的守卫和没有守卫是一回事。
    expect(files.length).toBeGreaterThan(50);
  });

  it('不许再出现 localhost:3000(实际端口是 3010)', () => {
    const offenders = [];
    for (const f of files) {
      // 扫的是活目录:walk 列出名字之后、读到它之前,别的用例可能正好把自己的运行态
      // 目录清掉(`zz-*` 那几个夹具就是这么干的)→ ENOENT。**只放过「文件没了」这一种**,
      // 其它读错误照抛 —— 一律 try/catch 吞掉的话,权限错、编码错也会被当成「无违例」。
      let src;
      try {
        src = fs.readFileSync(f, 'utf-8');
      } catch (e) {
        if (e.code === 'ENOENT') continue;
        throw e;
      }
      src.split('\n').forEach((line, i) => {
        if (line.includes('localhost:3000') || line.includes('127.0.0.1:3000')) {
          offenders.push(`${path.relative(ROOT, f)}:${i + 1} → ${line.trim().slice(0, 90)}`);
        }
      });
    }
    expect(offenders).toEqual([]);
  });

  it('config.port 的缺省确实是 3010(守卫的前提,变了要先改这里)', () => {
    const cfg = require_('../../config');
    expect(cfg.port).toBe(3010);
  });
});