mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 11:41:04 +00:00
* add http/async/function type * fix url error * resolve comment * align cc non blocking error * fix hookRunner for async * fix(hooks): update hook type validation to support http and function types - Change validated hook types from ['command', 'plugin'] to ['command', 'http', 'function'] - Add validation for HTTP hooks requiring url field - Add validation for function hooks requiring callback field - Add comprehensive test coverage for all hook type validations Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(hooks): align SSRF protection with Claude Code behavior - Allow 127.0.0.0/8 (loopback) for local dev hooks - Allow localhost hostname for local dev hooks - Allow ::1 (IPv6 loopback) for local dev hooks - Add 100.64.0.0/10 (CGNAT) to blocked ranges (RFC 6598) - Update tests to match Claude Code's ssrfGuard.ts behavior This fixes HTTP hooks failing to connect to local dev servers. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(hooks): align HTTP hook security with Claude Code behavior - Add CRLF/NUL sanitization for env var interpolation (header injection) - Implement combined abort signal (external signal + timeout) - Upgrade SSRF protection to DNS-level with ssrfGuard - Allow loopback (127.0.0.0/8, ::1) for local dev hooks - Block CGNAT (100.64.0.0/10) and IPv6 private ranges - Increase default HTTP hook timeout to 10 minutes - Fix VS Code hooks schema to support http type - Add url, headers, allowedEnvVars, async, once, statusMessage, shell fields - Note: "function" type is SDK-only (callback cannot be serialized to JSON) * feat(hooks): enhance Function Hook with messages, skillRoot, shell, and matcher support - Add MessagesProvider for automatic conversation history passing to function hooks - Add FunctionHookContext with messages, toolUseID, and signal - Add skillRoot support for skill-scoped session hooks - Add shell parameter support for command hooks (bash/powershell) - Add regex matcher support for hook pattern matching - Add statusMessage to CommandHookConfig - Change default function hook timeout from 60s to 5s - Add comprehensive unit tests for all new features Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * add session hook for skill * fix function hook parsing * refactor ui for http hook/async hook/function hook * update doc and add integration test * change telemetryn type and refactor SSRF * fix project level bug --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Qwen Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import { createCombinedAbortSignal } from './combinedAbortSignal.js';
|
|
|
|
describe('createCombinedAbortSignal', () => {
|
|
it('should return a non-aborted signal by default', () => {
|
|
const { signal, cleanup } = createCombinedAbortSignal();
|
|
expect(signal.aborted).toBe(false);
|
|
cleanup();
|
|
});
|
|
|
|
it('should abort after timeout', async () => {
|
|
const { signal, cleanup } = createCombinedAbortSignal(undefined, {
|
|
timeoutMs: 50,
|
|
});
|
|
expect(signal.aborted).toBe(false);
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
expect(signal.aborted).toBe(true);
|
|
cleanup();
|
|
});
|
|
|
|
it('should abort when external signal is aborted', () => {
|
|
const externalController = new AbortController();
|
|
const { signal, cleanup } = createCombinedAbortSignal(
|
|
externalController.signal,
|
|
);
|
|
expect(signal.aborted).toBe(false);
|
|
|
|
externalController.abort();
|
|
expect(signal.aborted).toBe(true);
|
|
cleanup();
|
|
});
|
|
|
|
it('should abort immediately if external signal is already aborted', () => {
|
|
const externalController = new AbortController();
|
|
externalController.abort();
|
|
|
|
const { signal, cleanup } = createCombinedAbortSignal(
|
|
externalController.signal,
|
|
);
|
|
expect(signal.aborted).toBe(true);
|
|
cleanup();
|
|
});
|
|
|
|
it('should cleanup timeout timer', async () => {
|
|
const { signal, cleanup } = createCombinedAbortSignal(undefined, {
|
|
timeoutMs: 50,
|
|
});
|
|
|
|
cleanup();
|
|
|
|
// Wait longer than timeout - should not abort because timer was cleared
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
expect(signal.aborted).toBe(false);
|
|
});
|
|
|
|
it('should work with both external signal and timeout', async () => {
|
|
const externalController = new AbortController();
|
|
const { signal, cleanup } = createCombinedAbortSignal(
|
|
externalController.signal,
|
|
{ timeoutMs: 200 },
|
|
);
|
|
|
|
// Abort external signal before timeout
|
|
externalController.abort();
|
|
expect(signal.aborted).toBe(true);
|
|
cleanup();
|
|
});
|
|
|
|
it('should timeout before external signal', async () => {
|
|
const externalController = new AbortController();
|
|
const { signal, cleanup } = createCombinedAbortSignal(
|
|
externalController.signal,
|
|
{ timeoutMs: 50 },
|
|
);
|
|
|
|
// Wait for timeout
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
expect(signal.aborted).toBe(true);
|
|
|
|
// External signal is still not aborted
|
|
expect(externalController.signal.aborted).toBe(false);
|
|
cleanup();
|
|
});
|
|
});
|