mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
fix(muse): make package installs and project rules work by default (#5775)
* fix(muse): make launch defaults usable and configure each agent Disable sandboxing and trust workspaces by default so package installs and project rules work. Replace the host settings UI with validated per-agent providerOptions, preserving posture across session resume. * docs(muse): describe shared provider options configuration
This commit is contained in:
+1
-1
@@ -104,7 +104,7 @@ availability and a diagnostic after the daemon resolves the executable. Configur
|
||||
environment, or enablement overrides under `agents.providers.muse`. Omit `extends` to keep
|
||||
the bundled integration; an entry with `extends` shadows it with a custom provider. See
|
||||
[provider contributions](#contribute-a-provider) for the contract and
|
||||
[Muse Code](../public-docs/muse-code.md) for setup, settings, and version limitations.
|
||||
[Muse Code](../public-docs/muse-code.md) for setup, per-agent options, and version limitations.
|
||||
|
||||
## Install a Git source
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { BuiltinPluginLoader } from "../plugins/builtin/index.js";
|
||||
import { createTestPaseoDaemon } from "../test-utils/paseo-daemon.js";
|
||||
import { DaemonClient } from "../test-utils/daemon-client.js";
|
||||
|
||||
test("Muse publishes its client settings bundle while installed plugins are disabled", async () => {
|
||||
test("Muse registers without a client settings bundle", async () => {
|
||||
const daemon = await createTestPaseoDaemon({
|
||||
builtinPlugins: new BuiltinPluginLoader(undefined, ["muse-provider"]),
|
||||
});
|
||||
@@ -21,8 +21,8 @@ test("Muse publishes its client settings bundle while installed plugins are disa
|
||||
try {
|
||||
await client.connect();
|
||||
const muse = (await client.getPluginCatalog()).find((plugin) => plugin.id === "muse-provider");
|
||||
expect(muse?.clientBundle).toBeTypeOf("string");
|
||||
expect(muse?.clientBundle).not.toBe("");
|
||||
expect(muse).toBeDefined();
|
||||
expect(muse?.clientBundle).toBe("");
|
||||
} finally {
|
||||
await client.close();
|
||||
await daemon.close();
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Text } from "react-native";
|
||||
import { useSettings, type PluginScreenProps, type SettingsState } from "@getpaseo/plugin/client";
|
||||
import {
|
||||
SettingsAction,
|
||||
SettingsCard,
|
||||
SettingsSection,
|
||||
SettingsSelect,
|
||||
SettingsSwitch,
|
||||
} from "@getpaseo/plugin/client/ui";
|
||||
import { museSettings } from "../shared/settings.js";
|
||||
|
||||
const networks = [
|
||||
{ label: "Proxy only", value: "proxy-only" },
|
||||
{ label: "Restricted", value: "restricted" },
|
||||
{ label: "Enabled", value: "enabled" },
|
||||
] as const;
|
||||
type ReadySettings = Extract<SettingsState<typeof museSettings.schema>, { status: "ready" }>;
|
||||
|
||||
function Controls({
|
||||
settings,
|
||||
theme,
|
||||
}: {
|
||||
settings: ReadySettings;
|
||||
theme: PluginScreenProps["theme"];
|
||||
}) {
|
||||
const style = useMemo(() => ({ color: theme.colors.foreground }), [theme]);
|
||||
const changeSandbox = useCallback(
|
||||
(sandbox: boolean) => {
|
||||
void settings.save({ ...settings.values, sandbox }, settings.revision);
|
||||
},
|
||||
[settings],
|
||||
);
|
||||
const changeNetwork = useCallback(
|
||||
(network: ReadySettings["values"]["network"]) => {
|
||||
void settings.save({ ...settings.values, network }, settings.revision);
|
||||
},
|
||||
[settings],
|
||||
);
|
||||
const changeTrust = useCallback(
|
||||
(trustWorkspace: boolean) => {
|
||||
void settings.save({ ...settings.values, trustWorkspace }, settings.revision);
|
||||
},
|
||||
[settings],
|
||||
);
|
||||
return (
|
||||
<SettingsSection title="Muse Code">
|
||||
<SettingsCard>
|
||||
<SettingsSwitch
|
||||
label="Sandbox"
|
||||
value={settings.values.sandbox}
|
||||
disabled={settings.saving}
|
||||
onValueChange={changeSandbox}
|
||||
/>
|
||||
<SettingsSelect
|
||||
label="Network"
|
||||
value={settings.values.network}
|
||||
options={networks}
|
||||
disabled={settings.saving || !settings.values.sandbox}
|
||||
onValueChange={changeNetwork}
|
||||
/>
|
||||
<SettingsSwitch
|
||||
label="Trust workspace"
|
||||
value={settings.values.trustWorkspace}
|
||||
disabled={settings.saving}
|
||||
onValueChange={changeTrust}
|
||||
/>
|
||||
</SettingsCard>
|
||||
<Text style={style}>
|
||||
Changes apply to new Muse sessions. Disabling the sandbox enables full network access. Trust
|
||||
workspace lets Muse load project skills and configuration.
|
||||
</Text>
|
||||
{settings.saveError ? (
|
||||
<Text accessibilityRole="alert" style={style}>
|
||||
{settings.saveError}
|
||||
</Text>
|
||||
) : null}
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
|
||||
export function MuseSettings({ theme }: PluginScreenProps) {
|
||||
const settings = useSettings(museSettings);
|
||||
const style = useMemo(() => ({ color: theme.colors.foreground }), [theme]);
|
||||
if (settings.status === "loading") return <Text style={style}>Loading settings…</Text>;
|
||||
if (settings.status !== "ready")
|
||||
return (
|
||||
<SettingsSection title="Muse Code">
|
||||
<Text accessibilityRole="alert" style={style}>
|
||||
{settings.error}
|
||||
</Text>
|
||||
<SettingsAction label="Try again" actionLabel="Reload" onPress={settings.reload} />
|
||||
{settings.status === "invalid" ? (
|
||||
<SettingsAction
|
||||
label="Restore default settings"
|
||||
actionLabel="Reset"
|
||||
onPress={settings.reset}
|
||||
/>
|
||||
) : null}
|
||||
</SettingsSection>
|
||||
);
|
||||
return <Controls settings={settings} theme={theme} />;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import type { PluginClientContext } from "@getpaseo/plugin/client";
|
||||
import { MuseSettings } from "./client/settings.js";
|
||||
|
||||
export default function contribute(client: PluginClientContext) {
|
||||
client.addScreen({
|
||||
id: "muse",
|
||||
title: "Muse Code",
|
||||
Component: MuseSettings,
|
||||
});
|
||||
client.addCommandCenterItem({
|
||||
id: "settings",
|
||||
title: "Configure Muse Code",
|
||||
icon: "Settings",
|
||||
context: "global",
|
||||
onSelect({ openScreen }) {
|
||||
openScreen({ screenId: "muse" });
|
||||
},
|
||||
});
|
||||
return () => {};
|
||||
}
|
||||
@@ -1,18 +1,13 @@
|
||||
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
||||
import { createMuseProvider } from "./server/provider.js";
|
||||
|
||||
import { museSettings } from "./shared/settings.js";
|
||||
import { Usage } from "./server/usage.js";
|
||||
|
||||
export default function contribute(
|
||||
server: Pick<
|
||||
PluginServerContext,
|
||||
"registerProvider" | "registerSettings" | "registerUsageSource"
|
||||
>,
|
||||
server: Pick<PluginServerContext, "registerProvider" | "registerUsageSource">,
|
||||
) {
|
||||
const settings = server.registerSettings(museSettings);
|
||||
const usage = new Usage();
|
||||
server.registerUsageSource(usage.registration());
|
||||
server.registerProvider(createMuseProvider(settings, usage));
|
||||
server.registerProvider(createMuseProvider(usage));
|
||||
return () => {};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { z } from "zod";
|
||||
import { MuseError } from "./errors.js";
|
||||
|
||||
const optionsSchema = z
|
||||
.object({
|
||||
sandbox: z
|
||||
.object({
|
||||
enabled: z.boolean().default(false),
|
||||
network: z.enum(["proxy-only", "restricted", "enabled"]).default("proxy-only"),
|
||||
})
|
||||
.strict()
|
||||
.prefault({}),
|
||||
trustWorkspace: z.boolean().default(true),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export function serveArgs(providerOptions: unknown): string[] {
|
||||
const parsed = optionsSchema.safeParse(providerOptions === undefined ? {} : providerOptions);
|
||||
if (!parsed.success) {
|
||||
const errors = parsed.error.issues
|
||||
.map((issue) => `${["providerOptions", ...issue.path].join(".")}: ${issue.message}`)
|
||||
.join("; ");
|
||||
throw new MuseError("invalidProviderOptions", `Invalid Muse providerOptions: ${errors}`);
|
||||
}
|
||||
const { sandbox, trustWorkspace } = parsed.data;
|
||||
return [
|
||||
"--sandbox-network",
|
||||
sandbox.network,
|
||||
...(sandbox.enabled ? [] : ["--disable-sandbox"]),
|
||||
...(trustWorkspace ? ["--trust-workspace"] : []),
|
||||
];
|
||||
}
|
||||
@@ -8,9 +8,7 @@ import {
|
||||
type ProviderRegistration,
|
||||
type ProviderStatus,
|
||||
} from "@getpaseo/plugin/server/provider";
|
||||
import type { PluginSettings } from "@getpaseo/plugin/server";
|
||||
import { serveArgs } from "./settings.js";
|
||||
import { settingsSchema } from "../shared/settings.js";
|
||||
import { serveArgs } from "./options.js";
|
||||
import { Usage } from "./usage.js";
|
||||
import { execFile } from "node:child_process";
|
||||
import { Catalog, launchKey } from "./catalog.js";
|
||||
@@ -31,10 +29,7 @@ const capabilities = [
|
||||
"session.list",
|
||||
] as const;
|
||||
|
||||
export function createMuseProvider(
|
||||
settings: PluginSettings<typeof settingsSchema>,
|
||||
usage: Usage,
|
||||
): ProviderRegistration {
|
||||
export function createMuseProvider(usage: Usage): ProviderRegistration {
|
||||
return {
|
||||
id: "muse",
|
||||
label: "Muse Code",
|
||||
@@ -56,7 +51,6 @@ export function createMuseProvider(
|
||||
return connect(
|
||||
requireLaunch(request.launch),
|
||||
negotiateProviderCapabilities(request.capabilities, capabilities),
|
||||
settings,
|
||||
usage,
|
||||
);
|
||||
},
|
||||
@@ -101,7 +95,6 @@ async function status(launch: ProviderLaunch): Promise<ProviderStatus> {
|
||||
function connect(
|
||||
launch: ProviderLaunch,
|
||||
negotiated: readonly string[],
|
||||
settings: PluginSettings<typeof settingsSchema>,
|
||||
usage: Usage,
|
||||
): ProviderConnection {
|
||||
const listeners = new Set<(event: ProviderEvent) => void>();
|
||||
@@ -134,7 +127,7 @@ function connect(
|
||||
launch,
|
||||
emit,
|
||||
capabilities: negotiated,
|
||||
serveArgs: await serveArgs(settings),
|
||||
serveArgs: serveArgs(input.config.providerOptions),
|
||||
});
|
||||
sessions.set(input.sessionId, session);
|
||||
try {
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { PluginSettings } from "@getpaseo/plugin/server";
|
||||
import { settingsSchema } from "../shared/settings.js";
|
||||
import { MuseError } from "./errors.js";
|
||||
|
||||
export async function serveArgs(
|
||||
settings: PluginSettings<typeof settingsSchema>,
|
||||
): Promise<string[]> {
|
||||
const state = await settings.read();
|
||||
if (state.status === "invalid") throw new MuseError("invalidSettings", state.error);
|
||||
const { sandbox, network, trustWorkspace } = state.values;
|
||||
return [
|
||||
"--sandbox-network",
|
||||
network,
|
||||
...(sandbox ? [] : ["--disable-sandbox"]),
|
||||
...(trustWorkspace ? ["--trust-workspace"] : []),
|
||||
];
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { z } from "zod";
|
||||
import { defineSettings } from "@getpaseo/plugin";
|
||||
|
||||
export const settingsSchema = z.object({
|
||||
sandbox: z
|
||||
.boolean()
|
||||
.default(true)
|
||||
.describe("Keep Muse filesystem and network sandboxing enabled"),
|
||||
network: z
|
||||
.enum(["proxy-only", "restricted", "enabled"])
|
||||
.default("proxy-only")
|
||||
.describe("Sandbox network access; disabling sandbox forces full network access"),
|
||||
trustWorkspace: z
|
||||
.boolean()
|
||||
.default(false)
|
||||
.describe("Let Muse load project-scoped skills and config from the workspace"),
|
||||
});
|
||||
|
||||
export const museSettings = defineSettings({
|
||||
id: "muse",
|
||||
scope: "host",
|
||||
version: 1,
|
||||
schema: settingsSchema,
|
||||
});
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
type ProviderLaunch,
|
||||
type ProviderInput,
|
||||
} from "@getpaseo/plugin/server/provider";
|
||||
import { settingsSchema } from "../shared/settings.js";
|
||||
import type { UsageSourceRegistration } from "@getpaseo/plugin/server";
|
||||
import contribute from "../index.server.js";
|
||||
|
||||
@@ -25,21 +24,10 @@ afterEach(async () => {
|
||||
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
||||
});
|
||||
function registration(
|
||||
readSettings = () => settingsSchema.parse({}),
|
||||
registerUsageSource = (_source: UsageSourceRegistration) => {},
|
||||
): ProviderRegistration {
|
||||
let provider: ProviderRegistration | undefined;
|
||||
const server = {
|
||||
registerSettings() {
|
||||
return {
|
||||
read: async () => ({
|
||||
status: "ready" as const,
|
||||
revision: "1",
|
||||
values: readSettings(),
|
||||
}),
|
||||
subscribe: () => () => {},
|
||||
};
|
||||
},
|
||||
registerUsageSource,
|
||||
registerProvider(value: ProviderRegistration) {
|
||||
provider = value;
|
||||
@@ -49,7 +37,11 @@ function registration(
|
||||
if (!provider) throw new Error("Provider was not registered");
|
||||
return provider;
|
||||
}
|
||||
async function harness(scenario = "text-reasoning", env: Record<string, string> = {}) {
|
||||
async function harness(
|
||||
scenario = "text-reasoning",
|
||||
env: Record<string, string> = {},
|
||||
providerOptions?: Record<string, unknown>,
|
||||
) {
|
||||
const root = await mkdtemp(path.join(os.tmpdir(), "muse-provider-test-"));
|
||||
roots.push(root);
|
||||
const requests = path.join(root, "requests.ndjson");
|
||||
@@ -59,14 +51,10 @@ async function harness(scenario = "text-reasoning", env: Record<string, string>
|
||||
env: { MUSE_TEST_SCENARIO: scenario, MUSE_TEST_REQUESTS: requests, ...env },
|
||||
};
|
||||
await writeFile(requests, "");
|
||||
let settings = settingsSchema.parse({});
|
||||
let usageSource!: UsageSourceRegistration;
|
||||
const provider = registration(
|
||||
() => settings,
|
||||
(source) => {
|
||||
usageSource = source;
|
||||
},
|
||||
);
|
||||
const provider = registration((source) => {
|
||||
usageSource = source;
|
||||
});
|
||||
const connection = await provider.connect({
|
||||
launch,
|
||||
versions: [1],
|
||||
@@ -103,6 +91,7 @@ async function harness(scenario = "text-reasoning", env: Record<string, string>
|
||||
sessionId: "paseo-session",
|
||||
config: {
|
||||
cwd: root,
|
||||
providerOptions,
|
||||
env: {},
|
||||
settings: {},
|
||||
mcpServers: {},
|
||||
@@ -155,9 +144,6 @@ async function harness(scenario = "text-reasoning", env: Record<string, string>
|
||||
recorded,
|
||||
root,
|
||||
usageSource,
|
||||
setSettings: (values: unknown) => {
|
||||
settings = settingsSchema.parse(values);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1011,22 +997,81 @@ test("silent active turns page after two minutes and stop polling on recovered t
|
||||
await vi.advanceTimersByTimeAsync(240000);
|
||||
expect((await h.recorded()).filter((f) => f.method === "view/page")).toHaveLength(1);
|
||||
});
|
||||
test("sandbox, network and workspace trust settings apply only on the next open", async () => {
|
||||
test("default launch disables sandbox and trusts workspace", async () => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
h.setSettings({ sandbox: false, network: "enabled", trustWorkspace: true });
|
||||
expect((await h.recorded()).filter((f) => f.event === "hostStarted").map((f) => f.args)).toEqual([
|
||||
["serve", "--sandbox-network", "proxy-only"],
|
||||
expect((await h.recorded()).find((f) => f.event === "hostStarted").args).toEqual([
|
||||
"serve",
|
||||
"--sandbox-network",
|
||||
"proxy-only",
|
||||
"--disable-sandbox",
|
||||
"--trust-workspace",
|
||||
]);
|
||||
await h.send({ type: "session.close", sessionId: "paseo-session", requestId: "close-settings" });
|
||||
});
|
||||
test.each([
|
||||
[{ sandbox: { enabled: true } }, ["--sandbox-network", "proxy-only", "--trust-workspace"]],
|
||||
[{ trustWorkspace: false }, ["--sandbox-network", "proxy-only", "--disable-sandbox"]],
|
||||
[
|
||||
{ sandbox: { network: "restricted" } },
|
||||
["--sandbox-network", "restricted", "--disable-sandbox", "--trust-workspace"],
|
||||
],
|
||||
[
|
||||
{ sandbox: { network: "enabled" } },
|
||||
["--sandbox-network", "enabled", "--disable-sandbox", "--trust-workspace"],
|
||||
],
|
||||
[
|
||||
{ sandbox: { enabled: true, network: "proxy-only" }, trustWorkspace: false },
|
||||
["--sandbox-network", "proxy-only"],
|
||||
],
|
||||
])("provider options %j control launch flags", async (params, args) => {
|
||||
const h = await harness("text-reasoning", {}, params);
|
||||
await h.open();
|
||||
expect((await h.recorded()).find((f) => f.event === "hostStarted").args).toEqual([
|
||||
"serve",
|
||||
...args,
|
||||
]);
|
||||
});
|
||||
test.each([
|
||||
[{ sandbox: { enabled: "true" } }, "sandbox.enabled"],
|
||||
[{ sandbox: { network: "invalid" } }, "sandbox.network"],
|
||||
[{ trustWorkspace: 1 }, "trustWorkspace"],
|
||||
[{ unexpected: true }, "unexpected"],
|
||||
])("invalid provider options %j fail session creation clearly", async (options, field) => {
|
||||
const h = await harness("text-reasoning", {}, options);
|
||||
const event = await h.open();
|
||||
expect(event).toMatchObject({
|
||||
type: "request.failed",
|
||||
error: { code: "invalidProviderOptions" },
|
||||
});
|
||||
if (event.type !== "request.failed") throw new Error("Expected failure");
|
||||
expect(event.error.message).toContain("Invalid Muse providerOptions");
|
||||
expect(event.error.message).toContain(field);
|
||||
expect(await h.recorded()).toEqual([]);
|
||||
});
|
||||
test("resuming a session reapplies its provider options to the new host", async () => {
|
||||
const h = await harness(
|
||||
"resume-without-cursor",
|
||||
{},
|
||||
{
|
||||
sandbox: { enabled: true, network: "restricted" },
|
||||
trustWorkspace: false,
|
||||
},
|
||||
);
|
||||
await h.open({ version: 1, data: { sessionId: "saved-session" } });
|
||||
const saved = h.events.find((e) => e.type === "session.opened");
|
||||
if (saved?.type !== "session.opened" || !saved.persistence)
|
||||
throw new Error("Expected persistence");
|
||||
await h.send({ type: "session.close", sessionId: "paseo-session", requestId: "close" });
|
||||
await h.wait((e) => e.type === "session.closed");
|
||||
const from = h.events.length;
|
||||
await h.open();
|
||||
await h.open(saved.persistence);
|
||||
await h.wait((e) => e.type === "session.ready", from);
|
||||
expect((await h.recorded()).filter((f) => f.event === "hostStarted").map((f) => f.args)).toEqual([
|
||||
["serve", "--sandbox-network", "proxy-only"],
|
||||
["serve", "--sandbox-network", "enabled", "--disable-sandbox", "--trust-workspace"],
|
||||
const frames = await h.recorded();
|
||||
expect(frames.filter((f) => f.event === "hostStarted").map((f) => f.args)).toEqual([
|
||||
["serve", "--sandbox-network", "restricted"],
|
||||
["serve", "--sandbox-network", "restricted"],
|
||||
]);
|
||||
expect(frames.some((f) => f.method === "session/resume")).toBe(true);
|
||||
});
|
||||
test("sessions list filters workspace and imported persistence opens via resume", async () => {
|
||||
const h = await harness("resume-without-cursor");
|
||||
@@ -1203,8 +1248,7 @@ test("default workflow children render structured tool details from the real no-
|
||||
|
||||
for (const source of ["user", "project"] as const) {
|
||||
test(`real ${source} skill catalog and command replay with explicit workspace trust`, async () => {
|
||||
const h = await harness(`phase3-${source}-skill`);
|
||||
h.setSettings({ trustWorkspace: source === "project" });
|
||||
const h = await harness(`phase3-${source}-skill`, {}, { trustWorkspace: source === "project" });
|
||||
await h.open();
|
||||
expect(await h.wait((e) => e.type === "session.commands")).toMatchObject({
|
||||
commands: expect.arrayContaining([
|
||||
|
||||
+37
-10
@@ -27,20 +27,47 @@ In Paseo, select **Muse Code**, then choose a model, approval mode, and reasonin
|
||||
Paseo runs `muse serve` and communicates over the Muse Session Protocol (MSP). Your Muse
|
||||
configuration determines the available models.
|
||||
|
||||
## Plugin settings
|
||||
## Provider options
|
||||
|
||||
Open Command Center and select **Configure Muse Code**. Changes apply when an agent's
|
||||
Muse host next opens, including a refresh of an existing agent.
|
||||
Paseo launches Muse with the sandbox disabled (`--disable-sandbox`) and workspace trust
|
||||
on (`--trust-workspace`). Muse can install packages using your npm cache and load project
|
||||
rules, skills, and configuration.
|
||||
|
||||
| Setting | Default | Effect |
|
||||
| --------------- | ------------ | ----------------------------------------------------------------------------------------------- |
|
||||
| Sandbox | On | Keeps Muse's filesystem and network sandbox enabled. Turning it off allows full network access. |
|
||||
| Network | `proxy-only` | Muse's sandbox network policy: `proxy-only`, `restricted`, or `enabled`. |
|
||||
| Trust workspace | Off | Lets Muse load project-scoped skills and configuration. |
|
||||
Set defaults for every Muse agent in `config.json` on the daemon machine under
|
||||
`agents.providers.muse.options`:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"providers": {
|
||||
"muse": {
|
||||
"options": {
|
||||
"sandbox": { "enabled": true, "network": "proxy-only" },
|
||||
"trustWorkspace": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also supply per-agent `providerOptions` when creating an agent. The daemon merges
|
||||
provider defaults with the per-agent options and delivers the effective `providerOptions`
|
||||
to Muse. See [provider options](/docs/sdk/provider-options) for configuration and creation.
|
||||
|
||||
Per-agent options are saved with the agent and reapplied when its Muse host opens on refresh
|
||||
or resume. Invalid values and unknown keys fail session creation with a `providerOptions`
|
||||
error.
|
||||
|
||||
| Option | Default | Effect |
|
||||
| ----------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `sandbox.enabled` | `false` | Enables Muse's filesystem and network sandbox. Its read-only home directory prevents npm from writing the default cache. |
|
||||
| `sandbox.network` | `"proxy-only"` | Sandbox network policy: `"proxy-only"`, `"restricted"`, or `"enabled"`. With sandbox disabled, Muse allows full network access. |
|
||||
| `trustWorkspace` | `true` | Lets Muse load project rules, skills, and configuration. |
|
||||
|
||||
Approval modes control tool decisions separately: **Default**, **Ask**, **Strict**, and
|
||||
**Full access**. Full access automatically allows escalated approval stages; it keeps the
|
||||
sandbox setting you chose. Skills appear in the slash-command menu, alongside `/compact`.
|
||||
**Full access**. Full access automatically allows escalated approval stages; it preserves
|
||||
the effective provider options. Skills appear in the slash-command menu, alongside `/compact`.
|
||||
|
||||
## Limitations in Muse 1.4.1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user