Skip to content

Commit e13c68d

Browse files
committed
Merge branch 'main' of https://github.com/continuedev/continue into uinstinct/gemini-3-support
2 parents b024fa8 + 8bc2a7b commit e13c68d

File tree

27 files changed

+1879
-1226
lines changed

27 files changed

+1879
-1226
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
name: Sub Agent Background Prompt
3+
description: Start a subagent using the continue cli in the background
4+
invokable: true
5+
---
6+
7+
# Continue Sub Agent Background Prompt
8+
9+
Take the prompt provided by the user and using the terminal tool run the following command in the background:
10+
11+
cn -p "{{prompt}}"
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
name: Sub Agent Foreground Prompt
3+
description: Start a subagent using the continue cli in the foreground
4+
invokable: true
5+
---
6+
7+
# Continue Sub Agent Foreground Prompt
8+
9+
Take the prompt provided by the user and using the terminal tool run the following command in the foreground:
10+
11+
cn -p "{{prompt}}"

.github/workflows/cla.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ jobs:
2727
path-to-document: "https://github.com/continuedev/continue/blob/main/CLA.md"
2828
branch: cla-signatures
2929
# Bots and CLAs signed outside of GitHub
30-
allowlist: dependabot[bot],fbricon,panyamkeerthana,Jazzcort,owtaylor,halfline,[email protected],[email protected],snyk-bot,[email protected]
30+
allowlist: dependabot[bot],fbricon,panyamkeerthana,Jazzcort,owtaylor,halfline,[email protected],[email protected],continue[bot],snyk-bot,[email protected]
3131
signed-commit-message: "CLA signed in $pullRequestNo"

