@akiojin/gwt 4.11.6 → 4.12.1
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 +36 -10
- package/dist/claude.d.ts +1 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +81 -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 +280 -50
- 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 +67 -13
- package/dist/cli/ui/components/solid/WizardController.js.map +1 -1
- package/dist/cli/ui/components/solid/WizardSteps.d.ts +5 -0
- package/dist/cli/ui/components/solid/WizardSteps.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/WizardSteps.js +50 -70
- 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/installedVersionCache.d.ts +33 -0
- package/dist/cli/ui/utils/installedVersionCache.d.ts.map +1 -0
- package/dist/cli/ui/utils/installedVersionCache.js +59 -0
- package/dist/cli/ui/utils/installedVersionCache.js.map +1 -0
- package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -1
- package/dist/cli/ui/utils/modelOptions.js +16 -0
- package/dist/cli/ui/utils/modelOptions.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 +95 -25
- 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 +35 -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 +22 -0
- package/dist/logging/reader.d.ts.map +1 -1
- package/dist/logging/reader.js +116 -1
- package/dist/logging/reader.js.map +1 -1
- package/dist/opentui/index.solid.js +2575 -888
- package/dist/services/codingAgentResolver.d.ts.map +1 -1
- package/dist/services/codingAgentResolver.js +8 -6
- package/dist/services/codingAgentResolver.js.map +1 -1
- package/dist/services/dependency-installer.js +2 -2
- package/dist/services/dependency-installer.js.map +1 -1
- package/dist/shared/codingAgentConstants.d.ts +3 -0
- package/dist/shared/codingAgentConstants.d.ts.map +1 -1
- package/dist/shared/codingAgentConstants.js +66 -0
- package/dist/shared/codingAgentConstants.js.map +1 -1
- package/dist/utils/bun-runtime.d.ts +12 -0
- package/dist/utils/bun-runtime.d.ts.map +1 -0
- package/dist/utils/bun-runtime.js +13 -0
- package/dist/utils/bun-runtime.js.map +1 -0
- 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 +47 -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 +99 -28
- package/src/cli/ui/App.solid.tsx +373 -51
- package/src/cli/ui/__tests__/solid/AppSolid.cleanup.test.tsx +921 -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/WizardController.test.tsx +71 -0
- package/src/cli/ui/__tests__/solid/components/WizardSteps.test.tsx +95 -2
- 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 +85 -12
- package/src/cli/ui/components/solid/WizardSteps.tsx +78 -90
- 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/__tests__/installedVersionCache.test.ts +46 -0
- package/src/cli/ui/utils/branchFormatter.ts +35 -7
- package/src/cli/ui/utils/continueSession.ts +90 -3
- package/src/cli/ui/utils/installedVersionCache.ts +84 -0
- package/src/cli/ui/utils/modelOptions.test.ts +6 -0
- package/src/cli/ui/utils/modelOptions.ts +16 -0
- package/src/cli/ui/utils/versionCache.ts +93 -0
- package/src/cli/ui/utils/versionFetcher.ts +120 -0
- package/src/codex.ts +124 -26
- 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 +41 -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 +165 -1
- package/src/services/__tests__/BatchMergeService.test.ts +34 -14
- package/src/services/codingAgentResolver.ts +12 -5
- package/src/services/dependency-installer.ts +2 -2
- package/src/shared/codingAgentConstants.ts +73 -0
- package/src/utils/bun-runtime.ts +29 -0
- package/src/utils/session/common.ts +28 -0
- package/src/utils/session/parsers/claude.ts +79 -29
- package/src/utils/session/parsers/codex.ts +49 -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
package/src/codex.ts
CHANGED
|
@@ -13,8 +13,18 @@ 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";
|
|
20
|
+
import { createLogger } from "./logging/logger.js";
|
|
21
|
+
import {
|
|
22
|
+
shouldEnableCodexSkillsFlag,
|
|
23
|
+
withCodexSkillsFlag,
|
|
24
|
+
} from "./shared/codingAgentConstants.js";
|
|
16
25
|
|
|
17
26
|
const CODEX_CLI_PACKAGE = "@openai/codex";
|
|
27
|
+
const logger = createLogger({ category: "codex" });
|
|
18
28
|
|
|
19
29
|
/**
|
|
20
30
|
* Reasoning effort levels supported by Codex CLI.
|
|
@@ -43,8 +53,6 @@ export const buildDefaultCodexArgs = (
|
|
|
43
53
|
): string[] => [
|
|
44
54
|
"--enable",
|
|
45
55
|
"web_search_request",
|
|
46
|
-
"--enable",
|
|
47
|
-
"skills",
|
|
48
56
|
`--model=${model}`,
|
|
49
57
|
"--sandbox",
|
|
50
58
|
"workspace-write",
|
|
@@ -96,6 +104,7 @@ export async function launchCodexCLI(
|
|
|
96
104
|
model?: string;
|
|
97
105
|
reasoningEffort?: CodexReasoningEffort;
|
|
98
106
|
sessionId?: string | null;
|
|
107
|
+
branch?: string | null;
|
|
99
108
|
version?: string | null;
|
|
100
109
|
} = {},
|
|
101
110
|
): Promise<{ sessionId?: string | null }> {
|
|
@@ -166,7 +175,18 @@ export async function launchCodexCLI(
|
|
|
166
175
|
args.push(...options.extraArgs);
|
|
167
176
|
}
|
|
168
177
|
|
|
169
|
-
const
|
|
178
|
+
const requestedVersion = options.version ?? "latest";
|
|
179
|
+
const codexLookup = await findCommand("codex");
|
|
180
|
+
const skillsFlagVersion =
|
|
181
|
+
requestedVersion === "installed"
|
|
182
|
+
? (codexLookup.version ?? null)
|
|
183
|
+
: requestedVersion === "latest"
|
|
184
|
+
? null
|
|
185
|
+
: requestedVersion;
|
|
186
|
+
const codexArgs = withCodexSkillsFlag(
|
|
187
|
+
buildDefaultCodexArgs(model, reasoningEffort),
|
|
188
|
+
shouldEnableCodexSkillsFlag(skillsFlagVersion),
|
|
189
|
+
);
|
|
170
190
|
|
|
171
191
|
args.push(...codexArgs);
|
|
172
192
|
|
|
@@ -175,8 +195,6 @@ export async function launchCodexCLI(
|
|
|
175
195
|
terminal.exitRawMode();
|
|
176
196
|
resetTerminalModes(terminal.stdout);
|
|
177
197
|
|
|
178
|
-
const childStdio = createChildStdio();
|
|
179
|
-
|
|
180
198
|
const env = Object.fromEntries(
|
|
181
199
|
Object.entries({
|
|
182
200
|
...process.env,
|
|
@@ -185,26 +203,116 @@ export async function launchCodexCLI(
|
|
|
185
203
|
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
186
204
|
),
|
|
187
205
|
);
|
|
206
|
+
const captureOutput = shouldCaptureAgentOutput(env);
|
|
207
|
+
const childStdio = captureOutput ? null : createChildStdio();
|
|
188
208
|
|
|
189
|
-
//
|
|
190
|
-
const codexLookup = await findCommand("codex");
|
|
209
|
+
// codexLookup is used to decide local vs bunx execution
|
|
191
210
|
|
|
192
211
|
const execChild = async (child: Promise<unknown>) => {
|
|
193
212
|
try {
|
|
194
|
-
await child
|
|
213
|
+
const result = (await child) as {
|
|
214
|
+
exitCode?: number | null;
|
|
215
|
+
signal?: string | null;
|
|
216
|
+
};
|
|
217
|
+
return {
|
|
218
|
+
exitCode: result.exitCode ?? 0,
|
|
219
|
+
signal: result.signal ?? null,
|
|
220
|
+
};
|
|
195
221
|
} catch (execError: unknown) {
|
|
196
222
|
// Treat SIGINT/SIGTERM as normal exit (user pressed Ctrl+C)
|
|
197
223
|
const signal = (execError as { signal?: unknown })?.signal;
|
|
224
|
+
const exitCode =
|
|
225
|
+
(execError as { exitCode?: number | null })?.exitCode ?? null;
|
|
198
226
|
if (signal === "SIGINT" || signal === "SIGTERM") {
|
|
227
|
+
return { exitCode, signal };
|
|
228
|
+
}
|
|
229
|
+
throw execError;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
// Treat SIGINT (2), SIGTERM (15) as normal exit signals (user interrupts)
|
|
233
|
+
const isNormalExitSignal = (signal?: number | null) =>
|
|
234
|
+
signal === 2 || signal === 15;
|
|
235
|
+
const runCommand = async (command: string, commandArgs: string[]) => {
|
|
236
|
+
const runStartedAt = Date.now();
|
|
237
|
+
if (captureOutput) {
|
|
238
|
+
const result = await runAgentWithPty({
|
|
239
|
+
command,
|
|
240
|
+
args: commandArgs,
|
|
241
|
+
cwd: worktreePath,
|
|
242
|
+
env,
|
|
243
|
+
agentId: "codex-cli",
|
|
244
|
+
});
|
|
245
|
+
const durationMs = Date.now() - runStartedAt;
|
|
246
|
+
const exitCode = result.exitCode ?? null;
|
|
247
|
+
const signal = result.signal ?? null;
|
|
248
|
+
const signalIsNormal = isNormalExitSignal(signal);
|
|
249
|
+
const hasError =
|
|
250
|
+
(!signalIsNormal && signal !== null && signal !== undefined) ||
|
|
251
|
+
(exitCode !== null && exitCode !== 0);
|
|
252
|
+
if (hasError) {
|
|
253
|
+
logger.error({ exitCode, signal, durationMs }, "Codex CLI exited");
|
|
254
|
+
} else {
|
|
255
|
+
logger.info({ exitCode, signal, durationMs }, "Codex CLI exited");
|
|
256
|
+
}
|
|
257
|
+
if (signalIsNormal) {
|
|
199
258
|
return;
|
|
200
259
|
}
|
|
260
|
+
if (signal !== null && signal !== undefined) {
|
|
261
|
+
throw new Error(`Codex CLI terminated by signal ${signal}`);
|
|
262
|
+
}
|
|
263
|
+
if (exitCode !== null && exitCode !== 0) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
`Codex CLI exited with code ${exitCode ?? "unknown"}`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (!childStdio) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const child = execa(command, commandArgs, {
|
|
277
|
+
cwd: worktreePath,
|
|
278
|
+
stdin: childStdio.stdin,
|
|
279
|
+
stdout: childStdio.stdout,
|
|
280
|
+
stderr: childStdio.stderr,
|
|
281
|
+
env,
|
|
282
|
+
});
|
|
283
|
+
const result = await execChild(child);
|
|
284
|
+
const durationMs = Date.now() - runStartedAt;
|
|
285
|
+
logger.info(
|
|
286
|
+
{
|
|
287
|
+
exitCode: result.exitCode ?? null,
|
|
288
|
+
signal: result.signal,
|
|
289
|
+
durationMs,
|
|
290
|
+
},
|
|
291
|
+
"Codex CLI exited",
|
|
292
|
+
);
|
|
293
|
+
} catch (execError: unknown) {
|
|
294
|
+
const durationMs = Date.now() - runStartedAt;
|
|
295
|
+
const exitCode =
|
|
296
|
+
(execError as { exitCode?: number | null })?.exitCode ?? null;
|
|
297
|
+
const signal =
|
|
298
|
+
(execError as { signal?: string | null })?.signal ?? null;
|
|
299
|
+
logger.error({ exitCode, signal, durationMs }, "Codex CLI failed");
|
|
201
300
|
throw execError;
|
|
202
301
|
}
|
|
203
302
|
};
|
|
204
303
|
|
|
205
304
|
// Determine execution strategy based on version selection
|
|
206
305
|
// FR-063b: "installed" option only appears when local command exists
|
|
207
|
-
|
|
306
|
+
let selectedVersion = requestedVersion;
|
|
307
|
+
|
|
308
|
+
if (requestedVersion === "installed" && !codexLookup.path) {
|
|
309
|
+
writeTerminalLine(
|
|
310
|
+
chalk.yellow(
|
|
311
|
+
" ⚠️ Installed codex command not found. Falling back to latest.",
|
|
312
|
+
),
|
|
313
|
+
);
|
|
314
|
+
selectedVersion = "latest";
|
|
315
|
+
}
|
|
208
316
|
|
|
209
317
|
// Log version information (FR-072)
|
|
210
318
|
if (selectedVersion === "installed") {
|
|
@@ -220,30 +328,16 @@ export async function launchCodexCLI(
|
|
|
220
328
|
writeTerminalLine(
|
|
221
329
|
chalk.green(" ✨ Using locally installed codex command"),
|
|
222
330
|
);
|
|
223
|
-
|
|
224
|
-
cwd: worktreePath,
|
|
225
|
-
stdin: childStdio.stdin,
|
|
226
|
-
stdout: childStdio.stdout,
|
|
227
|
-
stderr: childStdio.stderr,
|
|
228
|
-
env,
|
|
229
|
-
});
|
|
230
|
-
await execChild(child);
|
|
331
|
+
await runCommand(codexLookup.path, args);
|
|
231
332
|
} else {
|
|
232
333
|
// FR-067, FR-068: Use bunx with version suffix for latest/specific versions
|
|
233
334
|
const packageWithVersion = `${CODEX_CLI_PACKAGE}@${selectedVersion}`;
|
|
234
335
|
writeTerminalLine(chalk.cyan(` 🔄 Using bunx ${packageWithVersion}`));
|
|
235
336
|
|
|
236
|
-
|
|
237
|
-
cwd: worktreePath,
|
|
238
|
-
stdin: childStdio.stdin,
|
|
239
|
-
stdout: childStdio.stdout,
|
|
240
|
-
stderr: childStdio.stderr,
|
|
241
|
-
env,
|
|
242
|
-
});
|
|
243
|
-
await execChild(child);
|
|
337
|
+
await runCommand("bunx", [packageWithVersion, ...args]);
|
|
244
338
|
}
|
|
245
339
|
} finally {
|
|
246
|
-
childStdio
|
|
340
|
+
childStdio?.cleanup();
|
|
247
341
|
}
|
|
248
342
|
|
|
249
343
|
// File-based session detection only - no stdout capture
|
|
@@ -257,6 +351,10 @@ export async function launchCodexCLI(
|
|
|
257
351
|
preferClosestTo: finishedAt,
|
|
258
352
|
windowMs: 10 * 60 * 1000,
|
|
259
353
|
cwd: worktreePath,
|
|
354
|
+
branch: options.branch ?? null,
|
|
355
|
+
worktrees: options.branch
|
|
356
|
+
? [{ path: worktreePath, branch: options.branch }]
|
|
357
|
+
: null,
|
|
260
358
|
});
|
|
261
359
|
const detectedSessionId = latest?.id ?? null;
|
|
262
360
|
// 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");
|
package/src/gemini.ts
CHANGED
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
} from "./utils/terminal.js";
|
|
10
10
|
import { findCommand } from "./utils/command.js";
|
|
11
11
|
import { findLatestGeminiSessionId } from "./utils/session.js";
|
|
12
|
+
import {
|
|
13
|
+
runAgentWithPty,
|
|
14
|
+
shouldCaptureAgentOutput,
|
|
15
|
+
} from "./logging/agentOutput.js";
|
|
12
16
|
|
|
13
17
|
const GEMINI_CLI_PACKAGE = "@google/gemini-cli";
|
|
14
18
|
|
|
@@ -45,6 +49,7 @@ export async function launchGeminiCLI(
|
|
|
45
49
|
envOverrides?: Record<string, string>;
|
|
46
50
|
model?: string;
|
|
47
51
|
sessionId?: string | null;
|
|
52
|
+
branch?: string | null;
|
|
48
53
|
version?: string | null;
|
|
49
54
|
} = {},
|
|
50
55
|
): Promise<{ sessionId?: string | null }> {
|
|
@@ -154,8 +159,8 @@ export async function launchGeminiCLI(
|
|
|
154
159
|
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
155
160
|
),
|
|
156
161
|
);
|
|
157
|
-
|
|
158
|
-
const childStdio = createChildStdio();
|
|
162
|
+
const captureOutput = shouldCaptureAgentOutput(baseEnv);
|
|
163
|
+
const childStdio = captureOutput ? null : createChildStdio();
|
|
159
164
|
|
|
160
165
|
// Auto-detect locally installed gemini command
|
|
161
166
|
const geminiLookup = await findCommand("gemini");
|
|
@@ -166,7 +171,17 @@ export async function launchGeminiCLI(
|
|
|
166
171
|
|
|
167
172
|
// Determine execution strategy based on version selection
|
|
168
173
|
// FR-063b: "installed" option only appears when local command exists
|
|
169
|
-
const
|
|
174
|
+
const requestedVersion = options.version ?? "latest";
|
|
175
|
+
let selectedVersion = requestedVersion;
|
|
176
|
+
|
|
177
|
+
if (requestedVersion === "installed" && !geminiLookup.path) {
|
|
178
|
+
writeTerminalLine(
|
|
179
|
+
chalk.yellow(
|
|
180
|
+
" ⚠️ Installed gemini command not found. Falling back to latest.",
|
|
181
|
+
),
|
|
182
|
+
);
|
|
183
|
+
selectedVersion = "latest";
|
|
184
|
+
}
|
|
170
185
|
|
|
171
186
|
// Log version information (FR-072)
|
|
172
187
|
if (selectedVersion === "installed") {
|
|
@@ -188,8 +203,35 @@ export async function launchGeminiCLI(
|
|
|
188
203
|
throw execError;
|
|
189
204
|
}
|
|
190
205
|
};
|
|
206
|
+
// Treat SIGHUP (1), SIGINT (2), SIGTERM (15) as normal exit signals
|
|
207
|
+
// SIGHUP can occur when the PTY closes, SIGINT/SIGTERM are user interrupts
|
|
208
|
+
const isNormalExitSignal = (signal?: number | null) =>
|
|
209
|
+
signal === 1 || signal === 2 || signal === 15;
|
|
191
210
|
|
|
192
211
|
const run = async (cmd: string, args: string[]) => {
|
|
212
|
+
if (captureOutput) {
|
|
213
|
+
const result = await runAgentWithPty({
|
|
214
|
+
command: cmd,
|
|
215
|
+
args,
|
|
216
|
+
cwd: worktreePath,
|
|
217
|
+
env: baseEnv,
|
|
218
|
+
agentId: "gemini-cli",
|
|
219
|
+
});
|
|
220
|
+
if (isNormalExitSignal(result.signal)) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (result.exitCode !== null && result.exitCode !== 0) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Gemini CLI exited with code ${result.exitCode ?? "unknown"}`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!childStdio) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
193
235
|
const child = execa(cmd, args, {
|
|
194
236
|
cwd: worktreePath,
|
|
195
237
|
stdin: childStdio.stdin,
|
|
@@ -237,7 +279,7 @@ export async function launchGeminiCLI(
|
|
|
237
279
|
}
|
|
238
280
|
}
|
|
239
281
|
} finally {
|
|
240
|
-
childStdio
|
|
282
|
+
childStdio?.cleanup();
|
|
241
283
|
}
|
|
242
284
|
|
|
243
285
|
const explicitResumeSucceeded = usedExplicitSessionId && !fellBackToLatest;
|
|
@@ -253,6 +295,10 @@ export async function launchGeminiCLI(
|
|
|
253
295
|
capturedSessionId =
|
|
254
296
|
(await findLatestGeminiSessionId(worktreePath, {
|
|
255
297
|
cwd: worktreePath,
|
|
298
|
+
branch: options.branch ?? null,
|
|
299
|
+
worktrees: options.branch
|
|
300
|
+
? [{ path: worktreePath, branch: options.branch }]
|
|
301
|
+
: null,
|
|
256
302
|
})) ?? null;
|
|
257
303
|
} catch {
|
|
258
304
|
capturedSessionId = null;
|
package/src/index.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, spyOn, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { getPackageVersion } from "./utils";
|
|
2
3
|
import * as utils from "./utils";
|
|
3
4
|
|
|
4
5
|
// showVersion関数のテスト(TDD Green phase)
|
|
@@ -28,7 +29,6 @@ describe("showVersion via CLI args", () => {
|
|
|
28
29
|
consoleLogSpy.mockRestore();
|
|
29
30
|
consoleErrorSpy.mockRestore();
|
|
30
31
|
processExitSpy.mockRestore();
|
|
31
|
-
|
|
32
32
|
// process.argvを復元
|
|
33
33
|
process.argv = originalArgv;
|
|
34
34
|
});
|
|
@@ -37,32 +37,34 @@ describe("showVersion via CLI args", () => {
|
|
|
37
37
|
// Arrange: CLIフラグを設定
|
|
38
38
|
process.argv = ["node", "index.js", "--version"];
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
const expectedVersion = await getPackageVersion();
|
|
41
|
+
if (!expectedVersion) {
|
|
42
|
+
throw new Error("Failed to resolve package version for test.");
|
|
43
|
+
}
|
|
43
44
|
|
|
44
45
|
// Act: main()を呼び出す
|
|
45
46
|
const { main } = await import("./index");
|
|
46
47
|
await main();
|
|
47
48
|
|
|
48
49
|
// Assert: 標準出力にバージョンが表示されることを期待
|
|
49
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
50
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expectedVersion);
|
|
50
51
|
}, 30000);
|
|
51
52
|
|
|
52
53
|
it("正常系: -vフラグでバージョンを表示する", async () => {
|
|
53
54
|
// Arrange: CLIフラグを設定
|
|
54
55
|
process.argv = ["node", "index.js", "-v"];
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const expectedVersion = await getPackageVersion();
|
|
58
|
+
if (!expectedVersion) {
|
|
59
|
+
throw new Error("Failed to resolve package version for test.");
|
|
60
|
+
}
|
|
59
61
|
|
|
60
62
|
// Act: main()を呼び出す
|
|
61
63
|
const { main } = await import("./index");
|
|
62
64
|
await main();
|
|
63
65
|
|
|
64
66
|
// Assert: 標準出力にバージョンが表示されることを期待
|
|
65
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
67
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expectedVersion);
|
|
66
68
|
}, 30000);
|
|
67
69
|
|
|
68
70
|
// Note: This test is skipped due to module caching issues in CI environment
|
|
@@ -72,7 +74,10 @@ describe("showVersion via CLI args", () => {
|
|
|
72
74
|
process.argv = ["node", "index.js", "--version"];
|
|
73
75
|
|
|
74
76
|
// getPackageVersion()をモックしてnullを返す
|
|
75
|
-
spyOn(
|
|
77
|
+
const getPackageVersionSpy = spyOn(
|
|
78
|
+
utils,
|
|
79
|
+
"getPackageVersion",
|
|
80
|
+
).mockResolvedValue(null);
|
|
76
81
|
|
|
77
82
|
// Act: main()を呼び出す
|
|
78
83
|
const { main } = await import("./index");
|
|
@@ -83,5 +88,6 @@ describe("showVersion via CLI args", () => {
|
|
|
83
88
|
expect.stringContaining("Error"),
|
|
84
89
|
);
|
|
85
90
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
91
|
+
getPackageVersionSpy.mockRestore();
|
|
86
92
|
});
|
|
87
93
|
});
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
isProtectedBranchName,
|
|
28
28
|
switchToProtectedBranch,
|
|
29
29
|
resolveWorktreePathForBranch,
|
|
30
|
+
listAllWorktrees,
|
|
30
31
|
repairWorktreePath,
|
|
31
32
|
} from "./worktree.js";
|
|
32
33
|
import {
|
|
@@ -192,6 +193,11 @@ function performTerminalCleanup(): void {
|
|
|
192
193
|
* Returns SelectionResult if user made selections, undefined if user quit
|
|
193
194
|
*/
|
|
194
195
|
async function mainSolidUI(): Promise<SelectionResult | undefined> {
|
|
196
|
+
if (!("Bun" in globalThis)) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
"OpenTUI requires the Bun runtime. Run with Bun (e.g. bunx @akiojin/gwt@latest).",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
195
201
|
const { renderSolidApp } = await import("./opentui/index.solid.js");
|
|
196
202
|
const terminal = getTerminalStreams();
|
|
197
203
|
|
|
@@ -629,6 +635,7 @@ export async function handleAIToolWorkflow(
|
|
|
629
635
|
model?: string;
|
|
630
636
|
sessionId?: string | null;
|
|
631
637
|
chrome?: boolean;
|
|
638
|
+
branch?: string | null;
|
|
632
639
|
version?: string | null;
|
|
633
640
|
} = {
|
|
634
641
|
mode:
|
|
@@ -641,6 +648,7 @@ export async function handleAIToolWorkflow(
|
|
|
641
648
|
envOverrides: sharedEnv,
|
|
642
649
|
sessionId: resumeSessionId,
|
|
643
650
|
chrome: true,
|
|
651
|
+
branch,
|
|
644
652
|
version: toolVersion ?? null,
|
|
645
653
|
};
|
|
646
654
|
if (normalizedModel) {
|
|
@@ -655,6 +663,7 @@ export async function handleAIToolWorkflow(
|
|
|
655
663
|
model?: string;
|
|
656
664
|
reasoningEffort?: CodexReasoningEffort;
|
|
657
665
|
sessionId?: string | null;
|
|
666
|
+
branch?: string | null;
|
|
658
667
|
version?: string | null;
|
|
659
668
|
} = {
|
|
660
669
|
mode:
|
|
@@ -666,6 +675,7 @@ export async function handleAIToolWorkflow(
|
|
|
666
675
|
bypassApprovals: skipPermissions,
|
|
667
676
|
envOverrides: sharedEnv,
|
|
668
677
|
sessionId: resumeSessionId,
|
|
678
|
+
branch,
|
|
669
679
|
version: toolVersion ?? null,
|
|
670
680
|
};
|
|
671
681
|
if (normalizedModel) {
|
|
@@ -683,6 +693,7 @@ export async function handleAIToolWorkflow(
|
|
|
683
693
|
envOverrides?: Record<string, string>;
|
|
684
694
|
model?: string;
|
|
685
695
|
sessionId?: string | null;
|
|
696
|
+
branch?: string | null;
|
|
686
697
|
version?: string | null;
|
|
687
698
|
} = {
|
|
688
699
|
mode:
|
|
@@ -694,6 +705,7 @@ export async function handleAIToolWorkflow(
|
|
|
694
705
|
skipPermissions,
|
|
695
706
|
envOverrides: sharedEnv,
|
|
696
707
|
sessionId: resumeSessionId,
|
|
708
|
+
branch,
|
|
697
709
|
version: toolVersion ?? null,
|
|
698
710
|
};
|
|
699
711
|
if (normalizedModel) {
|
|
@@ -715,6 +727,9 @@ export async function handleAIToolWorkflow(
|
|
|
715
727
|
cwd: worktreePath,
|
|
716
728
|
sharedEnv,
|
|
717
729
|
};
|
|
730
|
+
if (tool === "opencode" && normalizedModel) {
|
|
731
|
+
customLaunchOptions.extraArgs = ["-m", normalizedModel];
|
|
732
|
+
}
|
|
718
733
|
if (toolVersion) {
|
|
719
734
|
customLaunchOptions.version = toolVersion;
|
|
720
735
|
}
|
|
@@ -734,10 +749,29 @@ export async function handleAIToolWorkflow(
|
|
|
734
749
|
resumeSessionId ??
|
|
735
750
|
null;
|
|
736
751
|
|
|
752
|
+
let resolvedWorktrees: { path: string; branch: string }[] | null = null;
|
|
753
|
+
if (branch) {
|
|
754
|
+
try {
|
|
755
|
+
const allWorktrees = await listAllWorktrees();
|
|
756
|
+
resolvedWorktrees = allWorktrees
|
|
757
|
+
.filter((entry) => entry?.path && entry?.branch)
|
|
758
|
+
.map((entry) => ({ path: entry.path, branch: entry.branch }));
|
|
759
|
+
} catch {
|
|
760
|
+
resolvedWorktrees = null;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
const worktreeLookupOptions =
|
|
764
|
+
resolvedWorktrees && resolvedWorktrees.length > 0
|
|
765
|
+
? { worktrees: resolvedWorktrees }
|
|
766
|
+
: {};
|
|
767
|
+
|
|
737
768
|
if (!finalSessionId && tool === "claude-code") {
|
|
738
769
|
try {
|
|
739
770
|
finalSessionId =
|
|
740
|
-
(await findLatestClaudeSessionId(worktreePath
|
|
771
|
+
(await findLatestClaudeSessionId(worktreePath, {
|
|
772
|
+
branch,
|
|
773
|
+
...worktreeLookupOptions,
|
|
774
|
+
})) ?? null;
|
|
741
775
|
} catch {
|
|
742
776
|
finalSessionId = null;
|
|
743
777
|
}
|
|
@@ -752,6 +786,8 @@ export async function handleAIToolWorkflow(
|
|
|
752
786
|
preferClosestTo: finishedAt,
|
|
753
787
|
windowMs: 60 * 60 * 1000,
|
|
754
788
|
cwd: worktreePath,
|
|
789
|
+
branch,
|
|
790
|
+
...worktreeLookupOptions,
|
|
755
791
|
});
|
|
756
792
|
if (latest) {
|
|
757
793
|
finalSessionId = latest.id;
|
|
@@ -766,6 +802,8 @@ export async function handleAIToolWorkflow(
|
|
|
766
802
|
until: finishedAt + 60_000,
|
|
767
803
|
preferClosestTo: finishedAt,
|
|
768
804
|
windowMs: 60 * 60 * 1000,
|
|
805
|
+
branch,
|
|
806
|
+
...worktreeLookupOptions,
|
|
769
807
|
});
|
|
770
808
|
if (latestClaude) {
|
|
771
809
|
finalSessionId = latestClaude.id;
|
|
@@ -781,6 +819,8 @@ export async function handleAIToolWorkflow(
|
|
|
781
819
|
preferClosestTo: finishedAt,
|
|
782
820
|
windowMs: 60 * 60 * 1000,
|
|
783
821
|
cwd: worktreePath,
|
|
822
|
+
branch,
|
|
823
|
+
...worktreeLookupOptions,
|
|
784
824
|
});
|
|
785
825
|
if (latestGemini) {
|
|
786
826
|
finalSessionId = latestGemini.id;
|