@akiojin/gwt 4.12.0 → 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.
Files changed (65) hide show
  1. package/bin/gwt.js +35 -9
  2. package/dist/claude.d.ts.map +1 -1
  3. package/dist/claude.js +45 -14
  4. package/dist/claude.js.map +1 -1
  5. package/dist/cli/ui/App.solid.d.ts.map +1 -1
  6. package/dist/cli/ui/App.solid.js +40 -8
  7. package/dist/cli/ui/App.solid.js.map +1 -1
  8. package/dist/cli/ui/components/solid/WizardController.d.ts.map +1 -1
  9. package/dist/cli/ui/components/solid/WizardController.js +48 -2
  10. package/dist/cli/ui/components/solid/WizardController.js.map +1 -1
  11. package/dist/cli/ui/components/solid/WizardSteps.d.ts +5 -0
  12. package/dist/cli/ui/components/solid/WizardSteps.d.ts.map +1 -1
  13. package/dist/cli/ui/components/solid/WizardSteps.js +29 -6
  14. package/dist/cli/ui/components/solid/WizardSteps.js.map +1 -1
  15. package/dist/cli/ui/utils/installedVersionCache.d.ts +33 -0
  16. package/dist/cli/ui/utils/installedVersionCache.d.ts.map +1 -0
  17. package/dist/cli/ui/utils/installedVersionCache.js +59 -0
  18. package/dist/cli/ui/utils/installedVersionCache.js.map +1 -0
  19. package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -1
  20. package/dist/cli/ui/utils/modelOptions.js +16 -0
  21. package/dist/cli/ui/utils/modelOptions.js.map +1 -1
  22. package/dist/codex.d.ts.map +1 -1
  23. package/dist/codex.js +63 -22
  24. package/dist/codex.js.map +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +3 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/logging/reader.d.ts +2 -1
  29. package/dist/logging/reader.d.ts.map +1 -1
  30. package/dist/logging/reader.js +37 -1
  31. package/dist/logging/reader.js.map +1 -1
  32. package/dist/opentui/index.solid.js +298 -264
  33. package/dist/services/codingAgentResolver.d.ts.map +1 -1
  34. package/dist/services/codingAgentResolver.js +8 -6
  35. package/dist/services/codingAgentResolver.js.map +1 -1
  36. package/dist/shared/codingAgentConstants.d.ts +3 -0
  37. package/dist/shared/codingAgentConstants.d.ts.map +1 -1
  38. package/dist/shared/codingAgentConstants.js +66 -0
  39. package/dist/shared/codingAgentConstants.js.map +1 -1
  40. package/dist/utils/bun-runtime.d.ts +12 -0
  41. package/dist/utils/bun-runtime.d.ts.map +1 -0
  42. package/dist/utils/bun-runtime.js +13 -0
  43. package/dist/utils/bun-runtime.js.map +1 -0
  44. package/dist/utils/session/parsers/codex.d.ts.map +1 -1
  45. package/dist/utils/session/parsers/codex.js +0 -1
  46. package/dist/utils/session/parsers/codex.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/claude.ts +49 -14
  49. package/src/cli/ui/App.solid.tsx +55 -6
  50. package/src/cli/ui/__tests__/solid/AppSolid.cleanup.test.tsx +91 -0
  51. package/src/cli/ui/__tests__/solid/components/WizardController.test.tsx +71 -0
  52. package/src/cli/ui/__tests__/solid/components/WizardSteps.test.tsx +91 -1
  53. package/src/cli/ui/components/solid/WizardController.tsx +65 -1
  54. package/src/cli/ui/components/solid/WizardSteps.tsx +55 -10
  55. package/src/cli/ui/utils/__tests__/installedVersionCache.test.ts +46 -0
  56. package/src/cli/ui/utils/installedVersionCache.ts +84 -0
  57. package/src/cli/ui/utils/modelOptions.test.ts +6 -0
  58. package/src/cli/ui/utils/modelOptions.ts +16 -0
  59. package/src/codex.ts +78 -22
  60. package/src/index.ts +3 -0
  61. package/src/logging/reader.ts +48 -1
  62. package/src/services/codingAgentResolver.ts +12 -5
  63. package/src/shared/codingAgentConstants.ts +73 -0
  64. package/src/utils/bun-runtime.ts +29 -0
  65. package/src/utils/session/parsers/codex.ts +0 -1
package/src/codex.ts CHANGED
@@ -17,8 +17,14 @@ import {
17
17
  runAgentWithPty,
18
18
  shouldCaptureAgentOutput,
19
19
  } from "./logging/agentOutput.js";
