mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
chore: include uncommitted workspace files from orions-belt
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
"experimental": {
|
"experimental": {
|
||||||
"plan": true,
|
"plan": true,
|
||||||
"extensionReloading": true,
|
"extensionReloading": true
|
||||||
"modelSteering": true
|
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"devtools": true
|
"devtools": true
|
||||||
|
},
|
||||||
|
"tools": {
|
||||||
|
"allowed": ["run_shell_command"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Build & Launch CLI",
|
"name": "Build & Launch CLI",
|
||||||
"runtimeExecutable": "npm",
|
"runtimeExecutable": "npm",
|
||||||
"runtimeArgs": ["run", "build-and-start"],
|
"runtimeArgs": ["run", "build-and-start", "--", "-m", "abc"],
|
||||||
"skipFiles": ["<node_internals>/**"],
|
"skipFiles": ["<node_internals>/**"],
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
|
|||||||
@@ -8,10 +8,14 @@ import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|||||||
import { agentsCommand } from './agentsCommand.js';
|
import { agentsCommand } from './agentsCommand.js';
|
||||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||||
import type { Config } from '@google/gemini-cli-core';
|
import type { Config } from '@google/gemini-cli-core';
|
||||||
|
import { Storage } from '@google/gemini-cli-core';
|
||||||
import type { LoadedSettings } from '../../config/settings.js';
|
import type { LoadedSettings } from '../../config/settings.js';
|
||||||
import { MessageType } from '../types.js';
|
import { MessageType } from '../types.js';
|
||||||
import { enableAgent, disableAgent } from '../../utils/agentSettings.js';
|
import { enableAgent, disableAgent } from '../../utils/agentSettings.js';
|
||||||
import { renderAgentActionFeedback } from '../../utils/agentUtils.js';
|
import { renderAgentActionFeedback } from '../../utils/agentUtils.js';
|
||||||
|
import * as fs from 'node:fs/promises';
|
||||||
|
|
||||||
|
vi.mock('node:fs/promises');
|
||||||
|
|
||||||
vi.mock('../../utils/agentSettings.js', () => ({
|
vi.mock('../../utils/agentSettings.js', () => ({
|
||||||
enableAgent: vi.fn(),
|
enableAgent: vi.fn(),
|
||||||
@@ -105,40 +109,34 @@ describe('agentsCommand', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reload the agent registry when reload subcommand is called', async () => {
|
it('should reload the agent registry when refresh subcommand is called', async () => {
|
||||||
const reloadSpy = vi.fn().mockResolvedValue(undefined);
|
const reloadSpy = vi.fn().mockResolvedValue(undefined);
|
||||||
mockConfig.getAgentRegistry = vi.fn().mockReturnValue({
|
mockConfig.getAgentRegistry = vi.fn().mockReturnValue({
|
||||||
reload: reloadSpy,
|
reload: reloadSpy,
|
||||||
});
|
});
|
||||||
|
|
||||||
const reloadCommand = agentsCommand.subCommands?.find(
|
const refreshCommand = agentsCommand.subCommands?.find(
|
||||||
(cmd) => cmd.name === 'reload',
|
(cmd) => cmd.name === 'refresh',
|
||||||
);
|
);
|
||||||
expect(reloadCommand).toBeDefined();
|
expect(refreshCommand).toBeDefined();
|
||||||
|
|
||||||
const result = await reloadCommand!.action!(mockContext, '');
|
const result = await refreshCommand!.action!(mockContext, '');
|
||||||
|
|
||||||
expect(reloadSpy).toHaveBeenCalled();
|
expect(reloadSpy).toHaveBeenCalled();
|
||||||
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
type: MessageType.INFO,
|
|
||||||
text: 'Reloading agent registry...',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
messageType: 'info',
|
messageType: 'info',
|
||||||
content: 'Agents reloaded successfully',
|
content: 'Agents refreshed successfully.',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show an error if agent registry is not available during reload', async () => {
|
it('should show an error if agent registry is not available during refresh', async () => {
|
||||||
mockConfig.getAgentRegistry = vi.fn().mockReturnValue(undefined);
|
mockConfig.getAgentRegistry = vi.fn().mockReturnValue(undefined);
|
||||||
|
|
||||||
const reloadCommand = agentsCommand.subCommands?.find(
|
const refreshCommand = agentsCommand.subCommands?.find(
|
||||||
(cmd) => cmd.name === 'reload',
|
(cmd) => cmd.name === 'refresh',
|
||||||
);
|
);
|
||||||
const result = await reloadCommand!.action!(mockContext, '');
|
const result = await refreshCommand!.action!(mockContext, '');
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
@@ -462,4 +460,56 @@ describe('agentsCommand', () => {
|
|||||||
expect(completions).toEqual(['agent1', 'agent2']);
|
expect(completions).toEqual(['agent1', 'agent2']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('import sub-command', () => {
|
||||||
|
it('should import an agent with correct tool mapping', async () => {
|
||||||
|
vi.mocked(fs.readFile).mockResolvedValue(`---
|
||||||
|
name: test-claude-agent
|
||||||
|
tools: Read, Glob, Grep, Bash
|
||||||
|
model: sonnet
|
||||||
|
---
|
||||||
|
System prompt content`);
|
||||||
|
|
||||||
|
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
||||||
|
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
||||||
|
vi.spyOn(Storage, 'getUserAgentsDir').mockReturnValue('/mock/agents');
|
||||||
|
|
||||||
|
const importCommand = agentsCommand.subCommands?.find(
|
||||||
|
(cmd) => cmd.name === 'import',
|
||||||
|
);
|
||||||
|
expect(importCommand).toBeDefined();
|
||||||
|
|
||||||
|
const result = await importCommand!.action!(
|
||||||
|
mockContext,
|
||||||
|
'/path/to/claude.md',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(fs.readFile).toHaveBeenCalledWith('/path/to/claude.md', 'utf-8');
|
||||||
|
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||||
|
expect.stringMatching(/test-claude-agent\.md$/),
|
||||||
|
expect.stringContaining('tools:\n - read_file\n - run_command\n'),
|
||||||
|
'utf-8',
|
||||||
|
);
|
||||||
|
expect(result).toEqual({
|
||||||
|
type: 'message',
|
||||||
|
messageType: 'info',
|
||||||
|
content: expect.stringContaining(
|
||||||
|
"Successfully imported agent 'test-claude-agent'",
|
||||||
|
),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show error if no file path provided', async () => {
|
||||||
|
const importCommand = agentsCommand.subCommands?.find(
|
||||||
|
(cmd) => cmd.name === 'import',
|
||||||
|
);
|
||||||
|
const result = await importCommand!.action!(mockContext, ' ');
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
type: 'message',
|
||||||
|
messageType: 'error',
|
||||||
|
content: 'Usage: /agents import <file-path>',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Define paths
|
||||||
|
CLI_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||||
|
EXA_ROOT="$(cd "$CLI_ROOT/../jetski/Exafunction" && pwd)"
|
||||||
|
TELEPORTER_TS="$CLI_ROOT/packages/core/src/teleportation/trajectory_teleporter.ts"
|
||||||
|
TELEPORTER_MIN_JS="$CLI_ROOT/packages/core/src/teleportation/trajectory_teleporter.min.js"
|
||||||
|
|
||||||
|
if [ ! -d "$EXA_ROOT" ]; then
|
||||||
|
echo "Error: Exafunction directory not found at $EXA_ROOT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Building Protobuf JS definitions in Exafunction..."
|
||||||
|
cd "$EXA_ROOT"
|
||||||
|
pnpm --dir exa/proto_ts build
|
||||||
|
|
||||||
|
echo "Bundling and minifying trajectory_teleporter.ts..."
|
||||||
|
# Because esbuild resolves relative imports from the source file's directory,
|
||||||
|
# and trajectory_teleporter.ts playfully imports './exa/...', we copy it to EXA_ROOT
|
||||||
|
# temporarily for the build step to succeed.
|
||||||
|
cp "$TELEPORTER_TS" "$EXA_ROOT/trajectory_teleporter_tmp.ts"
|
||||||
|
|
||||||
|
cd "$EXA_ROOT"
|
||||||
|
pnpm dlx esbuild "./trajectory_teleporter_tmp.ts" \
|
||||||
|
--bundle \
|
||||||
|
--format=esm \
|
||||||
|
--platform=node \
|
||||||
|
--outfile="$TELEPORTER_MIN_JS"
|
||||||
|
|
||||||
|
rm "$EXA_ROOT/trajectory_teleporter_tmp.ts"
|
||||||
|
|
||||||
|
echo "Done! Wrote bundle to $TELEPORTER_MIN_JS"
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
# Remote Teleportation Architecture Proposal
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Prevent leaking proprietary JetSki Protobuf schemas and decryption keys directly
|
||||||
|
within the public Gemini CLI bundle. When a user requests to resume a JetSki
|
||||||
|
trajectory, the CLI will interact with an external, secure, or isolated
|
||||||
|
converter to fetch the standard `ConversationRecord` JSON.
|
||||||
|
|
||||||
|
## Guarantees & Constraints
|
||||||
|
|
||||||
|
- No Protobuf definitions are packaged or visible in the Gemini CLI
|
||||||
|
distribution.
|
||||||
|
- Telemetry/Decryption keys do not need to be hardcoded in the CLI.
|
||||||
|
- Conversion remains effortless and instantaneous for the end-user.
|
||||||
|
- **Strict Logic Confidentiality**: Proprietary conversion logic and Protobuf
|
||||||
|
schemas cannot be readable or reverse-engineered by the user via local caches
|
||||||
|
or easily accessible client-side code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option 1: Local MCP Server (JetSki Daemon Extension)
|
||||||
|
|
||||||
|
Since Gemini CLI already natively supports the **Model Context Protocol (MCP)**,
|
||||||
|
we can utilize JetSki directly as an MCP provider.
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
|
||||||
|
The JetSki extension (or its already running background daemon) can expose an
|
||||||
|
MCP tool, for example, `get_trajectory_history`. When the user runs `/resume`,
|
||||||
|
Gemini CLI invokes this tool via the standard MCP pipeline.
|
||||||
|
|
||||||
|
1. **User runs `/resume`**: Gemini CLI lists available trajectories (it can
|
||||||
|
list .pb files from the local directory or query the MCP server for a
|
||||||
|
history list).
|
||||||
|
2. **Conversation Selection**: The user selects a session.
|
||||||
|
3. **MCP Tool Call**: Gemini calls the MCP tool
|
||||||
|
`get_trajectory(filePath: string)`.
|
||||||
|
4. **Local Conversion**: JetSki decrypts, parses, and returns the strictly
|
||||||
|
CLI-compliant `HistoryItem` JSON payload over stdio/SSE.
|
||||||
|
5. **No Overhead**: The CLI injects the payload directly into its state without
|
||||||
|
ever touching a Protobuf.
|
||||||
|
|
||||||
|
### Effort & Trade-offs
|
||||||
|
|
||||||
|
- **Effort**: Very Low. Gemini CLI already supports MCP out of the box. No new
|
||||||
|
endpoints, infrastructure, or routing is needed. We only need to write a small
|
||||||
|
MCP server module using `@modelcontextprotocol/sdk` inside JetSki.
|
||||||
|
- **Pros**: Zero remote network latency; highly secure (stays on local machine);
|
||||||
|
incredibly seamless.
|
||||||
|
- **Cons**: Requires the JetSki service to be installed and running in the
|
||||||
|
background.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option 2: Stateless Cloud Function (API/Microservice)
|
||||||
|
|
||||||
|
Host the trajectory parsing logic completely server-side.
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
|
||||||
|
1. **User runs `/resume`**: The CLI scans local `.pb` trajectories.
|
||||||
|
2. **Cloud Request**: The CLI sends a
|
||||||
|
`POST https://api.exafunction.com/v1/teleport` request. The body contains
|
||||||
|
the binary payload (and maybe an authorization token/key).
|
||||||
|
3. **Cloud Conversion**: The cloud function decrypts, parses, and formats the
|
||||||
|
trajectory.
|
||||||
|
4. **Response**: The API responds with the `ConversationRecord` JSON.
|
||||||
|
|
||||||
|
### Effort & Trade-offs
|
||||||
|
|
||||||
|
- **Effort**: Medium. Requires setting up a secured API endpoint, likely using
|
||||||
|
Google Cloud Functions or AWS Lambda.
|
||||||
|
- **Pros**: Completely decouples schema and keys from the end-user's device
|
||||||
|
entirely. The conversion environment is completely under Exafunction's
|
||||||
|
control.
|
||||||
|
- **Cons**: Network roundtrip latency whenever the user converts a session.
|
||||||
|
Involves sending binary trajectory files (potentially containing source code
|
||||||
|
snippets) over the network, which could be a privacy concern for some users.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option 3: Remote "Plugin" Loader (WASM / Transient Javascript)
|
||||||
|
|
||||||
|
The CLI downloads the interpreter on-demand, or runs it inside an isolated
|
||||||
|
Sandbox (like WebAssembly).
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
|
||||||
|
1. **On-Demand Download**: When `/resume` is first used, the CLI downloads a
|
||||||
|
private, versioned conversion payload (e.g., `teleporter_v1.2.3.wasm` or an
|
||||||
|
obfuscated JS bundle) from a secure URL and caches it locally.
|
||||||
|
2. **Local Execution**: It runs this script locally to perform decryption and
|
||||||
|
conversion.
|
||||||
|
3. It keeps the schema out of the open-source CLI bundle, though a determined
|
||||||
|
user could still reverse engineer the WASM/JS if they inspect their local
|
||||||
|
caches.
|
||||||
|
|
||||||
|
### Effort & Trade-offs
|
||||||
|
|
||||||
|
- **Effort**: Medium-High. Requires robust WebAssembly compilation from the
|
||||||
|
Protobuf definitions, or a dynamic code loading execution chain in Node.js
|
||||||
|
that doesn't trigger security flags.
|
||||||
|
- **Pros**: Speed of local processing, decoupled from the main CLI installation.
|
||||||
|
- **Cons**: Adds complexity to the bundle distribution system.
|
||||||
|
- **Cons (Security)**: A determined user could reverse engineer the cached
|
||||||
|
WebAssembly or obfuscated JS bundle to reconstruct the JetSki Protobuf schema,
|
||||||
|
breaking the logic confidentiality requirement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option 4: Remote MCP Server (via SSE)
|
||||||
|
|
||||||
|
A remote MCP server would bridge the user's local trajectory files with
|
||||||
|
Exafunction's parser inside a totally separate host.
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
|
||||||
|
- We run an SSE MCP service on the Exafunction side.
|
||||||
|
- Instead of using stdio transport from a local background process (Option 1),
|
||||||
|
the Gemini CLI establishes an SSE connection to
|
||||||
|
`https://mcp.exafunction.com/sse`.
|
||||||
|
- The local CLI still queries the filesystem for the `.pb` trajectories.
|
||||||
|
- It invokes a remote tool: `parse_trajectory(trajectoryPb: string)` and passes
|
||||||
|
the local protobuf string as the request argument.
|
||||||
|
- The remote server unmarshalls, decrypts, and maps the proprietary protobufs
|
||||||
|
into the standard JSON response, which the CLI renders natively.
|
||||||
|
|
||||||
|
### Effort & Trade-offs
|
||||||
|
|
||||||
|
- **Effort**: Medium. Gemini CLI already supports SSE network transports for
|
||||||
|
MCP. You would need to host an MCP SSE server remotely.
|
||||||
|
- **Pros**: Proprietary mapping logic, credentials, and Protobuf schemas are
|
||||||
|
hosted totally remote and are decoupled from JetSki OR the CLI. Since it uses
|
||||||
|
standard MCP, it requires absolutely no specialized HTTP routing or bespoke
|
||||||
|
protocol headers in the CLI because Gemini naturally maps arguments
|
||||||
|
dynamically already.
|
||||||
|
- **Cons**: High network latency (sending large Protobuf strings back and
|
||||||
|
forth), privacy concerns because user code trajectories are being transmitted
|
||||||
|
remotely.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
**Option 4 (Remote MCP)** or **Option 2 (Cloud Function Isolation)** is the
|
||||||
|
recommended production approach to ensure strict confidentiality.
|
||||||
|
|
||||||
|
By keeping the proprietary deserialization binary completely remote behind an
|
||||||
|
authentication layer, Gemini CLI ensures that end users cannot observe execution
|
||||||
|
state, trace unmarshalled arrays, or scrape proprietary JetSki Protobuf
|
||||||
|
primitives natively. If removing heavy network latency is the highest priority,
|
||||||
|
**Option 1 (JetSki Local MCP)** remains the most effortless and robust path
|
||||||
|
forward without modifying the open-source CLI distribution.
|
||||||
Reference in New Issue
Block a user