Skip to content

Commit 2baf4d5

Browse files
committed
Merge remote-tracking branch 'upstream/main' into fix/robust-image-url-parsing
2 parents 3436b3e + 8292fbc commit 2baf4d5

File tree

25 files changed

+1676
-76
lines changed

25 files changed

+1676
-76
lines changed

core/config/markdown/loadMarkdownRules.ts

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import {
33
markdownToRule,
44
} from "@continuedev/config-yaml";
55
import { IDE, RuleWithSource } from "../..";
6+
import { PROMPTS_DIR_NAME, RULES_DIR_NAME } from "../../promptFiles";
67
import { joinPathsToUri } from "../../util/uri";
78
import { getAllDotContinueDefinitionFiles } from "../loadLocalAssistants";
89

910
export const SUPPORTED_AGENT_FILES = ["AGENTS.md", "AGENT.md", "CLAUDE.md"];
1011
/**
11-
* Loads rules from markdown files in the .continue/rules directory
12+
* Loads rules from markdown files in the .continue/rules and .continue/prompts directories
1213
* and agent files (AGENTS.md, AGENT.md, CLAUDE.md) at workspace root
1314
*/
1415
export async function loadMarkdownRules(ide: IDE): Promise<{
@@ -53,37 +54,45 @@ export async function loadMarkdownRules(ide: IDE): Promise<{
5354
}
5455
}
5556

