mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-11 17:55:44 +00:00
fix(integration-tests): make the project typecheckable and fix what that found (#8693)
* fix(integration-tests): make the project typecheckable and fix what that found `tsc -p integration-tests/tsconfig.json` could not run at all. The config carried a `"//"` documentation key inside `compilerOptions.paths`, and every value there must be an array, so tsc aborted with TS5063 before checking a single file. Nothing in CI runs it either, so the directory has been unchecked for its whole life -- which is how PR #8620 shipped an `integration-tests/cli/qwen-serve-streaming.test.ts` that referenced an undeclared `REPO_ROOT`, swallowed the ReferenceError in a bare catch, and reported a green skip for a security regression test. Moving that note out of `paths` exposed 404 errors. Three more config defects accounted for 353 of them: - `composite: true` is inherited from the root config for the packages that are actually referenced. Composite requires every file in the program to appear in `include`, and these tests import package sources by relative path, so it produced 324 TS6307. Nothing references this project and it emits nothing, so it is now `composite: false`. - The root `lib` is ES2023 only. The suite drives browser-side code in `terminal-capture/` and pulls SDK sources that name `WebSocket` and `HeadersInit`, so 21 identifiers resolved to nothing. Now DOM + DOM.Iterable + ES2023, matching packages/cli. - Workspace packages resolved through `packages/core/dist` via a project reference, so with core unbuilt the checker reported a dozen members as missing from `Storage` that are right there in the source. They now resolve from source through `paths`, mirroring packages/cli, and the reference is gone. node-pty declares `types` at the top level but its `exports` map is a bare string with no `types` condition, so nodenext never reached the declarations and every pty handle degraded to `any` -- which is what silently untyped the `data` and `exitCode` callbacks in test-helper.ts. It now resolves through `paths` as well. `@types/jsdom` is added for the one file that uses it; DefinitelyTyped has no release matching jsdom 26 (it jumps 21 -> 27), so this pins the current 28.x. Two real defects fell out of the remaining 51: - write_file.test.ts built a detailed tool-call failure message and passed it to `toBeTruthy()`, which takes no arguments. It was discarded on every failure, leaving only a bare literal. - Two terminal-capture scenarios set `gif: true` inside `streaming`, where the runner never reads it. It is a scenario-level switch. The rest was making an existing `undefined` visible. `readToolLogs()` promised `name: string` for fields copied straight out of telemetry attributes that nothing validates; the stdout fallback can promise them, the telemetry branch cannot, and claiming otherwise just moved the `undefined` past the type checker into the assertions. This is type resolution only. `integration-tests/vitest.config.ts` keeps its own hardcoded aliases onto the built SDK bundle, so the suite still exercises the published-bundle shape at runtime. Not wired into CI here, but not for cost reasons: a cold run of `tsc -p integration-tests/tsconfig.json` takes about 106s on an idle developer box. The program is 2679 files, of which 103 are integration tests and roughly 1100 are package sources their own projects already check, so there is duplicated work available to reclaim by resolving the packages from their built declarations -- but at ~106s it is already cheap enough to gate on as-is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(integration-tests): isolate jsdom types and complete source-resolution paths Address review round 1: - external-context: override `types` to ["node"]. The root @types/jsdom entered its program through vitest's optional jsdom types and injected lib dom, flipping @types/node's fetch globals to DOM variants whose ReadableStream is not async-iterable (TS2504 in http-client.ts), which failed every CI job during the npm ci prepare build. - integration-tests tsconfig: explicit nodenext paths entries for every workspace subpath the program imports (sdk/daemon, 19 acp-bridge subpaths, core goalWire/memoryScopes/userPromptSubmitContext, webui daemon-react-sdk, channel-base); drop the dead `*` wildcards; include **/*.tsx. Typechecks green with the source packages' dists removed. - Relax noPropertyAccessFromIndexSignature in integration-tests and revert the six bracket-access rewrites it forced in SDK sources. - channel-plugin: import channels/base from src and map @qwen-code/channel-base to source so both declarations agree. - qwen-serve-streaming: asAccepted delegates to the SDK's exported isNonBlockingAccepted type predicate instead of a drifted copy. - sleep-interception: tighten blocked predicates to success === false and fix the comment describing them. - Declare jsdom at the root next to @types/jsdom. * fix(integration-tests): complete source-resolution paths and restore single channel-base instance Address review round 2: - Map the eight builtin channel adapters and web-templates to source. channel-registry.ts and html.ts still resolved them through their exports maps to dist, so the typecheck's build-independence was incomplete: on a tree without built dists it failed with the exact 9 x TS2307 the maintainer verification measured. - channel-plugin.test.ts: import @qwen-code/channel-base by bare specifier instead of a relative src path. At runtime the test and plugin-example now resolve the same dist/index.js through the exports map, restoring the single ChannelBase / SessionRouter instance the relative src import silently split; type resolution still maps to source through paths, and vitest.config.ts keeps pointing e2e runs at the built bundles. --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
This commit is contained in:
parent
bb8f2c0129
commit
3037744602
19 changed files with 316 additions and 61 deletions
|
|
@ -31,16 +31,15 @@ import { fileURLToPath } from 'node:url';
|
|||
import { mkdirSync } from 'node:fs';
|
||||
|
||||
// Import from the monorepo channel packages
|
||||
import {
|
||||
AcpBridge,
|
||||
SessionRouter,
|
||||
} from '../packages/channels/base/dist/index.js';
|
||||
import type { ChannelConfig } from '../packages/channels/base/dist/index.js';
|
||||
import { AcpBridge, SessionRouter } from '@qwen-code/channel-base';
|
||||
import {
|
||||
MockPluginChannel,
|
||||
createMockServer,
|
||||
} from '../packages/channels/plugin-example/src/index.js';
|
||||
import type { MockServerHandle } from '../packages/channels/plugin-example/src/index.js';
|
||||
import type {
|
||||
MockServerHandle,
|
||||
MockPluginConfig,
|
||||
} from '../packages/channels/plugin-example/src/index.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const CLI_PATH = join(__dirname, '..', 'dist', 'cli.js');
|
||||
|
|
@ -74,7 +73,9 @@ describe('Channel Plugin (Mock WebSocket E2E)', () => {
|
|||
await bridge.start();
|
||||
|
||||
// 3. Create and connect MockPluginChannel via WebSocket
|
||||
const config: ChannelConfig & Record<string, unknown> = {
|
||||
// MockPluginConfig, not ChannelConfig: the constructor below requires
|
||||
// `serverWsUrl`, and typing the literal as the base interface erased it.
|
||||
const config: MockPluginConfig & Record<string, unknown> = {
|
||||
type: 'plugin-example',
|
||||
token: '',
|
||||
senderPolicy: 'open',
|
||||
|
|
@ -82,6 +83,7 @@ describe('Channel Plugin (Mock WebSocket E2E)', () => {
|
|||
sessionScope: 'user',
|
||||
cwd: testDir,
|
||||
groupPolicy: 'disabled',
|
||||
dmPolicy: 'open',
|
||||
groups: {},
|
||||
serverWsUrl: server.wsUrl,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ describe('file-system', () => {
|
|||
const readAttempt = toolLogs.find(
|
||||
(log) =>
|
||||
log.toolRequest.name === 'read_file' &&
|
||||
log.toolRequest.args.includes(fileName),
|
||||
log.toolRequest.args?.includes(fileName),
|
||||
);
|
||||
const editAttempt = toolLogs.find(
|
||||
(log) => log.toolRequest.name === 'edit_file',
|
||||
|
|
|
|||
|
|
@ -83,9 +83,9 @@ const expectNoSuccessfulRawNotebookWrites = (
|
|||
.readToolLogs()
|
||||
.filter(
|
||||
(log) =>
|
||||
['edit', 'write_file'].includes(log.toolRequest.name) &&
|
||||
['edit', 'write_file'].includes(log.toolRequest.name ?? '') &&
|
||||
log.toolRequest.success &&
|
||||
log.toolRequest.args.includes(notebookFileName),
|
||||
log.toolRequest.args?.includes(notebookFileName),
|
||||
);
|
||||
|
||||
expect(rawNotebookWrites).toEqual([]);
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|||
import { isPathWithinRoot } from '@qwen-code/qwen-code-core';
|
||||
import { DaemonClient, parseSseStream } from '@qwen-code/sdk';
|
||||
import type { DaemonEvent, DaemonSessionSummary } from '@qwen-code/sdk';
|
||||
import {
|
||||
isNonBlockingAccepted,
|
||||
type NonBlockingPromptAccepted,
|
||||
} from '@qwen-code/sdk/daemon';
|
||||
import {
|
||||
fakeToolCall,
|
||||
startFakeOpenAIServer,
|
||||
|
|
@ -135,6 +139,12 @@ function findExternalReadBase(): string | undefined {
|
|||
|
||||
const externalReadBase = findExternalReadBase();
|
||||
|
||||
function asAccepted(
|
||||
result: Awaited<ReturnType<DaemonClient['promptNonBlocking']>>,
|
||||
): NonBlockingPromptAccepted | undefined {
|
||||
return isNonBlockingAccepted(result) ? result : undefined;
|
||||
}
|
||||
|
||||
let daemon: ChildProcess;
|
||||
let port = 0;
|
||||
let base = '';
|
||||
|
|
@ -619,11 +629,13 @@ describePOSIX('qwen serve — same-host external text reads', () => {
|
|||
const requestStart = fakeServer.requests.length;
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
const accepted = await client.promptNonBlocking(session.sessionId, {
|
||||
prompt: [{ type: 'text', text: marker }],
|
||||
});
|
||||
expect('promptId' in accepted).toBe(true);
|
||||
if (!('promptId' in accepted)) return;
|
||||
const accepted = asAccepted(
|
||||
await client.promptNonBlocking(session.sessionId, {
|
||||
prompt: [{ type: 'text', text: marker }],
|
||||
}),
|
||||
);
|
||||
expect(accepted).toBeDefined();
|
||||
if (!accepted) return;
|
||||
promptId = accepted.promptId;
|
||||
|
||||
await expect.poll(findReadPermission, { timeout: 30_000 }).toBeDefined();
|
||||
|
|
@ -766,11 +778,13 @@ describePOSIX('qwen serve — daemon Todo Stop Guard replay', () => {
|
|||
});
|
||||
const requestStart = fakeServer.requests.length;
|
||||
const guardMarker = `todo-guard-e2e-${requestStart}`;
|
||||
const accepted = await client.promptNonBlocking(session.sessionId, {
|
||||
prompt: [{ type: 'text', text: guardMarker }],
|
||||
});
|
||||
expect('promptId' in accepted).toBe(true);
|
||||
if (!('promptId' in accepted)) return;
|
||||
const accepted = asAccepted(
|
||||
await client.promptNonBlocking(session.sessionId, {
|
||||
prompt: [{ type: 'text', text: guardMarker }],
|
||||
}),
|
||||
);
|
||||
expect(accepted).toBeDefined();
|
||||
if (!accepted) return;
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
|
|
|
|||
|
|
@ -157,16 +157,19 @@ describe('qwen serve WebUI live journal recovery', () => {
|
|||
root = createRoot(container);
|
||||
await act(async () => {
|
||||
root?.render(
|
||||
createElement(
|
||||
DaemonSessionProvider,
|
||||
{
|
||||
autoConnect: true,
|
||||
baseUrl: activeDaemon!.base,
|
||||
token: activeDaemon!.token,
|
||||
sessionId: created.sessionId,
|
||||
},
|
||||
createElement(Harness),
|
||||
),
|
||||
// `children` is the one required prop on DaemonSessionProviderProps,
|
||||
// and a trailing createElement argument does not satisfy it — the
|
||||
// call only type checks with children in the props object. The lint
|
||||
// rule guards JSX readability, which does not apply in this .ts file
|
||||
// where createElement is already being called by hand.
|
||||
// eslint-disable-next-line react/no-children-prop
|
||||
createElement(DaemonSessionProvider, {
|
||||
autoConnect: true,
|
||||
baseUrl: activeDaemon!.base,
|
||||
token: activeDaemon!.token,
|
||||
sessionId: created.sessionId,
|
||||
children: createElement(Harness),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,12 @@ describe('sleep-interception', () => {
|
|||
}
|
||||
});
|
||||
|
||||
// Mirrors the optionality of the parsed telemetry these come from: a
|
||||
// malformed record yields `undefined` rather than a crash. The predicates
|
||||
// below only match an explicit `success` boolean.
|
||||
type ShellCall = {
|
||||
args: string;
|
||||
success: boolean;
|
||||
args?: string;
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
|
|
@ -67,7 +70,7 @@ describe('sleep-interception', () => {
|
|||
);
|
||||
|
||||
const foundBlockedCall = await waitForShellCall(
|
||||
(call) => call.args.includes('sleep 5') && !call.success,
|
||||
(call) => !!call.args?.includes('sleep 5') && call.success === false,
|
||||
);
|
||||
|
||||
if (!foundBlockedCall) {
|
||||
|
|
@ -85,7 +88,7 @@ describe('sleep-interception', () => {
|
|||
// error attribute is only available from file-based telemetry; the
|
||||
// podman stdout fallback leaves it undefined.
|
||||
const blockedCall = shellCalls().find(
|
||||
(call) => call.args.includes('sleep 5') && !call.success,
|
||||
(call) => !!call.args?.includes('sleep 5') && call.success === false,
|
||||
);
|
||||
if (blockedCall?.error !== undefined) {
|
||||
expect(blockedCall.error).toContain('Monitor');
|
||||
|
|
@ -107,7 +110,7 @@ describe('sleep-interception', () => {
|
|||
);
|
||||
|
||||
const foundSuccessfulCall = await waitForShellCall(
|
||||
(call) => call.args.includes('sleep 1') && call.success,
|
||||
(call) => !!call.args?.includes('sleep 1') && call.success === true,
|
||||
);
|
||||
|
||||
if (!foundSuccessfulCall) {
|
||||
|
|
@ -140,7 +143,8 @@ describe('sleep-interception', () => {
|
|||
// The escape hatch worked iff a call carrying the intentional-sleep
|
||||
// comment completed successfully.
|
||||
const foundIntentionalCall = await waitForShellCall(
|
||||
(call) => call.args.includes('intentional-sleep') && call.success,
|
||||
(call) =>
|
||||
!!call.args?.includes('intentional-sleep') && call.success === true,
|
||||
);
|
||||
|
||||
if (!foundIntentionalCall) {
|
||||
|
|
@ -175,7 +179,7 @@ describe('sleep-interception', () => {
|
|||
);
|
||||
|
||||
const foundBlockedCall = await waitForShellCall(
|
||||
(call) => call.args.includes('sleep 5') && !call.success,
|
||||
(call) => !!call.args?.includes('sleep 5') && call.success === false,
|
||||
);
|
||||
|
||||
if (!foundBlockedCall) {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,12 @@ describe.skip('stdin context', () => {
|
|||
const lastRequest = rig.readLastApiRequest();
|
||||
expect(lastRequest).not.toBeNull();
|
||||
|
||||
const historyString = lastRequest.attributes.request_text;
|
||||
// `expect(...).not.toBeNull()` is a runtime check; it does not narrow the
|
||||
// type. Assert the shape explicitly so the `indexOf` calls below are not
|
||||
// reaching into `unknown`.
|
||||
const historyString = String(
|
||||
lastRequest?.attributes?.['request_text'] ?? '',
|
||||
);
|
||||
|
||||
// TODO: This test currently fails in sandbox mode (Docker/Podman) because
|
||||
// stdin content is not properly forwarded to the container when used
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ Use the todo_write tool to create this list.`;
|
|||
expect(todoWriteCalls.length).toBeGreaterThan(0);
|
||||
|
||||
// Parse the arguments to verify they contain our tasks
|
||||
const todoArgs = JSON.parse(todoWriteCalls[0].toolRequest.args);
|
||||
const todoArgs = JSON.parse(todoWriteCalls[0].toolRequest.args ?? '{}');
|
||||
|
||||
expect(todoArgs.todos).toBeDefined();
|
||||
expect(Array.isArray(todoArgs.todos)).toBe(true);
|
||||
|
|
|
|||
|
|
@ -28,13 +28,17 @@ describe('write_file', () => {
|
|||
}
|
||||
|
||||
const allTools = rig.readToolLogs();
|
||||
expect(foundToolCall, 'Expected to find a write_file tool call').toBeTruthy(
|
||||
// The detailed message belongs on `expect`, not on `toBeTruthy` — the
|
||||
// latter takes no arguments, so this diagnostic was being built and
|
||||
// discarded on every failure, leaving only the bare literal.
|
||||
expect(
|
||||
foundToolCall,
|
||||
createToolCallErrorMessage(
|
||||
'write_file',
|
||||
allTools.map((t) => t.toolRequest.name),
|
||||
result,
|
||||
),
|
||||
);
|
||||
).toBeTruthy();
|
||||
|
||||
// Validate model output - will throw if no output, warn if missing expected content
|
||||
validateModelOutput(result, 'dad.txt', 'Write file test');
|
||||
|
|
|
|||
|
|
@ -1427,11 +1427,8 @@ describe('Hooks System Integration', () => {
|
|||
});
|
||||
|
||||
// When Stop hooks block, agent continues execution normally (with max turns to prevent infinite loop)
|
||||
const _result = await rig.run(
|
||||
'Say all block',
|
||||
'--max-session-turns',
|
||||
'3',
|
||||
);
|
||||
// The run is the subject of the assertions below; its output is not.
|
||||
await rig.run('Say all block', '--max-session-turns', '3');
|
||||
|
||||
// Verify Stop hook was invoked multiple times (indicating multiple rounds)
|
||||
const hookInvokeCount = rig
|
||||
|
|
|
|||
|
|
@ -98,7 +98,9 @@ describe('terminal-bench integration', () => {
|
|||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const available = new Set(baseTestTasks.map((t) => t));
|
||||
// Set<string>, not Set<of the literal union>: the whole point is to test
|
||||
// arbitrary env-supplied ids for membership.
|
||||
const available = new Set<string>(baseTestTasks);
|
||||
const unknown = selected.filter((s) => !available.has(s));
|
||||
if (unknown.length > 0) {
|
||||
throw new Error(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ export default {
|
|||
name: 'streaming-bugfix-2833',
|
||||
spawn: ['node', 'dist/cli.js', '--yolo'],
|
||||
terminal: { title: 'qwen-code', cwd: '../../..' },
|
||||
// Generate an animated GIF. This is a scenario-level switch (see
|
||||
// ScenarioConfig); it used to sit inside `streaming` below, where the runner
|
||||
// never read it.
|
||||
gif: true,
|
||||
flow: [
|
||||
{
|
||||
type: '/qc:bugfix https://github.com/QwenLM/qwen-code/issues/2833',
|
||||
|
|
@ -17,7 +21,6 @@ export default {
|
|||
delayMs: 10000, // Wait 10s for initial prompt processing
|
||||
intervalMs: 30000, // Capture every 30 seconds
|
||||
count: 50, // Up to 25 minutes of capture (50 * 30s)
|
||||
gif: true, // Generate animated GIF
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ export default {
|
|||
name: 'pr-2371-review',
|
||||
spawn: ['node', 'dist/cli.js', '--yolo'],
|
||||
terminal: { title: 'qwen-code', cwd: '../../..' },
|
||||
// `gif` is a scenario-level switch (see ScenarioConfig). It used to sit
|
||||
// inside `streaming` below, where the runner never read it.
|
||||
gif: true,
|
||||
flow: [
|
||||
{
|
||||
type: '/review https://github.com/QwenLM/qwen-code/pull/2371',
|
||||
|
|
@ -11,7 +14,6 @@ export default {
|
|||
delayMs: 5000,
|
||||
intervalMs: 10000, // Every 10s
|
||||
count: 60, // 10 minutes total (60 * 10s)
|
||||
gif: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@
|
|||
* QWEN_TUI_E2E_OUT output dir (default under os.tmpdir())
|
||||
* QWEN_TUI_E2E_REPO repo root whose dist/cli.js is launched
|
||||
*/
|
||||
import { createServer, type AddressInfo } from 'node:http';
|
||||
import { createServer } from 'node:http';
|
||||
// AddressInfo is declared by node:net, not node:http.
|
||||
import type { AddressInfo } from 'node:net';
|
||||
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { basename, dirname, join, resolve } from 'node:path';
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@ function sanitizeTestName(name: string) {
|
|||
// Helper to create detailed error messages
|
||||
export function createToolCallErrorMessage(
|
||||
expectedTools: string | string[],
|
||||
foundTools: string[],
|
||||
// Callers build this by mapping `toolRequest.name` over the parsed
|
||||
// telemetry, where the name is optional. This is a failure message, so a
|
||||
// missing entry should print as `undefined` rather than force every call
|
||||
// site to filter first.
|
||||
foundTools: Array<string | undefined>,
|
||||
result: string,
|
||||
) {
|
||||
const expectedStr = Array.isArray(expectedTools)
|
||||
|
|
@ -171,6 +175,10 @@ interface ParsedLog {
|
|||
duration_ms?: number;
|
||||
status?: string;
|
||||
'error.message'?: string;
|
||||
// Telemetry carries far more attributes than the tool-call subset named
|
||||
// above; callers reach them by key (`attributes['request_text']`). Every
|
||||
// value is `unknown` because nothing validates the payload shape.
|
||||
[key: string]: unknown;
|
||||
};
|
||||
scopeMetrics?: {
|
||||
metrics: {
|
||||
|
|
@ -810,12 +818,17 @@ export class TestRig {
|
|||
}
|
||||
|
||||
const parsedLogs = this._readAndParseTelemetryLog();
|
||||
// Every field is optional because it is copied straight out of the
|
||||
// telemetry attributes, which nothing validates. The stdout fallback above
|
||||
// reconstructs the same fields from a regex and can promise them; this
|
||||
// branch cannot, and claiming otherwise just moved the `undefined` past
|
||||
// the type checker into the assertions.
|
||||
const logs: {
|
||||
toolRequest: {
|
||||
name: string;
|
||||
args: string;
|
||||
success: boolean;
|
||||
duration_ms: number;
|
||||
name?: string;
|
||||
args?: string;
|
||||
success?: boolean;
|
||||
duration_ms?: number;
|
||||
status?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
|
@ -844,7 +857,9 @@ export class TestRig {
|
|||
return logs;
|
||||
}
|
||||
|
||||
readLastApiRequest(): Record<string, unknown> | null {
|
||||
// Returns the parsed log, not a bare record: callers want `.attributes`,
|
||||
// and `Record<string, unknown>` hid that the value already has a shape.
|
||||
readLastApiRequest(): ParsedLog | null {
|
||||
const logs = this._readAndParseTelemetryLog();
|
||||
const apiRequests = logs.filter(
|
||||
(logData) =>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,159 @@
|
|||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"allowJs": true,
|
||||
// Nothing references this project and it emits nothing, but the root
|
||||
// config turns `composite` on for the packages that do. Composite demands
|
||||
// that every file in the program appear in `include`, and these tests
|
||||
// import package sources across the repo by relative path, so inheriting
|
||||
// it produced 300+ TS6307 "not listed within the file list" errors.
|
||||
"composite": false,
|
||||
// The root turns `noPropertyAccessFromIndexSignature` on, which forced
|
||||
// bracket-access rewrites in production SDK sources just to satisfy this
|
||||
// test program (packages/desktop already sets it false). Relax it here so
|
||||
// packages keep their own compiler regime and the tests keep dot access.
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
// Matches packages/cli. The suite drives browser-side code in
|
||||
// `terminal-capture/` and pulls SDK sources that reference `WebSocket` /
|
||||
// `HeadersInit`, none of which exist in the root's ES2023-only lib.
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2023"],
|
||||
"baseUrl": ".",
|
||||
// Resolve workspace packages from source so `tsc -p` here does not depend
|
||||
// on whether someone had built them recently — a missing dist used to fail
|
||||
// resolution outright and a stale one silently typechecked against old
|
||||
// declarations. nodenext does no extension or index probing on
|
||||
// substituted paths, so every subpath the program imports needs an
|
||||
// explicit entry naming its source file; a bare wildcard falls through to
|
||||
// the package exports map, i.e. back to dist. Keep these in sync with the
|
||||
// packages' exports maps. The runtime vitest aliases
|
||||
// (`integration-tests/vitest.config.ts`) still point at the built SDK
|
||||
// bundle to exercise the published-bundle shape; these entries only affect
|
||||
// type resolution.
|
||||
//
|
||||
// Keep notes like this OUT of `paths` itself: every value there must be an
|
||||
// array, so a `"//"` string key makes tsc abort with TS5063 before it type
|
||||
// checks a single file — which is how this project silently went unchecked.
|
||||
"paths": {
|
||||
"//": "Resolve types from SDK source rather than dist so `tsc -p` here does not require a fresh `npm run build` of the SDK package before checking integration tests. The runtime vitest alias (`integration-tests/vitest.config.ts`) still points at the built `dist/index.mjs` to exercise the published-bundle shape; this paths entry only affects type resolution.",
|
||||
"@qwen-code/sdk": ["../packages/sdk-typescript/src/index.ts"]
|
||||
// These tests import package sources by relative path
|
||||
// (`../../packages/cli/src/...`), so those files get checked here too
|
||||
// and must resolve their own imports.
|
||||
"@qwen-code/qwen-code-core": ["../packages/core/src/index.ts"],
|
||||
"@qwen-code/qwen-code-core/transcriptRecords": [
|
||||
"../packages/core/src/utils/transcript-records.ts"
|
||||
],
|
||||
"@qwen-code/qwen-code-core/goalWire": [
|
||||
"../packages/core/src/goals/goal-wire.ts"
|
||||
],
|
||||
"@qwen-code/qwen-code-core/memoryScopes": [
|
||||
"../packages/core/src/memory/scopes.ts"
|
||||
],
|
||||
"@qwen-code/qwen-code-core/userPromptSubmitContext": [
|
||||
"../packages/core/src/hooks/user-prompt-submit-context.ts"
|
||||
],
|
||||
"@qwen-code/sdk": ["../packages/sdk-typescript/src/index.ts"],
|
||||
"@qwen-code/sdk/daemon": [
|
||||
"../packages/sdk-typescript/src/daemon/index.ts"
|
||||
],
|
||||
"@qwen-code/sdk/daemon/transcript": [
|
||||
"../packages/sdk-typescript/src/daemon/transcript.ts"
|
||||
],
|
||||
"@qwen-code/sdk/daemon/transports": [
|
||||
"../packages/sdk-typescript/src/daemon/transports.ts"
|
||||
],
|
||||
"@qwen-code/sdk/daemon/types": [
|
||||
"../packages/sdk-typescript/src/daemon/types.ts"
|
||||
],
|
||||
"@qwen-code/sdk/daemon/ui/transcript": [
|
||||
"../packages/sdk-typescript/src/daemon/ui/transcript.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge": ["../packages/acp-bridge/src/index.ts"],
|
||||
"@qwen-code/acp-bridge/bridge": ["../packages/acp-bridge/src/bridge.ts"],
|
||||
"@qwen-code/acp-bridge/bridgeClient": [
|
||||
"../packages/acp-bridge/src/bridgeClient.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/bridgeErrors": [
|
||||
"../packages/acp-bridge/src/bridgeErrors.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/bridgeFileSystem": [
|
||||
"../packages/acp-bridge/src/bridgeFileSystem.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/bridgeOptions": [
|
||||
"../packages/acp-bridge/src/bridgeOptions.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/bridgeTypes": [
|
||||
"../packages/acp-bridge/src/bridgeTypes.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/channelControlTimeouts": [
|
||||
"../packages/acp-bridge/src/channel-control-timeouts.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/childHeapPolicy": [
|
||||
"../packages/acp-bridge/src/child-heap-policy.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/daemonEventTypes": [
|
||||
"../packages/acp-bridge/src/daemonEventTypes.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/daemonMemoryBudget": [
|
||||
"../packages/acp-bridge/src/daemon-memory-budget.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/eventBus": [
|
||||
"../packages/acp-bridge/src/eventBus.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/externalToolGuard": [
|
||||
"../packages/acp-bridge/src/externalToolGuard.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/logRedaction": [
|
||||
"../packages/acp-bridge/src/logRedaction.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/mcpTimeouts": [
|
||||
"../packages/acp-bridge/src/mcpTimeouts.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/sessionArtifacts": [
|
||||
"../packages/acp-bridge/src/sessionArtifacts.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/spawnChannel": [
|
||||
"../packages/acp-bridge/src/spawnChannel.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/status": ["../packages/acp-bridge/src/status.ts"],
|
||||
"@qwen-code/acp-bridge/transcriptReplay": [
|
||||
"../packages/acp-bridge/src/transcript-replay.ts"
|
||||
],
|
||||
"@qwen-code/acp-bridge/workspacePaths": [
|
||||
"../packages/acp-bridge/src/workspacePaths.ts"
|
||||
],
|
||||
// qwen-serve-webui-live-journal-recovery.test.ts imports this subpath;
|
||||
// without an entry it resolves through the exports map to dist.
|
||||
"@qwen-code/webui/daemon-react-sdk": [
|
||||
"../packages/webui/src/daemon-react-sdk.ts"
|
||||
],
|
||||
// channel-plugin.test.ts and the plugin-example sources it imports
|
||||
// both import `@qwen-code/channel-base`. Map it to source so the
|
||||
// typecheck does not depend on channel-base's dist and both import
|
||||
// sites share one declaration — mixing src and dist declarations
|
||||
// produces duplicate-private-class errors.
|
||||
"@qwen-code/channel-base": ["../packages/channels/base/src/index.ts"],
|
||||
// cli's channel-registry.ts imports the eight builtin channel
|
||||
// adapters and html.ts imports web-templates; without entries they
|
||||
// resolve through their exports maps to dist, leaving the typecheck
|
||||
// dependent on those packages being built.
|
||||
"@qwen-code/channel-telegram": [
|
||||
"../packages/channels/telegram/src/index.ts"
|
||||
],
|
||||
"@qwen-code/channel-weixin": ["../packages/channels/weixin/src/index.ts"],
|
||||
"@qwen-code/channel-dingtalk": [
|
||||
"../packages/channels/dingtalk/src/index.ts"
|
||||
],
|
||||
"@qwen-code/channel-wecom": ["../packages/channels/wecom/src/index.ts"],
|
||||
"@qwen-code/channel-feishu": ["../packages/channels/feishu/src/index.ts"],
|
||||
"@qwen-code/channel-qqbot": ["../packages/channels/qqbot/src/index.ts"],
|
||||
"@qwen-code/channel-github": ["../packages/channels/github/src/index.ts"],
|
||||
"@qwen-code/channel-gitlab": ["../packages/channels/gitlab/src/index.ts"],
|
||||
"@qwen-code/web-templates": ["../packages/web-templates/src/index.ts"],
|
||||
// node-pty declares `types` at the top level but its `exports` map is a
|
||||
// bare string with no `types` condition, so nodenext resolution never
|
||||
// reaches the declarations and every pty handle degrades to `any` —
|
||||
// which is what silently untyped the `data` / `exitCode` callbacks in
|
||||
// test-helper.ts. Point at the shipped .d.ts directly.
|
||||
"@lydell/node-pty": ["../node_modules/@lydell/node-pty/node-pty.d.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"references": [{ "path": "../packages/core" }]
|
||||
"include": ["**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,15 @@
|
|||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
// Override the root's `vitest/globals` entry: vitest's types import the
|
||||
// optional `jsdom` peer types, and once `@types/jsdom` is installed that
|
||||
// drags `/// <reference lib="dom" />` into this program. The DOM lib
|
||||
// flips @types/node's conditional fetch globals to their DOM variants,
|
||||
// whose ReadableStream is not async-iterable (needs lib.dom.asynciterable),
|
||||
// breaking the `for await` over `response.body` in http-client.ts. The
|
||||
// sources compiled here use no vitest globals (tests are excluded), so
|
||||
// `node` alone is enough.
|
||||
"types": ["node"],
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
|
|
|
|||
46
package-lock.json
generated
46
package-lock.json
generated
|
|
@ -34,6 +34,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/chrome": "^0.1.32",
|
||||
"@types/jsdom": "^28.0.3",
|
||||
"@types/marked": "^5.0.2",
|
||||
"@types/mime-types": "^3.0.1",
|
||||
"@types/minimatch": "^5.1.2",
|
||||
|
|
@ -58,6 +59,7 @@
|
|||
"glob": "^10.5.0",
|
||||
"globals": "^16.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^26.1.0",
|
||||
"json": "^11.0.0",
|
||||
"lint-staged": "^16.1.6",
|
||||
"memfs": "^4.42.0",
|
||||
|
|
@ -91,7 +93,7 @@
|
|||
},
|
||||
"integrations/external-context": {
|
||||
"name": "@qwen-code/external-context",
|
||||
"version": "0.21.7",
|
||||
"version": "0.20.1",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.30.0",
|
||||
"undici": "^7.28.0",
|
||||
|
|
@ -8445,6 +8447,48 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/jsdom": {
|
||||
"version": "28.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-28.0.3.tgz",
|
||||
"integrity": "sha512-/HQ2uFoetFTXuye8vzIcHw2z6Fwi7Hi/qcgC+RoS9NCyewiqxhVGqlG+ViGB6lkax481R6dmhf1I7lIGlzJStQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/tough-cookie": "*",
|
||||
"parse5": "^8.0.0",
|
||||
"undici-types": "^7.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jsdom/node_modules/entities": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
|
||||
"integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jsdom/node_modules/parse5": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
|
||||
"integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"entities": "^8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jsdom/node_modules/undici-types": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.29.0.tgz",
|
||||
"integrity": "sha512-vamA8dGlzMwhpyYpQp9d8vka3o4D/yn5I7ez7Or+msDA4bZ8Uh+Zy91WvWf3I73gDAkFha9JcYRqm2li0Npfgg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@
|
|||
],
|
||||
"devDependencies": {
|
||||
"@types/chrome": "^0.1.32",
|
||||
"@types/jsdom": "^28.0.3",
|
||||
"@types/marked": "^5.0.2",
|
||||
"@types/mime-types": "^3.0.1",
|
||||
"@types/minimatch": "^5.1.2",
|
||||
|
|
@ -142,6 +143,7 @@
|
|||
"glob": "^10.5.0",
|
||||
"globals": "^16.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jsdom": "^26.1.0",
|
||||
"json": "^11.0.0",
|
||||
"lint-staged": "^16.1.6",
|
||||
"memfs": "^4.42.0",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue