chore: sync all local changes
This commit is contained in:
@@ -7,7 +7,7 @@ import type {
|
||||
|
||||
import { ClaudeAgentClient } from "./providers/claude-agent.js";
|
||||
import { CodexMcpAgentClient } from "./providers/codex-mcp-agent.js";
|
||||
import { OpenCodeAgentClient } from "./providers/opencode-agent.js";
|
||||
import { OpenCodeAgentClient, OpenCodeServerManager } from "./providers/opencode-agent.js";
|
||||
|
||||
import {
|
||||
AGENT_PROVIDER_DEFINITIONS,
|
||||
@@ -78,3 +78,7 @@ export async function fetchProviderModels(
|
||||
const definition = getProviderDefinition(provider);
|
||||
return definition.fetchModels(options);
|
||||
}
|
||||
|
||||
export async function shutdownProviders(): Promise<void> {
|
||||
await OpenCodeServerManager.getInstance().shutdown();
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ async function findAvailablePort(): Promise<number> {
|
||||
|
||||
export class OpenCodeServerManager {
|
||||
private static instance: OpenCodeServerManager | null = null;
|
||||
private static exitHandlerRegistered = false;
|
||||
private server: ChildProcess | null = null;
|
||||
private port: number | null = null;
|
||||
private startPromise: Promise<{ port: number; url: string }> | null = null;
|
||||
@@ -69,10 +70,29 @@ export class OpenCodeServerManager {
|
||||
static getInstance(): OpenCodeServerManager {
|
||||
if (!OpenCodeServerManager.instance) {
|
||||
OpenCodeServerManager.instance = new OpenCodeServerManager();
|
||||
OpenCodeServerManager.registerExitHandler();
|
||||
}
|
||||
return OpenCodeServerManager.instance;
|
||||
}
|
||||
|
||||
private static registerExitHandler(): void {
|
||||
if (OpenCodeServerManager.exitHandlerRegistered) {
|
||||
return;
|
||||
}
|
||||
OpenCodeServerManager.exitHandlerRegistered = true;
|
||||
|
||||
const cleanup = () => {
|
||||
const instance = OpenCodeServerManager.instance;
|
||||
if (instance?.server && !instance.server.killed) {
|
||||
instance.server.kill("SIGTERM");
|
||||
}
|
||||
};
|
||||
|
||||
process.on("exit", cleanup);
|
||||
process.on("SIGTERM", cleanup);
|
||||
process.on("SIGINT", cleanup);
|
||||
}
|
||||
|
||||
async ensureRunning(): Promise<{ port: number; url: string }> {
|
||||
if (this.startPromise) {
|
||||
return this.startPromise;
|
||||
|
||||
@@ -47,7 +47,7 @@ import { AgentRegistry } from "./agent/agent-registry.js";
|
||||
import { initializeTitleGenerator } from "../services/agent-title-generator.js";
|
||||
import { attachAgentRegistryPersistence } from "./persistence-hooks.js";
|
||||
import { createAgentMcpServer } from "./agent/mcp-server.js";
|
||||
import { createAllClients } from "./agent/provider-registry.js";
|
||||
import { createAllClients, shutdownProviders } from "./agent/provider-registry.js";
|
||||
import type {
|
||||
AgentClient,
|
||||
AgentControlMcpConfig,
|
||||
@@ -444,6 +444,7 @@ export async function createPaseoDaemon(
|
||||
|
||||
const stop = async () => {
|
||||
await closeAllAgents(agentManager);
|
||||
await shutdownProviders();
|
||||
await wsServer.close();
|
||||
await new Promise<void>((resolve) => {
|
||||
httpServer.close(() => resolve());
|
||||
|
||||
@@ -79,10 +79,14 @@ export function loadConfig(
|
||||
? Buffer.from(`${agentMcpUser}:${agentMcpPassword}`).toString("base64")
|
||||
: undefined;
|
||||
|
||||
const envCorsOrigins = env.PASEO_CORS_ORIGINS
|
||||
? env.PASEO_CORS_ORIGINS.split(",").map((s) => s.trim())
|
||||
: [];
|
||||
|
||||
return {
|
||||
listen,
|
||||
paseoHome,
|
||||
corsAllowedOrigins: persisted.cors.allowedOrigins,
|
||||
corsAllowedOrigins: [...persisted.cors.allowedOrigins, ...envCorsOrigins],
|
||||
agentMcpRoute: DEFAULT_AGENT_MCP_ROUTE,
|
||||
agentMcpAllowedHosts: [mcpListen, `localhost:${mcpListen.split(":")[1]}`],
|
||||
auth: {
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
type FileDownloadTokenRequest,
|
||||
type GitSetupOptions,
|
||||
} from "./messages.js";
|
||||
import { parseAndHighlightDiff } from "./utils/diff-highlighter.js";
|
||||
import { parseAndHighlightDiff, type ParsedDiffFile } from "./utils/diff-highlighter.js";
|
||||
import { getSystemPrompt } from "./agent/system-prompt.js";
|
||||
import { getAllTools } from "./agent/llm-openai.js";
|
||||
import { TTSManager } from "./agent/tts-manager.js";
|
||||
@@ -2102,6 +2102,9 @@ export class Session {
|
||||
`[Session ${this.clientId}] Handling highlighted diff request for agent ${agentId}`
|
||||
);
|
||||
|
||||
// Maximum lines changed before we skip showing the diff content
|
||||
const MAX_DIFF_LINES = 5000;
|
||||
|
||||
try {
|
||||
const agents = this.agentManager.listAgents();
|
||||
const agent = agents.find((a) => a.id === agentId);
|
||||
@@ -2119,57 +2122,161 @@ export class Session {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get diff for tracked files
|
||||
const { stdout: trackedDiff } = await execAsync("git diff HEAD", {
|
||||
cwd: agent.cwd,
|
||||
});
|
||||
// Step 1: Get the list of changed files with their stats (numstat gives additions/deletions per file)
|
||||
const { stdout: numstatOutput } = await execAsync(
|
||||
"git diff --numstat HEAD",
|
||||
{ cwd: agent.cwd }
|
||||
);
|
||||
|
||||
// Get diff for untracked files (new files not yet added to git)
|
||||
let untrackedDiff = "";
|
||||
// Parse numstat output: "additions\tdeletions\tfilepath" or "-\t-\tfilepath" for binary
|
||||
interface FileStats {
|
||||
path: string;
|
||||
additions: number;
|
||||
deletions: number;
|
||||
isBinary: boolean;
|
||||
isTracked: boolean;
|
||||
}
|
||||
const fileStats: FileStats[] = [];
|
||||
|
||||
for (const line of numstatOutput.trim().split("\n").filter(Boolean)) {
|
||||
const parts = line.split("\t");
|
||||
if (parts.length >= 3) {
|
||||
const [addStr, delStr, ...pathParts] = parts;
|
||||
const path = pathParts.join("\t"); // Handle paths with tabs
|
||||
const isBinary = addStr === "-" && delStr === "-";
|
||||
fileStats.push({
|
||||
path,
|
||||
additions: isBinary ? 0 : parseInt(addStr, 10),
|
||||
deletions: isBinary ? 0 : parseInt(delStr, 10),
|
||||
isBinary,
|
||||
isTracked: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Get untracked files
|
||||
try {
|
||||
const { stdout: untrackedFiles } = await execAsync(
|
||||
"git ls-files --others --exclude-standard",
|
||||
{ cwd: agent.cwd }
|
||||
);
|
||||
const newFiles = untrackedFiles.trim().split("\n").filter(Boolean);
|
||||
|
||||
for (const file of newFiles) {
|
||||
for (const filePath of untrackedFiles.trim().split("\n").filter(Boolean)) {
|
||||
// Use git's numstat with --no-index to detect binary files (cross-platform)
|
||||
// Binary files show as "-\t-\tfilepath", text files show line counts
|
||||
try {
|
||||
const { stdout: fileDiff } = await execAsync(
|
||||
`git diff --no-index /dev/null "${file}" || true`,
|
||||
const { stdout: numstatLine } = await execAsync(
|
||||
`git diff --numstat --no-index /dev/null "${filePath}" || true`,
|
||||
{ cwd: agent.cwd }
|
||||
);
|
||||
if (fileDiff) {
|
||||
untrackedDiff += fileDiff;
|
||||
}
|
||||
const parts = numstatLine.trim().split("\t");
|
||||
const isBinary = parts[0] === "-" && parts[1] === "-";
|
||||
const additions = isBinary ? 0 : (parseInt(parts[0], 10) || 0);
|
||||
|
||||
fileStats.push({
|
||||
path: filePath,
|
||||
additions,
|
||||
deletions: 0,
|
||||
isBinary,
|
||||
isTracked: false,
|
||||
});
|
||||
} catch {
|
||||
// Ignore errors for individual files
|
||||
// If we can't determine, assume text and try to get it
|
||||
fileStats.push({
|
||||
path: filePath,
|
||||
additions: 0,
|
||||
deletions: 0,
|
||||
isBinary: false,
|
||||
isTracked: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors getting untracked files
|
||||
}
|
||||
|
||||
const combinedDiff = trackedDiff + untrackedDiff;
|
||||
// Step 3: Fetch diffs per-file, respecting limits
|
||||
const allFiles: ParsedDiffFile[] = [];
|
||||
|
||||
// Parse and highlight the diff
|
||||
const highlightedFiles = await parseAndHighlightDiff(
|
||||
combinedDiff,
|
||||
agent.cwd
|
||||
);
|
||||
for (const stats of fileStats) {
|
||||
const totalLines = stats.additions + stats.deletions;
|
||||
|
||||
// Handle binary files
|
||||
if (stats.isBinary) {
|
||||
allFiles.push({
|
||||
path: stats.path,
|
||||
isNew: !stats.isTracked,
|
||||
isDeleted: false,
|
||||
additions: 0,
|
||||
deletions: 0,
|
||||
hunks: [],
|
||||
status: "binary",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle files that are too large
|
||||
if (totalLines > MAX_DIFF_LINES) {
|
||||
allFiles.push({
|
||||
path: stats.path,
|
||||
isNew: !stats.isTracked,
|
||||
isDeleted: false,
|
||||
additions: stats.additions,
|
||||
deletions: stats.deletions,
|
||||
hunks: [],
|
||||
status: "too_large",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch the actual diff for this file
|
||||
try {
|
||||
let fileDiff: string;
|
||||
if (stats.isTracked) {
|
||||
const { stdout } = await execAsync(
|
||||
`git diff HEAD -- "${stats.path}"`,
|
||||
{ cwd: agent.cwd }
|
||||
);
|
||||
fileDiff = stdout;
|
||||
} else {
|
||||
const { stdout } = await execAsync(
|
||||
`git diff --no-index /dev/null "${stats.path}" || true`,
|
||||
{ cwd: agent.cwd }
|
||||
);
|
||||
fileDiff = stdout;
|
||||
}
|
||||
|
||||
if (fileDiff) {
|
||||
const parsedFiles = await parseAndHighlightDiff(fileDiff, agent.cwd);
|
||||
for (const file of parsedFiles) {
|
||||
allFiles.push({ ...file, status: "ok" });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// If diff fails for this file, add it with empty hunks
|
||||
allFiles.push({
|
||||
path: stats.path,
|
||||
isNew: !stats.isTracked,
|
||||
isDeleted: false,
|
||||
additions: stats.additions,
|
||||
deletions: stats.deletions,
|
||||
hunks: [],
|
||||
status: "ok",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.emit({
|
||||
type: "highlighted_diff_response",
|
||||
payload: {
|
||||
agentId,
|
||||
files: highlightedFiles,
|
||||
files: allFiles,
|
||||
error: null,
|
||||
requestId,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[Session ${this.clientId}] Highlighted diff for agent ${agentId} completed (${highlightedFiles.length} files)`
|
||||
`[Session ${this.clientId}] Highlighted diff for agent ${agentId} completed (${allFiles.length} files)`
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface ParsedDiffFile {
|
||||
additions: number;
|
||||
deletions: number;
|
||||
hunks: DiffHunk[];
|
||||
status?: "ok" | "too_large" | "binary";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -461,6 +461,7 @@ const ParsedDiffFileSchema = z.object({
|
||||
additions: z.number(),
|
||||
deletions: z.number(),
|
||||
hunks: z.array(DiffHunkSchema),
|
||||
status: z.enum(["ok", "too_large", "binary"]).optional(),
|
||||
});
|
||||
|
||||
export const HighlightedDiffRequestSchema = z.object({
|
||||
|
||||
Reference in New Issue
Block a user