mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Update files
This commit is contained in:
@@ -192,6 +192,10 @@ export interface ProjectGroup {
|
||||
projectKey: string;
|
||||
projectName: string;
|
||||
agents: AggregatedAgent[];
|
||||
/** Number of truly active agents (running or requires attention) */
|
||||
activeCount: number;
|
||||
/** Total agents before any limit was applied */
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
export interface DateGroup {
|
||||
@@ -204,7 +208,7 @@ export interface GroupedAgents {
|
||||
inactiveGroups: DateGroup[];
|
||||
}
|
||||
|
||||
const ACTIVE_GRACE_PERIOD_MS = 15 * 60 * 1000; // 15 minutes
|
||||
const ACTIVE_GRACE_PERIOD_MS = 24 * 60 * 60 * 1000; // 24 hours
|
||||
|
||||
interface GroupAgentsOptions {
|
||||
/**
|
||||
@@ -214,9 +218,15 @@ interface GroupAgentsOptions {
|
||||
getRemoteUrl?: (agent: AggregatedAgent) => string | null;
|
||||
}
|
||||
|
||||
const MAX_INACTIVE_PER_PROJECT = 5;
|
||||
|
||||
/**
|
||||
* Groups agents into active (by project) and inactive (by date) sections.
|
||||
* Active = running, requires attention, or had activity within the last 5 minutes.
|
||||
* Active = running, requires attention, or had activity within the grace period (24 hours).
|
||||
*
|
||||
* Within each project group:
|
||||
* - All truly active agents (running/requires attention) are always shown
|
||||
* - Recently active (within grace period but not running) are limited to MAX_INACTIVE_PER_PROJECT
|
||||
*/
|
||||
export function groupAgents(
|
||||
agents: AggregatedAgent[],
|
||||
@@ -240,28 +250,61 @@ export function groupAgents(
|
||||
}
|
||||
}
|
||||
|
||||
// Group active agents by project
|
||||
const projectMap = new Map<string, AggregatedAgent[]>();
|
||||
// Group active agents by project, tracking truly active vs recently active
|
||||
const projectMap = new Map<
|
||||
string,
|
||||
{ trulyActive: AggregatedAgent[]; recentlyActive: AggregatedAgent[] }
|
||||
>();
|
||||
for (const agent of activeAgents) {
|
||||
const remoteKey = deriveRemoteProjectKey(
|
||||
options?.getRemoteUrl?.(agent) ?? null
|
||||
);
|
||||
const projectKey = remoteKey ?? deriveProjectKey(agent.cwd);
|
||||
const existing = projectMap.get(projectKey) || [];
|
||||
existing.push(agent);
|
||||
const existing = projectMap.get(projectKey) || {
|
||||
trulyActive: [],
|
||||
recentlyActive: [],
|
||||
};
|
||||
|
||||
const isTrulyActive =
|
||||
agent.status === "running" || agent.requiresAttention;
|
||||
if (isTrulyActive) {
|
||||
existing.trulyActive.push(agent);
|
||||
} else {
|
||||
existing.recentlyActive.push(agent);
|
||||
}
|
||||
|
||||
projectMap.set(projectKey, existing);
|
||||
}
|
||||
|
||||
// Sort agents within each project by lastActivityAt (newest first)
|
||||
// Build project groups with limits applied
|
||||
const activeGroups: ProjectGroup[] = [];
|
||||
for (const [projectKey, projectAgents] of projectMap) {
|
||||
projectAgents.sort(
|
||||
for (const [projectKey, { trulyActive, recentlyActive }] of projectMap) {
|
||||
// Sort both arrays by lastActivityAt (newest first)
|
||||
trulyActive.sort(
|
||||
(a, b) => b.lastActivityAt.getTime() - a.lastActivityAt.getTime()
|
||||
);
|
||||
recentlyActive.sort(
|
||||
(a, b) => b.lastActivityAt.getTime() - a.lastActivityAt.getTime()
|
||||
);
|
||||
|
||||
// All truly active agents shown, limit recently active to MAX_INACTIVE_PER_PROJECT
|
||||
const limitedRecentlyActive = recentlyActive.slice(
|
||||
0,
|
||||
MAX_INACTIVE_PER_PROJECT
|
||||
);
|
||||
const combinedAgents = [...trulyActive, ...limitedRecentlyActive];
|
||||
|
||||
// Re-sort combined list by lastActivityAt
|
||||
combinedAgents.sort(
|
||||
(a, b) => b.lastActivityAt.getTime() - a.lastActivityAt.getTime()
|
||||
);
|
||||
|
||||
activeGroups.push({
|
||||
projectKey,
|
||||
projectName: deriveProjectName(projectKey),
|
||||
agents: projectAgents,
|
||||
agents: combinedAgents,
|
||||
activeCount: trulyActive.length,
|
||||
totalCount: trulyActive.length + recentlyActive.length,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1046,6 +1046,10 @@ export class DaemonClientV2 {
|
||||
if (!requestId) {
|
||||
const existing = this.checkoutStatusInFlight.get(agentId);
|
||||
if (existing) {
|
||||
this.logger.debug(
|
||||
{ agentId, inFlightCount: this.checkoutStatusInFlight.size },
|
||||
"getCheckoutStatus: returning existing in-flight request"
|
||||
);
|
||||
return existing;
|
||||
}
|
||||
}
|
||||
@@ -1057,6 +1061,11 @@ export class DaemonClientV2 {
|
||||
requestId: resolvedRequestId,
|
||||
});
|
||||
|
||||
this.logger.debug(
|
||||
{ agentId, requestId: resolvedRequestId, waiterCount: this.waiters.size },
|
||||
"getCheckoutStatus: creating new request"
|
||||
);
|
||||
|
||||
const responsePromise = (async () => {
|
||||
const response = this.waitFor(
|
||||
(msg) => {
|
||||
@@ -1077,11 +1086,24 @@ export class DaemonClientV2 {
|
||||
|
||||
if (!requestId) {
|
||||
this.checkoutStatusInFlight.set(agentId, responsePromise);
|
||||
responsePromise.finally(() => {
|
||||
if (this.checkoutStatusInFlight.get(agentId) === responsePromise) {
|
||||
this.checkoutStatusInFlight.delete(agentId);
|
||||
}
|
||||
});
|
||||
responsePromise
|
||||
.then(() => {
|
||||
this.logger.debug(
|
||||
{ agentId, requestId: resolvedRequestId },
|
||||
"getCheckoutStatus: request completed successfully"
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.debug(
|
||||
{ agentId, requestId: resolvedRequestId, error: err?.message },
|
||||
"getCheckoutStatus: request failed"
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
if (this.checkoutStatusInFlight.get(agentId) === responsePromise) {
|
||||
this.checkoutStatusInFlight.delete(agentId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return responsePromise;
|
||||
@@ -2099,6 +2121,10 @@ export class DaemonClientV2 {
|
||||
this.lastErrorValue = reason.trim();
|
||||
}
|
||||
|
||||
// Clear all pending waiters since the connection was lost and responses
|
||||
// from the previous connection will never arrive.
|
||||
this.clearWaiters(new Error(reason ?? "Connection lost"));
|
||||
|
||||
this.updateConnectionState({
|
||||
status: "disconnected",
|
||||
...(reason ? { reason } : {}),
|
||||
|
||||
168
packages/server/src/server/daemon-e2e/checkout-debug.ts
Normal file
168
packages/server/src/server/daemon-e2e/checkout-debug.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
/**
|
||||
* Ad-hoc script to debug checkout_status_request timeouts.
|
||||
*
|
||||
* Usage:
|
||||
* npx tsx packages/server/src/server/daemon-e2e/checkout-debug.ts [agentId1] [agentId2]
|
||||
*
|
||||
* To test against a different daemon:
|
||||
* PASEO_PORT=7777 npx tsx packages/server/src/server/daemon-e2e/checkout-debug.ts
|
||||
*/
|
||||
|
||||
import { WebSocket } from "ws";
|
||||
import { DaemonClientV2 } from "../../client/daemon-client-v2.js";
|
||||
|
||||
// Patch WebSocket to log all messages
|
||||
const OriginalWebSocket = WebSocket;
|
||||
class LoggingWebSocket extends OriginalWebSocket {
|
||||
constructor(url: string, ...args: any[]) {
|
||||
super(url, ...args);
|
||||
console.log(`[WS] Connecting to ${url}`);
|
||||
this.on("open", () => console.log("[WS] Connection opened"));
|
||||
this.on("close", (code, reason) => console.log(`[WS] Connection closed: ${code} ${reason}`));
|
||||
this.on("error", (err) => console.log(`[WS] Error: ${err}`));
|
||||
this.on("message", (data) => {
|
||||
const str = data.toString().slice(0, 200);
|
||||
console.log(`[WS] Message received (${data.toString().length} bytes): ${str}...`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const PASEO_HOME = process.env.PASEO_HOME ?? "/Users/moboudra/.paseo";
|
||||
const PASEO_PORT = process.env.PASEO_PORT ?? "6767";
|
||||
const DAEMON_URL = `ws://127.0.0.1:${PASEO_PORT}/ws`;
|
||||
|
||||
async function testMultiAgentSequence() {
|
||||
console.log("\n=== Testing multi-agent checkout sequence ===");
|
||||
console.log(`Daemon URL: ${DAEMON_URL}`);
|
||||
|
||||
const client = new DaemonClientV2({
|
||||
url: DAEMON_URL,
|
||||
webSocketFactory: (url) => new LoggingWebSocket(url) as any,
|
||||
reconnect: { enabled: false },
|
||||
});
|
||||
|
||||
const agents: Array<{ id: string; title: string }> = [];
|
||||
|
||||
// Subscribe to all events for debugging
|
||||
const unsub = client.subscribe((event) => {
|
||||
console.log(`[Event] type=${event.type}`);
|
||||
if (event.type === "session_state") {
|
||||
console.log(` ${event.agents.length} agents`);
|
||||
agents.length = 0;
|
||||
for (const a of event.agents) {
|
||||
agents.push({ id: a.id, title: a.title ?? "(untitled)" });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Also log ALL raw messages
|
||||
client.on("session_state", (msg: any) => {
|
||||
console.log(`[RAW session_state] agents=${msg.agents?.length}`);
|
||||
});
|
||||
|
||||
// Also log raw messages for debugging
|
||||
client.on("checkout_status_response", (msg: any) => {
|
||||
console.log(`[RAW checkout_status_response] requestId=${msg.payload.requestId} agentId=${msg.payload.agentId}`);
|
||||
});
|
||||
|
||||
// Listen to connection state changes
|
||||
client.subscribeConnectionStatus((state) => {
|
||||
console.log(`[Connection] status=${state.status}`);
|
||||
});
|
||||
|
||||
try {
|
||||
await client.connect();
|
||||
console.log("Connected to daemon");
|
||||
console.log(`Connection state: ${JSON.stringify(client.getConnectionState())}`);
|
||||
|
||||
// Request session state (the app does this after connecting)
|
||||
console.log("Requesting session state...");
|
||||
client.requestSessionState();
|
||||
|
||||
// Wait a bit for session state to arrive
|
||||
console.log("Waiting 3s for session state...");
|
||||
await new Promise((r) => setTimeout(r, 3000));
|
||||
|
||||
if (agents.length === 0) {
|
||||
console.log("No agents found!");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("\nAvailable agents:");
|
||||
for (const a of agents.slice(0, 10)) {
|
||||
console.log(` - ${a.id.slice(0, 8)}... ${a.title}`);
|
||||
}
|
||||
if (agents.length > 10) {
|
||||
console.log(` ... and ${agents.length - 10} more`);
|
||||
}
|
||||
|
||||
// Pick first two agents (or use command line args)
|
||||
const agent1Id = process.argv[2] ?? agents[0]?.id;
|
||||
const agent2Id = process.argv[3] ?? agents[1]?.id ?? agents[0]?.id;
|
||||
|
||||
if (!agent1Id) {
|
||||
console.log("No agents available to test");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\n=== Test 1: Request checkout for agent1 (${agent1Id.slice(0, 8)}...) ===`);
|
||||
const start1 = Date.now();
|
||||
try {
|
||||
const status1 = await client.getCheckoutStatus(agent1Id);
|
||||
console.log(`✓ Agent1 completed in ${Date.now() - start1}ms - branch: ${status1.currentBranch}`);
|
||||
} catch (err) {
|
||||
console.log(`✗ Agent1 failed after ${Date.now() - start1}ms:`, err);
|
||||
}
|
||||
|
||||
console.log(`\n=== Test 2: Request checkout for agent2 (${agent2Id.slice(0, 8)}...) ===`);
|
||||
const start2 = Date.now();
|
||||
try {
|
||||
const status2 = await client.getCheckoutStatus(agent2Id);
|
||||
console.log(`✓ Agent2 completed in ${Date.now() - start2}ms - branch: ${status2.currentBranch}`);
|
||||
} catch (err) {
|
||||
console.log(`✗ Agent2 failed after ${Date.now() - start2}ms:`, err);
|
||||
}
|
||||
|
||||
console.log(`\n=== Test 3: Request checkout for agent1 again ===`);
|
||||
const start3 = Date.now();
|
||||
try {
|
||||
const status3 = await client.getCheckoutStatus(agent1Id);
|
||||
console.log(`✓ Agent1 (retry) completed in ${Date.now() - start3}ms - branch: ${status3.currentBranch}`);
|
||||
} catch (err) {
|
||||
console.log(`✗ Agent1 (retry) failed after ${Date.now() - start3}ms:`, err);
|
||||
}
|
||||
|
||||
console.log(`\n=== Test 4: Request both agents in parallel ===`);
|
||||
const start4 = Date.now();
|
||||
try {
|
||||
const [p1, p2] = await Promise.all([
|
||||
client.getCheckoutStatus(agent1Id),
|
||||
client.getCheckoutStatus(agent2Id),
|
||||
]);
|
||||
console.log(`✓ Parallel completed in ${Date.now() - start4}ms`);
|
||||
console.log(` Agent1 branch: ${p1.currentBranch}`);
|
||||
console.log(` Agent2 branch: ${p2.currentBranch}`);
|
||||
} catch (err) {
|
||||
console.log(`✗ Parallel failed after ${Date.now() - start4}ms:`, err);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Test failed:", error);
|
||||
} finally {
|
||||
unsub();
|
||||
await client.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("Checkout Debug Script - Multi-Agent Sequence Test");
|
||||
console.log("==================================================");
|
||||
console.log(`PASEO_HOME: ${PASEO_HOME}`);
|
||||
|
||||
await testMultiAgentSequence();
|
||||
|
||||
console.log("\n=== Done ===");
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
Reference in New Issue
Block a user