@akiojin/gwt 2.2.0 → 2.4.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 (172) hide show
  1. package/README.ja.md +6 -4
  2. package/README.md +6 -4
  3. package/dist/claude.d.ts +1 -0
  4. package/dist/claude.d.ts.map +1 -1
  5. package/dist/claude.js +6 -3
  6. package/dist/claude.js.map +1 -1
  7. package/dist/cli/ui/components/App.d.ts +6 -4
  8. package/dist/cli/ui/components/App.d.ts.map +1 -1
  9. package/dist/cli/ui/components/App.js +184 -107
  10. package/dist/cli/ui/components/App.js.map +1 -1
  11. package/dist/cli/ui/components/common/Confirm.d.ts +1 -1
  12. package/dist/cli/ui/components/common/Confirm.d.ts.map +1 -1
  13. package/dist/cli/ui/components/common/Confirm.js +7 -7
  14. package/dist/cli/ui/components/common/Confirm.js.map +1 -1
  15. package/dist/cli/ui/components/common/ErrorBoundary.d.ts +1 -1
  16. package/dist/cli/ui/components/common/ErrorBoundary.d.ts.map +1 -1
  17. package/dist/cli/ui/components/common/ErrorBoundary.js +4 -4
  18. package/dist/cli/ui/components/common/ErrorBoundary.js.map +1 -1
  19. package/dist/cli/ui/components/common/Input.d.ts +2 -2
  20. package/dist/cli/ui/components/common/Input.d.ts.map +1 -1
  21. package/dist/cli/ui/components/common/Input.js +4 -4
  22. package/dist/cli/ui/components/common/Input.js.map +1 -1
  23. package/dist/cli/ui/components/common/LoadingIndicator.d.ts +1 -1
  24. package/dist/cli/ui/components/common/LoadingIndicator.d.ts.map +1 -1
  25. package/dist/cli/ui/components/common/LoadingIndicator.js +4 -4
  26. package/dist/cli/ui/components/common/LoadingIndicator.js.map +1 -1
  27. package/dist/cli/ui/components/common/Select.d.ts +1 -1
  28. package/dist/cli/ui/components/common/Select.d.ts.map +1 -1
  29. package/dist/cli/ui/components/common/Select.js +11 -12
  30. package/dist/cli/ui/components/common/Select.js.map +1 -1
  31. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +3 -3
  32. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +1 -1
  33. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js +11 -11
  34. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +1 -1
  35. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts +1 -1
  36. package/dist/cli/ui/components/screens/BranchCreatorScreen.d.ts.map +1 -1
  37. package/dist/cli/ui/components/screens/BranchCreatorScreen.js +39 -36
  38. package/dist/cli/ui/components/screens/BranchCreatorScreen.js.map +1 -1
  39. package/dist/cli/ui/components/screens/BranchListScreen.d.ts +3 -3
  40. package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
  41. package/dist/cli/ui/components/screens/BranchListScreen.js +55 -50
  42. package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
  43. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +2 -2
  44. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
  45. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +25 -25
  46. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
  47. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts +18 -0
  48. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +1 -0
  49. package/dist/cli/ui/components/screens/ModelSelectorScreen.js +201 -0
  50. package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +1 -0
  51. package/dist/cli/ui/components/screens/PRCleanupScreen.d.ts +2 -2
  52. package/dist/cli/ui/components/screens/PRCleanupScreen.js +21 -21
  53. package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +1 -1
  54. package/dist/cli/ui/components/screens/SessionSelectorScreen.js +8 -8
  55. package/dist/cli/ui/components/screens/WorktreeManagerScreen.d.ts +1 -1
  56. package/dist/cli/ui/components/screens/WorktreeManagerScreen.js +8 -8
  57. package/dist/cli/ui/screens/BranchActionSelectorScreen.d.ts.map +1 -1
  58. package/dist/cli/ui/screens/BranchActionSelectorScreen.js +7 -4
  59. package/dist/cli/ui/screens/BranchActionSelectorScreen.js.map +1 -1
  60. package/dist/cli/ui/types.d.ts +11 -1
  61. package/dist/cli/ui/types.d.ts.map +1 -1
  62. package/dist/cli/ui/utils/modelOptions.d.ts +6 -0
  63. package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -0
  64. package/dist/cli/ui/utils/modelOptions.js +111 -0
  65. package/dist/cli/ui/utils/modelOptions.js.map +1 -0
  66. package/dist/client/assets/{index-V6hDu9KS.js → index-Difv1Hwu.js} +2 -2
  67. package/dist/client/index.html +1 -1
  68. package/dist/codex.d.ts +6 -0
  69. package/dist/codex.d.ts.map +1 -1
  70. package/dist/codex.js +11 -4
  71. package/dist/codex.js.map +1 -1
  72. package/dist/config/builtin-tools.d.ts +10 -2
  73. package/dist/config/builtin-tools.d.ts.map +1 -1
  74. package/dist/config/builtin-tools.js +40 -4
  75. package/dist/config/builtin-tools.js.map +1 -1
  76. package/dist/config/index.d.ts.map +1 -1
  77. package/dist/config/index.js.map +1 -1
  78. package/dist/config/tools.d.ts.map +1 -1
  79. package/dist/config/tools.js +4 -3
  80. package/dist/config/tools.js.map +1 -1
  81. package/dist/gemini.d.ts +13 -0
  82. package/dist/gemini.d.ts.map +1 -0
  83. package/dist/gemini.js +157 -0
  84. package/dist/gemini.js.map +1 -0
  85. package/dist/git.d.ts.map +1 -1
  86. package/dist/git.js.map +1 -1
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +59 -7
  89. package/dist/index.js.map +1 -1
  90. package/dist/qwen.d.ts +13 -0
  91. package/dist/qwen.d.ts.map +1 -0
  92. package/dist/qwen.js +157 -0
  93. package/dist/qwen.js.map +1 -0
  94. package/dist/services/git.service.d.ts.map +1 -1
  95. package/dist/services/git.service.js.map +1 -1
  96. package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
  97. package/dist/web/client/src/components/BranchGraph.js +1 -1
  98. package/dist/web/client/src/components/BranchGraph.js.map +1 -1
  99. package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
  100. package/dist/web/client/src/components/EnvEditor.js +7 -4
  101. package/dist/web/client/src/components/EnvEditor.js.map +1 -1
  102. package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
  103. package/dist/web/client/src/pages/BranchDetailPage.js +55 -18
  104. package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
  105. package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
  106. package/dist/web/client/src/pages/BranchListPage.js +10 -4
  107. package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
  108. package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
  109. package/dist/web/client/src/pages/ConfigManagementPage.js +4 -2
  110. package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
  111. package/package.json +2 -1
  112. package/src/claude.ts +8 -3
  113. package/src/cli/ui/__tests__/acceptance/navigation.acceptance.test.tsx +69 -50
  114. package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +67 -45
  115. package/src/cli/ui/__tests__/components/App.shortcuts.test.tsx +117 -75
  116. package/src/cli/ui/__tests__/components/App.test.tsx +45 -37
  117. package/src/cli/ui/__tests__/components/ModelSelectorScreen.initial.test.tsx +81 -0
  118. package/src/cli/ui/__tests__/components/common/Confirm.test.tsx +35 -22
  119. package/src/cli/ui/__tests__/components/common/ErrorBoundary.test.tsx +22 -22
  120. package/src/cli/ui/__tests__/components/common/Input.test.tsx +29 -22
  121. package/src/cli/ui/__tests__/components/common/LoadingIndicator.test.tsx +63 -43
  122. package/src/cli/ui/__tests__/components/common/Select.memo.test.tsx +57 -66
  123. package/src/cli/ui/__tests__/components/common/Select.test.tsx +121 -91
  124. package/src/cli/ui/__tests__/components/parts/Footer.test.tsx +18 -16
  125. package/src/cli/ui/__tests__/components/parts/Header.test.tsx +13 -13
  126. package/src/cli/ui/__tests__/components/parts/ScrollableList.test.tsx +20 -20
  127. package/src/cli/ui/__tests__/components/parts/Stats.test.tsx +38 -26
  128. package/src/cli/ui/__tests__/components/screens/AIToolSelectorScreen.test.tsx +31 -31
  129. package/src/cli/ui/__tests__/components/screens/BranchCreatorScreen.test.tsx +73 -37
  130. package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +261 -153
  131. package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +38 -32
  132. package/src/cli/ui/__tests__/components/screens/PRCleanupScreen.test.tsx +39 -39
  133. package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +49 -21
  134. package/src/cli/ui/__tests__/components/screens/WorktreeManagerScreen.test.tsx +52 -28
  135. package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +84 -48
  136. package/src/cli/ui/__tests__/integration/navigation.test.tsx +111 -83
  137. package/src/cli/ui/__tests__/integration/realtimeUpdate.test.tsx +111 -108
  138. package/src/cli/ui/__tests__/performance/branchList.performance.test.tsx +50 -37
  139. package/src/cli/ui/__tests__/performance/useMemoOptimization.test.tsx +75 -76
  140. package/src/cli/ui/components/App.tsx +317 -150
  141. package/src/cli/ui/components/common/Confirm.tsx +13 -9
  142. package/src/cli/ui/components/common/ErrorBoundary.tsx +8 -5
  143. package/src/cli/ui/components/common/Input.tsx +12 -4
  144. package/src/cli/ui/components/common/LoadingIndicator.tsx +8 -5
  145. package/src/cli/ui/components/common/Select.tsx +28 -17
  146. package/src/cli/ui/components/parts/Header.test.tsx +5 -15
  147. package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +20 -15
  148. package/src/cli/ui/components/screens/BranchCreatorScreen.tsx +74 -54
  149. package/src/cli/ui/components/screens/BranchListScreen.tsx +92 -75
  150. package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +35 -28
  151. package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +320 -0
  152. package/src/cli/ui/components/screens/PRCleanupScreen.tsx +22 -22
  153. package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +8 -8
  154. package/src/cli/ui/components/screens/WorktreeManagerScreen.tsx +8 -8
  155. package/src/cli/ui/screens/BranchActionSelectorScreen.tsx +9 -4
  156. package/src/cli/ui/types.ts +21 -1
  157. package/src/cli/ui/utils/modelOptions.test.ts +36 -0
  158. package/src/cli/ui/utils/modelOptions.ts +122 -0
  159. package/src/codex.ts +23 -4
  160. package/src/config/builtin-tools.ts +42 -4
  161. package/src/config/index.ts +2 -12
  162. package/src/config/tools.ts +16 -6
  163. package/src/gemini.ts +207 -0
  164. package/src/git.ts +2 -1
  165. package/src/index.ts +86 -6
  166. package/src/qwen.ts +213 -0
  167. package/src/services/git.service.ts +2 -1
  168. package/src/web/client/src/components/BranchGraph.tsx +3 -2
  169. package/src/web/client/src/components/EnvEditor.tsx +44 -11
  170. package/src/web/client/src/pages/BranchDetailPage.tsx +165 -54
  171. package/src/web/client/src/pages/BranchListPage.tsx +37 -13
  172. package/src/web/client/src/pages/ConfigManagementPage.tsx +28 -9
