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
21 changes: 10 additions & 11 deletions docs/chatgpt-coding-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,34 +150,33 @@ sessions for that workspace.

## Tool Names

DevSpace exposes these tool names:
The Claude surface exposes these tool names:

- `open_workspace`
- `read`
- `write`
- `edit`
- `bash`

By default, DevSpace also runs in `DEVSPACE_TOOL_MODE=minimal`, so dedicated
`grep`, `glob`, and `ls` tools are hidden. Use `bash` with command-line tools
such as `rg`, `find`, and `ls` for search and directory inspection.

Use `DEVSPACE_TOOL_MODE=full` to restore dedicated search and directory tools.

The experimental Codex-style surface is enabled with
`DEVSPACE_TOOL_MODE=codex`. It exposes:
DevSpace uses the Codex-style surface by default. It exposes:

- `open_workspace`
- `read`
- `apply_patch`
- `exec_command`
- `write_stdin`

In this mode, `write`, `edit`, `bash`, `grep`, `glob`, and `ls` are not
registered. `exec_command` returns a process session ID when a command is still
In this mode, `write`, `edit`, and `bash` are not registered. `exec_command`
Comment thread
Waishnav marked this conversation as resolved.
returns a process session ID when a command is still
running after its yield window. Use `write_stdin` to poll it, send input, resize
a PTY, or send Ctrl-C. Set `tty: true` only for commands that need a terminal.

Set `tools.mode` to `claude` in `~/.devspace/config.json` to expose `write`,
`edit`, and `bash` instead of the Codex mutation and command tools. Dedicated
MCP tools for `grep`, `glob`, and `ls` are not registered in either mode; use
the configured shell tool with command-line tools such as `rg`, `find`, and
`ls`.

## Show Changes

By default, `DEVSPACE_WIDGETS=full`.
Expand Down
26 changes: 17 additions & 9 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,27 @@ MCP clients discover metadata from:

## Tool Modes

`DEVSPACE_TOOL_MODE` controls the tool surface.
`tools.mode` in `~/.devspace/config.json` controls the tool surface:

```json
{
"tools": {
"mode": "codex"
}
}
```

`DEVSPACE_TOOL_MODE` and `DEVSPACE_MINIMAL_TOOLS` are no longer read. Set
`tools.mode` in the configuration file when selecting the Claude surface;
omitting it selects Codex.

| Value | Behavior |
| --- | --- |
| `minimal` | Default. Exposes `open_workspace`, `read`, `write`, `edit`, and `bash`. Clients use `bash` with tools such as `rg`, `find`, and `ls` for inspection. |
| `full` | Exposes the minimal tools plus dedicated `grep`, `glob`, and `ls` tools. |
| `codex` | Experimental. Exposes `open_workspace`, `read`, `apply_patch`, `exec_command`, and `write_stdin`. Existing mutation and shell tools are hidden. |
| `codex` | Default. Exposes `open_workspace`, `read`, `apply_patch`, `exec_command`, and `write_stdin`. |
| `claude` | Exposes `open_workspace`, `read`, `write`, `edit`, and `bash`. Clients use `bash` with tools such as `rg`, `find`, and `ls` for inspection. |