core/config/workspace/workspaceBlocks.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
ConfigYaml,
44
createPromptMarkdown,
55
createRuleMarkdown,
6+
sanitizeRuleName,
67
} from "@continuedev/config-yaml";
78
import * as YAML from "yaml";
89
import { IDE } from "../..";
@@ -116,13 +117,28 @@ export async function findAvailableFilename(
116117
fileExists: (uri: string) => Promise<boolean>,
117118
extension?: string,
118119
isGlobal?: boolean,
120+
baseFilenameOverride?: string,
119121
): Promise<string> {
120-
// Differentiate filename based on whether its a global rule or a workspace rule
121-
const baseFilename =
122-
blockType === "rules" && isGlobal
123-
? "global-rule"
124-
: `new-${BLOCK_TYPE_CONFIG[blockType]?.filename}`;
125122
const fileExtension = extension ?? getFileExtension(blockType);
123+
let baseFilename = "";
124+
125+
const trimmedOverride = baseFilenameOverride?.trim();
126+
if (trimmedOverride) {
127+
if (blockType === "rules") {
128+
const withoutExtension = trimmedOverride.replace(/\.[^./\\]+$/, "");
129+
const sanitized = sanitizeRuleName(withoutExtension);
130+
baseFilename = sanitized;
131+
} else {
132+
baseFilename = trimmedOverride;
133+
}
134+
}
135+
if (!baseFilename) {
136+
baseFilename =
137+
blockType === "rules" && isGlobal
138+
? "global-rule"
139+
: `new-${BLOCK_TYPE_CONFIG[blockType]?.filename}`;
140+
}
141+
126142
let counter = 0;
127143
let fileUri: string;
128144

@@ -141,6 +157,7 @@ export async function findAvailableFilename(
141157
export async function createNewWorkspaceBlockFile(
142158
ide: IDE,
143159
blockType: BlockType,
160+
baseFilename?: string,
144161
): Promise<void> {
145162
const workspaceDirs = await ide.getWorkspaceDirs();
146163
if (workspaceDirs.length === 0) {
@@ -155,6 +172,9 @@ export async function createNewWorkspaceBlockFile(
155172
baseDirUri,
156173
blockType,
157174
ide.fileExists.bind(ide),
175+
undefined,
176+
false,
177+
baseFilename,
158178
);
159179

160180
const fileContent = getFileContent(blockType);
@@ -163,7 +183,10 @@ export async function createNewWorkspaceBlockFile(
163183
await ide.openFile(fileUri);
164184
}
165185

166-
export async function createNewGlobalRuleFile(ide: IDE): Promise<void> {
186+
export async function createNewGlobalRuleFile(
187+
ide: IDE,
188+
baseFilename?: string,
189+
): Promise<void> {
167190
try {
168191
const globalDir = localPathToUri(getContinueGlobalPath());
169192

@@ -176,6 +199,7 @@ export async function createNewGlobalRuleFile(ide: IDE): Promise<void> {
176199
ide.fileExists.bind(ide),
177200
undefined,
178201
true, // isGlobal = true for global rules
202+
baseFilename,
179203
);
180204

181205
const fileContent = getFileContent("rules");

core/core.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,15 +409,19 @@ export class Core {
409409
});
410410

411411
on("config/addLocalWorkspaceBlock", async (msg) => {
412-
await createNewWorkspaceBlockFile(this.ide, msg.data.blockType);
412+
await createNewWorkspaceBlockFile(
413+
this.ide,
414+
msg.data.blockType,
415+
msg.data.baseFilename,
416+
);
413417
await this.configHandler.reloadConfig(
414418
"Local block created (config/addLocalWorkspaceBlock message)",
415419
);
416420
});
417421

418422
on("config/addGlobalRule", async (msg) => {
419423
try {
420-
await createNewGlobalRuleFile(this.ide);
424+
await createNewGlobalRuleFile(this.ide, msg.data?.baseFilename);
421425
await this.configHandler.reloadConfig(
422426
"Global rule created (config/addGlobalRule message)",
423427
);

core/protocol/core.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,11 @@ export type ToCoreFromIdeOrWebviewProtocol = {
9090
},
9191
void,
9292
];
93-
"config/addLocalWorkspaceBlock": [{ blockType: BlockType }, void];
94-
"config/addGlobalRule": [undefined, void];
93+
"config/addLocalWorkspaceBlock": [
94+
{ blockType: BlockType; baseFilename?: string },
95+
void,
96+
];
97+
"config/addGlobalRule": [undefined | { baseFilename?: string }, void];
9598
"config/newPromptFile": [undefined, void];
9699
"config/newAssistantFile": [undefined, void];
97100
"config/ideSettingsUpdate": [IdeSettings, void];

core/tools/implementations/runTerminalCommand.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,11 @@ export const runTerminalCommandImpl: ToolImpl = async (args, extras) => {
284284

285285
// Handle case where no workspace is available
286286
let cwd: string;
287-
if (workspaceDirs.length > 0) {
288-
cwd = fileURLToPath(workspaceDirs[0]);
287+
const fileWorkspaceDir = workspaceDirs.find((dir) =>
288+
dir.startsWith("file:/"),
289+
);
290+
if (fileWorkspaceDir) {
291+
cwd = fileURLToPath(fileWorkspaceDir);
289292
} else {
290293
// Default to user's home directory with fallbacks
291294
try {

core/tools/implementations/runTerminalCommand.vitest.ts

Lines changed: 210 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1+
import * as childProcess from "node:child_process";
2+
import * as fs from "node:fs";
3+
import * as os from "node:os";
4+
import * as path from "node:path";
5+
import { fileURLToPath } from "node:url";
16
import {
7+
afterAll,
8+
afterEach,
9+
beforeEach,
210
describe,
3-
it,
411
expect,
5-
beforeEach,
6-
afterEach,
7-
afterAll,
12+
it,
813
vi,
914
} from "vitest";
10-
import * as childProcess from "node:child_process";
11-
import * as fs from "node:fs";
12-
import * as os from "node:os";
13-
import * as path from "node:path";
1415
import { IDE, ToolExtras } from "../..";
1516
import * as processTerminalStates from "../../util/processTerminalStates";
16-
import { runTerminalCommandImpl } from "./runTerminalCommand";
1717
import { runTerminalCommandTool } from "../definitions/runTerminalCommand";
18+
import { runTerminalCommandImpl } from "./runTerminalCommand";
1819

1920
// We're using real child processes, so ensure these aren't mocked
2021
vi.unmock("node:child_process");
@@ -476,6 +477,206 @@ describe("runTerminalCommandImpl", () => {
476477
process.cwd = originalCwd;
477478
}
478479
});
480+
481+
describe("cwd handling", () => {
482+
describe("workspace directory handling", () => {
483+
it("should use file:// URI when available", async () => {
484+
const fileUri = "file:///home/user/workspace";
485+
mockGetWorkspaceDirs.mockResolvedValue([fileUri]);
486+
487+
// We can't easily test the internal cwd without mocking child_process,
488+
// but we can verify the function doesn't throw with file URIs
489+
await expect(
490+
runTerminalCommandImpl(
491+
{ command: "echo test", waitForCompletion: false },
492+
createMockExtras(),
493+
),
494+
).resolves.toBeDefined();
495+
});
496+
497+
it("should skip non-file URIs and use the first file:// URI", async () => {
498+
const workspaceDirs = [
499+
"vscode-vfs://github/user/repo",
500+
"untitled:/Untitled-1",
501+
"file:///home/user/workspace",
502+
"file:///home/user/other-workspace",
503+
];
504+
mockGetWorkspaceDirs.mockResolvedValue(workspaceDirs);
505+
506+
await expect(
507+
runTerminalCommandImpl(
508+
{ command: "echo test", waitForCompletion: false },
509+
createMockExtras(),
510+
),
511+
).resolves.toBeDefined();
512+
});
513+
514+
it("should handle workspace with only non-file URIs", async () => {
515+
const workspaceDirs = [
516+
"vscode-vfs://github/user/repo",
517+
"untitled:/Untitled-1",
518+
];
519+
mockGetWorkspaceDirs.mockResolvedValue(workspaceDirs);
520+
521+
// Should fall back to HOME/USERPROFILE or process.cwd() without throwing
522+
await expect(
523+
runTerminalCommandImpl(
524+
{ command: "echo test", waitForCompletion: false },
525+
createMockExtras(),
526+
),
527+
).resolves.toBeDefined();
528+
});
529+
530+
it("should handle empty workspace directories", async () => {
531+
mockGetWorkspaceDirs.mockResolvedValue([]);
532+
533+
// Should fall back to HOME/USERPROFILE or process.cwd() without throwing
534+
await expect(
535+
runTerminalCommandImpl(
536+
{ command: "echo test", waitForCompletion: false },
537+
createMockExtras(),
538+
),
539+
).resolves.toBeDefined();
540+
});
541+
542+
it("should properly convert file:// URIs to paths", () => {
543+
const fileUri = "file:///home/user/workspace";
544+
const expectedPath = "/home/user/workspace";
545+
546+
// Test that fileURLToPath works correctly with file:// URIs
547+
expect(fileURLToPath(fileUri)).toBe(expectedPath);
548+
});
549+
550+
it("should throw error when trying to convert non-file URI", () => {
551+
const nonFileUri = "vscode-vfs://github/user/repo";
552+
553+
// This demonstrates why the fix is needed - fileURLToPath throws on non-file URIs
554+
expect(() => fileURLToPath(nonFileUri)).toThrow();
555+
});
556+
});
557+
558+
describe("remote environment handling", () => {
559+
it("should use ide.runCommand for non-enabled remote environments", async () => {
560+
const extras = createMockExtras({
561+
remoteName: "some-unsupported-remote",
562+
});
563+
564+
const result = await runTerminalCommandImpl(
565+
{ command: "echo test" },
566+
extras,
567+
);
568+
569+
expect(mockRunCommand).toHaveBeenCalledWith("echo test");
570+
expect(result[0].content).toContain("Terminal output not available");
571+
});
572+
573+
it("should handle local environment with file URIs", async () => {
574+
mockGetWorkspaceDirs.mockResolvedValue(["file:///home/user/workspace"]);
575+
576+
await expect(
577+
runTerminalCommandImpl(
578+
{ command: "echo test", waitForCompletion: false },
579+
createMockExtras({ remoteName: "local" }),
580+
),
581+
).resolves.toBeDefined();
582+
});
583+
584+
it("should handle WSL environment", async () => {
585+
mockGetWorkspaceDirs.mockResolvedValue(["file:///home/user/workspace"]);
586+
587+
await expect(
588+
runTerminalCommandImpl(
589+
{ command: "echo test", waitForCompletion: false },
590+
createMockExtras({ remoteName: "wsl" }),
591+
),
592+
).resolves.toBeDefined();
593+
});
594+
595+
it("should handle dev-container environment", async () => {
596+
mockGetWorkspaceDirs.mockResolvedValue(["file:///workspace"]);
597+
598+
await expect(
599+
runTerminalCommandImpl(
600+
{ command: "echo test", waitForCompletion: false },
601+
createMockExtras({ remoteName: "dev-container" }),
602+
),
603+
).resolves.toBeDefined();
604+
});
605+
});
606+
607+
describe("fallback behavior", () => {
608+
it("should use HOME environment variable as fallback", async () => {
609+
const originalHome = process.env.HOME;
610+
process.env.HOME = "/home/testuser";
611+
612+
mockGetWorkspaceDirs.mockResolvedValue([
613+
"vscode-vfs://github/user/repo",
614+
]);
615+
616+
try {
617+
await expect(
618+
runTerminalCommandImpl(
619+
{ command: "echo test", waitForCompletion: false },
620+
createMockExtras(),
621+
),
622+
).resolves.toBeDefined();
623+
} finally {
624+
process.env.HOME = originalHome;
625+
}
626+
});
627+
628+
it("should use USERPROFILE on Windows as fallback", async () => {
629+
const originalHome = process.env.HOME;
630+
const originalUserProfile = process.env.USERPROFILE;
631+
632+
delete process.env.HOME;
633+
process.env.USERPROFILE = "C:\\Users\\TestUser";
634+
635+
mockGetWorkspaceDirs.mockResolvedValue([]);
636+
637+
try {
638+
await expect(
639+
runTerminalCommandImpl(
640+
{ command: "echo test", waitForCompletion: false },
641+
createMockExtras(),
642+
),
643+
).resolves.toBeDefined();
644+
} finally {
645+
process.env.HOME = originalHome;
646+
process.env.USERPROFILE = originalUserProfile;
647+
}
648+
});
649+
650+
it("should use os.tmpdir() as final fallback", async () => {
651+
const originalHome = process.env.HOME;
652+
const originalUserProfile = process.env.USERPROFILE;
653+
const originalCwd = process.cwd;
654+
655+
delete process.env.HOME;
656+
delete process.env.USERPROFILE;
657+
// Mock process.cwd to throw an error
658+
process.cwd = vi.fn().mockImplementation(() => {
659+
throw new Error("No cwd available");
660+
}) as typeof process.cwd;
661+
662+
mockGetWorkspaceDirs.mockResolvedValue([]);
663+
664+
try {
665+
// Should fall back to os.tmpdir() without throwing
666+
await expect(
667+
runTerminalCommandImpl(
668+
{ command: "echo test", waitForCompletion: false },
669+
createMockExtras(),
670+
),
671+
).resolves.toBeDefined();
672+
} finally {
673+
process.env.HOME = originalHome;
674+
process.env.USERPROFILE = originalUserProfile;
675+
process.cwd = originalCwd;
676+
}
677+
});
678+
});
679+
});
479680
});
480681

481682
describe("runTerminalCommandTool.evaluateToolCallPolicy", () => {

docs/favicon.png

23.2 KB
Loading

0 commit comments

Comments
 (0)