Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
22bbef4
🤖 feat: mode-scoped AI defaults + per-mode overrides
ThomasK33 Dec 22, 2025
5869afb
🤖 fix: preserve existing model/thinking in mode sync
ThomasK33 Dec 22, 2025
e96b171
🤖 tests: add Settings Modes story for Chromatic
ThomasK33 Dec 22, 2025
58602a8
fix: apply mode defaults in creation chat input
ThomasK33 Dec 22, 2025
fa842dc
feat: user-defined agents
ThomasK33 Dec 23, 2025
56cb289
fix: stabilize bun tests for timers and module mocks
ThomasK33 Dec 23, 2025
a5a2d6d
fix: make RefreshController tests bun-compatible
ThomasK33 Dec 23, 2025
b4f316f
tests: update RefreshController tests for debounced API
ThomasK33 Dec 23, 2025
9953779
tests: cover agent scoping + tool policy resolution
ThomasK33 Dec 23, 2025
47c23c6
docs: document agent definitions and Agent-scoped instructions
ThomasK33 Dec 23, 2025
7e4bcea
tests: fix lint and formatting
ThomasK33 Dec 24, 2025
35755f4
fix: make mux api runnable in ESM bundle
ThomasK33 Dec 24, 2025
c97d33c
tests: stabilize storybook and e2e harness
ThomasK33 Dec 24, 2025
d1b965f
tests: stabilize OpenAI image integration model
ThomasK33 Dec 24, 2025
1f7cc4a
fix: respect MUX_ROOT when expanding ~/.mux paths
ThomasK33 Dec 24, 2025
30e47cd
tests: fix e2e agent selector + isolate mux root
ThomasK33 Dec 24, 2025
f197ecc
tests: fix refresh + background termination timing
ThomasK33 Dec 27, 2025
81d4afc
🤖 feat: mux-native agent picker
ThomasK33 Dec 28, 2025
8451fad
🤖 tests: update e2e setMode for AgentModePicker
ThomasK33 Dec 28, 2025
a1be790
🤖 tests: handle spaces in macOS MUX_ROOT parsing
ThomasK33 Dec 28, 2025
21e4aaf
feat: agent definition permissions + ui defaults
ThomasK33 Dec 29, 2025
299b0fb
feat: add Ask example agent
ThomasK33 Dec 29, 2025
f311a91
🤖 feat: cycle agent hotkey + show tool allowlist tooltips
ThomasK33 Dec 29, 2025
45d7fa2
🤖 feat: tooltip base policy in agent defaults
ThomasK33 Dec 29, 2025
c94582d
🤖 feat: add potato example agent
ThomasK33 Dec 29, 2025
b093a5b
🤖 feat: show agent source path + sub-agent label
ThomasK33 Dec 29, 2025
9f50d76
🤖 fix: close agent picker on ArrowUp + simplify labels
ThomasK33 Dec 29, 2025
ec0b848
🤖 fix: show non-selectable active agent in picker
ThomasK33 Dec 29, 2025
97c7e1f
🤖 fix: avoid duplicating UI agents in Tasks settings
ThomasK33 Dec 29, 2025
1e37afd
🤖 feat: label UI agents runnable as sub-agents
ThomasK33 Dec 29, 2025
021138a
🤖 fix: allow custom subagent_type + stabilize explore label
ThomasK33 Dec 29, 2025
ec615f9
🤖 fix: type Start Here sourceMode as AgentMode
ThomasK33 Dec 29, 2025
0631d8e
🤖 fix: always enable agent_report for subagents
ThomasK33 Dec 29, 2025
557e9e3
🤖 fix: apply per-agent AI defaults for main agent
ThomasK33 Dec 29, 2025
1697f81
🤖 fix: avoid clobbering mode AI overrides for custom agents
ThomasK33 Dec 29, 2025
b09b59a
🤖 fix: skip persisting base mode AI settings for custom agents
ThomasK33 Dec 29, 2025
29bf94e
🤖 chore: remove potato agent
ThomasK33 Dec 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .mux/agents/ask.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
name: Ask
description: Delegate questions to Explore sub-agents and synthesize an answer.
color: "#6b5bff"

# Safe-by-default: omit permissionMode => no tools.
# This agent needs task delegation, but should remain read-only otherwise.
permissionMode: readOnly
tools:
- task
- task_.*

subagent:
runnable: false

policy:
base: exec
---

You are **Ask**.

Your job is to answer the user's question by delegating research to sub-agents (typically **Explore**), then synthesizing a concise, actionable response.

## When to delegate
- Delegate when the question requires repository exploration, multiple viewpoints, or verification.
- If the answer is obvious and does not require looking anything up, answer directly.