@@ -1,30 +1,32 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
- import { useApp } from 'ink';
3
- import { ErrorBoundary } from './common/ErrorBoundary.js';
4
- import { BranchListScreen } from './screens/BranchListScreen.js';
5
- import { WorktreeManagerScreen } from './screens/WorktreeManagerScreen.js';
6
- import { BranchCreatorScreen } from './screens/BranchCreatorScreen.js';
7
- import { BranchActionSelectorScreen } from '../screens/BranchActionSelectorScreen.js';
8
- import { AIToolSelectorScreen } from './screens/AIToolSelectorScreen.js';
9
- import { SessionSelectorScreen } from './screens/SessionSelectorScreen.js';
10
- import { ExecutionModeSelectorScreen } from './screens/ExecutionModeSelectorScreen.js';
11
- import { useGitData } from '../hooks/useGitData.js';
12
- import { useScreenState } from '../hooks/useScreenState.js';
13
- import { formatBranchItems } from '../utils/branchFormatter.js';
14
- import { calculateStatistics } from '../utils/statisticsCalculator.js';
15
- import { getRepositoryRoot, deleteBranch } from '../../../git.js';
16
- import { createWorktree, generateWorktreePath, getMergedPRWorktrees, isProtectedBranchName, removeWorktree, switchToProtectedBranch, } from '../../../worktree.js';
17
- import { getPackageVersion } from '../../../utils.js';
18
- import { resolveBaseBranchLabel, resolveBaseBranchRef, } from '../utils/baseBranch.js';
19
- const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧'];
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react";
2
+ import { useApp } from "ink";
3
+ import { ErrorBoundary } from "./common/ErrorBoundary.js";
4
+ import { BranchListScreen } from "./screens/BranchListScreen.js";
5
+ import { WorktreeManagerScreen } from "./screens/WorktreeManagerScreen.js";
6
+ import { BranchCreatorScreen } from "./screens/BranchCreatorScreen.js";
7
+ import { BranchActionSelectorScreen } from "../screens/BranchActionSelectorScreen.js";
8
+ import { AIToolSelectorScreen } from "./screens/AIToolSelectorScreen.js";
9
+ import { SessionSelectorScreen } from "./screens/SessionSelectorScreen.js";
10
+ import { ExecutionModeSelectorScreen } from "./screens/ExecutionModeSelectorScreen.js";
11
+ import { ModelSelectorScreen, } from "./screens/ModelSelectorScreen.js";
12
+ import { useGitData } from "../hooks/useGitData.js";
13
+ import { useScreenState } from "../hooks/useScreenState.js";
14
+ import { formatBranchItems } from "../utils/branchFormatter.js";
15
+ import { calculateStatistics } from "../utils/statisticsCalculator.js";
16
+ import { getRepositoryRoot, deleteBranch } from "../../../git.js";
17
+ import { createWorktree, generateWorktreePath, getMergedPRWorktrees, isProtectedBranchName, removeWorktree, switchToProtectedBranch, } from "../../../worktree.js";
18
+ import { getPackageVersion } from "../../../utils.js";
19
+ import { resolveBaseBranchLabel, resolveBaseBranchRef, } from "../utils/baseBranch.js";
20
+ import { getDefaultInferenceForModel, getDefaultModelOption, } from "../utils/modelOptions.js";
21
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧"];
20
22
  const COMPLETION_HOLD_DURATION_MS = 3000;
