Update files
This commit is contained in:
@@ -171,7 +171,7 @@ export function AgentList({
|
|||||||
|
|
||||||
void queryClient.prefetchQuery({
|
void queryClient.prefetchQuery({
|
||||||
queryKey,
|
queryKey,
|
||||||
queryFn: async () => await client.getCheckoutStatus(agent.id, { cwd: agent.cwd }),
|
queryFn: async () => await client.getCheckoutStatus(agent.cwd),
|
||||||
staleTime: CHECKOUT_STATUS_STALE_TIME,
|
staleTime: CHECKOUT_STATUS_STALE_TIME,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -192,7 +192,6 @@ export function AgentList({
|
|||||||
|
|
||||||
const checkoutQuery = useCheckoutStatusCacheOnly({
|
const checkoutQuery = useCheckoutStatusCacheOnly({
|
||||||
serverId: agent.serverId,
|
serverId: agent.serverId,
|
||||||
agentId: agent.id,
|
|
||||||
cwd: agent.cwd,
|
cwd: agent.cwd,
|
||||||
});
|
});
|
||||||
const checkout = checkoutQuery.data ?? null;
|
const checkout = checkoutQuery.data ?? null;
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ function SidebarContent({
|
|||||||
isMobile,
|
isMobile,
|
||||||
}: SidebarContentProps) {
|
}: SidebarContentProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const { status } = useCheckoutStatusQuery({ serverId, agentId, cwd });
|
const { status } = useCheckoutStatusQuery({ serverId, cwd });
|
||||||
const isGit = status?.isGit ?? false;
|
const isGit = status?.isGit ?? false;
|
||||||
|
|
||||||
// If not a git repo, only show files tab
|
// If not a git repo, only show files tab
|
||||||
|
|||||||
@@ -432,7 +432,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
const [shipDefault, setShipDefault] = useState<"merge" | "pr">("merge");
|
const [shipDefault, setShipDefault] = useState<"merge" | "pr">("merge");
|
||||||
const { status, isLoading: isStatusLoading, isFetching: isStatusFetching, isError: isStatusError, error: statusError, refresh: refreshStatus } =
|
const { status, isLoading: isStatusLoading, isFetching: isStatusFetching, isError: isStatusError, error: statusError, refresh: refreshStatus } =
|
||||||
useCheckoutStatusQuery({ serverId, agentId, cwd });
|
useCheckoutStatusQuery({ serverId, cwd });
|
||||||
const gitStatus = status && status.isGit ? status : null;
|
const gitStatus = status && status.isGit ? status : null;
|
||||||
const isGit = Boolean(gitStatus);
|
const isGit = Boolean(gitStatus);
|
||||||
const notGit = status !== null && !status.isGit && !status.error;
|
const notGit = status !== null && !status.isGit && !status.error;
|
||||||
@@ -456,7 +456,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
refresh: refreshDiff,
|
refresh: refreshDiff,
|
||||||
} = useCheckoutDiffQuery({
|
} = useCheckoutDiffQuery({
|
||||||
serverId,
|
serverId,
|
||||||
agentId,
|
cwd,
|
||||||
mode: diffMode,
|
mode: diffMode,
|
||||||
baseRef,
|
baseRef,
|
||||||
enabled: isGit,
|
enabled: isGit,
|
||||||
@@ -467,7 +467,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
refresh: refreshPrStatus,
|
refresh: refreshPrStatus,
|
||||||
} = useCheckoutPrStatusQuery({
|
} = useCheckoutPrStatusQuery({
|
||||||
serverId,
|
serverId,
|
||||||
agentId,
|
cwd,
|
||||||
enabled: isGit,
|
enabled: isGit,
|
||||||
});
|
});
|
||||||
// Track user-initiated refresh to avoid iOS RefreshControl animation on background fetches
|
// Track user-initiated refresh to avoid iOS RefreshControl animation on background fetches
|
||||||
@@ -583,16 +583,12 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
});
|
});
|
||||||
}, [agentId, diffMetrics, isDiffFetching, isDiffLoading, serverId]);
|
}, [agentId, diffMetrics, isDiffFetching, isDiffLoading, serverId]);
|
||||||
|
|
||||||
const agentExists = useSessionStore((state) =>
|
|
||||||
state.sessions[serverId]?.agents?.has(agentId) ?? false
|
|
||||||
);
|
|
||||||
|
|
||||||
const commitMutation = useMutation({
|
const commitMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error("Daemon client unavailable");
|
throw new Error("Daemon client unavailable");
|
||||||
}
|
}
|
||||||
const payload = await client.checkoutCommit(agentId, { addAll: true });
|
const payload = await client.checkoutCommit(cwd, { addAll: true });
|
||||||
if (payload.error) {
|
if (payload.error) {
|
||||||
throw new Error(payload.error.message);
|
throw new Error(payload.error.message);
|
||||||
}
|
}
|
||||||
@@ -614,7 +610,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error("Daemon client unavailable");
|
throw new Error("Daemon client unavailable");
|
||||||
}
|
}
|
||||||
const payload = await client.checkoutPrCreate(agentId, {});
|
const payload = await client.checkoutPrCreate(cwd, {});
|
||||||
if (payload.error) {
|
if (payload.error) {
|
||||||
throw new Error(payload.error.message);
|
throw new Error(payload.error.message);
|
||||||
}
|
}
|
||||||
@@ -635,7 +631,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error("Daemon client unavailable");
|
throw new Error("Daemon client unavailable");
|
||||||
}
|
}
|
||||||
const payload = await client.checkoutMerge(agentId, {
|
const payload = await client.checkoutMerge(cwd, {
|
||||||
baseRef,
|
baseRef,
|
||||||
strategy: "merge",
|
strategy: "merge",
|
||||||
requireCleanTarget: true,
|
requireCleanTarget: true,
|
||||||
@@ -661,7 +657,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error("Daemon client unavailable");
|
throw new Error("Daemon client unavailable");
|
||||||
}
|
}
|
||||||
const payload = await client.checkoutMergeFromBase(agentId, {
|
const payload = await client.checkoutMergeFromBase(cwd, {
|
||||||
baseRef,
|
baseRef,
|
||||||
requireCleanTarget: true,
|
requireCleanTarget: true,
|
||||||
});
|
});
|
||||||
@@ -686,7 +682,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error("Daemon client unavailable");
|
throw new Error("Daemon client unavailable");
|
||||||
}
|
}
|
||||||
const payload = await client.checkoutPush(agentId);
|
const payload = await client.checkoutPush(cwd);
|
||||||
if (payload.error) {
|
if (payload.error) {
|
||||||
throw new Error(payload.error.message);
|
throw new Error(payload.error.message);
|
||||||
}
|
}
|
||||||
@@ -753,14 +749,6 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
|
|
||||||
const keyExtractor = useCallback((item: ParsedDiffFile) => item.path, []);
|
const keyExtractor = useCallback((item: ParsedDiffFile) => item.path, []);
|
||||||
|
|
||||||
if (!agentExists) {
|
|
||||||
return (
|
|
||||||
<View style={styles.errorContainer}>
|
|
||||||
<Text style={styles.errorText}>Agent not found</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasChanges = files.length > 0;
|
const hasChanges = files.length > 0;
|
||||||
const diffErrorMessage =
|
const diffErrorMessage =
|
||||||
diffPayloadError?.message ??
|
diffPayloadError?.message ??
|
||||||
@@ -770,6 +758,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
gitStatus?.currentBranch ?? (notGit ? "Not a git repository" : "Unknown");
|
gitStatus?.currentBranch ?? (notGit ? "Not a git repository" : "Unknown");
|
||||||
const actionsDisabled = !isGit || Boolean(status?.error) || isStatusLoading;
|
const actionsDisabled = !isGit || Boolean(status?.error) || isStatusLoading;
|
||||||
const aheadCount = gitStatus?.aheadBehind?.ahead ?? 0;
|
const aheadCount = gitStatus?.aheadBehind?.ahead ?? 0;
|
||||||
|
const aheadOfOrigin = gitStatus?.aheadOfOrigin ?? 0;
|
||||||
const baseRefLabel = useMemo(() => {
|
const baseRefLabel = useMemo(() => {
|
||||||
if (!baseRef) return "base";
|
if (!baseRef) return "base";
|
||||||
const trimmed = baseRef.replace(/^refs\/(heads|remotes)\//, "").trim();
|
const trimmed = baseRef.replace(/^refs\/(heads|remotes)\//, "").trim();
|
||||||
@@ -855,13 +844,15 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Rules (in priority order):
|
// Rules (in priority order):
|
||||||
// 1. Uncommitted changes → "Commit" is primary
|
// 1. Uncommitted changes → "Commit" is primary
|
||||||
// 2. Has PR → "View PR" is primary
|
// 2. Ahead of origin (unpushed commits) → "Push" is primary
|
||||||
// 3. Ahead of base → "Merge branch" or "Create PR" based on shipDefault preference
|
// 3. Has PR → "View PR" is primary
|
||||||
// 4. Nothing to do → no primary CTA
|
// 4. Ahead of base → "Merge branch" or "Create PR" based on shipDefault preference
|
||||||
|
// 5. Nothing to do → no primary CTA
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
type PrimaryCTA =
|
type PrimaryCTA =
|
||||||
| { type: "commit" }
|
| { type: "commit" }
|
||||||
|
| { type: "push" }
|
||||||
| { type: "view-pr"; url: string }
|
| { type: "view-pr"; url: string }
|
||||||
| { type: "ship"; action: "merge" | "create-pr" }
|
| { type: "ship"; action: "merge" | "create-pr" }
|
||||||
| null;
|
| null;
|
||||||
@@ -874,12 +865,17 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
return { type: "commit" };
|
return { type: "commit" };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule 2: Has PR → View PR
|
// Rule 2: Ahead of origin → Push
|
||||||
|
if (aheadOfOrigin > 0 && !pushDisabled) {
|
||||||
|
return { type: "push" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 3: Has PR → View PR
|
||||||
if (hasPullRequest && prStatus?.url) {
|
if (hasPullRequest && prStatus?.url) {
|
||||||
return { type: "view-pr", url: prStatus.url };
|
return { type: "view-pr", url: prStatus.url };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule 3: Ahead of base → Ship (merge or create PR based on preference)
|
// Rule 4: Ahead of base → Ship (merge or create PR based on preference)
|
||||||
if (aheadCount > 0) {
|
if (aheadCount > 0) {
|
||||||
const preferredAction = shipDefault === "merge" ? "merge" : "create-pr";
|
const preferredAction = shipDefault === "merge" ? "merge" : "create-pr";
|
||||||
// If preferred action is disabled, fall back to the other
|
// If preferred action is disabled, fall back to the other
|
||||||
@@ -892,15 +888,17 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
return { type: "ship", action: preferredAction };
|
return { type: "ship", action: preferredAction };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule 4: Nothing to do
|
// Rule 5: Nothing to do
|
||||||
return null;
|
return null;
|
||||||
}, [isGit, hasUncommittedChanges, hasPullRequest, prStatus?.url, aheadCount, shipDefault, mergeDisabled, prDisabled]);
|
}, [isGit, hasUncommittedChanges, aheadOfOrigin, pushDisabled, hasPullRequest, prStatus?.url, aheadCount, shipDefault, mergeDisabled, prDisabled]);
|
||||||
|
|
||||||
const primaryCTALabel = useMemo(() => {
|
const primaryCTALabel = useMemo(() => {
|
||||||
if (!primaryCTA) return "";
|
if (!primaryCTA) return "";
|
||||||
switch (primaryCTA.type) {
|
switch (primaryCTA.type) {
|
||||||
case "commit":
|
case "commit":
|
||||||
return "Commit";
|
return "Commit";
|
||||||
|
case "push":
|
||||||
|
return "Push";
|
||||||
case "view-pr":
|
case "view-pr":
|
||||||
return "View PR";
|
return "View PR";
|
||||||
case "ship":
|
case "ship":
|
||||||
@@ -913,24 +911,28 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
switch (primaryCTA.type) {
|
switch (primaryCTA.type) {
|
||||||
case "commit":
|
case "commit":
|
||||||
return commitDisabled;
|
return commitDisabled;
|
||||||
|
case "push":
|
||||||
|
return pushDisabled;
|
||||||
case "view-pr":
|
case "view-pr":
|
||||||
return false; // View PR is never disabled
|
return false; // View PR is never disabled
|
||||||
case "ship":
|
case "ship":
|
||||||
return primaryCTA.action === "merge" ? mergeDisabled : prDisabled;
|
return primaryCTA.action === "merge" ? mergeDisabled : prDisabled;
|
||||||
}
|
}
|
||||||
}, [primaryCTA, actionsDisabled, commitDisabled, mergeDisabled, prDisabled]);
|
}, [primaryCTA, actionsDisabled, commitDisabled, pushDisabled, mergeDisabled, prDisabled]);
|
||||||
|
|
||||||
const primaryCTAStatus: ActionStatus = useMemo(() => {
|
const primaryCTAStatus: ActionStatus = useMemo(() => {
|
||||||
if (!primaryCTA) return "idle";
|
if (!primaryCTA) return "idle";
|
||||||
switch (primaryCTA.type) {
|
switch (primaryCTA.type) {
|
||||||
case "commit":
|
case "commit":
|
||||||
return commitAction.status;
|
return commitAction.status;
|
||||||
|
case "push":
|
||||||
|
return pushAction.status;
|
||||||
case "view-pr":
|
case "view-pr":
|
||||||
return "idle"; // View PR is instant, no status
|
return "idle"; // View PR is instant, no status
|
||||||
case "ship":
|
case "ship":
|
||||||
return primaryCTA.action === "merge" ? mergeAction.status : prCreateAction.status;
|
return primaryCTA.action === "merge" ? mergeAction.status : prCreateAction.status;
|
||||||
}
|
}
|
||||||
}, [primaryCTA, commitAction.status, mergeAction.status, prCreateAction.status]);
|
}, [primaryCTA, commitAction.status, pushAction.status, mergeAction.status, prCreateAction.status]);
|
||||||
|
|
||||||
const primaryCTADisplayLabel = useMemo(() => {
|
const primaryCTADisplayLabel = useMemo(() => {
|
||||||
if (!primaryCTA) return "";
|
if (!primaryCTA) return "";
|
||||||
@@ -941,6 +943,10 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
if (status === "pending") return "Committing...";
|
if (status === "pending") return "Committing...";
|
||||||
if (status === "success") return "Committed";
|
if (status === "success") return "Committed";
|
||||||
return "Commit";
|
return "Commit";
|
||||||
|
case "push":
|
||||||
|
if (status === "pending") return "Pushing...";
|
||||||
|
if (status === "success") return "Pushed";
|
||||||
|
return "Push";
|
||||||
case "view-pr":
|
case "view-pr":
|
||||||
return "View PR";
|
return "View PR";
|
||||||
case "ship":
|
case "ship":
|
||||||
@@ -962,6 +968,9 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
case "commit":
|
case "commit":
|
||||||
commitAction.trigger();
|
commitAction.trigger();
|
||||||
break;
|
break;
|
||||||
|
case "push":
|
||||||
|
pushAction.trigger();
|
||||||
|
break;
|
||||||
case "view-pr":
|
case "view-pr":
|
||||||
openURLInNewTab(primaryCTA.url);
|
openURLInNewTab(primaryCTA.url);
|
||||||
break;
|
break;
|
||||||
@@ -973,7 +982,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, [primaryCTA, primaryCTADisabled, primaryCTAStatus, commitAction, mergeAction, prCreateAction]);
|
}, [primaryCTA, primaryCTADisabled, primaryCTAStatus, commitAction, pushAction, mergeAction, prCreateAction]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ function SectionHeader({
|
|||||||
// For project sections, try to get repo name from checkout status
|
// For project sections, try to get repo name from checkout status
|
||||||
const checkoutQuery = useCheckoutStatusCacheOnly({
|
const checkoutQuery = useCheckoutStatusCacheOnly({
|
||||||
serverId: section.firstAgentServerId ?? "",
|
serverId: section.firstAgentServerId ?? "",
|
||||||
agentId: section.firstAgentId ?? "",
|
|
||||||
cwd: section.workingDir ?? "",
|
cwd: section.workingDir ?? "",
|
||||||
});
|
});
|
||||||
const checkout = checkoutQuery.data ?? null;
|
const checkout = checkoutQuery.data ?? null;
|
||||||
@@ -299,7 +298,7 @@ export function GroupedAgentList({
|
|||||||
|
|
||||||
void queryClient.prefetchQuery({
|
void queryClient.prefetchQuery({
|
||||||
queryKey,
|
queryKey,
|
||||||
queryFn: async () => await client.getCheckoutStatus(agent.id, { cwd: agent.cwd }),
|
queryFn: async () => await client.getCheckoutStatus(agent.cwd),
|
||||||
staleTime: CHECKOUT_STATUS_STALE_TIME,
|
staleTime: CHECKOUT_STATUS_STALE_TIME,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -409,7 +408,6 @@ export function GroupedAgentList({
|
|||||||
|
|
||||||
const checkoutQuery = useCheckoutStatusCacheOnly({
|
const checkoutQuery = useCheckoutStatusCacheOnly({
|
||||||
serverId: agent.serverId,
|
serverId: agent.serverId,
|
||||||
agentId: agent.id,
|
|
||||||
cwd: agent.cwd,
|
cwd: agent.cwd,
|
||||||
});
|
});
|
||||||
const checkout = checkoutQuery.data ?? null;
|
const checkout = checkoutQuery.data ?? null;
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
export type CheckoutStatusRevalidationParams = {
|
export type CheckoutStatusRevalidationParams = {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
agentId: string;
|
cwd: string;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
explorerTab: string;
|
explorerTab: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function checkoutStatusRevalidationKey(params: CheckoutStatusRevalidationParams): string | null {
|
export function checkoutStatusRevalidationKey(params: CheckoutStatusRevalidationParams): string | null {
|
||||||
if (!params.agentId) return null;
|
if (!params.cwd) return null;
|
||||||
if (!params.isOpen) return null;
|
if (!params.isOpen) return null;
|
||||||
if (params.explorerTab !== "changes") return null;
|
if (params.explorerTab !== "changes") return null;
|
||||||
return `${params.serverId}:${params.agentId}`;
|
return `${params.serverId}:${params.cwd}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function nextCheckoutStatusRefetchDecision(
|
export function nextCheckoutStatusRefetchDecision(
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ const CHECKOUT_DIFF_STALE_TIME = 30_000;
|
|||||||
|
|
||||||
function checkoutDiffQueryKey(
|
function checkoutDiffQueryKey(
|
||||||
serverId: string,
|
serverId: string,
|
||||||
agentId: string,
|
cwd: string,
|
||||||
mode: "uncommitted" | "base",
|
mode: "uncommitted" | "base",
|
||||||
baseRef?: string
|
baseRef?: string
|
||||||
) {
|
) {
|
||||||
return ["checkoutDiff", serverId, agentId, mode, baseRef ?? ""] as const;
|
return ["checkoutDiff", serverId, cwd, mode, baseRef ?? ""] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseCheckoutDiffQueryOptions {
|
interface UseCheckoutDiffQueryOptions {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
agentId: string;
|
cwd: string;
|
||||||
mode: "uncommitted" | "base";
|
mode: "uncommitted" | "base";
|
||||||
baseRef?: string;
|
baseRef?: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
@@ -31,7 +31,7 @@ export type HighlightToken = NonNullable<DiffLine["tokens"]>[number];
|
|||||||
|
|
||||||
export function useCheckoutDiffQuery({
|
export function useCheckoutDiffQuery({
|
||||||
serverId,
|
serverId,
|
||||||
agentId,
|
cwd,
|
||||||
mode,
|
mode,
|
||||||
baseRef,
|
baseRef,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
@@ -51,27 +51,27 @@ export function useCheckoutDiffQuery({
|
|||||||
const isOpen = isMobile ? mobileView === "file-explorer" : desktopFileExplorerOpen;
|
const isOpen = isMobile ? mobileView === "file-explorer" : desktopFileExplorerOpen;
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: checkoutDiffQueryKey(serverId, agentId, mode, baseRef),
|
queryKey: checkoutDiffQueryKey(serverId, cwd, mode, baseRef),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error("Daemon client not available");
|
throw new Error("Daemon client not available");
|
||||||
}
|
}
|
||||||
return await client.getCheckoutDiff(agentId, { mode, baseRef });
|
return await client.getCheckoutDiff(cwd, { mode, baseRef });
|
||||||
},
|
},
|
||||||
enabled: !!client && isConnected && !!agentId && enabled,
|
enabled: !!client && isConnected && !!cwd && enabled,
|
||||||
staleTime: CHECKOUT_DIFF_STALE_TIME,
|
staleTime: CHECKOUT_DIFF_STALE_TIME,
|
||||||
refetchInterval: 10_000,
|
refetchInterval: 10_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Revalidate when sidebar opens with "changes" tab active
|
// Revalidate when sidebar opens with "changes" tab active
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen || explorerTab !== "changes" || !agentId) {
|
if (!isOpen || explorerTab !== "changes" || !cwd) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: checkoutDiffQueryKey(serverId, agentId, mode, baseRef),
|
queryKey: checkoutDiffQueryKey(serverId, cwd, mode, baseRef),
|
||||||
});
|
});
|
||||||
}, [isOpen, explorerTab, serverId, agentId, mode, baseRef, queryClient]);
|
}, [isOpen, explorerTab, serverId, cwd, mode, baseRef, queryClient]);
|
||||||
|
|
||||||
const refresh = useCallback(() => {
|
const refresh = useCallback(() => {
|
||||||
return query.refetch();
|
return query.refetch();
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import type { CheckoutPrStatusResponse } from "@server/shared/messages";
|
|||||||
|
|
||||||
const CHECKOUT_PR_STATUS_STALE_TIME = 20_000;
|
const CHECKOUT_PR_STATUS_STALE_TIME = 20_000;
|
||||||
|
|
||||||
function checkoutPrStatusQueryKey(serverId: string, agentId: string) {
|
function checkoutPrStatusQueryKey(serverId: string, cwd: string) {
|
||||||
return ["checkoutPrStatus", serverId, agentId] as const;
|
return ["checkoutPrStatus", serverId, cwd] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseCheckoutPrStatusQueryOptions {
|
interface UseCheckoutPrStatusQueryOptions {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
agentId: string;
|
cwd: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ export type CheckoutPrStatusPayload = CheckoutPrStatusResponse["payload"];
|
|||||||
|
|
||||||
export function useCheckoutPrStatusQuery({
|
export function useCheckoutPrStatusQuery({
|
||||||
serverId,
|
serverId,
|
||||||
agentId,
|
cwd,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
}: UseCheckoutPrStatusQueryOptions) {
|
}: UseCheckoutPrStatusQueryOptions) {
|
||||||
const client = useSessionStore(
|
const client = useSessionStore(
|
||||||
@@ -29,14 +29,14 @@ export function useCheckoutPrStatusQuery({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: checkoutPrStatusQueryKey(serverId, agentId),
|
queryKey: checkoutPrStatusQueryKey(serverId, cwd),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error("Daemon client not available");
|
throw new Error("Daemon client not available");
|
||||||
}
|
}
|
||||||
return await client.checkoutPrStatus(agentId);
|
return await client.checkoutPrStatus(cwd);
|
||||||
},
|
},
|
||||||
enabled: !!client && isConnected && !!agentId && enabled,
|
enabled: !!client && isConnected && !!cwd && enabled,
|
||||||
staleTime: CHECKOUT_PR_STATUS_STALE_TIME,
|
staleTime: CHECKOUT_PR_STATUS_STALE_TIME,
|
||||||
refetchInterval: 15_000,
|
refetchInterval: 15_000,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ describe("useCheckoutStatusQuery", () => {
|
|||||||
expect(
|
expect(
|
||||||
checkoutStatusRevalidationKey({
|
checkoutStatusRevalidationKey({
|
||||||
serverId: "daemon-1",
|
serverId: "daemon-1",
|
||||||
agentId: "agent-1",
|
cwd: "/path/to/project",
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
explorerTab: "changes",
|
explorerTab: "changes",
|
||||||
})
|
})
|
||||||
@@ -18,7 +18,7 @@ describe("useCheckoutStatusQuery", () => {
|
|||||||
expect(
|
expect(
|
||||||
checkoutStatusRevalidationKey({
|
checkoutStatusRevalidationKey({
|
||||||
serverId: "daemon-1",
|
serverId: "daemon-1",
|
||||||
agentId: "agent-1",
|
cwd: "/path/to/project",
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
explorerTab: "files",
|
explorerTab: "files",
|
||||||
})
|
})
|
||||||
@@ -29,11 +29,11 @@ describe("useCheckoutStatusQuery", () => {
|
|||||||
expect(
|
expect(
|
||||||
checkoutStatusRevalidationKey({
|
checkoutStatusRevalidationKey({
|
||||||
serverId: "daemon-1",
|
serverId: "daemon-1",
|
||||||
agentId: "agent-1",
|
cwd: "/path/to/project",
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
explorerTab: "changes",
|
explorerTab: "changes",
|
||||||
})
|
})
|
||||||
).toBe("daemon-1:agent-1");
|
).toBe("daemon-1:/path/to/project");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,21 +17,19 @@ export function checkoutStatusQueryKey(serverId: string, cwd: string) {
|
|||||||
|
|
||||||
interface UseCheckoutStatusQueryOptions {
|
interface UseCheckoutStatusQueryOptions {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
agentId: string;
|
|
||||||
cwd: string;
|
cwd: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CheckoutStatusPayload = CheckoutStatusResponse["payload"];
|
export type CheckoutStatusPayload = CheckoutStatusResponse["payload"];
|
||||||
|
|
||||||
function fetchCheckoutStatus(
|
function fetchCheckoutStatus(
|
||||||
client: { getCheckoutStatus: (agentId: string, options?: { cwd?: string }) => Promise<CheckoutStatusPayload> },
|
client: { getCheckoutStatus: (cwd: string) => Promise<CheckoutStatusPayload> },
|
||||||
agentId: string,
|
|
||||||
cwd: string
|
cwd: string
|
||||||
): Promise<CheckoutStatusPayload> {
|
): Promise<CheckoutStatusPayload> {
|
||||||
return client.getCheckoutStatus(agentId, { cwd });
|
return client.getCheckoutStatus(cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCheckoutStatusQuery({ serverId, agentId, cwd }: UseCheckoutStatusQueryOptions) {
|
export function useCheckoutStatusQuery({ serverId, cwd }: UseCheckoutStatusQueryOptions) {
|
||||||
const client = useSessionStore(
|
const client = useSessionStore(
|
||||||
(state) => state.sessions[serverId]?.client ?? null
|
(state) => state.sessions[serverId]?.client ?? null
|
||||||
);
|
);
|
||||||
@@ -51,9 +49,9 @@ export function useCheckoutStatusQuery({ serverId, agentId, cwd }: UseCheckoutSt
|
|||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error("Daemon client not available");
|
throw new Error("Daemon client not available");
|
||||||
}
|
}
|
||||||
return await fetchCheckoutStatus(client, agentId, cwd);
|
return await fetchCheckoutStatus(client, cwd);
|
||||||
},
|
},
|
||||||
enabled: !!client && isConnected && !!agentId && !!cwd,
|
enabled: !!client && isConnected && !!cwd,
|
||||||
staleTime: CHECKOUT_STATUS_STALE_TIME,
|
staleTime: CHECKOUT_STATUS_STALE_TIME,
|
||||||
refetchInterval: 10_000,
|
refetchInterval: 10_000,
|
||||||
refetchIntervalInBackground: true,
|
refetchIntervalInBackground: true,
|
||||||
@@ -62,8 +60,8 @@ export function useCheckoutStatusQuery({ serverId, agentId, cwd }: UseCheckoutSt
|
|||||||
|
|
||||||
// Revalidate when sidebar is open with "changes" tab active.
|
// Revalidate when sidebar is open with "changes" tab active.
|
||||||
const revalidationKey = useMemo(
|
const revalidationKey = useMemo(
|
||||||
() => checkoutStatusRevalidationKey({ serverId, agentId, isOpen, explorerTab }),
|
() => checkoutStatusRevalidationKey({ serverId, cwd, isOpen, explorerTab }),
|
||||||
[serverId, agentId, isOpen, explorerTab]
|
[serverId, cwd, isOpen, explorerTab]
|
||||||
);
|
);
|
||||||
const lastRevalidationKey = useRef<string | null>(null);
|
const lastRevalidationKey = useRef<string | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -88,7 +86,7 @@ export function useCheckoutStatusQuery({ serverId, agentId, cwd }: UseCheckoutSt
|
|||||||
* initiating a fetch. Useful for list rows where a parent component prefetches
|
* initiating a fetch. Useful for list rows where a parent component prefetches
|
||||||
* only the visible agents.
|
* only the visible agents.
|
||||||
*/
|
*/
|
||||||
export function useCheckoutStatusCacheOnly({ serverId, agentId, cwd }: UseCheckoutStatusQueryOptions) {
|
export function useCheckoutStatusCacheOnly({ serverId, cwd }: UseCheckoutStatusQueryOptions) {
|
||||||
const client = useSessionStore(
|
const client = useSessionStore(
|
||||||
(state) => state.sessions[serverId]?.client ?? null
|
(state) => state.sessions[serverId]?.client ?? null
|
||||||
);
|
);
|
||||||
@@ -99,7 +97,7 @@ export function useCheckoutStatusCacheOnly({ serverId, agentId, cwd }: UseChecko
|
|||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error("Daemon client not available");
|
throw new Error("Daemon client not available");
|
||||||
}
|
}
|
||||||
return await fetchCheckoutStatus(client, agentId, cwd);
|
return await fetchCheckoutStatus(client, cwd);
|
||||||
},
|
},
|
||||||
enabled: false,
|
enabled: false,
|
||||||
staleTime: CHECKOUT_STATUS_STALE_TIME,
|
staleTime: CHECKOUT_STATUS_STALE_TIME,
|
||||||
|
|||||||
@@ -414,7 +414,6 @@ function AgentScreenContent({
|
|||||||
// Checkout status for header subtitle
|
// Checkout status for header subtitle
|
||||||
const checkoutStatusQuery = useCheckoutStatusQuery({
|
const checkoutStatusQuery = useCheckoutStatusQuery({
|
||||||
serverId,
|
serverId,
|
||||||
agentId: resolvedAgentId ?? "",
|
|
||||||
cwd: agent?.cwd ?? "",
|
cwd: agent?.cwd ?? "",
|
||||||
});
|
});
|
||||||
const checkout = checkoutStatusQuery.status;
|
const checkout = checkoutStatusQuery.status;
|
||||||
|
|||||||
@@ -75,14 +75,14 @@ describe("DaemonClientV2", () => {
|
|||||||
mock.triggerOpen();
|
mock.triggerOpen();
|
||||||
await connectPromise;
|
await connectPromise;
|
||||||
|
|
||||||
const p1 = client.getCheckoutStatus("agent-1");
|
const p1 = client.getCheckoutStatus("/tmp/project");
|
||||||
const p2 = client.getCheckoutStatus("agent-1");
|
const p2 = client.getCheckoutStatus("/tmp/project");
|
||||||
|
|
||||||
expect(mock.sent).toHaveLength(1);
|
expect(mock.sent).toHaveLength(1);
|
||||||
|
|
||||||
const request = JSON.parse(mock.sent[0]) as {
|
const request = JSON.parse(mock.sent[0]) as {
|
||||||
type: "session";
|
type: "session";
|
||||||
message: { type: "checkout_status_request"; agentId: string; requestId: string };
|
message: { type: "checkout_status_request"; cwd: string; requestId: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
@@ -90,8 +90,7 @@ describe("DaemonClientV2", () => {
|
|||||||
message: {
|
message: {
|
||||||
type: "checkout_status_response",
|
type: "checkout_status_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId: "agent-1",
|
cwd: "/tmp/project",
|
||||||
cwd: "/tmp",
|
|
||||||
error: null,
|
error: null,
|
||||||
requestId: request.message.requestId,
|
requestId: request.message.requestId,
|
||||||
isGit: false,
|
isGit: false,
|
||||||
@@ -109,16 +108,16 @@ describe("DaemonClientV2", () => {
|
|||||||
|
|
||||||
mock.triggerMessage(JSON.stringify(response));
|
mock.triggerMessage(JSON.stringify(response));
|
||||||
const [r1, r2] = await Promise.all([p1, p2]);
|
const [r1, r2] = await Promise.all([p1, p2]);
|
||||||
expect(r1).toMatchObject({ agentId: "agent-1", requestId: request.message.requestId, isGit: false });
|
expect(r1).toMatchObject({ cwd: "/tmp/project", requestId: request.message.requestId, isGit: false });
|
||||||
expect(r2).toMatchObject({ agentId: "agent-1", requestId: request.message.requestId, isGit: false });
|
expect(r2).toMatchObject({ cwd: "/tmp/project", requestId: request.message.requestId, isGit: false });
|
||||||
|
|
||||||
// After completion, a new call should issue a new request.
|
// After completion, a new call should issue a new request.
|
||||||
const p3 = client.getCheckoutStatus("agent-1");
|
const p3 = client.getCheckoutStatus("/tmp/project");
|
||||||
expect(mock.sent).toHaveLength(2);
|
expect(mock.sent).toHaveLength(2);
|
||||||
|
|
||||||
const request2 = JSON.parse(mock.sent[1]) as {
|
const request2 = JSON.parse(mock.sent[1]) as {
|
||||||
type: "session";
|
type: "session";
|
||||||
message: { type: "checkout_status_request"; agentId: string; requestId: string };
|
message: { type: "checkout_status_request"; cwd: string; requestId: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
mock.triggerMessage(
|
mock.triggerMessage(
|
||||||
|
|||||||
@@ -1130,14 +1130,13 @@ export class DaemonClientV2 {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
async getCheckoutStatus(
|
async getCheckoutStatus(
|
||||||
agentId: string,
|
cwd: string,
|
||||||
options?: { cwd?: string; requestId?: string }
|
options?: { requestId?: string }
|
||||||
): Promise<CheckoutStatusPayload> {
|
): Promise<CheckoutStatusPayload> {
|
||||||
const requestId = options?.requestId;
|
const requestId = options?.requestId;
|
||||||
const cwd = options?.cwd;
|
|
||||||
|
|
||||||
if (!requestId) {
|
if (!requestId) {
|
||||||
const existing = this.checkoutStatusInFlight.get(agentId);
|
const existing = this.checkoutStatusInFlight.get(cwd);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
@@ -1146,7 +1145,6 @@ export class DaemonClientV2 {
|
|||||||
const resolvedRequestId = this.createRequestId(requestId);
|
const resolvedRequestId = this.createRequestId(requestId);
|
||||||
const message = SessionInboundMessageSchema.parse({
|
const message = SessionInboundMessageSchema.parse({
|
||||||
type: "checkout_status_request",
|
type: "checkout_status_request",
|
||||||
agentId,
|
|
||||||
cwd,
|
cwd,
|
||||||
requestId: resolvedRequestId,
|
requestId: resolvedRequestId,
|
||||||
});
|
});
|
||||||
@@ -1170,10 +1168,10 @@ export class DaemonClientV2 {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
if (!requestId) {
|
if (!requestId) {
|
||||||
this.checkoutStatusInFlight.set(agentId, responsePromise);
|
this.checkoutStatusInFlight.set(cwd, responsePromise);
|
||||||
responsePromise.finally(() => {
|
responsePromise.finally(() => {
|
||||||
if (this.checkoutStatusInFlight.get(agentId) === responsePromise) {
|
if (this.checkoutStatusInFlight.get(cwd) === responsePromise) {
|
||||||
this.checkoutStatusInFlight.delete(agentId);
|
this.checkoutStatusInFlight.delete(cwd);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1182,14 +1180,14 @@ export class DaemonClientV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCheckoutDiff(
|
async getCheckoutDiff(
|
||||||
agentId: string,
|
cwd: string,
|
||||||
compare: { mode: "uncommitted" | "base"; baseRef?: string },
|
compare: { mode: "uncommitted" | "base"; baseRef?: string },
|
||||||
requestId?: string
|
requestId?: string
|
||||||
): Promise<CheckoutDiffPayload> {
|
): Promise<CheckoutDiffPayload> {
|
||||||
const resolvedRequestId = this.createRequestId(requestId);
|
const resolvedRequestId = this.createRequestId(requestId);
|
||||||
const message = SessionInboundMessageSchema.parse({
|
const message = SessionInboundMessageSchema.parse({
|
||||||
type: "checkout_diff_request",
|
type: "checkout_diff_request",
|
||||||
agentId,
|
cwd,
|
||||||
compare,
|
compare,
|
||||||
requestId: resolvedRequestId,
|
requestId: resolvedRequestId,
|
||||||
});
|
});
|
||||||
@@ -1211,14 +1209,14 @@ export class DaemonClientV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkoutCommit(
|
async checkoutCommit(
|
||||||
agentId: string,
|
cwd: string,
|
||||||
input: { message?: string; addAll?: boolean },
|
input: { message?: string; addAll?: boolean },
|
||||||
requestId?: string
|
requestId?: string
|
||||||
): Promise<CheckoutCommitPayload> {
|
): Promise<CheckoutCommitPayload> {
|
||||||
const resolvedRequestId = this.createRequestId(requestId);
|
const resolvedRequestId = this.createRequestId(requestId);
|
||||||
const message = SessionInboundMessageSchema.parse({
|
const message = SessionInboundMessageSchema.parse({
|
||||||
type: "checkout_commit_request",
|
type: "checkout_commit_request",
|
||||||
agentId,
|
cwd,
|
||||||
message: input.message,
|
message: input.message,
|
||||||
addAll: input.addAll,
|
addAll: input.addAll,
|
||||||
requestId: resolvedRequestId,
|
requestId: resolvedRequestId,
|
||||||
@@ -1241,14 +1239,14 @@ export class DaemonClientV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkoutMerge(
|
async checkoutMerge(
|
||||||
agentId: string,
|
cwd: string,
|
||||||
input: { baseRef?: string; strategy?: "merge" | "squash"; requireCleanTarget?: boolean },
|
input: { baseRef?: string; strategy?: "merge" | "squash"; requireCleanTarget?: boolean },
|
||||||
requestId?: string
|
requestId?: string
|
||||||
): Promise<CheckoutMergePayload> {
|
): Promise<CheckoutMergePayload> {
|
||||||
const resolvedRequestId = this.createRequestId(requestId);
|
const resolvedRequestId = this.createRequestId(requestId);
|
||||||
const message = SessionInboundMessageSchema.parse({
|
const message = SessionInboundMessageSchema.parse({
|
||||||
type: "checkout_merge_request",
|
type: "checkout_merge_request",
|
||||||
agentId,
|
cwd,
|
||||||
baseRef: input.baseRef,
|
baseRef: input.baseRef,
|
||||||
strategy: input.strategy,
|
strategy: input.strategy,
|
||||||
requireCleanTarget: input.requireCleanTarget,
|
requireCleanTarget: input.requireCleanTarget,
|
||||||
@@ -1272,14 +1270,14 @@ export class DaemonClientV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkoutMergeFromBase(
|
async checkoutMergeFromBase(
|
||||||
agentId: string,
|
cwd: string,
|
||||||
input: { baseRef?: string; requireCleanTarget?: boolean },
|
input: { baseRef?: string; requireCleanTarget?: boolean },
|
||||||
requestId?: string
|
requestId?: string
|
||||||
): Promise<CheckoutMergeFromBasePayload> {
|
): Promise<CheckoutMergeFromBasePayload> {
|
||||||
const resolvedRequestId = this.createRequestId(requestId);
|
const resolvedRequestId = this.createRequestId(requestId);
|
||||||
const message = SessionInboundMessageSchema.parse({
|
const message = SessionInboundMessageSchema.parse({
|
||||||
type: "checkout_merge_from_base_request",
|
type: "checkout_merge_from_base_request",
|
||||||
agentId,
|
cwd,
|
||||||
baseRef: input.baseRef,
|
baseRef: input.baseRef,
|
||||||
requireCleanTarget: input.requireCleanTarget,
|
requireCleanTarget: input.requireCleanTarget,
|
||||||
requestId: resolvedRequestId,
|
requestId: resolvedRequestId,
|
||||||
@@ -1301,11 +1299,11 @@ export class DaemonClientV2 {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkoutPush(agentId: string, requestId?: string): Promise<CheckoutPushPayload> {
|
async checkoutPush(cwd: string, requestId?: string): Promise<CheckoutPushPayload> {
|
||||||
const resolvedRequestId = this.createRequestId(requestId);
|
const resolvedRequestId = this.createRequestId(requestId);
|
||||||
const message = SessionInboundMessageSchema.parse({
|
const message = SessionInboundMessageSchema.parse({
|
||||||
type: "checkout_push_request",
|
type: "checkout_push_request",
|
||||||
agentId,
|
cwd,
|
||||||
requestId: resolvedRequestId,
|
requestId: resolvedRequestId,
|
||||||
});
|
});
|
||||||
const response = this.waitFor(
|
const response = this.waitFor(
|
||||||
@@ -1326,14 +1324,14 @@ export class DaemonClientV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkoutPrCreate(
|
async checkoutPrCreate(
|
||||||
agentId: string,
|
cwd: string,
|
||||||
input: { title?: string; body?: string; baseRef?: string },
|
input: { title?: string; body?: string; baseRef?: string },
|
||||||
requestId?: string
|
requestId?: string
|
||||||
): Promise<CheckoutPrCreatePayload> {
|
): Promise<CheckoutPrCreatePayload> {
|
||||||
const resolvedRequestId = this.createRequestId(requestId);
|
const resolvedRequestId = this.createRequestId(requestId);
|
||||||
const message = SessionInboundMessageSchema.parse({
|
const message = SessionInboundMessageSchema.parse({
|
||||||
type: "checkout_pr_create_request",
|
type: "checkout_pr_create_request",
|
||||||
agentId,
|
cwd,
|
||||||
title: input.title,
|
title: input.title,
|
||||||
body: input.body,
|
body: input.body,
|
||||||
baseRef: input.baseRef,
|
baseRef: input.baseRef,
|
||||||
@@ -1357,13 +1355,13 @@ export class DaemonClientV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkoutPrStatus(
|
async checkoutPrStatus(
|
||||||
agentId: string,
|
cwd: string,
|
||||||
requestId?: string
|
requestId?: string
|
||||||
): Promise<CheckoutPrStatusPayload> {
|
): Promise<CheckoutPrStatusPayload> {
|
||||||
const resolvedRequestId = this.createRequestId(requestId);
|
const resolvedRequestId = this.createRequestId(requestId);
|
||||||
const message = SessionInboundMessageSchema.parse({
|
const message = SessionInboundMessageSchema.parse({
|
||||||
type: "checkout_pr_status_request",
|
type: "checkout_pr_status_request",
|
||||||
agentId,
|
cwd,
|
||||||
requestId: resolvedRequestId,
|
requestId: resolvedRequestId,
|
||||||
});
|
});
|
||||||
const response = this.waitFor(
|
const response = this.waitFor(
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ describe("daemon checkout ship loop", () => {
|
|||||||
});
|
});
|
||||||
agentId = agent.id;
|
agentId = agent.id;
|
||||||
|
|
||||||
const status = await ctx.client.getCheckoutStatus(agent.id);
|
const status = await ctx.client.getCheckoutStatus(worktree.worktreePath);
|
||||||
expect(status.isGit).toBe(true);
|
expect(status.isGit).toBe(true);
|
||||||
expect(status.isPaseoOwnedWorktree).toBe(true);
|
expect(status.isPaseoOwnedWorktree).toBe(true);
|
||||||
expect(status.repoRoot).toContain(repoDir);
|
expect(status.repoRoot).toContain(repoDir);
|
||||||
@@ -207,13 +207,13 @@ describe("daemon checkout ship loop", () => {
|
|||||||
const renamePayload = getStructuredContent(renameResult);
|
const renamePayload = getStructuredContent(renameResult);
|
||||||
expect(renamePayload?.success).toBe(true);
|
expect(renamePayload?.success).toBe(true);
|
||||||
|
|
||||||
const updatedStatus = await ctx.client.getCheckoutStatus(agent.id);
|
const updatedStatus = await ctx.client.getCheckoutStatus(worktree.worktreePath);
|
||||||
expect(updatedStatus.currentBranch).toBe("ship-loop-ready");
|
expect(updatedStatus.currentBranch).toBe("ship-loop-ready");
|
||||||
|
|
||||||
const readmePath = path.join(worktree.worktreePath, "README.md");
|
const readmePath = path.join(worktree.worktreePath, "README.md");
|
||||||
writeFileSync(readmePath, "init\nship loop update\n");
|
writeFileSync(readmePath, "init\nship loop update\n");
|
||||||
|
|
||||||
const diffUncommitted = await ctx.client.getCheckoutDiff(agent.id, {
|
const diffUncommitted = await ctx.client.getCheckoutDiff(worktree.worktreePath, {
|
||||||
mode: "uncommitted",
|
mode: "uncommitted",
|
||||||
});
|
});
|
||||||
expect(diffUncommitted.error).toBeNull();
|
expect(diffUncommitted.error).toBeNull();
|
||||||
@@ -221,7 +221,7 @@ describe("daemon checkout ship loop", () => {
|
|||||||
|
|
||||||
const timelineBeforeCommit =
|
const timelineBeforeCommit =
|
||||||
ctx.daemon.daemon.agentManager.getTimeline(agent.id).length;
|
ctx.daemon.daemon.agentManager.getTimeline(agent.id).length;
|
||||||
const commitResult = await ctx.client.checkoutCommit(agent.id, {
|
const commitResult = await ctx.client.checkoutCommit(worktree.worktreePath, {
|
||||||
addAll: true,
|
addAll: true,
|
||||||
});
|
});
|
||||||
expect(commitResult.error).toBeNull();
|
expect(commitResult.error).toBeNull();
|
||||||
@@ -230,12 +230,12 @@ describe("daemon checkout ship loop", () => {
|
|||||||
ctx.daemon.daemon.agentManager.getTimeline(agent.id).length;
|
ctx.daemon.daemon.agentManager.getTimeline(agent.id).length;
|
||||||
expect(timelineAfterCommit).toBe(timelineBeforeCommit);
|
expect(timelineAfterCommit).toBe(timelineBeforeCommit);
|
||||||
|
|
||||||
const diffAfterCommit = await ctx.client.getCheckoutDiff(agent.id, {
|
const diffAfterCommit = await ctx.client.getCheckoutDiff(worktree.worktreePath, {
|
||||||
mode: "uncommitted",
|
mode: "uncommitted",
|
||||||
});
|
});
|
||||||
expect(diffAfterCommit.files.length).toBe(0);
|
expect(diffAfterCommit.files.length).toBe(0);
|
||||||
|
|
||||||
const baseDiff = await ctx.client.getCheckoutDiff(agent.id, {
|
const baseDiff = await ctx.client.getCheckoutDiff(worktree.worktreePath, {
|
||||||
mode: "base",
|
mode: "base",
|
||||||
baseRef: "main",
|
baseRef: "main",
|
||||||
});
|
});
|
||||||
@@ -243,7 +243,7 @@ describe("daemon checkout ship loop", () => {
|
|||||||
|
|
||||||
const timelineBeforePr =
|
const timelineBeforePr =
|
||||||
ctx.daemon.daemon.agentManager.getTimeline(agent.id).length;
|
ctx.daemon.daemon.agentManager.getTimeline(agent.id).length;
|
||||||
const prCreate = await ctx.client.checkoutPrCreate(agent.id, {
|
const prCreate = await ctx.client.checkoutPrCreate(worktree.worktreePath, {
|
||||||
baseRef: "main",
|
baseRef: "main",
|
||||||
});
|
});
|
||||||
expect(prCreate.error).toBeNull();
|
expect(prCreate.error).toBeNull();
|
||||||
@@ -252,12 +252,12 @@ describe("daemon checkout ship loop", () => {
|
|||||||
ctx.daemon.daemon.agentManager.getTimeline(agent.id).length;
|
ctx.daemon.daemon.agentManager.getTimeline(agent.id).length;
|
||||||
expect(timelineAfterPr).toBe(timelineBeforePr);
|
expect(timelineAfterPr).toBe(timelineBeforePr);
|
||||||
|
|
||||||
const prStatus = await ctx.client.checkoutPrStatus(agent.id);
|
const prStatus = await ctx.client.checkoutPrStatus(worktree.worktreePath);
|
||||||
expect(prStatus.error).toBeNull();
|
expect(prStatus.error).toBeNull();
|
||||||
expect(prStatus.status?.url).toContain(repoName);
|
expect(prStatus.status?.url).toContain(repoName);
|
||||||
expect(prStatus.status?.state).toBeTruthy();
|
expect(prStatus.status?.state).toBeTruthy();
|
||||||
|
|
||||||
const mergeResult = await ctx.client.checkoutMerge(agent.id, {
|
const mergeResult = await ctx.client.checkoutMerge(worktree.worktreePath, {
|
||||||
baseRef: "main",
|
baseRef: "main",
|
||||||
strategy: "merge",
|
strategy: "merge",
|
||||||
requireCleanTarget: true,
|
requireCleanTarget: true,
|
||||||
@@ -265,14 +265,14 @@ describe("daemon checkout ship loop", () => {
|
|||||||
expect(mergeResult.error).toBeNull();
|
expect(mergeResult.error).toBeNull();
|
||||||
expect(mergeResult.success).toBe(true);
|
expect(mergeResult.success).toBe(true);
|
||||||
|
|
||||||
const statusAfterMerge = await ctx.client.getCheckoutStatus(agent.id);
|
const statusAfterMerge = await ctx.client.getCheckoutStatus(worktree.worktreePath);
|
||||||
expect(statusAfterMerge.isGit).toBe(true);
|
expect(statusAfterMerge.isGit).toBe(true);
|
||||||
if (statusAfterMerge.isGit) {
|
if (statusAfterMerge.isGit) {
|
||||||
expect(statusAfterMerge.baseRef).toBe("main");
|
expect(statusAfterMerge.baseRef).toBe("main");
|
||||||
expect(statusAfterMerge.aheadBehind?.ahead ?? 0).toBe(0);
|
expect(statusAfterMerge.aheadBehind?.ahead ?? 0).toBe(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseDiffAfterMerge = await ctx.client.getCheckoutDiff(agent.id, {
|
const baseDiffAfterMerge = await ctx.client.getCheckoutDiff(worktree.worktreePath, {
|
||||||
mode: "base",
|
mode: "base",
|
||||||
baseRef: "main",
|
baseRef: "main",
|
||||||
});
|
});
|
||||||
@@ -353,7 +353,7 @@ describe("daemon checkout ship loop", () => {
|
|||||||
});
|
});
|
||||||
agentId = agent.id;
|
agentId = agent.id;
|
||||||
|
|
||||||
const status = await ctx.client.getCheckoutStatus(agent.id);
|
const status = await ctx.client.getCheckoutStatus(worktree.worktreePath);
|
||||||
expect(status.isGit).toBe(true);
|
expect(status.isGit).toBe(true);
|
||||||
if (status.isGit) {
|
if (status.isGit) {
|
||||||
expect(status.hasRemote).toBe(true);
|
expect(status.hasRemote).toBe(true);
|
||||||
@@ -374,14 +374,14 @@ describe("daemon checkout ship loop", () => {
|
|||||||
|
|
||||||
// Add a commit on the agent branch.
|
// Add a commit on the agent branch.
|
||||||
writeFileSync(path.join(worktree.worktreePath, "feature.txt"), "feature\n");
|
writeFileSync(path.join(worktree.worktreePath, "feature.txt"), "feature\n");
|
||||||
const commitResult = await ctx.client.checkoutCommit(agent.id, {
|
const commitResult = await ctx.client.checkoutCommit(worktree.worktreePath, {
|
||||||
message: "feature commit",
|
message: "feature commit",
|
||||||
addAll: true,
|
addAll: true,
|
||||||
});
|
});
|
||||||
expect(commitResult.error).toBeNull();
|
expect(commitResult.error).toBeNull();
|
||||||
expect(commitResult.success).toBe(true);
|
expect(commitResult.success).toBe(true);
|
||||||
|
|
||||||
const mergeFromBase = await ctx.client.checkoutMergeFromBase(agent.id, {
|
const mergeFromBase = await ctx.client.checkoutMergeFromBase(worktree.worktreePath, {
|
||||||
baseRef: "main",
|
baseRef: "main",
|
||||||
requireCleanTarget: true,
|
requireCleanTarget: true,
|
||||||
});
|
});
|
||||||
@@ -394,7 +394,7 @@ describe("daemon checkout ship loop", () => {
|
|||||||
stdio: "pipe",
|
stdio: "pipe",
|
||||||
});
|
});
|
||||||
|
|
||||||
const pushResult = await ctx.client.checkoutPush(agent.id);
|
const pushResult = await ctx.client.checkoutPush(worktree.worktreePath);
|
||||||
expect(pushResult.error).toBeNull();
|
expect(pushResult.error).toBeNull();
|
||||||
expect(pushResult.success).toBe(true);
|
expect(pushResult.success).toBe(true);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -423,15 +423,15 @@ describe("daemon checkout ship loop", () => {
|
|||||||
});
|
});
|
||||||
agentId = agent.id;
|
agentId = agent.id;
|
||||||
|
|
||||||
const status = await ctx.client.getCheckoutStatus(agent.id);
|
const status = await ctx.client.getCheckoutStatus(worktree.worktreePath);
|
||||||
expect(status.isGit).toBe(false);
|
expect(status.isGit).toBe(false);
|
||||||
|
|
||||||
const diff = await ctx.client.getCheckoutDiff(agent.id, {
|
const diff = await ctx.client.getCheckoutDiff(worktree.worktreePath, {
|
||||||
mode: "uncommitted",
|
mode: "uncommitted",
|
||||||
});
|
});
|
||||||
expect(diff.error?.code).toBe("NOT_GIT_REPO");
|
expect(diff.error?.code).toBe("NOT_GIT_REPO");
|
||||||
|
|
||||||
const commit = await ctx.client.checkoutCommit(agent.id, {
|
const commit = await ctx.client.checkoutCommit(worktree.worktreePath, {
|
||||||
message: "Should fail",
|
message: "Should fail",
|
||||||
addAll: true,
|
addAll: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { exec } from "child_process";
|
|||||||
import { promisify, inspect } from "util";
|
import { promisify, inspect } from "util";
|
||||||
import { join, resolve, sep } from "path";
|
import { join, resolve, sep } from "path";
|
||||||
import invariant from "tiny-invariant";
|
import invariant from "tiny-invariant";
|
||||||
|
import { z } from "zod";
|
||||||
import { streamText, stepCountIs } from "ai";
|
import { streamText, stepCountIs } from "ai";
|
||||||
import type { ToolSet } from "ai";
|
import type { ToolSet } from "ai";
|
||||||
import type { ModelMessage } from "@ai-sdk/provider-utils";
|
import type { ModelMessage } from "@ai-sdk/provider-utils";
|
||||||
import { z } from "zod";
|
|
||||||
import {
|
import {
|
||||||
createOpenRouter,
|
createOpenRouter,
|
||||||
OpenRouterProviderOptions,
|
OpenRouterProviderOptions,
|
||||||
@@ -121,6 +121,12 @@ let restartRequested = false;
|
|||||||
const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_IDS[0];
|
const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_IDS[0];
|
||||||
const RESTART_EXIT_DELAY_MS = 250;
|
const RESTART_EXIT_DELAY_MS = 250;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default model used for auto-generating commit messages and PR descriptions.
|
||||||
|
* Uses Claude Haiku for speed and cost efficiency.
|
||||||
|
*/
|
||||||
|
const AUTO_GEN_MODEL = "haiku";
|
||||||
|
|
||||||
type ProcessingPhase = "idle" | "transcribing" | "llm";
|
type ProcessingPhase = "idle" | "transcribing" | "llm";
|
||||||
|
|
||||||
type NormalizedGitOptions = {
|
type NormalizedGitOptions = {
|
||||||
@@ -2357,18 +2363,8 @@ export class Session {
|
|||||||
return resolvedCandidate.startsWith(resolvedRoot + sep);
|
return resolvedCandidate.startsWith(resolvedRoot + sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildEphemeralAgentConfig(agent: ManagedAgent, title: string): AgentSessionConfig {
|
private async generateCommitMessage(cwd: string): Promise<string> {
|
||||||
return {
|
const diff = await getCheckoutDiff(cwd, { mode: "uncommitted" }, { paseoHome: this.paseoHome });
|
||||||
...agent.config,
|
|
||||||
cwd: agent.cwd,
|
|
||||||
title,
|
|
||||||
parentAgentId: agent.id,
|
|
||||||
internal: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async generateCommitMessage(agent: ManagedAgent): Promise<string> {
|
|
||||||
const diff = await getCheckoutDiff(agent.cwd, { mode: "uncommitted" }, { paseoHome: this.paseoHome });
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
message: z
|
message: z
|
||||||
.string()
|
.string()
|
||||||
@@ -2385,7 +2381,13 @@ export class Session {
|
|||||||
try {
|
try {
|
||||||
const result = await generateStructuredAgentResponse({
|
const result = await generateStructuredAgentResponse({
|
||||||
manager: this.agentManager,
|
manager: this.agentManager,
|
||||||
agentConfig: this.buildEphemeralAgentConfig(agent, "Commit generator"),
|
agentConfig: {
|
||||||
|
provider: "claude",
|
||||||
|
model: AUTO_GEN_MODEL,
|
||||||
|
cwd,
|
||||||
|
title: "Commit generator",
|
||||||
|
internal: true,
|
||||||
|
},
|
||||||
prompt,
|
prompt,
|
||||||
schema,
|
schema,
|
||||||
schemaName: "CommitMessage",
|
schemaName: "CommitMessage",
|
||||||
@@ -2400,12 +2402,12 @@ export class Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async generatePullRequestText(agent: ManagedAgent, baseRef?: string): Promise<{
|
private async generatePullRequestText(cwd: string, baseRef?: string): Promise<{
|
||||||
title: string;
|
title: string;
|
||||||
body: string;
|
body: string;
|
||||||
}> {
|
}> {
|
||||||
const diff = await getCheckoutDiff(
|
const diff = await getCheckoutDiff(
|
||||||
agent.cwd,
|
cwd,
|
||||||
{
|
{
|
||||||
mode: "base",
|
mode: "base",
|
||||||
baseRef,
|
baseRef,
|
||||||
@@ -2425,7 +2427,13 @@ export class Session {
|
|||||||
try {
|
try {
|
||||||
return await generateStructuredAgentResponse({
|
return await generateStructuredAgentResponse({
|
||||||
manager: this.agentManager,
|
manager: this.agentManager,
|
||||||
agentConfig: this.buildEphemeralAgentConfig(agent, "PR generator"),
|
agentConfig: {
|
||||||
|
provider: "claude",
|
||||||
|
model: AUTO_GEN_MODEL,
|
||||||
|
cwd,
|
||||||
|
title: "PR generator",
|
||||||
|
internal: true,
|
||||||
|
},
|
||||||
prompt,
|
prompt,
|
||||||
schema,
|
schema,
|
||||||
schemaName: "PullRequest",
|
schemaName: "PullRequest",
|
||||||
@@ -2853,31 +2861,7 @@ export class Session {
|
|||||||
private async handleCheckoutStatusRequest(
|
private async handleCheckoutStatusRequest(
|
||||||
msg: Extract<SessionInboundMessage, { type: "checkout_status_request" }>
|
msg: Extract<SessionInboundMessage, { type: "checkout_status_request" }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { agentId, requestId } = msg;
|
const { cwd, requestId } = msg;
|
||||||
const agent = this.agentManager.getAgent(agentId);
|
|
||||||
// Use cwd from agent if found, otherwise fall back to cwd from message
|
|
||||||
const cwd = agent?.cwd ?? msg.cwd;
|
|
||||||
if (!cwd) {
|
|
||||||
this.emit({
|
|
||||||
type: "checkout_status_response",
|
|
||||||
payload: {
|
|
||||||
agentId,
|
|
||||||
cwd: "",
|
|
||||||
isGit: false,
|
|
||||||
repoRoot: null,
|
|
||||||
currentBranch: null,
|
|
||||||
isDirty: null,
|
|
||||||
baseRef: null,
|
|
||||||
aheadBehind: null,
|
|
||||||
hasRemote: false,
|
|
||||||
remoteUrl: null,
|
|
||||||
isPaseoOwnedWorktree: false,
|
|
||||||
error: { code: "UNKNOWN", message: `Agent not found and no cwd provided: ${agentId}` },
|
|
||||||
requestId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const status = await getCheckoutStatus(cwd, { paseoHome: this.paseoHome });
|
const status = await getCheckoutStatus(cwd, { paseoHome: this.paseoHome });
|
||||||
@@ -2885,7 +2869,6 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_status_response",
|
type: "checkout_status_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
|
||||||
cwd,
|
cwd,
|
||||||
isGit: false,
|
isGit: false,
|
||||||
repoRoot: null,
|
repoRoot: null,
|
||||||
@@ -2893,6 +2876,7 @@ export class Session {
|
|||||||
isDirty: null,
|
isDirty: null,
|
||||||
baseRef: null,
|
baseRef: null,
|
||||||
aheadBehind: null,
|
aheadBehind: null,
|
||||||
|
aheadOfOrigin: null,
|
||||||
hasRemote: false,
|
hasRemote: false,
|
||||||
remoteUrl: null,
|
remoteUrl: null,
|
||||||
isPaseoOwnedWorktree: false,
|
isPaseoOwnedWorktree: false,
|
||||||
@@ -2907,7 +2891,6 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_status_response",
|
type: "checkout_status_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
|
||||||
cwd,
|
cwd,
|
||||||
isGit: true,
|
isGit: true,
|
||||||
repoRoot: status.repoRoot ?? null,
|
repoRoot: status.repoRoot ?? null,
|
||||||
@@ -2915,6 +2898,7 @@ export class Session {
|
|||||||
isDirty: status.isDirty ?? null,
|
isDirty: status.isDirty ?? null,
|
||||||
baseRef: status.baseRef,
|
baseRef: status.baseRef,
|
||||||
aheadBehind: status.aheadBehind ?? null,
|
aheadBehind: status.aheadBehind ?? null,
|
||||||
|
aheadOfOrigin: status.aheadOfOrigin ?? null,
|
||||||
hasRemote: status.hasRemote,
|
hasRemote: status.hasRemote,
|
||||||
remoteUrl: status.remoteUrl,
|
remoteUrl: status.remoteUrl,
|
||||||
isPaseoOwnedWorktree: true,
|
isPaseoOwnedWorktree: true,
|
||||||
@@ -2928,7 +2912,6 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_status_response",
|
type: "checkout_status_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
|
||||||
cwd,
|
cwd,
|
||||||
isGit: true,
|
isGit: true,
|
||||||
repoRoot: status.repoRoot ?? null,
|
repoRoot: status.repoRoot ?? null,
|
||||||
@@ -2936,6 +2919,7 @@ export class Session {
|
|||||||
isDirty: status.isDirty ?? null,
|
isDirty: status.isDirty ?? null,
|
||||||
baseRef: status.baseRef ?? null,
|
baseRef: status.baseRef ?? null,
|
||||||
aheadBehind: status.aheadBehind ?? null,
|
aheadBehind: status.aheadBehind ?? null,
|
||||||
|
aheadOfOrigin: status.aheadOfOrigin ?? null,
|
||||||
hasRemote: status.hasRemote,
|
hasRemote: status.hasRemote,
|
||||||
remoteUrl: status.remoteUrl,
|
remoteUrl: status.remoteUrl,
|
||||||
isPaseoOwnedWorktree: false,
|
isPaseoOwnedWorktree: false,
|
||||||
@@ -2947,7 +2931,6 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_status_response",
|
type: "checkout_status_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
|
||||||
cwd,
|
cwd,
|
||||||
isGit: false,
|
isGit: false,
|
||||||
repoRoot: null,
|
repoRoot: null,
|
||||||
@@ -2955,6 +2938,7 @@ export class Session {
|
|||||||
isDirty: null,
|
isDirty: null,
|
||||||
baseRef: null,
|
baseRef: null,
|
||||||
aheadBehind: null,
|
aheadBehind: null,
|
||||||
|
aheadOfOrigin: null,
|
||||||
hasRemote: false,
|
hasRemote: false,
|
||||||
remoteUrl: null,
|
remoteUrl: null,
|
||||||
isPaseoOwnedWorktree: false,
|
isPaseoOwnedWorktree: false,
|
||||||
@@ -2968,24 +2952,11 @@ export class Session {
|
|||||||
private async handleCheckoutDiffRequest(
|
private async handleCheckoutDiffRequest(
|
||||||
msg: Extract<SessionInboundMessage, { type: "checkout_diff_request" }>
|
msg: Extract<SessionInboundMessage, { type: "checkout_diff_request" }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { agentId, requestId, compare } = msg;
|
const { cwd, requestId, compare } = msg;
|
||||||
const agent = this.agentManager.getAgent(agentId);
|
|
||||||
if (!agent) {
|
|
||||||
this.emit({
|
|
||||||
type: "checkout_diff_response",
|
|
||||||
payload: {
|
|
||||||
agentId,
|
|
||||||
files: [],
|
|
||||||
error: { code: "UNKNOWN", message: `Agent not found: ${agentId}` },
|
|
||||||
requestId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const diffResult = await getCheckoutDiff(
|
const diffResult = await getCheckoutDiff(
|
||||||
agent.cwd,
|
cwd,
|
||||||
{
|
{
|
||||||
mode: compare.mode,
|
mode: compare.mode,
|
||||||
baseRef: compare.baseRef,
|
baseRef: compare.baseRef,
|
||||||
@@ -2996,7 +2967,7 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_diff_response",
|
type: "checkout_diff_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
files: diffResult.structured ?? [],
|
files: diffResult.structured ?? [],
|
||||||
error: null,
|
error: null,
|
||||||
requestId,
|
requestId,
|
||||||
@@ -3006,7 +2977,7 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_diff_response",
|
type: "checkout_diff_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
files: [],
|
files: [],
|
||||||
error: this.toCheckoutError(error),
|
error: this.toCheckoutError(error),
|
||||||
requestId,
|
requestId,
|
||||||
@@ -3018,31 +2989,18 @@ export class Session {
|
|||||||
private async handleCheckoutCommitRequest(
|
private async handleCheckoutCommitRequest(
|
||||||
msg: Extract<SessionInboundMessage, { type: "checkout_commit_request" }>
|
msg: Extract<SessionInboundMessage, { type: "checkout_commit_request" }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { agentId, requestId } = msg;
|
const { cwd, requestId } = msg;
|
||||||
const agent = this.agentManager.getAgent(agentId);
|
|
||||||
if (!agent) {
|
|
||||||
this.emit({
|
|
||||||
type: "checkout_commit_response",
|
|
||||||
payload: {
|
|
||||||
agentId,
|
|
||||||
success: false,
|
|
||||||
error: { code: "UNKNOWN", message: `Agent not found: ${agentId}` },
|
|
||||||
requestId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let message = msg.message?.trim() ?? "";
|
let message = msg.message?.trim() ?? "";
|
||||||
if (!message) {
|
if (!message) {
|
||||||
message = await this.generateCommitMessage(agent);
|
message = await this.generateCommitMessage(cwd);
|
||||||
}
|
}
|
||||||
if (!message) {
|
if (!message) {
|
||||||
throw new Error("Commit message is required");
|
throw new Error("Commit message is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
await commitChanges(agent.cwd, {
|
await commitChanges(cwd, {
|
||||||
message,
|
message,
|
||||||
addAll: msg.addAll ?? true,
|
addAll: msg.addAll ?? true,
|
||||||
});
|
});
|
||||||
@@ -3050,7 +3008,7 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_commit_response",
|
type: "checkout_commit_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
success: true,
|
success: true,
|
||||||
error: null,
|
error: null,
|
||||||
requestId,
|
requestId,
|
||||||
@@ -3060,7 +3018,7 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_commit_response",
|
type: "checkout_commit_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
success: false,
|
success: false,
|
||||||
error: this.toCheckoutError(error),
|
error: this.toCheckoutError(error),
|
||||||
requestId,
|
requestId,
|
||||||
@@ -3072,29 +3030,14 @@ export class Session {
|
|||||||
private async handleCheckoutMergeRequest(
|
private async handleCheckoutMergeRequest(
|
||||||
msg: Extract<SessionInboundMessage, { type: "checkout_merge_request" }>
|
msg: Extract<SessionInboundMessage, { type: "checkout_merge_request" }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { agentId, requestId } = msg;
|
const { cwd, requestId } = msg;
|
||||||
const agent = this.agentManager.getAgent(agentId);
|
|
||||||
if (!agent) {
|
|
||||||
this.emit({
|
|
||||||
type: "checkout_merge_response",
|
|
||||||
payload: {
|
|
||||||
agentId,
|
|
||||||
success: false,
|
|
||||||
error: { code: "UNKNOWN", message: `Agent not found: ${agentId}` },
|
|
||||||
requestId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const status = await getCheckoutStatus(agent.cwd, { paseoHome: this.paseoHome });
|
const status = await getCheckoutStatus(cwd, { paseoHome: this.paseoHome });
|
||||||
if (!status.isGit) {
|
if (!status.isGit) {
|
||||||
// `getCheckoutStatus` can return `isGit=false` in transient situations (e.g. cwd lookup
|
|
||||||
// running during other git operations). Double-check with git directly before failing.
|
|
||||||
try {
|
try {
|
||||||
await execAsync("git rev-parse --is-inside-work-tree", {
|
await execAsync("git rev-parse --is-inside-work-tree", {
|
||||||
cwd: agent.cwd,
|
cwd,
|
||||||
env: READ_ONLY_GIT_ENV,
|
env: READ_ONLY_GIT_ENV,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -3104,13 +3047,13 @@ export class Session {
|
|||||||
: error instanceof Error
|
: error instanceof Error
|
||||||
? error.message
|
? error.message
|
||||||
: String(error);
|
: String(error);
|
||||||
throw new Error(`Not a git repository: ${agent.cwd}\n${details}`.trim());
|
throw new Error(`Not a git repository: ${cwd}\n${details}`.trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.requireCleanTarget) {
|
if (msg.requireCleanTarget) {
|
||||||
const { stdout } = await execAsync("git status --porcelain", {
|
const { stdout } = await execAsync("git status --porcelain", {
|
||||||
cwd: agent.cwd,
|
cwd,
|
||||||
env: READ_ONLY_GIT_ENV,
|
env: READ_ONLY_GIT_ENV,
|
||||||
});
|
});
|
||||||
if (stdout.trim().length > 0) {
|
if (stdout.trim().length > 0) {
|
||||||
@@ -3127,7 +3070,7 @@ export class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await mergeToBase(
|
await mergeToBase(
|
||||||
agent.cwd,
|
cwd,
|
||||||
{
|
{
|
||||||
baseRef,
|
baseRef,
|
||||||
mode: msg.strategy === "squash" ? "squash" : "merge",
|
mode: msg.strategy === "squash" ? "squash" : "merge",
|
||||||
@@ -3138,7 +3081,7 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_merge_response",
|
type: "checkout_merge_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
success: true,
|
success: true,
|
||||||
error: null,
|
error: null,
|
||||||
requestId,
|
requestId,
|
||||||
@@ -3148,7 +3091,7 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_merge_response",
|
type: "checkout_merge_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
success: false,
|
success: false,
|
||||||
error: this.toCheckoutError(error),
|
error: this.toCheckoutError(error),
|
||||||
requestId,
|
requestId,
|
||||||
@@ -3160,25 +3103,12 @@ export class Session {
|
|||||||
private async handleCheckoutMergeFromBaseRequest(
|
private async handleCheckoutMergeFromBaseRequest(
|
||||||
msg: Extract<SessionInboundMessage, { type: "checkout_merge_from_base_request" }>
|
msg: Extract<SessionInboundMessage, { type: "checkout_merge_from_base_request" }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { agentId, requestId } = msg;
|
const { cwd, requestId } = msg;
|
||||||
const agent = this.agentManager.getAgent(agentId);
|
|
||||||
if (!agent) {
|
|
||||||
this.emit({
|
|
||||||
type: "checkout_merge_from_base_response",
|
|
||||||
payload: {
|
|
||||||
agentId,
|
|
||||||
success: false,
|
|
||||||
error: { code: "UNKNOWN", message: `Agent not found: ${agentId}` },
|
|
||||||
requestId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (msg.requireCleanTarget ?? true) {
|
if (msg.requireCleanTarget ?? true) {
|
||||||
const { stdout } = await execAsync("git status --porcelain", {
|
const { stdout } = await execAsync("git status --porcelain", {
|
||||||
cwd: agent.cwd,
|
cwd,
|
||||||
env: READ_ONLY_GIT_ENV,
|
env: READ_ONLY_GIT_ENV,
|
||||||
});
|
});
|
||||||
if (stdout.trim().length > 0) {
|
if (stdout.trim().length > 0) {
|
||||||
@@ -3187,7 +3117,7 @@ export class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await mergeFromBase(
|
await mergeFromBase(
|
||||||
agent.cwd,
|
cwd,
|
||||||
{
|
{
|
||||||
baseRef: msg.baseRef,
|
baseRef: msg.baseRef,
|
||||||
requireCleanTarget: msg.requireCleanTarget ?? true,
|
requireCleanTarget: msg.requireCleanTarget ?? true,
|
||||||
@@ -3197,7 +3127,7 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_merge_from_base_response",
|
type: "checkout_merge_from_base_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
success: true,
|
success: true,
|
||||||
error: null,
|
error: null,
|
||||||
requestId,
|
requestId,
|
||||||
@@ -3207,7 +3137,7 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_merge_from_base_response",
|
type: "checkout_merge_from_base_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
success: false,
|
success: false,
|
||||||
error: this.toCheckoutError(error),
|
error: this.toCheckoutError(error),
|
||||||
requestId,
|
requestId,
|
||||||
@@ -3219,27 +3149,14 @@ export class Session {
|
|||||||
private async handleCheckoutPushRequest(
|
private async handleCheckoutPushRequest(
|
||||||
msg: Extract<SessionInboundMessage, { type: "checkout_push_request" }>
|
msg: Extract<SessionInboundMessage, { type: "checkout_push_request" }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { agentId, requestId } = msg;
|
const { cwd, requestId } = msg;
|
||||||
const agent = this.agentManager.getAgent(agentId);
|
|
||||||
if (!agent) {
|
|
||||||
this.emit({
|
|
||||||
type: "checkout_push_response",
|
|
||||||
payload: {
|
|
||||||
agentId,
|
|
||||||
success: false,
|
|
||||||
error: { code: "UNKNOWN", message: `Agent not found: ${agentId}` },
|
|
||||||
requestId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await pushCurrentBranch(agent.cwd);
|
await pushCurrentBranch(cwd);
|
||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_push_response",
|
type: "checkout_push_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
success: true,
|
success: true,
|
||||||
error: null,
|
error: null,
|
||||||
requestId,
|
requestId,
|
||||||
@@ -3249,7 +3166,7 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_push_response",
|
type: "checkout_push_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
success: false,
|
success: false,
|
||||||
error: this.toCheckoutError(error),
|
error: this.toCheckoutError(error),
|
||||||
requestId,
|
requestId,
|
||||||
@@ -3261,39 +3178,19 @@ export class Session {
|
|||||||
private async handleCheckoutPrCreateRequest(
|
private async handleCheckoutPrCreateRequest(
|
||||||
msg: Extract<SessionInboundMessage, { type: "checkout_pr_create_request" }>
|
msg: Extract<SessionInboundMessage, { type: "checkout_pr_create_request" }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { agentId, requestId } = msg;
|
const { cwd, requestId } = msg;
|
||||||
const agent = this.agentManager.getAgent(agentId);
|
|
||||||
if (!agent) {
|
|
||||||
this.emit({
|
|
||||||
type: "checkout_pr_create_response",
|
|
||||||
payload: {
|
|
||||||
agentId,
|
|
||||||
url: null,
|
|
||||||
number: null,
|
|
||||||
error: { code: "UNKNOWN", message: `Agent not found: ${agentId}` },
|
|
||||||
requestId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let title = msg.title?.trim() ?? "";
|
let title = msg.title?.trim() ?? "";
|
||||||
let body = msg.body?.trim() ?? "";
|
let body = msg.body?.trim() ?? "";
|
||||||
|
|
||||||
if (!title || !body) {
|
if (!title || !body) {
|
||||||
const generated = await this.generatePullRequestText(agent, msg.baseRef);
|
const generated = await this.generatePullRequestText(cwd, msg.baseRef);
|
||||||
if (!title) {
|
if (!title) title = generated.title;
|
||||||
title = generated.title;
|
if (!body) body = generated.body;
|
||||||
}
|
|
||||||
if (!body) {
|
|
||||||
body = generated.body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!title) {
|
|
||||||
throw new Error("Pull request title is required");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await createPullRequest(agent.cwd, {
|
const result = await createPullRequest(cwd, {
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
base: msg.baseRef,
|
base: msg.baseRef,
|
||||||
@@ -3302,7 +3199,7 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_pr_create_response",
|
type: "checkout_pr_create_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
url: result.url ?? null,
|
url: result.url ?? null,
|
||||||
number: result.number ?? null,
|
number: result.number ?? null,
|
||||||
error: null,
|
error: null,
|
||||||
@@ -3313,7 +3210,7 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_pr_create_response",
|
type: "checkout_pr_create_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
url: null,
|
url: null,
|
||||||
number: null,
|
number: null,
|
||||||
error: this.toCheckoutError(error),
|
error: this.toCheckoutError(error),
|
||||||
@@ -3326,27 +3223,14 @@ export class Session {
|
|||||||
private async handleCheckoutPrStatusRequest(
|
private async handleCheckoutPrStatusRequest(
|
||||||
msg: Extract<SessionInboundMessage, { type: "checkout_pr_status_request" }>
|
msg: Extract<SessionInboundMessage, { type: "checkout_pr_status_request" }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { agentId, requestId } = msg;
|
const { cwd, requestId } = msg;
|
||||||
const agent = this.agentManager.getAgent(agentId);
|
|
||||||
if (!agent) {
|
|
||||||
this.emit({
|
|
||||||
type: "checkout_pr_status_response",
|
|
||||||
payload: {
|
|
||||||
agentId,
|
|
||||||
status: null,
|
|
||||||
error: { code: "UNKNOWN", message: `Agent not found: ${agentId}` },
|
|
||||||
requestId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const status = await getPullRequestStatus(agent.cwd);
|
const status = await getPullRequestStatus(cwd);
|
||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_pr_status_response",
|
type: "checkout_pr_status_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
status,
|
status,
|
||||||
error: null,
|
error: null,
|
||||||
requestId,
|
requestId,
|
||||||
@@ -3356,7 +3240,7 @@ export class Session {
|
|||||||
this.emit({
|
this.emit({
|
||||||
type: "checkout_pr_status_response",
|
type: "checkout_pr_status_response",
|
||||||
payload: {
|
payload: {
|
||||||
agentId,
|
cwd,
|
||||||
status: null,
|
status: null,
|
||||||
error: this.toCheckoutError(error),
|
error: this.toCheckoutError(error),
|
||||||
requestId,
|
requestId,
|
||||||
|
|||||||
@@ -476,22 +476,20 @@ const CheckoutDiffCompareSchema = z.object({
|
|||||||
|
|
||||||
export const CheckoutStatusRequestSchema = z.object({
|
export const CheckoutStatusRequestSchema = z.object({
|
||||||
type: z.literal("checkout_status_request"),
|
type: z.literal("checkout_status_request"),
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
/** Optional cwd to use if the agent is not live in memory (e.g. from persisted agents.json) */
|
|
||||||
cwd: z.string().optional(),
|
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CheckoutDiffRequestSchema = z.object({
|
export const CheckoutDiffRequestSchema = z.object({
|
||||||
type: z.literal("checkout_diff_request"),
|
type: z.literal("checkout_diff_request"),
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
compare: CheckoutDiffCompareSchema,
|
compare: CheckoutDiffCompareSchema,
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CheckoutCommitRequestSchema = z.object({
|
export const CheckoutCommitRequestSchema = z.object({
|
||||||
type: z.literal("checkout_commit_request"),
|
type: z.literal("checkout_commit_request"),
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
message: z.string().optional(),
|
message: z.string().optional(),
|
||||||
addAll: z.boolean().optional(),
|
addAll: z.boolean().optional(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
@@ -499,7 +497,7 @@ export const CheckoutCommitRequestSchema = z.object({
|
|||||||
|
|
||||||
export const CheckoutMergeRequestSchema = z.object({
|
export const CheckoutMergeRequestSchema = z.object({
|
||||||
type: z.literal("checkout_merge_request"),
|
type: z.literal("checkout_merge_request"),
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
baseRef: z.string().optional(),
|
baseRef: z.string().optional(),
|
||||||
strategy: z.enum(["merge", "squash"]).optional(),
|
strategy: z.enum(["merge", "squash"]).optional(),
|
||||||
requireCleanTarget: z.boolean().optional(),
|
requireCleanTarget: z.boolean().optional(),
|
||||||
@@ -508,7 +506,7 @@ export const CheckoutMergeRequestSchema = z.object({
|
|||||||
|
|
||||||
export const CheckoutMergeFromBaseRequestSchema = z.object({
|
export const CheckoutMergeFromBaseRequestSchema = z.object({
|
||||||
type: z.literal("checkout_merge_from_base_request"),
|
type: z.literal("checkout_merge_from_base_request"),
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
baseRef: z.string().optional(),
|
baseRef: z.string().optional(),
|
||||||
requireCleanTarget: z.boolean().optional(),
|
requireCleanTarget: z.boolean().optional(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
@@ -516,13 +514,13 @@ export const CheckoutMergeFromBaseRequestSchema = z.object({
|
|||||||
|
|
||||||
export const CheckoutPushRequestSchema = z.object({
|
export const CheckoutPushRequestSchema = z.object({
|
||||||
type: z.literal("checkout_push_request"),
|
type: z.literal("checkout_push_request"),
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CheckoutPrCreateRequestSchema = z.object({
|
export const CheckoutPrCreateRequestSchema = z.object({
|
||||||
type: z.literal("checkout_pr_create_request"),
|
type: z.literal("checkout_pr_create_request"),
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
body: z.string().optional(),
|
body: z.string().optional(),
|
||||||
baseRef: z.string().optional(),
|
baseRef: z.string().optional(),
|
||||||
@@ -531,7 +529,7 @@ export const CheckoutPrCreateRequestSchema = z.object({
|
|||||||
|
|
||||||
export const CheckoutPrStatusRequestSchema = z.object({
|
export const CheckoutPrStatusRequestSchema = z.object({
|
||||||
type: z.literal("checkout_pr_status_request"),
|
type: z.literal("checkout_pr_status_request"),
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1052,7 +1050,6 @@ const AheadBehindSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const CheckoutStatusCommonSchema = z.object({
|
const CheckoutStatusCommonSchema = z.object({
|
||||||
agentId: z.string(),
|
|
||||||
cwd: z.string(),
|
cwd: z.string(),
|
||||||
error: CheckoutErrorSchema.nullable(),
|
error: CheckoutErrorSchema.nullable(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
@@ -1066,6 +1063,7 @@ const CheckoutStatusNotGitSchema = CheckoutStatusCommonSchema.extend({
|
|||||||
isDirty: z.null(),
|
isDirty: z.null(),
|
||||||
baseRef: z.null(),
|
baseRef: z.null(),
|
||||||
aheadBehind: z.null(),
|
aheadBehind: z.null(),
|
||||||
|
aheadOfOrigin: z.null(),
|
||||||
hasRemote: z.boolean(),
|
hasRemote: z.boolean(),
|
||||||
remoteUrl: z.null(),
|
remoteUrl: z.null(),
|
||||||
});
|
});
|
||||||
@@ -1078,6 +1076,7 @@ const CheckoutStatusGitNonPaseoSchema = CheckoutStatusCommonSchema.extend({
|
|||||||
isDirty: z.boolean(),
|
isDirty: z.boolean(),
|
||||||
baseRef: z.string().nullable(),
|
baseRef: z.string().nullable(),
|
||||||
aheadBehind: AheadBehindSchema.nullable(),
|
aheadBehind: AheadBehindSchema.nullable(),
|
||||||
|
aheadOfOrigin: z.number().nullable(),
|
||||||
hasRemote: z.boolean(),
|
hasRemote: z.boolean(),
|
||||||
remoteUrl: z.string().nullable(),
|
remoteUrl: z.string().nullable(),
|
||||||
});
|
});
|
||||||
@@ -1090,6 +1089,7 @@ const CheckoutStatusGitPaseoSchema = CheckoutStatusCommonSchema.extend({
|
|||||||
isDirty: z.boolean(),
|
isDirty: z.boolean(),
|
||||||
baseRef: z.string(),
|
baseRef: z.string(),
|
||||||
aheadBehind: AheadBehindSchema.nullable(),
|
aheadBehind: AheadBehindSchema.nullable(),
|
||||||
|
aheadOfOrigin: z.number().nullable(),
|
||||||
hasRemote: z.boolean(),
|
hasRemote: z.boolean(),
|
||||||
remoteUrl: z.string().nullable(),
|
remoteUrl: z.string().nullable(),
|
||||||
});
|
});
|
||||||
@@ -1106,7 +1106,7 @@ export const CheckoutStatusResponseSchema = z.object({
|
|||||||
export const CheckoutDiffResponseSchema = z.object({
|
export const CheckoutDiffResponseSchema = z.object({
|
||||||
type: z.literal("checkout_diff_response"),
|
type: z.literal("checkout_diff_response"),
|
||||||
payload: z.object({
|
payload: z.object({
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
files: z.array(ParsedDiffFileSchema),
|
files: z.array(ParsedDiffFileSchema),
|
||||||
error: CheckoutErrorSchema.nullable(),
|
error: CheckoutErrorSchema.nullable(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
@@ -1116,7 +1116,7 @@ export const CheckoutDiffResponseSchema = z.object({
|
|||||||
export const CheckoutCommitResponseSchema = z.object({
|
export const CheckoutCommitResponseSchema = z.object({
|
||||||
type: z.literal("checkout_commit_response"),
|
type: z.literal("checkout_commit_response"),
|
||||||
payload: z.object({
|
payload: z.object({
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
success: z.boolean(),
|
success: z.boolean(),
|
||||||
error: CheckoutErrorSchema.nullable(),
|
error: CheckoutErrorSchema.nullable(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
@@ -1126,7 +1126,7 @@ export const CheckoutCommitResponseSchema = z.object({
|
|||||||
export const CheckoutMergeResponseSchema = z.object({
|
export const CheckoutMergeResponseSchema = z.object({
|
||||||
type: z.literal("checkout_merge_response"),
|
type: z.literal("checkout_merge_response"),
|
||||||
payload: z.object({
|
payload: z.object({
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
success: z.boolean(),
|
success: z.boolean(),
|
||||||
error: CheckoutErrorSchema.nullable(),
|
error: CheckoutErrorSchema.nullable(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
@@ -1136,7 +1136,7 @@ export const CheckoutMergeResponseSchema = z.object({
|
|||||||
export const CheckoutMergeFromBaseResponseSchema = z.object({
|
export const CheckoutMergeFromBaseResponseSchema = z.object({
|
||||||
type: z.literal("checkout_merge_from_base_response"),
|
type: z.literal("checkout_merge_from_base_response"),
|
||||||
payload: z.object({
|
payload: z.object({
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
success: z.boolean(),
|
success: z.boolean(),
|
||||||
error: CheckoutErrorSchema.nullable(),
|
error: CheckoutErrorSchema.nullable(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
@@ -1146,7 +1146,7 @@ export const CheckoutMergeFromBaseResponseSchema = z.object({
|
|||||||
export const CheckoutPushResponseSchema = z.object({
|
export const CheckoutPushResponseSchema = z.object({
|
||||||
type: z.literal("checkout_push_response"),
|
type: z.literal("checkout_push_response"),
|
||||||
payload: z.object({
|
payload: z.object({
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
success: z.boolean(),
|
success: z.boolean(),
|
||||||
error: CheckoutErrorSchema.nullable(),
|
error: CheckoutErrorSchema.nullable(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
@@ -1156,7 +1156,7 @@ export const CheckoutPushResponseSchema = z.object({
|
|||||||
export const CheckoutPrCreateResponseSchema = z.object({
|
export const CheckoutPrCreateResponseSchema = z.object({
|
||||||
type: z.literal("checkout_pr_create_response"),
|
type: z.literal("checkout_pr_create_response"),
|
||||||
payload: z.object({
|
payload: z.object({
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
url: z.string().nullable(),
|
url: z.string().nullable(),
|
||||||
number: z.number().nullable(),
|
number: z.number().nullable(),
|
||||||
error: CheckoutErrorSchema.nullable(),
|
error: CheckoutErrorSchema.nullable(),
|
||||||
@@ -1175,7 +1175,7 @@ const CheckoutPrStatusSchema = z.object({
|
|||||||
export const CheckoutPrStatusResponseSchema = z.object({
|
export const CheckoutPrStatusResponseSchema = z.object({
|
||||||
type: z.literal("checkout_pr_status_response"),
|
type: z.literal("checkout_pr_status_response"),
|
||||||
payload: z.object({
|
payload: z.object({
|
||||||
agentId: z.string(),
|
cwd: z.string(),
|
||||||
status: CheckoutPrStatusSchema.nullable(),
|
status: CheckoutPrStatusSchema.nullable(),
|
||||||
error: CheckoutErrorSchema.nullable(),
|
error: CheckoutErrorSchema.nullable(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export type CheckoutStatusGitNonPaseo = {
|
|||||||
isDirty: boolean;
|
isDirty: boolean;
|
||||||
baseRef: string | null;
|
baseRef: string | null;
|
||||||
aheadBehind: AheadBehind | null;
|
aheadBehind: AheadBehind | null;
|
||||||
|
aheadOfOrigin: number | null;
|
||||||
hasRemote: boolean;
|
hasRemote: boolean;
|
||||||
remoteUrl: string | null;
|
remoteUrl: string | null;
|
||||||
isPaseoOwnedWorktree: false;
|
isPaseoOwnedWorktree: false;
|
||||||
@@ -82,6 +83,7 @@ export type CheckoutStatusGitPaseo = {
|
|||||||
isDirty: boolean;
|
isDirty: boolean;
|
||||||
baseRef: string;
|
baseRef: string;
|
||||||
aheadBehind: AheadBehind | null;
|
aheadBehind: AheadBehind | null;
|
||||||
|
aheadOfOrigin: number | null;
|
||||||
hasRemote: boolean;
|
hasRemote: boolean;
|
||||||
remoteUrl: string | null;
|
remoteUrl: string | null;
|
||||||
isPaseoOwnedWorktree: true;
|
isPaseoOwnedWorktree: true;
|
||||||
@@ -393,6 +395,22 @@ async function getAheadBehind(cwd: string, baseRef: string, currentBranch: strin
|
|||||||
return { ahead, behind };
|
return { ahead, behind };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getAheadOfOrigin(cwd: string, currentBranch: string): Promise<number | null> {
|
||||||
|
if (!currentBranch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { stdout } = await execAsync(
|
||||||
|
`git rev-list --count origin/${currentBranch}..${currentBranch}`,
|
||||||
|
{ cwd, env: READ_ONLY_GIT_ENV }
|
||||||
|
);
|
||||||
|
const count = Number.parseInt(stdout.trim(), 10);
|
||||||
|
return Number.isNaN(count) ? null : count;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getUntrackedDiff(cwd: string): Promise<string> {
|
async function getUntrackedDiff(cwd: string): Promise<string> {
|
||||||
let untrackedDiff = "";
|
let untrackedDiff = "";
|
||||||
try {
|
try {
|
||||||
@@ -443,6 +461,8 @@ export async function getCheckoutStatus(
|
|||||||
const baseRef = configured.baseRef ?? (await resolveBaseRef(repoInfo.path));
|
const baseRef = configured.baseRef ?? (await resolveBaseRef(repoInfo.path));
|
||||||
const aheadBehind =
|
const aheadBehind =
|
||||||
baseRef && currentBranch ? await getAheadBehind(cwd, baseRef, currentBranch) : null;
|
baseRef && currentBranch ? await getAheadBehind(cwd, baseRef, currentBranch) : null;
|
||||||
|
const aheadOfOrigin =
|
||||||
|
hasRemote && currentBranch ? await getAheadOfOrigin(cwd, currentBranch) : null;
|
||||||
|
|
||||||
if (configured.isPaseoOwnedWorktree) {
|
if (configured.isPaseoOwnedWorktree) {
|
||||||
return {
|
return {
|
||||||
@@ -452,6 +472,7 @@ export async function getCheckoutStatus(
|
|||||||
isDirty,
|
isDirty,
|
||||||
baseRef: configured.baseRef,
|
baseRef: configured.baseRef,
|
||||||
aheadBehind,
|
aheadBehind,
|
||||||
|
aheadOfOrigin,
|
||||||
hasRemote,
|
hasRemote,
|
||||||
remoteUrl,
|
remoteUrl,
|
||||||
isPaseoOwnedWorktree: true,
|
isPaseoOwnedWorktree: true,
|
||||||
@@ -465,6 +486,7 @@ export async function getCheckoutStatus(
|
|||||||
isDirty,
|
isDirty,
|
||||||
baseRef,
|
baseRef,
|
||||||
aheadBehind,
|
aheadBehind,
|
||||||
|
aheadOfOrigin,
|
||||||
hasRemote,
|
hasRemote,
|
||||||
remoteUrl,
|
remoteUrl,
|
||||||
isPaseoOwnedWorktree: false,
|
isPaseoOwnedWorktree: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user