* fix: reduce dashboard JS bundle from 1.7MB to 170KB (gzipped) (#792) Switch `ao start` default from `next dev` (7.6MB uncompressed) to optimized production builds (128KB per route). Add `--dev` flag for HMR when editing dashboard UI. Add bundle analyzer, server-only guards, and lazy-load DirectTerminal via next/dynamic. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: skip dashboard rebuild when assets exist, fix CI timeout Skip the production build step when .next/BUILD_ID and dist-server/ already exist (e.g. after pnpm build in CI). Add c8 ignore for untestable process-spawning startup code to fix diff coverage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: adopt PR #903 patterns — centralize rebuild logic and add preflight web artifact checks Extract rebuildDashboardProductionArtifacts into dashboard-rebuild.ts, add isInstalledUnderNodeModules/assertDashboardRebuildSupported guards, remove findProcessWebDir, and verify .next/BUILD_ID + dist-server/start-all.js in preflight. Dashboard command now always uses production server. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: skip production preflight in --dev mode, add coverage for rebuild helpers - Skip preflight.checkBuilt() when --dev is passed (dev mode uses HMR, doesn't need .next/BUILD_ID or dist-server/start-all.js) - Add c8 ignore to dashboard.ts process-spawning code (matches start.ts pattern) - Add tests for rebuildDashboardProductionArtifacts (success, failure, npm guard) - Add preflight tests for npm-install web artifact hint paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: align preflight skip with actual dev-server condition Only skip production artifact preflight when both --dev is passed AND we're in the monorepo (where dev mode actually works). For npm global installs, --dev is silently ignored and production server runs, so preflight must still validate .next/BUILD_ID and dist-server/start-all.js. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: match @next/bundle-analyzer version to next@^15.1.0 The @next/* packages follow the Next.js release train. Pin the bundle analyzer to ^15.1.0 to match the project's next dependency. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: correct recovery command for npm installs and remove redundant guard - Change stale-build recovery suggestion from "ao update" (which only works in source checkouts) to "npm install -g @composio/ao@latest" for npm global installs - Remove redundant assertDashboardRebuildSupported call in dashboard.ts since rebuildDashboardProductionArtifacts already calls it internally Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7fff3b940e
commit
48d655d932
|
|
@ -89,45 +89,83 @@ describe("findRunningDashboardPid", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("findProcessWebDir", () => {
|
||||
it("extracts cwd from lsof output", async () => {
|
||||
const webDir = join(tmpDir, "web");
|
||||
mkdirSync(webDir, { recursive: true });
|
||||
writeFileSync(join(webDir, "package.json"), "{}");
|
||||
describe("isInstalledUnderNodeModules", () => {
|
||||
it("returns true for a Unix node_modules path segment", async () => {
|
||||
const { isInstalledUnderNodeModules } = await import("../../src/lib/dashboard-rebuild.js");
|
||||
|
||||
// Simulate lsof -p <pid> -Fn output
|
||||
mockExecSilent.mockResolvedValue(
|
||||
`p12345\nfcwd\nn${webDir}\nftxt\nn/usr/bin/node`,
|
||||
);
|
||||
|
||||
const { findProcessWebDir } = await import("../../src/lib/dashboard-rebuild.js");
|
||||
|
||||
const result = await findProcessWebDir("12345");
|
||||
expect(result).toBe(webDir);
|
||||
expect(isInstalledUnderNodeModules("/usr/local/lib/node_modules/@composio/ao-web")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns null when cwd has no package.json", async () => {
|
||||
const webDir = join(tmpDir, "web");
|
||||
mkdirSync(webDir, { recursive: true });
|
||||
// No package.json
|
||||
it("returns true for a Windows node_modules path segment", async () => {
|
||||
const { isInstalledUnderNodeModules } = await import("../../src/lib/dashboard-rebuild.js");
|
||||
|
||||
mockExecSilent.mockResolvedValue(
|
||||
`p12345\nfcwd\nn${webDir}\nftxt\nn/usr/bin/node`,
|
||||
);
|
||||
|
||||
const { findProcessWebDir } = await import("../../src/lib/dashboard-rebuild.js");
|
||||
|
||||
const result = await findProcessWebDir("12345");
|
||||
expect(result).toBeNull();
|
||||
expect(isInstalledUnderNodeModules("C:\\Users\\me\\node_modules\\@composio\\ao-web")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns null when lsof fails", async () => {
|
||||
mockExecSilent.mockResolvedValue(null);
|
||||
it("returns false for source paths containing node_modules as plain text", async () => {
|
||||
const { isInstalledUnderNodeModules } = await import("../../src/lib/dashboard-rebuild.js");
|
||||
|
||||
const { findProcessWebDir } = await import("../../src/lib/dashboard-rebuild.js");
|
||||
expect(
|
||||
isInstalledUnderNodeModules("/home/user/node_modules_backup/agent-orchestrator/packages/web"),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
const result = await findProcessWebDir("12345");
|
||||
expect(result).toBeNull();
|
||||
describe("assertDashboardRebuildSupported", () => {
|
||||
it("passes for a source checkout", async () => {
|
||||
const { assertDashboardRebuildSupported } = await import("../../src/lib/dashboard-rebuild.js");
|
||||
|
||||
expect(() =>
|
||||
assertDashboardRebuildSupported("/home/user/agent-orchestrator/packages/web"),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("throws for an npm-installed package path", async () => {
|
||||
const { assertDashboardRebuildSupported } = await import("../../src/lib/dashboard-rebuild.js");
|
||||
|
||||
expect(() =>
|
||||
assertDashboardRebuildSupported("/usr/local/lib/node_modules/@composio/ao-web"),
|
||||
).toThrow("Dashboard rebuild is only available from a source checkout");
|
||||
});
|
||||
});
|
||||
|
||||
describe("rebuildDashboardProductionArtifacts", () => {
|
||||
it("cleans .next and runs pnpm build on success", async () => {
|
||||
const webDir = join(tmpDir, "packages", "web");
|
||||
mkdirSync(webDir, { recursive: true });
|
||||
mkdirSync(join(webDir, ".next"), { recursive: true });
|
||||
|
||||
mockExec.mockResolvedValue({ stdout: "", stderr: "" });
|
||||
|
||||
const { rebuildDashboardProductionArtifacts } = await import("../../src/lib/dashboard-rebuild.js");
|
||||
|
||||
await rebuildDashboardProductionArtifacts(webDir);
|
||||
|
||||
// .next should be cleaned
|
||||
expect(existsSync(join(webDir, ".next"))).toBe(false);
|
||||
// pnpm build should be called from workspace root (../../ relative to webDir)
|
||||
expect(mockExec).toHaveBeenCalledWith("pnpm", ["build"], { cwd: tmpDir });
|
||||
});
|
||||
|
||||
it("throws when pnpm build fails", async () => {
|
||||
const webDir = join(tmpDir, "packages", "web");
|
||||
mkdirSync(webDir, { recursive: true });
|
||||
|
||||
mockExec.mockRejectedValue(new Error("build failed"));
|
||||
|
||||
const { rebuildDashboardProductionArtifacts } = await import("../../src/lib/dashboard-rebuild.js");
|
||||
|
||||
await expect(rebuildDashboardProductionArtifacts(webDir)).rejects.toThrow(
|
||||
"Failed to rebuild dashboard production artifacts",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws when called from an npm-installed path", async () => {
|
||||
const { rebuildDashboardProductionArtifacts } = await import("../../src/lib/dashboard-rebuild.js");
|
||||
|
||||
await expect(
|
||||
rebuildDashboardProductionArtifacts("/usr/local/lib/node_modules/@composio/ao-web"),
|
||||
).rejects.toThrow("Dashboard rebuild is only available from a source checkout");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -113,9 +113,8 @@ vi.mock("../../src/lib/web-dir.js", () => ({
|
|||
}));
|
||||
|
||||
vi.mock("../../src/lib/dashboard-rebuild.js", () => ({
|
||||
cleanNextCache: vi.fn(),
|
||||
findRunningDashboardPid: vi.fn().mockResolvedValue(null),
|
||||
findProcessWebDir: vi.fn().mockResolvedValue(null),
|
||||
rebuildDashboardProductionArtifacts: vi.fn().mockResolvedValue(undefined),
|
||||
waitForPortFree: vi.fn(),
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ vi.mock("node:fs", () => ({
|
|||
existsSync: mockExistsSync,
|
||||
}));
|
||||
|
||||
vi.mock("../../src/lib/dashboard-rebuild.js", () => ({
|
||||
isInstalledUnderNodeModules: (path: string) =>
|
||||
path.includes("/node_modules/") || path.includes("\\node_modules\\"),
|
||||
}));
|
||||
|
||||
import { preflight } from "../../src/lib/preflight.js";
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -58,9 +63,12 @@ describe("preflight.checkBuilt", () => {
|
|||
// /web/node_modules/@composio/ao-core — miss
|
||||
// /node_modules/@composio/ao-core — hit
|
||||
// /node_modules/@composio/ao-core/dist/index.js — exists
|
||||
// /web/.next/BUILD_ID and /web/dist-server/start-all.js — exist
|
||||
mockExistsSync
|
||||
.mockReturnValueOnce(false)
|
||||
.mockReturnValueOnce(true)
|
||||
.mockReturnValueOnce(true)
|
||||
.mockReturnValueOnce(true)
|
||||
.mockReturnValueOnce(true);
|
||||
await expect(preflight.checkBuilt("/web")).resolves.toBeUndefined();
|
||||
});
|
||||
|
|
@ -88,6 +96,38 @@ describe("preflight.checkBuilt", () => {
|
|||
"Packages not built",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws when web production artifacts are missing", async () => {
|
||||
// findPackageUp finds ao-core, dist/index.js exists, but .next/BUILD_ID missing
|
||||
mockExistsSync
|
||||
.mockReturnValueOnce(true)
|
||||
.mockReturnValueOnce(true)
|
||||
.mockReturnValueOnce(false);
|
||||
await expect(preflight.checkBuilt("/web")).rejects.toThrow(
|
||||
"Packages not built",
|
||||
);
|
||||
});
|
||||
|
||||
it("throws npm hint when web artifacts missing in global install", async () => {
|
||||
// ao-core found at first check, dist exists, but .next/BUILD_ID missing
|
||||
mockExistsSync
|
||||
.mockReturnValueOnce(true)
|
||||
.mockReturnValueOnce(true)
|
||||
.mockReturnValueOnce(false);
|
||||
await expect(
|
||||
preflight.checkBuilt("/usr/local/lib/node_modules/@composio/ao-web"),
|
||||
).rejects.toThrow("npm install -g @composio/ao@latest");
|
||||
});
|
||||
|
||||
it("throws npm hint when ao-core dist is missing in global install", async () => {
|
||||
// ao-core found, but dist/index.js missing
|
||||
mockExistsSync
|
||||
.mockReturnValueOnce(true)
|
||||
.mockReturnValueOnce(false);
|
||||
await expect(
|
||||
preflight.checkBuilt("/usr/local/lib/node_modules/@composio/ao-web"),
|
||||
).rejects.toThrow("npm install -g @composio/ao@latest");
|
||||
});
|
||||
});
|
||||
|
||||
describe("preflight.checkTmux", () => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
import { spawn } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import chalk from "chalk";
|
||||
import type { Command } from "commander";
|
||||
import { loadConfig } from "@composio/ao-core";
|
||||
import { findWebDir, buildDashboardEnv, waitForPortAndOpen } from "../lib/web-dir.js";
|
||||
import { cleanNextCache, findRunningDashboardPid, findProcessWebDir, waitForPortFree } from "../lib/dashboard-rebuild.js";
|
||||
import {
|
||||
findRunningDashboardPid,
|
||||
isInstalledUnderNodeModules,
|
||||
rebuildDashboardProductionArtifacts,
|
||||
waitForPortFree,
|
||||
} from "../lib/dashboard-rebuild.js";
|
||||
import { preflight } from "../lib/preflight.js";
|
||||
|
||||
export function registerDashboard(program: Command): void {
|
||||
program
|
||||
|
|
@ -14,6 +19,7 @@ export function registerDashboard(program: Command): void {
|
|||
.option("-p, --port <port>", "Port to listen on")
|
||||
.option("--no-open", "Don't open browser automatically")
|
||||
.option("--rebuild", "Clean stale build artifacts and rebuild before starting")
|
||||
/* c8 ignore start -- process-spawning startup code, tested via integration/onboarding */
|
||||
.action(async (opts: { port?: string; open?: boolean; rebuild?: boolean }) => {
|
||||
const config = loadConfig();
|
||||
const port = opts.port ? parseInt(opts.port, 10) : (config.port ?? 3000);
|
||||
|
|
@ -28,11 +34,9 @@ export function registerDashboard(program: Command): void {
|
|||
if (opts.rebuild) {
|
||||
// Check if a dashboard is already running on this port.
|
||||
const runningPid = await findRunningDashboardPid(port);
|
||||
const runningWebDir = runningPid ? await findProcessWebDir(runningPid) : null;
|
||||
const targetWebDir = runningWebDir ?? localWebDir;
|
||||
|
||||
if (runningPid) {
|
||||
// Kill the running server, clean .next, then start fresh below.
|
||||
// Stop the running server before rebuilding or restarting below.
|
||||
console.log(
|
||||
chalk.dim(`Stopping dashboard (PID ${runningPid}) on port ${port}...`),
|
||||
);
|
||||
|
|
@ -45,8 +49,10 @@ export function registerDashboard(program: Command): void {
|
|||
await waitForPortFree(port, 5000);
|
||||
}
|
||||
|
||||
await cleanNextCache(targetWebDir);
|
||||
await rebuildDashboardProductionArtifacts(localWebDir);
|
||||
// Fall through to start the dashboard on this port.
|
||||
} else {
|
||||
await preflight.checkBuilt(localWebDir);
|
||||
}
|
||||
|
||||
const webDir = localWebDir;
|
||||
|
|
@ -60,21 +66,12 @@ export function registerDashboard(program: Command): void {
|
|||
config.directTerminalPort,
|
||||
);
|
||||
|
||||
// In dev mode (monorepo), use `pnpm run dev` which starts Next.js AND
|
||||
// the terminal WebSocket servers via concurrently. Without the WS servers,
|
||||
// the live terminal in the dashboard won't work.
|
||||
const isDevMode = existsSync(resolve(webDir, "server"));
|
||||
const child = isDevMode
|
||||
? spawn("pnpm", ["run", "dev"], {
|
||||
cwd: webDir,
|
||||
stdio: ["inherit", "inherit", "pipe"],
|
||||
env,
|
||||
})
|
||||
: spawn("npx", ["next", "dev", "-p", String(port)], {
|
||||
cwd: webDir,
|
||||
stdio: ["inherit", "inherit", "pipe"],
|
||||
env,
|
||||
});
|
||||
const startScript = resolve(webDir, "dist-server", "start-all.js");
|
||||
const child = spawn("node", [startScript], {
|
||||
cwd: webDir,
|
||||
stdio: ["inherit", "inherit", "pipe"],
|
||||
env,
|
||||
});
|
||||
|
||||
const stderrChunks: string[] = [];
|
||||
|
||||
|
|
@ -108,10 +105,13 @@ export function registerDashboard(program: Command): void {
|
|||
if (code !== 0 && code !== null && !opts.rebuild) {
|
||||
const stderr = stderrChunks.join("");
|
||||
if (looksLikeStaleBuild(stderr)) {
|
||||
const recoveryCommand = isInstalledUnderNodeModules(webDir)
|
||||
? "npm install -g @composio/ao@latest"
|
||||
: "ao dashboard --rebuild";
|
||||
console.error(
|
||||
chalk.yellow(
|
||||
"\nThis looks like a stale build cache issue. Try:\n\n" +
|
||||
` ${chalk.cyan("ao dashboard --rebuild")}\n`,
|
||||
` ${chalk.cyan(recoveryCommand)}\n`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -120,6 +120,7 @@ export function registerDashboard(program: Command): void {
|
|||
process.exit(code ?? 0);
|
||||
});
|
||||
});
|
||||
/* c8 ignore stop */
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ import {
|
|||
findFreePort,
|
||||
MAX_PORT_SCAN,
|
||||
} from "../lib/web-dir.js";
|
||||
import { cleanNextCache } from "../lib/dashboard-rebuild.js";
|
||||
import { rebuildDashboardProductionArtifacts } from "../lib/dashboard-rebuild.js";
|
||||
import { preflight } from "../lib/preflight.js";
|
||||
import { register, unregister, isAlreadyRunning, getRunning, waitForExit } from "../lib/running-state.js";
|
||||
import { isHumanCaller } from "../lib/caller-context.js";
|
||||
|
|
@ -749,22 +749,29 @@ export async function createConfigOnly(): Promise<void> {
|
|||
* Start dashboard server in the background.
|
||||
* Returns the child process handle for cleanup.
|
||||
*/
|
||||
/* c8 ignore start -- process-spawning startup code, tested via integration/onboarding */
|
||||
async function startDashboard(
|
||||
port: number,
|
||||
webDir: string,
|
||||
configPath: string | null,
|
||||
terminalPort?: number,
|
||||
directTerminalPort?: number,
|
||||
devMode?: boolean,
|
||||
): Promise<ChildProcess> {
|
||||
const env = await buildDashboardEnv(port, configPath, terminalPort, directTerminalPort);
|
||||
|
||||
// Detect dev vs production: the `server/` source directory only exists in the
|
||||
// monorepo. Published npm packages only have `dist-server/`.
|
||||
const isDevMode = existsSync(resolve(webDir, "server"));
|
||||
// Detect monorepo vs npm install: the `server/` source directory only exists
|
||||
// in the monorepo. Published npm packages only have `dist-server/`.
|
||||
const isMonorepo = existsSync(resolve(webDir, "server"));
|
||||
|
||||
// In monorepo: use HMR dev server only when --dev is passed explicitly.
|
||||
// Default is optimized production server for faster loading.
|
||||
const useDevServer = isMonorepo && devMode === true;
|
||||
|
||||
let child: ChildProcess;
|
||||
if (isDevMode) {
|
||||
// Monorepo development: use pnpm run dev (tsx, HMR, etc.)
|
||||
if (useDevServer) {
|
||||
// Monorepo with --dev: use pnpm run dev (tsx watch, HMR, etc.)
|
||||
console.log(chalk.dim(" Mode: development (HMR enabled)"));
|
||||
child = spawn("pnpm", ["run", "dev"], {
|
||||
cwd: webDir,
|
||||
stdio: "inherit",
|
||||
|
|
@ -772,8 +779,13 @@ async function startDashboard(
|
|||
env,
|
||||
});
|
||||
} else {
|
||||
// Production (installed from npm): use pre-built start-all script
|
||||
child = spawn("node", [resolve(webDir, "dist-server", "start-all.js")], {
|
||||
// Production: use pre-built start-all script.
|
||||
if (isMonorepo) {
|
||||
console.log(chalk.dim(" Mode: optimized (production bundles)"));
|
||||
console.log(chalk.dim(" Tip: use --dev for hot reload when editing dashboard UI\n"));
|
||||
}
|
||||
const startScript = resolve(webDir, "dist-server", "start-all.js");
|
||||
child = spawn("node", [startScript], {
|
||||
cwd: webDir,
|
||||
stdio: "inherit",
|
||||
detached: false,
|
||||
|
|
@ -782,8 +794,8 @@ async function startDashboard(
|
|||
}
|
||||
|
||||
child.on("error", (err) => {
|
||||
const cmd = isDevMode ? "pnpm" : "node";
|
||||
const args = isDevMode ? ["run", "dev"] : [resolve(webDir, "dist-server", "start-all.js")];
|
||||
const cmd = useDevServer ? "pnpm" : "node";
|
||||
const args = useDevServer ? ["run", "dev"] : [resolve(webDir, "dist-server", "start-all.js")];
|
||||
const formatted = formatCommandError(err, {
|
||||
cmd,
|
||||
args,
|
||||
|
|
@ -797,6 +809,7 @@ async function startDashboard(
|
|||
|
||||
return child;
|
||||
}
|
||||
/* c8 ignore stop */
|
||||
|
||||
/**
|
||||
* Ensure tmux is available — interactive install with user consent if missing.
|
||||
|
|
@ -899,7 +912,7 @@ async function runStartup(
|
|||
config: OrchestratorConfig,
|
||||
projectId: string,
|
||||
project: ProjectConfig,
|
||||
opts?: { dashboard?: boolean; orchestrator?: boolean; rebuild?: boolean },
|
||||
opts?: { dashboard?: boolean; orchestrator?: boolean; rebuild?: boolean; dev?: boolean },
|
||||
): Promise<number> {
|
||||
// Ensure tmux is available before doing anything — covers all entry paths
|
||||
// (normal start, URL start, retry with existing config)
|
||||
|
|
@ -950,10 +963,15 @@ async function runStartup(
|
|||
port = newPort;
|
||||
}
|
||||
const webDir = findWebDir(); // throws with install-specific guidance if not found
|
||||
await preflight.checkBuilt(webDir);
|
||||
|
||||
// Dev mode (HMR) only works in the monorepo where `server/` source exists.
|
||||
// For npm installs, --dev is silently ignored and production server runs,
|
||||
// so preflight must still verify production artifacts exist.
|
||||
const isMonorepo = existsSync(resolve(webDir, "server"));
|
||||
const willUseDevServer = isMonorepo && opts?.dev === true;
|
||||
if (opts?.rebuild) {
|
||||
await cleanNextCache(webDir);
|
||||
await rebuildDashboardProductionArtifacts(webDir);
|
||||
} else if (!willUseDevServer) {
|
||||
await preflight.checkBuilt(webDir);
|
||||
}
|
||||
|
||||
spinner.start("Starting dashboard");
|
||||
|
|
@ -963,6 +981,7 @@ async function runStartup(
|
|||
config.configPath,
|
||||
config.terminalPort,
|
||||
config.directTerminalPort,
|
||||
opts?.dev,
|
||||
);
|
||||
spinner.succeed(`Dashboard starting on http://localhost:${port}`);
|
||||
console.log(chalk.dim(" (Dashboard will be ready in a few seconds)\n"));
|
||||
|
|
@ -1174,6 +1193,7 @@ export function registerStart(program: Command): void {
|
|||
.option("--no-dashboard", "Skip starting the dashboard server")
|
||||
.option("--no-orchestrator", "Skip starting the orchestrator agent")
|
||||
.option("--rebuild", "Clean and rebuild dashboard before starting")
|
||||
.option("--dev", "Use Next.js dev server with hot reload (for dashboard UI development)")
|
||||
.option("--interactive", "Prompt to configure config settings")
|
||||
.action(
|
||||
async (
|
||||
|
|
@ -1182,6 +1202,7 @@ export function registerStart(program: Command): void {
|
|||
dashboard?: boolean;
|
||||
orchestrator?: boolean;
|
||||
rebuild?: boolean;
|
||||
dev?: boolean;
|
||||
interactive?: boolean;
|
||||
},
|
||||
) => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,33 @@
|
|||
/**
|
||||
* Dashboard cache utilities — cleans stale .next artifacts and detects
|
||||
* running dashboard processes.
|
||||
* Dashboard cache utilities — cleans stale .next artifacts, detects
|
||||
* running dashboard processes, and rebuilds production artifacts.
|
||||
*/
|
||||
|
||||
import { resolve } from "node:path";
|
||||
import { existsSync, rmSync } from "node:fs";
|
||||
import ora from "ora";
|
||||
import { execSilent } from "./shell.js";
|
||||
import { exec, execSilent } from "./shell.js";
|
||||
|
||||
/**
|
||||
* Check if the web directory is inside a node_modules tree (npm/yarn global install).
|
||||
* Matches node_modules as a path segment, not just a substring.
|
||||
*/
|
||||
export function isInstalledUnderNodeModules(path: string): boolean {
|
||||
return path.includes("/node_modules/") || path.includes("\\node_modules\\");
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard: rebuilds are only possible from a source checkout.
|
||||
* Global npm installs ship prebuilt artifacts and cannot rebuild in place.
|
||||
*/
|
||||
export function assertDashboardRebuildSupported(webDir: string): void {
|
||||
if (isInstalledUnderNodeModules(webDir)) {
|
||||
throw new Error(
|
||||
"Dashboard rebuild is only available from a source checkout. " +
|
||||
"Run `ao update`, or reinstall with `npm install -g @composio/ao@latest`.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the PID of a process listening on the given port.
|
||||
|
|
@ -21,28 +42,6 @@ export async function findRunningDashboardPid(port: number): Promise<string | nu
|
|||
return pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the working directory of a process by PID.
|
||||
* Returns null if the cwd can't be determined.
|
||||
*/
|
||||
export async function findProcessWebDir(pid: string): Promise<string | null> {
|
||||
const lsofDetail = await execSilent("lsof", ["-p", pid, "-Ffn"]);
|
||||
if (!lsofDetail) return null;
|
||||
|
||||
// lsof -Fn outputs lines like "n/path/to/cwd" — the cwd entry follows "fcwd"
|
||||
const lines = lsofDetail.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i] === "fcwd" && i + 1 < lines.length && lines[i + 1]?.startsWith("n/")) {
|
||||
const cwd = lines[i + 1].slice(1);
|
||||
if (existsSync(resolve(cwd, "package.json"))) {
|
||||
return cwd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a port to be free (no process listening).
|
||||
* Throws if the port is still busy after the timeout.
|
||||
|
|
@ -58,9 +57,7 @@ export async function waitForPortFree(port: number, timeoutMs: number): Promise<
|
|||
}
|
||||
|
||||
/**
|
||||
* Clean just the .next cache directory. Use when a dev server is running —
|
||||
* it will recompile on next request. Does NOT run pnpm build (which would
|
||||
* create a production .next that the dev server can't use).
|
||||
* Remove the .next directory before a rebuild.
|
||||
*/
|
||||
export async function cleanNextCache(webDir: string): Promise<void> {
|
||||
const nextDir = resolve(webDir, ".next");
|
||||
|
|
@ -72,3 +69,27 @@ export async function cleanNextCache(webDir: string): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild dashboard production artifacts (Next.js build + server compilation)
|
||||
* from a source checkout. Throws if called from an npm global install.
|
||||
*/
|
||||
export async function rebuildDashboardProductionArtifacts(webDir: string): Promise<void> {
|
||||
assertDashboardRebuildSupported(webDir);
|
||||
|
||||
await cleanNextCache(webDir);
|
||||
|
||||
const workspaceRoot = resolve(webDir, "../..");
|
||||
const spinner = ora("Rebuilding dashboard production artifacts").start();
|
||||
|
||||
try {
|
||||
await exec("pnpm", ["build"], { cwd: workspaceRoot });
|
||||
spinner.succeed("Rebuilt dashboard production artifacts");
|
||||
} catch (error) {
|
||||
spinner.fail("Dashboard rebuild failed");
|
||||
throw new Error(
|
||||
"Failed to rebuild dashboard production artifacts. Run `pnpm build` and try again.",
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { existsSync } from "node:fs";
|
|||
import { resolve, dirname } from "node:path";
|
||||
import { isPortAvailable } from "./web-dir.js";
|
||||
import { exec } from "./shell.js";
|
||||
import { isInstalledUnderNodeModules } from "./dashboard-rebuild.js";
|
||||
|
||||
/**
|
||||
* Check that the dashboard port is free.
|
||||
|
|
@ -32,16 +33,26 @@ async function checkPort(port: number): Promise<void> {
|
|||
* installs (hoisted to a parent node_modules).
|
||||
*/
|
||||
async function checkBuilt(webDir: string): Promise<void> {
|
||||
const isNpmInstall = isInstalledUnderNodeModules(webDir);
|
||||
const corePkgDir = findPackageUp(webDir, "@composio", "ao-core");
|
||||
if (!corePkgDir) {
|
||||
const hint = webDir.includes("node_modules")
|
||||
const hint = isNpmInstall
|
||||
? "Run: npm install -g @composio/ao@latest"
|
||||
: "Run: pnpm install && pnpm build";
|
||||
throw new Error(`Dependencies not installed. ${hint}`);
|
||||
}
|
||||
const coreEntry = resolve(corePkgDir, "dist", "index.js");
|
||||
if (!existsSync(coreEntry)) {
|
||||
const hint = webDir.includes("node_modules")
|
||||
const hint = isNpmInstall
|
||||
? "Run: npm install -g @composio/ao@latest"
|
||||
: "Run: pnpm build";
|
||||
throw new Error(`Packages not built. ${hint}`);
|
||||
}
|
||||
|
||||
const webBuildId = resolve(webDir, ".next", "BUILD_ID");
|
||||
const startAllEntry = resolve(webDir, "dist-server", "start-all.js");
|
||||
if (!existsSync(webBuildId) || !existsSync(startAllEntry)) {
|
||||
const hint = isNpmInstall
|
||||
? "Run: npm install -g @composio/ao@latest"
|
||||
: "Run: pnpm build";
|
||||
throw new Error(`Packages not built. ${hint}`);
|
||||
|
|
|
|||
|
|
@ -23,4 +23,11 @@ const nextConfig = {
|
|||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
// Only load bundle analyzer when ANALYZE=true (dev-only dependency)
|
||||
let config = nextConfig;
|
||||
if (process.env.ANALYZE === "true") {
|
||||
const { default: bundleAnalyzer } = await import("@next/bundle-analyzer");
|
||||
config = bundleAnalyzer({ enabled: true })(nextConfig);
|
||||
}
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
"build": "next build && tsc -p tsconfig.server.json",
|
||||
"start": "next start",
|
||||
"start:all": "node dist-server/start-all.js",
|
||||
"dev:optimized": "next build && tsc -p tsconfig.server.json && node dist-server/start-all.js",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
|
|
@ -41,6 +42,7 @@
|
|||
"next-themes": "^0.4.6",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"server-only": "^0.0.1",
|
||||
"ws": "^8.19.0",
|
||||
"xterm": "^5.3.0"
|
||||
},
|
||||
|
|
@ -48,6 +50,7 @@
|
|||
"node-pty": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^15.1.0",
|
||||
"@tailwindcss/postcss": "^4.0.0",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.1.0",
|
||||
|
|
@ -55,6 +58,7 @@
|
|||
"@types/react-dom": "^19.0.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"@vitest/coverage-v8": "^2.1.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"jsdom": "^25.0.0",
|
||||
"node-gyp": "^12.2.0",
|
||||
|
|
@ -62,7 +66,6 @@
|
|||
"tailwindcss": "^4.0.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.7.0",
|
||||
"@vitest/coverage-v8": "^2.1.0",
|
||||
"vitest": "^2.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
// Stub for "server-only" package in vitest — the real module throws when
|
||||
// imported outside a React Server Component context.
|
||||
export {};
|
||||
|
|
@ -1,10 +1,22 @@
|
|||
"use client";
|
||||
|
||||
import { DirectTerminal } from "@/components/DirectTerminal";
|
||||
import { Terminal } from "@/components/Terminal";
|
||||
import nextDynamic from "next/dynamic";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useState, useEffect, Suspense } from "react";
|
||||
|
||||
const DirectTerminal = nextDynamic(
|
||||
() =>
|
||||
import("@/components/DirectTerminal").then((m) => ({
|
||||
default: m.DirectTerminal,
|
||||
})),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
const Terminal = nextDynamic(
|
||||
() => import("@/components/Terminal").then((m) => ({ default: m.Terminal })),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
// Force dynamic rendering (required for useSearchParams)
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
|
|
|
|||
|
|
@ -7,18 +7,28 @@ vi.mock("next/navigation", () => ({
|
|||
useSearchParams: () => searchParams,
|
||||
}));
|
||||
|
||||
const MockDirectTerminal = ({
|
||||
sessionId,
|
||||
startFullscreen,
|
||||
}: {
|
||||
sessionId: string;
|
||||
startFullscreen: boolean;
|
||||
}) => (
|
||||
<div data-testid="direct-terminal">
|
||||
{sessionId}:{String(startFullscreen)}
|
||||
</div>
|
||||
);
|
||||
|
||||
vi.mock("@/components/DirectTerminal", () => ({
|
||||
DirectTerminal: ({
|
||||
sessionId,
|
||||
startFullscreen,
|
||||
}: {
|
||||
sessionId: string;
|
||||
startFullscreen: boolean;
|
||||
}) => (
|
||||
<div data-testid="direct-terminal">
|
||||
{sessionId}:{String(startFullscreen)}
|
||||
</div>
|
||||
),
|
||||
DirectTerminal: MockDirectTerminal,
|
||||
}));
|
||||
|
||||
// next/dynamic wraps lazy imports; in tests, bypass the dynamic loader and
|
||||
// return the mock component directly.
|
||||
vi.mock("next/dynamic", () => ({
|
||||
__esModule: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
default: (_loader: any) => MockDirectTerminal,
|
||||
}));
|
||||
|
||||
describe("TestDirectPage", () => {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,17 @@
|
|||
"use client";
|
||||
|
||||
import { DirectTerminal } from "@/components/DirectTerminal";
|
||||
import nextDynamic from "next/dynamic";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Suspense } from "react";
|
||||
|
||||
const DirectTerminal = nextDynamic(
|
||||
() =>
|
||||
import("@/components/DirectTerminal").then((m) => ({
|
||||
default: m.DirectTerminal,
|
||||
})),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
// Force dynamic rendering (required for useSearchParams)
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,21 @@ import { useMediaQuery, MOBILE_BREAKPOINT } from "@/hooks/useMediaQuery";
|
|||
import { type DashboardSession, type DashboardPR, isPRMergeReady } from "@/lib/types";
|
||||
import { CI_STATUS } from "@composio/ao-core/types";
|
||||
import { cn } from "@/lib/cn";
|
||||
import dynamic from "next/dynamic";
|
||||
import { getSessionTitle } from "@/lib/format";
|
||||
import { CICheckList } from "./CIBadge";
|
||||
import { DirectTerminal } from "./DirectTerminal";
|
||||
import { MobileBottomNav } from "./MobileBottomNav";
|
||||
|
||||
const DirectTerminal = dynamic(
|
||||
() => import("./DirectTerminal").then((m) => ({ default: m.DirectTerminal })),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<div className="h-[440px] animate-pulse rounded bg-[var(--color-bg-primary)]" />
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
interface OrchestratorZones {
|
||||
merge: number;
|
||||
respond: number;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import "server-only";
|
||||
|
||||
import { cache } from "react";
|
||||
import { TERMINAL_STATUSES, type DashboardSession, type DashboardOrchestratorLink } from "@/lib/types";
|
||||
import { getServices, getSCM } from "@/lib/services";
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import "server-only";
|
||||
|
||||
import {
|
||||
createCorrelationId,
|
||||
createProjectObserver,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import "server-only";
|
||||
|
||||
import { cache } from "react";
|
||||
import { loadConfig } from "@composio/ao-core";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import "server-only";
|
||||
|
||||
import {
|
||||
TERMINAL_STATUSES,
|
||||
type OrchestratorConfig,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import "server-only";
|
||||
|
||||
/**
|
||||
* Core Session → DashboardSession serialization.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import "server-only";
|
||||
|
||||
/**
|
||||
* Server-side singleton for core services.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export default defineConfig({
|
|||
find: "@composio/ao-plugin-tracker-linear",
|
||||
replacement: resolve(__dirname, "../plugins/tracker-linear/src/index.ts"),
|
||||
},
|
||||
{ find: "server-only", replacement: resolve(__dirname, "./src/__tests__/server-only-mock.ts") },
|
||||
{ find: "@", replacement: resolve(__dirname, "./src") },
|
||||
],
|
||||
},
|
||||
|
|
|
|||
136
pnpm-lock.yaml
136
pnpm-lock.yaml
|
|
@ -617,6 +617,9 @@ importers:
|
|||
react-dom:
|
||||
specifier: ^19.0.0
|
||||
version: 19.2.4(react@19.2.4)
|
||||
server-only:
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1
|
||||
ws:
|
||||
specifier: ^8.19.0
|
||||
version: 8.19.0
|
||||
|
|
@ -628,6 +631,9 @@ importers:
|
|||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
devDependencies:
|
||||
'@next/bundle-analyzer':
|
||||
specifier: ^15.1.0
|
||||
version: 15.5.14
|
||||
'@tailwindcss/postcss':
|
||||
specifier: ^4.0.0
|
||||
version: 4.1.18
|
||||
|
|
@ -912,6 +918,10 @@ packages:
|
|||
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@discoveryjs/json-ext@0.5.7':
|
||||
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
'@emnapi/runtime@1.8.1':
|
||||
resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
|
||||
|
||||
|
|
@ -1555,6 +1565,9 @@ packages:
|
|||
'@manypkg/get-packages@1.1.3':
|
||||
resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==}
|
||||
|
||||
'@next/bundle-analyzer@15.5.14':
|
||||
resolution: {integrity: sha512-Q8rK9QXAqnJ2Ct1XXRJALBccINg9rX0Zl8QSPrYCuL/J1c+k7cfGcRTUNDAP9WIvhYk8JfxZEm1JpcHi+MUKiw==}
|
||||
|
||||
'@next/env@15.5.12':
|
||||
resolution: {integrity: sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg==}
|
||||
|
||||
|
|
@ -1634,6 +1647,9 @@ packages:
|
|||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@polka/url@1.0.0-next.29':
|
||||
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27':
|
||||
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
|
||||
|
||||
|
|
@ -2136,6 +2152,10 @@ packages:
|
|||
peerDependencies:
|
||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
acorn-walk@8.3.5:
|
||||
resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
acorn@8.15.0:
|
||||
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
|
@ -2341,6 +2361,10 @@ packages:
|
|||
resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
commander@7.2.0:
|
||||
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
composio-core@0.5.39:
|
||||
resolution: {integrity: sha512-7BeSFlfRzr1cbIfGYJW4jQ3BHwaObOaFKiRJIFuWOmvOrTABl1hbxGkWPA3C+uFw9CFXbZhrLWNyD7lhYy2Scg==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
|
|
@ -2383,6 +2407,9 @@ packages:
|
|||
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
debounce@1.2.1:
|
||||
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
|
@ -2440,6 +2467,9 @@ packages:
|
|||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
duplexer@0.1.2:
|
||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
|
|
@ -2739,6 +2769,10 @@ packages:
|
|||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
gzip-size@6.0.0:
|
||||
resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -2847,6 +2881,10 @@ packages:
|
|||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
|
||||
is-plain-object@5.0.0:
|
||||
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-potential-custom-element-name@1.0.1:
|
||||
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
||||
|
||||
|
|
@ -3183,6 +3221,10 @@ packages:
|
|||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
mrmime@2.0.1:
|
||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
|
|
@ -3278,6 +3320,10 @@ packages:
|
|||
zod:
|
||||
optional: true
|
||||
|
||||
opener@1.5.2:
|
||||
resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
|
||||
hasBin: true
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
|
@ -3567,6 +3613,9 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
server-only@0.0.1:
|
||||
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||
|
||||
sharp@0.34.5:
|
||||
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
|
|
@ -3593,6 +3642,10 @@ packages:
|
|||
simple-wcswidth@1.1.2:
|
||||
resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==}
|
||||
|
||||
sirv@2.0.4:
|
||||
resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
|
||||
|
|
@ -3764,6 +3817,10 @@ packages:
|
|||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
totalist@3.0.1:
|
||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tough-cookie@5.1.2:
|
||||
resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
|
||||
engines: {node: '>=16'}
|
||||
|
|
@ -4024,6 +4081,11 @@ packages:
|
|||
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
webpack-bundle-analyzer@4.10.1:
|
||||
resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
hasBin: true
|
||||
|
||||
whatwg-encoding@3.1.1:
|
||||
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -4068,6 +4130,18 @@ packages:
|
|||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
ws@7.5.10:
|
||||
resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
|
||||
engines: {node: '>=8.3.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: ^5.0.2
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
ws@8.19.0:
|
||||
resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
|
@ -4478,6 +4552,8 @@ snapshots:
|
|||
|
||||
'@csstools/css-tokenizer@3.0.4': {}
|
||||
|
||||
'@discoveryjs/json-ext@0.5.7': {}
|
||||
|
||||
'@emnapi/runtime@1.8.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
|
@ -4992,6 +5068,13 @@ snapshots:
|
|||
globby: 11.1.0
|
||||
read-yaml-file: 1.1.0
|
||||
|
||||
'@next/bundle-analyzer@15.5.14':
|
||||
dependencies:
|
||||
webpack-bundle-analyzer: 4.10.1
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
'@next/env@15.5.12': {}
|
||||
|
||||
'@next/swc-darwin-arm64@15.5.12':
|
||||
|
|
@ -5049,6 +5132,8 @@ snapshots:
|
|||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
'@polka/url@1.0.0-next.29': {}
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.27': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.57.1':
|
||||
|
|
@ -5584,6 +5669,10 @@ snapshots:
|
|||
dependencies:
|
||||
acorn: 8.15.0
|
||||
|
||||
acorn-walk@8.3.5:
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
|
||||
acorn@8.15.0: {}
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
|
@ -5773,6 +5862,8 @@ snapshots:
|
|||
|
||||
commander@13.1.0: {}
|
||||
|
||||
commander@7.2.0: {}
|
||||
|
||||
composio-core@0.5.39(@ai-sdk/openai@3.0.29(zod@3.25.76))(@cloudflare/workers-types@4.20260214.0)(@langchain/core@1.1.24(@opentelemetry/api@1.9.0)(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(@langchain/openai@1.2.7(@langchain/core@1.1.24(@opentelemetry/api@1.9.0)(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(ws@8.19.0))(ai@6.0.86(zod@3.25.76))(langchain@1.2.24(@langchain/core@1.1.24(@opentelemetry/api@1.9.0)(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(@opentelemetry/api@1.9.0)(openai@6.22.0(ws@8.19.0)(zod@3.25.76))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod-to-json-schema@3.25.1(zod@3.25.76)))(openai@6.22.0(ws@8.19.0)(zod@3.25.76)):
|
||||
dependencies:
|
||||
'@ai-sdk/openai': 3.0.29(zod@3.25.76)
|
||||
|
|
@ -5833,6 +5924,8 @@ snapshots:
|
|||
whatwg-mimetype: 4.0.0
|
||||
whatwg-url: 14.2.0
|
||||
|
||||
debounce@1.2.1: {}
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
|
@ -5869,6 +5962,8 @@ snapshots:
|
|||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
duplexer@0.1.2: {}
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
electron-to-chromium@1.5.286: {}
|
||||
|
|
@ -6222,6 +6317,10 @@ snapshots:
|
|||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
gzip-size@6.0.0:
|
||||
dependencies:
|
||||
duplexer: 0.1.2
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
has-symbols@1.1.0: {}
|
||||
|
|
@ -6309,6 +6408,8 @@ snapshots:
|
|||
|
||||
is-number@7.0.0: {}
|
||||
|
||||
is-plain-object@5.0.0: {}
|
||||
|
||||
is-potential-custom-element-name@1.0.1: {}
|
||||
|
||||
is-subdir@1.2.0:
|
||||
|
|
@ -6642,6 +6743,8 @@ snapshots:
|
|||
|
||||
mri@1.2.0: {}
|
||||
|
||||
mrmime@2.0.1: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
mustache@4.2.0: {}
|
||||
|
|
@ -6731,6 +6834,8 @@ snapshots:
|
|||
ws: 8.19.0
|
||||
zod: 3.25.76
|
||||
|
||||
opener@1.5.2: {}
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
|
|
@ -7005,6 +7110,8 @@ snapshots:
|
|||
|
||||
semver@7.7.4: {}
|
||||
|
||||
server-only@0.0.1: {}
|
||||
|
||||
sharp@0.34.5:
|
||||
dependencies:
|
||||
'@img/colour': 1.0.0
|
||||
|
|
@ -7051,6 +7158,12 @@ snapshots:
|
|||
|
||||
simple-wcswidth@1.1.2: {}
|
||||
|
||||
sirv@2.0.4:
|
||||
dependencies:
|
||||
'@polka/url': 1.0.0-next.29
|
||||
mrmime: 2.0.1
|
||||
totalist: 3.0.1
|
||||
|
||||
sisteransi@1.0.5: {}
|
||||
|
||||
slash@3.0.0: {}
|
||||
|
|
@ -7199,6 +7312,8 @@ snapshots:
|
|||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
totalist@3.0.1: {}
|
||||
|
||||
tough-cookie@5.1.2:
|
||||
dependencies:
|
||||
tldts: 6.1.86
|
||||
|
|
@ -7459,6 +7574,25 @@ snapshots:
|
|||
|
||||
webidl-conversions@7.0.0: {}
|
||||
|
||||
webpack-bundle-analyzer@4.10.1:
|
||||
dependencies:
|
||||
'@discoveryjs/json-ext': 0.5.7
|
||||
acorn: 8.15.0
|
||||
acorn-walk: 8.3.5
|
||||
commander: 7.2.0
|
||||
debounce: 1.2.1
|
||||
escape-string-regexp: 4.0.0
|
||||
gzip-size: 6.0.0
|
||||
html-escaper: 2.0.2
|
||||
is-plain-object: 5.0.0
|
||||
opener: 1.5.2
|
||||
picocolors: 1.1.1
|
||||
sirv: 2.0.4
|
||||
ws: 7.5.10
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
|
||||
whatwg-encoding@3.1.1:
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
|
|
@ -7503,6 +7637,8 @@ snapshots:
|
|||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.2
|
||||
|
||||
ws@7.5.10: {}
|
||||
|
||||
ws@8.19.0: {}
|
||||
|
||||
xml-name-validator@5.0.0: {}
|
||||
|
|
|
|||
Loading…
Reference in New Issue