Ctrl K
ComunidadGuías…

test/cascade-turn-flag.test.js

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// The CascadeTurn extraction must be invisible when its flag is off.
//
// This drives the real cascadeChat against a scripted loopback LS whose
// planner step grows in place ("A" → "AB") and whose second step arrives
// later. Both flag positions must produce the same text, the same absolute
// stepOffset, and the same number of upstream step reads. The flag-on result
// additionally carries the producer snapshot (per-step cursors + window).
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import http2 from 'node:http2';

Object.assign(process.env, {
  GRPC_PROTOCOL: 'connect',
  CASCADE_POLL_INTERVAL_MS: '10',
  CASCADE_IDLE_GRACE_MS: '1',
  CASCADE_MAX_WAIT_MS: '2000',
  CASCADE_COLD_STALL_BASE_MS: '2000',
  CASCADE_WARM_STALL_MS: '2000',
  WINDSURFAPI_USER_JWT: '0',
});
delete process.env.DEVIN_CONNECT;
delete process.env.DEVIN_ONLY;
delete process.env.WINDSURFAPI_CASCADE_TURN;

const { WindsurfClient } = await import('../src/client.js');
const { unwrapRequest, wrapEnvelope, endOfStreamEnvelope } = await import('../src/connect.js');
const {
  getField, parseFields, writeMessageField, writeStringField, writeVarintField,
} = await import('../src/proto.js');

function plannerStep(text, status = 3) {
  return Buffer.concat([
    writeVarintField(1, 15), // CORTEX_STEP_TYPE_PLANNER_RESPONSE
    writeVarintField(4, status),
    writeMessageField(20, writeStringField(1, text)),
  ]);
}

function rawStepsResponse(steps) {
  return Buffer.concat(steps.map(step => writeMessageField(1, step)));
}

function readStepOffset(payload) {
  const field = getField(parseFields(payload), 2, 0);
  return field ? Number(field.value) : 0;
}

function respond(stream, headers, body) {
  stream.respond({ ':status': 200, 'content-type': headers['content-type'] || 'application/connect+proto' });
  stream.end(Buffer.concat([wrapEnvelope(body, { compress: false }), endOfStreamEnvelope()]));
}

async function withFakeLanguageServer(handler, fn) {
  const server = http2.createServer();
  const sessions = new Set();
  server.on('session', (session) => {
    sessions.add(session);
    session.on('close', () => sessions.delete(session));
  });
  server.on('stream', handler);
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  const port = server.address().port;
  try {
    return await fn(port);
  } finally {
    for (const session of sessions) {
      try { session.close(); } catch {}
      try { session.destroy(); } catch {}
    }
    await new Promise((resolve) => server.close(resolve));
  }
}

/**
 * One scripted turn: step 0 is "A" then "AB"; step 1 is "CD". The trajectory
 * is ACTIVE until the third steps poll, then IDLE. Returns the observed
 * request offsets so the test can compare the two flag positions.
 */
async function runScriptedTurn({ turnFlag }) {
  if (turnFlag) process.env.WINDSURFAPI_CASCADE_TURN = '1';
  else delete process.env.WINDSURFAPI_CASCADE_TURN;

  const state = { stepsCalls: 0, offsets: [] };
  try {
    return await withFakeLanguageServer((stream, headers) => {
      const chunks = [];
      stream.on('data', chunk => chunks.push(chunk));
      stream.on('end', () => {
        const method = String(headers[':path']).split('/').pop();
        const payload = unwrapRequest(Buffer.concat(chunks), headers);
        let body = Buffer.alloc(0);

        if (method === 'StartCascade') body = writeStringField(1, 'flag-cascade');
        if (method === 'GetCascadeTrajectorySteps') {
          state.stepsCalls += 1;
          const offset = readStepOffset(payload);
          state.offsets.push(offset);
          let trajectory;
          if (state.stepsCalls === 1) trajectory = [plannerStep('A', 1)];
          else if (state.stepsCalls === 2) trajectory = [plannerStep('AB', 1)];
          else trajectory = [plannerStep('AB', 3), plannerStep('CD', 3)];
          body = rawStepsResponse(trajectory.slice(offset));
        }
        if (method === 'GetCascadeTrajectory') {
          body = writeVarintField(2, state.stepsCalls >= 3 ? 1 : 2); // IDLE / ACTIVE
        }
        respond(stream, headers, body);
      });
    }, async (port) => {
      const client = new WindsurfClient('test-api-key', port, 'csrf-token');
      const chunks = await client.cascadeChat(
        [{ role: 'user', content: 'grow inside a step' }], 0, 'claude-sonnet-4-6',
      );
      return {
        text: chunks.map(chunk => chunk.text || '').join(''),
        stepOffset: chunks.stepOffset,
        turnSnapshot: chunks.turnSnapshot,
        offsets: state.offsets,
        stepsCalls: state.stepsCalls,
      };
    });
  } finally {
    delete process.env.WINDSURFAPI_CASCADE_TURN;
  }
}

