@akiojin/gwt 3.1.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.ja.md +4 -4
  2. package/README.md +4 -4
  3. package/dist/cli/ui/components/App.d.ts.map +1 -1
  4. package/dist/cli/ui/components/App.js +8 -8
  5. package/dist/cli/ui/components/App.js.map +1 -1
  6. package/dist/cli/ui/components/screens/BranchListScreen.js +1 -1
  7. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  8. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +1 -1
  9. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +26 -11
  10. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +1 -1
  11. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +1 -1
  12. package/dist/cli/ui/components/screens/ModelSelectorScreen.js +0 -5
  13. package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +1 -1
  14. package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -1
  15. package/dist/cli/ui/utils/modelOptions.js +25 -16
  16. package/dist/cli/ui/utils/modelOptions.js.map +1 -1
  17. package/dist/config/builtin-tools.d.ts +2 -0
  18. package/dist/config/builtin-tools.d.ts.map +1 -1
  19. package/dist/config/builtin-tools.js +2 -0
  20. package/dist/config/builtin-tools.js.map +1 -1
  21. package/dist/config/tools.d.ts.map +1 -1
  22. package/dist/config/tools.js +4 -1
  23. package/dist/config/tools.js.map +1 -1
  24. package/dist/gemini.d.ts.map +1 -1
  25. package/dist/gemini.js +22 -40
  26. package/dist/gemini.js.map +1 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +6 -21
  29. package/dist/index.js.map +1 -1
  30. package/dist/utils/session/common.d.ts +100 -0
  31. package/dist/utils/session/common.d.ts.map +1 -0
  32. package/dist/utils/session/common.js +417 -0
  33. package/dist/utils/session/common.js.map +1 -0
  34. package/dist/utils/session/index.d.ts +16 -0
  35. package/dist/utils/session/index.d.ts.map +1 -0
  36. package/dist/utils/session/index.js +20 -0
  37. package/dist/utils/session/index.js.map +1 -0
  38. package/dist/utils/session/parsers/claude.d.ts +56 -0
  39. package/dist/utils/session/parsers/claude.d.ts.map +1 -0
  40. package/dist/utils/session/parsers/claude.js +178 -0
  41. package/dist/utils/session/parsers/claude.js.map +1 -0
  42. package/dist/utils/session/parsers/codex.d.ts +37 -0
  43. package/dist/utils/session/parsers/codex.d.ts.map +1 -0
  44. package/dist/utils/session/parsers/codex.js +113 -0
  45. package/dist/utils/session/parsers/codex.js.map +1 -0
  46. package/dist/utils/session/parsers/gemini.d.ts +22 -0
  47. package/dist/utils/session/parsers/gemini.d.ts.map +1 -0
  48. package/dist/utils/session/parsers/gemini.js +81 -0
  49. package/dist/utils/session/parsers/gemini.js.map +1 -0
  50. package/dist/utils/session/parsers/index.d.ts +8 -0
  51. package/dist/utils/session/parsers/index.d.ts.map +1 -0
  52. package/dist/utils/session/parsers/index.js +12 -0
  53. package/dist/utils/session/parsers/index.js.map +1 -0
  54. package/dist/utils/session/parsers/qwen.d.ts +21 -0
  55. package/dist/utils/session/parsers/qwen.d.ts.map +1 -0
  56. package/dist/utils/session/parsers/qwen.js +36 -0
  57. package/dist/utils/session/parsers/qwen.js.map +1 -0
  58. package/dist/utils/session/types.d.ts +38 -0
  59. package/dist/utils/session/types.d.ts.map +1 -0
  60. package/dist/utils/session/types.js +5 -0
  61. package/dist/utils/session/types.js.map +1 -0
  62. package/dist/utils/session.d.ts +14 -79
  63. package/dist/utils/session.d.ts.map +1 -1
  64. package/dist/utils/session.js +14 -585
  65. package/dist/utils/session.js.map +1 -1
  66. package/package.json +1 -1
  67. package/src/cli/ui/components/App.tsx +10 -8
  68. package/src/cli/ui/components/screens/BranchListScreen.tsx +1 -1
  69. package/src/cli/ui/components/screens/BranchQuickStartScreen.tsx +40 -22
  70. package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +0 -8
  71. package/src/cli/ui/utils/modelOptions.test.ts +9 -6
  72. package/src/cli/ui/utils/modelOptions.ts +25 -18
  73. package/src/config/builtin-tools.ts +2 -0
  74. package/src/config/tools.ts +5 -1
  75. package/src/gemini.ts +22 -47
  76. package/src/index.ts +7 -27
  77. package/src/utils/session/common.ts +446 -0
  78. package/src/utils/session/index.ts +46 -0
  79. package/src/utils/session/parsers/claude.ts +233 -0
  80. package/src/utils/session/parsers/codex.ts +135 -0
  81. package/src/utils/session/parsers/gemini.ts +94 -0
  82. package/src/utils/session/parsers/index.ts +28 -0
  83. package/src/utils/session/parsers/qwen.ts +54 -0
  84. package/src/utils/session/types.ts +42 -0
  85. package/src/utils/session.ts +14 -755
