feat: add Shuru sandbox mode for agent shell execution (#215)
* feat: add Shuru sandbox mode for agent shell execution Adds a user-selectable sandbox mode that runs all agent-driven bash commands inside a Shuru microVM. Includes persistent user/project settings, CLI flags (--sandbox / --no-sandbox), a TUI picker via /sandbox, runtime toggling for live agents, delegation propagation, model prompt updates, and Phase 1 guardrails that block shell commands which would silently lose workspace mutations inside the guest overlay. * fix: use word boundary in git guardrail to catch compound commands The git mutation guard used ^ (start-of-string anchor), so compound expressions like `echo foo && git commit` bypassed it. Switch to \b to match the pattern used by the other guardrails. * fix: remove branch from git allowlist and stabilize bash tests - Remove `branch` from the read-only git allowlist since git branch can create/delete/rename branches, which are mutating operations. - Export wrapCommandForShuru and getSandboxMutationBlockReason for direct unit testing without child_process mocking. - Rewrite bash.test.ts to test pure functions directly, eliminating flaky vi.doMock/resetModules ESM interactions.
This commit is contained in:
1 parent
76498778d5
commit
2058cdfd09
12 files changed
+721
-35
No files matched your search
@@ -6,6 +6,7 @@ import type { ReasoningEffort } from "../types/index";
|
||||
|
||||
export type TelegramStreamingMode = "off" | "partial";
|
||||
export type TelegramAudioInputEngine = "whisper.cpp";
|
||||
export type SandboxMode = "off" | "shuru";
|
||||
|
||||
export const DEFAULT_TELEGRAM_AUDIO_INPUT_BINARY = "whisper-cli";
|
||||
export const DEFAULT_TELEGRAM_AUDIO_INPUT_MODEL = "tiny.en";
|
||||
@@ -107,6 +108,7 @@ export function loadValidSubAgents(): CustomSubagentConfig[] {
|
||||
export interface UserSettings {
|
||||
apiKey?: string;
|
||||
defaultModel?: string;
|
||||
sandboxMode?: SandboxMode;
|
||||
reasoningEffortByModel?: Record<string, ReasoningEffort>;
|
||||
telegram?: TelegramSettings;
|
||||
mcp?: McpSettings;
|
||||
@@ -115,6 +117,7 @@ export interface UserSettings {
|
||||
|
||||
export interface ProjectSettings {
|
||||
model?: string;
|
||||
sandboxMode?: SandboxMode;
|
||||
}
|
||||
|
||||
const USER_DIR = path.join(os.homedir(), ".grok");
|
||||
@@ -151,6 +154,7 @@ export function saveUserSettings(partial: Partial<UserSettings>): void {
|
||||
...partial,
|
||||
...(partial.apiKey !== undefined ? { apiKey: partial.apiKey } : {}),
|
||||
...(partial.defaultModel !== undefined ? { defaultModel: normalizeModelId(partial.defaultModel) } : {}),
|
||||
...(partial.sandboxMode !== undefined ? { sandboxMode: normalizeSandboxMode(partial.sandboxMode) } : {}),
|
||||
...(partial.reasoningEffortByModel !== undefined
|
||||
? {
|
||||
reasoningEffortByModel: Object.fromEntries(
|
||||
@@ -211,6 +215,7 @@ export function saveProjectSettings(partial: Partial<ProjectSettings>): void {
|
||||
...current,
|
||||
...partial,
|
||||
...(partial.model !== undefined ? { model: normalizeModelId(partial.model) } : {}),
|
||||
...(partial.sandboxMode !== undefined ? { sandboxMode: normalizeSandboxMode(partial.sandboxMode) } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -230,6 +235,18 @@ export function getCurrentModel(): string {
|
||||
return user.defaultModel ? normalizeModelId(user.defaultModel) : DEFAULT_MODEL;
|
||||
}
|
||||
|
||||
export function normalizeSandboxMode(value: unknown): SandboxMode {
|
||||
return value === "shuru" ? "shuru" : "off";
|
||||
}
|
||||
|
||||
export function getCurrentSandboxMode(): SandboxMode {
|
||||
const project = loadProjectSettings();
|
||||
if (project.sandboxMode) return normalizeSandboxMode(project.sandboxMode);
|
||||
const user = loadUserSettings();
|
||||
if (user.sandboxMode) return normalizeSandboxMode(user.sandboxMode);
|
||||
return "off";
|
||||
}
|
||||
|
||||
export function getReasoningEffortForModel(modelId: string): ReasoningEffort | undefined {
|
||||
const normalizedModelId = normalizeModelId(modelId);
|
||||
const savedEfforts = loadUserSettings().reasoningEffortByModel ?? {};
|
||||
|
||||
Reference in new issue
Block a user