Various spelling improvements (#3497)
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com> Co-authored-by: Sandy Tao <sandytao520@icloud.com>
This commit is contained in:
parent
8be5f8038a
commit
dc2ac144b7
|
@ -29,7 +29,7 @@ body:
|
||||||
id: info
|
id: info
|
||||||
attributes:
|
attributes:
|
||||||
label: Client information
|
label: Client information
|
||||||
description: Please paste the full text from the `/about` command run from Gemini CLI. Also include which platform (MacOS, Windows, Linux).
|
description: Please paste the full text from the `/about` command run from Gemini CLI. Also include which platform (macOS, Windows, Linux).
|
||||||
value: |
|
value: |
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
## Dive Deeper
|
## Dive Deeper
|
||||||
|
|
||||||
<!-- more thoughts and in depth discussion here -->
|
<!-- more thoughts and in-depth discussion here -->
|
||||||
|
|
||||||
## Reviewer Test Plan
|
## Reviewer Test Plan
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ process_pr() {
|
||||||
ISSUE_NUMBER=$(echo "$PR_BODY" | grep -oE '#[0-9]+' | head -1 | sed 's/#//' 2>/dev/null || echo "")
|
ISSUE_NUMBER=$(echo "$PR_BODY" | grep -oE '#[0-9]+' | head -1 | sed 's/#//' 2>/dev/null || echo "")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Pattern 2: Closes/Fixes/Resolves patterns (case insensitive)
|
# Pattern 2: Closes/Fixes/Resolves patterns (case-insensitive)
|
||||||
if [ -z "$ISSUE_NUMBER" ]; then
|
if [ -z "$ISSUE_NUMBER" ]; then
|
||||||
ISSUE_NUMBER=$(echo "$PR_BODY" | grep -iE '(closes?|fixes?|resolves?) #[0-9]+' | grep -oE '#[0-9]+' | head -1 | sed 's/#//' 2>/dev/null || echo "")
|
ISSUE_NUMBER=$(echo "$PR_BODY" | grep -iE '(closes?|fixes?|resolves?) #[0-9]+' | grep -oE '#[0-9]+' | head -1 | sed 's/#//' 2>/dev/null || echo "")
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -272,19 +272,19 @@ To debug the CLI's React-based UI, you can use React DevTools. Ink, the library
|
||||||
|
|
||||||
## Sandboxing
|
## Sandboxing
|
||||||
|
|
||||||
### MacOS Seatbelt
|
### macOS Seatbelt
|
||||||
|
|
||||||
On MacOS, `gemini` uses Seatbelt (`sandbox-exec`) under a `permissive-open` profile (see `packages/cli/src/utils/sandbox-macos-permissive-open.sb`) that restricts writes to the project folder but otherwise allows all other operations and outbound network traffic ("open") by default. You can switch to a `restrictive-closed` profile (see `packages/cli/src/utils/sandbox-macos-restrictive-closed.sb`) that declines all operations and outbound network traffic ("closed") by default by setting `SEATBELT_PROFILE=restrictive-closed` in your environment or `.env` file. Available built-in profiles are `{permissive,restrictive}-{open,closed,proxied}` (see below for proxied networking). You can also switch to a custom profile `SEATBELT_PROFILE=<profile>` if you also create a file `.gemini/sandbox-macos-<profile>.sb` under your project settings directory `.gemini`.
|
On macOS, `gemini` uses Seatbelt (`sandbox-exec`) under a `permissive-open` profile (see `packages/cli/src/utils/sandbox-macos-permissive-open.sb`) that restricts writes to the project folder but otherwise allows all other operations and outbound network traffic ("open") by default. You can switch to a `restrictive-closed` profile (see `packages/cli/src/utils/sandbox-macos-restrictive-closed.sb`) that declines all operations and outbound network traffic ("closed") by default by setting `SEATBELT_PROFILE=restrictive-closed` in your environment or `.env` file. Available built-in profiles are `{permissive,restrictive}-{open,closed,proxied}` (see below for proxied networking). You can also switch to a custom profile `SEATBELT_PROFILE=<profile>` if you also create a file `.gemini/sandbox-macos-<profile>.sb` under your project settings directory `.gemini`.
|
||||||
|
|
||||||
### Container-based Sandboxing (All Platforms)
|
### Container-based Sandboxing (All Platforms)
|
||||||
|
|
||||||
For stronger container-based sandboxing on MacOS or other platforms, you can set `GEMINI_SANDBOX=true|docker|podman|<command>` in your environment or `.env` file. The specified command (or if `true` then either `docker` or `podman`) must be installed on the host machine. Once enabled, `npm run build:all` will build a minimal container ("sandbox") image and `npm start` will launch inside a fresh instance of that container. The first build can take 20-30s (mostly due to downloading of the base image) but after that both build and start overhead should be minimal. Default builds (`npm run build`) will not rebuild the sandbox.
|
For stronger container-based sandboxing on macOS or other platforms, you can set `GEMINI_SANDBOX=true|docker|podman|<command>` in your environment or `.env` file. The specified command (or if `true` then either `docker` or `podman`) must be installed on the host machine. Once enabled, `npm run build:all` will build a minimal container ("sandbox") image and `npm start` will launch inside a fresh instance of that container. The first build can take 20-30s (mostly due to downloading of the base image) but after that both build and start overhead should be minimal. Default builds (`npm run build`) will not rebuild the sandbox.
|
||||||
|
|
||||||
Container-based sandboxing mounts the project directory (and system temp directory) with read-write access and is started/stopped/removed automatically as you start/stop Gemini CLI. Files created within the sandbox should be automatically mapped to your user/group on host machine. You can easily specify additional mounts, ports, or environment variables by setting `SANDBOX_{MOUNTS,PORTS,ENV}` as needed. You can also fully customize the sandbox for your projects by creating the files `.gemini/sandbox.Dockerfile` and/or `.gemini/sandbox.bashrc` under your project settings directory (`.gemini`) and running `gemini` with `BUILD_SANDBOX=1` to trigger building of your custom sandbox.
|
Container-based sandboxing mounts the project directory (and system temp directory) with read-write access and is started/stopped/removed automatically as you start/stop Gemini CLI. Files created within the sandbox should be automatically mapped to your user/group on host machine. You can easily specify additional mounts, ports, or environment variables by setting `SANDBOX_{MOUNTS,PORTS,ENV}` as needed. You can also fully customize the sandbox for your projects by creating the files `.gemini/sandbox.Dockerfile` and/or `.gemini/sandbox.bashrc` under your project settings directory (`.gemini`) and running `gemini` with `BUILD_SANDBOX=1` to trigger building of your custom sandbox.
|
||||||
|
|
||||||
#### Proxied Networking
|
#### Proxied Networking
|
||||||
|
|
||||||
All sandboxing methods, including MacOS Seatbelt using `*-proxied` profiles, support restricting outbound network traffic through a custom proxy server that can be specified as `GEMINI_SANDBOX_PROXY_COMMAND=<command>`, where `<command>` must start a proxy server that listens on `:::8877` for relevant requests. See `docs/examples/proxy-script.md` for a minimal proxy that only allows `HTTPS` connections to `example.com:443` (e.g. `curl https://example.com`) and declines all other requests. The proxy is started and stopped automatically alongside the sandbox.
|
All sandboxing methods, including macOS Seatbelt using `*-proxied` profiles, support restricting outbound network traffic through a custom proxy server that can be specified as `GEMINI_SANDBOX_PROXY_COMMAND=<command>`, where `<command>` must start a proxy server that listens on `:::8877` for relevant requests. See `docs/examples/proxy-script.md` for a minimal proxy that only allows `HTTPS` connections to `example.com:443` (e.g. `curl https://example.com`) and declines all other requests. The proxy is started and stopped automatically alongside the sandbox.
|
||||||
|
|
||||||
## Manual Publish
|
## Manual Publish
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ With the Gemini CLI you can:
|
||||||
- Use tools and MCP servers to connect new capabilities, including [media generation with Imagen,
|
- Use tools and MCP servers to connect new capabilities, including [media generation with Imagen,
|
||||||
Veo or Lyria](https://github.com/GoogleCloudPlatform/vertex-ai-creative-studio/tree/main/experiments/mcp-genmedia)
|
Veo or Lyria](https://github.com/GoogleCloudPlatform/vertex-ai-creative-studio/tree/main/experiments/mcp-genmedia)
|
||||||
- Ground your queries with the [Google Search](https://ai.google.dev/gemini-api/docs/grounding)
|
- Ground your queries with the [Google Search](https://ai.google.dev/gemini-api/docs/grounding)
|
||||||
tool, built in to Gemini.
|
tool, built into Gemini.
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ The Gemini CLI requires you to authenticate with Google's AI services. On initia
|
||||||
1. You have been assigned a license to a current Gemini Code Assist standard or enterprise subscription.
|
1. You have been assigned a license to a current Gemini Code Assist standard or enterprise subscription.
|
||||||
1. You are using the product outside the [supported regions](https://developers.google.com/gemini-code-assist/resources/available-locations) for free individual usage.
|
1. You are using the product outside the [supported regions](https://developers.google.com/gemini-code-assist/resources/available-locations) for free individual usage.
|
||||||
1. You are a Google account holder under the age of 18
|
1. You are a Google account holder under the age of 18
|
||||||
- If you fall into one of these categories, you must first configure a Google Cloud Project Id to use, [enable the Gemini for Cloud API](https://cloud.google.com/gemini/docs/discover/set-up-gemini#enable-api) and [configure access permissions](https://cloud.google.com/gemini/docs/discover/set-up-gemini#grant-iam).
|
- If you fall into one of these categories, you must first configure a Google Cloud Project ID to use, [enable the Gemini for Cloud API](https://cloud.google.com/gemini/docs/discover/set-up-gemini#enable-api) and [configure access permissions](https://cloud.google.com/gemini/docs/discover/set-up-gemini#grant-iam).
|
||||||
|
|
||||||
You can temporarily set the environment variable in your current shell session using the following command:
|
You can temporarily set the environment variable in your current shell session using the following command:
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ The Gemini CLI core (`packages/core`) features a robust system for defining, reg
|
||||||
- `name`: A unique internal name (used in API calls to Gemini).
|
- `name`: A unique internal name (used in API calls to Gemini).
|
||||||
- `displayName`: A user-friendly name.
|
- `displayName`: A user-friendly name.
|
||||||
- `description`: A clear explanation of what the tool does, which is provided to the Gemini model.
|
- `description`: A clear explanation of what the tool does, which is provided to the Gemini model.
|
||||||
- `parameterSchema`: A JSON schema defining the parameters the tool accepts. This is crucial for the Gemini model to understand how to call the tool correctly.
|
- `parameterSchema`: A JSON schema defining the parameters that the tool accepts. This is crucial for the Gemini model to understand how to call the tool correctly.
|
||||||
- `validateToolParams()`: A method to validate incoming parameters.
|
- `validateToolParams()`: A method to validate incoming parameters.
|
||||||
- `getDescription()`: A method to provide a human-readable description of what the tool will do with specific parameters before execution.
|
- `getDescription()`: A method to provide a human-readable description of what the tool will do with specific parameters before execution.
|
||||||
- `shouldConfirmExecute()`: A method to determine if user confirmation is required before execution (e.g., for potentially destructive operations).
|
- `shouldConfirmExecute()`: A method to determine if user confirmation is required before execution (e.g., for potentially destructive operations).
|
||||||
|
|
|
@ -90,7 +90,7 @@ The Gemini CLI provides a comprehensive suite of tools for interacting with the
|
||||||
- `path` (string, optional): The absolute path to the directory to search within. Defaults to the current working directory.
|
- `path` (string, optional): The absolute path to the directory to search within. Defaults to the current working directory.
|
||||||
- `include` (string, optional): A glob pattern to filter which files are searched (e.g., `"*.js"`, `"src/**/*.{ts,tsx}"`). If omitted, searches most files (respecting common ignores).
|
- `include` (string, optional): A glob pattern to filter which files are searched (e.g., `"*.js"`, `"src/**/*.{ts,tsx}"`). If omitted, searches most files (respecting common ignores).
|
||||||
- **Behavior:**
|
- **Behavior:**
|
||||||
- Uses `git grep` if available in a Git repository for speed, otherwise falls back to system `grep` or a JavaScript-based search.
|
- Uses `git grep` if available in a Git repository for speed; otherwise, falls back to system `grep` or a JavaScript-based search.
|
||||||
- Returns a list of matching lines, each prefixed with its file path (relative to the search directory) and line number.
|
- Returns a list of matching lines, each prefixed with its file path (relative to the search directory) and line number.
|
||||||
- **Output (`llmContent`):** A formatted string of matches, e.g.:
|
- **Output (`llmContent`):** A formatted string of matches, e.g.:
|
||||||
```
|
```
|
||||||
|
|
|
@ -27,7 +27,7 @@ This guide provides solutions to common issues and debugging tips.
|
||||||
## Common error messages and solutions
|
## Common error messages and solutions
|
||||||
|
|
||||||
- **Error: `EADDRINUSE` (Address already in use) when starting an MCP server.**
|
- **Error: `EADDRINUSE` (Address already in use) when starting an MCP server.**
|
||||||
- **Cause:** Another process is already using the port the MCP server is trying to bind to.
|
- **Cause:** Another process is already using the port that the MCP server is trying to bind to.
|
||||||
- **Solution:**
|
- **Solution:**
|
||||||
Either stop the other process that is using the port or configure the MCP server to use a different port.
|
Either stop the other process that is using the port or configure the MCP server to use a different port.
|
||||||
|
|
||||||
|
|
|
@ -229,14 +229,14 @@ describe('runNonInteractive', () => {
|
||||||
it('should not exit if a tool is not found, and should send error back to model', async () => {
|
it('should not exit if a tool is not found, and should send error back to model', async () => {
|
||||||
const functionCall: FunctionCall = {
|
const functionCall: FunctionCall = {
|
||||||
id: 'fcNotFound',
|
id: 'fcNotFound',
|
||||||
name: 'nonExistentTool',
|
name: 'nonexistentTool',
|
||||||
args: {},
|
args: {},
|
||||||
};
|
};
|
||||||
const errorResponsePart: Part = {
|
const errorResponsePart: Part = {
|
||||||
functionResponse: {
|
functionResponse: {
|
||||||
name: 'nonExistentTool',
|
name: 'nonexistentTool',
|
||||||
id: 'fcNotFound',
|
id: 'fcNotFound',
|
||||||
response: { error: 'Tool "nonExistentTool" not found in registry.' },
|
response: { error: 'Tool "nonexistentTool" not found in registry.' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -246,8 +246,8 @@ describe('runNonInteractive', () => {
|
||||||
vi.mocked(mockCoreExecuteToolCall).mockResolvedValue({
|
vi.mocked(mockCoreExecuteToolCall).mockResolvedValue({
|
||||||
callId: 'fcNotFound',
|
callId: 'fcNotFound',
|
||||||
responseParts: [errorResponsePart],
|
responseParts: [errorResponsePart],
|
||||||
resultDisplay: 'Tool "nonExistentTool" not found in registry.',
|
resultDisplay: 'Tool "nonexistentTool" not found in registry.',
|
||||||
error: new Error('Tool "nonExistentTool" not found in registry.'),
|
error: new Error('Tool "nonexistentTool" not found in registry.'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const stream1 = (async function* () {
|
const stream1 = (async function* () {
|
||||||
|
@ -278,7 +278,7 @@ describe('runNonInteractive', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||||
'Error executing tool nonExistentTool: Tool "nonExistentTool" not found in registry.',
|
'Error executing tool nonexistentTool: Tool "nonexistentTool" not found in registry.',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mockProcessExit).not.toHaveBeenCalled();
|
expect(mockProcessExit).not.toHaveBeenCalled();
|
||||||
|
|
|
@ -298,7 +298,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
||||||
config.getContentGeneratorConfig().authType ===
|
config.getContentGeneratorConfig().authType ===
|
||||||
AuthType.LOGIN_WITH_GOOGLE
|
AuthType.LOGIN_WITH_GOOGLE
|
||||||
) {
|
) {
|
||||||
// Use actual user tier if available, otherwise default to FREE tier behavior (safe default)
|
// Use actual user tier if available; otherwise, default to FREE tier behavior (safe default)
|
||||||
const isPaidTier =
|
const isPaidTier =
|
||||||
userTier === UserTierId.LEGACY || userTier === UserTierId.STANDARD;
|
userTier === UserTierId.LEGACY || userTier === UserTierId.STANDARD;
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ export const Footer: React.FC<FooterProps> = ({
|
||||||
</Text>
|
</Text>
|
||||||
) : process.env.SANDBOX === 'sandbox-exec' ? (
|
) : process.env.SANDBOX === 'sandbox-exec' ? (
|
||||||
<Text color={Colors.AccentYellow}>
|
<Text color={Colors.AccentYellow}>
|
||||||
MacOS Seatbelt{' '}
|
macOS Seatbelt{' '}
|
||||||
<Text color={Colors.Gray}>({process.env.SEATBELT_PROFILE})</Text>
|
<Text color={Colors.Gray}>({process.env.SEATBELT_PROFILE})</Text>
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -448,7 +448,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to the text buffer's default input handling for all other keys
|
// Fall back to the text buffer's default input handling for all other keys
|
||||||
buffer.handleInput(key);
|
buffer.handleInput(key);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
|
|
@ -73,7 +73,7 @@ export function ThemeDialog({
|
||||||
const initialThemeIndex = themeItems.findIndex(
|
const initialThemeIndex = themeItems.findIndex(
|
||||||
(item) => item.value === selectedThemeName,
|
(item) => item.value === selectedThemeName,
|
||||||
);
|
);
|
||||||
// If not found, fallback to the first theme
|
// If not found, fall back to the first theme
|
||||||
const safeInitialThemeIndex = initialThemeIndex >= 0 ? initialThemeIndex : 0;
|
const safeInitialThemeIndex = initialThemeIndex >= 0 ? initialThemeIndex : 0;
|
||||||
|
|
||||||
const scopeItems = [
|
const scopeItems = [
|
||||||
|
@ -185,7 +185,7 @@ export function ThemeDialog({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't focus the scope selection if it is hidden due to height constraints.
|
// Don't focus the scope selection if it is hidden due to height constraints.
|
||||||
const currenFocusedSection = !showScopeSelection ? 'theme' : focusedSection;
|
const currentFocusedSection = !showScopeSelection ? 'theme' : focusedSection;
|
||||||
|
|
||||||
// Vertical space taken by elements other than the two code blocks in the preview pane.
|
// Vertical space taken by elements other than the two code blocks in the preview pane.
|
||||||
// Includes "Preview" title, borders, and margin between blocks.
|
// Includes "Preview" title, borders, and margin between blocks.
|
||||||
|
@ -224,8 +224,8 @@ export function ThemeDialog({
|
||||||
<Box flexDirection="row">
|
<Box flexDirection="row">
|
||||||
{/* Left Column: Selection */}
|
{/* Left Column: Selection */}
|
||||||
<Box flexDirection="column" width="45%" paddingRight={2}>
|
<Box flexDirection="column" width="45%" paddingRight={2}>
|
||||||
<Text bold={currenFocusedSection === 'theme'} wrap="truncate">
|
<Text bold={currentFocusedSection === 'theme'} wrap="truncate">
|
||||||
{currenFocusedSection === 'theme' ? '> ' : ' '}Select Theme{' '}
|
{currentFocusedSection === 'theme' ? '> ' : ' '}Select Theme{' '}
|
||||||
<Text color={Colors.Gray}>{otherScopeModifiedMessage}</Text>
|
<Text color={Colors.Gray}>{otherScopeModifiedMessage}</Text>
|
||||||
</Text>
|
</Text>
|
||||||
<RadioButtonSelect
|
<RadioButtonSelect
|
||||||
|
@ -234,25 +234,25 @@ export function ThemeDialog({
|
||||||
initialIndex={safeInitialThemeIndex}
|
initialIndex={safeInitialThemeIndex}
|
||||||
onSelect={handleThemeSelect}
|
onSelect={handleThemeSelect}
|
||||||
onHighlight={handleThemeHighlight}
|
onHighlight={handleThemeHighlight}
|
||||||
isFocused={currenFocusedSection === 'theme'}
|
isFocused={currentFocusedSection === 'theme'}
|
||||||
maxItemsToShow={8}
|
maxItemsToShow={8}
|
||||||
showScrollArrows={true}
|
showScrollArrows={true}
|
||||||
showNumbers={currenFocusedSection === 'theme'}
|
showNumbers={currentFocusedSection === 'theme'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Scope Selection */}
|
{/* Scope Selection */}
|
||||||
{showScopeSelection && (
|
{showScopeSelection && (
|
||||||
<Box marginTop={1} flexDirection="column">
|
<Box marginTop={1} flexDirection="column">
|
||||||
<Text bold={currenFocusedSection === 'scope'} wrap="truncate">
|
<Text bold={currentFocusedSection === 'scope'} wrap="truncate">
|
||||||
{currenFocusedSection === 'scope' ? '> ' : ' '}Apply To
|
{currentFocusedSection === 'scope' ? '> ' : ' '}Apply To
|
||||||
</Text>
|
</Text>
|
||||||
<RadioButtonSelect
|
<RadioButtonSelect
|
||||||
items={scopeItems}
|
items={scopeItems}
|
||||||
initialIndex={0} // Default to User Settings
|
initialIndex={0} // Default to User Settings
|
||||||
onSelect={handleScopeSelect}
|
onSelect={handleScopeSelect}
|
||||||
onHighlight={handleScopeHighlight}
|
onHighlight={handleScopeHighlight}
|
||||||
isFocused={currenFocusedSection === 'scope'}
|
isFocused={currentFocusedSection === 'scope'}
|
||||||
showNumbers={currenFocusedSection === 'scope'}
|
showNumbers={currentFocusedSection === 'scope'}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
@ -261,7 +261,7 @@ export function ThemeDialog({
|
||||||
{/* Right Column: Preview */}
|
{/* Right Column: Preview */}
|
||||||
<Box flexDirection="column" width="55%" paddingLeft={2}>
|
<Box flexDirection="column" width="55%" paddingLeft={2}>
|
||||||
<Text bold>Preview</Text>
|
<Text bold>Preview</Text>
|
||||||
{/* Get the Theme object for the highlighted theme, fallback to default if not found */}
|
{/* Get the Theme object for the highlighted theme, fall back to default if not found */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const previewTheme =
|
const previewTheme =
|
||||||
themeManager.getTheme(
|
themeManager.getTheme(
|
||||||
|
|
|
@ -146,7 +146,7 @@ function executeShellCommand(
|
||||||
process.kill(-child.pid, 'SIGKILL');
|
process.kill(-child.pid, 'SIGKILL');
|
||||||
}
|
}
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
// Fallback to killing just the main process if group kill fails.
|
// Fall back to killing just the main process if group kill fails.
|
||||||
if (!exited) child.kill('SIGKILL');
|
if (!exited) child.kill('SIGKILL');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@ describe('useHistoryManager', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not change history if updateHistoryItem is called with a non-existent ID', () => {
|
it('should not change history if updateHistoryItem is called with a nonexistent ID', () => {
|
||||||
const { result } = renderHook(() => useHistory());
|
const { result } = renderHook(() => useHistory());
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const itemData: Omit<HistoryItem, 'id'> = {
|
const itemData: Omit<HistoryItem, 'id'> = {
|
||||||
|
@ -107,7 +107,7 @@ describe('useHistoryManager', () => {
|
||||||
const originalHistory = [...result.current.history]; // Clone before update attempt
|
const originalHistory = [...result.current.history]; // Clone before update attempt
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
result.current.updateItem(99999, { text: 'Should not apply' }); // Non-existent ID
|
result.current.updateItem(99999, { text: 'Should not apply' }); // Nonexistent ID
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.current.history).toEqual(originalHistory);
|
expect(result.current.history).toEqual(originalHistory);
|
||||||
|
|
|
@ -67,7 +67,7 @@ describe('useShellHistory', () => {
|
||||||
expect(command).toBe('cmd2');
|
expect(command).toBe('cmd2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle a non-existent history file gracefully', async () => {
|
it('should handle a nonexistent history file gracefully', async () => {
|
||||||
const error = new Error('File not found') as NodeJS.ErrnoException;
|
const error = new Error('File not found') as NodeJS.ErrnoException;
|
||||||
error.code = 'ENOENT';
|
error.code = 'ENOENT';
|
||||||
mockedFs.readFile.mockRejectedValue(error);
|
mockedFs.readFile.mockRejectedValue(error);
|
||||||
|
|
|
@ -341,7 +341,7 @@ describe('useReactToolScheduler', () => {
|
||||||
const schedule = result.current[1];
|
const schedule = result.current[1];
|
||||||
const request: ToolCallRequestInfo = {
|
const request: ToolCallRequestInfo = {
|
||||||
callId: 'call1',
|
callId: 'call1',
|
||||||
name: 'nonExistentTool',
|
name: 'nonexistentTool',
|
||||||
args: {},
|
args: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -361,7 +361,7 @@ describe('useReactToolScheduler', () => {
|
||||||
request,
|
request,
|
||||||
response: expect.objectContaining({
|
response: expect.objectContaining({
|
||||||
error: expect.objectContaining({
|
error: expect.objectContaining({
|
||||||
message: 'Tool "nonExistentTool" not found in registry.',
|
message: 'Tool "nonexistentTool" not found in registry.',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shades of Purple Theme — for Highlightjs.
|
* Shades of Purple Theme — for Highlight.js.
|
||||||
* @author Ahmad Awais <https://twitter.com/mrahmadawais/>
|
* @author Ahmad Awais <https://twitter.com/mrahmadawais/>
|
||||||
*/
|
*/
|
||||||
import { type ColorsTheme, Theme } from './theme.js';
|
import { type ColorsTheme, Theme } from './theme.js';
|
||||||
|
|
|
@ -85,7 +85,7 @@ describe('ThemeManager', () => {
|
||||||
expect(themeManager.getTheme('MyCustomTheme')).toBeDefined();
|
expect(themeManager.getTheme('MyCustomTheme')).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fallback to default theme if active theme is invalid', () => {
|
it('should fall back to default theme if active theme is invalid', () => {
|
||||||
(themeManager as unknown as { activeTheme: unknown }).activeTheme = {
|
(themeManager as unknown as { activeTheme: unknown }).activeTheme = {
|
||||||
name: 'NonExistent',
|
name: 'NonExistent',
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
|
|
|
@ -117,7 +117,7 @@ class ThemeManager {
|
||||||
if (process.env.NO_COLOR) {
|
if (process.env.NO_COLOR) {
|
||||||
return NoColorTheme;
|
return NoColorTheme;
|
||||||
}
|
}
|
||||||
// Ensure the active theme is always valid (fallback to default if not)
|
// Ensure the active theme is always valid (fall back to default if not)
|
||||||
if (!this.activeTheme || !this.findThemeByName(this.activeTheme.name)) {
|
if (!this.activeTheme || !this.findThemeByName(this.activeTheme.name)) {
|
||||||
this.activeTheme = DEFAULT_THEME;
|
this.activeTheme = DEFAULT_THEME;
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,7 +151,7 @@ export class Theme {
|
||||||
inkTheme[key] = resolvedColor;
|
inkTheme[key] = resolvedColor;
|
||||||
}
|
}
|
||||||
// If color is not resolvable, it's omitted from the map,
|
// If color is not resolvable, it's omitted from the map,
|
||||||
// allowing fallback to the default foreground color.
|
// this enables falling back to the default foreground color.
|
||||||
}
|
}
|
||||||
// We currently only care about the 'color' property for Ink rendering.
|
// We currently only care about the 'color' property for Ink rendering.
|
||||||
// Other properties like background, fontStyle, etc., are ignored.
|
// Other properties like background, fontStyle, etc., are ignored.
|
||||||
|
|
|
@ -50,7 +50,7 @@ function renderHastNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the color to pass down: Use this element's specific color
|
// Determine the color to pass down: Use this element's specific color
|
||||||
// if found, otherwise, continue passing down the already inherited color.
|
// if found; otherwise, continue passing down the already inherited color.
|
||||||
const colorToPassDown = elementColor || inheritedColor;
|
const colorToPassDown = elementColor || inheritedColor;
|
||||||
|
|
||||||
// Recursively render children, passing the determined color down
|
// Recursively render children, passing the determined color down
|
||||||
|
@ -70,7 +70,7 @@ function renderHastNode(
|
||||||
|
|
||||||
// Handle Root Node: Start recursion with initially inherited color
|
// Handle Root Node: Start recursion with initially inherited color
|
||||||
if (node.type === 'root') {
|
if (node.type === 'root') {
|
||||||
// Check if children array is empty - this happens when lowlight can't detect language – fallback to plain text
|
// Check if children array is empty - this happens when lowlight can't detect language – fall back to plain text
|
||||||
if (!node.children || node.children.length === 0) {
|
if (!node.children || node.children.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -161,7 +161,7 @@ export function colorizeCode(
|
||||||
`[colorizeCode] Error highlighting code for language "${language}":`,
|
`[colorizeCode] Error highlighting code for language "${language}":`,
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
// Fallback to plain text with default color on error
|
// Fall back to plain text with default color on error
|
||||||
// Also display line numbers in fallback
|
// Also display line numbers in fallback
|
||||||
const lines = codeToHighlight.split('\n');
|
const lines = codeToHighlight.split('\n');
|
||||||
const padWidth = String(lines.length).length; // Calculate padding width based on number of lines
|
const padWidth = String(lines.length).length; // Calculate padding width based on number of lines
|
||||||
|
|
|
@ -200,7 +200,7 @@ describe('commandUtils', () => {
|
||||||
expect(mockChild.stdin.end).toHaveBeenCalled();
|
expect(mockChild.stdin.end).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fallback to xsel when xclip fails', async () => {
|
it('should fall back to xsel when xclip fails', async () => {
|
||||||
const testText = 'Hello, world!';
|
const testText = 'Hello, world!';
|
||||||
let callCount = 0;
|
let callCount = 0;
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ This function aims to find an *intelligent* or "safe" index within the provided
|
||||||
* **Single Line Breaks:** If no double newline is found in a suitable range, it will look for a single newline (`\n`).
|
* **Single Line Breaks:** If no double newline is found in a suitable range, it will look for a single newline (`\n`).
|
||||||
* Any newline chosen as a split point must also not be inside a code block.
|
* Any newline chosen as a split point must also not be inside a code block.
|
||||||
|
|
||||||
4. **Fallback to `idealMaxLength`:**
|
4. **Fall back to `idealMaxLength`:**
|
||||||
* If no "safer" split point (respecting code blocks or finding suitable newlines) is identified before or at `idealMaxLength`, and `idealMaxLength` itself is not determined to be an unsafe split point (e.g., inside a code block), the function may return a length larger than `idealMaxLength`, again it CANNOT break markdown formatting. This could happen with very long lines of text without Markdown block structures or newlines.
|
* If no "safer" split point (respecting code blocks or finding suitable newlines) is identified before or at `idealMaxLength`, and `idealMaxLength` itself is not determined to be an unsafe split point (e.g., inside a code block), the function may return a length larger than `idealMaxLength`, again it CANNOT break markdown formatting. This could happen with very long lines of text without Markdown block structures or newlines.
|
||||||
|
|
||||||
**In essence, `findSafeSplitPoint` tries to be a good Markdown citizen when forced to divide content, preferring structural boundaries over arbitrary character limits, with a strong emphasis on not corrupting code blocks.**
|
**In essence, `findSafeSplitPoint` tries to be a good Markdown citizen when forced to divide content, preferring structural boundaries over arbitrary character limits, with a strong emphasis on not corrupting code blocks.**
|
||||||
|
|
|
@ -99,7 +99,7 @@ async function shouldUseCurrentUserInSandbox(): Promise<boolean> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// docker does not allow container names to contain ':' or '/', so we
|
// docker does not allow container names to contain ':' or '/', so we
|
||||||
// parse those out and make the name a little shorter
|
// parse those out to shorten the name
|
||||||
function parseImageName(image: string): string {
|
function parseImageName(image: string): string {
|
||||||
const [fullName, tag] = image.split(':');
|
const [fullName, tag] = image.split(':');
|
||||||
const name = fullName.split('/').at(-1) ?? 'unknown-image';
|
const name = fullName.split('/').at(-1) ?? 'unknown-image';
|
||||||
|
@ -187,7 +187,7 @@ export async function start_sandbox(
|
||||||
if (config.command === 'sandbox-exec') {
|
if (config.command === 'sandbox-exec') {
|
||||||
// disallow BUILD_SANDBOX
|
// disallow BUILD_SANDBOX
|
||||||
if (process.env.BUILD_SANDBOX) {
|
if (process.env.BUILD_SANDBOX) {
|
||||||
console.error('ERROR: cannot BUILD_SANDBOX when using MacOS Seatbelt');
|
console.error('ERROR: cannot BUILD_SANDBOX when using macOS Seatbelt');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
const profile = (process.env.SEATBELT_PROFILE ??= 'permissive-open');
|
const profile = (process.env.SEATBELT_PROFILE ??= 'permissive-open');
|
||||||
|
@ -847,7 +847,7 @@ async function ensureSandboxImageIsPresent(
|
||||||
|
|
||||||
console.info(`Sandbox image ${image} not found locally.`);
|
console.info(`Sandbox image ${image} not found locally.`);
|
||||||
if (image === LOCAL_DEV_SANDBOX_IMAGE_NAME) {
|
if (image === LOCAL_DEV_SANDBOX_IMAGE_NAME) {
|
||||||
// user needs to build the image themself
|
// user needs to build the image themselves
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
CodeAssistGlobalUserSettingResponse,
|
CodeAssistGlobalUserSettingResponse,
|
||||||
LoadCodeAssistRequest,
|
LoadCodeAssistRequest,
|
||||||
LoadCodeAssistResponse,
|
LoadCodeAssistResponse,
|
||||||
LongrunningOperationResponse,
|
LongRunningOperationResponse,
|
||||||
OnboardUserRequest,
|
OnboardUserRequest,
|
||||||
SetCodeAssistGlobalUserSettingRequest,
|
SetCodeAssistGlobalUserSettingRequest,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
@ -79,8 +79,8 @@ export class CodeAssistServer implements ContentGenerator {
|
||||||
|
|
||||||
async onboardUser(
|
async onboardUser(
|
||||||
req: OnboardUserRequest,
|
req: OnboardUserRequest,
|
||||||
): Promise<LongrunningOperationResponse> {
|
): Promise<LongRunningOperationResponse> {
|
||||||
return await this.requestPost<LongrunningOperationResponse>(
|
return await this.requestPost<LongRunningOperationResponse>(
|
||||||
'onboardUser',
|
'onboardUser',
|
||||||
req,
|
req,
|
||||||
);
|
);
|
||||||
|
|
|
@ -127,10 +127,10 @@ export interface OnboardUserRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents LongrunningOperation proto
|
* Represents LongRunningOperation proto
|
||||||
* http://google3/google/longrunning/operations.proto;rcl=698857719;l=107
|
* http://google3/google/longrunning/operations.proto;rcl=698857719;l=107
|
||||||
*/
|
*/
|
||||||
export interface LongrunningOperationResponse {
|
export interface LongRunningOperationResponse {
|
||||||
name: string;
|
name: string;
|
||||||
done?: boolean;
|
done?: boolean;
|
||||||
response?: OnboardUserResponse;
|
response?: OnboardUserResponse;
|
||||||
|
|
|
@ -72,7 +72,7 @@ describe('Flash Model Fallback Configuration', () => {
|
||||||
expect(config.getModel()).toBe(DEFAULT_GEMINI_FLASH_MODEL);
|
expect(config.getModel()).toBe(DEFAULT_GEMINI_FLASH_MODEL);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fallback to initial model if contentGeneratorConfig is not available', () => {
|
it('should fall back to initial model if contentGeneratorConfig is not available', () => {
|
||||||
// Test with fresh config where contentGeneratorConfig might not be set
|
// Test with fresh config where contentGeneratorConfig might not be set
|
||||||
const newConfig = new Config({
|
const newConfig = new Config({
|
||||||
sessionId: 'test-session-2',
|
sessionId: 'test-session-2',
|
||||||
|
|
|
@ -798,8 +798,8 @@ When requested to perform tasks like fixing bugs, adding features, refactoring,
|
||||||
- **Feedback:** To report a bug or provide feedback, please use the /bug command.
|
- **Feedback:** To report a bug or provide feedback, please use the /bug command.
|
||||||
|
|
||||||
|
|
||||||
# MacOS Seatbelt
|
# macOS Seatbelt
|
||||||
You are running under macos seatbelt with limited access to files outside the project directory or system temp directory, and with limited access to host system resources such as ports. If you encounter failures that could be due to MacOS Seatbelt (e.g. if a command fails with 'Operation not permitted' or similar error), as you report the error to the user, also explain why you think it could be due to MacOS Seatbelt, and how the user may need to adjust their Seatbelt profile.
|
You are running under macos seatbelt with limited access to files outside the project directory or system temp directory, and with limited access to host system resources such as ports. If you encounter failures that could be due to macOS Seatbelt (e.g. if a command fails with 'Operation not permitted' or similar error), as you report the error to the user, also explain why you think it could be due to macOS Seatbelt, and how the user may need to adjust their Seatbelt profile.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -665,8 +665,8 @@ export class GeminiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles fallback to Flash model when persistent 429 errors occur for OAuth users.
|
* Handles falling back to Flash model when persistent 429 errors occur for OAuth users.
|
||||||
* Uses a fallback handler if provided by the config, otherwise returns null.
|
* Uses a fallback handler if provided by the config; otherwise, returns null.
|
||||||
*/
|
*/
|
||||||
private async handleFlashFallback(
|
private async handleFlashFallback(
|
||||||
authType?: string,
|
authType?: string,
|
||||||
|
|
|
@ -62,7 +62,7 @@ export function createContentGeneratorConfig(
|
||||||
const googleCloudProject = process.env.GOOGLE_CLOUD_PROJECT || undefined;
|
const googleCloudProject = process.env.GOOGLE_CLOUD_PROJECT || undefined;
|
||||||
const googleCloudLocation = process.env.GOOGLE_CLOUD_LOCATION || undefined;
|
const googleCloudLocation = process.env.GOOGLE_CLOUD_LOCATION || undefined;
|
||||||
|
|
||||||
// Use runtime model from config if available, otherwise fallback to parameter or default
|
// Use runtime model from config if available; otherwise, fall back to parameter or default
|
||||||
const effectiveModel = config.getModel() || DEFAULT_GEMINI_MODEL;
|
const effectiveModel = config.getModel() || DEFAULT_GEMINI_MODEL;
|
||||||
|
|
||||||
const contentGeneratorConfig: ContentGeneratorConfig = {
|
const contentGeneratorConfig: ContentGeneratorConfig = {
|
||||||
|
|
|
@ -201,8 +201,8 @@ export class GeminiChat {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles fallback to Flash model when persistent 429 errors occur for OAuth users.
|
* Handles falling back to Flash model when persistent 429 errors occur for OAuth users.
|
||||||
* Uses a fallback handler if provided by the config, otherwise returns null.
|
* Uses a fallback handler if provided by the config; otherwise, returns null.
|
||||||
*/
|
*/
|
||||||
private async handleFlashFallback(
|
private async handleFlashFallback(
|
||||||
authType?: string,
|
authType?: string,
|
||||||
|
|
|
@ -453,7 +453,7 @@ describe('Logger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an empty array if a tagged checkpoint file does not exist', async () => {
|
it('should return an empty array if a tagged checkpoint file does not exist', async () => {
|
||||||
const loaded = await logger.loadCheckpoint('non-existent-tag');
|
const loaded = await logger.loadCheckpoint('nonexistent-tag');
|
||||||
expect(loaded).toEqual([]);
|
expect(loaded).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ describe('executeToolCall', () => {
|
||||||
it('should return an error if tool is not found', async () => {
|
it('should return an error if tool is not found', async () => {
|
||||||
const request: ToolCallRequestInfo = {
|
const request: ToolCallRequestInfo = {
|
||||||
callId: 'call2',
|
callId: 'call2',
|
||||||
name: 'nonExistentTool',
|
name: 'nonexistentTool',
|
||||||
args: {},
|
args: {},
|
||||||
isClientInitiated: false,
|
isClientInitiated: false,
|
||||||
prompt_id: 'prompt-id-2',
|
prompt_id: 'prompt-id-2',
|
||||||
|
@ -123,17 +123,17 @@ describe('executeToolCall', () => {
|
||||||
expect(response.callId).toBe('call2');
|
expect(response.callId).toBe('call2');
|
||||||
expect(response.error).toBeInstanceOf(Error);
|
expect(response.error).toBeInstanceOf(Error);
|
||||||
expect(response.error?.message).toBe(
|
expect(response.error?.message).toBe(
|
||||||
'Tool "nonExistentTool" not found in registry.',
|
'Tool "nonexistentTool" not found in registry.',
|
||||||
);
|
);
|
||||||
expect(response.resultDisplay).toBe(
|
expect(response.resultDisplay).toBe(
|
||||||
'Tool "nonExistentTool" not found in registry.',
|
'Tool "nonexistentTool" not found in registry.',
|
||||||
);
|
);
|
||||||
expect(response.responseParts).toEqual([
|
expect(response.responseParts).toEqual([
|
||||||
{
|
{
|
||||||
functionResponse: {
|
functionResponse: {
|
||||||
name: 'nonExistentTool',
|
name: 'nonexistentTool',
|
||||||
id: 'call2',
|
id: 'call2',
|
||||||
response: { error: 'Tool "nonExistentTool" not found in registry.' },
|
response: { error: 'Tool "nonexistentTool" not found in registry.' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -67,7 +67,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||||
vi.stubEnv('SANDBOX', 'true'); // Generic sandbox value
|
vi.stubEnv('SANDBOX', 'true'); // Generic sandbox value
|
||||||
const prompt = getCoreSystemPrompt();
|
const prompt = getCoreSystemPrompt();
|
||||||
expect(prompt).toContain('# Sandbox');
|
expect(prompt).toContain('# Sandbox');
|
||||||
expect(prompt).not.toContain('# MacOS Seatbelt');
|
expect(prompt).not.toContain('# macOS Seatbelt');
|
||||||
expect(prompt).not.toContain('# Outside of Sandbox');
|
expect(prompt).not.toContain('# Outside of Sandbox');
|
||||||
expect(prompt).toMatchSnapshot();
|
expect(prompt).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -75,7 +75,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||||
it('should include seatbelt-specific instructions when SANDBOX env var is "sandbox-exec"', () => {
|
it('should include seatbelt-specific instructions when SANDBOX env var is "sandbox-exec"', () => {
|
||||||
vi.stubEnv('SANDBOX', 'sandbox-exec');
|
vi.stubEnv('SANDBOX', 'sandbox-exec');
|
||||||
const prompt = getCoreSystemPrompt();
|
const prompt = getCoreSystemPrompt();
|
||||||
expect(prompt).toContain('# MacOS Seatbelt');
|
expect(prompt).toContain('# macOS Seatbelt');
|
||||||
expect(prompt).not.toContain('# Sandbox');
|
expect(prompt).not.toContain('# Sandbox');
|
||||||
expect(prompt).not.toContain('# Outside of Sandbox');
|
expect(prompt).not.toContain('# Outside of Sandbox');
|
||||||
expect(prompt).toMatchSnapshot();
|
expect(prompt).toMatchSnapshot();
|
||||||
|
@ -86,7 +86,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||||
const prompt = getCoreSystemPrompt();
|
const prompt = getCoreSystemPrompt();
|
||||||
expect(prompt).toContain('# Outside of Sandbox');
|
expect(prompt).toContain('# Outside of Sandbox');
|
||||||
expect(prompt).not.toContain('# Sandbox');
|
expect(prompt).not.toContain('# Sandbox');
|
||||||
expect(prompt).not.toContain('# MacOS Seatbelt');
|
expect(prompt).not.toContain('# macOS Seatbelt');
|
||||||
expect(prompt).toMatchSnapshot();
|
expect(prompt).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -116,8 +116,8 @@ ${(function () {
|
||||||
|
|
||||||
if (isSandboxExec) {
|
if (isSandboxExec) {
|
||||||
return `
|
return `
|
||||||
# MacOS Seatbelt
|
# macOS Seatbelt
|
||||||
You are running under macos seatbelt with limited access to files outside the project directory or system temp directory, and with limited access to host system resources such as ports. If you encounter failures that could be due to MacOS Seatbelt (e.g. if a command fails with 'Operation not permitted' or similar error), as you report the error to the user, also explain why you think it could be due to MacOS Seatbelt, and how the user may need to adjust their Seatbelt profile.
|
You are running under macos seatbelt with limited access to files outside the project directory or system temp directory, and with limited access to host system resources such as ports. If you encounter failures that could be due to macOS Seatbelt (e.g. if a command fails with 'Operation not permitted' or similar error), as you report the error to the user, also explain why you think it could be due to macOS Seatbelt, and how the user may need to adjust their Seatbelt profile.
|
||||||
`;
|
`;
|
||||||
} else if (isGenericSandbox) {
|
} else if (isGenericSandbox) {
|
||||||
return `
|
return `
|
||||||
|
|
|
@ -86,7 +86,7 @@ export function createIdeContextStore() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the current active file context.
|
* Retrieves the current active file context.
|
||||||
* @returns The `OpenFiles` object if a file is active, otherwise `undefined`.
|
* @returns The `OpenFiles` object if a file is active; otherwise, `undefined`.
|
||||||
*/
|
*/
|
||||||
function getOpenFilesContext(): OpenFiles | undefined {
|
function getOpenFilesContext(): OpenFiles | undefined {
|
||||||
return openFilesContext;
|
return openFilesContext;
|
||||||
|
|
|
@ -209,7 +209,7 @@ Expectation for required parameters:
|
||||||
// Creating a new file
|
// Creating a new file
|
||||||
isNewFile = true;
|
isNewFile = true;
|
||||||
} else if (!fileExists) {
|
} else if (!fileExists) {
|
||||||
// Trying to edit a non-existent file (and old_string is not empty)
|
// Trying to edit a nonexistent file (and old_string is not empty)
|
||||||
error = {
|
error = {
|
||||||
display: `File not found. Cannot apply edit. Use an empty old_string to create a new file.`,
|
display: `File not found. Cannot apply edit. Use an empty old_string to create a new file.`,
|
||||||
raw: `File not found: ${params.file_path}`,
|
raw: `File not found: ${params.file_path}`,
|
||||||
|
@ -239,12 +239,12 @@ Expectation for required parameters:
|
||||||
raw: `Failed to edit, 0 occurrences found for old_string in ${params.file_path}. No edits made. The exact text in old_string was not found. Ensure you're not escaping content incorrectly and check whitespace, indentation, and context. Use ${ReadFileTool.Name} tool to verify.`,
|
raw: `Failed to edit, 0 occurrences found for old_string in ${params.file_path}. No edits made. The exact text in old_string was not found. Ensure you're not escaping content incorrectly and check whitespace, indentation, and context. Use ${ReadFileTool.Name} tool to verify.`,
|
||||||
};
|
};
|
||||||
} else if (occurrences !== expectedReplacements) {
|
} else if (occurrences !== expectedReplacements) {
|
||||||
const occurenceTerm =
|
const occurrenceTerm =
|
||||||
expectedReplacements === 1 ? 'occurrence' : 'occurrences';
|
expectedReplacements === 1 ? 'occurrence' : 'occurrences';
|
||||||
|
|
||||||
error = {
|
error = {
|
||||||
display: `Failed to edit, expected ${expectedReplacements} ${occurenceTerm} but found ${occurrences}.`,
|
display: `Failed to edit, expected ${expectedReplacements} ${occurrenceTerm} but found ${occurrences}.`,
|
||||||
raw: `Failed to edit, Expected ${expectedReplacements} ${occurenceTerm} but found ${occurrences} for old_string in file: ${params.file_path}`,
|
raw: `Failed to edit, Expected ${expectedReplacements} ${occurrenceTerm} but found ${occurrences} for old_string in file: ${params.file_path}`,
|
||||||
};
|
};
|
||||||
} else if (finalOldString === finalNewString) {
|
} else if (finalOldString === finalNewString) {
|
||||||
error = {
|
error = {
|
||||||
|
|
|
@ -17,7 +17,7 @@ vi.mock('child_process', () => ({
|
||||||
on: (event: string, cb: (...args: unknown[]) => void) => {
|
on: (event: string, cb: (...args: unknown[]) => void) => {
|
||||||
if (event === 'error' || event === 'close') {
|
if (event === 'error' || event === 'close') {
|
||||||
// Simulate command not found or error for git grep and system grep
|
// Simulate command not found or error for git grep and system grep
|
||||||
// to force fallback to JS implementation.
|
// to force it to fall back to JS implementation.
|
||||||
setTimeout(() => cb(1), 0); // cb(1) for error/close
|
setTimeout(() => cb(1), 0); // cb(1) for error/close
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -272,7 +272,7 @@ describe('ReadManyFilesTool', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle non-existent specific files gracefully', async () => {
|
it('should handle nonexistent specific files gracefully', async () => {
|
||||||
const params = { paths: ['nonexistent-file.txt'] };
|
const params = { paths: ['nonexistent-file.txt'] };
|
||||||
const result = await tool.execute(params, new AbortController().signal);
|
const result = await tool.execute(params, new AbortController().signal);
|
||||||
expect(result.llmContent).toEqual([
|
expect(result.llmContent).toEqual([
|
||||||
|
|
|
@ -81,7 +81,7 @@ describe('editCorrector', () => {
|
||||||
it('should correctly count occurrences when substring is longer', () => {
|
it('should correctly count occurrences when substring is longer', () => {
|
||||||
expect(countOccurrences('abc', 'abcdef')).toBe(0);
|
expect(countOccurrences('abc', 'abcdef')).toBe(0);
|
||||||
});
|
});
|
||||||
it('should be case sensitive', () => {
|
it('should be case-sensitive', () => {
|
||||||
expect(countOccurrences('abcABC', 'a')).toBe(1);
|
expect(countOccurrences('abcABC', 'a')).toBe(1);
|
||||||
expect(countOccurrences('abcABC', 'A')).toBe(1);
|
expect(countOccurrences('abcABC', 'A')).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
|
@ -202,7 +202,7 @@ describe('editor utils', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
it(`should fallback to last command "${commands[commands.length - 1]}" when none exist on non-windows`, () => {
|
it(`should fall back to last command "${commands[commands.length - 1]}" when none exist on non-windows`, () => {
|
||||||
Object.defineProperty(process, 'platform', { value: 'linux' });
|
Object.defineProperty(process, 'platform', { value: 'linux' });
|
||||||
(execSync as Mock).mockImplementation(() => {
|
(execSync as Mock).mockImplementation(() => {
|
||||||
throw new Error(); // all commands not found
|
throw new Error(); // all commands not found
|
||||||
|
@ -247,7 +247,7 @@ describe('editor utils', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
it(`should fallback to last command "${win32Commands[win32Commands.length - 1]}" when none exist on windows`, () => {
|
it(`should fall back to last command "${win32Commands[win32Commands.length - 1]}" when none exist on windows`, () => {
|
||||||
Object.defineProperty(process, 'platform', { value: 'win32' });
|
Object.defineProperty(process, 'platform', { value: 'win32' });
|
||||||
(execSync as Mock).mockImplementation(() => {
|
(execSync as Mock).mockImplementation(() => {
|
||||||
throw new Error(); // all commands not found
|
throw new Error(); // all commands not found
|
||||||
|
|
|
@ -42,7 +42,7 @@ describe('fileUtils', () => {
|
||||||
let testImageFilePath: string;
|
let testImageFilePath: string;
|
||||||
let testPdfFilePath: string;
|
let testPdfFilePath: string;
|
||||||
let testBinaryFilePath: string;
|
let testBinaryFilePath: string;
|
||||||
let nonExistentFilePath: string;
|
let nonexistentFilePath: string;
|
||||||
let directoryPath: string;
|
let directoryPath: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -57,7 +57,7 @@ describe('fileUtils', () => {
|
||||||
testImageFilePath = path.join(tempRootDir, 'image.png');
|
testImageFilePath = path.join(tempRootDir, 'image.png');
|
||||||
testPdfFilePath = path.join(tempRootDir, 'document.pdf');
|
testPdfFilePath = path.join(tempRootDir, 'document.pdf');
|
||||||
testBinaryFilePath = path.join(tempRootDir, 'app.exe');
|
testBinaryFilePath = path.join(tempRootDir, 'app.exe');
|
||||||
nonExistentFilePath = path.join(tempRootDir, 'notfound.txt');
|
nonexistentFilePath = path.join(tempRootDir, 'nonexistent.txt');
|
||||||
directoryPath = path.join(tempRootDir, 'subdir');
|
directoryPath = path.join(tempRootDir, 'subdir');
|
||||||
|
|
||||||
actualNodeFs.mkdirSync(directoryPath, { recursive: true }); // Ensure subdir exists
|
actualNodeFs.mkdirSync(directoryPath, { recursive: true }); // Ensure subdir exists
|
||||||
|
@ -284,7 +284,7 @@ describe('fileUtils', () => {
|
||||||
|
|
||||||
it('should handle file not found', async () => {
|
it('should handle file not found', async () => {
|
||||||
const result = await processSingleFileContent(
|
const result = await processSingleFileContent(
|
||||||
nonExistentFilePath,
|
nonexistentFilePath,
|
||||||
tempRootDir,
|
tempRootDir,
|
||||||
);
|
);
|
||||||
expect(result.error).toContain('File not found');
|
expect(result.error).toContain('File not found');
|
||||||
|
|
|
@ -185,7 +185,7 @@ export async function detectFileType(
|
||||||
return 'binary';
|
return 'binary';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to content-based check if mime type wasn't conclusive for image/pdf
|
// Fall back to content-based check if mime type wasn't conclusive for image/pdf
|
||||||
// and it's not a known binary extension.
|
// and it's not a known binary extension.
|
||||||
if (await isBinaryFile(filePath)) {
|
if (await isBinaryFile(filePath)) {
|
||||||
return 'binary';
|
return 'binary';
|
||||||
|
|
|
@ -245,7 +245,7 @@ Showing up to 1 items (files + folders). Folders or files indicated with ... con
|
||||||
expect(structure.trim()).toBe(expectedRevisedMax1);
|
expect(structure.trim()).toBe(expectedRevisedMax1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle non-existent directory', async () => {
|
it('should handle nonexistent directory', async () => {
|
||||||
// Temporarily make fsPromises.readdir throw ENOENT for this specific path
|
// Temporarily make fsPromises.readdir throw ENOENT for this specific path
|
||||||
const originalReaddir = fsPromises.readdir;
|
const originalReaddir = fsPromises.readdir;
|
||||||
(fsPromises.readdir as Mock).mockImplementation(
|
(fsPromises.readdir as Mock).mockImplementation(
|
||||||
|
|
|
@ -44,7 +44,7 @@ export function shortenPath(filePath: string, maxLen: number = 35): string {
|
||||||
|
|
||||||
// Handle cases with no segments after root (e.g., "/", "C:\") or only one segment
|
// Handle cases with no segments after root (e.g., "/", "C:\") or only one segment
|
||||||
if (segments.length <= 1) {
|
if (segments.length <= 1) {
|
||||||
// Fallback to simple start/end truncation for very short paths or single segments
|
// Fall back to simple start/end truncation for very short paths or single segments
|
||||||
const keepLen = Math.floor((maxLen - 3) / 2);
|
const keepLen = Math.floor((maxLen - 3) / 2);
|
||||||
// Ensure keepLen is not negative if maxLen is very small
|
// Ensure keepLen is not negative if maxLen is very small
|
||||||
if (keepLen <= 0) {
|
if (keepLen <= 0) {
|
||||||
|
|
|
@ -196,7 +196,7 @@ export async function retryWithBackoff<T>(
|
||||||
// Reset currentDelay for next potential non-429 error, or if Retry-After is not present next time
|
// Reset currentDelay for next potential non-429 error, or if Retry-After is not present next time
|
||||||
currentDelay = initialDelayMs;
|
currentDelay = initialDelayMs;
|
||||||
} else {
|
} else {
|
||||||
// Fallback to exponential backoff with jitter
|
// Fall back to exponential backoff with jitter
|
||||||
logRetryAttempt(attempt, error, errorStatus);
|
logRetryAttempt(attempt, error, errorStatus);
|
||||||
// Add jitter: +/- 30% of currentDelay
|
// Add jitter: +/- 30% of currentDelay
|
||||||
const jitter = currentDelay * 0.3 * (Math.random() * 2 - 1);
|
const jitter = currentDelay * 0.3 * (Math.random() * 2 - 1);
|
||||||
|
|
Loading…
Reference in New Issue