Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
99c8f84
feat(cli): add standalone auto-update support
yiliang114 May 29, 2026
853d5c3
feat(cli): harden standalone update — smoke test, rollback, signature
yiliang114 May 30, 2026
5990f40
fix(cli): address CI + review findings on standalone update
yiliang114 May 30, 2026
867deee
fix(cli): use event payload in update UI + add rollback tests
yiliang114 May 30, 2026
fad1317
fix(cli): address review findings — 5 bugs in standalone-update
yiliang114 May 30, 2026
d22b178
fix(cli): update handleAutoUpdate tests for payload-driven messages
yiliang114 May 30, 2026
7057db7
feat(cli): auto-migrate npm users to standalone when prefix is not wr…
yiliang114 May 30, 2026
09c9d8d
fix(cli): remove unused _mockedAccessSync (TS6133)
yiliang114 May 30, 2026
a2ebbab
fix(cli): address wenshao review — Windows deferred, PATH, rollback, …
yiliang114 Jun 1, 2026
dfe203a
fix(cli): add i18n translations for /doctor rollback command
yiliang114 Jun 1, 2026
80451dc
fix(cli): skip Unix-only wrapper tests on Windows
yiliang114 Jun 1, 2026
fd7f046
fix(cli): address CodeQL + test coverage on standalone-update
yiliang114 Jun 1, 2026
8854b34
fix(cli): address wenshao review round 2 — bat error handling, rollba…
yiliang114 Jun 1, 2026
71f3dfa
fix(cli): prevent symlink attack on staging directory via mkdtempSync
yiliang114 Jun 1, 2026
5e0fdc9
fix(cli): harden standalone-update — download size guard, shell-meta …
yiliang114 Jun 1, 2026
924bdc9
fix(cli): harden standalone-update — tar preservePaths, timeout signa…
yiliang114 Jun 1, 2026
47dc004
fix(cli): address review round 3 — archive timeout, stderr capture, r…
yiliang114 Jun 1, 2026
2144f5b
fix(test): drop pre-mkdir in shell-safety double-quote test
yiliang114 Jun 2, 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
12 changes: 12 additions & 0 deletions packages/cli/src/i18n/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1898,6 +1898,18 @@ export default {
'Open the memory manager.': 'Open the memory manager.',
'Show current process memory diagnostics':
'Show current process memory diagnostics',
'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: no previous version found (.old directory missing).':
'Rollback failed: no previous version found (.old directory missing).',
'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
11 changes: 11 additions & 0 deletions packages/cli/src/i18n/locales/zh-TW.js
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,17 @@ export default {
// === Core: added from PR #3328 ===
'Open the memory manager.': '打開記憶管理器。',
'Show current process memory diagnostics': '顯示目前程序的內存診斷。',
'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: no previous version found (.old directory missing).':
'回滾失敗:未找到上一個版本(.old 目錄缺失)。',
'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
11 changes: 11 additions & 0 deletions packages/cli/src/i18n/locales/zh.js
Original file line number Diff line number Diff line change
Expand Up @@ -1720,6 +1720,17 @@ export default {
'Loading suggestions...': '正在加载建议...',
'Open the memory manager.': '打开记忆管理器。',
'Show current process memory diagnostics': '显示当前进程的内存诊断。',
'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: no previous version found (.old directory missing).':
'回滚失败:未找到上一个版本(.old 目录缺失)。',
'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
8 changes: 7 additions & 1 deletion packages/cli/src/ui/commands/doctorCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,14 @@ describe('doctorCommand', () => {
it('should complete memory subcommand names', async () => {
await expect(doctorCommand.completion!(mockContext, '')).resolves.toEqual([
'memory',
'rollback',
]);
await expect(
doctorCommand.completion!(mockContext, 'mem'),
).resolves.toEqual(['memory']);
await expect(
doctorCommand.completion!(mockContext, 'roll'),
).resolves.toEqual(['rollback']);
await expect(doctorCommand.completion!(mockContext, 'x')).resolves.toEqual(
[],
);
Expand Down Expand Up @@ -1049,6 +1053,8 @@ describe('doctorCommand', () => {
});

it('should advertise the memory subcommand on the parent doctor argumentHint', () => {
expect(doctorCommand.argumentHint).toBe('[memory] [--sample] [--snapshot]');
expect(doctorCommand.argumentHint).toBe(
'[memory|rollback] [--sample] [--snapshot]',
);
});
});
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 @@ -16,6 +16,8 @@ import {
isHighHeapPressure,
writeMemoryHeapSnapshot,
} from '../../utils/memoryDiagnostics.js';
import { rollbackStandaloneUpdate } from '../../utils/standalone-update.js';
import { getInstallationInfo } from '../../utils/installationInfo.js';
import { t } from '../../i18n/index.js';
import {
collectMemoryDiagnostics,
Expand All @@ -24,7 +26,8 @@ import {
import { formatMemoryUsage } from '../utils/formatters.js';

const MEMORY_SUBCOMMAND = 'memory';
const DOCTOR_SUBCOMMANDS = [MEMORY_SUBCOMMAND] as const;
const ROLLBACK_SUBCOMMAND = 'rollback';
const DOCTOR_SUBCOMMANDS = [MEMORY_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 @@ -49,12 +52,13 @@ export const doctorCommand: SlashCommand = {
},
kind: CommandKind.BUILT_IN,
supportedModes: ['interactive', 'non_interactive', 'acp'] as const,
argumentHint: '[memory] [--sample] [--snapshot]',
argumentHint: '[memory|rollback] [--sample] [--snapshot]',
examples: [
'/doctor',
'/doctor memory',
'/doctor memory --sample',
'/doctor memory --snapshot',
'/doctor rollback',
],
completion: async (_context, partialArg) => {
const trimmed = partialArg.trimStart();
Expand All @@ -71,6 +75,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 @@ -233,6 +248,15 @@ export const doctorCommand: SlashCommand = {
argumentHint: '[--json] [--sample] [--snapshot]',
action: memoryDoctorAction,
},
{
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 @@ -382,3 +406,57 @@ function formatCoreDiagnostics(diagnostics: MemoryDiagnostics): string {
);
return lines.join('\n');
}

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