@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
|
@@ -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
|
|
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
|
-
|
|
274
|
-
if (codexPath) {
|
|
281
|
+
if (codexLookup.source === "installed" && codexLookup.path) {
|
|
275
282
|
logger.info(
|
|
276
|
-
{ command:
|
|
283
|
+
{ command: codexLookup.path, usesFallback: false },
|
|
277
284
|
"Codex command resolved",
|
|
278
285
|
);
|
|
279
286
|
return {
|
|
280
|
-
command:
|
|
287
|
+
command: codexLookup.path,
|
|
281
288
|
args,
|
|
282
289
|
usesFallback: false,
|
|
283
290
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
3
|
import { execa } from "execa";
|
|
4
4
|
|
|
5
5
|
export type PackageManager = "bun" | "pnpm" | "npm";
|
|
@@ -72,7 +72,7 @@ const INSTALL_CANDIDATES: PackageManagerCandidate[] = [
|
|
|
72
72
|
|
|
73
73
|
async function fileExists(targetPath: string): Promise<boolean> {
|
|
74
74
|
try {
|
|
75
|
-
await
|
|
75
|
+
await access(targetPath);
|
|
76
76
|
return true;
|
|
77
77
|
} catch (error) {
|
|
78
78
|
const code = (error as NodeJS.ErrnoException)?.code;
|
|
@@ -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
|
+
}
|
|
@@ -444,3 +444,31 @@ export function matchesCwd(
|
|
|
444
444
|
isPathPrefix(normalizedSession, normalizedTarget)
|
|
445
445
|
);
|
|
446
446
|
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Resolves a branch name for a session cwd using worktree paths.
|
|
450
|
+
* Picks the worktree with the longest matching path prefix.
|
|
451
|
+
*/
|
|
452
|
+
export function resolveBranchFromCwd(
|
|
453
|
+
sessionCwd: string | null,
|
|
454
|
+
worktrees: { path: string; branch: string }[],
|
|
455
|
+
): string | null {
|
|
456
|
+
if (!sessionCwd) return null;
|
|
457
|
+
const normalizedSession = normalizePath(sessionCwd);
|
|
458
|
+
let bestMatch: { branch: string; path: string } | null = null;
|
|
459
|
+
|
|
460
|
+
for (const worktree of worktrees) {
|
|
461
|
+
if (!worktree?.path || !worktree.branch) continue;
|
|
462
|
+
const normalizedPath = normalizePath(worktree.path);
|
|
463
|
+
if (
|
|
464
|
+
normalizedSession === normalizedPath ||
|
|
465
|
+
isPathPrefix(normalizedPath, normalizedSession)
|
|
466
|
+
) {
|
|
467
|
+
if (!bestMatch || normalizedPath.length > bestMatch.path.length) {
|
|
468
|
+
bestMatch = { branch: worktree.branch, path: normalizedPath };
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return bestMatch?.branch ?? null;
|
|
474
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
readFileContent,
|
|
17
17
|
checkFileStat,
|
|
18
18
|
} from "../common.js";
|
|
19
|
+
import { listAllWorktrees } from "../../../worktree.js";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Encodes a project path for Claude's directory structure.
|
|
@@ -75,36 +76,72 @@ function getClaudeRootCandidates(): string[] {
|
|
|
75
76
|
* 3. ~/.claude/history.jsonl (global history fallback)
|
|
76
77
|
*
|
|
77
78
|
* @param cwd - The working directory to find sessions for
|
|
78
|
-
* @param options - Search options (since, until, preferClosestTo, windowMs)
|
|
79
|
+
* @param options - Search options (since, until, preferClosestTo, windowMs, branch/worktrees)
|
|
79
80
|
* @returns Session info with ID and modification time, or null if not found
|
|
80
81
|
*/
|
|
81
82
|
export async function findLatestClaudeSession(
|
|
82
83
|
cwd: string,
|
|
83
|
-
options:
|
|
84
|
+
options: SessionSearchOptions = {},
|
|
84
85
|
): Promise<ClaudeSessionInfo | null> {
|
|
85
86
|
const rootCandidates = getClaudeRootCandidates();
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const projectDir = path.join(claudeRoot, "projects", encoded);
|
|
91
|
-
const sessionsDir = path.join(projectDir, "sessions");
|
|
92
|
-
|
|
93
|
-
// 1) Look under sessions/ (official location)
|
|
94
|
-
const session = await findNewestSessionIdFromDir(
|
|
95
|
-
sessionsDir,
|
|
96
|
-
false,
|
|
97
|
-
options,
|
|
98
|
-
);
|
|
99
|
-
if (session) return session;
|
|
87
|
+
const branchFilter =
|
|
88
|
+
typeof options.branch === "string" && options.branch.trim().length > 0
|
|
89
|
+
? options.branch.trim()
|
|
90
|
+
: null;
|
|
100
91
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
92
|
+
let cwdCandidates: string[] = [];
|
|
93
|
+
if (branchFilter) {
|
|
94
|
+
let worktrees: { path: string; branch: string }[] = [];
|
|
95
|
+
if (Array.isArray(options.worktrees) && options.worktrees.length > 0) {
|
|
96
|
+
worktrees = options.worktrees
|
|
97
|
+
.filter((entry) => entry?.path && entry?.branch)
|
|
98
|
+
.map((entry) => ({ path: entry.path, branch: entry.branch }));
|
|
99
|
+
} else {
|
|
100
|
+
try {
|
|
101
|
+
const allWorktrees = await listAllWorktrees();
|
|
102
|
+
worktrees = allWorktrees
|
|
103
|
+
.filter((entry) => entry?.path && entry?.branch)
|
|
104
|
+
.map((entry) => ({ path: entry.path, branch: entry.branch }));
|
|
105
|
+
} catch {
|
|
106
|
+
worktrees = [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const matches = worktrees
|
|
110
|
+
.filter((entry) => entry.branch === branchFilter)
|
|
111
|
+
.map((entry) => entry.path);
|
|
112
|
+
if (!matches.length) return null;
|
|
113
|
+
cwdCandidates = matches;
|
|
114
|
+
} else {
|
|
115
|
+
const baseCwd = options.cwd ?? cwd;
|
|
116
|
+
if (!baseCwd) return null;
|
|
117
|
+
cwdCandidates = [baseCwd];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const uniqueCwds = Array.from(new Set(cwdCandidates));
|
|
121
|
+
|
|
122
|
+
for (const candidateCwd of uniqueCwds) {
|
|
123
|
+
const encodedPaths = generateClaudeProjectPathCandidates(candidateCwd);
|
|
124
|
+
for (const claudeRoot of rootCandidates) {
|
|
125
|
+
for (const encoded of encodedPaths) {
|
|
126
|
+
const projectDir = path.join(claudeRoot, "projects", encoded);
|
|
127
|
+
const sessionsDir = path.join(projectDir, "sessions");
|
|
128
|
+
|
|
129
|
+
// 1) Look under sessions/ (official location)
|
|
130
|
+
const session = await findNewestSessionIdFromDir(
|
|
131
|
+
sessionsDir,
|
|
132
|
+
false,
|
|
133
|
+
options,
|
|
134
|
+
);
|
|
135
|
+
if (session) return session;
|
|
136
|
+
|
|
137
|
+
// 2) Look directly under project dir and subdirs
|
|
138
|
+
const rootSession = await findNewestSessionIdFromDir(
|
|
139
|
+
projectDir,
|
|
140
|
+
true,
|
|
141
|
+
options,
|
|
142
|
+
);
|
|
143
|
+
if (rootSession) return rootSession;
|
|
144
|
+
}
|
|
108
145
|
}
|
|
109
146
|
}
|
|
110
147
|
|
|
@@ -124,7 +161,10 @@ export async function findLatestClaudeSession(
|
|
|
124
161
|
typeof parsed.project === "string" ? parsed.project : null;
|
|
125
162
|
const sessionId =
|
|
126
163
|
typeof parsed.sessionId === "string" ? parsed.sessionId : null;
|
|
127
|
-
|
|
164
|
+
const matchesProject = uniqueCwds.some((candidate) =>
|
|
165
|
+
matchesCwd(project, candidate),
|
|
166
|
+
);
|
|
167
|
+
if (project && sessionId && matchesProject) {
|
|
128
168
|
return { id: sessionId, mtime: historyStat.mtimeMs };
|
|
129
169
|
}
|
|
130
170
|
} catch {
|
|
@@ -141,14 +181,14 @@ export async function findLatestClaudeSession(
|
|
|
141
181
|
/**
|
|
142
182
|
* Finds the latest Claude session ID for a given working directory.
|
|
143
183
|
* @param cwd - The working directory to find sessions for
|
|
144
|
-
* @param options - Search options (since, until, preferClosestTo, windowMs)
|
|
184
|
+
* @param options - Search options (since, until, preferClosestTo, windowMs, branch/worktrees)
|
|
145
185
|
* @returns Session ID string or null if not found
|
|
146
186
|
*/
|
|
147
187
|
export async function findLatestClaudeSessionId(
|
|
148
188
|
cwd: string,
|
|
149
|
-
options:
|
|
189
|
+
options: SessionSearchOptions = {},
|
|
150
190
|
): Promise<string | null> {
|
|
151
|
-
const found = await findLatestClaudeSession(cwd, options);
|
|
191
|
+
const found = await findLatestClaudeSession(options.cwd ?? cwd, options);
|
|
152
192
|
return found?.id ?? null;
|
|
153
193
|
}
|
|
154
194
|
|
|
@@ -167,6 +207,9 @@ export async function waitForClaudeSessionId(
|
|
|
167
207
|
until?: number;
|
|
168
208
|
preferClosestTo?: number;
|
|
169
209
|
windowMs?: number;
|
|
210
|
+
branch?: string | null;
|
|
211
|
+
worktrees?: { path: string; branch: string }[] | null;
|
|
212
|
+
cwd?: string | null;
|
|
170
213
|
} = {},
|
|
171
214
|
): Promise<string | null> {
|
|
172
215
|
const timeoutMs = options.timeoutMs ?? 120_000;
|
|
@@ -174,15 +217,22 @@ export async function waitForClaudeSessionId(
|
|
|
174
217
|
const deadline = Date.now() + timeoutMs;
|
|
175
218
|
|
|
176
219
|
// Build search options once outside the loop
|
|
177
|
-
const searchOptions:
|
|
220
|
+
const searchOptions: SessionSearchOptions = {};
|
|
178
221
|
if (options.since !== undefined) searchOptions.since = options.since;
|
|
179
222
|
if (options.until !== undefined) searchOptions.until = options.until;
|
|
180
223
|
if (options.preferClosestTo !== undefined)
|
|
181
224
|
searchOptions.preferClosestTo = options.preferClosestTo;
|
|
182
225
|
if (options.windowMs !== undefined) searchOptions.windowMs = options.windowMs;
|
|
226
|
+
if (options.branch !== undefined) searchOptions.branch = options.branch;
|
|
227
|
+
if (options.worktrees !== undefined)
|
|
228
|
+
searchOptions.worktrees = options.worktrees;
|
|
229
|
+
if (options.cwd !== undefined) searchOptions.cwd = options.cwd;
|
|
183
230
|
|
|
184
231
|
while (Date.now() < deadline) {
|
|
185
|
-
const found = await findLatestClaudeSession(
|
|
232
|
+
const found = await findLatestClaudeSession(
|
|
233
|
+
options.cwd ?? cwd,
|
|
234
|
+
searchOptions,
|
|
235
|
+
);
|
|
186
236
|
if (found?.id) return found.id;
|
|
187
237
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
188
238
|
}
|
|
@@ -14,8 +14,10 @@ import {
|
|
|
14
14
|
UUID_REGEX,
|
|
15
15
|
collectFilesIterative,
|
|
16
16
|
matchesCwd,
|
|
17
|
+
resolveBranchFromCwd,
|
|
17
18
|
readSessionInfoFromFile,
|
|
18
19
|
} from "../common.js";
|
|
20
|
+
import { listAllWorktrees } from "../../../worktree.js";
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Finds the latest Codex session with optional time filtering and cwd matching.
|
|
@@ -63,43 +65,64 @@ export async function findLatestCodexSession(
|
|
|
63
65
|
return b.mtime - a.mtime;
|
|
64
66
|
});
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
const branchFilter =
|
|
69
|
+
typeof options.branch === "string" && options.branch.trim().length > 0
|
|
70
|
+
? options.branch.trim()
|
|
71
|
+
: null;
|
|
72
|
+
const shouldCheckBranch = Boolean(branchFilter);
|
|
73
|
+
const shouldCheckCwd = Boolean(options.cwd) && !shouldCheckBranch;
|
|
74
|
+
|
|
75
|
+
let worktrees: { path: string; branch: string }[] = [];
|
|
76
|
+
if (shouldCheckBranch) {
|
|
77
|
+
if (Array.isArray(options.worktrees) && options.worktrees.length > 0) {
|
|
78
|
+
worktrees = options.worktrees
|
|
79
|
+
.filter((entry) => entry?.path && entry?.branch)
|
|
80
|
+
.map((entry) => ({ path: entry.path, branch: entry.branch }));
|
|
81
|
+
} else {
|
|
82
|
+
try {
|
|
83
|
+
const allWorktrees = await listAllWorktrees();
|
|
84
|
+
worktrees = allWorktrees
|
|
85
|
+
.filter((entry) => entry?.path && entry?.branch)
|
|
86
|
+
.map((entry) => ({ path: entry.path, branch: entry.branch }));
|
|
87
|
+
} catch {
|
|
88
|
+
worktrees = [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!worktrees.length) return null;
|
|
92
|
+
}
|
|
67
93
|
|
|
68
94
|
for (const file of ordered) {
|
|
69
95
|
// Priority 1: Extract session ID from filename (most reliable for Codex)
|
|
70
96
|
const filenameMatch = path.basename(file.fullPath).match(UUID_REGEX);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
continue; // cwd doesn't match, try next file
|
|
97
|
+
const idFromName = filenameMatch?.[0] ?? null;
|
|
98
|
+
const needsInfo = shouldCheckBranch || shouldCheckCwd || !idFromName;
|
|
99
|
+
const info = needsInfo
|
|
100
|
+
? await readSessionInfoFromFile(file.fullPath)
|
|
101
|
+
: null;
|
|
102
|
+
const sessionCwd = info?.cwd ?? null;
|
|
103
|
+
|
|
104
|
+
if (shouldCheckBranch) {
|
|
105
|
+
const resolvedBranch = resolveBranchFromCwd(sessionCwd, worktrees);
|
|
106
|
+
if (resolvedBranch !== branchFilter) {
|
|
107
|
+
continue;
|
|
83
108
|
}
|
|
84
|
-
return { id: sessionId, mtime: file.mtime };
|
|
85
109
|
}
|
|
86
110
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (options.cwd) {
|
|
91
|
-
if (matchesCwd(info.cwd, options.cwd)) {
|
|
92
|
-
return { id: info.id, mtime: file.mtime };
|
|
111
|
+
if (shouldCheckCwd && options.cwd) {
|
|
112
|
+
if (!matchesCwd(sessionCwd, options.cwd)) {
|
|
113
|
+
continue;
|
|
93
114
|
}
|
|
94
|
-
if (!info.cwd && !fallbackMissingCwd) {
|
|
95
|
-
fallbackMissingCwd = { id: info.id, mtime: file.mtime };
|
|
96
|
-
}
|
|
97
|
-
continue;
|
|
98
115
|
}
|
|
99
|
-
|
|
116
|
+
|
|
117
|
+
const sessionId = idFromName ?? info?.id ?? null;
|
|
118
|
+
if (sessionId) {
|
|
119
|
+
return { id: sessionId, mtime: file.mtime };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// (already handled via info above)
|
|
100
123
|
}
|
|
101
124
|
|
|
102
|
-
return
|
|
125
|
+
return null;
|
|
103
126
|
}
|
|
104
127
|
|
|
105
128
|
/**
|
|
@@ -12,8 +12,10 @@ import type { GeminiSessionInfo, SessionSearchOptions } from "../types.js";
|
|
|
12
12
|
import {
|
|
13
13
|
collectFilesIterative,
|
|
14
14
|
matchesCwd,
|
|
15
|
+
resolveBranchFromCwd,
|
|
15
16
|
readSessionInfoFromFile,
|
|
16
17
|
} from "../common.js";
|
|
18
|
+
import { listAllWorktrees } from "../../../worktree.js";
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Finds the latest Gemini session with optional time filtering and cwd matching.
|
|
@@ -57,15 +59,50 @@ export async function findLatestGeminiSession(
|
|
|
57
59
|
return b.mtime - a.mtime;
|
|
58
60
|
});
|
|
59
61
|
|
|
62
|
+
const branchFilter =
|
|
63
|
+
typeof options.branch === "string" && options.branch.trim().length > 0
|
|
64
|
+
? options.branch.trim()
|
|
65
|
+
: null;
|
|
66
|
+
const shouldCheckBranch = Boolean(branchFilter);
|
|
67
|
+
const shouldCheckCwd = Boolean(options.cwd) && !shouldCheckBranch;
|
|
68
|
+
|
|
69
|
+
let worktrees: { path: string; branch: string }[] = [];
|
|
70
|
+
if (shouldCheckBranch) {
|
|
71
|
+
if (Array.isArray(options.worktrees) && options.worktrees.length > 0) {
|
|
72
|
+
worktrees = options.worktrees
|
|
73
|
+
.filter((entry) => entry?.path && entry?.branch)
|
|
74
|
+
.map((entry) => ({ path: entry.path, branch: entry.branch }));
|
|
75
|
+
} else {
|
|
76
|
+
try {
|
|
77
|
+
const allWorktrees = await listAllWorktrees();
|
|
78
|
+
worktrees = allWorktrees
|
|
79
|
+
.filter((entry) => entry?.path && entry?.branch)
|
|
80
|
+
.map((entry) => ({ path: entry.path, branch: entry.branch }));
|
|
81
|
+
} catch {
|
|
82
|
+
worktrees = [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!worktrees.length) return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
60
88
|
for (const file of pool) {
|
|
61
89
|
const info = await readSessionInfoFromFile(file.fullPath);
|
|
62
90
|
if (!info.id) continue;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
91
|
+
const sessionCwd = info.cwd ?? null;
|
|
92
|
+
|
|
93
|
+
if (shouldCheckBranch) {
|
|
94
|
+
const resolvedBranch = resolveBranchFromCwd(sessionCwd, worktrees);
|
|
95
|
+
if (resolvedBranch !== branchFilter) {
|
|
96
|
+
continue;
|
|
66
97
|
}
|
|
67
|
-
continue;
|
|
68
98
|
}
|
|
99
|
+
|
|
100
|
+
if (shouldCheckCwd && options.cwd) {
|
|
101
|
+
if (!matchesCwd(sessionCwd, options.cwd)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
69
106
|
return { id: info.id, mtime: file.mtime };
|
|
70
107
|
}
|
|
71
108
|
|
|
@@ -82,7 +119,11 @@ export async function findLatestGeminiSessionId(
|
|
|
82
119
|
cwd: string,
|
|
83
120
|
options: SessionSearchOptions = {},
|
|
84
121
|
): Promise<string | null> {
|
|
85
|
-
const searchOptions: SessionSearchOptions = {
|
|
122
|
+
const searchOptions: SessionSearchOptions = {
|
|
123
|
+
cwd: options.cwd ?? cwd,
|
|
124
|
+
branch: options.branch ?? null,
|
|
125
|
+
worktrees: options.worktrees ?? null,
|
|
126
|
+
};
|
|
86
127
|
if (options.since !== undefined) searchOptions.since = options.since;
|
|
87
128
|
if (options.until !== undefined) searchOptions.until = options.until;
|
|
88
129
|
if (options.preferClosestTo !== undefined)
|
|
@@ -13,8 +13,10 @@ import type { OpenCodeSessionInfo, SessionSearchOptions } from "../types.js";
|
|
|
13
13
|
import {
|
|
14
14
|
collectFilesIterative,
|
|
15
15
|
matchesCwd,
|
|
16
|
+
resolveBranchFromCwd,
|
|
16
17
|
readSessionInfoFromFile,
|
|
17
18
|
} from "../common.js";
|
|
19
|
+
import { listAllWorktrees } from "../../../worktree.js";
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Returns the OpenCode session directory
|
|
@@ -73,15 +75,50 @@ export async function findLatestOpenCodeSession(
|
|
|
73
75
|
return b.mtime - a.mtime;
|
|
74
76
|
});
|
|
75
77
|
|
|
78
|
+
const branchFilter =
|
|
79
|
+
typeof options.branch === "string" && options.branch.trim().length > 0
|
|
80
|
+
? options.branch.trim()
|
|
81
|
+
: null;
|
|
82
|
+
const shouldCheckBranch = Boolean(branchFilter);
|
|
83
|
+
const shouldCheckCwd = Boolean(options.cwd) && !shouldCheckBranch;
|
|
84
|
+
|
|
85
|
+
let worktrees: { path: string; branch: string }[] = [];
|
|
86
|
+
if (shouldCheckBranch) {
|
|
87
|
+
if (Array.isArray(options.worktrees) && options.worktrees.length > 0) {
|
|
88
|
+
worktrees = options.worktrees
|
|
89
|
+
.filter((entry) => entry?.path && entry?.branch)
|
|
90
|
+
.map((entry) => ({ path: entry.path, branch: entry.branch }));
|
|
91
|
+
} else {
|
|
92
|
+
try {
|
|
93
|
+
const allWorktrees = await listAllWorktrees();
|
|
94
|
+
worktrees = allWorktrees
|
|
95
|
+
.filter((entry) => entry?.path && entry?.branch)
|
|
96
|
+
.map((entry) => ({ path: entry.path, branch: entry.branch }));
|
|
97
|
+
} catch {
|
|
98
|
+
worktrees = [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!worktrees.length) return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
76
104
|
for (const file of pool) {
|
|
77
105
|
const info = await readSessionInfoFromFile(file.fullPath);
|
|
78
106
|
if (!info.id) continue;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
107
|
+
const sessionCwd = info.cwd ?? null;
|
|
108
|
+
|
|
109
|
+
if (shouldCheckBranch) {
|
|
110
|
+
const resolvedBranch = resolveBranchFromCwd(sessionCwd, worktrees);
|
|
111
|
+
if (resolvedBranch !== branchFilter) {
|
|
112
|
+
continue;
|
|
82
113
|
}
|
|
83
|
-
continue;
|
|
84
114
|
}
|
|
115
|
+
|
|
116
|
+
if (shouldCheckCwd && options.cwd) {
|
|
117
|
+
if (!matchesCwd(sessionCwd, options.cwd)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
85
122
|
return { id: info.id, mtime: file.mtime };
|
|
86
123
|
}
|
|
87
124
|
|
|
@@ -98,7 +135,11 @@ export async function findLatestOpenCodeSessionId(
|
|
|
98
135
|
cwd: string,
|
|
99
136
|
options: SessionSearchOptions = {},
|
|
100
137
|
): Promise<string | null> {
|
|
101
|
-
const searchOptions: SessionSearchOptions = {
|
|
138
|
+
const searchOptions: SessionSearchOptions = {
|
|
139
|
+
cwd: options.cwd ?? cwd,
|
|
140
|
+
branch: options.branch ?? null,
|
|
141
|
+
worktrees: options.worktrees ?? null,
|
|
142
|
+
};
|
|
102
143
|
if (options.since !== undefined) searchOptions.since = options.since;
|
|
103
144
|
if (options.until !== undefined) searchOptions.until = options.until;
|
|
104
145
|
if (options.preferClosestTo !== undefined)
|
|
@@ -16,6 +16,10 @@ export interface SessionSearchOptions {
|
|
|
16
16
|
windowMs?: number;
|
|
17
17
|
/** Working directory filter */
|
|
18
18
|
cwd?: string | null;
|
|
19
|
+
/** Branch name filter (resolved from session cwd via worktree paths) */
|
|
20
|
+
branch?: string | null;
|
|
21
|
+
/** Optional worktree list to avoid shelling out for branch mapping */
|
|
22
|
+
worktrees?: { path: string; branch: string }[] | null;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
/**
|