Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6f3922d
feat(cli): add standalone auto-update support
yiliang114 May 29, 2026
6309a5c
feat(cli): harden standalone update — smoke test, rollback, signature
yiliang114 May 30, 2026
524de0b
fix(cli): address CI + review findings on standalone update
yiliang114 May 30, 2026
23f7242
fix(cli): use event payload in update UI + add rollback tests
yiliang114 May 30, 2026
ce71d0e
fix(cli): address review findings — 5 bugs in standalone-update
yiliang114 May 30, 2026
ee9c4f8
fix(cli): update handleAutoUpdate tests for payload-driven messages
yiliang114 May 30, 2026
07913be
feat(cli): auto-migrate npm users to standalone when prefix is not wr…
yiliang114 May 30, 2026
9620c01
fix(cli): remove unused _mockedAccessSync (TS6133)
yiliang114 May 30, 2026
73f30e1
fix(cli): address wenshao review — Windows deferred, PATH, rollback, …
yiliang114 Jun 1, 2026
3abe8ca
fix(cli): add i18n translations for /doctor rollback command
yiliang114 Jun 1, 2026
4a055ab
fix(cli): skip Unix-only wrapper tests on Windows
yiliang114 Jun 1, 2026
fe838b8
fix(cli): address CodeQL + test coverage on standalone-update
yiliang114 Jun 1, 2026
7928d4b
fix(cli): address wenshao review round 2 — bat error handling, rollba…
yiliang114 Jun 1, 2026
124b393
fix(cli): prevent symlink attack on staging directory via mkdtempSync
yiliang114 Jun 1, 2026
d1a7116
fix(cli): harden standalone-update — download size guard, shell-meta …
yiliang114 Jun 1, 2026
595a07a
fix(cli): harden standalone-update — tar preservePaths, timeout signa…
yiliang114 Jun 1, 2026
70fc1d2
fix(cli): address review round 3 — archive timeout, stderr capture, r…
yiliang114 Jun 1, 2026
3aede34
fix(test): drop pre-mkdir in shell-safety double-quote test
yiliang114 Jun 2, 2026
3ad8794
refactor(standalone-update): remove over-engineered defenses
yiliang114 Jun 2, 2026
9f7aa9f
fix(cli): harden standalone-update error paths and lock release
yiliang114 Jun 2, 2026
c594515
fix(cli): address multi-model review findings on standalone-update
yiliang114 Jun 3, 2026
66dbf5a
fix(cli): move shell validation after platform branch in ensureBinWra…
yiliang114 Jun 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"string-width": "^7.1.0",
"strip-ansi": "^7.1.0",
"strip-json-comments": "^3.1.1",
"tar": "^7.5.2",
"undici": "^6.22.0",
"update-notifier": "^7.3.1",
"wrap-ansi": "^10.0.0",
Expand Down
11 changes: 11 additions & 0 deletions packages/cli/src/i18n/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1901,6 +1901,17 @@ export default {
'Show current process memory diagnostics',
'Record a CPU profile for Chrome DevTools analysis':
'Record a CPU profile for Chrome DevTools analysis',
'Roll back a standalone update to the previous version':
'Roll back a standalone update to the previous version',
'Rollback is not available in ACP mode.':
'Rollback is not available in ACP mode.',
'Rollback is only available for standalone installations.':
'Rollback is only available for standalone installations.',
'Rollback successful. Restart your terminal to use the previous version.':
'Rollback successful. Restart your terminal to use the previous version.',
'Rollback failed:': 'Rollback failed:',
'Rollback on Windows requires manual intervention. Rename qwen-code.old to qwen-code in your installation directory.':
'Rollback on Windows requires manual intervention. Rename qwen-code.old to qwen-code in your installation directory.',
'Save a durable memory to the memory system.':
'Save a durable memory to the memory system.',
'Ask a quick side question without affecting the main conversation':
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/i18n/locales/zh-TW.js
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,16 @@ export default {
'Show current process memory diagnostics': '顯示目前程序的內存診斷。',
'Record a CPU profile for Chrome DevTools analysis':
'錄製 CPU 效能分析檔案,用於 Chrome DevTools 分析',
'Roll back a standalone update to the previous version':
'將獨立安裝回滾到上一個版本',
'Rollback is not available in ACP mode.': '回滾在 ACP 模式下不可用。',
'Rollback is only available for standalone installations.':
'回滾僅適用於獨立安裝。',
'Rollback successful. Restart your terminal to use the previous version.':
'回滾成功。請重啟終端以使用上一個版本。',
'Rollback failed:': '回滾失敗:',
'Rollback on Windows requires manual intervention. Rename qwen-code.old to qwen-code in your installation directory.':
'在 Windows 上回滾需要手動操作。請將安裝目錄中的 qwen-code.old 重新命名為 qwen-code。',
'Save a durable memory to the memory system.': '將持久記憶保存到記憶系統。',
'Ask a quick side question without affecting the main conversation':
'在不影響主對話的情況下快速提問旁支問題',
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/i18n/locales/zh.js
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,16 @@ export default {
'Show current process memory diagnostics': '显示当前进程的内存诊断。',
'Record a CPU profile for Chrome DevTools analysis':
'录制 CPU 性能分析文件,用于 Chrome DevTools 分析',
'Roll back a standalone update to the previous version':
'将独立安装回滚到上一个版本',
'Rollback is not available in ACP mode.': '回滚在 ACP 模式下不可用。',
'Rollback is only available for standalone installations.':
'回滚仅适用于独立安装。',
'Rollback successful. Restart your terminal to use the previous version.':
'回滚成功。请重启终端以使用上一个版本。',
'Rollback failed:': '回滚失败:',
'Rollback on Windows requires manual intervention. Rename qwen-code.old to qwen-code in your installation directory.':
'在 Windows 上回滚需要手动操作。请将安装目录中的 qwen-code.old 重命名为 qwen-code。',
'Save a durable memory to the memory system.':
'将一条持久记忆保存到记忆系统。',
'Show per-item context usage breakdown.': '显示按项目划分的上下文使用详情。',
Expand Down
6 changes: 5 additions & 1 deletion packages/cli/src/ui/commands/doctorCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,17 @@ describe('doctorCommand', () => {
await expect(doctorCommand.completion!(mockContext, '')).resolves.toEqual([
'memory',
'cpu-profile',
'rollback',
]);
await expect(
doctorCommand.completion!(mockContext, 'mem'),
).resolves.toEqual(['memory']);
await expect(
doctorCommand.completion!(mockContext, 'cpu'),
).resolves.toEqual(['cpu-profile']);
await expect(
doctorCommand.completion!(mockContext, 'roll'),
).resolves.toEqual(['rollback']);
await expect(doctorCommand.completion!(mockContext, 'x')).resolves.toEqual(
[],
);
Expand Down Expand Up @@ -1054,7 +1058,7 @@ describe('doctorCommand', () => {

it('should advertise the memory subcommand on the parent doctor argumentHint', () => {
expect(doctorCommand.argumentHint).toBe(
'[memory|cpu-profile] [--sample] [--snapshot] [--duration]',
'[memory|cpu-profile|rollback] [--sample] [--snapshot] [--duration]',
);
});
});
82 changes: 80 additions & 2 deletions packages/cli/src/ui/commands/doctorCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
startCpuProfile,
stopCpuProfile,
} from '../../utils/cpuProfiler.js';
import { rollbackStandaloneUpdate } from '../../utils/standalone-update.js';
import { getInstallationInfo } from '../../utils/installationInfo.js';
import { t } from '../../i18n/index.js';
import {
collectMemoryDiagnostics,
Expand All @@ -30,7 +32,8 @@ import { formatMemoryUsage } from '../utils/formatters.js';

const MEMORY_SUBCOMMAND = 'memory';
const CPU_PROFILE_SUBCOMMAND = 'cpu-profile';
const DOCTOR_SUBCOMMANDS = [MEMORY_SUBCOMMAND, CPU_PROFILE_SUBCOMMAND] as const;
const ROLLBACK_SUBCOMMAND = 'rollback';
const DOCTOR_SUBCOMMANDS = [MEMORY_SUBCOMMAND, CPU_PROFILE_SUBCOMMAND, ROLLBACK_SUBCOMMAND] as const;
function getHeapSnapshotSensitiveDataWarning(): string {
return t(
'Heap snapshot may contain prompts, file contents, tool results, and other sensitive data. Do not share it publicly without reviewing it first.',
Expand All @@ -55,14 +58,15 @@ export const doctorCommand: SlashCommand = {
},
kind: CommandKind.BUILT_IN,
supportedModes: ['interactive', 'non_interactive', 'acp'] as const,
argumentHint: '[memory|cpu-profile] [--sample] [--snapshot] [--duration]',
argumentHint: '[memory|cpu-profile|rollback] [--sample] [--snapshot] [--duration]',
examples: [
'/doctor',
'/doctor memory',
'/doctor memory --sample',
'/doctor memory --snapshot',
'/doctor cpu-profile',
'/doctor cpu-profile --duration 10',
'/doctor rollback',
],
completion: async (_context, partialArg) => {
const trimmed = partialArg.trimStart();
Expand All @@ -79,6 +83,17 @@ export const doctorCommand: SlashCommand = {
const shouldWriteHeapSnapshot = subCommandArgs.includes('--snapshot');
const shouldSampleMemory = subCommandArgs.includes('--sample');

if (subCommand === ROLLBACK_SUBCOMMAND) {
if (executionMode === 'acp') {
return {
type: 'message' as const,
messageType: 'error' as const,
content: t('Rollback is not available in ACP mode.'),
};
}
return rollbackDoctorAction(context);
}

if (subCommand === MEMORY_SUBCOMMAND) {
if (abortSignal?.aborted) {
return;
Expand Down Expand Up @@ -255,6 +270,15 @@ export const doctorCommand: SlashCommand = {
argumentHint: '[--duration <seconds>]',
action: cpuProfileDoctorAction,
},
{
name: 'rollback',
get description() {
return t('Roll back a standalone update to the previous version');
},
kind: CommandKind.BUILT_IN,
supportedModes: ['interactive', 'non_interactive'] as const,
action: rollbackDoctorAction,
},
],
};

Expand Down Expand Up @@ -580,3 +604,57 @@ async function cpuProfileDoctorAction(
}
return { type: 'message', messageType: 'info', content: successMsg };
}

function rollbackDoctorAction(context: CommandContext) {
const installInfo = getInstallationInfo(process.cwd(), false);
if (!installInfo.isStandalone || !installInfo.standaloneDir) {
const msg = t('Rollback is only available for standalone installations.');
if (context.executionMode === 'interactive') {
context.ui.addItem({ type: 'info', text: msg }, Date.now());
return;
}
return {
type: 'message' as const,
messageType: 'info' as const,
content: msg,
};
}

if (process.platform === 'win32') {
const winMsg = t(
'Rollback on Windows requires manual intervention. Rename qwen-code.old to qwen-code in your installation directory.',
);
if (context.executionMode === 'interactive') {
context.ui.addItem({ type: 'info', text: winMsg }, Date.now());
return;
}
return {
type: 'message' as const,
messageType: 'info' as const,
content: winMsg,
};
}

const result = rollbackStandaloneUpdate(installInfo.standaloneDir);
let msg: string;
let messageType: 'info' | 'error';
if (result.ok) {
msg = t(
'Rollback successful. Restart your terminal to use the previous version.',
);
messageType = 'info';
} else {
msg = `${t('Rollback failed:')} ${result.detail}`;
messageType = 'error';
}

if (context.executionMode === 'interactive') {
context.ui.addItem({ type: messageType, text: msg }, Date.now());
return;
}
return {
type: 'message' as const,
messageType: messageType as 'info' | 'error',
content: msg,
};
}
Loading
Loading