mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-24 02:29:13 +00:00
Compare commits
5 Commits
update-doc
...
release/v0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e3dc31989 | ||
|
|
b01ddf0aed | ||
|
|
67e2e270bd | ||
|
|
adabd96a42 | ||
|
|
4a96646732 |
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11-nightly.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11-nightly.7",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
@@ -12512,7 +12512,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11-nightly.7",
|
||||
"dependencies": {
|
||||
"@google/genai": "1.9.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
@@ -12696,7 +12696,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@qwen-code/qwen-code-core",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11-nightly.7",
|
||||
"dependencies": {
|
||||
"@google/genai": "1.13.0",
|
||||
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||
@@ -12861,7 +12861,7 @@
|
||||
},
|
||||
"packages/test-utils": {
|
||||
"name": "@qwen-code/qwen-code-test-utils",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11-nightly.7",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.3.3"
|
||||
@@ -12872,7 +12872,7 @@
|
||||
},
|
||||
"packages/vscode-ide-companion": {
|
||||
"name": "qwen-code-vscode-ide-companion",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11-nightly.7",
|
||||
"license": "LICENSE",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.15.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11-nightly.7",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
@@ -13,7 +13,7 @@
|
||||
"url": "git+https://github.com/QwenLM/qwen-code.git"
|
||||
},
|
||||
"config": {
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.10"
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.11-nightly.7"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node scripts/start.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11-nightly.7",
|
||||
"description": "Qwen Code",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -25,7 +25,7 @@
|
||||
"dist"
|
||||
],
|
||||
"config": {
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.10"
|
||||
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.11-nightly.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "1.9.0",
|
||||
|
||||
@@ -204,7 +204,7 @@ export const StatsDisplay: React.FC<StatsDisplayProps> = ({
|
||||
<StatRow title="Tool Calls:">
|
||||
<Text>
|
||||
{tools.totalCalls} ({' '}
|
||||
<Text color={theme.status.success}>✔ {tools.totalSuccess}</Text>{' '}
|
||||
<Text color={theme.status.success}>✓ {tools.totalSuccess}</Text>{' '}
|
||||
<Text color={theme.status.error}>✖ {tools.totalFail}</Text> )
|
||||
</Text>
|
||||
</StatRow>
|
||||
|
||||
@@ -7,7 +7,7 @@ exports[`<SessionSummaryDisplay /> > renders the summary display with a title 1`
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: │
|
||||
│ Tool Calls: 0 ( ✔ 0 ✖ 0 ) │
|
||||
│ Tool Calls: 0 ( ✓ 0 ✖ 0 ) │
|
||||
│ Success Rate: 0.0% │
|
||||
│ Code Changes: +42 -15 │
|
||||
│ │
|
||||
|
||||
@@ -7,7 +7,7 @@ exports[`<StatsDisplay /> > Code Changes Display > displays Code Changes when li
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 1 ( ✔ 1 ✖ 0 ) │
|
||||
│ Tool Calls: 1 ( ✓ 1 ✖ 0 ) │
|
||||
│ Success Rate: 100.0% │
|
||||
│ Code Changes: +42 -18 │
|
||||
│ │
|
||||
@@ -28,7 +28,7 @@ exports[`<StatsDisplay /> > Code Changes Display > hides Code Changes when no li
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 1 ( ✔ 1 ✖ 0 ) │
|
||||
│ Tool Calls: 1 ( ✓ 1 ✖ 0 ) │
|
||||
│ Success Rate: 100.0% │
|
||||
│ │
|
||||
│ Performance │
|
||||
@@ -48,7 +48,7 @@ exports[`<StatsDisplay /> > Conditional Color Tests > renders success rate in gr
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 10 ( ✔ 10 ✖ 0 ) │
|
||||
│ Tool Calls: 10 ( ✓ 10 ✖ 0 ) │
|
||||
│ Success Rate: 100.0% │
|
||||
│ │
|
||||
│ Performance │
|
||||
@@ -68,7 +68,7 @@ exports[`<StatsDisplay /> > Conditional Color Tests > renders success rate in re
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 10 ( ✔ 5 ✖ 5 ) │
|
||||
│ Tool Calls: 10 ( ✓ 5 ✖ 5 ) │
|
||||
│ Success Rate: 50.0% │
|
||||
│ │
|
||||
│ Performance │
|
||||
@@ -88,7 +88,7 @@ exports[`<StatsDisplay /> > Conditional Color Tests > renders success rate in ye
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 10 ( ✔ 9 ✖ 1 ) │
|
||||
│ Tool Calls: 10 ( ✓ 9 ✖ 1 ) │
|
||||
│ Success Rate: 90.0% │
|
||||
│ │
|
||||
│ Performance │
|
||||
@@ -108,7 +108,7 @@ exports[`<StatsDisplay /> > Conditional Rendering Tests > hides Efficiency secti
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 0 ( ✔ 0 ✖ 0 ) │
|
||||
│ Tool Calls: 0 ( ✓ 0 ✖ 0 ) │
|
||||
│ Success Rate: 0.0% │
|
||||
│ │
|
||||
│ Performance │
|
||||
@@ -132,7 +132,7 @@ exports[`<StatsDisplay /> > Conditional Rendering Tests > hides User Agreement w
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 2 ( ✔ 1 ✖ 1 ) │
|
||||
│ Tool Calls: 2 ( ✓ 1 ✖ 1 ) │
|
||||
│ Success Rate: 50.0% │
|
||||
│ │
|
||||
│ Performance │
|
||||
@@ -152,7 +152,7 @@ exports[`<StatsDisplay /> > Title Rendering > renders the custom title when a ti
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 0 ( ✔ 0 ✖ 0 ) │
|
||||
│ Tool Calls: 0 ( ✓ 0 ✖ 0 ) │
|
||||
│ Success Rate: 0.0% │
|
||||
│ │
|
||||
│ Performance │
|
||||
@@ -172,7 +172,7 @@ exports[`<StatsDisplay /> > Title Rendering > renders the default title when no
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 0 ( ✔ 0 ✖ 0 ) │
|
||||
│ Tool Calls: 0 ( ✓ 0 ✖ 0 ) │
|
||||
│ Success Rate: 0.0% │
|
||||
│ │
|
||||
│ Performance │
|
||||
@@ -192,7 +192,7 @@ exports[`<StatsDisplay /> > renders a table with two models correctly 1`] = `
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 0 ( ✔ 0 ✖ 0 ) │
|
||||
│ Tool Calls: 0 ( ✓ 0 ✖ 0 ) │
|
||||
│ Success Rate: 0.0% │
|
||||
│ │
|
||||
│ Performance │
|
||||
@@ -221,7 +221,7 @@ exports[`<StatsDisplay /> > renders all sections when all data is present 1`] =
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 2 ( ✔ 1 ✖ 1 ) │
|
||||
│ Tool Calls: 2 ( ✓ 1 ✖ 1 ) │
|
||||
│ Success Rate: 50.0% │
|
||||
│ User Agreement: 100.0% (1 reviewed) │
|
||||
│ │
|
||||
@@ -250,7 +250,7 @@ exports[`<StatsDisplay /> > renders only the Performance section in its zero sta
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 0 ( ✔ 0 ✖ 0 ) │
|
||||
│ Tool Calls: 0 ( ✓ 0 ✖ 0 ) │
|
||||
│ Success Rate: 0.0% │
|
||||
│ │
|
||||
│ Performance │
|
||||
|
||||
@@ -80,6 +80,7 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
|
||||
marginLeft={1}
|
||||
borderDimColor={hasPending}
|
||||
borderColor={borderColor}
|
||||
gap={1}
|
||||
>
|
||||
{toolCalls.map((tool) => {
|
||||
const isConfirming = toolAwaitingApproval?.callId === tool.callId;
|
||||
|
||||
@@ -84,19 +84,19 @@ describe('<ToolMessage />', () => {
|
||||
StreamingState.Idle,
|
||||
);
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('✔'); // Success indicator
|
||||
expect(output).toContain('✓'); // Success indicator
|
||||
expect(output).toContain('test-tool');
|
||||
expect(output).toContain('A tool for testing');
|
||||
expect(output).toContain('MockMarkdown:Test result');
|
||||
});
|
||||
|
||||
describe('ToolStatusIndicator rendering', () => {
|
||||
it('shows ✔ for Success status', () => {
|
||||
it('shows ✓ for Success status', () => {
|
||||
const { lastFrame } = renderWithContext(
|
||||
<ToolMessage {...baseProps} status={ToolCallStatus.Success} />,
|
||||
StreamingState.Idle,
|
||||
);
|
||||
expect(lastFrame()).toContain('✔');
|
||||
expect(lastFrame()).toContain('✓');
|
||||
});
|
||||
|
||||
it('shows o for Pending status', () => {
|
||||
@@ -138,7 +138,7 @@ describe('<ToolMessage />', () => {
|
||||
);
|
||||
expect(lastFrame()).toContain('⊷');
|
||||
expect(lastFrame()).not.toContain('MockRespondingSpinner');
|
||||
expect(lastFrame()).not.toContain('✔');
|
||||
expect(lastFrame()).not.toContain('✓');
|
||||
});
|
||||
|
||||
it('shows paused spinner for Executing status when streamingState is WaitingForConfirmation', () => {
|
||||
@@ -148,7 +148,7 @@ describe('<ToolMessage />', () => {
|
||||
);
|
||||
expect(lastFrame()).toContain('⊷');
|
||||
expect(lastFrame()).not.toContain('MockRespondingSpinner');
|
||||
expect(lastFrame()).not.toContain('✔');
|
||||
expect(lastFrame()).not.toContain('✓');
|
||||
});
|
||||
|
||||
it('shows MockRespondingSpinner for Executing status when streamingState is Responding', () => {
|
||||
@@ -157,7 +157,7 @@ describe('<ToolMessage />', () => {
|
||||
StreamingState.Responding, // Simulate app still responding
|
||||
);
|
||||
expect(lastFrame()).toContain('MockRespondingSpinner');
|
||||
expect(lastFrame()).not.toContain('✔');
|
||||
expect(lastFrame()).not.toContain('✓');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -269,7 +269,7 @@ const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({
|
||||
/>
|
||||
)}
|
||||
{status === ToolCallStatus.Success && (
|
||||
<Text color={Colors.AccentGreen}>✔</Text>
|
||||
<Text color={Colors.AccentGreen}>✓</Text>
|
||||
)}
|
||||
{status === ToolCallStatus.Confirming && (
|
||||
<Text color={Colors.AccentYellow}>?</Text>
|
||||
@@ -321,7 +321,8 @@ const ToolInfo: React.FC<ToolInfo> = ({
|
||||
>
|
||||
<Text color={nameColor} bold>
|
||||
{name}
|
||||
</Text>{' '}
|
||||
</Text>
|
||||
<Text> </Text>
|
||||
<Text color={Colors.Gray}>{description}</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -288,7 +288,7 @@ const ToolCallItem: React.FC<{
|
||||
case 'awaiting_approval':
|
||||
return <Text color={theme.status.warning}>?</Text>;
|
||||
case 'success':
|
||||
return <Text color={color}>✔</Text>;
|
||||
return <Text color={color}>✓</Text>;
|
||||
case 'failed':
|
||||
return (
|
||||
<Text color={color} bold>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code-core",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11-nightly.7",
|
||||
"description": "Qwen Code Core",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -548,6 +548,17 @@ describe('oauth2', () => {
|
||||
expect(updatedAccountData.old).toContain('test@example.com');
|
||||
});
|
||||
|
||||
it('should handle Qwen module clearing gracefully', async () => {
|
||||
// This test verifies that clearCachedCredentialFile doesn't throw
|
||||
// when Qwen modules are available and can be cleared
|
||||
|
||||
// Since dynamic imports in tests are complex, we'll just verify
|
||||
// that the function completes without error and doesn't throw
|
||||
await expect(clearCachedCredentialFile()).resolves.not.toThrow();
|
||||
|
||||
// The actual Qwen clearing logic is tested separately in the Qwen module tests
|
||||
});
|
||||
|
||||
it('should clear the in-memory OAuth client cache', async () => {
|
||||
const mockSetCredentials = vi.fn();
|
||||
const mockGetAccessToken = vi
|
||||
|
||||
@@ -402,6 +402,25 @@ export async function clearCachedCredentialFile() {
|
||||
await clearCachedGoogleAccount();
|
||||
// Clear the in-memory OAuth client cache to force re-authentication
|
||||
clearOauthClientCache();
|
||||
|
||||
/**
|
||||
* Also clear Qwen SharedTokenManager cache and credentials file to prevent stale credentials
|
||||
* when switching between auth types
|
||||
* TODO: We do not depend on code_assist, we'll have to build an independent auth-cleaning procedure.
|
||||
*/
|
||||
try {
|
||||
const { SharedTokenManager } = await import(
|
||||
'../qwen/sharedTokenManager.js'
|
||||
);
|
||||
const { clearQwenCredentials } = await import('../qwen/qwenOAuth2.js');
|
||||
|
||||
const sharedManager = SharedTokenManager.getInstance();
|
||||
sharedManager.clearCache();
|
||||
|
||||
await clearQwenCredentials();
|
||||
} catch (qwenError) {
|
||||
console.debug('Could not clear Qwen credentials:', qwenError);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to clear cached credentials:', e);
|
||||
}
|
||||
|
||||
@@ -26,17 +26,20 @@ const QWEN_LOCK_FILENAME = 'oauth_creds.lock';
|
||||
// Token and Cache Configuration
|
||||
const TOKEN_REFRESH_BUFFER_MS = 30 * 1000; // 30 seconds
|
||||
const LOCK_TIMEOUT_MS = 10000; // 10 seconds lock timeout
|
||||
const CACHE_CHECK_INTERVAL_MS = 1000; // 1 second cache check interval
|
||||
const CACHE_CHECK_INTERVAL_MS = 5000; // 5 seconds cache check interval (increased from 1 second)
|
||||
|
||||
// Lock acquisition configuration (can be overridden for testing)
|
||||
interface LockConfig {
|
||||
maxAttempts: number;
|
||||
attemptInterval: number;
|
||||
// Add exponential backoff parameters
|
||||
maxInterval: number;
|
||||
}
|
||||
|
||||
const DEFAULT_LOCK_CONFIG: LockConfig = {
|
||||
maxAttempts: 50,
|
||||
attemptInterval: 200,
|
||||
maxAttempts: 20, // Reduced from 50 to prevent excessive waiting
|
||||
attemptInterval: 100, // Reduced from 200ms to check more frequently
|
||||
maxInterval: 2000, // Maximum interval for exponential backoff
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -300,7 +303,25 @@ export class SharedTokenManager {
|
||||
|
||||
try {
|
||||
const filePath = this.getCredentialFilePath();
|
||||
const stats = await fs.stat(filePath);
|
||||
// Add timeout to file stat operation
|
||||
const withTimeout = async <T>(
|
||||
promise: Promise<T>,
|
||||
timeoutMs: number,
|
||||
): Promise<T> =>
|
||||
Promise.race([
|
||||
promise,
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
new Error(`File operation timed out after ${timeoutMs}ms`),
|
||||
),
|
||||
timeoutMs,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
const stats = await withTimeout(fs.stat(filePath), 3000);
|
||||
const fileModTime = stats.mtimeMs;
|
||||
|
||||
// Reload credentials if file has been modified since last cache
|
||||
@@ -423,6 +444,7 @@ export class SharedTokenManager {
|
||||
qwenClient: IQwenOAuth2Client,
|
||||
forceRefresh = false,
|
||||
): Promise<QwenCredentials> {
|
||||
const startTime = Date.now();
|
||||
const lockPath = this.getLockFilePath();
|
||||
|
||||
try {
|
||||
@@ -439,6 +461,15 @@ export class SharedTokenManager {
|
||||
// Acquire distributed file lock
|
||||
await this.acquireLock(lockPath);
|
||||
|
||||
// Check if the operation is taking too long
|
||||
const lockAcquisitionTime = Date.now() - startTime;
|
||||
if (lockAcquisitionTime > 5000) {
|
||||
// 5 seconds warning threshold
|
||||
console.warn(
|
||||
`Token refresh lock acquisition took ${lockAcquisitionTime}ms`,
|
||||
);
|
||||
}
|
||||
|
||||
// Double-check if another process already refreshed the token (unless force refresh is requested)
|
||||
// Skip the time-based throttling since we're already in a locked refresh operation
|
||||
await this.forceFileCheck(qwenClient);
|
||||
@@ -456,6 +487,13 @@ export class SharedTokenManager {
|
||||
// Perform the actual token refresh
|
||||
const response = await qwenClient.refreshAccessToken();
|
||||
|
||||
// Check if the token refresh is taking too long
|
||||
const totalOperationTime = Date.now() - startTime;
|
||||
if (totalOperationTime > 10000) {
|
||||
// 10 seconds warning threshold
|
||||
console.warn(`Token refresh operation took ${totalOperationTime}ms`);
|
||||
}
|
||||
|
||||
if (!response || isErrorResponse(response)) {
|
||||
const errorData = response as ErrorData;
|
||||
throw new TokenManagerError(
|
||||
@@ -551,9 +589,27 @@ export class SharedTokenManager {
|
||||
const dirPath = path.dirname(filePath);
|
||||
const tempPath = `${filePath}.tmp.${randomUUID()}`;
|
||||
|
||||
// Add timeout wrapper for file operations
|
||||
const withTimeout = async <T>(
|
||||
promise: Promise<T>,
|
||||
timeoutMs: number,
|
||||
): Promise<T> =>
|
||||
Promise.race([
|
||||
promise,
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(
|
||||
() => reject(new Error(`Operation timed out after ${timeoutMs}ms`)),
|
||||
timeoutMs,
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
// Create directory with restricted permissions
|
||||
try {
|
||||
await fs.mkdir(dirPath, { recursive: true, mode: 0o700 });
|
||||
await withTimeout(
|
||||
fs.mkdir(dirPath, { recursive: true, mode: 0o700 }),
|
||||
5000,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new TokenManagerError(
|
||||
TokenError.FILE_ACCESS_ERROR,
|
||||
@@ -566,18 +622,21 @@ export class SharedTokenManager {
|
||||
|
||||
try {
|
||||
// Write to temporary file first with restricted permissions
|
||||
await fs.writeFile(tempPath, credString, { mode: 0o600 });
|
||||
await withTimeout(
|
||||
fs.writeFile(tempPath, credString, { mode: 0o600 }),
|
||||
5000,
|
||||
);
|
||||
|
||||
// Atomic move to final location
|
||||
await fs.rename(tempPath, filePath);
|
||||
await withTimeout(fs.rename(tempPath, filePath), 5000);
|
||||
|
||||
// Update cached file modification time atomically after successful write
|
||||
const stats = await fs.stat(filePath);
|
||||
const stats = await withTimeout(fs.stat(filePath), 5000);
|
||||
this.memoryCache.fileModTime = stats.mtimeMs;
|
||||
} catch (error) {
|
||||
// Clean up temp file if it exists
|
||||
try {
|
||||
await fs.unlink(tempPath);
|
||||
await withTimeout(fs.unlink(tempPath), 1000);
|
||||
} catch (_cleanupError) {
|
||||
// Ignore cleanup errors - temp file might not exist
|
||||
}
|
||||
@@ -628,9 +687,11 @@ export class SharedTokenManager {
|
||||
* @throws TokenManagerError if lock cannot be acquired within timeout period
|
||||
*/
|
||||
private async acquireLock(lockPath: string): Promise<void> {
|
||||
const { maxAttempts, attemptInterval } = this.lockConfig;
|
||||
const { maxAttempts, attemptInterval, maxInterval } = this.lockConfig;
|
||||
const lockId = randomUUID(); // Use random UUID instead of PID for security
|
||||
|
||||
let currentInterval = attemptInterval;
|
||||
|
||||
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||
try {
|
||||
// Attempt to create lock file atomically (exclusive mode)
|
||||
@@ -671,8 +732,10 @@ export class SharedTokenManager {
|
||||
);
|
||||
}
|
||||
|
||||
// Wait before retrying
|
||||
await new Promise((resolve) => setTimeout(resolve, attemptInterval));
|
||||
// Wait before retrying with exponential backoff
|
||||
await new Promise((resolve) => setTimeout(resolve, currentInterval));
|
||||
// Increase interval for next attempt (exponential backoff), but cap at maxInterval
|
||||
currentInterval = Math.min(currentInterval * 1.5, maxInterval);
|
||||
} else {
|
||||
throw new TokenManagerError(
|
||||
TokenError.FILE_ACCESS_ERROR,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@qwen-code/qwen-code-test-utils",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11-nightly.7",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "qwen-code-vscode-ide-companion",
|
||||
"displayName": "Qwen Code Companion",
|
||||
"description": "Enable Qwen Code with direct access to your VS Code workspace.",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11-nightly.7",
|
||||
"publisher": "qwenlm",
|
||||
"icon": "assets/icon.png",
|
||||
"repository": {
|
||||
|
||||
Reference in New Issue
Block a user