20
+ import { createLogger } from "./logging/logger.js";
21
+ import {
22
+ shouldEnableCodexSkillsFlag,
23
+ withCodexSkillsFlag,
24
+ } from "./shared/codingAgentConstants.js";
20
25
 
21
26
  const CODEX_CLI_PACKAGE = "@openai/codex";
27
+ const logger = createLogger({ category: "codex" });
22
28
 
23
29
  /**
24
30
  * Reasoning effort levels supported by Codex CLI.
@@ -47,8 +53,6 @@ export const buildDefaultCodexArgs = (
47
53
  ): string[] => [
48
54
  "--enable",
49
55
  "web_search_request",
50
- "--enable",
51
- "skills",
52
56
  `--model=${model}`,
53
57
  "--sandbox",
54
58
  "workspace-write",
@@ -171,7 +175,18 @@ export async function launchCodexCLI(
171
175
  args.push(...options.extraArgs);
172
176
  }
173
177
 
174
- const codexArgs = buildDefaultCodexArgs(model, reasoningEffort);
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
+ );
175
190
 
176
191
  args.push(...codexArgs);
177
192
 
@@ -191,26 +206,34 @@ export async function launchCodexCLI(
191
206
  const captureOutput = shouldCaptureAgentOutput(env);
192
207
  const childStdio = captureOutput ? null : createChildStdio();
193
208
 
194
- // Auto-detect locally installed codex command
195
- const codexLookup = await findCommand("codex");
209
+ // codexLookup is used to decide local vs bunx execution
196
210
 
197
211
  const execChild = async (child: Promise<unknown>) => {
198
212
  try {
199
- 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
+ };
200
221
  } catch (execError: unknown) {
201
222
  // Treat SIGINT/SIGTERM as normal exit (user pressed Ctrl+C)
202
223
  const signal = (execError as { signal?: unknown })?.signal;
224
+ const exitCode =
225
+ (execError as { exitCode?: number | null })?.exitCode ?? null;
203
226
  if (signal === "SIGINT" || signal === "SIGTERM") {
204
- return;
227
+ return { exitCode, signal };
205
228
  }
206
229
  throw execError;
207
230
  }
208
231
  };
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
232
+ // Treat SIGINT (2), SIGTERM (15) as normal exit signals (user interrupts)
211
233
  const isNormalExitSignal = (signal?: number | null) =>
212
- signal === 1 || signal === 2 || signal === 15;
234
+ signal === 2 || signal === 15;
213
235
  const runCommand = async (command: string, commandArgs: string[]) => {
236
+ const runStartedAt = Date.now();
214
237
  if (captureOutput) {
215
238
  const result = await runAgentWithPty({
216
239
  command,
@@ -219,12 +242,27 @@ export async function launchCodexCLI(
219
242
  env,
220
243
  agentId: "codex-cli",
221
244
  });
222
- if (isNormalExitSignal(result.signal)) {
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) {
223
258
  return;
224
259
  }
225
- if (result.exitCode !== null && result.exitCode !== 0) {
260
+ if (signal !== null && signal !== undefined) {
261
+ throw new Error(`Codex CLI terminated by signal ${signal}`);
262
+ }
263
+ if (exitCode !== null && exitCode !== 0) {
226
264
  throw new Error(
227
- `Codex CLI exited with code ${result.exitCode ?? "unknown"}`,
265
+ `Codex CLI exited with code ${exitCode ?? "unknown"}`,
228
266
  );
229
267
  }
230
268
  return;
@@ -234,19 +272,37 @@ export async function launchCodexCLI(
234
272
  return;
235
273
  }
236
274
 
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);
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");
300
+ throw execError;
301
+ }
245
302
  };
246
303
 
247
304
  // Determine execution strategy based on version selection
248
305
  // FR-063b: "installed" option only appears when local command exists
249
- const requestedVersion = options.version ?? "latest";
250
306
  let selectedVersion = requestedVersion;
251
307
 
252
308
  if (requestedVersion === "installed" && !codexLookup.path) {
package/src/index.ts CHANGED
@@ -727,6 +727,9 @@ export async function handleAIToolWorkflow(
727
727
  cwd: worktreePath,
728
728
  sharedEnv,
729
729
  };
730
+ if (tool === "opencode" && normalizedModel) {
731
+ customLaunchOptions.extraArgs = ["-m", normalizedModel];
732
+ }
730
733
  if (toolVersion) {
731
734
  customLaunchOptions.version = toolVersion;
732
735
  }
@@ -14,6 +14,7 @@ export type LogTargetReason =
14
14
  | "worktree-inaccessible"
15
15
  | "current-working-directory"
16
16
  | "working-directory"
17
+ | "working-directory-fallback"
17
18
  | "no-worktree";
18
19
 
19
20
  export interface LogTargetBranch {
@@ -115,7 +116,7 @@ export async function listLogFiles(logDir: string): Promise<LogFileInfo[]> {
115
116
  }
116
117
  }
117
118
 
118
- return files.sort((a, b) => b.date.localeCompare(a.date));
119
+ return files.sort((a, b) => b.mtimeMs - a.mtimeMs);
119
120
  } catch (error) {
120
121
  const err = error as NodeJS.ErrnoException;
121
122
  if (err.code === "ENOENT") {
@@ -125,6 +126,52 @@ export async function listLogFiles(logDir: string): Promise<LogFileInfo[]> {
125
126
  }
126
127
  }
127
128
 
129
+ const getLatestLogMtimeWithContent = async (
130
+ logDir: string,
131
+ ): Promise<number | null> => {
132
+ const files = await listLogFiles(logDir);
133
+ for (const file of files) {
134
+ const lines = await readLogFileLines(file.path);
135
+ if (lines.length > 0) {
136
+ return file.mtimeMs;
137
+ }
138
+ }
139
+ return null;
140
+ };
141
+
142
+ export async function selectLogTargetByRecency(
143
+ primary: LogTargetResolution,
144
+ fallback: LogTargetResolution,
145
+ ): Promise<LogTargetResolution> {
146
+ if (!primary.logDir || !primary.sourcePath) {
147
+ return primary;
148
+ }
149
+ if (!fallback.logDir || !fallback.sourcePath) {
150
+ return primary;
151
+ }
152
+ if (primary.logDir === fallback.logDir) {
153
+ return primary;
154
+ }
155
+ if (primary.reason !== "worktree") {
156
+ return primary;
157
+ }
158
+
159
+ const [primaryMtime, fallbackMtime] = await Promise.all([
160
+ getLatestLogMtimeWithContent(primary.logDir),
161
+ getLatestLogMtimeWithContent(fallback.logDir),
162
+ ]);
163
+
164
+ if (
165
+ fallbackMtime !== null &&
166
+ (primaryMtime === null || fallbackMtime > primaryMtime)
167
+ ) {
168
+ return {
169
+ ...fallback,
170
+ reason: "working-directory-fallback",
171
+ };
172
+ }
173
+ return primary;
174
+ }
128
175
  export async function clearLogFiles(logDir: string): Promise<number> {
129
176
  const files = await listLogFiles(logDir);
130
177
  let cleared = 0;
@@ -5,10 +5,13 @@ import { CLAUDE_CODE_TOOL } from "../config/builtin-coding-agents.js";
5
5
  import {
6
6
  CODEX_DEFAULT_ARGS,
7
7
  CLAUDE_PERMISSION_SKIP_ARGS,
8
+ shouldEnableCodexSkillsFlag,
9
+ withCodexSkillsFlag,
8
10
  } from "../shared/codingAgentConstants.js";
9
11
  import { prepareCodingAgentExecution } from "./codingAgentCommandResolver.js";
10
12
  import type { CodingAgentLaunchOptions } from "../types/tools.js";
11
13
  import { createLogger } from "../logging/logger.js";
14
+ import { findCommand } from "../utils/command.js";
12
15
 
13
16
  const logger = createLogger({ category: "resolver" });
14
17
 
@@ -267,17 +270,21 @@ export function buildCodexArgs(options: CodexCommandOptions = {}): string[] {
267
270
  export async function resolveCodexCommand(
268
271
  options: CodexCommandOptions = {},
269
272
  ): Promise<ResolvedCommand> {
270
- const args = buildCodexArgs(options);
273
+ const codexLookup = await findCommand("codex");
274
+ const baseArgs = buildCodexArgs(options);
275
+ const args = withCodexSkillsFlag(
276
+ baseArgs,
277
+ shouldEnableCodexSkillsFlag(codexLookup.version),
278
+ );
271
279
 
272
280
  // フルパスを取得(node-ptyはシェルを経由しないため必要)
273
- const codexPath = await resolveCommandPath("codex");
274
- if (codexPath) {
281
+ if (codexLookup.source === "installed" && codexLookup.path) {
275
282
  logger.info(
276
- { command: codexPath, usesFallback: false },
283
+ { command: codexLookup.path, usesFallback: false },
277
284
  "Codex command resolved",
278
285
  );
279
286
  return {
280
- command: codexPath,
287
+ command: codexLookup.path,
281
288
  args,
282
289
  usesFallback: false,
283
290
  };
@@ -28,3 +28,76 @@ export const CODEX_DEFAULT_ARGS = [
28
28
  "-c",
29
29
  "shell_environment_policy.experimental_use_profile=true",
30
30
  ] as const;
31
+
32
+ export const CODEX_SKILLS_FLAG_DEPRECATED_FROM = "0.80.0";
33
+
34
+ type ParsedVersion = {
35
+ major: number;
36
+ minor: number;
37
+ patch: number;
38
+ prerelease: string | null;
39
+ };
40
+
41
+ const MODEL_FLAG_PREFIX = "--model=";
42
+
43
+ function normalizeVersion(value?: string | null): string | null {
44
+ if (!value) return null;
45
+ const trimmed = value.trim();
46
+ if (!trimmed) return null;
47
+ return trimmed.replace(/^v/i, "");
48
+ }
49
+
50
+ function parseVersion(value?: string | null): ParsedVersion | null {
51
+ const normalized = normalizeVersion(value);
52
+ if (!normalized) return null;
53
+ const match = normalized.match(
54
+ /^(\d+)\.(\d+)(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?$/,
55
+ );
56
+ if (!match) return null;
57
+ const major = Number(match[1]);
58
+ const minor = Number(match[2]);
59
+ const patch = Number(match[3] ?? "0");
60
+ if (![major, minor, patch].every(Number.isFinite)) return null;
61
+ return {
62
+ major,
63
+ minor,
64
+ patch,
65
+ prerelease: match[4] ?? null,
66
+ };
67
+ }
68
+
69
+ function compareVersions(a: ParsedVersion, b: ParsedVersion): number {
70
+ if (a.major !== b.major) return a.major - b.major;
71
+ if (a.minor !== b.minor) return a.minor - b.minor;
72
+ if (a.patch !== b.patch) return a.patch - b.patch;
73
+ if (a.prerelease && !b.prerelease) return -1;
74
+ if (!a.prerelease && b.prerelease) return 1;
75
+ if (a.prerelease && b.prerelease) {
76
+ return a.prerelease.localeCompare(b.prerelease);
77
+ }
78
+ return 0;
79
+ }
80
+
81
+ export function shouldEnableCodexSkillsFlag(version?: string | null): boolean {
82
+ const parsed = parseVersion(version);
83
+ if (!parsed) return false;
84
+ const threshold = parseVersion(CODEX_SKILLS_FLAG_DEPRECATED_FROM);
85
+ if (!threshold) return false;
86
+ return compareVersions(parsed, threshold) < 0;
87
+ }
88
+
89
+ export function withCodexSkillsFlag(
90
+ args: readonly string[],
91
+ enable: boolean,
92
+ ): string[] {
93
+ if (!enable) return Array.from(args);
94
+ const alreadyEnabled = args.some(
95
+ (arg, index) => arg === "--enable" && args[index + 1] === "skills",
96
+ );
97
+ if (alreadyEnabled) return Array.from(args);
98
+ const next = Array.from(args);
99
+ const modelIndex = next.findIndex((arg) => arg.startsWith(MODEL_FLAG_PREFIX));
100
+ const insertIndex = modelIndex === -1 ? next.length : modelIndex;
101
+ next.splice(insertIndex, 0, "--enable", "skills");
102
+ return next;
103
+ }
@@ -0,0 +1,29 @@
1
+ export interface BunReexecInput {
2
+ hasBunGlobal: boolean;
3
+ bunExecPath?: string | null;
4
+ argv: string[];
5
+ scriptPath: string;
6
+ }
7
+
8
+ export interface BunReexecCommand {
9
+ command: string;
10
+ args: string[];
11
+ }
12
+
13
+ export function buildBunReexecCommand(
14
+ input: BunReexecInput,
15
+ ): BunReexecCommand | null {
16
+ if (input.hasBunGlobal) {
17
+ return null;
18
+ }
19
+
20
+ const scriptPath = input.scriptPath?.trim();
21
+ if (!scriptPath) {
22
+ return null;
23
+ }
24
+
25
+ const bunCommand = input.bunExecPath?.trim() || "bun";
26
+ const args = [scriptPath, ...(input.argv?.slice(2) ?? [])];
27
+
28
+ return { command: bunCommand, args };
29
+ }
@@ -119,7 +119,6 @@ export async function findLatestCodexSession(
119
119
  return { id: sessionId, mtime: file.mtime };
120
120
  }
121
121
 
122
- // Priority 2: Fallback to reading file content if filename lacks UUID
123
122
  // (already handled via info above)
124
123
  }
125
124