Update files
This commit is contained in:
@@ -521,8 +521,9 @@ export function AgentStreamView({
|
|||||||
});
|
});
|
||||||
}, [agentId, pendingPermissionItems.length, streamHead, streamItems]);
|
}, [agentId, pendingPermissionItems.length, streamHead, streamItems]);
|
||||||
|
|
||||||
|
const showSyncingIndicator = isSyncingHistory;
|
||||||
const showWorkingIndicator = agent.status === "running";
|
const showWorkingIndicator = agent.status === "running";
|
||||||
const showBottomBar = showWorkingIndicator || isVoiceMode;
|
const showBottomBar = showSyncingIndicator || showWorkingIndicator || isVoiceMode;
|
||||||
|
|
||||||
const listHeaderComponent = useMemo(() => {
|
const listHeaderComponent = useMemo(() => {
|
||||||
const hasPermissions = pendingPermissionItems.length > 0;
|
const hasPermissions = pendingPermissionItems.length > 0;
|
||||||
@@ -532,7 +533,11 @@ export function AgentStreamView({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const leftContent = showWorkingIndicator ? <WorkingIndicator /> : null;
|
const leftContent = showSyncingIndicator
|
||||||
|
? <SyncingIndicator />
|
||||||
|
: showWorkingIndicator
|
||||||
|
? <WorkingIndicator />
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={stylesheet.contentWrapper}>
|
<View style={stylesheet.contentWrapper}>
|
||||||
@@ -582,6 +587,7 @@ export function AgentStreamView({
|
|||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
pendingPermissionItems,
|
pendingPermissionItems,
|
||||||
|
showSyncingIndicator,
|
||||||
showWorkingIndicator,
|
showWorkingIndicator,
|
||||||
client,
|
client,
|
||||||
streamHead,
|
streamHead,
|
||||||
@@ -864,6 +870,15 @@ function WorkingIndicator() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SyncingIndicator() {
|
||||||
|
return (
|
||||||
|
<View style={stylesheet.syncingIndicator}>
|
||||||
|
<ActivityIndicator size="small" color={stylesheet.syncingIndicatorText.color} />
|
||||||
|
<Text style={stylesheet.syncingIndicatorText}>Catching up…</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Permission Request Card Component
|
// Permission Request Card Component
|
||||||
function PermissionRequestCard({
|
function PermissionRequestCard({
|
||||||
permission,
|
permission,
|
||||||
@@ -1298,6 +1313,17 @@ const stylesheet = StyleSheet.create((theme) => ({
|
|||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
backgroundColor: theme.colors.foregroundMuted,
|
backgroundColor: theme.colors.foregroundMuted,
|
||||||
},
|
},
|
||||||
|
syncingIndicator: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: theme.spacing[2],
|
||||||
|
paddingVertical: theme.spacing[1],
|
||||||
|
paddingLeft: theme.spacing[2],
|
||||||
|
},
|
||||||
|
syncingIndicatorText: {
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
fontSize: theme.fontSize.sm,
|
||||||
|
},
|
||||||
invertedWrapper: {
|
invertedWrapper: {
|
||||||
transform: [{ scaleY: -1 }],
|
transform: [{ scaleY: -1 }],
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
|||||||
@@ -188,6 +188,14 @@ type FileDownloadTokenPayload = Extract<
|
|||||||
{ type: "file_download_token_response" }
|
{ type: "file_download_token_response" }
|
||||||
>["payload"];
|
>["payload"];
|
||||||
|
|
||||||
|
type AgentUpdatePayload = Extract<
|
||||||
|
SessionOutboundMessage,
|
||||||
|
{ type: "agent_update" }
|
||||||
|
>["payload"];
|
||||||
|
|
||||||
|
const getAgentIdFromUpdate = (update: AgentUpdatePayload): string =>
|
||||||
|
update.kind === "remove" ? update.agentId : update.agent.id;
|
||||||
|
|
||||||
function normalizeAgentSnapshot(
|
function normalizeAgentSnapshot(
|
||||||
snapshot: AgentSnapshotPayload,
|
snapshot: AgentSnapshotPayload,
|
||||||
serverId: string
|
serverId: string
|
||||||
@@ -310,7 +318,6 @@ export function SessionProvider({
|
|||||||
const setFileExplorer = useSessionStore((state) => state.setFileExplorer);
|
const setFileExplorer = useSessionStore((state) => state.setFileExplorer);
|
||||||
const clearDraftInput = useDraftStore((state) => state.clearDraftInput);
|
const clearDraftInput = useDraftStore((state) => state.clearDraftInput);
|
||||||
const setQueuedMessages = useSessionStore((state) => state.setQueuedMessages);
|
const setQueuedMessages = useSessionStore((state) => state.setQueuedMessages);
|
||||||
const getSession = useSessionStore((state) => state.getSession);
|
|
||||||
const updateSessionClient = useSessionStore((state) => state.updateSessionClient);
|
const updateSessionClient = useSessionStore((state) => state.updateSessionClient);
|
||||||
const updateSessionConnection = useSessionStore(
|
const updateSessionConnection = useSessionStore(
|
||||||
(state) => state.updateSessionConnection
|
(state) => state.updateSessionConnection
|
||||||
@@ -354,6 +361,9 @@ export function SessionProvider({
|
|||||||
);
|
);
|
||||||
const attentionNotifiedRef = useRef<Map<string, number>>(new Map());
|
const attentionNotifiedRef = useRef<Map<string, number>>(new Map());
|
||||||
const appStateRef = useRef(AppState.currentState);
|
const appStateRef = useRef(AppState.currentState);
|
||||||
|
const pendingAgentUpdatesRef = useRef<Map<string, AgentUpdatePayload>>(
|
||||||
|
new Map()
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = AppState.addEventListener("change", (nextState) => {
|
const subscription = AppState.addEventListener("change", (nextState) => {
|
||||||
@@ -499,10 +509,115 @@ export function SessionProvider({
|
|||||||
// If the client drops mid-initialization, clear pending flags
|
// If the client drops mid-initialization, clear pending flags
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!connectionSnapshot.isConnected) {
|
if (!connectionSnapshot.isConnected) {
|
||||||
|
pendingAgentUpdatesRef.current.clear();
|
||||||
setInitializingAgents(serverId, new Map());
|
setInitializingAgents(serverId, new Map());
|
||||||
}
|
}
|
||||||
}, [serverId, connectionSnapshot.isConnected, setInitializingAgents]);
|
}, [serverId, connectionSnapshot.isConnected, setInitializingAgents]);
|
||||||
|
|
||||||
|
const applyAgentUpdatePayload = useCallback(
|
||||||
|
(update: AgentUpdatePayload) => {
|
||||||
|
if (update.kind === "remove") {
|
||||||
|
const agentId = update.agentId;
|
||||||
|
previousAgentStatusRef.current.delete(agentId);
|
||||||
|
pendingAgentUpdatesRef.current.delete(agentId);
|
||||||
|
|
||||||
|
setAgents(serverId, (prev) => {
|
||||||
|
if (!prev.has(agentId)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
const next = new Map(prev);
|
||||||
|
next.delete(agentId);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
setPendingPermissions(serverId, (prev) => {
|
||||||
|
if (prev.size === 0) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
let changed = false;
|
||||||
|
const next = new Map(prev);
|
||||||
|
for (const [key, pending] of Array.from(next.entries())) {
|
||||||
|
if (pending.agentId === agentId) {
|
||||||
|
next.delete(key);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed ? next : prev;
|
||||||
|
});
|
||||||
|
|
||||||
|
setQueuedMessages(serverId, (prev) => {
|
||||||
|
if (!prev.has(agentId)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
const next = new Map(prev);
|
||||||
|
next.delete(agentId);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const agent = normalizeAgentSnapshot(update.agent, serverId);
|
||||||
|
|
||||||
|
console.log("[Session] Agent update:", agent.id, agent.status);
|
||||||
|
|
||||||
|
setAgents(serverId, (prev) => {
|
||||||
|
const next = new Map(prev);
|
||||||
|
next.set(agent.id, agent);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update agentLastActivity slice (top-level)
|
||||||
|
setAgentLastActivity(agent.id, agent.lastActivityAt);
|
||||||
|
|
||||||
|
setPendingPermissions(serverId, (prev) => {
|
||||||
|
const next = new Map(prev);
|
||||||
|
for (const [key, pending] of Array.from(next.entries())) {
|
||||||
|
if (pending.agentId === agent.id) {
|
||||||
|
next.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const request of agent.pendingPermissions) {
|
||||||
|
const key = derivePendingPermissionKey(agent.id, request);
|
||||||
|
next.set(key, { key, agentId: agent.id, request });
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Flush queued messages when agent transitions from running to not running
|
||||||
|
const prevStatus = previousAgentStatusRef.current.get(agent.id);
|
||||||
|
if (prevStatus === "running" && agent.status !== "running") {
|
||||||
|
const session = useSessionStore.getState().sessions[serverId];
|
||||||
|
const queue = session?.queuedMessages.get(agent.id);
|
||||||
|
if (queue && queue.length > 0) {
|
||||||
|
const [next, ...rest] = queue;
|
||||||
|
console.log(
|
||||||
|
"[Session] Flushing queued message for agent:",
|
||||||
|
agent.id,
|
||||||
|
next.text
|
||||||
|
);
|
||||||
|
if (sendAgentMessageRef.current) {
|
||||||
|
void sendAgentMessageRef.current(agent.id, next.text, next.images);
|
||||||
|
}
|
||||||
|
setQueuedMessages(serverId, (prev) => {
|
||||||
|
const updated = new Map(prev);
|
||||||
|
updated.set(agent.id, rest);
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousAgentStatusRef.current.set(agent.id, agent.status);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
serverId,
|
||||||
|
setAgents,
|
||||||
|
setAgentLastActivity,
|
||||||
|
setPendingPermissions,
|
||||||
|
setQueuedMessages,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
updateConnectionStatus(serverId, { status: "offline", activeConnection, lastError: null });
|
updateConnectionStatus(serverId, { status: "offline", activeConnection, lastError: null });
|
||||||
@@ -774,97 +889,20 @@ export function SessionProvider({
|
|||||||
const unsubAgentUpdate = client.on("agent_update", (message) => {
|
const unsubAgentUpdate = client.on("agent_update", (message) => {
|
||||||
if (message.type !== "agent_update") return;
|
if (message.type !== "agent_update") return;
|
||||||
const update = message.payload;
|
const update = message.payload;
|
||||||
|
const agentId = getAgentIdFromUpdate(update);
|
||||||
|
const initKey = getInitKey(serverId, agentId);
|
||||||
|
const session = useSessionStore.getState().sessions[serverId];
|
||||||
|
const isSyncingHistory =
|
||||||
|
session?.initializingAgents.get(agentId) === true &&
|
||||||
|
Boolean(getInitDeferred(initKey));
|
||||||
|
|
||||||
if (update.kind === "remove") {
|
if (isSyncingHistory) {
|
||||||
const agentId = update.agentId;
|
pendingAgentUpdatesRef.current.set(agentId, update);
|
||||||
previousAgentStatusRef.current.delete(agentId);
|
|
||||||
|
|
||||||
setAgents(serverId, (prev) => {
|
|
||||||
if (!prev.has(agentId)) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
const next = new Map(prev);
|
|
||||||
next.delete(agentId);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
|
|
||||||
setPendingPermissions(serverId, (prev) => {
|
|
||||||
if (prev.size === 0) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
let changed = false;
|
|
||||||
const next = new Map(prev);
|
|
||||||
for (const [key, pending] of Array.from(next.entries())) {
|
|
||||||
if (pending.agentId === agentId) {
|
|
||||||
next.delete(key);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return changed ? next : prev;
|
|
||||||
});
|
|
||||||
|
|
||||||
setQueuedMessages(serverId, (prev) => {
|
|
||||||
if (!prev.has(agentId)) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
const next = new Map(prev);
|
|
||||||
next.delete(agentId);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const agent = normalizeAgentSnapshot(update.agent, serverId);
|
pendingAgentUpdatesRef.current.delete(agentId);
|
||||||
|
applyAgentUpdatePayload(update);
|
||||||
console.log("[Session] Agent update:", agent.id, agent.status);
|
|
||||||
|
|
||||||
setAgents(serverId, (prev) => {
|
|
||||||
const next = new Map(prev);
|
|
||||||
next.set(agent.id, agent);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update agentLastActivity slice (top-level)
|
|
||||||
setAgentLastActivity(agent.id, agent.lastActivityAt);
|
|
||||||
|
|
||||||
setPendingPermissions(serverId, (prev) => {
|
|
||||||
const next = new Map(prev);
|
|
||||||
for (const [key, pending] of Array.from(next.entries())) {
|
|
||||||
if (pending.agentId === agent.id) {
|
|
||||||
next.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const request of agent.pendingPermissions) {
|
|
||||||
const key = derivePendingPermissionKey(agent.id, request);
|
|
||||||
next.set(key, { key, agentId: agent.id, request });
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Flush queued messages when agent transitions from running to not running
|
|
||||||
const prevStatus = previousAgentStatusRef.current.get(agent.id);
|
|
||||||
if (prevStatus === "running" && agent.status !== "running") {
|
|
||||||
const session = useSessionStore.getState().sessions[serverId];
|
|
||||||
const queue = session?.queuedMessages.get(agent.id);
|
|
||||||
if (queue && queue.length > 0) {
|
|
||||||
const [next, ...rest] = queue;
|
|
||||||
console.log(
|
|
||||||
"[Session] Flushing queued message for agent:",
|
|
||||||
agent.id,
|
|
||||||
next.text
|
|
||||||
);
|
|
||||||
if (sendAgentMessageRef.current) {
|
|
||||||
void sendAgentMessageRef.current(agent.id, next.text, next.images);
|
|
||||||
}
|
|
||||||
setQueuedMessages(serverId, (prev) => {
|
|
||||||
const updated = new Map(prev);
|
|
||||||
updated.set(agent.id, rest);
|
|
||||||
return updated;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
previousAgentStatusRef.current.set(agent.id, agent.status);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const unsubAgentStream = client.on("agent_stream", (message) => {
|
const unsubAgentStream = client.on("agent_stream", (message) => {
|
||||||
@@ -929,6 +967,8 @@ export function SessionProvider({
|
|||||||
timestamp: new Date(timestamp),
|
timestamp: new Date(timestamp),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
const initKey = getInitKey(serverId, agentId);
|
||||||
|
const hasInFlightHistorySync = Boolean(getInitDeferred(initKey));
|
||||||
|
|
||||||
setAgentStreamTail(serverId, (prev) => {
|
setAgentStreamTail(serverId, (prev) => {
|
||||||
const next = new Map(prev);
|
const next = new Map(prev);
|
||||||
@@ -937,6 +977,12 @@ export function SessionProvider({
|
|||||||
});
|
});
|
||||||
clearAgentStreamHead(serverId, agentId);
|
clearAgentStreamHead(serverId, agentId);
|
||||||
|
|
||||||
|
const deferredUpdate = pendingAgentUpdatesRef.current.get(agentId);
|
||||||
|
pendingAgentUpdatesRef.current.delete(agentId);
|
||||||
|
if (hasInFlightHistorySync && deferredUpdate) {
|
||||||
|
applyAgentUpdatePayload(deferredUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
setInitializingAgents(serverId, (prev) => {
|
setInitializingAgents(serverId, (prev) => {
|
||||||
if (prev.get(agentId) !== true) {
|
if (prev.get(agentId) !== true) {
|
||||||
return prev;
|
return prev;
|
||||||
@@ -947,7 +993,6 @@ export function SessionProvider({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Resolve the initialization promise (even for empty history)
|
// Resolve the initialization promise (even for empty history)
|
||||||
const initKey = getInitKey(serverId, agentId);
|
|
||||||
resolveInitDeferred(initKey);
|
resolveInitDeferred(initKey);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -1258,6 +1303,7 @@ export function SessionProvider({
|
|||||||
}
|
}
|
||||||
const { agentId } = message.payload;
|
const { agentId } = message.payload;
|
||||||
console.log("[Session] Agent deleted:", agentId);
|
console.log("[Session] Agent deleted:", agentId);
|
||||||
|
pendingAgentUpdatesRef.current.delete(agentId);
|
||||||
|
|
||||||
setAgents(serverId, (prev) => {
|
setAgents(serverId, (prev) => {
|
||||||
if (!prev.has(agentId)) {
|
if (!prev.has(agentId)) {
|
||||||
@@ -1387,9 +1433,9 @@ export function SessionProvider({
|
|||||||
setFileExplorer,
|
setFileExplorer,
|
||||||
setHasHydratedAgents,
|
setHasHydratedAgents,
|
||||||
updateConnectionStatus,
|
updateConnectionStatus,
|
||||||
getSession,
|
|
||||||
clearDraftInput,
|
clearDraftInput,
|
||||||
notifyAgentAttention,
|
notifyAgentAttention,
|
||||||
|
applyAgentUpdatePayload,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const initializeAgent = useCallback(
|
const initializeAgent = useCallback(
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ import { deriveBranchLabel, deriveProjectPath } from "@/utils/agent-display-info
|
|||||||
import { useCheckoutStatusQuery } from "@/hooks/use-checkout-status-query";
|
import { useCheckoutStatusQuery } from "@/hooks/use-checkout-status-query";
|
||||||
import { useAgentInitialization } from "@/hooks/use-agent-initialization";
|
import { useAgentInitialization } from "@/hooks/use-agent-initialization";
|
||||||
import { useToast } from "@/contexts/toast-context";
|
import { useToast } from "@/contexts/toast-context";
|
||||||
|
import { getInitDeferred, getInitKey } from "@/utils/agent-initialization";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -401,6 +402,13 @@ function AgentScreenContent({
|
|||||||
}, [resolvedAgentId, setFocusedAgentId]);
|
}, [resolvedAgentId, setFocusedAgentId]);
|
||||||
|
|
||||||
const isInitializing = resolvedAgentId ? isInitializingFromMap !== false : false;
|
const isInitializing = resolvedAgentId ? isInitializingFromMap !== false : false;
|
||||||
|
const isHistorySyncing = useMemo(() => {
|
||||||
|
if (!resolvedAgentId || !isInitializing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const initKey = getInitKey(serverId, resolvedAgentId);
|
||||||
|
return Boolean(getInitDeferred(initKey));
|
||||||
|
}, [resolvedAgentId, isInitializing, serverId]);
|
||||||
|
|
||||||
const optimisticStreamItems = useMemo<StreamItem[]>(() => {
|
const optimisticStreamItems = useMemo<StreamItem[]>(() => {
|
||||||
if (!isPendingCreateForRoute || !pendingCreate) {
|
if (!isPendingCreateForRoute || !pendingCreate) {
|
||||||
@@ -840,7 +848,7 @@ function AgentScreenContent({
|
|||||||
shouldUseOptimisticStream ? mergedStreamItems : streamItems
|
shouldUseOptimisticStream ? mergedStreamItems : streamItems
|
||||||
}
|
}
|
||||||
pendingPermissions={pendingPermissions}
|
pendingPermissions={pendingPermissions}
|
||||||
isSyncingHistory={isInitializing && !shouldUseOptimisticStream}
|
isSyncingHistory={isHistorySyncing && !shouldUseOptimisticStream}
|
||||||
/>
|
/>
|
||||||
</ReanimatedAnimated.View>
|
</ReanimatedAnimated.View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
#!/usr/bin/env npx tsx
|
||||||
|
/**
|
||||||
|
* Ad-hoc checkout diff debugger.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* npx tsx packages/server/src/server/daemon-e2e/checkout-diff-debug.ts
|
||||||
|
* npx tsx packages/server/src/server/daemon-e2e/checkout-diff-debug.ts --agent <agentId>
|
||||||
|
* npx tsx packages/server/src/server/daemon-e2e/checkout-diff-debug.ts --cwd <path>
|
||||||
|
* npx tsx packages/server/src/server/daemon-e2e/checkout-diff-debug.ts --limit 3
|
||||||
|
*
|
||||||
|
* Optional env:
|
||||||
|
* PASEO_LISTEN=127.0.0.1:6767
|
||||||
|
*/
|
||||||
|
|
||||||
|
import os from "node:os";
|
||||||
|
import { DaemonClient } from "../../client/daemon-client.js";
|
||||||
|
|
||||||
|
type CliArgs = {
|
||||||
|
agentId?: string;
|
||||||
|
cwd?: string;
|
||||||
|
limit: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseArgs(argv: string[]): CliArgs {
|
||||||
|
const args: CliArgs = { limit: 5 };
|
||||||
|
for (let i = 0; i < argv.length; i += 1) {
|
||||||
|
const token = argv[i];
|
||||||
|
if (token === "--agent") {
|
||||||
|
const value = argv[i + 1];
|
||||||
|
if (value) {
|
||||||
|
args.agentId = value;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (token === "--cwd") {
|
||||||
|
const value = argv[i + 1];
|
||||||
|
if (value) {
|
||||||
|
args.cwd = value;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (token === "--limit") {
|
||||||
|
const value = Number.parseInt(argv[i + 1] ?? "", 10);
|
||||||
|
if (!Number.isNaN(value) && value > 0) {
|
||||||
|
args.limit = value;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtMs(ms: number): string {
|
||||||
|
return `${ms.toLocaleString()}ms`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const args = parseArgs(process.argv.slice(2));
|
||||||
|
const listen = process.env.PASEO_LISTEN ?? "127.0.0.1:6767";
|
||||||
|
const url = `ws://${listen}/ws`;
|
||||||
|
const home = process.env.PASEO_HOME ?? `${os.homedir()}/.paseo`;
|
||||||
|
|
||||||
|
console.log("Checkout Diff Debugger");
|
||||||
|
console.log(`daemon=${url}`);
|
||||||
|
console.log(`PASEO_HOME=${home}`);
|
||||||
|
console.log(
|
||||||
|
`filters agent=${args.agentId ?? "-"} cwd=${args.cwd ?? "-"} limit=${args.limit}`
|
||||||
|
);
|
||||||
|
console.log("");
|
||||||
|
|
||||||
|
const client = new DaemonClient({
|
||||||
|
url,
|
||||||
|
reconnect: { enabled: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("checkout_status_response", (message) => {
|
||||||
|
if (message.type !== "checkout_status_response") return;
|
||||||
|
const payload = message.payload;
|
||||||
|
console.log(
|
||||||
|
`[raw] checkout_status_response requestId=${payload.requestId} cwd=${payload.cwd} isGit=${payload.isGit}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("checkout_diff_response", (message) => {
|
||||||
|
if (message.type !== "checkout_diff_response") return;
|
||||||
|
const payload = message.payload;
|
||||||
|
console.log(
|
||||||
|
`[raw] checkout_diff_response requestId=${payload.requestId} cwd=${payload.cwd} files=${payload.files.length} error=${payload.error ? "yes" : "no"}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("rpc_error", (message) => {
|
||||||
|
if (message.type !== "rpc_error") return;
|
||||||
|
const payload = message.payload;
|
||||||
|
console.log(
|
||||||
|
`[raw] rpc_error requestId=${payload.requestId} requestType=${payload.requestType} code=${payload.code ?? "none"}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.connect();
|
||||||
|
const ping = await client.ping({ timeoutMs: 3000 });
|
||||||
|
console.log(`ping=${fmtMs(ping.rttMs)}`);
|
||||||
|
|
||||||
|
const snapshots = await client.fetchAgents({ filter: { labels: { ui: "true" } } });
|
||||||
|
const candidates = snapshots
|
||||||
|
.filter((snapshot) => !args.agentId || snapshot.id === args.agentId)
|
||||||
|
.map((snapshot) => ({
|
||||||
|
id: snapshot.id,
|
||||||
|
title: snapshot.title ?? "(untitled)",
|
||||||
|
cwd: snapshot.cwd,
|
||||||
|
updatedAt: snapshot.updatedAt,
|
||||||
|
}))
|
||||||
|
.filter((item) => !args.cwd || item.cwd === args.cwd)
|
||||||
|
.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1));
|
||||||
|
|
||||||
|
const targets = (args.cwd
|
||||||
|
? [{ id: "(manual)", title: "(manual)", cwd: args.cwd, updatedAt: new Date().toISOString() }]
|
||||||
|
: candidates
|
||||||
|
)
|
||||||
|
.slice(0, args.limit)
|
||||||
|
.filter((item, index, list) => list.findIndex((v) => v.cwd === item.cwd) === index);
|
||||||
|
|
||||||
|
if (targets.length === 0) {
|
||||||
|
console.log("No matching agents/cwds found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Testing ${targets.length} cwd target(s)\n`);
|
||||||
|
|
||||||
|
for (const target of targets) {
|
||||||
|
console.log(`--- ${target.cwd}`);
|
||||||
|
console.log(`agent=${target.id} title=${target.title}`);
|
||||||
|
|
||||||
|
const statusStart = Date.now();
|
||||||
|
let statusPayload: Awaited<ReturnType<typeof client.getCheckoutStatus>>;
|
||||||
|
try {
|
||||||
|
statusPayload = await client.getCheckoutStatus(target.cwd);
|
||||||
|
console.log(
|
||||||
|
`status: ok ${fmtMs(Date.now() - statusStart)} isGit=${statusPayload.isGit} branch=${statusPayload.currentBranch ?? "-"} dirty=${statusPayload.isDirty ?? "-"} baseRef=${statusPayload.baseRef ?? "-"}`
|
||||||
|
);
|
||||||
|
if (statusPayload.error) {
|
||||||
|
console.log(`status.error=${statusPayload.error.message}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`status: FAIL ${fmtMs(Date.now() - statusStart)} ${String(error)}`);
|
||||||
|
console.log("");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!statusPayload.isGit) {
|
||||||
|
console.log("diff: skipped (not a git repo)\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const compareMode = statusPayload.isDirty ? "uncommitted" : "base";
|
||||||
|
const diffStart = Date.now();
|
||||||
|
try {
|
||||||
|
const diff = await client.getCheckoutDiff(target.cwd, {
|
||||||
|
mode: compareMode,
|
||||||
|
baseRef: statusPayload.baseRef ?? undefined,
|
||||||
|
});
|
||||||
|
const diffDuration = Date.now() - diffStart;
|
||||||
|
console.log(
|
||||||
|
`diff: ok ${fmtMs(diffDuration)} mode=${compareMode} files=${diff.files.length} error=${diff.error ? "yes" : "no"}`
|
||||||
|
);
|
||||||
|
if (diff.error) {
|
||||||
|
console.log(`diff.error=${diff.error.message}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`diff: FAIL ${fmtMs(Date.now() - diffStart)} ${String(error)}`);
|
||||||
|
}
|
||||||
|
console.log("");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main().catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
|
|
||||||
Reference in New Issue
Block a user