mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-25 08:19:56 +00:00
* feat(cua): preload the JavaScript computer use runtime * refactor(cua): remove the native Wayland backend * feat(cua): move app computer use into the driver * feat(cua): preserve background delivery and port platform fixes * feat(cua): add explicit desktop input and browser runtime facade * feat(cua): package the unified 0.26.0 computer use runtime * fix(cua): finalize cross-platform native reliability * fix(cua): isolate runtime releases from CLI versioning * fix(cua): keep Electron out of root workspace installs * fix(cua): synchronize consumers and async cleanup tests * fix(browser): migrate smoke to preloaded cua runtime * docs(cua): document MCP setup for Qwen Code Claude Code and Codex
122 lines
3.9 KiB
TypeScript
122 lines
3.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Qwen
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import fs from 'node:fs';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { NodeReplKernelManager } from './kernel-manager.js';
|
|
import { NodeReplSecurityPolicy } from './security-policy.js';
|
|
|
|
// Regression: reported stack line numbers must match the source the caller
|
|
// wrote, and must NOT drift as session bindings accumulate. The generated
|
|
// prelude is one physical line (compensated by lineOffset: -1) and the
|
|
// per-statement snapshot markers are newline-free.
|
|
|
|
const managers: NodeReplKernelManager[] = [];
|
|
|
|
function makeManager(): NodeReplKernelManager {
|
|
const manager = new NodeReplKernelManager({
|
|
cwd: process.cwd(),
|
|
homeDir: os.homedir(),
|
|
tmpRootDir: fs.mkdtempSync(path.join(os.tmpdir(), 'qwen-node-repl-line-')),
|
|
policy: NodeReplSecurityPolicy.default(),
|
|
readableRoots: [process.cwd()],
|
|
});
|
|
managers.push(manager);
|
|
return manager;
|
|
}
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(managers.splice(0).map((manager) => manager.dispose()));
|
|
});
|
|
|
|
/** First `<cellfile>:<line>:<col>` frame from the error stack. */
|
|
function reportedLine(stack: string | undefined): number | null {
|
|
if (!stack) return null;
|
|
const match = stack.match(/_cell_[^\s:]*:(\d+):\d+/);
|
|
return match?.[1] ? Number(match[1]) : null;
|
|
}
|
|
|
|
describe('cell stack line fidelity', () => {
|
|
it('reports the correct line with no prior bindings', async () => {
|
|
const manager = makeManager();
|
|
// throw is on source line 1
|
|
const outcome = await manager.exec({
|
|
code: 'throw new Error("boom");',
|
|
timeoutMs: 30_000,
|
|
});
|
|
expect(outcome.status).toBe('error');
|
|
expect(reportedLine(outcome.error?.stack)).toBe(1);
|
|
});
|
|
|
|
it('reports the correct line further down a cell', async () => {
|
|
const manager = makeManager();
|
|
const code = ['const a = 1;', 'const b = 2;', 'throw new Error("deep");'];
|
|
const outcome = await manager.exec({
|
|
code: code.join('\n'),
|
|
timeoutMs: 30_000,
|
|
});
|
|
expect(outcome.status).toBe('error');
|
|
// throw is on source line 3
|
|
expect(reportedLine(outcome.error?.stack)).toBe(3);
|
|
});
|
|
|
|
it('does not drift as accumulated bindings grow', async () => {
|
|
const manager = makeManager();
|
|
// Build up many live bindings across several cells.
|
|
for (let i = 0; i < 12; i++) {
|
|
const ok = await manager.exec({
|
|
code: `const v${i} = ${i};`,
|
|
timeoutMs: 30_000,
|
|
});
|
|
expect(ok.status).toBe('ok');
|
|
}
|
|
// Same shape as the earlier test: throw on source line 3.
|
|
const outcome = await manager.exec({
|
|
code: ['const x = 1;', 'const y = 2;', 'throw new Error("late");'].join(
|
|
'\n',
|
|
),
|
|
timeoutMs: 30_000,
|
|
});
|
|
expect(outcome.status).toBe('error');
|
|
expect(reportedLine(outcome.error?.stack)).toBe(3);
|
|
});
|
|
|
|
it('keeps imported-module line numbers correct (no cell offset leakage)', async () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'qwen-node-repl-lm-'));
|
|
const modulePath = path.join(dir, 'thrower.mjs');
|
|
// The throw sits on line 4 of the imported file.
|
|
fs.writeFileSync(
|
|
modulePath,
|
|
[
|
|
'// line 1',
|
|
'// line 2',
|
|
'export function boom() {',
|
|
' throw new Error("inner");',
|
|
'}',
|
|
].join('\n'),
|
|
);
|
|
const manager = makeManager();
|
|
try {
|
|
const outcome = await manager.exec({
|
|
code: [
|
|
`const m = await import(${JSON.stringify(modulePath)});`,
|
|
'm.boom();',
|
|
].join('\n'),
|
|
timeoutMs: 30_000,
|
|
});
|
|
expect(outcome.status).toBe('error');
|
|
const stack = outcome.error?.stack ?? '';
|
|
// The imported module has no prelude and no lineOffset.
|
|
expect(stack).toMatch(/thrower\.mjs:4:/);
|
|
// The cell call-site is on cell source line 2.
|
|
expect(reportedLine(stack)).toBe(2);
|
|
} finally {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|