21
- const PROTECTED_BRANCH_WARNING = 'Root branches operate directly in the repository root. Create a new branch if you need a dedicated worktree.';
23
+ const PROTECTED_BRANCH_WARNING = "Root branches operate directly in the repository root. Create a new branch if you need a dedicated worktree.";
22
24
  const getSpinnerFrame = (index) => {
23
25
  const frame = SPINNER_FRAMES[index];
24
- if (typeof frame === 'string') {
26
+ if (typeof frame === "string") {
25
27
  return frame;
26
28
  }
27
- return SPINNER_FRAMES[0] ?? '';
29
+ return SPINNER_FRAMES[0] ?? "";
28
30
  };
29
31
  /**
30
32
  * App - Top-level component for Ink.js UI
@@ -44,6 +46,8 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
44
46
  const [selectedBranch, setSelectedBranch] = useState(null);
45
47
  const [creationSourceBranch, setCreationSourceBranch] = useState(null);
46
48
  const [selectedTool, setSelectedTool] = useState(null);
49
+ const [selectedModel, setSelectedModel] = useState(null);
50
+ const [lastModelByTool, setLastModelByTool] = useState({});
47
51
  // PR cleanup feedback
48
52
  const [cleanupIndicators, setCleanupIndicators] = useState({});
49
53
  const [cleanupProcessingBranch, setCleanupProcessingBranch] = useState(null);
@@ -66,7 +70,8 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
66
70
  return undefined;
67
71
  }
68
72
  const interval = setInterval(() => {
69
- spinnerFrameIndexRef.current = (spinnerFrameIndexRef.current + 1) % SPINNER_FRAMES.length;
73
+ spinnerFrameIndexRef.current =
74
+ (spinnerFrameIndexRef.current + 1) % SPINNER_FRAMES.length;
70
75
  setSpinnerFrameIndex(spinnerFrameIndexRef.current);
71
76
  }, 120);
72
77
  return () => {
@@ -83,17 +88,17 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
83
88
  if (cleanupProcessingBranch) {
84
89
  setCleanupIndicators((prev) => {
85
90
  const current = prev[cleanupProcessingBranch];
86
- if (current && current.icon === frame && current.color === 'cyan') {
91
+ if (current && current.icon === frame && current.color === "cyan") {
87
92
  return prev;
88
93
  }
89
94
  const next = {
90
95
  ...prev,
91
- [cleanupProcessingBranch]: { icon: frame, color: 'cyan' },
96
+ [cleanupProcessingBranch]: { icon: frame, color: "cyan" },
92
97
  };
93
98
  return next;
94
99
  });
95
100
  }
96
- setCleanupFooterMessage({ text: `Processing... ${frame}`, color: 'cyan' });
101
+ setCleanupFooterMessage({ text: `Processing... ${frame}`, color: "cyan" });
97
102
  }, [cleanupInputLocked, cleanupProcessingBranch, spinnerFrameIndex]);
98
103
  useEffect(() => {
99
104
  if (!hiddenBranches.length) {
@@ -113,9 +118,11 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
113
118
  }, []);
114
119
  const visibleBranches = useMemo(() => branches.filter((branch) => !hiddenBranches.includes(branch.name)), [branches, hiddenBranches]);
115
120
  // Helper function to create content-based hash for branches
116
- const branchHash = useMemo(() => visibleBranches.map((b) => `${b.name}-${b.type}-${b.isCurrent}`).join(','), [visibleBranches]);
121
+ const branchHash = useMemo(() => visibleBranches
122
+ .map((b) => `${b.name}-${b.type}-${b.isCurrent}`)
123
+ .join(","), [visibleBranches]);
117
124
  // Helper function to create content-based hash for worktrees
118
- const worktreeHash = useMemo(() => worktrees.map((w) => `${w.branch}-${w.path}`).join(','), [worktrees]);
125
+ const worktreeHash = useMemo(() => worktrees.map((w) => `${w.branch}-${w.path}`).join(","), [worktrees]);
119
126
  // Format branches to BranchItems (memoized for performance with content-based dependencies)
120
127
  const branchItems = useMemo(() => {
121
128
  // Build worktreeMap for sorting
@@ -139,46 +146,48 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
139
146
  isAccessible: wt.isAccessible ?? true,
140
147
  })), [worktrees]);
141
148
  const resolveBaseBranch = useCallback(() => {
142
- const localMain = branches.find((branch) => branch.type === 'local' && (branch.name === 'main' || branch.name === 'master'));
149
+ const localMain = branches.find((branch) => branch.type === "local" &&
150
+ (branch.name === "main" || branch.name === "master"));
143
151
  if (localMain) {
144
152
  return localMain.name;
145
153
  }
146
- const develop = branches.find((branch) => branch.type === 'local' && (branch.name === 'develop' || branch.name === 'dev'));
154
+ const develop = branches.find((branch) => branch.type === "local" &&
155
+ (branch.name === "develop" || branch.name === "dev"));
147
156
  if (develop) {
148
157
  return develop.name;
149
158
  }
150
- return 'main';
159
+ return "main";
151
160
  }, [branches]);
152
161
  const baseBranchLabel = useMemo(() => resolveBaseBranchLabel(creationSourceBranch, selectedBranch, resolveBaseBranch), [creationSourceBranch, resolveBaseBranch, selectedBranch]);
153
162
  // Handle branch selection
154
163
  const toLocalBranchName = useCallback((remoteName) => {
155
- const segments = remoteName.split('/');
164
+ const segments = remoteName.split("/");
156
165
  if (segments.length <= 1) {
157
166
  return remoteName;
158
167
  }
159
- return segments.slice(1).join('/');
168
+ return segments.slice(1).join("/");
160
169
  }, []);
161
170
  const inferBranchCategory = useCallback((branchName) => {
162
171
  const matched = branches.find((branch) => branch.name === branchName);
163
172
  if (matched) {
164
173
  return matched.branchType;
165
174
  }
166
- if (branchName === 'main' || branchName === 'master') {
167
- return 'main';
175
+ if (branchName === "main" || branchName === "master") {
176
+ return "main";
168
177
  }
169
- if (branchName === 'develop' || branchName === 'dev') {
170
- return 'develop';
178
+ if (branchName === "develop" || branchName === "dev") {
179
+ return "develop";
171
180
  }
172
- if (branchName.startsWith('feature/')) {
173
- return 'feature';
181
+ if (branchName.startsWith("feature/")) {
182
+ return "feature";
174
183
  }
175
- if (branchName.startsWith('hotfix/')) {
176
- return 'hotfix';
184
+ if (branchName.startsWith("hotfix/")) {
185
+ return "hotfix";
177
186
  }
178
- if (branchName.startsWith('release/')) {
179
- return 'release';
187
+ if (branchName.startsWith("release/")) {
188
+ return "release";
180
189
  }
181
- return 'other';
190
+ return "other";
182
191
  }, [branches]);
183
192
  const isProtectedSelection = useCallback((branch) => {
184
193
  if (!branch) {
@@ -186,9 +195,11 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
186
195
  }
187
196
  return (isProtectedBranchName(branch.name) ||
188
197
  isProtectedBranchName(branch.displayName) ||
189
- (branch.remoteBranch ? isProtectedBranchName(branch.remoteBranch) : false) ||
190
- branch.branchCategory === 'main' ||
191
- branch.branchCategory === 'develop');
198
+ (branch.remoteBranch
199
+ ? isProtectedBranchName(branch.remoteBranch)
200
+ : false) ||
201
+ branch.branchCategory === "main" ||
202
+ branch.branchCategory === "develop");
192
203
  }, [isProtectedBranchName]);
193
204
  const protectedBranchInfo = useMemo(() => {
194
205
  if (!selectedBranch) {
@@ -204,35 +215,43 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
204
215
  };
205
216
  }, [selectedBranch, isProtectedSelection]);
206
217
  const handleSelect = useCallback((item) => {
207
- const selection = item.type === 'remote'
218
+ const selection = item.type === "remote"
208
219
  ? {
209
220
  name: toLocalBranchName(item.name),
210
221
  displayName: item.name,
211
- branchType: 'remote',
222
+ branchType: "remote",
212
223
  branchCategory: item.branchType,
213
224
  remoteBranch: item.name,
214
225
  }
215
226
  : {
216
227
  name: item.name,
217
228
  displayName: item.name,
218
- branchType: 'local',
229
+ branchType: "local",
219
230
  branchCategory: item.branchType,
220
231
  };
221
232
  const protectedSelected = isProtectedSelection(selection);
222
233
  setSelectedBranch(selection);
223
234
  setSelectedTool(null);
235
+ setSelectedModel(null);
224
236
  setCreationSourceBranch(null);
225
237
  if (protectedSelected) {
226
238
  setCleanupFooterMessage({
227
239
  text: PROTECTED_BRANCH_WARNING,
228
- color: 'yellow',
240
+ color: "yellow",
229
241
  });
230
242
  }
231
243
  else {
232
244
  setCleanupFooterMessage(null);
233
245
  }
234
- navigateTo('branch-action-selector');
235
- }, [isProtectedSelection, navigateTo, setCleanupFooterMessage, setCreationSourceBranch, setSelectedTool, toLocalBranchName]);
246
+ navigateTo("branch-action-selector");
247
+ }, [
248
+ isProtectedSelection,
249
+ navigateTo,
250
+ setCleanupFooterMessage,
251
+ setCreationSourceBranch,
252
+ setSelectedTool,
253
+ toLocalBranchName,
254
+ ]);
236
255
  // Handle navigation
237
256
  const handleNavigate = useCallback((screen) => {
238
257
  navigateTo(screen);
@@ -241,14 +260,20 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
241
260
  setSelectedBranch({
242
261
  name: worktree.branch,
243
262
  displayName: worktree.branch,
244
- branchType: 'local',
263
+ branchType: "local",
245
264
  branchCategory: inferBranchCategory(worktree.branch),
246
265
  });
247
266
  setSelectedTool(null);
267
+ setSelectedModel(null);
248
268
  setCreationSourceBranch(null);
249
269
  setCleanupFooterMessage(null);
250
- navigateTo('ai-tool-selector');
251
- }, [inferBranchCategory, navigateTo, setCleanupFooterMessage, setCreationSourceBranch]);
270
+ navigateTo("ai-tool-selector");
271
+ }, [
272
+ inferBranchCategory,
273
+ navigateTo,
274
+ setCleanupFooterMessage,
275
+ setCreationSourceBranch,
276
+ ]);
252
277
  // Handle branch action selection
253
278
  const handleProtectedBranchSwitch = useCallback(async () => {
254
279
  if (!selectedBranch) {
@@ -257,12 +282,12 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
257
282
  try {
258
283
  setCleanupFooterMessage({
259
284
  text: `Preparing root branch '${selectedBranch.displayName ?? selectedBranch.name}'...`,
260
- color: 'cyan',
285
+ color: "cyan",
261
286
  });
262
287
  const repoRoot = await getRepositoryRoot();
263
288
  const remoteRef = selectedBranch.remoteBranch ??
264
- (selectedBranch.branchType === 'remote'
265
- ? selectedBranch.displayName ?? selectedBranch.name
289
+ (selectedBranch.branchType === "remote"
290
+ ? (selectedBranch.displayName ?? selectedBranch.name)
266
291
  : null);
267
292
  const result = await switchToProtectedBranch({
268
293
  branchName: selectedBranch.name,
@@ -270,43 +295,43 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
270
295
  remoteRef: remoteRef ?? null,
271
296
  });
272
297
  let successMessage = `'${selectedBranch.displayName ?? selectedBranch.name}' will use the repository root.`;
273
- if (result === 'remote') {
298
+ if (result === "remote") {
274
299
  successMessage = `Created a local tracking branch for '${selectedBranch.displayName ?? selectedBranch.name}' and switched to the protected branch.`;
275
300
  }
276
- else if (result === 'local') {
301
+ else if (result === "local") {
277
302
  successMessage = `Checked out '${selectedBranch.displayName ?? selectedBranch.name}' in the repository root.`;
278
303
  }
279
304
  setCleanupFooterMessage({
280
305
  text: successMessage,
281
- color: 'green',
306
+ color: "green",
282
307
  });
283
308
  refresh();
284
- navigateTo('ai-tool-selector');
309
+ navigateTo("ai-tool-selector");
285
310
  }
286
311
  catch (error) {
287
312
  const message = error instanceof Error ? error.message : String(error);
288
313
  setCleanupFooterMessage({
289
314
  text: `Failed to switch root branch: ${message}`,
290
- color: 'red',
315
+ color: "red",
291
316
  });
292
- console.error('Failed to switch protected branch:', error);
317
+ console.error("Failed to switch protected branch:", error);
293
318
  }
294
- }, [
295
- navigateTo,
296
- refresh,
297
- selectedBranch,
298
- setCleanupFooterMessage,
299
- ]);
319
+ }, [navigateTo, refresh, selectedBranch, setCleanupFooterMessage]);
300
320
  const handleUseExistingBranch = useCallback(() => {
301
321
  if (selectedBranch && isProtectedSelection(selectedBranch)) {
302
322
  void handleProtectedBranchSwitch();
303
323
  return;
304
324
  }
305
- navigateTo('ai-tool-selector');
306
- }, [handleProtectedBranchSwitch, isProtectedSelection, navigateTo, selectedBranch]);
325
+ navigateTo("ai-tool-selector");
326
+ }, [
327
+ handleProtectedBranchSwitch,
328
+ isProtectedSelection,
329
+ navigateTo,
330
+ selectedBranch,
331
+ ]);
307
332
  const handleCreateNewBranch = useCallback(() => {
308
333
  setCreationSourceBranch(selectedBranch);
309
- navigateTo('branch-creator');
334
+ navigateTo("branch-creator");
310
335
  }, [navigateTo, selectedBranch]);
311
336
  // Handle quit
312
337
  const handleQuit = useCallback(() => {
@@ -332,16 +357,17 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
332
357
  setSelectedBranch({
333
358
  name: branchName,
334
359
  displayName: branchName,
335
- branchType: 'local',
360
+ branchType: "local",
336
361
  branchCategory: inferBranchCategory(branchName),
337
362
  });
338
363
  setSelectedTool(null);
364
+ setSelectedModel(null);
339
365
  setCleanupFooterMessage(null);
340
- navigateTo('ai-tool-selector');
366
+ navigateTo("ai-tool-selector");
341
367
  }
342
368
  catch (error) {
343
369
  // On error, go back to branch list
344
- console.error('Failed to create branch:', error);
370
+ console.error("Failed to create branch:", error);
345
371
  goBack();
346
372
  refresh();
347
373
  }
@@ -382,7 +408,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
382
408
  setCleanupInputLocked(true);
383
409
  setCleanupIndicators({});
384
410
  const initialFrame = getSpinnerFrame(0);
385
- setCleanupFooterMessage({ text: `Processing... ${initialFrame}`, color: 'cyan' });
411
+ setCleanupFooterMessage({
412
+ text: `Processing... ${initialFrame}`,
413
+ color: "cyan",
414
+ });
386
415
  setCleanupProcessingBranch(null);
387
416
  spinnerFrameIndexRef.current = 0;
388
417
  setSpinnerFrameIndex(0);
@@ -393,7 +422,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
393
422
  catch (error) {
394
423
  const message = error instanceof Error ? error.message : String(error);
395
424
  setCleanupIndicators({});
396
- setCleanupFooterMessage({ text: `❌ ${message}`, color: 'red' });
425
+ setCleanupFooterMessage({ text: `❌ ${message}`, color: "red" });
397
426
  setCleanupInputLocked(false);
398
427
  completionTimerRef.current = setTimeout(() => {
399
428
  setCleanupFooterMessage(null);
@@ -403,7 +432,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
403
432
  }
404
433
  if (targets.length === 0) {
405
434
  setCleanupIndicators({});
406
- setCleanupFooterMessage({ text: '✅ Nothing to clean up.', color: 'green' });
435
+ setCleanupFooterMessage({
436
+ text: "✅ Nothing to clean up.",
437
+ color: "green",
438
+ });
407
439
  setCleanupInputLocked(false);
408
440
  completionTimerRef.current = setTimeout(() => {
409
441
  setCleanupFooterMessage(null);
@@ -414,8 +446,8 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
414
446
  // Reset hidden branches that may already be gone
415
447
  setHiddenBranches((prev) => prev.filter((name) => targets.find((t) => t.branch === name) === undefined));
416
448
  const initialIndicators = targets.reduce((acc, target, index) => {
417
- const icon = index === 0 ? getSpinnerFrame(0) : '';
418
- const color = index === 0 ? 'cyan' : 'yellow';
449
+ const icon = index === 0 ? getSpinnerFrame(0) : "";
450
+ const color = index === 0 ? "cyan" : "yellow";
419
451
  acc[target.branch] = { icon, color };
420
452
  return acc;
421
453
  }, {});
@@ -424,7 +456,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
424
456
  setCleanupProcessingBranch(firstTarget ? firstTarget.branch : null);
425
457
  spinnerFrameIndexRef.current = 0;
426
458
  setSpinnerFrameIndex(0);
427
- setCleanupFooterMessage({ text: `Processing... ${getSpinnerFrame(0)}`, color: 'cyan' });
459
+ setCleanupFooterMessage({
460
+ text: `Processing... ${getSpinnerFrame(0)}`,
461
+ color: "cyan",
462
+ });
428
463
  for (let index = 0; index < targets.length; index += 1) {
429
464
  const currentTarget = targets[index];
430
465
  if (!currentTarget) {
@@ -436,59 +471,79 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
436
471
  setSpinnerFrameIndex(0);
437
472
  setCleanupIndicators((prev) => {
438
473
  const updated = { ...prev };
439
- updated[target.branch] = { icon: getSpinnerFrame(0), color: 'cyan' };
474
+ updated[target.branch] = { icon: getSpinnerFrame(0), color: "cyan" };
440
475
  for (const pending of targets.slice(index + 1)) {
441
476
  const current = updated[pending.branch];
442
- if (!current || current.icon !== '') {
443
- updated[pending.branch] = { icon: '', color: 'yellow' };
477
+ if (!current || current.icon !== "") {
478
+ updated[pending.branch] = { icon: "", color: "yellow" };
444
479
  }
445
480
  }
446
481
  return updated;
447
482
  });
448
483
  const shouldSkip = target.hasUncommittedChanges ||
449
484
  target.hasUnpushedCommits ||
450
- (target.cleanupType === 'worktree-and-branch' && (!target.worktreePath || target.isAccessible === false));
485
+ (target.cleanupType === "worktree-and-branch" &&
486
+ (!target.worktreePath || target.isAccessible === false));
451
487
  if (shouldSkip) {
452
488
  setCleanupIndicators((prev) => ({
453
489
  ...prev,
454
- [target.branch]: { icon: '⏭️', color: 'yellow' },
490
+ [target.branch]: { icon: "⏭️", color: "yellow" },
455
491
  }));
456
492
  setCleanupProcessingBranch(null);
457
493
  continue;
458
494
  }
459
495
  try {
460
- if (target.cleanupType === 'worktree-and-branch' && target.worktreePath) {
496
+ if (target.cleanupType === "worktree-and-branch" &&
497
+ target.worktreePath) {
461
498
  await removeWorktree(target.worktreePath, true);
462
499
  }
463
500
  await deleteBranch(target.branch, true);
464
501
  succeededBranches.push(target.branch);
465
502
  setCleanupIndicators((prev) => ({
466
503
  ...prev,
467
- [target.branch]: { icon: '', color: 'green' },
504
+ [target.branch]: { icon: "", color: "green" },
468
505
  }));
469
506
  }
470
507
  catch {
471
- const icon = '';
508
+ const icon = "";
472
509
  setCleanupIndicators((prev) => ({
473
510
  ...prev,
474
- [target.branch]: { icon, color: 'red' },
511
+ [target.branch]: { icon, color: "red" },
475
512
  }));
476
513
  }
477
514
  setCleanupProcessingBranch(null);
478
515
  }
479
516
  setCleanupProcessingBranch(null);
480
517
  setCleanupInputLocked(false);
481
- setCleanupFooterMessage({ text: 'Cleanup completed. Finalizing...', color: 'green' });
482
- const holdDuration = typeof process !== 'undefined' && process.env?.NODE_ENV === 'test'
518
+ setCleanupFooterMessage({
519
+ text: "Cleanup completed. Finalizing...",
520
+ color: "green",
521
+ });
522
+ const holdDuration = typeof process !== "undefined" && process.env?.NODE_ENV === "test"
483
523
  ? 0
484
524
  : COMPLETION_HOLD_DURATION_MS;
485
525
  completionTimerRef.current = setTimeout(resetAfterWait, holdDuration);
486
- }, [cleanupInputLocked, deleteBranch, getMergedPRWorktrees, refresh, removeWorktree]);
526
+ }, [
527
+ cleanupInputLocked,
528
+ deleteBranch,
529
+ getMergedPRWorktrees,
530
+ refresh,
531
+ removeWorktree,
532
+ ]);
487
533
  // Handle AI tool selection
488
534
  const handleToolSelect = useCallback((tool) => {
489
535
  setSelectedTool(tool);
490
- navigateTo('execution-mode-selector');
491
- }, [navigateTo]);
536
+ setSelectedModel(lastModelByTool[tool] ?? null);
537
+ navigateTo("model-selector");
538
+ }, [lastModelByTool, navigateTo]);
539
+ const handleModelSelect = useCallback((selection) => {
540
+ setSelectedModel(selection);
541
+ setLastModelByTool((prev) => ({
542
+ ...prev,
543
+ ...(selectedTool ? { [selectedTool]: selection } : {}),
544
+ }));
545
+ navigateTo("execution-mode-selector");
546
+ }, [navigateTo, selectedTool]);
492
547
  // Handle session selection
493
548
  const handleSessionSelect = useCallback((_session) => {
494
549
  // TODO: Load selected session and navigate to next screen
@@ -499,6 +554,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
499
554
  const handleModeSelect = useCallback((result) => {
500
555
  // All selections complete - exit with result
501
556
  if (selectedBranch && selectedTool) {
557
+ const defaultModel = getDefaultModelOption(selectedTool);
558
+ const resolvedModel = selectedModel?.model ?? defaultModel?.id ?? null;
559
+ const resolvedInference = selectedModel?.inferenceLevel ??
560
+ getDefaultInferenceForModel(defaultModel ?? undefined);
502
561
  const payload = {
503
562
  branch: selectedBranch.name,
504
563
  displayName: selectedBranch.displayName,
@@ -506,6 +565,10 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
506
565
  tool: selectedTool,
507
566
  mode: result.mode,
508
567
  skipPermissions: result.skipPermissions,
568
+ ...(resolvedModel !== undefined ? { model: resolvedModel } : {}),
569
+ ...(resolvedInference !== undefined
570
+ ? { inferenceLevel: resolvedInference }
571
+ : {}),
509
572
  ...(selectedBranch.remoteBranch
510
573
  ? { remoteBranch: selectedBranch.remoteBranch }
511
574
  : {}),
@@ -513,24 +576,32 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
513
576
  onExit(payload);
514
577
  exit();
515
578
  }
516
- }, [selectedBranch, selectedTool, onExit, exit]);
579
+ }, [
580
+ selectedBranch,
581
+ selectedTool,
582
+ selectedModel,
583
+ onExit,
584
+ exit,
585
+ getDefaultModelOption,
586
+ getDefaultInferenceForModel,
587
+ ]);
517
588
  // Render screen based on currentScreen
518
589
  const renderScreen = () => {
519
590
  switch (currentScreen) {
520
- case 'branch-list':
591
+ case "branch-list":
521
592
  return (React.createElement(BranchListScreen, { branches: branchItems, stats: stats, onSelect: handleSelect, onNavigate: handleNavigate, onQuit: handleQuit, onCleanupCommand: handleCleanupCommand, onRefresh: refresh, loading: loading, error: error, lastUpdated: lastUpdated, loadingIndicatorDelay: loadingIndicatorDelay, cleanupUI: {
522
593
  indicators: cleanupIndicators,
523
594
  footerMessage: cleanupFooterMessage,
524
595
  inputLocked: cleanupInputLocked,
525
596
  }, version: version, workingDirectory: workingDirectory }));
526
- case 'worktree-manager':
597
+ case "worktree-manager":
527
598
  return (React.createElement(WorktreeManagerScreen, { worktrees: worktreeItems, onBack: goBack, onSelect: handleWorktreeSelect, version: version }));
528
- case 'branch-creator':
599
+ case "branch-creator":
529
600
  return (React.createElement(BranchCreatorScreen, { onBack: goBack, onCreate: handleCreate, baseBranch: baseBranchLabel, version: version }));
530
- case 'branch-action-selector': {
601
+ case "branch-action-selector": {
531
602
  const isProtected = Boolean(protectedBranchInfo);
532
603
  const baseProps = {
533
- selectedBranch: selectedBranch?.displayName ?? '',
604
+ selectedBranch: selectedBranch?.displayName ?? "",
534
605
  onUseExisting: handleUseExistingBranch,
535
606
  onCreateNew: handleCreateNewBranch,
536
607
  onBack: goBack,
@@ -541,12 +612,18 @@ export function App({ onExit, loadingIndicatorDelay = 300 }) {
541
612
  }
542
613
  return React.createElement(BranchActionSelectorScreen, { ...baseProps });
543
614
  }
544
- case 'ai-tool-selector':
545
- return React.createElement(AIToolSelectorScreen, { onBack: goBack, onSelect: handleToolSelect, version: version });
546
- case 'session-selector':
615
+ case "ai-tool-selector":
616
+ return (React.createElement(AIToolSelectorScreen, { onBack: goBack, onSelect: handleToolSelect, version: version }));
617
+ case "model-selector":
618
+ if (!selectedTool) {
619
+ goBack();
620
+ return null;
621
+ }
622
+ return (React.createElement(ModelSelectorScreen, { tool: selectedTool, onBack: goBack, onSelect: handleModelSelect, version: version, initialSelection: selectedModel }));
623
+ case "session-selector":
547
624
  // TODO: Implement session data fetching
548
625
  return (React.createElement(SessionSelectorScreen, { sessions: [], onBack: goBack, onSelect: handleSessionSelect, version: version }));
549
- case 'execution-mode-selector':
626
+ case "execution-mode-selector":
550
627
  return (React.createElement(ExecutionModeSelectorScreen, { onBack: goBack, onSelect: handleModeSelect, version: version }));
551
628
  default:
552
629
  return (React.createElement(BranchListScreen, { branches: branchItems, stats: stats, onSelect: handleSelect, onNavigate: handleNavigate, onQuit: handleQuit, onRefresh: refresh, loading: loading, error: error, lastUpdated: lastUpdated, loadingIndicatorDelay: loadingIndicatorDelay, version: version, workingDirectory: workingDirectory }));