@akiojin/gwt 4.11.6 → 4.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/bin/gwt.js +1 -1
- package/dist/claude.d.ts +1 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +50 -24
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/App.solid.d.ts.map +1 -1
- package/dist/cli/ui/App.solid.js +247 -49
- package/dist/cli/ui/App.solid.js.map +1 -1
- package/dist/cli/ui/components/solid/QuickStartStep.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/QuickStartStep.js +35 -22
- package/dist/cli/ui/components/solid/QuickStartStep.js.map +1 -1
- package/dist/cli/ui/components/solid/SelectInput.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/SelectInput.js +2 -1
- package/dist/cli/ui/components/solid/SelectInput.js.map +1 -1
- package/dist/cli/ui/components/solid/WizardController.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/WizardController.js +19 -11
- package/dist/cli/ui/components/solid/WizardController.js.map +1 -1
- package/dist/cli/ui/components/solid/WizardSteps.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/WizardSteps.js +26 -69
- package/dist/cli/ui/components/solid/WizardSteps.js.map +1 -1
- package/dist/cli/ui/core/theme.d.ts +9 -0
- package/dist/cli/ui/core/theme.d.ts.map +1 -1
- package/dist/cli/ui/core/theme.js +21 -0
- package/dist/cli/ui/core/theme.js.map +1 -1
- package/dist/cli/ui/screens/solid/BranchListScreen.d.ts +9 -2
- package/dist/cli/ui/screens/solid/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/BranchListScreen.js +101 -28
- package/dist/cli/ui/screens/solid/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts +2 -1
- package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/ConfirmScreen.js +11 -3
- package/dist/cli/ui/screens/solid/ConfirmScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/EnvironmentScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/EnvironmentScreen.js +9 -10
- package/dist/cli/ui/screens/solid/EnvironmentScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/LogScreen.d.ts +7 -1
- package/dist/cli/ui/screens/solid/LogScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/LogScreen.js +254 -16
- package/dist/cli/ui/screens/solid/LogScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/ProfileEnvScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/ProfileEnvScreen.js +8 -5
- package/dist/cli/ui/screens/solid/ProfileEnvScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/SelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/SelectorScreen.js +12 -4
- package/dist/cli/ui/screens/solid/SelectorScreen.js.map +1 -1
- package/dist/cli/ui/types.d.ts +1 -0
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.d.ts +1 -0
- package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.js +29 -7
- package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
- package/dist/cli/ui/utils/continueSession.d.ts +14 -0
- package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
- package/dist/cli/ui/utils/continueSession.js +61 -3
- package/dist/cli/ui/utils/continueSession.js.map +1 -1
- package/dist/cli/ui/utils/versionCache.d.ts +37 -0
- package/dist/cli/ui/utils/versionCache.d.ts.map +1 -0
- package/dist/cli/ui/utils/versionCache.js +70 -0
- package/dist/cli/ui/utils/versionCache.js.map +1 -0
- package/dist/cli/ui/utils/versionFetcher.d.ts +41 -0
- package/dist/cli/ui/utils/versionFetcher.d.ts.map +1 -0
- package/dist/cli/ui/utils/versionFetcher.js +89 -0
- package/dist/cli/ui/utils/versionFetcher.js.map +1 -0
- package/dist/codex.d.ts +1 -0
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +48 -19
- package/dist/codex.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +10 -1
- package/dist/config/index.js.map +1 -1
- package/dist/gemini.d.ts +1 -0
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +36 -3
- package/dist/gemini.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -2
- package/dist/index.js.map +1 -1
- package/dist/launcher.d.ts.map +1 -1
- package/dist/launcher.js +43 -8
- package/dist/launcher.js.map +1 -1
- package/dist/logging/agentOutput.d.ts +21 -0
- package/dist/logging/agentOutput.d.ts.map +1 -0
- package/dist/logging/agentOutput.js +164 -0
- package/dist/logging/agentOutput.js.map +1 -0
- package/dist/logging/formatter.d.ts.map +1 -1
- package/dist/logging/formatter.js +18 -4
- package/dist/logging/formatter.js.map +1 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +2 -0
- package/dist/logging/logger.js.map +1 -1
- package/dist/logging/reader.d.ts +21 -0
- package/dist/logging/reader.d.ts.map +1 -1
- package/dist/logging/reader.js +79 -0
- package/dist/logging/reader.js.map +1 -1
- package/dist/opentui/index.solid.js +2306 -653
- package/dist/services/dependency-installer.js +2 -2
- package/dist/services/dependency-installer.js.map +1 -1
- package/dist/utils/session/common.d.ts +8 -0
- package/dist/utils/session/common.d.ts.map +1 -1
- package/dist/utils/session/common.js +22 -0
- package/dist/utils/session/common.js.map +1 -1
- package/dist/utils/session/parsers/claude.d.ts +10 -4
- package/dist/utils/session/parsers/claude.d.ts.map +1 -1
- package/dist/utils/session/parsers/claude.js +64 -18
- package/dist/utils/session/parsers/claude.js.map +1 -1
- package/dist/utils/session/parsers/codex.d.ts.map +1 -1
- package/dist/utils/session/parsers/codex.js +48 -28
- package/dist/utils/session/parsers/codex.js.map +1 -1
- package/dist/utils/session/parsers/gemini.d.ts.map +1 -1
- package/dist/utils/session/parsers/gemini.js +43 -6
- package/dist/utils/session/parsers/gemini.js.map +1 -1
- package/dist/utils/session/parsers/opencode.d.ts.map +1 -1
- package/dist/utils/session/parsers/opencode.js +43 -6
- package/dist/utils/session/parsers/opencode.js.map +1 -1
- package/dist/utils/session/types.d.ts +7 -0
- package/dist/utils/session/types.d.ts.map +1 -1
- package/dist/web/client/src/components/ui/alert.d.ts +1 -1
- package/dist/worktree.d.ts +4 -1
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +21 -15
- package/dist/worktree.js.map +1 -1
- package/package.json +2 -1
- package/src/claude.ts +64 -28
- package/src/cli/ui/App.solid.tsx +324 -51
- package/src/cli/ui/__tests__/solid/AppSolid.cleanup.test.tsx +830 -1
- package/src/cli/ui/__tests__/solid/BranchListScreen.test.tsx +105 -5
- package/src/cli/ui/__tests__/solid/ConfirmScreen.test.tsx +77 -0
- package/src/cli/ui/__tests__/solid/LogScreen.test.tsx +351 -0
- package/src/cli/ui/__tests__/solid/components/QuickStartStep.test.tsx +73 -2
- package/src/cli/ui/__tests__/solid/components/WizardSteps.test.tsx +4 -1
- package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +72 -45
- package/src/cli/ui/components/solid/QuickStartStep.tsx +35 -23
- package/src/cli/ui/components/solid/SearchInput.tsx +1 -1
- package/src/cli/ui/components/solid/SelectInput.tsx +4 -0
- package/src/cli/ui/components/solid/WizardController.tsx +20 -11
- package/src/cli/ui/components/solid/WizardSteps.tsx +29 -86
- package/src/cli/ui/core/theme.ts +32 -0
- package/src/cli/ui/hooks/solid/useAsyncOperation.ts +8 -6
- package/src/cli/ui/hooks/solid/useGitOperations.ts +6 -5
- package/src/cli/ui/screens/solid/BranchListScreen.tsx +135 -32
- package/src/cli/ui/screens/solid/ConfirmScreen.tsx +20 -8
- package/src/cli/ui/screens/solid/EnvironmentScreen.tsx +22 -20
- package/src/cli/ui/screens/solid/LogScreen.tsx +364 -35
- package/src/cli/ui/screens/solid/ProfileEnvScreen.tsx +19 -15
- package/src/cli/ui/screens/solid/SelectorScreen.tsx +25 -14
- package/src/cli/ui/screens/solid/SettingsScreen.tsx +5 -3
- package/src/cli/ui/types.ts +1 -0
- package/src/cli/ui/utils/__tests__/branchFormatter.test.ts +53 -6
- package/src/cli/ui/utils/branchFormatter.ts +35 -7
- package/src/cli/ui/utils/continueSession.ts +90 -3
- package/src/cli/ui/utils/versionCache.ts +93 -0
- package/src/cli/ui/utils/versionFetcher.ts +120 -0
- package/src/codex.ts +62 -20
- package/src/config/__tests__/saveSession.test.ts +2 -2
- package/src/config/index.ts +11 -1
- package/src/gemini.ts +50 -4
- package/src/index.test.ts +16 -10
- package/src/index.ts +38 -1
- package/src/launcher.ts +49 -8
- package/src/logging/agentOutput.ts +216 -0
- package/src/logging/formatter.ts +23 -4
- package/src/logging/logger.ts +2 -0
- package/src/logging/reader.ts +117 -0
- package/src/services/__tests__/BatchMergeService.test.ts +34 -14
- package/src/services/dependency-installer.ts +2 -2
- package/src/utils/session/common.ts +28 -0
- package/src/utils/session/parsers/claude.ts +79 -29
- package/src/utils/session/parsers/codex.ts +50 -26
- package/src/utils/session/parsers/gemini.ts +46 -5
- package/src/utils/session/parsers/opencode.ts +46 -5
- package/src/utils/session/types.ts +4 -0
- package/src/worktree.ts +28 -15
|
@@ -7,6 +7,31 @@ import type { BranchInfo } from "../../types.js";
|
|
|
7
7
|
|
|
8
8
|
// TDD: テストを先に書き、実装は後から行う
|
|
9
9
|
|
|
10
|
+
const LOCAL_DATE_TIME_FORMATTER = new Intl.DateTimeFormat(undefined, {
|
|
11
|
+
year: "numeric",
|
|
12
|
+
month: "2-digit",
|
|
13
|
+
day: "2-digit",
|
|
14
|
+
hour: "2-digit",
|
|
15
|
+
minute: "2-digit",
|
|
16
|
+
hour12: false,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const formatLocalDateTime = (timestampMs: number): string => {
|
|
20
|
+
const date = new Date(timestampMs);
|
|
21
|
+
const parts = LOCAL_DATE_TIME_FORMATTER.formatToParts(date);
|
|
22
|
+
const get = (type: Intl.DateTimeFormatPartTypes) =>
|
|
23
|
+
parts.find((part) => part.type === type)?.value;
|
|
24
|
+
const year = get("year");
|
|
25
|
+
const month = get("month");
|
|
26
|
+
const day = get("day");
|
|
27
|
+
const hour = get("hour");
|
|
28
|
+
const minute = get("minute");
|
|
29
|
+
if (!year || !month || !day || !hour || !minute) {
|
|
30
|
+
return LOCAL_DATE_TIME_FORMATTER.format(date);
|
|
31
|
+
}
|
|
32
|
+
return `${year}-${month}-${day} ${hour}:${minute}`;
|
|
33
|
+
};
|
|
34
|
+
|
|
10
35
|
describe("branchFormatter", () => {
|
|
11
36
|
describe("buildLastToolUsageLabel with version", () => {
|
|
12
37
|
const baseBranch: BranchInfo = {
|
|
@@ -31,7 +56,11 @@ describe("branchFormatter", () => {
|
|
|
31
56
|
};
|
|
32
57
|
|
|
33
58
|
const item = formatBranchItem(branch);
|
|
34
|
-
expect(item.lastToolUsageLabel).toBe(
|
|
59
|
+
expect(item.lastToolUsageLabel).toBe(
|
|
60
|
+
`Claude@1.0.3 | ${formatLocalDateTime(
|
|
61
|
+
new Date("2024-01-08T12:00:00").getTime(),
|
|
62
|
+
)}`,
|
|
63
|
+
);
|
|
35
64
|
});
|
|
36
65
|
|
|
37
66
|
it("should format as 'ToolName@latest | 2024-01-08 12:00' when version is null", () => {
|
|
@@ -48,7 +77,11 @@ describe("branchFormatter", () => {
|
|
|
48
77
|
};
|
|
49
78
|
|
|
50
79
|
const item = formatBranchItem(branch);
|
|
51
|
-
expect(item.lastToolUsageLabel).toBe(
|
|
80
|
+
expect(item.lastToolUsageLabel).toBe(
|
|
81
|
+
`Claude@latest | ${formatLocalDateTime(
|
|
82
|
+
new Date("2024-01-08T12:00:00").getTime(),
|
|
83
|
+
)}`,
|
|
84
|
+
);
|
|
52
85
|
});
|
|
53
86
|
|
|
54
87
|
it("should format as 'ToolName@latest | 2024-01-08 12:00' when version is undefined", () => {
|
|
@@ -64,7 +97,11 @@ describe("branchFormatter", () => {
|
|
|
64
97
|
};
|
|
65
98
|
|
|
66
99
|
const item = formatBranchItem(branch);
|
|
67
|
-
expect(item.lastToolUsageLabel).toBe(
|
|
100
|
+
expect(item.lastToolUsageLabel).toBe(
|
|
101
|
+
`Claude@latest | ${formatLocalDateTime(
|
|
102
|
+
new Date("2024-01-08T12:00:00").getTime(),
|
|
103
|
+
)}`,
|
|
104
|
+
);
|
|
68
105
|
});
|
|
69
106
|
|
|
70
107
|
it("should format Codex with version", () => {
|
|
@@ -82,7 +119,9 @@ describe("branchFormatter", () => {
|
|
|
82
119
|
|
|
83
120
|
const item = formatBranchItem(branch);
|
|
84
121
|
expect(item.lastToolUsageLabel).toBe(
|
|
85
|
-
|
|
122
|
+
`Codex@2.1.0-beta.1 | ${formatLocalDateTime(
|
|
123
|
+
new Date("2024-01-08T15:30:00").getTime(),
|
|
124
|
+
)}`,
|
|
86
125
|
);
|
|
87
126
|
});
|
|
88
127
|
|
|
@@ -100,7 +139,11 @@ describe("branchFormatter", () => {
|
|
|
100
139
|
};
|
|
101
140
|
|
|
102
141
|
const item = formatBranchItem(branch);
|
|
103
|
-
expect(item.lastToolUsageLabel).toBe(
|
|
142
|
+
expect(item.lastToolUsageLabel).toBe(
|
|
143
|
+
`Gemini@0.5.0 | ${formatLocalDateTime(
|
|
144
|
+
new Date("2024-01-08T09:15:00").getTime(),
|
|
145
|
+
)}`,
|
|
146
|
+
);
|
|
104
147
|
});
|
|
105
148
|
|
|
106
149
|
it("should format custom tool with version", () => {
|
|
@@ -117,7 +160,11 @@ describe("branchFormatter", () => {
|
|
|
117
160
|
};
|
|
118
161
|
|
|
119
162
|
const item = formatBranchItem(branch);
|
|
120
|
-
expect(item.lastToolUsageLabel).toBe(
|
|
163
|
+
expect(item.lastToolUsageLabel).toBe(
|
|
164
|
+
`MyTool@1.0.0 | ${formatLocalDateTime(
|
|
165
|
+
new Date("2024-01-08T10:00:00").getTime(),
|
|
166
|
+
)}`,
|
|
167
|
+
);
|
|
121
168
|
});
|
|
122
169
|
|
|
123
170
|
it("should return null when lastToolUsage is undefined", () => {
|
|
@@ -17,14 +17,42 @@ function mapToolLabel(toolId: string, toolLabel?: string): string {
|
|
|
17
17
|
return "Custom";
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const LOCAL_DATE_TIME_FORMATTER = new Intl.DateTimeFormat(undefined, {
|
|
21
|
+
year: "numeric",
|
|
22
|
+
month: "2-digit",
|
|
23
|
+
day: "2-digit",
|
|
24
|
+
hour: "2-digit",
|
|
25
|
+
minute: "2-digit",
|
|
26
|
+
hour12: false,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const formatLocalDateTimeParts = (date: Date): string => {
|
|
30
|
+
const parts = LOCAL_DATE_TIME_FORMATTER.formatToParts(date);
|
|
31
|
+
const get = (type: Intl.DateTimeFormatPartTypes) =>
|
|
32
|
+
parts.find((part) => part.type === type)?.value;
|
|
33
|
+
const year = get("year");
|
|
34
|
+
const month = get("month");
|
|
35
|
+
const day = get("day");
|
|
36
|
+
const hour = get("hour");
|
|
37
|
+
const minute = get("minute");
|
|
38
|
+
|
|
39
|
+
if (!year || !month || !day || !hour || !minute) {
|
|
40
|
+
return LOCAL_DATE_TIME_FORMATTER.format(date);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return `${year}-${month}-${day} ${hour}:${minute}`;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function formatLocalDateTime(timestampMs: number): string {
|
|
47
|
+
const date = new Date(timestampMs);
|
|
48
|
+
if (Number.isNaN(date.getTime())) {
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
return formatLocalDateTimeParts(date);
|
|
52
|
+
}
|
|
53
|
+
|
|
20
54
|
function formatTimestamp(ts: number): string {
|
|
21
|
-
|
|
22
|
-
const year = date.getFullYear();
|
|
23
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
24
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
25
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
26
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
27
|
-
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
55
|
+
return formatLocalDateTime(ts);
|
|
28
56
|
}
|
|
29
57
|
|
|
30
58
|
function buildLastToolUsageLabel(
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { SessionData, ToolSessionEntry } from "../../../config/index.js";
|
|
2
|
+
import type { SessionSearchOptions } from "../../../utils/session/index.js";
|
|
2
3
|
import {
|
|
4
|
+
findLatestClaudeSession,
|
|
3
5
|
findLatestClaudeSessionId,
|
|
6
|
+
findLatestCodexSession,
|
|
4
7
|
findLatestCodexSessionId,
|
|
8
|
+
findLatestGeminiSession,
|
|
5
9
|
findLatestGeminiSessionId,
|
|
6
|
-
|
|
10
|
+
findLatestOpenCodeSession,
|
|
11
|
+
} from "../../../utils/session/index.js";
|
|
12
|
+
import { listAllWorktrees } from "../../../worktree.js";
|
|
7
13
|
|
|
8
14
|
export interface ContinueSessionContext {
|
|
9
15
|
history: ToolSessionEntry[];
|
|
@@ -122,10 +128,10 @@ export function findLatestBranchSessionsByTool(
|
|
|
122
128
|
const byBranch = history.filter((entry) => entry && entry.branch === branch);
|
|
123
129
|
if (!byBranch.length) return [];
|
|
124
130
|
|
|
125
|
-
const
|
|
131
|
+
const source = worktreePath
|
|
126
132
|
? byBranch.filter((entry) => entry.worktreePath === worktreePath)
|
|
127
133
|
: byBranch;
|
|
128
|
-
|
|
134
|
+
if (!source.length) return [];
|
|
129
135
|
|
|
130
136
|
const latestByTool = new Map<string, ToolSessionEntry>();
|
|
131
137
|
for (const entry of source) {
|
|
@@ -142,3 +148,84 @@ export function findLatestBranchSessionsByTool(
|
|
|
142
148
|
(a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0),
|
|
143
149
|
);
|
|
144
150
|
}
|
|
151
|
+
|
|
152
|
+
export interface QuickStartRefreshContext {
|
|
153
|
+
branch: string;
|
|
154
|
+
worktreePath?: string | null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface QuickStartSessionLookups {
|
|
158
|
+
findLatestCodexSession?: typeof findLatestCodexSession;
|
|
159
|
+
findLatestClaudeSession?: typeof findLatestClaudeSession;
|
|
160
|
+
findLatestGeminiSession?: typeof findLatestGeminiSession;
|
|
161
|
+
findLatestOpenCodeSession?: typeof findLatestOpenCodeSession;
|
|
162
|
+
listAllWorktrees?: typeof listAllWorktrees;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function refreshQuickStartEntries(
|
|
166
|
+
entries: ToolSessionEntry[],
|
|
167
|
+
context: QuickStartRefreshContext,
|
|
168
|
+
lookups: QuickStartSessionLookups = {},
|
|
169
|
+
): Promise<ToolSessionEntry[]> {
|
|
170
|
+
if (!entries.length) return entries;
|
|
171
|
+
const worktreePath = context.worktreePath ?? null;
|
|
172
|
+
if (!worktreePath) return entries;
|
|
173
|
+
|
|
174
|
+
const lookupWorktrees = lookups.listAllWorktrees ?? listAllWorktrees;
|
|
175
|
+
let resolvedWorktrees: { path: string; branch: string }[] | null = null;
|
|
176
|
+
try {
|
|
177
|
+
const allWorktrees = await lookupWorktrees();
|
|
178
|
+
resolvedWorktrees = allWorktrees
|
|
179
|
+
.filter((entry) => entry?.path && entry?.branch)
|
|
180
|
+
.map((entry) => ({ path: entry.path, branch: entry.branch }));
|
|
181
|
+
} catch {
|
|
182
|
+
resolvedWorktrees = null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const searchOptions: SessionSearchOptions = {
|
|
186
|
+
branch: context.branch,
|
|
187
|
+
...(resolvedWorktrees && resolvedWorktrees.length > 0
|
|
188
|
+
? { worktrees: resolvedWorktrees }
|
|
189
|
+
: {}),
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const lookupCodex = lookups.findLatestCodexSession ?? findLatestCodexSession;
|
|
193
|
+
const lookupClaude =
|
|
194
|
+
lookups.findLatestClaudeSession ?? findLatestClaudeSession;
|
|
195
|
+
const lookupGemini =
|
|
196
|
+
lookups.findLatestGeminiSession ?? findLatestGeminiSession;
|
|
197
|
+
const lookupOpenCode =
|
|
198
|
+
lookups.findLatestOpenCodeSession ?? findLatestOpenCodeSession;
|
|
199
|
+
|
|
200
|
+
const updated = await Promise.all(
|
|
201
|
+
entries.map(async (entry) => {
|
|
202
|
+
let latest: { id: string; mtime: number } | null = null;
|
|
203
|
+
switch (entry.toolId) {
|
|
204
|
+
case "codex-cli":
|
|
205
|
+
latest = await lookupCodex(searchOptions);
|
|
206
|
+
break;
|
|
207
|
+
case "claude-code":
|
|
208
|
+
latest = await lookupClaude(worktreePath, searchOptions);
|
|
209
|
+
break;
|
|
210
|
+
case "gemini-cli":
|
|
211
|
+
latest = await lookupGemini(searchOptions);
|
|
212
|
+
break;
|
|
213
|
+
case "opencode":
|
|
214
|
+
latest = await lookupOpenCode(searchOptions);
|
|
215
|
+
break;
|
|
216
|
+
default:
|
|
217
|
+
return entry;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!latest?.id) return entry;
|
|
221
|
+
const updatedTimestamp = Math.max(entry.timestamp ?? 0, latest.mtime);
|
|
222
|
+
return {
|
|
223
|
+
...entry,
|
|
224
|
+
sessionId: latest.id,
|
|
225
|
+
timestamp: updatedTimestamp,
|
|
226
|
+
};
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
return updated.sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
|
|
231
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version cache for coding agents (FR-028 ~ FR-031)
|
|
3
|
+
*
|
|
4
|
+
* Provides caching mechanism for npm package versions to avoid
|
|
5
|
+
* repeated API calls during wizard navigation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { VersionInfo } from "../../../utils/npmRegistry.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* In-memory cache for agent versions
|
|
12
|
+
* Maps agentId -> VersionInfo[]
|
|
13
|
+
*/
|
|
14
|
+
const versionCache = new Map<string, VersionInfo[]>();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Type for version fetch function (injectable for testing)
|
|
18
|
+
*/
|
|
19
|
+
export type VersionFetcher = (agentId: string) => Promise<VersionInfo[]>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get cached versions for an agent
|
|
23
|
+
* @returns Cached versions or null if not cached
|
|
24
|
+
*/
|
|
25
|
+
export function getVersionCache(agentId: string): VersionInfo[] | null {
|
|
26
|
+
const cached = versionCache.get(agentId);
|
|
27
|
+
return cached !== undefined ? cached : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if versions are cached for an agent
|
|
32
|
+
*/
|
|
33
|
+
export function isVersionCachePopulated(agentId: string): boolean {
|
|
34
|
+
return versionCache.has(agentId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Clear all cached versions
|
|
39
|
+
*/
|
|
40
|
+
export function clearVersionCache(): void {
|
|
41
|
+
versionCache.clear();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Prefetch versions for multiple agents in parallel
|
|
46
|
+
* This should be called at application startup
|
|
47
|
+
*
|
|
48
|
+
* @param agentIds - List of agent IDs to prefetch versions for
|
|
49
|
+
* @param fetchFn - Optional custom fetch function (for testing)
|
|
50
|
+
*/
|
|
51
|
+
export async function prefetchAgentVersions(
|
|
52
|
+
agentIds: string[],
|
|
53
|
+
fetchFn?: VersionFetcher,
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
// Import default fetcher if not provided
|
|
56
|
+
const fetcher =
|
|
57
|
+
fetchFn ??
|
|
58
|
+
(async (agentId: string) => {
|
|
59
|
+
const { fetchVersionOptionsForAgent } =
|
|
60
|
+
await import("./versionFetcher.js");
|
|
61
|
+
return fetchVersionOptionsForAgent(agentId);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Fetch all versions in parallel
|
|
65
|
+
const results = await Promise.allSettled(
|
|
66
|
+
agentIds.map(async (agentId) => {
|
|
67
|
+
try {
|
|
68
|
+
const versions = await fetcher(agentId);
|
|
69
|
+
versionCache.set(agentId, versions);
|
|
70
|
+
} catch {
|
|
71
|
+
// On error, don't set cache (getVersionCache will return null)
|
|
72
|
+
}
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Log any failures for debugging (silently handled)
|
|
77
|
+
for (let i = 0; i < results.length; i++) {
|
|
78
|
+
const result = results[i];
|
|
79
|
+
if (result && result.status === "rejected") {
|
|
80
|
+
// Silently handle - cache will return null for this agent
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Set versions in cache (for testing or direct population)
|
|
87
|
+
*/
|
|
88
|
+
export function setVersionCache(
|
|
89
|
+
agentId: string,
|
|
90
|
+
versions: VersionInfo[],
|
|
91
|
+
): void {
|
|
92
|
+
versionCache.set(agentId, versions);
|
|
93
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version fetcher for coding agents
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to fetch version information from npm registry
|
|
5
|
+
* and local installations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { VersionInfo } from "../../../utils/npmRegistry.js";
|
|
9
|
+
import {
|
|
10
|
+
fetchPackageVersions,
|
|
11
|
+
parsePackageCommand,
|
|
12
|
+
} from "../../../utils/npmRegistry.js";
|
|
13
|
+
import { BUILTIN_CODING_AGENTS } from "../../../config/builtin-coding-agents.js";
|
|
14
|
+
import { findCommand } from "../../../utils/command.js";
|
|
15
|
+
import type { SelectInputItem } from "../components/solid/SelectInput.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get package name for an agent ID (only for bunx-type agents)
|
|
19
|
+
*/
|
|
20
|
+
export function getPackageNameForAgent(agentId: string): string | null {
|
|
21
|
+
const agent = BUILTIN_CODING_AGENTS.find((a) => a.id === agentId);
|
|
22
|
+
if (!agent || agent.type !== "bunx") {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const { packageName } = parsePackageCommand(agent.command);
|
|
26
|
+
return packageName;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Fetch version options for an agent from npm registry
|
|
31
|
+
*/
|
|
32
|
+
export async function fetchVersionOptionsForAgent(
|
|
33
|
+
agentId: string,
|
|
34
|
+
): Promise<VersionInfo[]> {
|
|
35
|
+
const packageName = getPackageNameForAgent(agentId);
|
|
36
|
+
if (!packageName) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
return fetchPackageVersions(packageName);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Agent ID to command name mapping
|
|
44
|
+
*/
|
|
45
|
+
const AGENT_COMMAND_MAP: Record<string, string> = {
|
|
46
|
+
"claude-code": "claude",
|
|
47
|
+
"codex-cli": "codex",
|
|
48
|
+
"gemini-cli": "gemini",
|
|
49
|
+
opencode: "opencode",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Installed version information
|
|
54
|
+
*/
|
|
55
|
+
export interface InstalledVersionInfo {
|
|
56
|
+
version: string;
|
|
57
|
+
path: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fetch installed version information for an agent
|
|
62
|
+
* Returns null if not installed locally
|
|
63
|
+
*/
|
|
64
|
+
export async function fetchInstalledVersionForAgent(
|
|
65
|
+
agentId: string,
|
|
66
|
+
): Promise<InstalledVersionInfo | null> {
|
|
67
|
+
const commandName = AGENT_COMMAND_MAP[agentId];
|
|
68
|
+
if (!commandName) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const result = await findCommand(commandName);
|
|
73
|
+
if (result.source !== "installed" || !result.path) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Version format: v1.0.3 -> 1.0.3
|
|
78
|
+
const version = result.version?.replace(/^v/, "") ?? "unknown";
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
version,
|
|
82
|
+
path: result.path,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Convert VersionInfo to SelectInputItem for UI
|
|
88
|
+
*/
|
|
89
|
+
export function versionInfoToSelectItem(v: VersionInfo): SelectInputItem {
|
|
90
|
+
const item: SelectInputItem = {
|
|
91
|
+
label: v.isPrerelease ? `${v.version} (pre)` : v.version,
|
|
92
|
+
value: v.version,
|
|
93
|
+
};
|
|
94
|
+
if (v.publishedAt) {
|
|
95
|
+
item.description = new Date(v.publishedAt).toLocaleDateString();
|
|
96
|
+
}
|
|
97
|
+
return item;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create installed option for SelectInput
|
|
102
|
+
*/
|
|
103
|
+
export function createInstalledOption(
|
|
104
|
+
installed: InstalledVersionInfo,
|
|
105
|
+
): SelectInputItem {
|
|
106
|
+
return {
|
|
107
|
+
label: `installed@${installed.version}`,
|
|
108
|
+
value: "installed",
|
|
109
|
+
description: installed.path,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get all bunx-type agent IDs
|
|
115
|
+
*/
|
|
116
|
+
export function getBunxAgentIds(): string[] {
|
|
117
|
+
return BUILTIN_CODING_AGENTS.filter((a) => a.type === "bunx").map(
|
|
118
|
+
(a) => a.id,
|
|
119
|
+
);
|
|
120
|
+
}
|
package/src/codex.ts
CHANGED
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
waitForCodexSessionId,
|
|
14
14
|
} from "./utils/session.js";
|
|
15
15
|
import { findCommand } from "./utils/command.js";
|
|
16
|
+
import {
|
|
17
|
+
runAgentWithPty,
|
|
18
|
+
shouldCaptureAgentOutput,
|
|
19
|
+
} from "./logging/agentOutput.js";
|
|
16
20
|
|
|
17
21
|
const CODEX_CLI_PACKAGE = "@openai/codex";
|
|
18
22
|
|
|
@@ -96,6 +100,7 @@ export async function launchCodexCLI(
|
|
|
96
100
|
model?: string;
|
|
97
101
|
reasoningEffort?: CodexReasoningEffort;
|
|
98
102
|
sessionId?: string | null;
|
|
103
|
+
branch?: string | null;
|
|
99
104
|
version?: string | null;
|
|
100
105
|
} = {},
|
|
101
106
|
): Promise<{ sessionId?: string | null }> {
|
|
@@ -175,8 +180,6 @@ export async function launchCodexCLI(
|
|
|
175
180
|
terminal.exitRawMode();
|
|
176
181
|
resetTerminalModes(terminal.stdout);
|
|
177
182
|
|
|
178
|
-
const childStdio = createChildStdio();
|
|
179
|
-
|
|
180
183
|
const env = Object.fromEntries(
|
|
181
184
|
Object.entries({
|
|
182
185
|
...process.env,
|
|
@@ -185,6 +188,8 @@ export async function launchCodexCLI(
|
|
|
185
188
|
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
186
189
|
),
|
|
187
190
|
);
|
|
191
|
+
const captureOutput = shouldCaptureAgentOutput(env);
|
|
192
|
+
const childStdio = captureOutput ? null : createChildStdio();
|
|
188
193
|
|
|
189
194
|
// Auto-detect locally installed codex command
|
|
190
195
|
const codexLookup = await findCommand("codex");
|
|
@@ -201,10 +206,57 @@ export async function launchCodexCLI(
|
|
|
201
206
|
throw execError;
|
|
202
207
|
}
|
|
203
208
|
};
|
|
209
|
+
// Treat SIGHUP (1), SIGINT (2), SIGTERM (15) as normal exit signals
|
|
210
|
+
// SIGHUP can occur when the PTY closes, SIGINT/SIGTERM are user interrupts
|
|
211
|
+
const isNormalExitSignal = (signal?: number | null) =>
|
|
212
|
+
signal === 1 || signal === 2 || signal === 15;
|
|
213
|
+
const runCommand = async (command: string, commandArgs: string[]) => {
|
|
214
|
+
if (captureOutput) {
|
|
215
|
+
const result = await runAgentWithPty({
|
|
216
|
+
command,
|
|
217
|
+
args: commandArgs,
|
|
218
|
+
cwd: worktreePath,
|
|
219
|
+
env,
|
|
220
|
+
agentId: "codex-cli",
|
|
221
|
+
});
|
|
222
|
+
if (isNormalExitSignal(result.signal)) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (result.exitCode !== null && result.exitCode !== 0) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`Codex CLI exited with code ${result.exitCode ?? "unknown"}`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!childStdio) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const child = execa(command, commandArgs, {
|
|
238
|
+
cwd: worktreePath,
|
|
239
|
+
stdin: childStdio.stdin,
|
|
240
|
+
stdout: childStdio.stdout,
|
|
241
|
+
stderr: childStdio.stderr,
|
|
242
|
+
env,
|
|
243
|
+
});
|
|
244
|
+
await execChild(child);
|
|
245
|
+
};
|
|
204
246
|
|
|
205
247
|
// Determine execution strategy based on version selection
|
|
206
248
|
// FR-063b: "installed" option only appears when local command exists
|
|
207
|
-
const
|
|
249
|
+
const requestedVersion = options.version ?? "latest";
|
|
250
|
+
let selectedVersion = requestedVersion;
|
|
251
|
+
|
|
252
|
+
if (requestedVersion === "installed" && !codexLookup.path) {
|
|
253
|
+
writeTerminalLine(
|
|
254
|
+
chalk.yellow(
|
|
255
|
+
" ⚠️ Installed codex command not found. Falling back to latest.",
|
|
256
|
+
),
|
|
257
|
+
);
|
|
258
|
+
selectedVersion = "latest";
|
|
259
|
+
}
|
|
208
260
|
|
|
209
261
|
// Log version information (FR-072)
|
|
210
262
|
if (selectedVersion === "installed") {
|
|
@@ -220,30 +272,16 @@ export async function launchCodexCLI(
|
|
|
220
272
|
writeTerminalLine(
|
|
221
273
|
chalk.green(" ✨ Using locally installed codex command"),
|
|
222
274
|
);
|
|
223
|
-
|
|
224
|
-
cwd: worktreePath,
|
|
225
|
-
stdin: childStdio.stdin,
|
|
226
|
-
stdout: childStdio.stdout,
|
|
227
|
-
stderr: childStdio.stderr,
|
|
228
|
-
env,
|
|
229
|
-
});
|
|
230
|
-
await execChild(child);
|
|
275
|
+
await runCommand(codexLookup.path, args);
|
|
231
276
|
} else {
|
|
232
277
|
// FR-067, FR-068: Use bunx with version suffix for latest/specific versions
|
|
233
278
|
const packageWithVersion = `${CODEX_CLI_PACKAGE}@${selectedVersion}`;
|
|
234
279
|
writeTerminalLine(chalk.cyan(` 🔄 Using bunx ${packageWithVersion}`));
|
|
235
280
|
|
|
236
|
-
|
|
237
|
-
cwd: worktreePath,
|
|
238
|
-
stdin: childStdio.stdin,
|
|
239
|
-
stdout: childStdio.stdout,
|
|
240
|
-
stderr: childStdio.stderr,
|
|
241
|
-
env,
|
|
242
|
-
});
|
|
243
|
-
await execChild(child);
|
|
281
|
+
await runCommand("bunx", [packageWithVersion, ...args]);
|
|
244
282
|
}
|
|
245
283
|
} finally {
|
|
246
|
-
childStdio
|
|
284
|
+
childStdio?.cleanup();
|
|
247
285
|
}
|
|
248
286
|
|
|
249
287
|
// File-based session detection only - no stdout capture
|
|
@@ -257,6 +295,10 @@ export async function launchCodexCLI(
|
|
|
257
295
|
preferClosestTo: finishedAt,
|
|
258
296
|
windowMs: 10 * 60 * 1000,
|
|
259
297
|
cwd: worktreePath,
|
|
298
|
+
branch: options.branch ?? null,
|
|
299
|
+
worktrees: options.branch
|
|
300
|
+
? [{ path: worktreePath, branch: options.branch }]
|
|
301
|
+
: null,
|
|
260
302
|
});
|
|
261
303
|
const detectedSessionId = latest?.id ?? null;
|
|
262
304
|
// When we explicitly resumed a specific session, keep that ID as the source of truth.
|
|
@@ -53,7 +53,7 @@ describe("saveSession", () => {
|
|
|
53
53
|
expect(loaded?.history?.[0]?.toolVersion).toBe("2.1.1");
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
it("should save
|
|
56
|
+
it("should save latest toolVersion when not provided", async () => {
|
|
57
57
|
// Arrange
|
|
58
58
|
const sessionData = {
|
|
59
59
|
lastWorktreePath: testRepoRoot,
|
|
@@ -73,7 +73,7 @@ describe("saveSession", () => {
|
|
|
73
73
|
// Assert
|
|
74
74
|
expect(loaded).not.toBeNull();
|
|
75
75
|
expect(loaded?.history).toHaveLength(1);
|
|
76
|
-
expect(loaded?.history?.[0]?.toolVersion).
|
|
76
|
+
expect(loaded?.history?.[0]?.toolVersion).toBe("latest");
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it("should preserve toolVersion across multiple saves", async () => {
|
package/src/config/index.ts
CHANGED
|
@@ -48,6 +48,14 @@ const DEFAULT_CONFIG: AppConfig = {
|
|
|
48
48
|
worktreeNamingPattern: "{repo}-{branch}",
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
function normalizeToolVersion(version?: string | null): string {
|
|
52
|
+
if (!version) {
|
|
53
|
+
return "latest";
|
|
54
|
+
}
|
|
55
|
+
const trimmed = version.trim();
|
|
56
|
+
return trimmed.length > 0 ? trimmed : "latest";
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
/**
|
|
52
60
|
* 設定ファイルを読み込む
|
|
53
61
|
*/
|
|
@@ -121,6 +129,7 @@ export async function saveSession(
|
|
|
121
129
|
try {
|
|
122
130
|
const sessionPath = getSessionFilePath(sessionData.repositoryRoot);
|
|
123
131
|
const sessionDir = path.dirname(sessionPath);
|
|
132
|
+
const resolvedToolVersion = normalizeToolVersion(sessionData.toolVersion);
|
|
124
133
|
|
|
125
134
|
// ディレクトリを作成
|
|
126
135
|
await mkdir(sessionDir, { recursive: true });
|
|
@@ -154,7 +163,7 @@ export async function saveSession(
|
|
|
154
163
|
model: sessionData.model ?? null,
|
|
155
164
|
reasoningLevel: sessionData.reasoningLevel ?? null,
|
|
156
165
|
skipPermissions: sessionData.skipPermissions ?? false,
|
|
157
|
-
toolVersion:
|
|
166
|
+
toolVersion: resolvedToolVersion,
|
|
158
167
|
timestamp: sessionData.timestamp,
|
|
159
168
|
};
|
|
160
169
|
existingHistory = [...existingHistory, entry].slice(-100); // keep latest 100
|
|
@@ -166,6 +175,7 @@ export async function saveSession(
|
|
|
166
175
|
lastSessionId: sessionData.lastSessionId ?? null,
|
|
167
176
|
reasoningLevel: sessionData.reasoningLevel ?? null,
|
|
168
177
|
skipPermissions: sessionData.skipPermissions ?? false,
|
|
178
|
+
toolVersion: resolvedToolVersion,
|
|
169
179
|
};
|
|
170
180
|
|
|
171
181
|
await writeFile(sessionPath, JSON.stringify(payload, null, 2), "utf-8");
|