@akiojin/gwt 2.11.0 → 2.12.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.
- package/dist/claude.d.ts +4 -1
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +51 -7
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.d.ts +7 -0
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +307 -18
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts +21 -0
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +1 -0
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +145 -0
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +1 -0
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +2 -1
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +4 -2
- package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/ModelSelectorScreen.js +1 -1
- package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +10 -2
- package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/SessionSelectorScreen.js +18 -7
- package/dist/cli/ui/components/screens/SessionSelectorScreen.js.map +1 -1
- package/dist/cli/ui/types.d.ts +1 -1
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.js +0 -13
- package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
- package/dist/cli/ui/utils/continueSession.d.ts +18 -0
- package/dist/cli/ui/utils/continueSession.d.ts.map +1 -0
- package/dist/cli/ui/utils/continueSession.js +67 -0
- package/dist/cli/ui/utils/continueSession.js.map +1 -0
- package/dist/codex.d.ts +4 -1
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +70 -5
- package/dist/codex.js.map +1 -1
- package/dist/config/index.d.ts +9 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +11 -2
- package/dist/config/index.js.map +1 -1
- package/dist/gemini.d.ts +4 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +146 -32
- package/dist/gemini.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +118 -8
- package/dist/index.js.map +1 -1
- package/dist/qwen.d.ts +4 -1
- package/dist/qwen.d.ts.map +1 -1
- package/dist/qwen.js +45 -4
- package/dist/qwen.js.map +1 -1
- package/dist/utils/session.d.ts +82 -0
- package/dist/utils/session.d.ts.map +1 -0
- package/dist/utils/session.js +579 -0
- package/dist/utils/session.js.map +1 -0
- package/package.json +1 -1
- package/src/claude.ts +69 -8
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +12 -2
- package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +2 -2
- package/src/cli/ui/__tests__/components/screens/BranchQuickStartScreen.test.tsx +142 -0
- package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +14 -0
- package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +29 -10
- package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +4 -1
- package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +0 -1
- package/src/cli/ui/components/App.tsx +403 -23
- package/src/cli/ui/components/screens/BranchQuickStartScreen.tsx +237 -0
- package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +5 -1
- package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +1 -1
- package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +34 -6
- package/src/cli/ui/types.ts +1 -0
- package/src/cli/ui/utils/branchFormatter.ts +0 -13
- package/src/cli/ui/utils/continueSession.ts +106 -0
- package/src/codex.ts +91 -6
- package/src/config/index.ts +22 -2
- package/src/gemini.ts +179 -41
- package/src/index.ts +144 -16
- package/src/qwen.ts +56 -5
- package/src/utils/session.ts +704 -0
|
@@ -14,6 +14,8 @@ import { AIToolSelectorScreen } from "./screens/AIToolSelectorScreen.js";
|
|
|
14
14
|
import { SessionSelectorScreen } from "./screens/SessionSelectorScreen.js";
|
|
15
15
|
import { ExecutionModeSelectorScreen } from "./screens/ExecutionModeSelectorScreen.js";
|
|
16
16
|
import type { ExecutionMode } from "./screens/ExecutionModeSelectorScreen.js";
|
|
17
|
+
import { BranchQuickStartScreen } from "./screens/BranchQuickStartScreen.js";
|
|
18
|
+
import type { QuickStartAction } from "./screens/BranchQuickStartScreen.js";
|
|
17
19
|
import {
|
|
18
20
|
ModelSelectorScreen,
|
|
19
21
|
type ModelSelectionResult,
|
|
@@ -30,6 +32,7 @@ import type {
|
|
|
30
32
|
SelectedBranchState,
|
|
31
33
|
} from "../types.js";
|
|
32
34
|
import { getRepositoryRoot, deleteBranch } from "../../../git.js";
|
|
35
|
+
import { loadSession } from "../../../config/index.js";
|
|
33
36
|
import {
|
|
34
37
|
createWorktree,
|
|
35
38
|
generateWorktreePath,
|
|
@@ -47,6 +50,17 @@ import {
|
|
|
47
50
|
getDefaultInferenceForModel,
|
|
48
51
|
getDefaultModelOption,
|
|
49
52
|
} from "../utils/modelOptions.js";
|
|
53
|
+
import {
|
|
54
|
+
resolveContinueSessionId,
|
|
55
|
+
findLatestBranchSessionsByTool,
|
|
56
|
+
} from "../utils/continueSession.js";
|
|
57
|
+
import {
|
|
58
|
+
findLatestCodexSession,
|
|
59
|
+
findLatestCodexSessionId,
|
|
60
|
+
findLatestClaudeSession,
|
|
61
|
+
findLatestGeminiSession,
|
|
62
|
+
} from "../../../utils/session.js";
|
|
63
|
+
import type { ToolSessionEntry } from "../../../config/index.js";
|
|
50
64
|
|
|
51
65
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧"];
|
|
52
66
|
const COMPLETION_HOLD_DURATION_MS = 3000;
|
|
@@ -71,6 +85,7 @@ export interface SelectionResult {
|
|
|
71
85
|
skipPermissions: boolean;
|
|
72
86
|
model?: string | null;
|
|
73
87
|
inferenceLevel?: InferenceLevel;
|
|
88
|
+
sessionId?: string | null;
|
|
74
89
|
}
|
|
75
90
|
|
|
76
91
|
export interface AppProps {
|
|
@@ -78,6 +93,13 @@ export interface AppProps {
|
|
|
78
93
|
loadingIndicatorDelay?: number;
|
|
79
94
|
}
|
|
80
95
|
|
|
96
|
+
export interface SessionOption {
|
|
97
|
+
sessionId: string;
|
|
98
|
+
toolLabel: string;
|
|
99
|
+
branch: string;
|
|
100
|
+
timestamp: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
81
103
|
/**
|
|
82
104
|
* App - Top-level component for Ink.js UI
|
|
83
105
|
* Integrates ErrorBoundary, data fetching, screen navigation, and all screens
|
|
@@ -96,6 +118,30 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
96
118
|
|
|
97
119
|
// Version state
|
|
98
120
|
const [version, setVersion] = useState<string | null>(null);
|
|
121
|
+
const [repoRoot, setRepoRoot] = useState<string | null>(null);
|
|
122
|
+
const [sessionOptions, setSessionOptions] = useState<SessionOption[]>([]);
|
|
123
|
+
const [sessionLoading, setSessionLoading] = useState(false);
|
|
124
|
+
const [sessionError, setSessionError] = useState<string | null>(null);
|
|
125
|
+
const [pendingExecution, setPendingExecution] = useState<{
|
|
126
|
+
mode: ExecutionMode;
|
|
127
|
+
skipPermissions: boolean;
|
|
128
|
+
} | null>(null);
|
|
129
|
+
const [continueSessionId, setContinueSessionId] = useState<string | null>(
|
|
130
|
+
null,
|
|
131
|
+
);
|
|
132
|
+
const [branchQuickStart, setBranchQuickStart] = useState<
|
|
133
|
+
{
|
|
134
|
+
toolId: AITool;
|
|
135
|
+
toolLabel: string;
|
|
136
|
+
model?: string | null;
|
|
137
|
+
sessionId?: string | null;
|
|
138
|
+
inferenceLevel?: InferenceLevel | null;
|
|
139
|
+
skipPermissions?: boolean | null;
|
|
140
|
+
timestamp?: number | null;
|
|
141
|
+
}[]
|
|
142
|
+
>([]);
|
|
143
|
+
const [branchQuickStartLoading, setBranchQuickStartLoading] =
|
|
144
|
+
useState(false);
|
|
99
145
|
|
|
100
146
|
// Selection state (for branch → tool → mode flow)
|
|
101
147
|
const [selectedBranch, setSelectedBranch] =
|
|
@@ -137,6 +183,13 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
137
183
|
.catch(() => setVersion(null));
|
|
138
184
|
}, []);
|
|
139
185
|
|
|
186
|
+
// Fetch repository root once for session lookups
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
getRepositoryRoot()
|
|
189
|
+
.then(setRepoRoot)
|
|
190
|
+
.catch(() => setRepoRoot(null));
|
|
191
|
+
}, []);
|
|
192
|
+
|
|
140
193
|
useEffect(() => {
|
|
141
194
|
if (!cleanupInputLocked) {
|
|
142
195
|
spinnerFrameIndexRef.current = 0;
|
|
@@ -199,6 +252,225 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
199
252
|
}
|
|
200
253
|
}, [branches, hiddenBranches]);
|
|
201
254
|
|
|
255
|
+
// Load available sessions when entering session selector
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (currentScreen !== "session-selector") {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (!selectedTool || !selectedBranch) {
|
|
261
|
+
setSessionOptions([]);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
setSessionLoading(true);
|
|
266
|
+
setSessionError(null);
|
|
267
|
+
|
|
268
|
+
(async () => {
|
|
269
|
+
try {
|
|
270
|
+
const root = repoRoot ?? (await getRepositoryRoot());
|
|
271
|
+
if (!repoRoot && root) {
|
|
272
|
+
setRepoRoot(root);
|
|
273
|
+
}
|
|
274
|
+
const sessionData = root ? await loadSession(root) : null;
|
|
275
|
+
const history = sessionData?.history ?? [];
|
|
276
|
+
|
|
277
|
+
const filtered = history
|
|
278
|
+
.filter(
|
|
279
|
+
(entry) =>
|
|
280
|
+
entry.sessionId &&
|
|
281
|
+
entry.toolId === selectedTool &&
|
|
282
|
+
entry.branch === selectedBranch.name,
|
|
283
|
+
)
|
|
284
|
+
.sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0))
|
|
285
|
+
.map((entry) => ({
|
|
286
|
+
sessionId: entry.sessionId as string,
|
|
287
|
+
toolLabel: entry.toolLabel,
|
|
288
|
+
branch: entry.branch,
|
|
289
|
+
timestamp: entry.timestamp,
|
|
290
|
+
}));
|
|
291
|
+
|
|
292
|
+
setSessionOptions(filtered);
|
|
293
|
+
} catch (_err) {
|
|
294
|
+
setSessionOptions([]);
|
|
295
|
+
setSessionError("セッション一覧の取得に失敗しました");
|
|
296
|
+
} finally {
|
|
297
|
+
setSessionLoading(false);
|
|
298
|
+
}
|
|
299
|
+
})();
|
|
300
|
+
}, [currentScreen, selectedTool, selectedBranch, repoRoot]);
|
|
301
|
+
|
|
302
|
+
// Load quick start options for selected branch (latest per tool)
|
|
303
|
+
useEffect(() => {
|
|
304
|
+
if (!selectedBranch) {
|
|
305
|
+
setBranchQuickStart([]);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
let cancelled = false;
|
|
309
|
+
setBranchQuickStartLoading(true);
|
|
310
|
+
(async () => {
|
|
311
|
+
try {
|
|
312
|
+
const root = repoRoot ?? (await getRepositoryRoot());
|
|
313
|
+
if (!repoRoot && root) {
|
|
314
|
+
setRepoRoot(root);
|
|
315
|
+
}
|
|
316
|
+
if (!root) {
|
|
317
|
+
if (!cancelled) setBranchQuickStart([]);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const sessionData = await loadSession(root);
|
|
321
|
+
const history = sessionData?.history ?? [];
|
|
322
|
+
|
|
323
|
+
const combinedHistory = [...history];
|
|
324
|
+
if (
|
|
325
|
+
sessionData?.lastSessionId &&
|
|
326
|
+
sessionData.lastBranch === selectedBranch.name &&
|
|
327
|
+
sessionData.lastUsedTool
|
|
328
|
+
) {
|
|
329
|
+
const synthetic: ToolSessionEntry = {
|
|
330
|
+
branch: sessionData.lastBranch,
|
|
331
|
+
worktreePath: sessionData.lastWorktreePath ?? null,
|
|
332
|
+
toolId: sessionData.lastUsedTool,
|
|
333
|
+
toolLabel: sessionData.toolLabel ?? sessionData.lastUsedTool,
|
|
334
|
+
sessionId: sessionData.lastSessionId,
|
|
335
|
+
mode: sessionData.mode ?? null,
|
|
336
|
+
model: sessionData.model ?? null,
|
|
337
|
+
reasoningLevel: sessionData.reasoningLevel ?? null,
|
|
338
|
+
skipPermissions: sessionData.skipPermissions ?? null,
|
|
339
|
+
timestamp: sessionData.timestamp ?? Date.now(),
|
|
340
|
+
};
|
|
341
|
+
combinedHistory.push(synthetic);
|
|
342
|
+
}
|
|
343
|
+
const latestPerTool = findLatestBranchSessionsByTool(
|
|
344
|
+
combinedHistory,
|
|
345
|
+
selectedBranch.name,
|
|
346
|
+
selectedWorktreePath,
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const mapped = await Promise.all(
|
|
350
|
+
latestPerTool.map(async (entry) => {
|
|
351
|
+
let sessionId = entry.sessionId ?? null;
|
|
352
|
+
|
|
353
|
+
// For Codex, prefer a newer filesystem session over stale history
|
|
354
|
+
if (entry.toolId === "codex-cli") {
|
|
355
|
+
try {
|
|
356
|
+
const latestCodex = await findLatestCodexSession();
|
|
357
|
+
const historyTs = entry.timestamp ?? 0;
|
|
358
|
+
if (
|
|
359
|
+
latestCodex &&
|
|
360
|
+
(!sessionId || latestCodex.mtime > historyTs)
|
|
361
|
+
) {
|
|
362
|
+
sessionId = latestCodex.id;
|
|
363
|
+
}
|
|
364
|
+
// Fallback when filesystem unavailable and history missing
|
|
365
|
+
if (!sessionId) {
|
|
366
|
+
const latestId = await findLatestCodexSessionId();
|
|
367
|
+
if (latestId) sessionId = latestId;
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
// ignore lookup failure
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// For Claude Code, prefer the newest session file in the worktree even if history is stale.
|
|
375
|
+
if (entry.toolId === "claude-code") {
|
|
376
|
+
try {
|
|
377
|
+
const worktree =
|
|
378
|
+
selectedWorktreePath ??
|
|
379
|
+
selectedBranch?.displayName ??
|
|
380
|
+
workingDirectory;
|
|
381
|
+
|
|
382
|
+
// Always resolve freshest on-disk session for this worktree (no window restriction)
|
|
383
|
+
const latestAny = await findLatestClaudeSession(worktree);
|
|
384
|
+
sessionId = latestAny?.id ?? sessionId ?? null;
|
|
385
|
+
|
|
386
|
+
} catch {
|
|
387
|
+
// ignore lookup failure
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// For Gemini, prefer newest session file (Gemini keeps per-project chats)
|
|
392
|
+
if (entry.toolId === "gemini-cli") {
|
|
393
|
+
try {
|
|
394
|
+
const gemSession = await findLatestGeminiSession(
|
|
395
|
+
selectedWorktreePath ??
|
|
396
|
+
selectedBranch?.displayName ??
|
|
397
|
+
workingDirectory,
|
|
398
|
+
{
|
|
399
|
+
...(entry.timestamp !== null && entry.timestamp !== undefined
|
|
400
|
+
? { since: entry.timestamp - 60_000, preferClosestTo: entry.timestamp }
|
|
401
|
+
: {}),
|
|
402
|
+
windowMs: 60 * 60 * 1000,
|
|
403
|
+
},
|
|
404
|
+
);
|
|
405
|
+
if (gemSession?.id) sessionId = gemSession.id;
|
|
406
|
+
} catch {
|
|
407
|
+
// ignore
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
toolId: entry.toolId as AITool,
|
|
413
|
+
toolLabel: entry.toolLabel,
|
|
414
|
+
model: entry.model ?? null,
|
|
415
|
+
inferenceLevel: (entry.reasoningLevel ??
|
|
416
|
+
sessionData?.reasoningLevel ??
|
|
417
|
+
null) as InferenceLevel | null,
|
|
418
|
+
sessionId,
|
|
419
|
+
skipPermissions:
|
|
420
|
+
entry.skipPermissions ?? sessionData?.skipPermissions ?? null,
|
|
421
|
+
timestamp: entry.timestamp ?? null,
|
|
422
|
+
};
|
|
423
|
+
}),
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
if (!cancelled) {
|
|
427
|
+
setBranchQuickStart(mapped);
|
|
428
|
+
}
|
|
429
|
+
} catch {
|
|
430
|
+
if (!cancelled) setBranchQuickStart([]);
|
|
431
|
+
} finally {
|
|
432
|
+
if (!cancelled) setBranchQuickStartLoading(false);
|
|
433
|
+
}
|
|
434
|
+
})();
|
|
435
|
+
|
|
436
|
+
return () => {
|
|
437
|
+
cancelled = true;
|
|
438
|
+
};
|
|
439
|
+
}, [selectedBranch, repoRoot]);
|
|
440
|
+
|
|
441
|
+
// Load last session ID for "Continue" label when entering execution mode selector
|
|
442
|
+
useEffect(() => {
|
|
443
|
+
if (currentScreen !== "execution-mode-selector") {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (!selectedTool || !selectedBranch) {
|
|
447
|
+
setContinueSessionId(null);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
(async () => {
|
|
451
|
+
try {
|
|
452
|
+
const root = repoRoot ?? (await getRepositoryRoot());
|
|
453
|
+
if (!repoRoot && root) {
|
|
454
|
+
setRepoRoot(root);
|
|
455
|
+
}
|
|
456
|
+
const sessionData = root ? await loadSession(root) : null;
|
|
457
|
+
const history = sessionData?.history ?? [];
|
|
458
|
+
|
|
459
|
+
const found = await resolveContinueSessionId({
|
|
460
|
+
history,
|
|
461
|
+
sessionData,
|
|
462
|
+
branch: selectedBranch.name,
|
|
463
|
+
toolId: selectedTool,
|
|
464
|
+
repoRoot: root,
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
setContinueSessionId(found ?? null);
|
|
468
|
+
} catch {
|
|
469
|
+
setContinueSessionId(null);
|
|
470
|
+
}
|
|
471
|
+
})();
|
|
472
|
+
}, [currentScreen, selectedTool, selectedBranch, repoRoot]);
|
|
473
|
+
|
|
202
474
|
// Update preferred tool when branch or data changes
|
|
203
475
|
useEffect(() => {
|
|
204
476
|
if (!selectedBranch) return;
|
|
@@ -227,6 +499,12 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
227
499
|
[branches, hiddenBranches],
|
|
228
500
|
);
|
|
229
501
|
|
|
502
|
+
const selectedWorktreePath = useMemo(() => {
|
|
503
|
+
if (!selectedBranch) return null;
|
|
504
|
+
const wt = worktrees.find((w) => w.branch === selectedBranch.name);
|
|
505
|
+
return wt?.path ?? null;
|
|
506
|
+
}, [selectedBranch, worktrees]);
|
|
507
|
+
|
|
230
508
|
// Helper function to create content-based hash for branches
|
|
231
509
|
const branchHash = useMemo(
|
|
232
510
|
() =>
|
|
@@ -445,7 +723,11 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
445
723
|
color: "green",
|
|
446
724
|
});
|
|
447
725
|
refresh();
|
|
448
|
-
|
|
726
|
+
const nextScreen =
|
|
727
|
+
branchQuickStart.length || branchQuickStartLoading
|
|
728
|
+
? "branch-quick-start"
|
|
729
|
+
: "ai-tool-selector";
|
|
730
|
+
navigateTo(nextScreen);
|
|
449
731
|
} catch (error) {
|
|
450
732
|
const message = error instanceof Error ? error.message : String(error);
|
|
451
733
|
setCleanupFooterMessage({
|
|
@@ -454,18 +736,30 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
454
736
|
});
|
|
455
737
|
console.error("Failed to switch protected branch:", error);
|
|
456
738
|
}
|
|
457
|
-
}, [
|
|
739
|
+
}, [
|
|
740
|
+
branchQuickStart,
|
|
741
|
+
branchQuickStartLoading,
|
|
742
|
+
navigateTo,
|
|
743
|
+
refresh,
|
|
744
|
+
selectedBranch,
|
|
745
|
+
setCleanupFooterMessage,
|
|
746
|
+
]);
|
|
458
747
|
|
|
459
748
|
const handleUseExistingBranch = useCallback(() => {
|
|
460
749
|
if (selectedBranch && isProtectedSelection(selectedBranch)) {
|
|
461
750
|
void handleProtectedBranchSwitch();
|
|
462
751
|
return;
|
|
463
752
|
}
|
|
464
|
-
|
|
753
|
+
if (branchQuickStart.length) {
|
|
754
|
+
navigateTo("branch-quick-start");
|
|
755
|
+
} else {
|
|
756
|
+
navigateTo("ai-tool-selector");
|
|
757
|
+
}
|
|
465
758
|
}, [
|
|
466
759
|
handleProtectedBranchSwitch,
|
|
467
760
|
isProtectedSelection,
|
|
468
761
|
navigateTo,
|
|
762
|
+
branchQuickStart.length,
|
|
469
763
|
selectedBranch,
|
|
470
764
|
]);
|
|
471
765
|
|
|
@@ -742,20 +1036,12 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
742
1036
|
[navigateTo, selectedTool],
|
|
743
1037
|
);
|
|
744
1038
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
},
|
|
752
|
-
[goBack],
|
|
753
|
-
);
|
|
754
|
-
|
|
755
|
-
// Handle execution mode and skipPermissions selection
|
|
756
|
-
const handleModeSelect = useCallback(
|
|
757
|
-
(result: { mode: ExecutionMode; skipPermissions: boolean }) => {
|
|
758
|
-
// All selections complete - exit with result
|
|
1039
|
+
const completeSelection = useCallback(
|
|
1040
|
+
(
|
|
1041
|
+
executionMode: ExecutionMode,
|
|
1042
|
+
skip: boolean,
|
|
1043
|
+
sessionId?: string | null,
|
|
1044
|
+
) => {
|
|
759
1045
|
if (selectedBranch && selectedTool) {
|
|
760
1046
|
const defaultModel = getDefaultModelOption(selectedTool);
|
|
761
1047
|
const resolvedModel = selectedModel?.model ?? defaultModel?.id ?? null;
|
|
@@ -763,22 +1049,21 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
763
1049
|
selectedModel?.inferenceLevel ??
|
|
764
1050
|
getDefaultInferenceForModel(defaultModel ?? undefined);
|
|
765
1051
|
|
|
766
|
-
const modelForPayload = resolvedModel;
|
|
767
|
-
|
|
768
1052
|
const payload: SelectionResult = {
|
|
769
1053
|
branch: selectedBranch.name,
|
|
770
1054
|
displayName: selectedBranch.displayName,
|
|
771
1055
|
branchType: selectedBranch.branchType,
|
|
772
1056
|
tool: selectedTool,
|
|
773
|
-
mode:
|
|
774
|
-
skipPermissions:
|
|
775
|
-
...(
|
|
1057
|
+
mode: executionMode,
|
|
1058
|
+
skipPermissions: skip,
|
|
1059
|
+
...(resolvedModel !== undefined ? { model: resolvedModel } : {}),
|
|
776
1060
|
...(resolvedInference !== undefined
|
|
777
1061
|
? { inferenceLevel: resolvedInference }
|
|
778
1062
|
: {}),
|
|
779
1063
|
...(selectedBranch.remoteBranch
|
|
780
1064
|
? { remoteBranch: selectedBranch.remoteBranch }
|
|
781
1065
|
: {}),
|
|
1066
|
+
...(sessionId ? { sessionId } : {}),
|
|
782
1067
|
};
|
|
783
1068
|
|
|
784
1069
|
onExit(payload);
|
|
@@ -796,6 +1081,79 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
796
1081
|
],
|
|
797
1082
|
);
|
|
798
1083
|
|
|
1084
|
+
// Handle session selection
|
|
1085
|
+
const handleSessionSelect = useCallback(
|
|
1086
|
+
(session: string) => {
|
|
1087
|
+
const execution = pendingExecution ?? {
|
|
1088
|
+
mode: "resume" as ExecutionMode,
|
|
1089
|
+
skipPermissions: false,
|
|
1090
|
+
};
|
|
1091
|
+
completeSelection(execution.mode, execution.skipPermissions, session);
|
|
1092
|
+
},
|
|
1093
|
+
[pendingExecution, completeSelection],
|
|
1094
|
+
);
|
|
1095
|
+
|
|
1096
|
+
const handleQuickStartSelect = useCallback(
|
|
1097
|
+
(action: QuickStartAction, toolId?: AITool | null) => {
|
|
1098
|
+
if (action === "manual" || !branchQuickStart.length) {
|
|
1099
|
+
navigateTo("ai-tool-selector");
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
const selected =
|
|
1104
|
+
branchQuickStart.find((opt) => opt.toolId === toolId) ??
|
|
1105
|
+
branchQuickStart[0];
|
|
1106
|
+
if (!selected) {
|
|
1107
|
+
navigateTo("ai-tool-selector");
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
setSelectedTool(selected.toolId);
|
|
1112
|
+
setPreferredToolId(selected.toolId);
|
|
1113
|
+
setSelectedModel(
|
|
1114
|
+
selected.model
|
|
1115
|
+
? ({
|
|
1116
|
+
model: selected.model,
|
|
1117
|
+
inferenceLevel: selected.inferenceLevel ?? undefined,
|
|
1118
|
+
} as ModelSelectionResult)
|
|
1119
|
+
: null,
|
|
1120
|
+
);
|
|
1121
|
+
|
|
1122
|
+
const skip = selected.skipPermissions ?? false;
|
|
1123
|
+
|
|
1124
|
+
if (action === "reuse-continue") {
|
|
1125
|
+
const hasSession = Boolean(selected.sessionId);
|
|
1126
|
+
const mode: ExecutionMode = hasSession ? "resume" : "continue";
|
|
1127
|
+
completeSelection(mode, skip, selected.sessionId ?? null);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// "Start new with previous settings" skips the execution mode screen and launches immediately
|
|
1132
|
+
completeSelection("normal", skip, null);
|
|
1133
|
+
},
|
|
1134
|
+
[
|
|
1135
|
+
branchQuickStart,
|
|
1136
|
+
navigateTo,
|
|
1137
|
+
setPreferredToolId,
|
|
1138
|
+
setSelectedModel,
|
|
1139
|
+
setSelectedTool,
|
|
1140
|
+
completeSelection,
|
|
1141
|
+
],
|
|
1142
|
+
);
|
|
1143
|
+
|
|
1144
|
+
// Handle execution mode and skipPermissions selection
|
|
1145
|
+
const handleModeSelect = useCallback(
|
|
1146
|
+
(result: { mode: ExecutionMode; skipPermissions: boolean }) => {
|
|
1147
|
+
if (result.mode === "resume") {
|
|
1148
|
+
setPendingExecution(result);
|
|
1149
|
+
navigateTo("session-selector");
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
completeSelection(result.mode, result.skipPermissions, null);
|
|
1153
|
+
},
|
|
1154
|
+
[completeSelection, navigateTo],
|
|
1155
|
+
);
|
|
1156
|
+
|
|
799
1157
|
// Render screen based on currentScreen
|
|
800
1158
|
const renderScreen = () => {
|
|
801
1159
|
switch (currentScreen) {
|
|
@@ -857,6 +1215,25 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
857
1215
|
return <BranchActionSelectorScreen {...baseProps} />;
|
|
858
1216
|
}
|
|
859
1217
|
|
|
1218
|
+
case "branch-quick-start":
|
|
1219
|
+
return (
|
|
1220
|
+
<BranchQuickStartScreen
|
|
1221
|
+
branchName={selectedBranch?.displayName ?? ""}
|
|
1222
|
+
previousOptions={branchQuickStart.map((opt) => ({
|
|
1223
|
+
toolId: opt.toolId,
|
|
1224
|
+
toolLabel: opt.toolLabel,
|
|
1225
|
+
model: opt.model ?? null,
|
|
1226
|
+
inferenceLevel: opt.inferenceLevel ?? null,
|
|
1227
|
+
skipPermissions: opt.skipPermissions ?? null,
|
|
1228
|
+
sessionId: opt.sessionId ?? null,
|
|
1229
|
+
}))}
|
|
1230
|
+
loading={branchQuickStartLoading}
|
|
1231
|
+
onBack={goBack}
|
|
1232
|
+
onSelect={handleQuickStartSelect}
|
|
1233
|
+
version={version}
|
|
1234
|
+
/>
|
|
1235
|
+
);
|
|
1236
|
+
|
|
860
1237
|
case "ai-tool-selector":
|
|
861
1238
|
return (
|
|
862
1239
|
<AIToolSelectorScreen
|
|
@@ -886,7 +1263,9 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
886
1263
|
// TODO: Implement session data fetching
|
|
887
1264
|
return (
|
|
888
1265
|
<SessionSelectorScreen
|
|
889
|
-
sessions={
|
|
1266
|
+
sessions={sessionOptions}
|
|
1267
|
+
loading={sessionLoading}
|
|
1268
|
+
errorMessage={sessionError}
|
|
890
1269
|
onBack={goBack}
|
|
891
1270
|
onSelect={handleSessionSelect}
|
|
892
1271
|
version={version}
|
|
@@ -899,6 +1278,7 @@ export function App({ onExit, loadingIndicatorDelay = 300 }: AppProps) {
|
|
|
899
1278
|
onBack={goBack}
|
|
900
1279
|
onSelect={handleModeSelect}
|
|
901
1280
|
version={version}
|
|
1281
|
+
continueSessionId={continueSessionId}
|
|
902
1282
|
/>
|
|
903
1283
|
);
|
|
904
1284
|
|