## Delegation workflow
1. Break the question into **1–3** focused research threads.
2. Spawn Explore sub-agents in parallel using the `task` tool:
- `subagent_type: "explore"`
- Use clear titles like `"Ask: find callsites"`, `"Ask: summarize behavior"`, etc.
- Ask for concrete outputs: file paths, symbols, commands to reproduce, and short excerpts.
3. Wait for results (use `task_await` if you launched tasks in the background).
4. Synthesize:
- Provide the final answer first.
- Then include supporting details (paths, commands, edge cases).

## Safety rules
- Do **not** modify repository files.
- Prefer `subagent_type: "explore"`. Only use `"exec"` if the user explicitly asks to implement changes.
145 changes: 142 additions & 3 deletions .storybook/mocks/orpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Creates a client that matches the AppRouter interface with configurable mock data.
*/
import type { APIClient } from "@/browser/contexts/API";
import type { AgentDefinitionDescriptor, AgentDefinitionPackage } from "@/common/types/agentDefinition";
import type { FrontendWorkspaceMetadata } from "@/common/types/workspace";
import type { ProjectConfig } from "@/node/config";
import type {
Expand All @@ -21,6 +22,11 @@ import {
type SubagentAiDefaults,
type TaskSettings,
} from "@/common/types/tasks";
import {
normalizeModeAiDefaults,
type ModeAiDefaults,
} from "@/common/types/modeAiDefaults";
import { normalizeAgentAiDefaults, type AgentAiDefaults } from "@/common/types/agentAiDefaults";
import { createAsyncMessageQueue } from "@/common/utils/asyncMessageQueue";
import { isWorkspaceArchived } from "@/common/utils/archive";

Expand Down Expand Up @@ -57,6 +63,12 @@ export interface MockORPCClientOptions {
workspaces?: FrontendWorkspaceMetadata[];
/** Initial task settings for config.getConfig (e.g., Settings → Tasks section) */
taskSettings?: Partial<TaskSettings>;
/** Initial mode AI defaults for config.getConfig (e.g., Settings → Modes section) */
modeAiDefaults?: ModeAiDefaults;
/** Initial unified AI defaults for agents (plan/exec/compact + subagents) */
agentAiDefaults?: AgentAiDefaults;
/** Agent definitions to expose via agents.list */
agentDefinitions?: AgentDefinitionDescriptor[];
/** Initial per-subagent AI defaults for config.getConfig (e.g., Settings → Tasks section) */
subagentAiDefaults?: SubagentAiDefaults;
/** Per-workspace chat callback. Return messages to emit, or use the callback for streaming. */
Expand Down Expand Up @@ -140,7 +152,10 @@ export function createMockORPCClient(options: MockORPCClientOptions = {}): APICl
mcpOverrides = new Map(),
mcpTestResults = new Map(),
taskSettings: initialTaskSettings,
modeAiDefaults: initialModeAiDefaults,
subagentAiDefaults: initialSubagentAiDefaults,
agentAiDefaults: initialAgentAiDefaults,
agentDefinitions: initialAgentDefinitions,
} = options;

// Feature flags
Expand All @@ -158,8 +173,78 @@ export function createMockORPCClient(options: MockORPCClientOptions = {}): APICl
};

const workspaceMap = new Map(workspaces.map((w) => [w.id, w]));

const agentDefinitions: AgentDefinitionDescriptor[] =
initialAgentDefinitions ??
([
{
id: "plan",
scope: "built-in",
name: "Plan",
description: "Create a plan before coding",
uiSelectable: true,
subagentRunnable: false,
policyBase: "plan",
},
{
id: "exec",
scope: "built-in",
name: "Exec",
description: "Implement changes in the repository",
uiSelectable: true,
subagentRunnable: true,
policyBase: "exec",
},
{
id: "compact",
scope: "built-in",
name: "Compact",
description: "History compaction (internal)",
uiSelectable: false,
subagentRunnable: false,
policyBase: "compact",
},
{
id: "explore",
scope: "built-in",
name: "Explore",
description: "Read-only repository exploration",
uiSelectable: false,
subagentRunnable: true,
policyBase: "exec",
},
] satisfies AgentDefinitionDescriptor[]);

let taskSettings = normalizeTaskSettings(initialTaskSettings ?? DEFAULT_TASK_SETTINGS);
let subagentAiDefaults = normalizeSubagentAiDefaults(initialSubagentAiDefaults ?? {});

let agentAiDefaults = normalizeAgentAiDefaults(
initialAgentAiDefaults ??
({
...(initialSubagentAiDefaults ?? {}),
...(initialModeAiDefaults ?? {}),
} as const)
);

const deriveModeAiDefaults = () =>
normalizeModeAiDefaults({
plan: agentAiDefaults.plan,
exec: agentAiDefaults.exec,
compact: agentAiDefaults.compact,
});