56-
try {
57-
// Get all .md files from .continue/rules
58-
const markdownFiles = await getAllDotContinueDefinitionFiles(
59-
ide,
60-
{ includeGlobal: true, includeWorkspace: true, fileExtType: "markdown" },
61-
"rules",
62-
);
57+
// Load markdown files from both .continue/rules and .continue/prompts
58+
const dirsToCheck = [RULES_DIR_NAME, PROMPTS_DIR_NAME];
6359

64-
// Filter to just .md files
65-
const mdFiles = markdownFiles.filter((file) => file.path.endsWith(".md"));
60+
for (const dirName of dirsToCheck) {
61+
try {
62+
const markdownFiles = await getAllDotContinueDefinitionFiles(
63+
ide,
64+
{
65+
includeGlobal: true,
66+
includeWorkspace: true,
67+
fileExtType: "markdown",
68+
},
69+
dirName,
70+
);
6671

67-
// Process each markdown file
68-
for (const file of mdFiles) {
69-
try {
70-
const rule = markdownToRule(file.content, {
71-
uriType: "file",
72-
fileUri: file.path,
73-
});
74-
rules.push({ ...rule, source: "rules-block", sourceFile: file.path });
75-
} catch (e) {
76-
errors.push({
77-
fatal: false,
78-
message: `Failed to parse markdown rule file ${file.path}: ${e instanceof Error ? e.message : e}`,
79-
});
72+
// Filter to just .md files
73+
const mdFiles = markdownFiles.filter((file) => file.path.endsWith(".md"));
74+
75+
// Process each markdown file
76+
for (const file of mdFiles) {
77+
try {
78+
const rule = markdownToRule(file.content, {
79+
uriType: "file",
80+
fileUri: file.path,
81+
});
82+
rules.push({ ...rule, source: "rules-block", sourceFile: file.path });
83+
} catch (e) {
84+
errors.push({
85+
fatal: false,
86+
message: `Failed to parse markdown rule file ${file.path}: ${e instanceof Error ? e.message : e}`,
87+
});
88+
}
8089
}
90+
} catch (e) {
91+
errors.push({
92+
fatal: false,
93+
message: `Error loading markdown rule files from ${dirName}: ${e instanceof Error ? e.message : e}`,
94+
});
8195
}
82-
} catch (e) {
83-
errors.push({
84-
fatal: false,
85-
message: `Error loading markdown rule files: ${e instanceof Error ? e.message : e}`,
86-
});
8796
}
8897

8998
return { rules, errors };

core/context/mcp/MCPConnection.ts

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2+
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
3+
import { fileURLToPath } from "url";
4+
25
import {
36
SSEClientTransport,
47
SseError,
58
} from "@modelcontextprotocol/sdk/client/sse.js";
69
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
710
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
811
import { WebSocketClientTransport } from "@modelcontextprotocol/sdk/client/websocket.js";
9-
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
1012
import { Agent as HttpsAgent } from "https";
1113
import {
1214
IDE,
@@ -22,6 +24,7 @@ import {
2224
MCPServerStatus,
2325
MCPTool,
2426
} from "../..";
27+
import { resolveRelativePathInDir } from "../../util/ideUtils";
2528
import { getEnvPathFromUserShell } from "../../util/shellPath";
2629
import { getOauthToken } from "./MCPOauth";
2730

@@ -38,6 +41,8 @@ const WINDOWS_BATCH_COMMANDS = [
3841
"bunx",
3942
];
4043

44+
const COMMONS_ENV_VARS = ["HOME", "USER", "USERPROFILE", "LOGNAME", "USERNAME"];
45+
4146
function is401Error(error: unknown) {
4247
return (
4348
(error instanceof SseError && error.code === 401) ||
@@ -411,6 +416,44 @@ class MCPConnection {
411416
};
412417
}
413418

419+
/**
420+
* Resolves the current working directory of the current workspace.
421+
* @param cwd The cwd parameter provided by user.
422+
* @returns Current working directory (user-provided cwd or workspace root).
423+
*/
424+
private async resolveCwd(cwd?: string) {
425+
if (!cwd) {
426+
return this.resolveWorkspaceCwd(undefined);
427+
}
428+
429+
if (cwd.startsWith("file://")) {
430+
return fileURLToPath(cwd);
431+
}
432+
433+
// Return cwd if cwd is an absolute path.
434+
if (cwd.charAt(0) === "/") {
435+
return cwd;
436+
}
437+
438+
return this.resolveWorkspaceCwd(cwd);
439+
}
440+
441+
private async resolveWorkspaceCwd(cwd: string | undefined) {
442+
const IDE = this.extras?.ide;
443+
if (IDE) {
444+
const target = cwd ?? ".";
445+
const resolved = await resolveRelativePathInDir(target, IDE);
446+
if (resolved) {
447+
if (resolved.startsWith("file://")) {
448+
return fileURLToPath(resolved);
449+
}
450+
return resolved;
451+
}
452+
return resolved;
453+
}
454+
return cwd;
455+
}
456+
414457
private constructWebsocketTransport(
415458
options: InternalWebsocketMcpOptions,
416459
): WebSocketClientTransport {
@@ -464,7 +507,16 @@ class MCPConnection {
464507
private async constructStdioTransport(
465508
options: InternalStdioMcpOptions,
466509
): Promise<StdioClientTransport> {
467-
const env: Record<string, string> = options.env ? { ...options.env } : {};
510+
const commonEnvVars: Record<string, string> = Object.fromEntries(
511+
COMMONS_ENV_VARS.filter((key) => process.env[key] !== undefined).map(
512+
(key) => [key, process.env[key] as string],
513+
),
514+
);
515+
516+
const env = {
517+
...commonEnvVars,
518+
...(options.env ?? {}),
519+
};
468520

469521
if (process.env.PATH !== undefined) {
470522
// Set the initial PATH from process.env
@@ -488,11 +540,13 @@ class MCPConnection {
488540
options.args || [],
489541
);
490542

543+
const cwd = await this.resolveCwd(options.cwd);
544+
491545
const transport = new StdioClientTransport({
492546
command,
493547
args,
494548
env,
495-
cwd: options.cwd,
549+
cwd,
496550
stderr: "pipe",
497551
});
498552

core/context/mcp/MCPConnection.vitest.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
InternalStdioMcpOptions,
66
InternalWebsocketMcpOptions,
77
} from "../..";
8+
import * as ideUtils from "../../util/ideUtils";
89
import MCPConnection from "./MCPConnection";
910

1011
// Mock the shell path utility
@@ -145,6 +146,37 @@ describe("MCPConnection", () => {
145146
});
146147
});
147148

149+
describe("resolveCwd", () => {
150+
const baseOptions = {
151+
name: "test-mcp",
152+
id: "test-id",
153+
type: "stdio" as const,
154+
command: "test-cmd",
155+
args: [],
156+
};
157+
158+
it("should return absolute cwd unchanged", async () => {
159+
const conn = new MCPConnection(baseOptions);
160+
161+
await expect((conn as any).resolveCwd("/tmp/project")).resolves.toBe(
162+
"/tmp/project",
163+
);
164+
});
165+
166+
it("should resolve relative cwd using IDE workspace", async () => {
167+
const ide = {} as any;
168+
const mockResolve = vi
169+
.spyOn(ideUtils, "resolveRelativePathInDir")
170+
.mockResolvedValue("file:///workspace/src");
171+
const conn = new MCPConnection(baseOptions, { ide });
172+
173+
await expect((conn as any).resolveCwd("src")).resolves.toBe(
174+
"/workspace/src",
175+
);
176+
expect(mockResolve).toHaveBeenCalledWith("src", ide);
177+
});
178+
});
179+
148180
describe("connectClient", () => {
149181
const options: InternalStdioMcpOptions = {
150182
name: "test-mcp",

core/index.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1875,6 +1875,16 @@ export interface CompiledMessagesResult {
18751875
contextPercentage: number;
18761876
}
18771877

1878+
export interface AddToChatPayload {
1879+
data: AddToChatPayloadItem[];
1880+
}
1881+
1882+
interface AddToChatPayloadItem {
1883+
type: "file" | "folder";
1884+
fullPath: string;
1885+
name: string;
1886+
}
1887+
18781888
export interface MessageOption {
18791889
precompiled: boolean;
18801890
}

core/promptFiles/getPromptFiles.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import { DEFAULT_PROMPTS_FOLDER_V1 } from ".";
1+
import path from "path";
2+
import {
3+
DEFAULT_PROMPTS_FOLDER_V1,
4+
DEFAULT_PROMPTS_FOLDER_V2,
5+
DEFAULT_RULES_FOLDER,
6+
RULES_DIR_NAME,
7+
} from ".";
28
import { IDE } from "..";
39
import { walkDir } from "../indexing/walkDir";
4-
import { readAllGlobalPromptFiles } from "../util/paths";
10+
import { getContinueGlobalPath, readAllGlobalPromptFiles } from "../util/paths";
511
import { joinPathsToUri } from "../util/uri";
612

7-
export const DEFAULT_PROMPTS_FOLDER_V2 = ".continue/prompts";
8-
913
export async function getPromptFilesFromDir(
1014
ide: IDE,
1115
dir: string,
@@ -40,7 +44,7 @@ export async function getAllPromptFiles(
4044
const workspaceDirs = await ide.getWorkspaceDirs();
4145
let promptFiles: { path: string; content: string }[] = [];
4246

43-
let dirsToCheck = [DEFAULT_PROMPTS_FOLDER_V2];
47+
let dirsToCheck = [DEFAULT_PROMPTS_FOLDER_V2, DEFAULT_RULES_FOLDER];
4448
if (checkV1DefaultFolder) {
4549
dirsToCheck.push(DEFAULT_PROMPTS_FOLDER_V1);
4650
}
@@ -56,9 +60,14 @@ export async function getAllPromptFiles(
5660
await Promise.all(fullDirs.map((dir) => getPromptFilesFromDir(ide, dir)))
5761
).flat();
5862

59-
// Also read from ~/.continue/prompts
63+
// Also read from ~/.continue/prompts and ~/.continue/rules
6064
promptFiles.push(...readAllGlobalPromptFiles());
6165

66+
const promptFilesFromRulesDirectory = readAllGlobalPromptFiles(
67+
path.join(getContinueGlobalPath(), RULES_DIR_NAME),
68+
);
69+
promptFiles.push(...promptFilesFromRulesDirectory);
70+
6271
return await Promise.all(
6372
promptFiles.map(async (file) => {
6473
const content = await ide.readFile(file.path);

core/promptFiles/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import { ContextProviderName } from "..";
22

33
export const DEFAULT_PROMPTS_FOLDER_V1 = ".prompts";
44
export const DEFAULT_PROMPTS_FOLDER_V2 = ".continue/prompts";
5+
export const DEFAULT_RULES_FOLDER = ".continue/rules";
6+
7+
// Subdirectory names (without .continue/ prefix)
8+
export const RULES_DIR_NAME = "rules";
9+
export const PROMPTS_DIR_NAME = "prompts";
510

611
export const SUPPORTED_PROMPT_CONTEXT_PROVIDERS: ContextProviderName[] = [
712
"file",

core/protocol/ideWebview.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ToWebviewFromIdeOrCoreProtocol } from "./webview";
33

44
import {
55
AcceptOrRejectDiffPayload,
6+
AddToChatPayload,
67
ApplyState,
78
ApplyToFilePayload,
89
HighlightedCodePayload,
@@ -78,4 +79,5 @@ export type ToWebviewFromIdeProtocol = ToWebviewFromIdeOrCoreProtocol & {
7879
exitEditMode: [undefined, void];
7980
focusEdit: [undefined, void];
8081
generateRule: [undefined, void];
82+
addToChat: [AddToChatPayload, void];
8183
};

docs/docs.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,11 @@
262262
"guides/posthog-github-continuous-ai",
263263
"guides/continue-docs-mcp-cookbook",
264264
"guides/sanity-mcp-continue-cookbook",
265+
"guides/sentry-mcp-error-monitoring",
265266
"guides/snyk-mcp-continue-cookbook",
266267
"guides/dlt-mcp-continue-cookbook",
267-
"guides/netlify-mcp-continuous-deployment"
268+
"guides/netlify-mcp-continuous-deployment",
269+
"guides/chrome-devtools-mcp-performance"
268270
]
269271
}
270272
]

0 commit comments

Comments
 (0)