mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e172d18c4 | ||
|
|
b3693b79d1 | ||
|
|
0458988825 | ||
|
|
4bd31e1a2e |
@@ -364,10 +364,6 @@ try {
|
||||
ids.every((id) => id.startsWith("gpt-")),
|
||||
"all codex model IDs should be from the gpt family",
|
||||
);
|
||||
assert(
|
||||
ids.some((id) => id.includes("codex")),
|
||||
"codex model list should include at least one codex-optimized model",
|
||||
);
|
||||
assert(
|
||||
data.every((m) => m.model && m.id && m.description),
|
||||
"every codex model should have model, id, and description fields",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { mkdtemp, mkdir, rm } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { expect, test } from "vitest";
|
||||
import { ProviderOverridesSchema } from "@getpaseo/protocol/provider-config";
|
||||
import { createTestLogger } from "../../test-utils/test-logger.js";
|
||||
import { buildProviderRegistry } from "./provider-registry.js";
|
||||
import type { AgentSession } from "./agent-sdk-types.js";
|
||||
|
||||
// Retained reproduction for #3178, awaiting the built-in ID configuration decision.
|
||||
// Requires OMP on PATH (or OMP_COMMAND). Uses no model calls or existing provider state.
|
||||
const runReproduction = process.env.OMP_PROVIDER_COLLISION_REPRO === "1";
|
||||
|
||||
for (const providerId of ["omp-acp", "omp"]) {
|
||||
test.skipIf(!runReproduction)(
|
||||
`explicit ACP configuration creates an ACP session under '${providerId}'`,
|
||||
async () => {
|
||||
const cwd = await mkdtemp(path.join(process.cwd(), ".provider-collision-"));
|
||||
const agentDir = path.join(cwd, "agent");
|
||||
await mkdir(agentDir);
|
||||
const providerOverrides = ProviderOverridesSchema.parse({
|
||||
[providerId]: {
|
||||
extends: "acp",
|
||||
label: "Oh My Pi ACP",
|
||||
command: [process.env.OMP_COMMAND ?? "omp", "acp"],
|
||||
enabled: true,
|
||||
env: {
|
||||
PI_CODING_AGENT_DIR: agentDir,
|
||||
OMP_PROFILE: "default",
|
||||
PI_PROFILE: "default",
|
||||
XDG_CONFIG_HOME: path.join(cwd, "config"),
|
||||
XDG_DATA_HOME: path.join(cwd, "data"),
|
||||
XDG_CACHE_HOME: path.join(cwd, "cache"),
|
||||
XDG_STATE_HOME: path.join(cwd, "state"),
|
||||
},
|
||||
},
|
||||
});
|
||||
const logger = createTestLogger();
|
||||
const registry = buildProviderRegistry(logger, { providerOverrides });
|
||||
const client = registry[providerId]!.createClient(logger);
|
||||
let session: AgentSession | undefined;
|
||||
try {
|
||||
session = await client.createSession({ provider: providerId, cwd });
|
||||
expect(session.provider).toBe(providerId);
|
||||
} finally {
|
||||
await session?.close();
|
||||
await client.shutdown?.();
|
||||
await rm(cwd, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
30_000,
|
||||
);
|
||||
}
|
||||
@@ -134,6 +134,44 @@ describe("daemon web UI bootstrap", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("spells out the scheme default port when the Host header omits it", async () => {
|
||||
const distDir = await createWebUiDist();
|
||||
|
||||
daemonHandle = await createTestPaseoDaemon({
|
||||
mcpEnabled: false,
|
||||
webUi: {
|
||||
enabled: true,
|
||||
distDir,
|
||||
},
|
||||
});
|
||||
|
||||
// A browser reaching a proxy that terminates TLS on 443 sends a Host header
|
||||
// with no port at all. The app rejects a portless `listen`, so the daemon
|
||||
// has to supply the default itself.
|
||||
const httpsHint = readInjectedConnectionHint(
|
||||
await fetchDaemonWebUi({
|
||||
port: daemonHandle.port,
|
||||
headers: { host: "paseo.example.test", "x-forwarded-proto": "https" },
|
||||
}),
|
||||
);
|
||||
const httpHint = readInjectedConnectionHint(
|
||||
await fetchDaemonWebUi({
|
||||
port: daemonHandle.port,
|
||||
headers: { host: "paseo.example.test" },
|
||||
}),
|
||||
);
|
||||
const ipv6Hint = readInjectedConnectionHint(
|
||||
await fetchDaemonWebUi({
|
||||
port: daemonHandle.port,
|
||||
headers: { host: "[::1]", "x-forwarded-proto": "https" },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(httpsHint.listen).toBe("paseo.example.test:443");
|
||||
expect(httpHint.listen).toBe("paseo.example.test:80");
|
||||
expect(ipv6Hint.listen).toBe("[::1]:443");
|
||||
});
|
||||
|
||||
test("ignores forwarded HTTPS when proxy trust is disabled", async () => {
|
||||
const distDir = await createWebUiDist();
|
||||
|
||||
|
||||
@@ -23,6 +23,49 @@ afterEach(async () => {
|
||||
}, 60000);
|
||||
|
||||
describe("file explorer", () => {
|
||||
test.each([
|
||||
{
|
||||
query: "implementation",
|
||||
expectedPaths: [
|
||||
".cursor/plans/implementation-plan.md",
|
||||
".codex/plans/implementation-plan.md",
|
||||
".claude/plans/implementation-plan.md",
|
||||
],
|
||||
},
|
||||
{
|
||||
query: ".cursor/plans/implementation",
|
||||
expectedPaths: [".cursor/plans/implementation-plan.md"],
|
||||
},
|
||||
])(
|
||||
"workspace suggestions find hidden agent plans for '$query'",
|
||||
async ({ query, expectedPaths }) => {
|
||||
const cwd = tmpCwd();
|
||||
try {
|
||||
for (const directory of [".cursor", ".codex", ".claude", ".hidden"]) {
|
||||
mkdirSync(path.join(cwd, directory, "plans"), { recursive: true });
|
||||
writeFileSync(path.join(cwd, directory, "plans", "implementation-plan.md"), "plan\n");
|
||||
}
|
||||
|
||||
const response = await ctx.client.getDirectorySuggestions({
|
||||
cwd,
|
||||
query,
|
||||
limit: 50,
|
||||
includeFiles: true,
|
||||
includeDirectories: true,
|
||||
});
|
||||
expect(response.error).toBeNull();
|
||||
expect(response.entries).toHaveLength(expectedPaths.length);
|
||||
expect(response.entries).toEqual(
|
||||
expect.arrayContaining(
|
||||
expectedPaths.map((entryPath) => ({ path: entryPath, kind: "file" })),
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test.each(["..", "..."])("supports file operations on names starting with %s", async (prefix) => {
|
||||
const cwd = tmpCwd();
|
||||
const fileName = `${prefix}notes.txt`;
|
||||
|
||||
@@ -250,6 +250,22 @@ function serializeInlineScriptJson(value: unknown): string {
|
||||
.replace(/&/g, "\\u0026");
|
||||
}
|
||||
|
||||
/**
|
||||
* A Host header carries no port when the client used the scheme's default one,
|
||||
* which is what every browser sends to a proxy terminating TLS on 443. The app
|
||||
* parses `listen` with parseHostPort, which requires an explicit port and
|
||||
* rejects the value outright, so the hint has to spell the port out.
|
||||
*/
|
||||
function hasExplicitPort(host: string): boolean {
|
||||
if (host.startsWith("[")) {
|
||||
// IPv6 literals are bracketed: [::1] has no port, [::1]:443 does.
|
||||
const end = host.indexOf("]");
|
||||
return end !== -1 && /^:\d{1,5}$/.test(host.slice(end + 1));
|
||||
}
|
||||
const colon = host.indexOf(":");
|
||||
return colon !== -1 && colon === host.lastIndexOf(":") && /^\d{1,5}$/.test(host.slice(colon + 1));
|
||||
}
|
||||
|
||||
function injectConnectionHint(
|
||||
html: string,
|
||||
req: Parameters<RequestHandler>[0],
|
||||
@@ -257,8 +273,16 @@ function injectConnectionHint(
|
||||
): string {
|
||||
const host = typeof req.headers.host === "string" ? req.headers.host : "";
|
||||
const useTls = req.protocol === "https";
|
||||
let listen = host;
|
||||
if (host && !hasExplicitPort(host)) {
|
||||
let defaultPort = 80;
|
||||
if (useTls) {
|
||||
defaultPort = 443;
|
||||
}
|
||||
listen = `${host}:${defaultPort}`;
|
||||
}
|
||||
const hint = {
|
||||
listen: host,
|
||||
listen,
|
||||
useTls,
|
||||
label,
|
||||
};
|
||||
|
||||
@@ -146,6 +146,7 @@ export const WORKSPACE_SEARCH_HIDDEN_DIRECTORIES = [
|
||||
".agents",
|
||||
".claude",
|
||||
".codex",
|
||||
".cursor",
|
||||
".github",
|
||||
".opencode",
|
||||
".paseo",
|
||||
|
||||
Reference in New Issue
Block a user