const deriveSubagentAiDefaults = () => {
const raw: Record<string, unknown> = {};
for (const [agentId, entry] of Object.entries(agentAiDefaults)) {
if (agentId === "plan" || agentId === "exec" || agentId === "compact") {
continue;
}
raw[agentId] = entry;
}
return normalizeSubagentAiDefaults(raw);
};

let modeAiDefaults = deriveModeAiDefaults();
let subagentAiDefaults = deriveSubagentAiDefaults();

const mockStats: ChatStats = {
consumers: [],
Expand Down Expand Up @@ -193,15 +278,69 @@ export function createMockORPCClient(options: MockORPCClientOptions = {}): APICl
setSshHost: async () => undefined,
},
config: {
getConfig: async () => ({ taskSettings, subagentAiDefaults }),
saveConfig: async (input: { taskSettings: unknown; subagentAiDefaults?: unknown }) => {
getConfig: async () => ({ taskSettings, agentAiDefaults, subagentAiDefaults, modeAiDefaults }),
saveConfig: async (input: {
taskSettings: unknown;
agentAiDefaults?: unknown;
subagentAiDefaults?: unknown;
}) => {
taskSettings = normalizeTaskSettings(input.taskSettings);

if (input.agentAiDefaults !== undefined) {
agentAiDefaults = normalizeAgentAiDefaults(input.agentAiDefaults);
modeAiDefaults = deriveModeAiDefaults();
subagentAiDefaults = deriveSubagentAiDefaults();
}

if (input.subagentAiDefaults !== undefined) {
subagentAiDefaults = normalizeSubagentAiDefaults(input.subagentAiDefaults);

const nextAgentAiDefaults: Record<string, unknown> = { ...agentAiDefaults };
for (const [agentType, entry] of Object.entries(subagentAiDefaults)) {
nextAgentAiDefaults[agentType] = entry;
}

agentAiDefaults = normalizeAgentAiDefaults(nextAgentAiDefaults);
modeAiDefaults = deriveModeAiDefaults();
}

return undefined;
},
updateAgentAiDefaults: async (input: { agentAiDefaults: unknown }) => {
agentAiDefaults = normalizeAgentAiDefaults(input.agentAiDefaults);
modeAiDefaults = deriveModeAiDefaults();
subagentAiDefaults = deriveSubagentAiDefaults();
return undefined;
},
updateModeAiDefaults: async (input: { modeAiDefaults: unknown }) => {
modeAiDefaults = normalizeModeAiDefaults(input.modeAiDefaults);
agentAiDefaults = normalizeAgentAiDefaults({ ...agentAiDefaults, ...modeAiDefaults });
modeAiDefaults = deriveModeAiDefaults();
subagentAiDefaults = deriveSubagentAiDefaults();
return undefined;
},
},
agents: {
list: async (_input: { workspaceId: string }) => agentDefinitions,
get: async (input: { workspaceId: string; agentId: string }) => {
const descriptor =
agentDefinitions.find((agent) => agent.id === input.agentId) ?? agentDefinitions[0];

return {
id: descriptor.id,
scope: descriptor.scope,
frontmatter: {
name: descriptor.name,
description: descriptor.description,
ui: { selectable: descriptor.uiSelectable },
subagent: { runnable: descriptor.subagentRunnable },
ai: descriptor.aiDefaults,
policy: { base: descriptor.policyBase, tools: descriptor.toolFilter },
},
body: "",
} satisfies AgentDefinitionPackage;
},
},
providers: {
list: async () => providersList,
getConfig: async () => providersConfig,
Expand Down
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,13 @@ dev: node_modules/.installed build-main ## Start development server (Vite + node
# https://github.com/oven-sh/bun/issues/18275
@MUX_DISABLE_TELEMETRY=$(if $(MUX_ENABLE_TELEMETRY_IN_DEV),,$(or $(MUX_DISABLE_TELEMETRY),1)) NODE_OPTIONS="--max-old-space-size=4096" npm x concurrently -k --raw \
"bun x nodemon --watch src --watch tsconfig.main.json --watch tsconfig.json --ext ts,tsx,json --ignore dist --ignore node_modules --exec node scripts/build-main-watch.js" \
"npx esbuild src/cli/api.ts --bundle --format=esm --platform=node --target=node20 --outfile=dist/cli/api.mjs --external:zod --external:commander --external:@trpc/server --watch" \
"npx esbuild src/cli/api.ts --bundle --format=esm --platform=node --target=node20 --outfile=dist/cli/api.mjs --external:zod --external:commander --external:@trpc/server --banner:js='import { createRequire } from \"module\"; const require = createRequire(import.meta.url);' --watch" \
"vite"
else
dev: node_modules/.installed build-main build-preload ## Start development server (Vite + tsgo watcher for 10x faster type checking)
@MUX_DISABLE_TELEMETRY=$(if $(MUX_ENABLE_TELEMETRY_IN_DEV),,$(or $(MUX_DISABLE_TELEMETRY),1)) bun x concurrently -k \
"bun x concurrently \"$(TSGO) -w -p tsconfig.main.json\" \"bun x tsc-alias -w -p tsconfig.main.json\"" \
"bun x esbuild src/cli/api.ts --bundle --format=esm --platform=node --target=node20 --outfile=dist/cli/api.mjs --external:zod --external:commander --external:@trpc/server --watch" \
"bun x esbuild src/cli/api.ts --bundle --format=esm --platform=node --target=node20 --outfile=dist/cli/api.mjs --external:zod --external:commander --external:@trpc/server --banner:js='import { createRequire } from \"module\"; const require = createRequire(import.meta.url);' --watch" \
"vite"
endif

Expand All @@ -159,7 +159,7 @@ dev-server: node_modules/.installed build-main ## Start server mode with hot rel
@# On Windows, use npm run because bunx doesn't correctly pass arguments
@MUX_DISABLE_TELEMETRY=$(if $(MUX_ENABLE_TELEMETRY_IN_DEV),,$(or $(MUX_DISABLE_TELEMETRY),1)) npmx concurrently -k \
"npmx nodemon --watch src --watch tsconfig.main.json --watch tsconfig.json --ext ts,tsx,json --ignore dist --ignore node_modules --exec node scripts/build-main-watch.js" \
"npx esbuild src/cli/api.ts --bundle --format=esm --platform=node --target=node20 --outfile=dist/cli/api.mjs --external:zod --external:commander --external:@trpc/server --watch" \
"npx esbuild src/cli/api.ts --bundle --format=esm --platform=node --target=node20 --outfile=dist/cli/api.mjs --external:zod --external:commander --external:@trpc/server --banner:js='import { createRequire } from \"module\"; const require = createRequire(import.meta.url);' --watch" \
"npmx nodemon --watch dist/cli/index.js --watch dist/cli/server.js --delay 500ms --exec \"node dist/cli/index.js server --host $(or $(BACKEND_HOST),localhost) --port $(or $(BACKEND_PORT),3000)\"" \
"$(SHELL) -lc \"MUX_VITE_HOST=$(or $(VITE_HOST),127.0.0.1) MUX_VITE_PORT=$(or $(VITE_PORT),5173) VITE_BACKEND_URL=http://$(or $(BACKEND_HOST),localhost):$(or $(BACKEND_PORT),3000) vite\""
else
Expand All @@ -171,7 +171,7 @@ dev-server: node_modules/.installed build-main ## Start server mode with hot rel
@echo "For remote access: make dev-server VITE_HOST=0.0.0.0 BACKEND_HOST=0.0.0.0"
@MUX_DISABLE_TELEMETRY=$(if $(MUX_ENABLE_TELEMETRY_IN_DEV),,$(or $(MUX_DISABLE_TELEMETRY),1)) bun x concurrently -k \
"bun x concurrently \"$(TSGO) -w -p tsconfig.main.json\" \"bun x tsc-alias -w -p tsconfig.main.json\"" \
"bun x esbuild src/cli/api.ts --bundle --format=esm --platform=node --target=node20 --outfile=dist/cli/api.mjs --external:zod --external:commander --external:@trpc/server --watch" \
"bun x esbuild src/cli/api.ts --bundle --format=esm --platform=node --target=node20 --outfile=dist/cli/api.mjs --external:zod --external:commander --external:@trpc/server --banner:js='import { createRequire } from \"module\"; const require = createRequire(import.meta.url);' --watch" \
"bun x nodemon --watch dist/cli/index.js --watch dist/cli/server.js --delay 500ms --exec 'NODE_ENV=development node dist/cli/index.js server --host $(or $(BACKEND_HOST),localhost) --port $(or $(BACKEND_PORT),3000)'" \
"MUX_VITE_HOST=$(or $(VITE_HOST),127.0.0.1) MUX_VITE_PORT=$(or $(VITE_PORT),5173) VITE_BACKEND_URL=http://$(or $(BACKEND_HOST),localhost):$(or $(BACKEND_PORT),3000) vite"
endif
Expand Down Expand Up @@ -200,6 +200,7 @@ dist/cli/api.mjs: src/cli/api.ts src/cli/proxifyOrpc.ts $(TS_SOURCES)
--platform=node \
--target=node20 \
--outfile=dist/cli/api.mjs \
--banner:js='import { createRequire } from "module"; const require = createRequire(import.meta.url);' \
--external:zod \
--external:commander \
--external:@trpc/server
Expand Down
Loading
Loading