@akiojin/gwt 2.11.1 → 2.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 (76) hide show
  1. package/dist/claude.d.ts +4 -1
  2. package/dist/claude.d.ts.map +1 -1
  3. package/dist/claude.js +51 -7
  4. package/dist/claude.js.map +1 -1
  5. package/dist/cli/ui/components/App.d.ts +7 -0
  6. package/dist/cli/ui/components/App.d.ts.map +1 -1
  7. package/dist/cli/ui/components/App.js +307 -18
  8. package/dist/cli/ui/components/App.js.map +1 -1
  9. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts +21 -0
  10. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +1 -0
  11. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +145 -0
  12. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +1 -0
  13. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts +2 -1
  14. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.d.ts.map +1 -1
  15. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js +4 -2
  16. package/dist/cli/ui/components/screens/ExecutionModeSelectorScreen.js.map +1 -1
  17. package/dist/cli/ui/components/screens/ModelSelectorScreen.js +1 -1
  18. package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts +10 -2
  19. package/dist/cli/ui/components/screens/SessionSelectorScreen.d.ts.map +1 -1
  20. package/dist/cli/ui/components/screens/SessionSelectorScreen.js +18 -7
  21. package/dist/cli/ui/components/screens/SessionSelectorScreen.js.map +1 -1
  22. package/dist/cli/ui/types.d.ts +1 -1
  23. package/dist/cli/ui/types.d.ts.map +1 -1
  24. package/dist/cli/ui/utils/continueSession.d.ts +18 -0
  25. package/dist/cli/ui/utils/continueSession.d.ts.map +1 -0
  26. package/dist/cli/ui/utils/continueSession.js +67 -0
  27. package/dist/cli/ui/utils/continueSession.js.map +1 -0
  28. package/dist/codex.d.ts +4 -1
  29. package/dist/codex.d.ts.map +1 -1
  30. package/dist/codex.js +70 -5
  31. package/dist/codex.js.map +1 -1
  32. package/dist/config/index.d.ts +9 -1
  33. package/dist/config/index.d.ts.map +1 -1
  34. package/dist/config/index.js +11 -2
  35. package/dist/config/index.js.map +1 -1
  36. package/dist/gemini.d.ts +4 -1
  37. package/dist/gemini.d.ts.map +1 -1
  38. package/dist/gemini.js +146 -32
  39. package/dist/gemini.js.map +1 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +119 -48
  42. package/dist/index.js.map +1 -1
  43. package/dist/qwen.d.ts +4 -1
  44. package/dist/qwen.d.ts.map +1 -1
  45. package/dist/qwen.js +45 -4
  46. package/dist/qwen.js.map +1 -1
  47. package/dist/utils/prompt.d.ts +6 -0
  48. package/dist/utils/prompt.d.ts.map +1 -0
  49. package/dist/utils/prompt.js +57 -0
  50. package/dist/utils/prompt.js.map +1 -0
  51. package/dist/utils/session.d.ts +82 -0
  52. package/dist/utils/session.d.ts.map +1 -0
  53. package/dist/utils/session.js +579 -0
  54. package/dist/utils/session.js.map +1 -0
  55. package/package.json +2 -2
  56. package/src/claude.ts +69 -8
  57. package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +12 -2
  58. package/src/cli/ui/__tests__/components/screens/BranchQuickStartScreen.test.tsx +142 -0
  59. package/src/cli/ui/__tests__/components/screens/ExecutionModeSelectorScreen.test.tsx +14 -0
  60. package/src/cli/ui/__tests__/components/screens/SessionSelectorScreen.test.tsx +29 -10
  61. package/src/cli/ui/__tests__/integration/edgeCases.test.tsx +4 -1
  62. package/src/cli/ui/components/App.tsx +403 -23
  63. package/src/cli/ui/components/screens/BranchQuickStartScreen.tsx +237 -0
  64. package/src/cli/ui/components/screens/ExecutionModeSelectorScreen.tsx +5 -1
  65. package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +1 -1
  66. package/src/cli/ui/components/screens/SessionSelectorScreen.tsx +34 -6
  67. package/src/cli/ui/types.ts +1 -0
  68. package/src/cli/ui/utils/continueSession.ts +106 -0
  69. package/src/codex.ts +91 -6
  70. package/src/config/index.ts +22 -2
  71. package/src/gemini.ts +179 -41
  72. package/src/index.ts +145 -61
  73. package/src/qwen.ts +56 -5
  74. package/src/utils/__tests__/prompt.test.ts +89 -0
  75. package/src/utils/prompt.ts +74 -0
  76. package/src/utils/session.ts +704 -0
