@akiojin/gwt 4.9.0 → 4.10.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.
Files changed (180) hide show
  1. package/README.ja.md +58 -34
  2. package/README.md +18 -34
  3. package/dist/cli/ui/components/App.d.ts +2 -2
  4. package/dist/cli/ui/components/App.d.ts.map +1 -1
  5. package/dist/cli/ui/components/App.js +27 -9
  6. package/dist/cli/ui/components/App.js.map +1 -1
  7. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts +1 -1
  8. package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +1 -1
  9. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +4 -1
  10. package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +1 -1
  11. package/dist/cli/ui/components/screens/CodingAgentSelectorScreen.d.ts +27 -0
  12. package/dist/cli/ui/components/screens/CodingAgentSelectorScreen.d.ts.map +1 -0
  13. package/dist/cli/ui/components/screens/{AIToolSelectorScreen.js → CodingAgentSelectorScreen.js} +35 -35
  14. package/dist/cli/ui/components/screens/CodingAgentSelectorScreen.js.map +1 -0
  15. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts +2 -2
  16. package/dist/cli/ui/components/screens/ModelSelectorScreen.d.ts.map +1 -1
  17. package/dist/cli/ui/components/screens/ModelSelectorScreen.js.map +1 -1
  18. package/dist/cli/ui/types.d.ts +2 -2
  19. package/dist/cli/ui/types.d.ts.map +1 -1
  20. package/dist/cli/ui/utils/modelOptions.d.ts +4 -4
  21. package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -1
  22. package/dist/cli/ui/utils/modelOptions.js.map +1 -1
  23. package/dist/client/assets/{index-PqK9jkug.js → index-LNPtOrn3.js} +17 -17
  24. package/dist/client/index.html +1 -1
  25. package/dist/config/builtin-coding-agents.d.ts +27 -0
  26. package/dist/config/builtin-coding-agents.d.ts.map +1 -0
  27. package/dist/config/{builtin-tools.js → builtin-coding-agents.js} +21 -6
  28. package/dist/config/builtin-coding-agents.js.map +1 -0
  29. package/dist/config/tools.d.ts +16 -16
  30. package/dist/config/tools.d.ts.map +1 -1
  31. package/dist/config/tools.js +92 -78
  32. package/dist/config/tools.js.map +1 -1
  33. package/dist/index.js +19 -19
  34. package/dist/index.js.map +1 -1
  35. package/dist/launcher.d.ts +8 -8
  36. package/dist/launcher.d.ts.map +1 -1
  37. package/dist/launcher.js +32 -28
  38. package/dist/launcher.js.map +1 -1
  39. package/dist/services/codingAgentCommandResolver.d.ts +10 -0
  40. package/dist/services/codingAgentCommandResolver.d.ts.map +1 -0
  41. package/dist/services/{customToolResolver.js → codingAgentCommandResolver.js} +25 -20
  42. package/dist/services/codingAgentCommandResolver.js.map +1 -0
  43. package/dist/services/{aiToolResolver.d.ts → codingAgentResolver.d.ts} +6 -6
  44. package/dist/services/codingAgentResolver.d.ts.map +1 -0
  45. package/dist/services/{aiToolResolver.js → codingAgentResolver.js} +23 -23
  46. package/dist/services/codingAgentResolver.js.map +1 -0
  47. package/dist/shared/{aiToolConstants.d.ts → codingAgentConstants.d.ts} +2 -2
  48. package/dist/shared/codingAgentConstants.d.ts.map +1 -0
  49. package/dist/shared/{aiToolConstants.js → codingAgentConstants.js} +2 -2
  50. package/dist/shared/codingAgentConstants.js.map +1 -0
  51. package/dist/types/api.d.ts +12 -12
  52. package/dist/types/api.d.ts.map +1 -1
  53. package/dist/types/tools.d.ts +30 -30
  54. package/dist/types/tools.d.ts.map +1 -1
  55. package/dist/types/tools.js +1 -1
  56. package/dist/utils/command.d.ts.map +1 -1
  57. package/dist/utils/command.js +9 -0
  58. package/dist/utils/command.js.map +1 -1
  59. package/dist/utils/session/index.d.ts +5 -3
  60. package/dist/utils/session/index.d.ts.map +1 -1
  61. package/dist/utils/session/index.js +5 -2
  62. package/dist/utils/session/index.js.map +1 -1
  63. package/dist/utils/session/parsers/index.d.ts +1 -0
  64. package/dist/utils/session/parsers/index.d.ts.map +1 -1
  65. package/dist/utils/session/parsers/index.js +2 -0
  66. package/dist/utils/session/parsers/index.js.map +1 -1
  67. package/dist/utils/session/parsers/opencode.d.ts +23 -0
  68. package/dist/utils/session/parsers/opencode.d.ts.map +1 -0
  69. package/dist/utils/session/parsers/opencode.js +89 -0
  70. package/dist/utils/session/parsers/opencode.js.map +1 -0
  71. package/dist/utils/session/types.d.ts +4 -0
  72. package/dist/utils/session/types.d.ts.map +1 -1
  73. package/dist/web/client/src/components/CodingAgentLaunchModal.d.ts +9 -0
  74. package/dist/web/client/src/components/CodingAgentLaunchModal.d.ts.map +1 -0
  75. package/dist/web/client/src/components/{AIToolLaunchModal.js → CodingAgentLaunchModal.js} +58 -58
  76. package/dist/web/client/src/components/CodingAgentLaunchModal.js.map +1 -0
  77. package/dist/web/client/src/components/CustomCodingAgentForm.d.ts +23 -0
  78. package/dist/web/client/src/components/CustomCodingAgentForm.d.ts.map +1 -0
  79. package/dist/web/client/src/components/{CustomToolForm.js → CustomCodingAgentForm.js} +5 -5
  80. package/dist/web/client/src/components/CustomCodingAgentForm.js.map +1 -0
  81. package/dist/web/client/src/components/CustomCodingAgentList.d.ts +10 -0
  82. package/dist/web/client/src/components/CustomCodingAgentList.d.ts.map +1 -0
  83. package/dist/web/client/src/components/{CustomToolList.js → CustomCodingAgentList.js} +17 -17
  84. package/dist/web/client/src/components/CustomCodingAgentList.js.map +1 -0
  85. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts +2 -2
  86. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts.map +1 -1
  87. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js +6 -6
  88. package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js.map +1 -1
  89. package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts +2 -2
  90. package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts.map +1 -1
  91. package/dist/web/client/src/components/branch-detail/ToolLauncher.js +5 -5
  92. package/dist/web/client/src/components/branch-detail/ToolLauncher.js.map +1 -1
  93. package/dist/web/client/src/hooks/useSessions.d.ts +4 -4
  94. package/dist/web/client/src/hooks/useSessions.d.ts.map +1 -1
  95. package/dist/web/client/src/hooks/useSessions.js.map +1 -1
  96. package/dist/web/client/src/lib/api.d.ts +5 -5
  97. package/dist/web/client/src/lib/api.d.ts.map +1 -1
  98. package/dist/web/client/src/lib/api.js +1 -1
  99. package/dist/web/client/src/lib/api.js.map +1 -1
  100. package/dist/web/client/src/pages/BranchDetailPage.js +24 -24
  101. package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
  102. package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
  103. package/dist/web/client/src/pages/ConfigManagementPage.js +15 -15
  104. package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
  105. package/dist/web/client/src/pages/ConfigPage.d.ts.map +1 -1
  106. package/dist/web/client/src/pages/ConfigPage.js +43 -39
  107. package/dist/web/client/src/pages/ConfigPage.js.map +1 -1
  108. package/dist/web/server/env/importer.d.ts.map +1 -1
  109. package/dist/web/server/env/importer.js +3 -3
  110. package/dist/web/server/env/importer.js.map +1 -1
  111. package/dist/web/server/pty/manager.d.ts +6 -6
  112. package/dist/web/server/pty/manager.d.ts.map +1 -1
  113. package/dist/web/server/pty/manager.js +11 -11
  114. package/dist/web/server/pty/manager.js.map +1 -1
  115. package/dist/web/server/routes/config.d.ts.map +1 -1
  116. package/dist/web/server/routes/config.js +34 -34
  117. package/dist/web/server/routes/config.js.map +1 -1
  118. package/dist/web/server/routes/sessions.d.ts +1 -1
  119. package/dist/web/server/routes/sessions.d.ts.map +1 -1
  120. package/dist/web/server/routes/sessions.js +20 -20
  121. package/dist/web/server/routes/sessions.js.map +1 -1
  122. package/package.json +2 -2
  123. package/src/cli/ui/__tests__/components/screens/{AIToolSelectorScreen.test.tsx → CodingAgentSelectorScreen.test.tsx} +38 -38
  124. package/src/cli/ui/components/App.tsx +44 -20
  125. package/src/cli/ui/components/screens/BranchQuickStartScreen.tsx +6 -3
  126. package/src/cli/ui/components/screens/CodingAgentSelectorScreen.tsx +159 -0
  127. package/src/cli/ui/components/screens/ModelSelectorScreen.tsx +6 -2
  128. package/src/cli/ui/types.ts +2 -2
  129. package/src/cli/ui/utils/modelOptions.ts +6 -4
  130. package/src/config/{builtin-tools.ts → builtin-coding-agents.ts} +25 -9
  131. package/src/config/tools.ts +120 -92
  132. package/src/index.ts +19 -19
  133. package/src/launcher.ts +38 -31
  134. package/src/services/{customToolResolver.ts → codingAgentCommandResolver.ts} +33 -28
  135. package/src/services/{aiToolResolver.ts → codingAgentResolver.ts} +28 -28
  136. package/src/shared/{aiToolConstants.ts → codingAgentConstants.ts} +1 -1
  137. package/src/types/api.ts +12 -12
  138. package/src/types/tools.ts +30 -30
  139. package/src/utils/command.ts +9 -0
  140. package/src/utils/session/index.ts +10 -2
  141. package/src/utils/session/parsers/index.ts +6 -0
  142. package/src/utils/session/parsers/opencode.ts +110 -0
  143. package/src/utils/session/types.ts +5 -0
  144. package/src/web/client/src/components/{AIToolLaunchModal.tsx → CodingAgentLaunchModal.tsx} +74 -70
  145. package/src/web/client/src/components/{CustomToolForm.tsx → CustomCodingAgentForm.tsx} +14 -14
  146. package/src/web/client/src/components/{CustomToolList.tsx → CustomCodingAgentList.tsx} +26 -26
  147. package/src/web/client/src/components/branch-detail/SessionHistoryTable.tsx +7 -7
  148. package/src/web/client/src/components/branch-detail/ToolLauncher.tsx +9 -9
  149. package/src/web/client/src/hooks/useSessions.ts +5 -5
  150. package/src/web/client/src/lib/api.ts +8 -8
  151. package/src/web/client/src/pages/BranchDetailPage.tsx +26 -26
  152. package/src/web/client/src/pages/ConfigManagementPage.tsx +32 -24
  153. package/src/web/client/src/pages/ConfigPage.tsx +55 -49
  154. package/src/web/server/env/importer.ts +6 -3
  155. package/src/web/server/pty/manager.ts +20 -20
  156. package/src/web/server/routes/config.ts +45 -39
  157. package/src/web/server/routes/sessions.ts +29 -26
  158. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts +0 -27
  159. package/dist/cli/ui/components/screens/AIToolSelectorScreen.d.ts.map +0 -1
  160. package/dist/cli/ui/components/screens/AIToolSelectorScreen.js.map +0 -1
  161. package/dist/config/builtin-tools.d.ts +0 -23
  162. package/dist/config/builtin-tools.d.ts.map +0 -1
  163. package/dist/config/builtin-tools.js.map +0 -1
  164. package/dist/services/aiToolResolver.d.ts.map +0 -1
  165. package/dist/services/aiToolResolver.js.map +0 -1
  166. package/dist/services/customToolResolver.d.ts +0 -10
  167. package/dist/services/customToolResolver.d.ts.map +0 -1
  168. package/dist/services/customToolResolver.js.map +0 -1
  169. package/dist/shared/aiToolConstants.d.ts.map +0 -1
  170. package/dist/shared/aiToolConstants.js.map +0 -1
  171. package/dist/web/client/src/components/AIToolLaunchModal.d.ts +0 -9
  172. package/dist/web/client/src/components/AIToolLaunchModal.d.ts.map +0 -1
  173. package/dist/web/client/src/components/AIToolLaunchModal.js.map +0 -1
  174. package/dist/web/client/src/components/CustomToolForm.d.ts +0 -23
  175. package/dist/web/client/src/components/CustomToolForm.d.ts.map +0 -1
  176. package/dist/web/client/src/components/CustomToolForm.js.map +0 -1
  177. package/dist/web/client/src/components/CustomToolList.d.ts +0 -10
  178. package/dist/web/client/src/components/CustomToolList.d.ts.map +0 -1
  179. package/dist/web/client/src/components/CustomToolList.js.map +0 -1
  180. package/src/cli/ui/components/screens/AIToolSelectorScreen.tsx +0 -153