@@ -327,7 +327,7 @@ export function BranchListScreen({
327
327
  case "gemini-cli":
328
328
  return chalk.magenta(label);
329
329
  case "qwen-cli":
330
- return chalk.green(label);
330
+ return chalk.gray(label);
331
331
  default: {
332
332
  const trimmed = label.trim().toLowerCase();
333
333
  if (!toolId || trimmed === "unknown") {
@@ -32,6 +32,10 @@ const formatSkip = (skip?: boolean | null) =>
32
32
 
33
33
  const supportsReasoning = (toolId?: string | null) => toolId === "codex-cli";
34
34
 
35
+ const UNSUPPORTED_TOOL_ID = "qwen-cli";
36
+ const UNSUPPORTED_TOOL_MESSAGE = "Unsupported tool (Qwen CLI). ";
37
+ const UNSUPPORTED_TOOL_CATEGORY_LABEL = "Qwen (unsupported)";
38
+
35
39
  const describe = (opt: BranchQuickStartOption, includeSessionId = true) => {
36
40
  const parts = [`Model: ${opt.model ?? "default"}`];
37
41
  if (supportsReasoning(opt.toolId)) {
@@ -111,6 +115,7 @@ export function BranchQuickStartScreen({
111
115
  const flat: QuickStartItem[] = [];
112
116
  sorted.forEach((opt, idx) => {
113
117
  const cat = resolveCategory(opt.toolId);
118
+ const isUnsupportedTool = opt.toolId === UNSUPPORTED_TOOL_ID;
114
119
  const prevCat =
115
120
  idx > 0 ? resolveCategory(sorted[idx - 1]?.toolId).label : null;
116
121
  const isNewCategory = prevCat !== cat.label;
@@ -121,7 +126,10 @@ export function BranchQuickStartScreen({
121
126
  value: `reuse-continue:${opt.toolId ?? "unknown"}:${idx}`,
122
127
  action: "reuse-continue",
123
128
  toolId: opt.toolId ?? null,
124
- description: describe(opt, true),
129
+ description: isUnsupportedTool
130
+ ? `${UNSUPPORTED_TOOL_MESSAGE}${describe(opt, true)}`
131
+ : describe(opt, true),
132
+ ...(isUnsupportedTool ? { disabled: true } : {}),
125
133
  groupStart: isNewCategory && flat.length > 0,
126
134
  category: cat.label,
127
135
  categoryColor: cat.color,
@@ -131,7 +139,10 @@ export function BranchQuickStartScreen({
131
139
  value: `reuse-new:${opt.toolId ?? "unknown"}:${idx}`,
132
140
  action: "reuse-new",
133
141
  toolId: opt.toolId ?? null,
134
- description: describe(opt, false),
142
+ description: isUnsupportedTool
143
+ ? `${UNSUPPORTED_TOOL_MESSAGE}${describe(opt, false)}`
144
+ : describe(opt, false),
145
+ ...(isUnsupportedTool ? { disabled: true } : {}),
135
146
  groupStart: false,
136
147
  category: cat.label,
137
148
  categoryColor: cat.color,
@@ -197,27 +208,34 @@ export function BranchQuickStartScreen({
197
208
  if (item.disabled) return;
198
209
  onSelect(item.action, item.toolId ?? null);
199
210
  }}
200
- renderItem={(item: QuickStartItem, isSelected) => (
201
- <Box
202
- flexDirection="column"
203
- marginTop={
204
- item.groupStart ? 1 : item.category === "Other" ? 1 : 0
205
- }
206
- >
207
- <Text>
208
- <Text color={item.categoryColor} inverse={isSelected}>
209
- {`[${item.category}] `}
210
- </Text>
211
- <Text inverse={isSelected}>
212
- {item.label}
213
- {item.disabled ? " (disabled)" : ""}
211
+ renderItem={(item: QuickStartItem, isSelected) => {
212
+ const categoryLabel =
213
+ item.toolId === UNSUPPORTED_TOOL_ID
214
+ ? UNSUPPORTED_TOOL_CATEGORY_LABEL
215
+ : item.category;
216
+
217
+ return (
218
+ <Box
219
+ flexDirection="column"
220
+ marginTop={
221
+ item.groupStart ? 1 : item.category === "Other" ? 1 : 0
222
+ }
223
+ >
224
+ <Text>
225
+ <Text color={item.categoryColor} inverse={isSelected}>
226
+ {`[${categoryLabel}] `}
227
+ </Text>
228
+ <Text inverse={isSelected}>
229
+ {item.label}
230
+ {item.disabled ? " (disabled)" : ""}
231
+ </Text>
214
232
  </Text>
215
- </Text>
216
- {item.description && (
217
- <Text color="gray"> {item.description}</Text>
218
- )}
219
- </Box>
220
- )}
233
+ {item.description && (
234
+ <Text color="gray"> {item.description}</Text>
235
+ )}
236
+ </Box>
237
+ );
238
+ }}
221
239
  />
222
240
  </Box>
223
241
 
@@ -37,7 +37,6 @@ const TOOL_LABELS: Record<string, string> = {
37
37
  "claude-code": "Claude Code",
38
38
  "codex-cli": "Codex",
39
39
  "gemini-cli": "Gemini",
40
- "qwen-cli": "Qwen",
41
40
  };
42
41
 
43
42
  const INFERENCE_LABELS: Record<InferenceLevel, string> = {
@@ -255,13 +254,6 @@ export function ModelSelectorScreen({
255
254
  {modelOptions.length === 0 ? " (no options)" : ""}
256
255
  </Text>
257
256
  </Box>
258
- {tool === "qwen-cli" ? (
259
- <Box marginBottom={1} flexDirection="column">
260
- <Text>Latest Qwen models from Alibaba Cloud ModelStudio:</Text>
261
- <Text>• coder-model (qwen3-coder-plus-2025-09-23)</Text>
262
- <Text>• vision-model (qwen3-vl-plus-2025-09-23)</Text>
263
- </Box>
264
- ) : null}
265
257
 
266
258
  {modelItems.length === 0 ? (
267
259
  <Select
@@ -8,13 +8,13 @@ import {
8
8
  const byId = (tool: string) => getModelOptions(tool).map((m) => m.id);
9
9
 
10
10
  describe("modelOptions", () => {
11
- it("lists Claude official aliases and sets Opus 4.5 as default", () => {
11
+ it("lists Claude official aliases and sets Default as default", () => {
12
12
  const options = getModelOptions("claude-code");
13
13
  const ids = options.map((m) => m.id);
14
- expect(ids).toEqual(["opus", "sonnet", "haiku"]);
14
+ expect(ids).toEqual(["", "opus", "sonnet", "haiku"]);
15
15
  const defaultModel = getDefaultModelOption("claude-code");
16
- expect(defaultModel?.id).toBe("opus");
17
- expect(defaultModel?.label).toBe("Opus 4.5");
16
+ expect(defaultModel?.id).toBe("");
17
+ expect(defaultModel?.label).toBe("Default (Auto)");
18
18
  });
19
19
 
20
20
  it("has unique Codex models", () => {
@@ -22,6 +22,7 @@ describe("modelOptions", () => {
22
22
  const unique = new Set(ids);
23
23
  expect(unique.size).toBe(ids.length);
24
24
  expect(ids).toEqual([
25
+ "",
25
26
  "gpt-5.1-codex",
26
27
  "gpt-5.2",
27
28
  "gpt-5.1-codex-max",
@@ -52,14 +53,16 @@ describe("modelOptions", () => {
52
53
 
53
54
  it("lists expected Gemini models", () => {
54
55
  expect(byId("gemini-cli")).toEqual([
56
+ "",
55
57
  "gemini-3-pro-preview",
58
+ "gemini-3-flash-preview",
56
59
  "gemini-2.5-pro",
57
60
  "gemini-2.5-flash",
58
61
  "gemini-2.5-flash-lite",
59
62
  ]);
60
63
  });
61
64
 
62
- it("lists expected Qwen models", () => {
63
- expect(byId("qwen-cli")).toEqual(["coder-model", "vision-model"]);
65
+ it("returns no models for unsupported tools", () => {
66
+ expect(byId("qwen-cli")).toEqual([]);
64
67
  });
65
68
  });
@@ -5,12 +5,17 @@ const CODEX_MAX_LEVELS: InferenceLevel[] = ["xhigh", "high", "medium", "low"];
5
5
 
6
6
  const MODEL_OPTIONS: Record<string, ModelOption[]> = {
7
7
  "claude-code": [
8
+ {
9
+ id: "",
10
+ label: "Default (Auto)",
11
+ description: "Use Claude Code default behavior",
12
+ isDefault: true,
13
+ },
8
14
  {
9
15
  id: "opus",
10
16
  label: "Opus 4.5",
11
17
  description:
12
18
  "Official Opus alias for Claude Code (non-custom, matches /model option).",
13
- isDefault: true,
14
19
  },
15
20
  {
16
21
  id: "sonnet",
@@ -25,13 +30,20 @@ const MODEL_OPTIONS: Record<string, ModelOption[]> = {
25
30
  },
26
31
  ],
27
32
  "codex-cli": [
33
+ {
34
+ id: "",
35
+ label: "Default (Auto)",
36
+ description: "Use Codex default model",
37
+ isDefault: true,
38
+ inferenceLevels: CODEX_BASE_LEVELS,
39
+ defaultInference: "high",
40
+ },
28
41
  {
29
42
  id: "gpt-5.1-codex",
30
43
  label: "gpt-5.1-codex",
31
44
  description: "Standard Codex model",
32
45
  inferenceLevels: CODEX_BASE_LEVELS,
33
46
  defaultInference: "high",
34
- isDefault: true,
35
47
  },
36
48
  {
37
49
  id: "gpt-5.2",
@@ -63,12 +75,22 @@ const MODEL_OPTIONS: Record<string, ModelOption[]> = {
63
75
  },
64
76
  ],
65
77
  "gemini-cli": [
78
+ {
79
+ id: "",
80
+ label: "Default (Auto)",
81
+ description: "Use Gemini CLI default model",
82
+ isDefault: true,
83
+ },
66
84
  {
67
85
  id: "gemini-3-pro-preview",
68
86
  label: "Pro (gemini-3-pro-preview)",
69
87
  description:
70
88
  "Default Pro. Falls back to gemini-2.5-pro when preview is unavailable.",
71
- isDefault: true,
89
+ },
90
+ {
91
+ id: "gemini-3-flash-preview",
92
+ label: "Flash (gemini-3-flash-preview)",
93
+ description: "Next-generation high-speed model",
72
94
  },
73
95
  {
74
96
  id: "gemini-2.5-pro",
@@ -86,21 +108,6 @@ const MODEL_OPTIONS: Record<string, ModelOption[]> = {
86
108
  description: "Fastest for simple tasks",
87
109
  },
88
110
  ],
89
- "qwen-cli": [
90
- {
91
- id: "coder-model",
92
- label: "Coder Model",
93
- description:
94
- "Latest Qwen Coder model (qwen3-coder-plus-2025-09-23) from Alibaba Cloud ModelStudio",
95
- isDefault: true,
96
- },
97
- {
98
- id: "vision-model",
99
- label: "Vision Model",
100
- description:
101
- "Latest Qwen Vision model (qwen3-vl-plus-2025-09-23) from Alibaba Cloud ModelStudio",
102
- },
103
- ],
104
111
  };
105
112
 
106
113
  export function getModelOptions(tool: AITool): ModelOption[] {
@@ -60,6 +60,8 @@ export const GEMINI_CLI_TOOL: CustomAITool = {
60
60
 
61
61
  /**
62
62
  * Qwen のビルトイン定義
63
+ *
64
+ * NOTE: 現在は未サポート(選択画面には表示しない)。ID予約のため定義のみ残す。
63
65
  */
64
66
  export const QWEN_CLI_TOOL: CustomAITool = {
65
67
  id: "qwen-cli",
@@ -299,13 +299,17 @@ export async function getToolById(
299
299
  export async function getAllTools(): Promise<AIToolConfig[]> {
300
300
  const config = await loadToolsConfig();
301
301
 
302
+ // Builtin tools that are reserved but not exposed in selectors.
303
+ // These IDs remain blocked from customTools to avoid ambiguity.
304
+ const UNSUPPORTED_BUILTIN_TOOL_IDS = new Set<string>(["qwen-cli"]);
305
+
302
306
  // ビルトインツールをAIToolConfig形式に変換
303
307
  const builtinConfigs: AIToolConfig[] = BUILTIN_TOOLS.map((tool) => ({
304
308
  id: tool.id,
305
309
  displayName: tool.displayName,
306
310
  ...(tool.icon ? { icon: tool.icon } : {}),
307
311
  isBuiltin: true,
308
- }));
312
+ })).filter((tool) => !UNSUPPORTED_BUILTIN_TOOL_IDS.has(tool.id));
309
313
 
310
314
  // カスタムツールをAIToolConfig形式に変換
311
315
  const customConfigs: AIToolConfig[] = config.customTools.map((tool) => ({
package/src/gemini.ts CHANGED
@@ -134,62 +134,41 @@ export async function launchGeminiCLI(
134
134
  // Auto-detect locally installed gemini command
135
135
  const hasLocalGemini = await isGeminiCommandAvailable();
136
136
 
137
- // Capture session ID from Gemini's exit summary
137
+ // Preserve TTY for interactive UI (colors/width) by inheriting stdout/stderr.
138
+ // Session ID is determined via file-based detection after exit.
138
139
  let capturedSessionId: string | null = null;
139
- const extractSessionId = (output: string | undefined) => {
140
- if (!output) return;
141
- // Gemini outputs "Session ID: <uuid>" in exit summary
142
- // UUID may be split across lines due to terminal width
143
- // First, find "Session ID:" and extract following hex characters
144
- const sessionIdIndex = output.indexOf("Session ID:");
145
- if (sessionIdIndex === -1) return;
146
-
147
- // Extract text after "Session ID:" until we have enough hex chars for UUID
148
- const afterLabel = output.slice(sessionIdIndex + "Session ID:".length);
149
- // Remove all non-hex characters except dash, then extract UUID pattern
150
- const hexOnly = afterLabel.replace(/[^0-9a-fA-F-]/g, "");
151
- // UUID format: 8-4-4-4-12 = 32 hex chars + 4 dashes
152
- const uuidMatch = hexOnly.match(
153
- /^([0-9a-f]{8})-?([0-9a-f]{4})-?([0-9a-f]{4})-?([0-9a-f]{4})-?([0-9a-f]{12})/i,
154
- );
155
- if (uuidMatch) {
156
- capturedSessionId = `${uuidMatch[1]}-${uuidMatch[2]}-${uuidMatch[3]}-${uuidMatch[4]}-${uuidMatch[5]}`;
157
- }
158
- };
159
140
 
160
- const runGemini = async (
161
- runArgs: string[],
162
- ): Promise<string | undefined> => {
163
- // Capture stdout while passing through to terminal
164
- // Store chunks to extract session ID after process exits
165
- const outputChunks: string[] = [];
141
+ const runGemini = async (runArgs: string[]): Promise<void> => {
142
+ const execChild = async (child: Promise<unknown>) => {
143
+ try {
144
+ await child;
145
+ } catch (execError: unknown) {
146
+ // Treat SIGINT/SIGTERM as normal exit (user pressed Ctrl+C)
147
+ const signal = (execError as { signal?: unknown })?.signal;
148
+ if (signal === "SIGINT" || signal === "SIGTERM") {
149
+ return;
150
+ }
151
+ throw execError;
152
+ }
153
+ };
166
154
 
167
- const runWithCapture = async (cmd: string, args: string[]) => {
155
+ const run = async (cmd: string, args: string[]) => {
168
156
  const child = execa(cmd, args, {
169
157
  cwd: worktreePath,
170
158
  shell: true,
171
159
  stdin: childStdio.stdin,
172
- stdout: "pipe",
160
+ stdout: childStdio.stdout,
173
161
  stderr: childStdio.stderr,
174
162
  env: baseEnv,
175
163
  });
176
-
177
- // Pass stdout through to terminal while capturing
178
- child.stdout?.on("data", (chunk: Buffer) => {
179
- const text = chunk.toString("utf8");
180
- outputChunks.push(text);
181
- terminal.stdout.write(chunk);
182
- });
183
-
184
- await child;
185
- return outputChunks.join("");
164
+ await execChild(child);
186
165
  };
187
166
 
188
167
  if (hasLocalGemini) {
189
168
  console.log(
190
169
  chalk.green(" ✨ Using locally installed gemini command"),
191
170
  );
192
- return await runWithCapture("gemini", runArgs);
171
+ return await run("gemini", runArgs);
193
172
  }
194
173
  console.log(
195
174
  chalk.cyan(" 🔄 Falling back to bunx @google/gemini-cli@latest"),
@@ -202,15 +181,14 @@ export async function launchGeminiCLI(
202
181
  console.log(chalk.yellow(" npm install -g @google/gemini-cli"));
203
182
  console.log("");
204
183
  await new Promise((resolve) => setTimeout(resolve, 2000));
205
- return await runWithCapture("bunx", [GEMINI_CLI_PACKAGE, ...runArgs]);
184
+ return await run("bunx", [GEMINI_CLI_PACKAGE, ...runArgs]);
206
185
  };
207
186
 
208
- let output: string | undefined;
209
187
  let fellBackToLatest = false;
210
188
  try {
211
189
  // Try with explicit session ID first (if any), then fallback to --resume (latest) once
212
190
  try {
213
- output = await runGemini(argsPrimary);
191
+ await runGemini(argsPrimary);
214
192
  } catch (err) {
215
193
  const shouldRetry =
216
194
  (options.mode === "resume" || options.mode === "continue") &&
@@ -222,7 +200,7 @@ export async function launchGeminiCLI(
222
200
  ` ⚠️ Failed to resume session ${resumeSessionId}. Retrying with latest session...`,
223
201
  ),
224
202
  );
225
- output = await runGemini(argsFallback);
203
+ await runGemini(argsFallback);
226
204
  } else {
227
205
  throw err;
228
206
  }
@@ -231,9 +209,6 @@ export async function launchGeminiCLI(
231
209
  childStdio.cleanup();
232
210
  }
233
211
 
234
- // Extract session ID from Gemini's exit summary output
235
- extractSessionId(output);
236
-
237
212
  const explicitResumeSucceeded = usedExplicitSessionId && !fellBackToLatest;
238
213
 
239
214
  // If we explicitly resumed a specific session (and did not fall back), keep that ID.
package/src/index.ts CHANGED
@@ -16,7 +16,6 @@ import {
16
16
  type CodexReasoningEffort,
17
17
  } from "./codex.js";
18
18
  import { launchGeminiCLI, GeminiError } from "./gemini.js";
19
- import { launchQwenCLI, QwenError } from "./qwen.js";
20
19
  import {
21
20
  WorktreeOrchestrator,
22
21
  type EnsureWorktreeOptions,
@@ -117,7 +116,6 @@ function isRecoverableError(error: unknown): boolean {
117
116
  error instanceof WorktreeError ||
118
117
  error instanceof CodexError ||
119
118
  error instanceof GeminiError ||
120
- error instanceof QwenError ||
121
119
  error instanceof DependencyInstallError
122
120
  ) {
123
121
  return true;
@@ -129,7 +127,6 @@ function isRecoverableError(error: unknown): boolean {
129
127
  error.name === "WorktreeError" ||
130
128
  error.name === "CodexError" ||
131
129
  error.name === "GeminiError" ||
132
- error.name === "QwenError" ||
133
130
  error.name === "DependencyInstallError"
134
131
  );
135
132
  }
@@ -144,7 +141,6 @@ function isRecoverableError(error: unknown): boolean {
144
141
  name === "WorktreeError" ||
145
142
  name === "CodexError" ||
146
143
  name === "GeminiError" ||
147
- name === "QwenError" ||
148
144
  name === "DependencyInstallError"
149
145
  );
150
146
  }
@@ -311,6 +307,12 @@ export async function handleAIToolWorkflow(
311
307
  `Selected: ${branchLabel} with ${tool} (${mode} mode${modelInfo}, skipPermissions: ${skipPermissions})`,
312
308
  );
313
309
 
310
+ if (tool === "qwen-cli") {
311
+ printError("Qwen CLI is currently unsupported.");
312
+ await waitForErrorAcknowledgement();
313
+ return;
314
+ }
315
+
314
316
  try {
315
317
  // Get repository root
316
318
  const repoRootResult = await runGitStep("retrieve repository root", () =>
@@ -658,28 +660,6 @@ export async function handleAIToolWorkflow(
658
660
  launchOptions.model = model;
659
661
  }
660
662
  launchResult = await launchGeminiCLI(worktreePath, launchOptions);
661
- } else if (tool === "qwen-cli") {
662
- const launchOptions: {
663
- mode?: "normal" | "continue" | "resume";
664
- skipPermissions?: boolean;
665
- envOverrides?: Record<string, string>;
666
- model?: string;
667
- sessionId?: string | null;
668
- } = {
669
- mode:
670
- mode === "resume"
671
- ? "resume"
672
- : mode === "continue"
673
- ? "continue"
674
- : "normal",
675
- skipPermissions,
676
- envOverrides: sharedEnv,
677
- sessionId: resumeSessionId,
678
- };
679
- if (model) {
680
- launchOptions.model = model;
681
- }
682
- launchResult = await launchQwenCLI(worktreePath, launchOptions);
683
663
  } else {
684
664
  // Custom tool
685
665
  printInfo(`Launching custom tool: ${toolConfig.displayName}`);
@@ -743,7 +723,7 @@ export async function handleAIToolWorkflow(
743
723
  }
744
724
  } else if (!finalSessionId && tool === "gemini-cli") {
745
725
  try {
746
- const latestGemini = await findLatestGeminiSession(worktreePath, {
726
+ const latestGemini = await findLatestGeminiSession({
747
727
  since: launchStartedAt - 60_000,
748
728
  until: finishedAt + 60_000,
749
729
  preferClosestTo: finishedAt,