describe('CascadeTurn flag-off compatibility', () => {
  it('produces identical chunks and poll offsets with the flag on and off', async () => {
    const off = await runScriptedTurn({ turnFlag: false });
    const on = await runScriptedTurn({ turnFlag: true });

    assert.equal(off.text, 'ABCD', 'the default path still re-reads partial-step growth');
    assert.equal(on.text, off.text, 'the turn path emits exactly the same text');
    assert.equal(on.stepOffset, off.stepOffset, 'both report the same absolute next step');
    assert.equal(off.stepOffset, 2);
    // Poll COUNT is scheduler-dependent (10ms poll, IDLE needs a settled
    // growth window). Compare the common prefix instead of demanding the two
    // runs land on the same final tick; the flag must not change the windows.
    const common = Math.min(on.offsets.length, off.offsets.length);
    assert.ok(common >= 3, 'both runs must poll at least the three scripted windows');
    assert.deepEqual(on.offsets.slice(0, common), off.offsets.slice(0, common), 'both poll the same bounded windows');
    assert.ok(Math.abs(on.stepsCalls - off.stepsCalls) <= 1, 'the turn path does not add more than a final settling read');
    assert.equal(off.turnSnapshot, undefined, 'the default return shape has no producer state');
  });

  it('exposes the per-step cursors and overlap window on the flag-on snapshot', async () => {
    const on = await runScriptedTurn({ turnFlag: true });
    const snapshot = on.turnSnapshot;

    assert.ok(snapshot, 'flag-on turns attach the producer snapshot');
    assert.equal(snapshot.cascadeId, 'flag-cascade');
    assert.equal(snapshot.stepOffset, 2);
    assert.equal(snapshot.pollFloor, 0);
    assert.equal(snapshot.windowStart, 0, 'the next window reaches behind the unread offset');
    assert.deepEqual(snapshot.yieldedByStep, [[0, 2], [1, 2]]);
    assert.deepEqual(snapshot.thinkingByStep, []);
    assert.equal(snapshot.totalYielded, 4);
    assert.equal(snapshot.chunks.map(chunk => chunk.text || '').join(''), 'ABCD');
  });
});

/**
 * A resumed cascade (reuseEntry.stepOffset=5) starts its turn at 5. The turn
 * must clamp the overlap window to that floor, never replay steps 0-4, and
 * still report the absolute next step.
 */
async function runResumedTurn({ turnFlag }) {
  if (turnFlag) process.env.WINDSURFAPI_CASCADE_TURN = '1';
  else delete process.env.WINDSURFAPI_CASCADE_TURN;

  const offsets = [];
  try {
    return await withFakeLanguageServer((stream, headers) => {
      const chunks = [];
      stream.on('data', chunk => chunks.push(chunk));
      stream.on('end', () => {
        const method = String(headers[':path']).split('/').pop();
        const payload = unwrapRequest(Buffer.concat(chunks), headers);
        let body = Buffer.alloc(0);

        if (method === 'GetCascadeTrajectorySteps') {
          const offset = readStepOffset(payload);
          offsets.push(offset);
          if (offset === 5) body = rawStepsResponse([plannerStep('R5'), plannerStep('R6')]);
        }
        if (method === 'GetCascadeTrajectory') body = writeVarintField(2, 1); // IDLE
        respond(stream, headers, body);
      });
    }, async (port) => {
      const client = new WindsurfClient('test-api-key', port, 'csrf-token');
      const chunks = await client.cascadeChat(
        [{ role: 'user', content: 'continue' }], 0, 'claude-sonnet-4-6',
        {
          reuseEntry: {
            cascadeId: 'resume-cascade',
            sessionId: 'resume-session',
            lsPort: port,
            apiKey: 'test-api-key',
            stepOffset: 5,
            generatorOffset: 5,
          },
        },
      );
      return {
        text: chunks.map(chunk => chunk.text || '').join(''),
        stepOffset: chunks.stepOffset,
        turnSnapshot: chunks.turnSnapshot,
        offsets,
      };
    });
  } finally {
    delete process.env.WINDSURFAPI_CASCADE_TURN;
  }
}

describe('CascadeTurn flag-on resume floor', () => {
  it('starts a resumed turn at reuseEntry.stepOffset and never polls behind it', async () => {
    const off = await runResumedTurn({ turnFlag: false });
    const on = await runResumedTurn({ turnFlag: true });

    assert.equal(off.text, 'R5R6');
    assert.equal(on.text, off.text);
    assert.equal(on.stepOffset, off.stepOffset);
    assert.equal(on.stepOffset, 7);
    const common = Math.min(on.offsets.length, off.offsets.length);
    assert.ok(common >= 3, 'both runs must poll at least the scripted windows');
    assert.deepEqual(on.offsets.slice(0, common), off.offsets.slice(0, common));
    assert.ok(on.offsets.every(offset => offset === 5), `polled behind the floor: ${on.offsets.join(',')}`);

    assert.equal(on.turnSnapshot.pollFloor, 5);
    assert.equal(on.turnSnapshot.stepOffset, 7);
    assert.equal(on.turnSnapshot.windowStart, 5, 'the overlap is clamped to the resume floor');
    assert.deepEqual(on.turnSnapshot.yieldedByStep, [[5, 2], [6, 2]]);
  });
});