@@ -1,5 +1,5 @@
1
1
  /**
2
- * カスタムAIツール対応機能の型定義
2
+ * コーディングエージェント対応機能の型定義
3
3
  *
4
4
  * この型定義ファイルは、設定ファイル(tools.json)のスキーマと
5
5
  * 内部で使用するデータ構造を定義します。
@@ -42,30 +42,30 @@ export interface ModeArgs {
42
42
  }
43
43
 
44
44
  /**
45
- * カスタムAIツール定義
45
+ * コーディングエージェント定義
46
46
  *
47
- * tools.jsonファイルで定義される個別のツール設定。
47
+ * tools.jsonファイルで定義される個別のエージェント設定。
48
48
  */
49
- export interface CustomAITool {
49
+ export interface CodingAgent {
50
50
  /**
51
- * ツールの一意識別子
51
+ * エージェントの一意識別子
52
52
  *
53
53
  * 小文字英数字とハイフンのみ使用可能(パターン: ^[a-z0-9-]+$)
54
- * ビルトインツール(claude-code, codex-cli)との重複は不可。
54
+ * ビルトインエージェント(claude-code, codex-cli)との重複は不可。
55
55
  */
56
56
  id: string;
57
57
 
58
58
  /**
59
59
  * UI表示名
60
60
  *
61
- * ツール選択画面で表示される名前。日本語も使用可能。
61
+ * エージェント選択画面で表示される名前。日本語も使用可能。
62
62
  */
63
63
  displayName: string;
64
64
 
65
65
  /**
66
66
  * アイコン文字(オプション)
67
67
  *
68
- * ツール選択画面で表示されるUnicode文字。
68
+ * エージェント選択画面で表示されるUnicode文字。
69
69
  */
70
70
  icon?: string;
71
71
 
@@ -96,7 +96,7 @@ export interface CustomAITool {
96
96
  /**
97
97
  * デフォルト引数(オプション)
98
98
  *
99
- * ツール実行時に常に付与される引数。
99
+ * エージェント実行時に常に付与される引数。
100
100
  * 最終的な引数は: defaultArgs + modeArgs[mode] + permissionSkipArgs + extraArgs
101
101
  */
102
102
  defaultArgs?: string[];
@@ -119,7 +119,7 @@ export interface CustomAITool {
119
119
  /**
120
120
  * 環境変数(オプション)
121
121
  *
122
- * ツール起動時に設定される環境変数。
122
+ * エージェント起動時に設定される環境変数。
123
123
  * APIキーや設定ファイルパスなどを指定。
124
124
  */
125
125
  env?: Record<string, string>;
@@ -136,11 +136,11 @@ export interface CustomAITool {
136
136
  }
137
137
 
138
138
  /**
139
- * ツール設定ファイル全体
139
+ * コーディングエージェント設定ファイル全体
140
140
  *
141
141
  * ~/.gwt/tools.json のスキーマ。
142
142
  */
143
- export interface ToolsConfig {
143
+ export interface CodingAgentsConfig {
144
144
  /**
145
145
  * 設定フォーマットのバージョン
146
146
  *
@@ -154,16 +154,16 @@ export interface ToolsConfig {
154
154
  updatedAt?: string;
155
155
 
156
156
  /**
157
- * すべてのツールで共有する環境変数
157
+ * すべてのエージェントで共有する環境変数
158
158
  */
159
159
  env?: Record<string, string>;
160
160
 
161
161
  /**
162
- * カスタムツール定義の配列
162
+ * カスタムコーディングエージェント定義の配列
163
163
  *
164
- * 空配列も許可(ビルトインツールのみ使用)。
164
+ * 空配列も許可(ビルトインエージェントのみ使用)。
165
165
  */
166
- customTools: CustomAITool[];
166
+ customCodingAgents: CodingAgent[];
167
167
  }
168
168
 
169
169
  // ============================================================================
@@ -171,17 +171,17 @@ export interface ToolsConfig {
171
171
  // ============================================================================
172
172
 
173
173
  /**
174
- * 統合ツール設定
174
+ * 統合コーディングエージェント設定
175
175
  *
176
- * ビルトインツールとカスタムツールを統合して扱うための内部型。
177
- * getAllTools() 関数がこの型の配列を返します。
176
+ * ビルトインエージェントとカスタムエージェントを統合して扱うための内部型。
177
+ * getAllCodingAgents() 関数がこの型の配列を返します。
178
178
  */
179
- export interface AIToolConfig {
179
+ export interface CodingAgentConfig {
180
180
  /**
181
- * ツールID
181
+ * エージェントID
182
182
  *
183
183
  * ビルトイン: "claude-code" | "codex-cli"
184
- * カスタム: CustomAITool.id
184
+ * カスタム: CodingAgent.id
185
185
  */
186
186
  id: string;
187
187
 
@@ -196,27 +196,27 @@ export interface AIToolConfig {
196
196
  icon?: string;
197
197
 
198
198
  /**
199
- * ビルトインツールかどうか
199
+ * ビルトインエージェントかどうか
200
200
  *
201
201
  * true: Claude Code または Codex CLI
202
- * false: カスタムツール
202
+ * false: カスタムエージェント
203
203
  */
204
204
  isBuiltin: boolean;
205
205
 
206
206
  /**
207
- * カスタムツールの場合、元の設定
207
+ * カスタムエージェントの場合、元の設定
208
208
  *
209
209
  * isBuiltin=false の場合のみ存在。
210
210
  */
211
- customConfig?: CustomAITool;
211
+ customConfig?: CodingAgent;
212
212
  }
213
213
 
214
214
  /**
215
- * ツール起動オプション
215
+ * コーディングエージェント起動オプション
216
216
  *
217
- * launchCustomAITool() 関数の引数として使用。
217
+ * launchCodingAgent() 関数の引数として使用。
218
218
  */
219
- export interface LaunchOptions {
219
+ export interface CodingAgentLaunchOptions {
220
220
  /**
221
221
  * 実行モード
222
222
  */
@@ -239,7 +239,7 @@ export interface LaunchOptions {
239
239
  /**
240
240
  * 作業ディレクトリ(ワークツリーパス)
241
241
  *
242
- * ツール起動時のcwdとして使用されます。
242
+ * エージェント起動時のcwdとして使用されます。
243
243
  */
244
244
  cwd?: string;
245
245
 
@@ -41,6 +41,14 @@ const KNOWN_INSTALL_PATHS: Record<string, { unix: string[]; win32: string[] }> =
41
41
  ],
42
42
  win32: [join(homedir(), ".bun", "bin", "gemini.exe")],
43
43
  },
44
+ opencode: {
45
+ unix: [
46
+ join(homedir(), ".bun", "bin", "opencode"),
47
+ join(homedir(), ".local", "bin", "opencode"),
48
+ "/usr/local/bin/opencode",
49
+ ],
50
+ win32: [join(homedir(), ".bun", "bin", "opencode.exe")],
51
+ },
44
52
  };
45
53
 
46
54
  /**
@@ -50,6 +58,7 @@ const BUILTIN_TOOLS = [
50
58
  { id: "claude-code", commandName: "claude", displayName: "Claude" },
51
59
  { id: "codex-cli", commandName: "codex", displayName: "Codex" },
52
60
  { id: "gemini-cli", commandName: "gemini", displayName: "Gemini" },
61
+ { id: "opencode", commandName: "opencode", displayName: "OpenCode" },
53
62
  ] as const;
54
63
 
55
64
  /**
@@ -1,10 +1,11 @@
1
1
  /**
2
- * Session module - unified session management for AI tools
2
+ * Session module - unified session management for coding agents
3
3
  *
4
- * This module provides session detection and management for various AI CLI tools:
4
+ * This module provides session detection and management for various coding agents:
5
5
  * - Claude Code
6
6
  * - Codex CLI
7
7
  * - Gemini CLI
8
+ * - OpenCode
8
9
  */
9
10
 
10
11
  // Type exports
@@ -14,6 +15,7 @@ export type {
14
15
  ClaudeSessionInfo,
15
16
  CodexSessionInfo,
16
17
  GeminiSessionInfo,
18
+ OpenCodeSessionInfo,
17
19
  } from "./types.js";
18
20
 
19
21
  // Common utilities
@@ -40,3 +42,9 @@ export {
40
42
  findLatestGeminiSession,
41
43
  findLatestGeminiSessionId,
42
44
  } from "./parsers/gemini.js";
45
+
46
+ // OpenCode parser
47
+ export {
48
+ findLatestOpenCodeSession,
49
+ findLatestOpenCodeSessionId,
50
+ } from "./parsers/opencode.js";
@@ -23,3 +23,9 @@ export {
23
23
  findLatestGeminiSession,
24
24
  findLatestGeminiSessionId,
25
25
  } from "./gemini.js";
26
+
27
+ // OpenCode
28
+ export {
29
+ findLatestOpenCodeSession,
30
+ findLatestOpenCodeSessionId,
31
+ } from "./opencode.js";
@@ -0,0 +1,110 @@
1
+ /**
2
+ * OpenCode session parser
3
+ *
4
+ * Handles session detection for OpenCode.
5
+ * Session files are stored in ~/.local/share/opencode/storage/session/[projectID]/[sessionID].json
6
+ * Session JSON contains: id, projectID, directory (cwd), title, created, updated, etc.
7
+ */
8
+
9
+ import path from "node:path";
10
+ import { homedir } from "node:os";
11
+
12
+ import type { OpenCodeSessionInfo, SessionSearchOptions } from "../types.js";
13
+ import {
14
+ collectFilesIterative,
15
+ matchesCwd,
16
+ readSessionInfoFromFile,
17
+ } from "../common.js";
18
+
19
+ /**
20
+ * Returns the OpenCode session directory
21
+ * OpenCode stores sessions in: ~/.local/share/opencode/storage/session/[projectID]/[sessionID].json
22
+ */
23
+ function getOpenCodeSessionDir(): string {
24
+ return path.join(
25
+ homedir(),
26
+ ".local",
27
+ "share",
28
+ "opencode",
29
+ "storage",
30
+ "session",
31
+ );
32
+ }
33
+
34
+ /**
35
+ * Finds the latest OpenCode session with optional time filtering and cwd matching.
36
+ *
37
+ * @param options - Search options including time filters and cwd matching
38
+ * @returns Session info with ID and modification time, or null if not found
39
+ */
40
+ export async function findLatestOpenCodeSession(
41
+ options: SessionSearchOptions = {},
42
+ ): Promise<OpenCodeSessionInfo | null> {
43
+ // OpenCode stores sessions under ~/.local/share/opencode/storage/session/[projectID]/
44
+ const baseDir = getOpenCodeSessionDir();
45
+ const files = await collectFilesIterative(
46
+ baseDir,
47
+ (name) => name.endsWith(".json") || name.endsWith(".jsonl"),
48
+ );
49
+ if (!files.length) return null;
50
+
51
+ // Apply time filters
52
+ let pool = files;
53
+ const sinceVal = options.since;
54
+ if (sinceVal !== undefined) {
55
+ pool = pool.filter((f) => f.mtime >= sinceVal);
56
+ }
57
+ const untilVal = options.until;
58
+ if (untilVal !== undefined) {
59
+ pool = pool.filter((f) => f.mtime <= untilVal);
60
+ }
61
+ if (!pool.length) return null;
62
+
63
+ // Sort by preference or mtime
64
+ const ref = options.preferClosestTo;
65
+ const window = options.windowMs ?? 30 * 60 * 1000;
66
+ pool = pool.slice().sort((a, b) => {
67
+ if (typeof ref === "number") {
68
+ const da = Math.abs(a.mtime - ref);
69
+ const db = Math.abs(b.mtime - ref);
70
+ if (da === db) return b.mtime - a.mtime;
71
+ if (da <= window || db <= window) return da - db;
72
+ }
73
+ return b.mtime - a.mtime;
74
+ });
75
+
76
+ for (const file of pool) {
77
+ const info = await readSessionInfoFromFile(file.fullPath);
78
+ if (!info.id) continue;
79
+ if (options.cwd) {
80
+ if (matchesCwd(info.cwd, options.cwd)) {
81
+ return { id: info.id, mtime: file.mtime };
82
+ }
83
+ continue;
84
+ }
85
+ return { id: info.id, mtime: file.mtime };
86
+ }
87
+
88
+ return null;
89
+ }
90
+
91
+ /**
92
+ * Finds the latest OpenCode session ID.
93
+ * @param cwd - The working directory to find sessions for (used as fallback if options.cwd not set)
94
+ * @param options - Search options including time filters and cwd matching
95
+ * @returns Session ID string or null if not found
96
+ */
97
+ export async function findLatestOpenCodeSessionId(
98
+ cwd: string,
99
+ options: SessionSearchOptions = {},
100
+ ): Promise<string | null> {
101
+ const searchOptions: SessionSearchOptions = { cwd: options.cwd ?? cwd };
102
+ if (options.since !== undefined) searchOptions.since = options.since;
103
+ if (options.until !== undefined) searchOptions.until = options.until;
104
+ if (options.preferClosestTo !== undefined)
105
+ searchOptions.preferClosestTo = options.preferClosestTo;
106
+ if (options.windowMs !== undefined) searchOptions.windowMs = options.windowMs;
107
+
108
+ const found = await findLatestOpenCodeSession(searchOptions);
109
+ return found?.id ?? null;
110
+ }
@@ -40,3 +40,8 @@ export type CodexSessionInfo = SessionInfo;
40
40
  * Gemini CLI session info
41
41
  */
42
42
  export type GeminiSessionInfo = SessionInfo;
43
+
44
+ /**
45
+ * OpenCode session info
46
+ */
47
+ export type OpenCodeSessionInfo = SessionInfo;
@@ -11,18 +11,18 @@ import {
11
11
  SelectTrigger,
12
12
  SelectValue,
13
13
  } from "@/components/ui/select";
14
- import type { Branch, CustomAITool } from "../../../../types/api.js";
14
+ import type { Branch, ApiCodingAgent } from "../../../../types/api.js";
15
15
  import {
16
16
  CLAUDE_PERMISSION_SKIP_ARGS,
17
17
  CODEX_DEFAULT_ARGS,
18
- } from "../../../../shared/aiToolConstants.js";
18
+ } from "../../../../shared/codingAgentConstants.js";
19
19
  import { useConfig } from "../hooks/useConfig";
20
20
  import { useStartSession } from "../hooks/useSessions";
21
21
  import { useCreateWorktree } from "../hooks/useWorktrees";
22
22
  import { useSyncBranch } from "../hooks/useBranches";
23
23
  import { ApiError } from "../lib/api";
24
24
 
25
- const BUILTIN_TOOL_SUMMARIES: Record<string, ToolSummary> = {
25
+ const BUILTIN_AGENT_SUMMARIES: Record<string, AgentSummary> = {
26
26
  "claude-code": {
27
27
  command: "claude",
28
28
  defaultArgs: [],
@@ -44,7 +44,7 @@ const BUILTIN_TOOL_SUMMARIES: Record<string, ToolSummary> = {
44
44
  },
45
45
  };
46
46
 
47
- interface ToolSummary {
47
+ interface AgentSummary {
48
48
  command: string;
49
49
  defaultArgs?: string[] | null;
50
50
  modeArgs?: {
@@ -55,19 +55,22 @@ interface ToolSummary {
55
55
  permissionSkipArgs?: string[] | null;
56
56
  }
57
57
 
58
- interface AIToolLaunchModalProps {
58
+ interface CodingAgentLaunchModalProps {
59
59
  branch: Branch;
60
60
  onClose: () => void;
61
61
  }
62
62
 
63
- type ToolMode = "normal" | "continue" | "resume";
63
+ type AgentMode = "normal" | "continue" | "resume";
64
64
 
65
- type SelectableTool =
65
+ type SelectableAgent =
66
66
  | { id: "claude-code"; label: string; target: "claude" }
67
67
  | { id: "codex-cli"; label: string; target: "codex" }
68
- | { id: string; label: string; target: "custom"; definition: CustomAITool };
68
+ | { id: string; label: string; target: "custom"; definition: ApiCodingAgent };
69
69
 
70
- export function AIToolLaunchModal({ branch, onClose }: AIToolLaunchModalProps) {
70
+ export function CodingAgentLaunchModal({
71
+ branch,
72
+ onClose,
73
+ }: CodingAgentLaunchModalProps) {
71
74
  const {
72
75
  data: config,
73
76
  isLoading: isConfigLoading,
@@ -78,8 +81,8 @@ export function AIToolLaunchModal({ branch, onClose }: AIToolLaunchModalProps) {
78
81
  const syncBranch = useSyncBranch(branch.name);
79
82
  const navigate = useNavigate();
80
83
 
81
- const [selectedToolId, setSelectedToolId] = useState<string>("claude-code");
82
- const [selectedMode, setSelectedMode] = useState<ToolMode>("normal");
84
+ const [selectedAgentId, setSelectedAgentId] = useState<string>("claude-code");
85
+ const [selectedMode, setSelectedMode] = useState<AgentMode>("normal");
83
86
  const [skipPermissions, setSkipPermissions] = useState(false);
84
87
  const [extraArgsText, setExtraArgsText] = useState("");
85
88
  const [banner, setBanner] = useState<{
@@ -89,74 +92,74 @@ export function AIToolLaunchModal({ branch, onClose }: AIToolLaunchModalProps) {
89
92
  const [isStartingSession, setIsStartingSession] = useState(false);
90
93
  const [isCreatingWorktree, setIsCreatingWorktree] = useState(false);
91
94
 
92
- const customTools = config?.tools ?? [];
93
- const availableTools: SelectableTool[] = useMemo(
95
+ const customAgents = config?.codingAgents ?? [];
96
+ const availableAgents: SelectableAgent[] = useMemo(
94
97
  () => [
95
98
  { id: "claude-code", label: "Claude Code", target: "claude" },
96
99
  { id: "codex-cli", label: "Codex CLI", target: "codex" },
97
- ...customTools.map((tool) => ({
98
- id: tool.id,
99
- label: tool.displayName,
100
+ ...customAgents.map((agent) => ({
101
+ id: agent.id,
102
+ label: agent.displayName,
100
103
  target: "custom" as const,
101
- definition: tool,
104
+ definition: agent,
102
105
  })),
103
106
  ],
104
- [customTools],
107
+ [customAgents],
105
108
  );
106
109
 
107
110
  useEffect(() => {
108
- if (!availableTools.length) {
109
- setSelectedToolId("claude-code");
111
+ if (!availableAgents.length) {
112
+ setSelectedAgentId("claude-code");
110
113
  return;
111
114
  }
112
- if (!availableTools.find((tool) => tool.id === selectedToolId)) {
113
- const first = availableTools[0];
115
+ if (!availableAgents.find((agent) => agent.id === selectedAgentId)) {
116
+ const first = availableAgents[0];
114
117
  if (first) {
115
- setSelectedToolId(first.id);
118
+ setSelectedAgentId(first.id);
116
119
  }
117
120
  }
118
- }, [availableTools, selectedToolId]);
121
+ }, [availableAgents, selectedAgentId]);
119
122
 
120
- const selectedTool = availableTools.find(
121
- (tool) => tool.id === selectedToolId,
123
+ const selectedAgent = availableAgents.find(
124
+ (agent) => agent.id === selectedAgentId,
122
125
  );
123
126
 
124
- const selectedToolSummary: ToolSummary | null = useMemo(() => {
125
- if (!selectedTool) {
127
+ const selectedAgentSummary: AgentSummary | null = useMemo(() => {
128
+ if (!selectedAgent) {
126
129
  return null;
127
130
  }
128
- if (selectedTool.target === "custom") {
131
+ if (selectedAgent.target === "custom") {
129
132
  return {
130
- command: selectedTool.definition.command,
131
- defaultArgs: selectedTool.definition.defaultArgs ?? null,
132
- modeArgs: selectedTool.definition.modeArgs,
133
- permissionSkipArgs: selectedTool.definition.permissionSkipArgs ?? null,
133
+ command: selectedAgent.definition.command,
134
+ defaultArgs: selectedAgent.definition.defaultArgs ?? null,
135
+ modeArgs: selectedAgent.definition.modeArgs,
136
+ permissionSkipArgs: selectedAgent.definition.permissionSkipArgs ?? null,
134
137
  };
135
138
  }
136
- return BUILTIN_TOOL_SUMMARIES[selectedTool.id] ?? null;
137
- }, [selectedTool]);
139
+ return BUILTIN_AGENT_SUMMARIES[selectedAgent.id] ?? null;
140
+ }, [selectedAgent]);
138
141
 
139
142
  const argsPreview = useMemo(() => {
140
- if (!selectedToolSummary) {
143
+ if (!selectedAgentSummary) {
141
144
  return null;
142
145
  }
143
146
  const args: string[] = [];
144
- if (selectedToolSummary.defaultArgs?.length) {
145
- args.push(...selectedToolSummary.defaultArgs);
147
+ if (selectedAgentSummary.defaultArgs?.length) {
148
+ args.push(...selectedAgentSummary.defaultArgs);
146
149
  }
147
- const mode = selectedToolSummary.modeArgs?.[selectedMode];
150
+ const mode = selectedAgentSummary.modeArgs?.[selectedMode];
148
151
  if (mode?.length) {
149
152
  args.push(...mode);
150
153
  }
151
- if (skipPermissions && selectedToolSummary.permissionSkipArgs?.length) {
152
- args.push(...selectedToolSummary.permissionSkipArgs);
154
+ if (skipPermissions && selectedAgentSummary.permissionSkipArgs?.length) {
155
+ args.push(...selectedAgentSummary.permissionSkipArgs);
153
156
  }
154
157
  const extraArgs = parseExtraArgs(extraArgsText);
155
158
  if (extraArgs.length) {
156
159
  args.push(...extraArgs);
157
160
  }
158
- return { command: selectedToolSummary.command, args };
159
- }, [selectedToolSummary, selectedMode, skipPermissions, extraArgsText]);
161
+ return { command: selectedAgentSummary.command, args };
162
+ }, [selectedAgentSummary, selectedMode, skipPermissions, extraArgsText]);
160
163
 
161
164
  const PROTECTED_BRANCHES = ["main", "master", "develop"];
162
165
  const isProtectedBranch = PROTECTED_BRANCHES.includes(
@@ -256,8 +259,8 @@ export function AIToolLaunchModal({ branch, onClose }: AIToolLaunchModalProps) {
256
259
  });
257
260
  return;
258
261
  }
259
- if (!selectedTool) {
260
- setBanner({ type: "error", message: "Select an AI tool to launch." });
262
+ if (!selectedAgent) {
263
+ setBanner({ type: "error", message: "Select a coding agent to launch." });
261
264
  return;
262
265
  }
263
266
  if (needsRemoteSync) {
@@ -285,23 +288,23 @@ export function AIToolLaunchModal({ branch, onClose }: AIToolLaunchModalProps) {
285
288
 
286
289
  setIsStartingSession(true);
287
290
  try {
288
- const toolType =
289
- selectedTool.target === "codex"
291
+ const agentType =
292
+ selectedAgent.target === "codex"
290
293
  ? "codex-cli"
291
- : selectedTool.target === "custom"
294
+ : selectedAgent.target === "custom"
292
295
  ? "custom"
293
296
  : "claude-code";
294
297
  const extraArgs = parseExtraArgs(extraArgsText);
295
298
  const sessionRequest = {
296
- toolType,
297
- toolName: selectedTool.target === "custom" ? selectedTool.id : null,
298
- ...(selectedTool.target === "custom"
299
- ? { customToolId: selectedTool.id }
299
+ agentType,
300
+ agentName: selectedAgent.target === "custom" ? selectedAgent.id : null,
301
+ ...(selectedAgent.target === "custom"
302
+ ? { customAgentId: selectedAgent.id }
300
303
  : {}),
301
304
  mode: selectedMode,
302
305
  worktreePath: branch.worktreePath,
303
306
  skipPermissions,
304
- ...(selectedTool.target === "codex"
307
+ ...(selectedAgent.target === "codex"
305
308
  ? { bypassApprovals: skipPermissions }
306
309
  : {}),
307
310
  ...(extraArgs.length ? { extraArgs } : {}),
@@ -333,7 +336,7 @@ export function AIToolLaunchModal({ branch, onClose }: AIToolLaunchModalProps) {
333
336
  <div className="flex items-start justify-between gap-4">
334
337
  <div>
335
338
  <p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
336
- Launch AI Tool
339
+ Launch Coding Agent
337
340
  </p>
338
341
  <h2 className="mt-1 text-lg font-semibold">{branch.name}</h2>
339
342
  </div>
@@ -379,7 +382,8 @@ export function AIToolLaunchModal({ branch, onClose }: AIToolLaunchModalProps) {
379
382
  ) : (
380
383
  <>
381
384
  <p>
382
- Worktree is missing. Create it before launching AI tools.
385
+ Worktree is missing. Create it before launching coding
386
+ agents.
383
387
  </p>
384
388
  <Button
385
389
  variant="secondary"
@@ -415,19 +419,19 @@ export function AIToolLaunchModal({ branch, onClose }: AIToolLaunchModalProps) {
415
419
 
416
420
  <div className="grid gap-4 sm:grid-cols-3">
417
421
  <div className="space-y-2">
418
- <label className="text-sm font-medium">AI tool</label>
422
+ <label className="text-sm font-medium">Coding agent</label>
419
423
  <Select
420
- value={selectedToolId}
421
- onValueChange={setSelectedToolId}
424
+ value={selectedAgentId}
425
+ onValueChange={setSelectedAgentId}
422
426
  disabled={isConfigLoading ?? false}
423
427
  >
424
428
  <SelectTrigger>
425
429
  <SelectValue />
426
430
  </SelectTrigger>
427
431
  <SelectContent>
428
- {availableTools.map((tool) => (
429
- <SelectItem key={tool.id} value={tool.id}>
430
- {tool.label}
432
+ {availableAgents.map((agent) => (
433
+ <SelectItem key={agent.id} value={agent.id}>
434
+ {agent.label}
431
435
  </SelectItem>
432
436
  ))}
433
437
  </SelectContent>
@@ -438,7 +442,7 @@ export function AIToolLaunchModal({ branch, onClose }: AIToolLaunchModalProps) {
438
442
  <label className="text-sm font-medium">Launch mode</label>
439
443
  <Select
440
444
  value={selectedMode}
441
- onValueChange={(value) => setSelectedMode(value as ToolMode)}
445
+ onValueChange={(value) => setSelectedMode(value as AgentMode)}
442
446
  >
443
447
  <SelectTrigger>
444
448
  <SelectValue />
@@ -477,12 +481,12 @@ export function AIToolLaunchModal({ branch, onClose }: AIToolLaunchModalProps) {
477
481
  onClick={handleStartSession}
478
482
  disabled={
479
483
  isStartingSession ||
480
- !selectedTool ||
484
+ !selectedAgent ||
481
485
  hasBlockingDivergence ||
482
486
  needsRemoteSync
483
487
  }
484
488
  >
485
- {isStartingSession ? "Launching..." : "Launch AI tool"}
489
+ {isStartingSession ? "Launching..." : "Launch coding agent"}
486
490
  </Button>
487
491
  <Button
488
492
  variant="secondary"
@@ -496,25 +500,25 @@ export function AIToolLaunchModal({ branch, onClose }: AIToolLaunchModalProps) {
496
500
  </Button>
497
501
  </div>
498
502
 
499
- {selectedToolSummary && (
503
+ {selectedAgentSummary && (
500
504
  <div className="space-y-2 rounded-lg border bg-muted/30 p-4 text-sm">
501
505
  <div className="grid gap-2 sm:grid-cols-3">
502
506
  <div>
503
507
  <span className="text-muted-foreground">Command:</span>{" "}
504
508
  <code className="rounded bg-muted px-1.5 py-0.5 font-mono">
505
- {selectedToolSummary.command}
509
+ {selectedAgentSummary.command}
506
510
  </code>
507
511
  </div>
508
512
  <div>
509
513
  <span className="text-muted-foreground">defaultArgs:</span>{" "}
510
514
  <span
511
515
  className={
512
- !selectedToolSummary.defaultArgs?.length
516
+ !selectedAgentSummary.defaultArgs?.length
513
517
  ? "text-muted-foreground/50"
514
518
  : ""
515
519
  }
516
520
  >
517
- {renderArgs(selectedToolSummary.defaultArgs)}
521
+ {renderArgs(selectedAgentSummary.defaultArgs)}
518
522
  </span>
519
523
  </div>
520
524
  <div>
@@ -523,12 +527,12 @@ export function AIToolLaunchModal({ branch, onClose }: AIToolLaunchModalProps) {
523
527
  </span>{" "}
524
528
  <span
525
529
  className={
526
- !selectedToolSummary.permissionSkipArgs?.length
530
+ !selectedAgentSummary.permissionSkipArgs?.length
527
531
  ? "text-muted-foreground/50"
528
532
  : ""
529
533
  }
530
534
  >
531
- {renderArgs(selectedToolSummary.permissionSkipArgs)}
535
+ {renderArgs(selectedAgentSummary.permissionSkipArgs)}
532
536
  </span>
533
537
  </div>
534
538
  </div>