`DEVSPACE_MINIMAL_TOOLS` remains a backward-compatible alias when
`DEVSPACE_TOOL_MODE` is unset: `1` selects `minimal` and `0` selects `full`.
The `codex` mode must be selected through `DEVSPACE_TOOL_MODE` and always uses
its fixed short tool names regardless of `DEVSPACE_TOOL_NAMING`.
The dedicated MCP tools `grep`, `glob`, and `ls` are no longer exposed. Both
modes use their shell tool for search, file discovery, and directory inspection.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Codex-mode commands run without a PTY by default. Set `tty: true` on
`exec_command` for interactive terminal programs. PTY support uses the optional
Expand Down Expand Up @@ -250,7 +259,6 @@ DEVSPACE_ALLOWED_ROOTS="$HOME/personal,$HOME/work" \
DEVSPACE_PUBLIC_BASE_URL="https://devspace.example.com" \
DEVSPACE_WORKTREE_ROOT="$HOME/.devspace/worktrees" \
DEVSPACE_ARTIFACTS="1" \
DEVSPACE_TOOL_MODE="minimal" \
DEVSPACE_WIDGETS="full" \
npx @waishnav/devspace serve
```
Expand Down
14 changes: 3 additions & 11 deletions src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,7 @@ assert.equal(loadConfig(baseEnv).widgets, "full");
assert.equal(loadConfig({ ...baseEnv, DEVSPACE_WIDGETS: "changes" }).widgets, "changes");
assert.equal(loadConfig({ ...baseEnv, DEVSPACE_WIDGETS: "full" }).widgets, "full");
assert.equal(loadConfig({ ...baseEnv, DEVSPACE_WIDGETS: "off" }).widgets, "off");
assert.equal(loadConfig(baseEnv).toolMode, "minimal");
assert.equal(loadConfig({ ...baseEnv, DEVSPACE_TOOL_MODE: "minimal" }).toolMode, "minimal");
assert.equal(loadConfig({ ...baseEnv, DEVSPACE_TOOL_MODE: "full" }).toolMode, "full");
assert.equal(loadConfig({ ...baseEnv, DEVSPACE_TOOL_MODE: "codex" }).toolMode, "codex");
assert.equal(loadConfig({ ...baseEnv, DEVSPACE_MINIMAL_TOOLS: "0" }).toolMode, "full");
assert.equal(loadConfig({ ...baseEnv, DEVSPACE_MINIMAL_TOOLS: "1" }).toolMode, "minimal");
assert.equal(loadConfig(baseEnv).toolMode, "codex");
assert.equal(loadConfig(baseEnv).skillsEnabled, true);
assert.equal(loadConfig(baseEnv).devspaceSkillsDir, join(emptyConfigDir, "skills"));
assert.equal(loadConfig(baseEnv).devspaceAgentsDir, join(emptyConfigDir, "agents"));
Expand Down Expand Up @@ -50,11 +45,6 @@ assert.throws(
() => loadConfig({ ...baseEnv, DEVSPACE_WIDGETS: "write-only" }),
/Invalid DEVSPACE_WIDGETS: write-only/,
);
assert.throws(
() => loadConfig({ ...baseEnv, DEVSPACE_TOOL_MODE: "invalid" }),
/Invalid DEVSPACE_TOOL_MODE: invalid/,
);

assert.deepEqual(loadConfig(baseEnv).logging, {
level: "info",
format: "json",
Expand Down Expand Up @@ -163,6 +153,7 @@ writeFileSync(
subagents: true,
artifactsEnabled: true,
artifactMaxFileBytes: 321,
tools: { mode: "claude" },
}),
);
writeFileSync(
Expand All @@ -180,6 +171,7 @@ assert.equal(fileConfig.subagents.enabled, true);
assert.equal(fileConfig.subagents.providers.length, 7);
assert.equal(fileConfig.artifactsEnabled, true);
assert.equal(fileConfig.artifactMaxFileBytes, 321);
assert.equal(fileConfig.toolMode, "claude");
assert.deepEqual(fileConfig.allowedHosts, [
"localhost",
"127.0.0.1",
Expand Down
15 changes: 2 additions & 13 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { OAuthConfig } from "./oauth-provider.js";
import { devspaceAgentsDir, devspaceSkillsDir, loadDevspaceFiles } from "./user-config.js";
import { resolveSubagentsConfig, type SubagentsConfig } from "./local-agent-config.js";

export type ToolMode = "minimal" | "full" | "codex";
export type ToolMode = "claude" | "codex";
export type WidgetMode = "off" | "changes" | "full";
const DEFAULT_OAUTH_ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
const DEFAULT_OAUTH_REFRESH_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60;
Expand Down Expand Up @@ -85,17 +85,6 @@ function parseBoolean(value: string | undefined): boolean {
return ["1", "true", "yes", "on"].includes(value?.toLowerCase() ?? "");
}

function parseToolMode(env: NodeJS.ProcessEnv): ToolMode {
const mode = env.DEVSPACE_TOOL_MODE;
if (mode === "minimal" || mode === "full" || mode === "codex") return mode;
if (mode) throw new Error(`Invalid DEVSPACE_TOOL_MODE: ${mode}`);

if (env.DEVSPACE_MINIMAL_TOOLS !== undefined) {
return parseBoolean(env.DEVSPACE_MINIMAL_TOOLS) ? "minimal" : "full";
}
return "minimal";
}

function parseLogLevel(value: string | undefined): LogLevel {
if (!value || value === "info") return "info";
if (["silent", "error", "warn", "debug"].includes(value)) return value as LogLevel;
Expand Down Expand Up @@ -231,7 +220,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {
allowedRoots: parseAllowedRoots(env.DEVSPACE_ALLOWED_ROOTS ?? files.config.allowedRoots),
Comment thread
Waishnav marked this conversation as resolved.
allowedHosts: parseAllowedHosts(env.DEVSPACE_ALLOWED_HOSTS, derivedAllowedHosts),
publicBaseUrl,
toolMode: parseToolMode(env),
toolMode: files.config.tools?.mode ?? "codex",
widgets: parseWidgetMode(env.DEVSPACE_WIDGETS),
stateDir: resolve(expandHomePath(env.DEVSPACE_STATE_DIR ?? files.config.stateDir ?? defaultStateDir())),
worktreeRoot: resolve(expandHomePath(env.DEVSPACE_WORKTREE_ROOT ?? files.config.worktreeRoot ?? defaultWorktreeRoot())),
Expand Down
27 changes: 0 additions & 27 deletions src/pi-tools.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import {
createBashTool,
createEditTool,
createFindTool,
createGrepTool,
createLsTool,
createReadTool,
createWriteTool,
type BashToolInput,
type EditToolInput,
type EditToolDetails,
type FindToolInput,
type GrepToolInput,
type LsToolInput,
type ReadToolInput,
type WriteToolInput,
type AgentToolResult,
Expand Down Expand Up @@ -97,27 +91,6 @@ export async function editFileTool(input: EditToolInput, context: ToolContext):
}, context);
}

export async function grepFilesTool(input: GrepToolInput, context: ToolContext): Promise<ToolResponse> {
if (input.path) resolveAllowedPath(input.path, context.cwd, [context.root]);
const tool = createGrepTool(context.cwd);

return runTool((params) => tool.execute("grep_files", params), input, context);
}

export async function findFilesTool(input: FindToolInput, context: ToolContext): Promise<ToolResponse> {
if (input.path) resolveAllowedPath(input.path, context.cwd, [context.root]);
const tool = createFindTool(context.cwd);

return runTool((params) => tool.execute("find_files", params), input, context);
}

export async function listDirectoryTool(input: LsToolInput, context: ToolContext): Promise<ToolResponse> {
if (input.path) resolveAllowedPath(input.path, context.cwd, [context.root]);
const tool = createLsTool(context.cwd);

return runTool((params) => tool.execute("list_directory", params), input, context);
}

export async function runShellTool(input: BashToolInput, context: ToolContext): Promise<ToolResponse> {
const tool = createBashTool(context.cwd);
const timeout = input.timeout === undefined ? 30 : Math.min(input.timeout, 300);
Expand Down
17 changes: 8 additions & 9 deletions src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,9 @@ test("tool modes expose the expected host-facing tool surface", async (t) => {
expected: string[];
}> = [
{
mode: "minimal",
mode: "claude",
expected: ["open_workspace", "read", "write", "edit", "bash"],
},
{
mode: "full",
expected: ["open_workspace", "read", "write", "edit", "bash", "grep", "glob", "ls"],
},
{
mode: "codex",
expected: ["open_workspace", "read", "apply_patch", "exec_command", "write_stdin"],
Expand Down Expand Up @@ -64,7 +60,7 @@ test("widget modes compose independently from tool modes", async (t) => {

for (const { widgets, showChanges, workspaceCard } of cases) {
await t.test(widgets, async (nested) => {
const context = await fixture(nested, { toolMode: "full", widgets });
const context = await fixture(nested, { toolMode: "claude", widgets });
const tools = await context.client.listTools();
const workspace = tools.tools.find((tool) => tool.name === "open_workspace");
const changes = tools.tools.find((tool) => tool.name === "show_changes");
Expand Down Expand Up @@ -344,14 +340,17 @@ async function fixture(
DEVSPACE_WORKTREE_ROOT: join(root, ".worktrees"),
DEVSPACE_AGENT_DIR: agentDir,
DEVSPACE_WIDGETS: options.widgets ?? "full",
DEVSPACE_TOOL_MODE: options.toolMode ?? "full",
DEVSPACE_SUBAGENTS: options.localAgentProviders ? "1" : "0",
DEVSPACE_OAUTH_OWNER_TOKEN: "test-owner-token-that-is-long-enough",
PORT: "1",
});
const modeConfig: ServerConfig = {
...loadedConfig,
toolMode: options.toolMode ?? loadedConfig.toolMode,
};
const config: ServerConfig = options.localAgentProviders
? {
...loadedConfig,
...modeConfig,
subagents: options.subagents ?? {
enabled: true,
providers: initialProviderAvailability.map((provider) => ({
Expand All @@ -360,7 +359,7 @@ async function fixture(
})),
},
}
: loadedConfig;
: modeConfig;
const resolveProviderAvailability: () => LocalAgentProviderAvailability[] =
typeof options.localAgentProviders === "function"
? options.localAgentProviders
Expand Down
Loading
Loading