Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 47 additions & 34 deletions docs/system-prompt.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,46 +47,59 @@ When the user asks you to remember something:
/**
* Build environment context XML block describing the workspace.
* @param workspacePath - Workspace directory path
* @param runtimeType - Runtime type: "local", "worktree", or "ssh"
* @param runtimeType - Runtime type (local, worktree, ssh, docker)
*/
function buildEnvironmentContext(
workspacePath: string,
runtimeType: "local" | "worktree" | "ssh"
): string {
if (runtimeType === "local") {
// Local runtime works directly in project directory - may or may not be git
return `
<environment>
You are working in a directory at ${workspacePath}

- Tools run here automatically
- You are meant to do your work isolated from the user and other agents
</environment>
`;
}

if (runtimeType === "ssh") {
// SSH runtime clones the repository on a remote host
return `
<environment>
You are in a clone of a git repository at ${workspacePath}

- This IS a git repository - run git commands directly (no cd needed)
- Tools run here automatically
- You are meant to do your work isolated from the user and other agents
</environment>
`;
function buildEnvironmentContext(workspacePath: string, runtimeType: RuntimeMode): string {
// Common lines shared across git-based runtimes
const gitCommonLines = [
"- This IS a git repository - run git commands directly (no cd needed)",
"- Tools run here automatically",
"- You are meant to do your work isolated from the user and other agents",
];

let description: string;
let lines: string[];

switch (runtimeType) {
case RUNTIME_MODE.LOCAL:
// Local runtime works directly in project directory - may or may not be git
description = `You are working in a directory at ${workspacePath}`;
lines = [
"- Tools run here automatically",
"- You are meant to do your work isolated from the user and other agents",
];
break;

case RUNTIME_MODE.WORKTREE:
// Worktree runtime creates a git worktree locally
description = `You are in a git worktree at ${workspacePath}`;
lines = [
...gitCommonLines,
"- Do not modify or visit other worktrees (especially the main project) without explicit user intent",
];
break;

case RUNTIME_MODE.SSH:
// SSH runtime clones the repository on a remote host
description = `You are in a clone of a git repository at ${workspacePath}`;
lines = gitCommonLines;
break;

case RUNTIME_MODE.DOCKER:
// Docker runtime runs in an isolated container
description = `You are in a clone of a git repository at ${workspacePath} inside a Docker container`;
lines = gitCommonLines;
break;

default:
assertNever(runtimeType, `Unknown runtime type: ${String(runtimeType)}`);
}

// Worktree runtime creates a git worktree locally
return `
<environment>
You are in a git worktree at ${workspacePath}
${description}

- This IS a git repository - run git commands directly (no cd needed)
- Tools run here automatically
- Do not modify or visit other worktrees (especially the main project) without explicit user intent
- You are meant to do your work isolated from the user and other agents
${lines.join("\n")}
</environment>
`;
}
Expand Down
4 changes: 3 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ module.exports = {
// Transform ESM modules to CommonJS for Jest
transformIgnorePatterns: ["node_modules/(?!(@orpc|shiki|json-schema-typed|rou3)/)"],
// Run tests in parallel (use 50% of available cores, or 4 minimum)
maxWorkers: "50%",
// CI runners often have a low core count; "50%" can result in a single Jest worker,
// which can push the integration job over its 10-minute timeout.
maxWorkers: process.env.CI ? 4 : "50%",
// Force exit after tests complete to avoid hanging on lingering handles
forceExit: true,
// 10 minute timeout for integration tests, 10s for unit tests
Expand Down
91 changes: 73 additions & 18 deletions src/browser/components/ChatInput/CreationControls.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useCallback, useEffect } from "react";
import { RUNTIME_MODE, type RuntimeMode } from "@/common/types/runtime";
import { RUNTIME_MODE, type RuntimeMode, type ParsedRuntime } from "@/common/types/runtime";
import { Select } from "../Select";
import { Loader2, Wand2 } from "lucide-react";
import { cn } from "@/common/lib/utils";
import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip";
import { SSHIcon, WorktreeIcon, LocalIcon } from "../icons/RuntimeIcons";
import { SSHIcon, WorktreeIcon, LocalIcon, DockerIcon } from "../icons/RuntimeIcons";
import { DocsLink } from "../DocsLink";
import type { WorkspaceNameState } from "@/browser/hooks/useWorkspaceName";

Expand All @@ -14,12 +14,12 @@ interface CreationControlsProps {
branchesLoaded: boolean;
trunkBranch: string;
onTrunkBranchChange: (branch: string) => void;
runtimeMode: RuntimeMode;
/** Currently selected runtime (discriminated union: SSH has host, Docker has image) */
selectedRuntime: ParsedRuntime;
defaultRuntimeMode: RuntimeMode;
sshHost: string;
onRuntimeModeChange: (mode: RuntimeMode) => void;
/** Set the currently selected runtime (discriminated union) */
onSelectedRuntimeChange: (runtime: ParsedRuntime) => void;
onSetDefaultRuntime: (mode: RuntimeMode) => void;
onSshHostChange: (host: string) => void;
disabled: boolean;
/** Project name to display as header */
projectName: string;
Expand Down Expand Up @@ -80,6 +80,17 @@ const RUNTIME_OPTIONS: Array<{
idleClass:
"bg-transparent text-muted border-transparent hover:border-[var(--color-runtime-ssh)]/40",
},
{
value: RUNTIME_MODE.DOCKER,
label: "Docker",
description: "Run in Docker container",
docsPath: "/runtime/docker",
Icon: DockerIcon,
activeClass:
"bg-[var(--color-runtime-docker)]/20 text-[var(--color-runtime-docker-text)] border-[var(--color-runtime-docker)]/60",
idleClass:
"bg-transparent text-muted border-transparent hover:border-[var(--color-runtime-docker)]/40",
},
];

function RuntimeButtonGroup(props: RuntimeButtonGroupProps) {
Expand Down Expand Up @@ -153,18 +164,20 @@ export function CreationControls(props: CreationControlsProps) {
// Don't check until branches have loaded to avoid prematurely switching runtime
const isNonGitRepo = props.branchesLoaded && props.branches.length === 0;

// Extract mode from discriminated union for convenience
const runtimeMode = props.selectedRuntime.mode;

// Local runtime doesn't need a trunk branch selector (uses project dir as-is)
const showTrunkBranchSelector =
props.branches.length > 0 && props.runtimeMode !== RUNTIME_MODE.LOCAL;
const showTrunkBranchSelector = props.branches.length > 0 && runtimeMode !== RUNTIME_MODE.LOCAL;

const { runtimeMode, onRuntimeModeChange } = props;
const { selectedRuntime, onSelectedRuntimeChange } = props;

// Force local runtime for non-git directories (only after branches loaded)
useEffect(() => {
if (isNonGitRepo && runtimeMode !== RUNTIME_MODE.LOCAL) {
onRuntimeModeChange(RUNTIME_MODE.LOCAL);
if (isNonGitRepo && selectedRuntime.mode !== RUNTIME_MODE.LOCAL) {
onSelectedRuntimeChange({ mode: "local" });
}
}, [isNonGitRepo, runtimeMode, onRuntimeModeChange]);
}, [isNonGitRepo, selectedRuntime.mode, onSelectedRuntimeChange]);

const handleNameChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -263,12 +276,39 @@ export function CreationControls(props: CreationControlsProps) {
<label className="text-muted-foreground text-xs font-medium">Workspace Type</label>
<div className="flex flex-wrap items-center gap-3">
<RuntimeButtonGroup
value={props.runtimeMode}
onChange={props.onRuntimeModeChange}
value={runtimeMode}
onChange={(mode) => {
// Convert mode to ParsedRuntime with appropriate defaults
switch (mode) {
case RUNTIME_MODE.SSH:
onSelectedRuntimeChange({
mode: "ssh",
host: selectedRuntime.mode === "ssh" ? selectedRuntime.host : "",
});
break;
case RUNTIME_MODE.DOCKER:
onSelectedRuntimeChange({
mode: "docker",
image: selectedRuntime.mode === "docker" ? selectedRuntime.image : "",
});
break;
case RUNTIME_MODE.LOCAL:
onSelectedRuntimeChange({ mode: "local" });
break;
case RUNTIME_MODE.WORKTREE:
default:
onSelectedRuntimeChange({ mode: "worktree" });
break;
}
}}
defaultMode={props.defaultRuntimeMode}
onSetDefault={props.onSetDefaultRuntime}
disabled={props.disabled}
disabledModes={isNonGitRepo ? [RUNTIME_MODE.WORKTREE, RUNTIME_MODE.SSH] : undefined}
disabledModes={
isNonGitRepo
? [RUNTIME_MODE.WORKTREE, RUNTIME_MODE.SSH, RUNTIME_MODE.DOCKER]
: undefined
}
/>

{/* Branch selector - shown for worktree/SSH */}
Expand All @@ -293,19 +333,34 @@ export function CreationControls(props: CreationControlsProps) {
)}

{/* SSH Host Input */}
{props.runtimeMode === RUNTIME_MODE.SSH && (
{selectedRuntime.mode === "ssh" && (
<div className="flex items-center gap-2">
<label className="text-muted-foreground text-xs">host</label>
<input
type="text"
value={props.sshHost}
onChange={(e) => props.onSshHostChange(e.target.value)}
value={selectedRuntime.host}
onChange={(e) => onSelectedRuntimeChange({ mode: "ssh", host: e.target.value })}
placeholder="user@host"
disabled={props.disabled}
className="bg-bg-dark text-foreground border-border-medium focus:border-accent h-7 w-36 rounded-md border px-2 text-sm focus:outline-none disabled:opacity-50"
/>
</div>
)}

{/* Docker Image Input */}
{selectedRuntime.mode === "docker" && (
<div className="flex items-center gap-2">
<label className="text-muted-foreground text-xs">image</label>
<input
type="text"
value={selectedRuntime.image}
onChange={(e) => onSelectedRuntimeChange({ mode: "docker", image: e.target.value })}
placeholder="ubuntu:22.04"
disabled={props.disabled}
className="bg-bg-dark text-foreground border-border-medium focus:border-accent h-7 w-36 rounded-md border px-2 text-sm focus:outline-none disabled:opacity-50"
/>
</div>
)}
</div>
</div>
</div>
Expand Down
6 changes: 2 additions & 4 deletions src/browser/components/ChatInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1446,12 +1446,10 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
branchesLoaded={creationState.branchesLoaded}
trunkBranch={creationState.trunkBranch}
onTrunkBranchChange={creationState.setTrunkBranch}
runtimeMode={creationState.runtimeMode}
selectedRuntime={creationState.selectedRuntime}
defaultRuntimeMode={creationState.defaultRuntimeMode}
sshHost={creationState.sshHost}
onRuntimeModeChange={creationState.setRuntimeMode}
onSelectedRuntimeChange={creationState.setSelectedRuntime}
onSetDefaultRuntime={creationState.setDefaultRuntimeMode}
onSshHostChange={creationState.setSshHost}
disabled={isSendInFlight}
projectName={props.projectName}
nameState={creationState.nameState}
Expand Down
Loading
Loading