package/src/qwen.ts CHANGED
@@ -2,6 +2,7 @@ import { execa } from "execa";
2
2
  import chalk from "chalk";
3
3
  import { existsSync } from "fs";
4
4
  import { createChildStdio, getTerminalStreams } from "./utils/terminal.js";
5
+ import { findLatestQwenSessionId } from "./utils/session.js";
5
6
 
6
7
  const QWEN_CLI_PACKAGE = "@qwen-code/qwen-code@latest";
7
8
 
@@ -23,8 +24,9 @@ export async function launchQwenCLI(
23
24
  extraArgs?: string[];
24
25
  envOverrides?: Record<string, string>;
25
26
  model?: string;
27
+ sessionId?: string | null;
26
28
  } = {},
27
- ): Promise<void> {
29
+ ): Promise<{ sessionId?: string | null }> {
28
30
  const terminal = getTerminalStreams();
29
31
 
30
32
  try {
@@ -46,18 +48,26 @@ export async function launchQwenCLI(
46
48
  // Handle execution mode
47
49
  // Note: Qwen CLI doesn't have explicit continue/resume CLI options at startup.
48
50
  // Session management is done via /chat commands during interactive sessions.
51
+ const resumeSessionId =
52
+ options.sessionId && options.sessionId.trim().length > 0
53
+ ? options.sessionId.trim()
54
+ : null;
49
55
  switch (options.mode) {
50
56
  case "continue":
51
57
  console.log(
52
58
  chalk.cyan(
53
- " ⏭️ Starting session (use /chat resume in the CLI to continue)",
59
+ resumeSessionId
60
+ ? ` ⏭️ Starting session (then /chat resume ${resumeSessionId})`
61
+ : " ⏭️ Starting session (use /chat resume in the CLI to continue)",
54
62
  ),
55
63
  );
56
64
  break;
57
65
  case "resume":
58
66
  console.log(
59
67
  chalk.cyan(
60
- " 🔄 Starting session (use /chat resume in the CLI to continue)",
68
+ resumeSessionId
69
+ ? ` 🔄 Starting session (then /chat resume ${resumeSessionId})`
70
+ : " 🔄 Starting session (use /chat resume in the CLI to continue)",
61
71
  ),
62
72
  );
63
73
  break;
@@ -80,6 +90,8 @@ export async function launchQwenCLI(
80
90
  args.push(...options.extraArgs);
81
91
  }
82
92
 
93
+ console.log(chalk.gray(` 📋 Args: ${args.join(" ")}`));
94
+
83
95
  terminal.exitRawMode();
84
96
 
85
97
  const baseEnv = {
@@ -93,10 +105,22 @@ export async function launchQwenCLI(
93
105
  const hasLocalQwen = await isQwenCommandAvailable();
94
106
 
95
107
  try {
108
+ const execChild = async (child: any) => {
109
+ try {
110
+ await child;
111
+ } catch (execError: any) {
112
+ // Treat SIGINT/SIGTERM as normal exit (user pressed Ctrl+C)
113
+ if (execError.signal === "SIGINT" || execError.signal === "SIGTERM") {
114
+ return;
115
+ }
116
+ throw execError;
117
+ }
118
+ };
119
+
96
120
  if (hasLocalQwen) {
97
121
  // Use locally installed qwen command
98
122
  console.log(chalk.green(" ✨ Using locally installed qwen command"));
99
- await execa("qwen", args, {
123
+ const child = execa("qwen", args, {
100
124
  cwd: worktreePath,
101
125
  shell: true,
102
126
  stdin: childStdio.stdin,
@@ -104,6 +128,7 @@ export async function launchQwenCLI(
104
128
  stderr: childStdio.stderr,
105
129
  env: baseEnv,
106
130
  } as any);
131
+ await execChild(child);
107
132
  } else {
108
133
  // Fallback to bunx
109
134
  console.log(
@@ -118,7 +143,7 @@ export async function launchQwenCLI(
118
143
  console.log("");
119
144
  // Wait 2 seconds to let user read the message
120
145
  await new Promise((resolve) => setTimeout(resolve, 2000));
121
- await execa("bunx", [QWEN_CLI_PACKAGE, ...args], {
146
+ const child = execa("bunx", [QWEN_CLI_PACKAGE, ...args], {
122
147
  cwd: worktreePath,
123
148
  shell: true,
124
149
  stdin: childStdio.stdin,
@@ -126,10 +151,36 @@ export async function launchQwenCLI(
126
151
  stderr: childStdio.stderr,
127
152
  env: baseEnv,
128
153
  } as any);
154
+ await execChild(child);
129
155
  }
130
156
  } finally {
131
157
  childStdio.cleanup();
132
158
  }
159
+
160
+ let capturedSessionId: string | null = null;
161
+ try {
162
+ capturedSessionId =
163
+ (await findLatestQwenSessionId(worktreePath)) ??
164
+ resumeSessionId ??
165
+ null;
166
+ } catch {
167
+ capturedSessionId = resumeSessionId ?? null;
168
+ }
169
+
170
+ if (capturedSessionId) {
171
+ console.log(chalk.cyan(`\n 🆔 Session tag: ${capturedSessionId}`));
172
+ console.log(
173
+ chalk.gray(` Resume in Qwen CLI: /chat resume ${capturedSessionId}`),
174
+ );
175
+ } else {
176
+ console.log(
177
+ chalk.yellow(
178
+ "\n ℹ️ Could not determine Qwen session tag automatically.",
179
+ ),
180
+ );
181
+ }
182
+
183
+ return capturedSessionId ? { sessionId: capturedSessionId } : {};
133
184
  } catch (error: any) {
134
185
  const hasLocalQwen = await isQwenCommandAvailable();
135
186
  let errorMessage: string;
@@ -0,0 +1,89 @@
1
+ import { PassThrough } from "node:stream";
2
+ import { describe, expect, it, vi } from "vitest";
3
+
4
+ // Shared mock target to avoid hoisting issues
5
+ const terminalStreams: Record<string, unknown> = {};
6
+
7
+ vi.mock("../terminal.js", () => ({
8
+ getTerminalStreams: () => terminalStreams,
9
+ }));
10
+
11
+ const withTimeout = <T>(promise: Promise<T>, ms = 500): Promise<T> =>
12
+ Promise.race([
13
+ promise,
14
+ new Promise<T>((_, reject) =>
15
+ setTimeout(() => reject(new Error("timeout")), ms),
16
+ ),
17
+ ]);
18
+
19
+ describe("waitForEnter", () => {
20
+ it("uses terminal stdin/stdout and resolves after newline on TTY", async () => {
21
+ vi.resetModules();
22
+ for (const key of Object.keys(terminalStreams)) {
23
+ delete terminalStreams[key];
24
+ }
25
+
26
+ const stdin = new PassThrough() as unknown as NodeJS.ReadStream;
27
+ const stdout = new PassThrough() as unknown as NodeJS.WriteStream;
28
+ Object.defineProperty(stdin, "isTTY", { value: true });
29
+
30
+ let resumed = false;
31
+ let paused = false;
32
+ const originalResume = stdin.resume.bind(stdin);
33
+ const originalPause = stdin.pause.bind(stdin);
34
+ // Track resume/pause calls
35
+ stdin.resume = (() => {
36
+ resumed = true;
37
+ return originalResume();
38
+ }) as typeof stdin.resume;
39
+ stdin.pause = (() => {
40
+ paused = true;
41
+ return originalPause();
42
+ }) as typeof stdin.pause;
43
+
44
+ const exitRawMode = vi.fn();
45
+
46
+ Object.assign(terminalStreams, {
47
+ stdin,
48
+ stdout,
49
+ stderr: stdout,
50
+ usingFallback: false,
51
+ exitRawMode,
52
+ });
53
+
54
+ const { waitForEnter } = await import("../prompt.js");
55
+
56
+ const waiting = withTimeout(waitForEnter("prompt"), 200);
57
+ stdin.write("hello\n");
58
+
59
+ await expect(waiting).resolves.toBeUndefined();
60
+ expect(resumed).toBe(true);
61
+ expect(paused).toBe(true);
62
+ expect(exitRawMode).toHaveBeenCalled();
63
+ });
64
+
65
+ it("returns immediately on non-TTY stdin", async () => {
66
+ vi.resetModules();
67
+ for (const key of Object.keys(terminalStreams)) {
68
+ delete terminalStreams[key];
69
+ }
70
+
71
+ const stdin = new PassThrough() as unknown as NodeJS.ReadStream;
72
+ const stdout = new PassThrough() as unknown as NodeJS.WriteStream;
73
+ Object.defineProperty(stdin, "isTTY", { value: false });
74
+
75
+ Object.assign(terminalStreams, {
76
+ stdin,
77
+ stdout,
78
+ stderr: stdout,
79
+ usingFallback: false,
80
+ exitRawMode: vi.fn(),
81
+ });
82
+
83
+ const { waitForEnter } = await import("../prompt.js");
84
+
85
+ const start = Date.now();
86
+ await waitForEnter("prompt");
87
+ expect(Date.now() - start).toBeLessThan(50);
88
+ });
89
+ });
@@ -0,0 +1,74 @@
1
+ import readline from "node:readline";
2
+ import { getTerminalStreams } from "./terminal.js";
3
+
4
+ /**
5
+ * Wait for Enter using the same terminal streams as Ink.
6
+ * Falls back to no-op on non-interactive stdin to avoid blocking pipelines.
7
+ */
8
+ export async function waitForEnter(promptMessage: string): Promise<void> {
9
+ const terminal = getTerminalStreams();
10
+ const stdin = terminal.stdin as NodeJS.ReadStream | undefined;
11
+ const stdout = terminal.stdout as NodeJS.WriteStream | undefined;
12
+
13
+ if (!stdin || typeof stdin.on !== "function" || !stdin.isTTY) {
14
+ return;
15
+ }
16
+
17
+ terminal.exitRawMode?.();
18
+
19
+ if (typeof stdin.resume === "function") {
20
+ stdin.resume();
21
+ }
22
+
23
+ if ((stdin as NodeJS.ReadStream & { isRaw?: boolean }).isRaw) {
24
+ try {
25
+ (stdin as NodeJS.ReadStream & { setRawMode?: (flag: boolean) => void }).setRawMode?.(false);
26
+ } catch {
27
+ // Ignore raw mode errors
28
+ }
29
+ }
30
+
31
+ await new Promise<void>((resolve) => {
32
+ const rl = readline.createInterface({ input: stdin, output: stdout });
33
+
34
+ const cleanup = () => {
35
+ rl.removeAllListeners();
36
+ rl.close();
37
+ const remover = (method: "off" | "removeListener") =>
38
+ (stdin as unknown as Record<string, (event: string, fn: () => void) => void>)[method]?.(
39
+ "end",
40
+ onEnd,
41
+ );
42
+ remover("off");
43
+ remover("removeListener");
44
+ const removerErr = (method: "off" | "removeListener") =>
45
+ (stdin as unknown as Record<string, (event: string, fn: () => void) => void>)[method]?.(
46
+ "error",
47
+ onEnd,
48
+ );
49
+ removerErr("off");
50
+ removerErr("removeListener");
51
+ if (typeof stdin.pause === "function") {
52
+ stdin.pause();
53
+ }
54
+ };
55
+
56
+ const onEnd = () => {
57
+ cleanup();
58
+ resolve();
59
+ };
60
+
61
+ rl.on("SIGINT", () => {
62
+ cleanup();
63
+ process.exit(0);
64
+ });
65
+
66
+ rl.question(`${promptMessage}\n`, () => {
67
+ cleanup();
68
+ resolve();
69
+ });
70
+
71
+ stdin.once("end", onEnd);
72
+ stdin.once("error", onEnd);
73
+ });
74
+ }